<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ full stack - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Descubre miles de cursos de programación escritos por expertos. Aprende Desarrollo Web, Ciencia de Datos, DevOps, Seguridad y obtén asesoramiento profesional para desarrolladores. ]]>
        </description>
        <link>https://www.freecodecamp.org/espanol/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ full stack - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/espanol/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 14 May 2026 19:58:39 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/espanol/news/tag/full-stack/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Cómo construir una aplicación CRUD usando React y Convex ]]>
                </title>
                <description>
                    <![CDATA[ Las operaciones CRUD son la base de cada aplicación, por lo que es esencial volverse competente en esto cuando se aprende nuevas tecnologías. En este tutorial, aprenderás cómo construir una aplicación CRUD usando React y Convex. Cubriremos estas operaciones al construir un proyecto que se llama Book Collections. En este ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-construir-una-aplicacion-crud-usando-react-y-convex/</link>
                <guid isPermaLink="false">68102fd4c8db8f04fae91582</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ convex ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Fri, 27 Jun 2025 01:18:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/04/Captura-desde-2025-04-28-22-49-09.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Las operaciones CRUD son la base de cada aplicación, por lo que es esencial volverse competente en esto cuando se aprende nuevas tecnologías.</p><p>En este tutorial, aprenderás cómo construir una aplicación CRUD usando React y Convex. Cubriremos estas operaciones al construir un proyecto que se llama Book Collections. En este proyecto, los usuarios serán capaz de agregar libros y actualizar sus estados una vez que completan un libro.</p><h2 id="tabla-de-contenido"><strong><strong>Tabl</strong>a<strong> </strong>de<strong> </strong>Contenido</strong></h2><!--kg-card-begin: markdown--><ul>
<li><a href="#que-es-convex">¿Qué es Convex?</a></li>
<li><a href="#como-configurar-proyecto">¿Cómo configurar tu proyecto?</a></li>
<li><a href="#como-crear-esquema">¿Cómo crear el esquema?</a></li>
<li><a href="#como-crear-ui">¿Cómo crear la UI?</a></li>
<li><a href="#como-crear-funciones-crud">¿Cómo crear las funciones CRUD?</a></li>
<li><a href="#estilos">Los estilos</a></li>
<li><a href="#resumen">Resumen</a></li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><h2 id="que-es-convex">¿Qué es Convex?</h2><!--kg-card-end: html--><p>Convex es la Plataforma BaaS que simplifica el desarrollo backend. Convex viene con una base de datos de tiempo real, y no tienes que preocuparte sobre escribir lógica de parte del servidor de forma separada porque provee métodos para solicitar y mutar la base de datos.</p><h3 id="pre-requisitos"><strong><strong>Pre</strong>-requisitos</strong></h3><p>Para seguir este tutorial, debes conocer los fundamentos de React. Estaré usando TypeScript en este proyecto, pero es opcional, así que puedes seguirme con JavaScript.</p><!--kg-card-begin: html--><h2 id="como-configurar-proyecto">Cómo configurar tu proyecto</h2><!--kg-card-end: html--><p>Crea una carpeta separa para el proyecto y llámalo como gustes – yo lo llamaré <strong>Books</strong>. Configuraremos Convex y React en esa carpeta.</p><p>Puedes crear una aplicación de React usando este comando:</p><pre><code class="language-bash">npm create vite@latest my-app -- --template react-ts
</code></pre><p>Si quieres trabajar con JavaScript, entonces quita el `ts` al final. Sería:</p><pre><code class="language-bash">npm create vite@latest my-app -- --template react
</code></pre><h3 id="c-mo-configurar-convex"><strong>Cómo configurar<strong> Convex</strong></strong></h3><p>Tenemos que instalar Convex en la misma carpeta. Puedes hacer eso usando este comando:</p><pre><code class="language-bash">npm install convex
</code></pre><p>Luego, ejecuta <code>npx convex dev</code>. Si lo estás haciendo por primera vez, te debería pedirte la autenticación. De otra forma, debería preguntarte por el nombre del proyecto.</p><p>Puedes visitar el <a href="https://www.convex.dev/">panel de Convex</a> para ver el proyecto que has creado.</p><p>Ahora que hemos configurado Convex y React, necesitamos conectar el backend de Convex a la aplicación de React.</p><p>En el <strong><code>src/main.tsx</code>, </strong>envuelve tu componente <code>App</code> con <code>ConvexReactClient</code>:</p><pre><code class="language-tsx">import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import "./index.css"

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

createRoot(document.getElementById("root")!).render(
  &lt;ConvexProvider client={convex}&gt;
    &lt;App /&gt;
  &lt;/ConvexProvider&gt;
);
</code></pre><p>Cuando configuras Convex, se crea un archivo <code>.env.local</code>. Puedes ver el URL de tu backend en ese archivo.</p><p>En la línea de abajo, instanciamos el Cliente de Convex de React con el URL.</p><pre><code class="language-typescript">const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
</code></pre><!--kg-card-begin: html--><h2 id="como-crear-esquema">Cómo crear el Esquema</h2><!--kg-card-end: html--><p>En tu carpeta principal del proyecto, deberías de ver la carpeta <strong>convex</strong>. Manejaremos las solicitudes de la base de datos y las mutaciones aquí.</p><p>Crea un archivo <strong>schema.ts</strong> en la carpeta <strong>convex:</strong></p><pre><code class="language-typescript">import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  books: defineTable({
    title: v.string(),
    author: v.string(),
    isCompleted: v.boolean(),
  }),
});
</code></pre><p>Puedes definir un Esquema para tu documento con <code>defineSchema</code> y crea un tabla con <code>defineTable</code>. Convex provee estas funciones para definir un esquema y crear una tabla.</p><p><code>v</code> es el validador de tipo, se usa para proveer tipos para cada dato que agregamos a la tabla.</p><p>Para este proyecto, ya que es una aplicación de colección de libros, la estructura tendrá <code>title</code>, <code>author</code>, y <code>isCompleted</code>. Puedes agregar mas campos.</p><p>Ahora que tienes definido tu esquema, configuremos la UI básica en React.</p><!--kg-card-begin: html--><h2 id="como-crear-ui">Cómo crear la UI</h2><!--kg-card-end: html--><p>En la carpeta <strong>src</strong>, crea una carpeta llamada <strong>component</strong> y un archivo <strong>Home.tsx</strong>. Aquí, puedes definir la UI.</p><pre><code class="language-tsx">import { useState } from "react";
import "../styles/home.css";

const Home = () =&gt; {
  const [title, setTitle] = useState("");
  const [author, setAuthor] = useState("");
  return (
    &lt;div className="main-container"&gt;
      &lt;h1&gt;Book Collections&lt;/h1&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type="text"
          name="title"
          value={title}
          onChange={(e) =&gt; setTitle(e.target.value)}
          placeholder="book title"
        /&gt;
        &lt;br /&gt;
        &lt;input
          type="text"
          name="author"
          value={author}
          onChange={(e) =&gt; setAuthor(e.target.value)}
          placeholder="book author"
        /&gt;
        &lt;br /&gt;
        &lt;input type="submit" /&gt;
      &lt;/form&gt;
      {books ? &lt;Books books={books} /&gt; : "Loading..."}
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Puedes crear tu componente como gustes. Agregué dos campos input <code>title</code>, <code>author</code>, y un botón <code>submit</code>. Esta es la estructura básica. Ahora podemos crear los métodos CRUD en el backend.</p><!--kg-card-begin: html--><h2 id="como-crear-funciones-crud">Cómo crear las funciones CRUD</h2><!--kg-card-end: html--><p>En la carpeta <strong>convex</strong>, puedes crear un archivo <strong>queries.ts</strong> separado para las funciones CRUD.</p><h3 id="la-funci-n-create"><strong>La<strong> </strong>Función Create</strong></h3><p>Ie <strong><strong>convex/queries.ts</strong></strong>:</p><p>Puedes definir una función <code>createBooks</code>. Puedes usar la función <code>mutation</code> de Convex para crear, actualizar, y eliminar datos. Leer los datos será a través de <code>query</code>.</p><p>La función <code>mutation</code> espera estos argumentos:</p><ul><li><code>agrs</code>: los datos que necesitamos almacenar en la base de datos.</li><li><code>handler</code>: maneja la lógica para almacenar los datos en la base de datos. El <code>handler</code> es una función asíncrona, y tiene dos argumentos: <code>ctx</code> y <code>args</code>. Aquí, <code>ctx</code> es el objeto contexto que usaremos para manejar las operaciones de la base de datos.</li></ul><p>Usarás el método <code>insert</code> para ingresar nuevos datos. El primer parámetro en el <code>insert</code> es el nombre de la tabla y el segundo es para los datos que necesitan ser insertados.</p><p>Como último, puedes regresar los datos desde la base de datos.</p><p>Aquí estás el código:</p><pre><code class="language-typescript">import { mutation} from "./_generated/server";
import { v } from "convex/values";

export const createBooks = mutation({
  args: { title: v.string(), author: v.string() },
  handler: async (ctx, args) =&gt; {
    const newBookId = await ctx.db.insert("books", {
      title: args.title,
      author: args.author,
      isCompleted: false,
    });
    return newBookId;
  },
});
</code></pre><h3 id="la-funci-n-read"><strong>La Función <strong>Read</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><pre><code class="language-typescript">import { query } from "./_generated/server";
import { v } from "convex/values";

//read
export const getBooks = query({
  args: {},
  handler: async (ctx) =&gt; {
    return await ctx.db.query("books").collect();
  },
});
</code></pre><p>En esta operación read, usamos la función incorporada <code>query</code> de Convex. Aquí, <code>args</code> estará vacía ya que no obtenemos ningún datos del usuario. De forma similar, la función <code>handler</code> es asíncrona y usa el objeto <code>ctx</code> para solicitar de la base de datos y regresar los datos.</p><h3 id="la-funci-n-update"><strong>La Función <strong>Update</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><p>Crea una función <code>updateStatus</code>. Vamos a actualizar solamente el estado <code>isCompleted</code>.</p><p>Aquí, necesitas obtener el ID del documento y el estado del usuario. En el <code>args</code>, definiremos el <code>id</code> y el <code>isCompleted</code>, los cuales vendrán del usuario.</p><p>En el <code>handler</code>, usaremos el método <code>patch</code> para actualizar los datos. El método <code>patch</code> espera dos argumentos: el primer argumento es el <code>id</code> del documento y el segundo es para los datos actualizados.</p><pre><code class="language-typescript">import { mutation } from "./_generated/server";
import { v } from "convex/values";

//update
export const updateStatus = mutation({
  args: { id: v.id("books"), isCompleted: v.boolean() },
  handler: async (ctx, args) =&gt; {
    const { id } = args;
    await ctx.db.patch(id, { isCompleted: args.isCompleted });
    return "updated"
  },
});
</code></pre><h3 id="funci-n-delete"><strong>Función <strong>Delete</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><p>Crea una función <code>deleteBooks</code> y usa la función <code>mutation</code>. Necesitaremos el ID del documento para que se elimine. En el <code>args</code>, define un ID. En el <code>handler</code>, usa el método <code>delete</code> del objeto <code>ctx</code>, y pasa el ID. Esto eliminará el documento.</p><pre><code class="language-typescript">import { mutation } from "./_generated/server";
import { v } from "convex/values";

//delete
export const deleteBooks = mutation({
  args: { id: v.id("books") },
  handler: async (ctx, args) =&gt; {
    await ctx.db.delete(args.id);
    return "deleted";
  },
});
</code></pre><p>A partir de ahora, has completado las funciones CRUD en el backend. Ahora necesitamos hacerlo funcionar en el UI. Volvamos a React.</p><h3 id="actualizar-el-ui"><strong>Actualizar<strong> </strong>el<strong> UI</strong></strong></h3><p>Ya has creado un UI básico en la aplicación de React, con algunos campos input. Vamos a actualizarlo.</p><p>En <strong><strong>src/component/Home.tsx</strong></strong>:</p><pre><code class="language-tsx">import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Books } from "./Books";
import { useState } from "react";
import "../styles/home.css";

const Home = () =&gt; {
  const [title, setTitle] = useState("");
  const [author, setAuthor] = useState("");
  const books = useQuery(api.queries.getBooks);
  const createBooks = useMutation(api.queries.createBooks);

  const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    createBooks({ title, author })
      .then(() =&gt; {
        console.log("created");
        setTitle("");
        setAuthor("");
      })
      .catch((err) =&gt; console.log(err));
  };
  return (
    &lt;div className="main-container"&gt;
      &lt;h1&gt;Book Collections&lt;/h1&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type="text"
          name="title"
          value={title}
          onChange={(e) =&gt; setTitle(e.target.value)}
          placeholder="book title"
        /&gt;
        &lt;br /&gt;
        &lt;input
          type="text"
          name="author"
          value={author}
          onChange={(e) =&gt; setAuthor(e.target.value)}
          placeholder="book author"
        /&gt;
        &lt;br /&gt;
        &lt;input type="submit" /&gt;
      &lt;/form&gt;
      {books ? &lt;Books books={books} /&gt; : "Loading..."}
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Ahora podemos usar las funciones API del backend al usar <code>api</code> de Convex. Como puedes ver, llamamos a dos funciones de la API: puedes usar <code>useQuery</code> si vas a obtener datos y <code>useMutation</code> si quieres cambiar los datos. Ahora en este archivo, que estamos haciendo, dos operaciones que son create y read.</p><p>Obtuvimos todos los datos al usar este método:</p><pre><code class="language-typescript"> const books = useQuery(api.queries.getBooks);
</code></pre><p>El arreglo de objetos serán almacenados en la variable books.</p><p>Obtuvimos la función create del backend con esta línea de código:</p><pre><code class="language-typescript">const createBooks = useMutation(api.queries.createBooks);
</code></pre><h3 id="c-mo-usar-la-funci-n-create-en-el-ui"><strong>Cómo usar la función<strong> </strong>create<strong> </strong>en el<strong> UI</strong></strong></h3><p>Usemos la función create en el UI.</p><p>Ya que los campos input están en la etiqueta <code>form</code>, usaremos el atributo <code>onSubmit</code> para manejar el envío del formulario.</p><pre><code class="language-typescript">// En el Home.tsx

const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    createBooks({ title, author })
      .then(() =&gt; {
        console.log("created");
        setTitle("");
        setAuthor("");
      })
      .catch((err) =&gt; console.log(err));
  };
</code></pre><p>Cuando haces clic en submit, dispara la función <code>handleSubmit</code>.</p><p>Usamos el <code>createBooks</code> para pasar el <code>title</code> y <code>author</code> del estado. La función del endpoint es async, por lo que podemos usar el <code>handleSubmit</code> como async o usar <code>.then</code>. Usé el método <code>.then</code> para manejar los datos asíncronos.</p><p>Puedes crear un componente separado para mostrar los datos solicitados de la base de datos. Los datos regresados están en el <strong>Home.tsx</strong>, así que pasaremos los datos al componente <strong>Book.jsx</strong> como props.</p><p>En <strong><strong>Books.tsx</strong></strong>:</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);
  const [id, setId] = useState("");

  const deleteBooks = useMutation(api.queries.deleteBooks);
  const updateStatus = useMutation(api.queries.updateStatus);

  const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };

  const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
  };

  const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>En el componente <strong>Book.jsx, </strong>puedes mostrar los datos de la base de datos y manejar la funcionalidad para actualizar y eliminar los registros.</p><p>Veamos paso a paso cada una de estas características.</p><h3 id="c-mo-mostar-los-datos"><strong>Cómo mostar<strong> </strong>los<strong> Dat</strong>os</strong></h3><p>Puedes obtener los datos pasados como un prop en el componente <code>Home.tsx</code>. Si estás usando TypeScript, he definido un tipo para el objeto que es regresado de la solicitud. Puedes ignorar esto si estás usando JavaScript.</p><p>Crea <strong><strong><code>books.types.ts</code></strong></strong>:</p><pre><code class="language-typescript">export type book = {
    _id: string,
    title: string,
    author: string,
    isCompleted: boolean
}
</code></pre><p>Puedes usar la función <code>map</code> para mostrar los datos.</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>Esta es la estructura básica. Mostramos el título, autor, y estado, junto con un botón actualizar y eliminar.</p><p>Ahora, agreguemos las funcionalidades.</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);
  const [id, setId] = useState("");

  const deleteBooks = useMutation(api.queries.deleteBooks);
  const updateStatus = useMutation(api.queries.updateStatus);

  const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };

  const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
  };

  const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>Este es todo el código del componente. Déjame explicarte qué hice.</p><p>Primero, necesitamos alternar la actualización, así que definimos la función <code>handleClick</code>, y le pasamos un ID del documento.</p><pre><code class="language-typescript">//handleClick
 const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };
</code></pre><p>En el <code>handleClick</code> puedes actualizar el estado del ID y alternar el estado de la actualización de forma que alternará la actualización cuando se haga clic, y en otro clic, se cerrará.</p><p>Luego, tenemos <code>handleUpdate</code>. Necesitamos el ID del documento para actualizar los datos, así que pasamos el objeto evento así también como el ID del documento. Para obtener la entrada, podemos usar <code>FormData</code>.</p><pre><code class="language-typescript">const updateStatus = useMutation(api.queries.updateStatus);

const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };
</code></pre><p>Necesitamos usar el <code>useMutation</code> para obtener la función <code>updateStatus</code>. Pasa el ID y el estado completado a la función, y maneja la parte asíncrona usando <code>.then</code>.</p><p>Para la función delete, el ID del documento es suficiente. Como el anterior, llama la función delete usando el <code>useMutation</code> y pásale el ID.</p><p>Luego pasa el ID del documento y maneja la promesa.</p><pre><code class="language-typescript">const deleteBooks = useMutation(api.queries.deleteBooks);

const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
 };
</code></pre><!--kg-card-begin: html--><h2 id="estilos">Estilos</h2><!--kg-card-end: html--><p>Finalmente, lo que queda es agregar algo de estilo. Agregué algunos estilos básicos. Si el libro no ha sido completado, estará en rojo, y si el libro se ha completado, estará en verde.</p><p>Aquí la captura de pantalla:</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729428111374/1d1a69ef-5d35-4410-91f4-d8cf4817991d.png" class="kg-image" alt="final output" width="851" height="601" loading="lazy"></figure><p>¡¡Esto es todo chicos!!</p><p>Puedes visitar mi repositorio para ver todo el código: <a href="https://github.com/sanjayr-12/convex-crud">convex-curd</a></p><!--kg-card-begin: html--><h2 id="resumen">Resumen</h2><!--kg-card-end: html--><p>En este artículo, implementamos las operaciones CRUD (Crear, Leer, Actualizar y Eliminar) al construir una aplicación de colecciones de libros. Comenzamos configurando Convex y React, y escribir la lógica de CRUD.</p><p>Este tutorial cubrió tanto el frontend como el backend, demostrando cómo construir una aplicación serverless.</p><p>Puedes encontrar todo el código aquí: <a href="https://github.com/sanjayr-12/convex-crud">convex-curd</a></p><p>Si hay algún error o cualquier duda, contáctame en <a href="https://www.linkedin.com/in/sanjay-r-ab6064294/">LinkedIn</a>, <a href="https://www.instagram.com/_sanjayxr_12_/">Instagram</a>.</p><p>¡Gracias por leer!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo construir una Aplicación de Chat en Tiempo Real con React, Node, Socket.io, y HarperDB ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo, estaremos usando Socket.io y HarperDB para construir una aplicación chat fullstack de tiempo real con salas de chat. Este será un gran proyecto para aprender cómo armar las aplicaciones fullstack, y cómo crear una aplicación donde el back-end puede comunicarse con el front-end en tiempo real. Normalmente, ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-construir-una-aplicacion-de-chat-en-tiempo-real-con-react-node-socket-io-y-harperdb/</link>
                <guid isPermaLink="false">675af63196d716043ef3d8e3</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Socket.io ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HarperDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Fri, 24 Jan 2025 16:28:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/12/pexels-keira-burton-6146929.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>En este artículo, estaremos usando Socket.io y HarperDB para construir una aplicación chat fullstack de tiempo real con salas de chat.</p><p>Este será un gran proyecto para aprender cómo armar las aplicaciones fullstack, y cómo crear una aplicación donde el back-end puede comunicarse con el front-end en tiempo real.</p><p>Normalmente, usando solicitudes HTTP, el servidor no puede enviar datos al cliente en tiempo real. Pero al usar Socket.io, el servidor es capaz de enviar información en tiempo real al cliente sobre algunos eventos que sucedieron en el servidor.</p><p>La aplicación que estaremos construyendo tiene dos páginas:</p><p>Una página <code>join-a-chat-room</code>:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/SyHBvbkVavSJTxNV1nOi2-V_YYXm3upFOJvAzBXwd1VNu10SKV4WBSyQS1tdf4OhiDbqlq3sLqCxWRSJafZwhfcsp72DSKEy3-hk3JvNVGcmsSgkHHpEH69pnBDVKCv6bXiMza4cC4BZiLCOiqKPAIk" class="kg-image" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="475" height="468" loading="lazy"></figure><p>Y una página <code>chat-room</code>:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/uRkjeHOuGGGf9HnK7bZ1Zd6WeNMo8kaR6Py0_RiEDx1VUuTPx4oYNvfmPlOxNLAicM7bnr9rm0oY0E7k0fwfaZIEz4K1V-5ejOM3ztmrmjIjC8OsRyzNf0HZurxMWUMzdLgic7o8oC-RQxELo8vdcVw" class="kg-image" alt="The finished chat page" width="1216" height="678" loading="lazy"></figure><p>Aquí está lo que estaremos usando para construir este aplicación:</p><ul><li><strong><strong>Front</strong>-<strong>end</strong></strong>: <a href="https://reactjs.org/docs/create-a-new-react-app.html">React</a> (Un framework de front-end de JavaScript para construir aplicaciones interactvias)</li><li><strong><strong>Back</strong>-<strong>end</strong></strong>: <a href="https://nodejs.org/en/">Node</a> y <a href="https://expressjs.com/">Express</a> (Express es un framework muy popular de NodeJS que nos permite crear fácilmente APIs y back-ends)</li><li><strong>Base de Datos</strong>: <a href="https://harperdb.io/">HarperDB</a> (una plataforma de datos + aplicación que te permite solicitar datos usando SQL o NoSQL. HarperDB también tiene una API integrada, ahorrándonos el escribir mucho código de back-end)</li><li><strong>Comunicación en Tiempo Real</strong>: <a href="https://socket.io/docs/v3/">Socket.io</a> (¡ve abajo!)</li></ul><p><a href="https://github.com/DoableDanny/Realtime-chat-app-with-rooms">Aquí está el código fuente</a> (recuerda de darle una estrella ⭐).</p><h2 id="tabla-de-contenidos"><strong>Tabla de Contenidos</strong></h2><ol><li>¿<a href="#que-es-socketio">Qué es Socket.io?</a></li><li><a href="#configuracion-proyecto">Configuración del Proyecto</a></li><li><a href="#como-construir-pagina-join-a-room">Cómo construir la Página "Join a Room"</a></li><li><a href="#como-configurar-servidor">Cómo configurar el Servidor</a></li><li><a href="#como-crear-primer-escucha-evento-socketio">Cómo crear nuestro primer Escucha de Evento de Socket.io en el Servidor</a></li><li><a href="#como-funcionan-salas-socketio">Cómo funcionan las Salas en Socket.io</a></li><li><a href="#como-construir-pagina-chat">Cómo construir la Página Chat</a></li><li><a href="#como-crear-componente-messages">Cómo crear el componente Messages (B)</a></li><li><a href="#como-crear-esquema-y-tabla-harperdb">Cómo crear un Esquema y una Tabla en HarperDB</a></li><li><a href="#como-crear-componente-send-message">Cómo crear el Componente Send Message (C)</a></li><li><a href="#como-configurar-variables-entorno-harperdb">Cómo configurar las Variables de Entorno de HarperDB</a></li><li><a href="#como-permitir-a-usuarios-envien-mensajes-con-socketio">Cómo permitir a los Usuarios que se envíen Mensajes con Socket.io</a></li><li><a href="#como-obtener-mensajes-desde-harperdb">Cómo obtener los Mensajes desde HarperDB</a></li><li><a href="#como-mostrar-ultimos-100-mensajes-en-el-cliente">Cómo mostrar los Últimos 100 Mensajes en el Client</a>e</li><li><a href="#como-mostrar-sala-y-usuarios">Cómo mostrar la Sala y los Usuarios (A)</a></li><li><a href="#como-quitar-un-usuario-de-sala-socketio">Cómo quitar a un Usuario de una Sala de Socket.io</a></li><li><a href="#como-agregar-escucha-evento-disconnect">Cómo agregar el Escucha de Evento Disconnect de Socket.io</a></li></ol><!--kg-card-begin: html--><h2 id="que-es-socketio">¿Qué es Socket.io?</h2><!--kg-card-end: html--><p>Socket.io permite al servidor enviar información al cliente en tiempo real, cuando los eventos ocurren en el servidor.</p><p>Por ejemplo, si estuvieras jugando un juego de múltiple jugador, un evento podría ser tu "amigo" que consiguió un gol espectacular en contra tuya.</p><p>Con Socket.io, sabrías (casi) instantáneamente sobre conceder un gol.</p><p>Sin Socket.io, el cliente tendría que hacer múltiples llamadas <em>polling</em> de AJAX para verificar que el evento ha ocurrido en el servidor. Por ejemplo, el cliente podría usar JavaScript para verificar un evento en el servidor cada 5 segundos.</p><p>Socket.io significa que el cliente no tiene que hacer múltiples llamadas polling de AJAX para verificar si algunos eventos han ocurrido en el servidor. Más bien, el servidor envía la información al cliente tan pronto como lo obtenga. Mucho mejor.👌</p><p>Así que, Socket.io nos permite construir fácilmente aplicaciones de tiempo real, tales comos aplicaciones de chat y juegos multijugador.</p><!--kg-card-begin: html--><h2 id="configuracion-proyecto">Configuración del Proyecto</h2><!--kg-card-end: html--><h3 id="1-c-mo-configurar-nuestras-carpetas"><strong>1. Cómo configurar nuestras carpetas</strong></h3><p>Comienza un nuevo proyecto en tu editor de texto de prefrencia (VS Code para mí), y crea dos carpetas a nivel raíz llamados client y server.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/folder-structure.JPG" class="kg-image" alt="Realtime chat app folder structure" width="600" height="400" loading="lazy"></figure><p>Crearemos nuestra aplicación React Front-end &nbsp;en la carpeta client, y nuestro backend con Node/express en la carpeta server.</p><h3 id="2-c-mo-instalar-nuestras-dependencias-de-client"><strong>2. Cómo instalar nuestras dependencias de client</strong></h3><p>Abre una terminal en la raíz del proyecto (en VS Code, puedes hacer esto al presionar Ctrl + ' o al ir a <em>terminal-&gt;new terminal</em>).</p><p>Luego, instalaremos React en nuestra carpeta client:</p><pre><code class="language-bash">$ npx create-react-app client
</code></pre><p>Después de que React se haya instalado, ve hacia la carpeta client, e instala las siguientes dependencias:</p><pre><code class="language-bash">$ cd client
$ npm i react-router-dom socket.io-client
</code></pre><p>React-router-dom nos permitirá configurar las rutas a nuestros distintos componentes de React – esencialmente creando diferentes páginas.</p><p>Socket.io-client es la versión cliente de socket.io, que nos permite "emitir" eventos al servidor. Una vez que el servidor lo recibe, podemos usar la versión del servidor de socket.io para hacer cosas como enviar mensajes a los usuarios en la misma sala como el emisor, o unirse con un usuario a una sala de socket.</p><p>Ganarás un mejor entendimiento de esto más tarde cuando lleguemos a implementar estas ideas con código.</p><h3 id="3-c-mo-iniciar-la-aplicaci-n-de-react"><strong>3. Cómo iniciar la aplicación de React </strong></h3><p>Vamos a asegurarnos que todo está funcionando bien ejecutando el siguiente comando desde la carpeta client:</p><pre><code class="language-bash">$ npm start
</code></pre><p>Webpack construirá la aplicación de React y lo servirá en `http://localhost:3000`:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/react-is-running.JPG" class="kg-image" alt="Create react app up and running on localhost" width="600" height="400" loading="lazy"></figure><p>Ahora configuremos nuestra base de datos de HarperDB que usaremos para guardar mensajes de forma permanente enviados por los usuarios.</p><h3 id="c-mo-configurar-harperdb"><strong>Cómo configurar HarperDB</strong></h3><p>Primero, crea una <a href="https://studio.harperdb.io/">cuenta con HarperDB</a>.</p><p>Luego crea una nueva instancia en la nube de HarperDB.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/harper_instance.JPG" class="kg-image" alt="create HarperDB instance" width="600" height="400" loading="lazy"></figure><p>Para hacer las cosas fáciles, selecciona la instancia cloud:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance-type.JPG" class="kg-image" alt="select HarperDB instance type" width="600" height="400" loading="lazy"></figure><p>Selecciona el proveedor del cloud (Yo escojo AWS):</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/cloud_provider.JPG" class="kg-image" alt="select HarperDB cloud provider" width="600" height="400" loading="lazy"></figure><p>Brinda un nombre a tu instancia de la nube, y crea tus credenciales de la instancia:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_credentials.JPG" class="kg-image" alt="select HarperDB instance credentials" width="600" height="400" loading="lazy"></figure><p>HarperDB tiene una capa gratuita generosa que podemos usar para este proyecto, así que selecciona ese:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_specs.JPG" class="kg-image" alt="select HarperDB instance specs" width="600" height="400" loading="lazy"></figure><p>Verifica que tus detalles son correctos, luego crea la instancia.</p><p>Tomará unos pocos minutos para crear la instancia, así que continuemos y, ¡hagamos nuestro primer componente de React!</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_loading.JPG" class="kg-image" alt="HarperDB instance loading" width="600" height="400" loading="lazy"></figure><!--kg-card-begin: html--><h2 id="como-construir-pagina-join-a-room">Cómo construir la Página "Join a Room"</h2><!--kg-card-end: html--><p>Nuestra página principal va a terminar luciendo así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page.JPG" class="kg-image" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="600" height="400" loading="lazy"></figure><p>El usuario ingresará un nombre de usuario, selecciona una sala de chat desde el despegable, luego haz clic en "Join Room". El usuario será dirigido a la página de la sala de chat.</p><p>Así que, hagamos esta página principal.</p><h3 id="1-c-mo-crear-el-formulario-html-y-agregar-estilos"><strong>1. Cómo crear el formulario HTML y agregar estilos</strong></h3><p>Crea un nuevo archivo en <code>src/pages/home/index.js</code>.</p><p>Agregaremos estilos básicos a nuestra aplicación usando módulos de CSS, así que crea un nuevo archivo: <code>src/pages/home/styles.module.css</code>.</p><p>Nuestra estructura de carpeta debería ahora lucir así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/pages-folder-structure.JPG" class="kg-image" alt="pages folder with home page component" width="600" height="400" loading="lazy"></figure><p>Ahora creemos el formulario básico HTML:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

import styles from './styles.module.css';

const Home = () =&gt; {
  return (
    &lt;div className={styles.container}&gt;
      &lt;div className={styles.formContainer}&gt;
        &lt;h1&gt;{`&lt;&gt;DevRooms&lt;/&gt;`}&lt;/h1&gt;
        &lt;input className={styles.input} placeholder='Username...' /&gt;

        &lt;select className={styles.input}&gt;
          &lt;option&gt;-- Select Room --&lt;/option&gt;
          &lt;option value='javascript'&gt;JavaScript&lt;/option&gt;
          &lt;option value='node'&gt;Node&lt;/option&gt;
          &lt;option value='express'&gt;Express&lt;/option&gt;
          &lt;option value='react'&gt;React&lt;/option&gt;
        &lt;/select&gt;

        &lt;button className='btn btn-secondary'&gt;Join Room&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Arriba, tenemos un input de texto sencillo para capturar el nombre de usuario, y un despegable seleccionable con algunas opciones por defecto para que el usuario seleccione una sala de chat para unirse.</p><p>Ahora importemos este componente en <code>App.js</code> y configuremos una ruta para el componente usando el paquete <code>react-router-dom</code>. Este será nuestra página principal, así que la ruta será <code>/</code>:</p><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/home';

function App() {
  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route path='/' element={&lt;Home /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Ahora agreguemos algunos estilos básicos para hacer que nuestra aplicación luzca mas presentable:</p><pre><code class="language-css">/* client/src/App.css */

html * {
  font-family: Arial;
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: rgb(63, 73, 204);
}
::-webkit-scrollbar {
  width: 20px;
}
::-webkit-scrollbar-track {
  background-color: transparent;
}
::-webkit-scrollbar-thumb {
  background-color: #d6dee1;
  border-radius: 20px;
  border: 6px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background-color: #a8bbbf;
}
.btn {
  padding: 14px 14px;
  border-radius: 6px;
  font-weight: bold;
  font-size: 1.1rem;
  cursor: pointer;
  border: none;
}
.btn-outline {
  color: rgb(153, 217, 234);
  border: 1px solid rgb(153, 217, 234);
  background: rgb(63, 73, 204);
}
.btn-primary {
  background: rgb(153, 217, 234);
  color: rgb(0, 24, 111);
}
.btn-secondary {
  background: rgb(0, 24, 111);
  color: #fff;
}
</code></pre><p>También agreguemos los estilos específicos a nuestro componente de la página principal:</p><pre><code class="language-css">/* client/src/pages/home/styles.module.css */

.container {
  height: 100vh;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgb(63, 73, 204);
}
.formContainer {
  width: 400px;
  margin: 0 auto 0 auto;
  padding: 32px;
  background: lightblue;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}
.input {
  width: 100%;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid rgb(63, 73, 204);
  font-size: 0.9rem;
}
.input option {
  margin-top: 20px;
}
</code></pre><p>También hagamos que el botón "Join Room" tenga el ancho completo agregando un atributo de estilo:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

&lt;button className='btn btn-secondary' style={{ width: '100%' }}&gt;Join Room&lt;/button&gt;
</code></pre><p>Nuestra página principal ahora se ve sólido:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page-html.JPG" class="kg-image" alt="Fully-styled home page" width="600" height="400" loading="lazy"></figure><h3 id="2-c-mo-agregar-funcionalidad-al-formulario-join-room"><strong>2. Cómo agregar funcionalidad al formulario Join Room</strong></h3><p>Ahora tenemos un formulario básico y estilos, así que es tiempo de agregar algo de funcionalidad.</p><p>Esto es lo que queremos que suceda cuando el usuario haga clic en el botón "Join Room":</p><ol><li>Verificar que el nombre de usuario y los campos de la sala sean rellenados.</li><li>Si es así, emitimos un evento de socket a nuestro servidor.</li><li>Redireccionar al usuario a la página Chat (el cual crearemos luego).</li></ol><p>Vamos a necesitar crear algo de estado para almacenar los valores <em>nombre de usuario</em> y <em>room</em>. También necesitamos crear una instancia del socket.</p><p>Podríamos crear estos estados directamente dentro de nuestro componente <em>home</em>, pero nuestra página <em>Chat</em> también necesitará acceder al <em>username, <em>room</em> y <em>socket</em></em>. Así que elevaremos el estado al <code>App.js</code>, donde podemos pasar estas variables a las páginas <em>HomePage</em> y <em>Chat</em>.</p><p>Así que, creemos nuestro estado y configuremos un socket en <code>App.js</code>, y pasemos estas variables como props al componente. También pasaremos la función <code>setState</code> de forma que podamos alterar el estado:</p><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { useState } from 'react'; // Add this
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import io from 'socket.io-client'; // Add this
import Home from './pages/home';

const socket = io.connect('http://localhost:4000'); // Add this -- our server will run on port 4000, so we connect to it from here

function App() {
  const [username, setUsername] = useState(''); // Add this
  const [room, setRoom] = useState(''); // Add this

  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route
            path='/'
            element={
              &lt;Home
                username={username} // Add this
                setUsername={setUsername} // Add this
                room={room} // Add this
                setRoom={setRoom} // Add this
                socket={socket} // Add this
              /&gt;
            }
          /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Ahora podemos acceder a estos props en nuestro componente <em>Home</em>. Usaremos la <a href="https://www.freecodecamp.org/espanol/news/como-usar-la-desestructuracion-de-arreglos-y-objetos-en-javascript/">destructuración</a> para obtener estas props:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

import styles from './style.module.css';

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  return (
    // ...
  );
};

export default Home;
</code></pre><p>Cuando el usuario ingrese su nombre de usuario o seleccione una sala, necesitamos actualizar las variables de estado <code>username</code> y <code>room</code><em>:</em></p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  return (
    &lt;div className={styles.container}&gt;
      // ...
        &lt;input
          className={styles.input}
          placeholder='Username...'
          onChange={(e) =&gt; setUsername(e.target.value)} // Add this
        /&gt;

        &lt;select
          className={styles.input}
          onChange={(e) =&gt; setRoom(e.target.value)} // Add this
        &gt;
         // ...
        &lt;/select&gt;

        // ...
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Ahora estamos capturamos los datos ingresados por el usuario, podemos crear una función <a href="https://www.freecodecamp.org/espanol/news/funciones-callback-en-javascript-que-son-los-callback-en-js-y-como-usarlos/">callback</a> <em>joinRoom()</em> para cuando el usuario haga clic en el botón "Join Room":</p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {

  // Add this
  const joinRoom = () =&gt; {
    if (room !== '' &amp;&amp; username !== '') {
      socket.emit('join_room', { username, room });
    }
  };

  return (
    &lt;div className={styles.container}&gt;
      // ...

        &lt;button
          className='btn btn-secondary'
          style={{ width: '100%' }}
          onClick={joinRoom} // Add this
        &gt;
          Join Room
        &lt;/button&gt;
      // ...
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Arriba, cuando el usuario haga clic en el botón, un evento del socket llamado <code>joinroom</code> se emite, juntamente con un objeto conteniendo el nombre de usuario y la sala seleccionada. Este evento será recibido por nuestro servidor un poco más tarde cuando haremos algo de magia.</p><p>Para terminar nuestra página principal, necesitamos agregar una redirección al final de nuestra función <code>joinRoom()</code> para llevar al usuario a la página <em>/chat</em>:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...
import { useNavigate } from 'react-router-dom'; // Agrega esto

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  const navigate = useNavigate(); // Agrega esto

  const joinRoom = () =&gt; {
    if (room !== '' &amp;&amp; username !== '') {
      socket.emit('join_room', { username, room });
    }

    // Redirecciona a /chat
    navigate('/chat', { replace: true }); // Agrega esto
  };

 // ...
</code></pre><p>Pruébalo: escribe un nombre de usuario y selecciona una sala, luego haz clic en <em>Join Room</em>. Deberías ser dirigido a la ruta <code>http://localhost:3000/chat</code> – actualmente una página vacía.</p><p>Pero antes que creemos nuestra Página <em>Chat</em>, tengamos algo ejecutándose en el servidor.</p><!--kg-card-begin: html--><h2 id="como-configurar-servidor">Cómo configurar el Servidor</h2><!--kg-card-end: html--><p>En el servidor, vamos a escuchar los eventos del socket que son emitidos desde el frontend. Actualmente, solamente tenemos un evento <code>join_room</code> siendo emitido desde React, así que agregaremos este escucha de evento primero.</p><p>Pero antes de eso, necesitamos instalar nuestras dependencias del servidor y tener el servidor activo y ejecutándose.</p><h3 id="1-c-mo-instalar-las-dependencias-del-servidor"><strong>1. Cómo instalar las dependencias del servidor</strong></h3><p>Abre una nueva terminal (en VS Code: <code>Terminal-&gt;Nueva Terminal</code>), cambia la carpeta a nuestra carpeta de servidor, inicializa un archivo package.json, e instala las siguientes dependencias:</p><pre><code class="language-bash">$ cd server
$ npm init -y
$ npm i axios cors express socket.io dotenv
</code></pre><ul><li><strong>Axios</strong> es un paquete usado comunmente para hacer solicitudes fácilmente a APIs.</li><li><strong>Cors</strong> permite a nuestro cliente hacer solicitudes a otros orígenes – necesario para socket.io para que funcione de forma apropiada. Ve ¿<a href="https://aws.amazon.com/es/what-is/cross-origin-resource-sharing/">Qué es el CORS?</a> si no has escuchado de CORS antes.</li><li><strong>Express</strong> es un framework de Node.js que nos permite escribir nuestro backend más fácilmente con menos código.</li><li><strong>Socket.io</strong> es una librería que permite que el cliente y el servidor se comunique a tiempo real – el cual no es posible con solicitudes estándares de HTTP.</li><li><strong>Dotenv</strong> es un módulo que nos permite almacenar claves privadas y contraseñas de forma segura, y cargarlos en nuestro códio cuando sea necesario.</li></ul><p>También instalaremos <strong>nodemon</strong> como una dependencia dev, así no tenemos que reiniciar nuestro servidor cada vez que hagamos un cambio al código – ahorrándonos tiempo y energía:</p><pre><code class="language-bash">$ npm i -D nodemon
</code></pre><h3 id="2-c-mo-iniciar-nuestro-servidor"><strong>2. Cómo iniciar nuestro servidor</strong></h3><p>Crea un archivo <code>index.js</code> en la raíz de nuestra carpeta server, y agrega el siguiente código para iniciar el servidor:</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
const http = require('http');
const cors = require('cors');

app.use(cors()); // Add cors middleware

const server = http.createServer(app);

server.listen(4000, () =&gt; 'Server is running on port 4000');
</code></pre><p>Abre el archivo <code>package.json</code> en nuestro servidor, y agrega un script que nos permitirá usar nodemon en desarrollo:</p><pre><code class="language-json">{
  ...
  "scripts": {
    "dev": "nodemon index.js"
  },
  ...
}
</code></pre><p>Ahora, iniciemos nuestro servidor al ejecutar el siguiente comando:</p><pre><code class="language-bash">$ npm run dev
</code></pre><p>Podemos verificar rápidamente que nuestro servidor se está ejecutando correctamente al agregar un manejador de solicitudes get:</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
http = require('http');
const cors = require('cors');

app.use(cors()); // Agrega cors middleware

const server = http.createServer(app);

// Agrega esto
app.get('/', (req, res) =&gt; {
  res.send('Hello world');
});

server.listen(4000, () =&gt; 'El servidor está ejecutándose en el puerto 3000');
</code></pre><p>Ahora ve a <a href="http://localhost:4000/"><code>http://localhost:4000/</code></a>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/localhost4000.JPG" class="kg-image" alt="Image" width="600" height="400" loading="lazy"></figure><p>Nuestro servidor está activo y en ejecución. ¡Ahora es tiempo de hacer cosas de Socket.io en el servidor!</p><!--kg-card-begin: html--><h2 id="como-crear-primer-escucha-evento-socketio">Cómo crear nuestro primer Escucha de Evento de Socket.io en el Servidor</h2><!--kg-card-end: html--><p>Recuerda que cuando emitimos un evento <code>joinroom</code> desde el cliente? Bueno, pronto vamos a estar escuchar ese evento en el servidor y agregar el usuario a una sala del socket.</p><p>Pero primero, necesitamos escuchar cuando un cliente se conecte al servidor a través de <code>socket.io-client</code>.</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
http = require('http');
const cors = require('cors');
const { Server } = require('socket.io'); // Agrega esto

app.use(cors()); // Agrega cors middleware

const server = http.createServer(app); // Agrega esto

// Agrega esto
// Crea un servidor io y permite CORS desde http://localhost:3000 con metodos GET y POST
const io = new Server(server, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

// Agrega esto
// Escucha cuando el cliente se conecta a traves de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Podemos escribir nuestro our socket event listeners in here...
});

server.listen(4000, () =&gt; 'El servidor está ejecutándose en el puerto 3000');
</code></pre><p>Ahora, cuando el cliente se conecta desde el frontend, el backend captura el evento de la conexión, y registrará <code>User connected</code> con el id único del socket para ese cliente particular.</p><p>Probemos si el servidor ahora está captrando el evento de la conección desde el cliente. Ve a tu aplicación de React en <code>http://localhost:3000/</code> y refresca la página.</p><p>Deberías ver el siguiente registro en tu consola de terminal del servidor:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/user-connected.JPG" class="kg-image" alt="Image" width="600" height="400" loading="lazy"></figure><p>Genial, nuestro cliente se ha conectado a nuestro servidor a través de socket.io. Nuestro cliente y servidor ahora se pueden comunicar en, ¡tiempo real!</p><!--kg-card-begin: html--><h2 id="como-funcionan-salas-socketio">Cómo funcionan las Salas en Socket.io</h2><!--kg-card-end: html--><p>Desde la documentación de <a href="https://socket.io/docs/v3/rooms/">Socket.io</a> (no está disponible en español todavía):</p><blockquote><em><em>"</em>Una<em> </em>sala<em> </em>e<em>s </em>u<em>n</em> canal<em> </em>arbitrario<em> </em>al que<em> sockets </em>se puede unir (<em><code>join</code></em>)<em> </em>y abandonar (<em><code>leave</code></em>)<em>. </em>Puede ser usado<em> </em>para emitir<em> event</em>o<em>s </em>a<em> </em>un<em> </em>subconjunto<em> </em>de<em> client</em>e<em>s."</em></em></blockquote><p>Así que, podemos unir el usuario a una sala, y luego el servidor puede enviar mensajes a todos los usuarios en esa sala – permitiendo a los usuarios enviarse mensajes en tiempo real. ¡Genial! </p><h3 id="c-mo-unir-el-usuario-a-una-sala-de-socket-io"><strong>Cómo unir el usuario a una sala de Socket.io</strong></h3><p>Una vez que el usuario se ha conectado a través de Socket.io, podemos agregar nuestros escuchas de eventos del socket en el servidor para escuchar eventos emitidos desde el cliente. También, podemos emitir eventos en el servidor, y esucharlos en el cliente.</p><p>Ahora escuchemos por el evento <code>joinroom</code>, que capture los datos (nombre de usuario y sala), y agregue al usuario a una sala de socket:</p><pre><code class="language-javascript">// server/index.js

// Escuchar cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega esto
  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {
    const { username, room } = data; // Datos enviados desde el cliente cuando el evento join_room se emite
    socket.join(room); // Une el usuario a una sala de socket
  });
});
</code></pre><h3 id="c-mo-enviar-un-mensaje-a-los-usuarios-en-una-sala"><strong>Cómo enviar un mensaje a los usuarios en una sala</strong></h3><p>Ahora enviemos un mensaje a todos los usuarios en la sala, aparte del usuario que se acaba de unir, para notificarlos que un nuevo usuario se ha unido:</p><pre><code class="language-javascript">// server/index.js

const CHAT_BOT = 'ChatBot'; // Agrega esto
// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {
    const { username, room } = data; // Datos enviados desde el cliente cuando el evento join_room se emite
    socket.join(room); // Une el usuario a una sala de socket

    // Agrega esto
    let __createdtime__ = Date.now(); // Marcas de tiempo actuales
    // Envía un mensaje a todos los usuario en la sala, aparte del usuario que se acaba de unir
    socket.to(room).emit('receive_message', {
      message: `${username} has joined the chat room`,
      username: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre><p>Arriba, estamos emitiendo un evento <code>receive_message</code> a todos los clientes en la sala en el que el usuario se acaba de unir, juntamente con algo de dato: el mensaje, el nombre de usuario quien envió el mensaje, y el tiempo en que el mensaje fue enviado.</p><p>Agregaremos un escucha de evento en nuestra aplicación de React un poco después para capturar este evento, y muestra el mensaje en la pantalla.</p><p>También enviemos un mensaje de bienvenida al nuevo usuario recién unido:</p><pre><code class="language-javascript">// server/index.js

io.on('connection', (socket) =&gt; {
  // ...

    // Agrega esto
    // Envía el msg de bienvenida al usuario que se acaba de unir
    socket.emit('receive_message', {
      message: `Welcome ${username}`,
      username: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre><p>Cuando agregamos un usuario a una sala de Socket.io, Socket.io solamente almacena los ids de los sockets para cada usuario. Pero necesitaremos los nombres de usuario de todos en la sala, así también como el nombre de sala. Así que, almacenemos esos datos en variables en el servidor:</p><pre><code class="language-javascript">// server/index.js

// ...

const CHAT_BOT = 'ChatBot';
// Agrega esto
let chatRoom = ''; // Ej. javascript, node,...
let allUsers = []; // Todos los usuarios en la sala de chat actual

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
    // ...

    // Agrega esto
    // Guarda el nuevo usuario a la sala
    chatRoom = room;
    allUsers.push({ id: socket.id, username, room });
    chatRoomUsers = allUsers.filter((user) =&gt; user.room === room);
    socket.to(room).emit('chatroom_users', chatRoomUsers);
    socket.emit('chatroom_users', chatRoomUsers);
  });
});
</code></pre><p>Arriba, también estamos enviando un arreglo de todo el chatRoomUsers de nuevo al cliente a través del evento <code>chatroomusers</code>, así que podemos listar todos los nombres de usuario en el frontend.</p><p>Antes que agreguemos cualquier otro código a nuestro servidor, volvamos a nuestro frontend y creamos la página <em>Chat</em> – así podemos probar si estamos recibiendo los eventos <code>receivemessage</code>.</p><!--kg-card-begin: html--><h2 id="como-construir-pagina-chat">Cómo construir la Página Chat</h2><!--kg-card-end: html--><p>En tu carpeta cliente, crea dos nuevos archivos:</p><ol><li><code>src/pages/chat/index.js</code></li><li><code>src/pages/chat/styles.module.css</code></li></ol><p>Agreguemos algunos estilos que usaremos en nuestra página <em>Chat</em> y componentes:</p><pre><code class="language-css">/* client/src/pages/chat/styles.module.css */

.chatContainer {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 4fr;
  gap: 20px;
}

/* Room and users component */
.roomAndUsersColumn {
  border-right: 1px solid #dfdfdf;
}
.roomTitle {
  margin-bottom: 60px;
  text-transform: uppercase;
  font-size: 2rem;
  color: #fff;
}
.usersTitle {
  font-size: 1.2rem;
  color: #fff;
}
.usersList {
  list-style-type: none;
  padding-left: 0;
  margin-bottom: 60px;
  color: rgb(153, 217, 234);
}
.usersList li {
  margin-bottom: 12px;
}

/* Messages */
.messagesColumn {
  height: 85vh;
  overflow: auto;
  padding: 10px 10px 10px 40px;
}
.message {
  background: rgb(0, 24, 111);
  border-radius: 6px;
  margin-bottom: 24px;
  max-width: 600px;
  padding: 12px;
}
.msgMeta {
  color: rgb(153, 217, 234);
  font-size: 0.75rem;
}
.msgText {
  color: #fff;
}

/* Message input and button */
.sendMessageContainer {
  padding: 16px 20px 20px 16px;
}
.messageInput {
  padding: 14px;
  margin-right: 16px;
  width: 60%;
  border-radius: 6px;
  border: 1px solid rgb(153, 217, 234);
  font-size: 0.9rem;
}
</code></pre><p>Ahora, veamos cómo terminará luciendo nuestra página <em>Chat</em>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/chat-page.JPG" class="kg-image" alt="The finished chat page" width="600" height="400" loading="lazy"></figure><p>Agregando todo el código y lógica para esta página en un archivo podría volverse confuso y difícil de manejar, así que aprovechemos el hecho de que estamos usando un framework de frontend fabuloso (React) y <strong>dividir nuestra página en componentes:</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" class="kg-image" alt="The chat page split into three components" width="600" height="400" loading="lazy"></figure><h3 id="los-componentes-de-la-p-gina-chat-"><strong>Los componentes de la página chat:</strong></h3><p><strong><strong>A</strong></strong>: Contiene el nombre de la sala, una lista de usuarios en esa sala, y un botón "Leave" que quitar al usuario desde la sala.</p><p><strong><strong>B</strong></strong>: Los mensajes enviados. Al inicio de la renderización, los últimos 100 mensajes enviados en esa sala será solicitado desde la base de datos y mostrado al usuario.</p><p><strong><strong>C</strong></strong>: Una entrada y botón para escribir y enviar un mensaje.</p><p>Primero crearemos el componente B, así podemos mostrar los mensajes al usuario.</p><!--kg-card-begin: html--><h2 id="como-crear-componente-messages">Cómo crear el Componente Messages (B)</h2><!--kg-card-end: html--><p>Crea un nuevo archivo en <code>src/pages/chat/messages.js</code> y agrega el siguiente código:</p><pre><code class="language-jsx">// client/src/pages/chat/messages.js

import styles from './styles.module.css';
import { useState, useEffect } from 'react';

const Messages = ({ socket }) =&gt; {
  const [messagesRecieved, setMessagesReceived] = useState([]);

  // Se ejecuta cuando sea que un evento del socket es recibido desde el servidor
  useEffect(() =&gt; {
    socket.on('receive_message', (data) =&gt; {
      console.log(data);
      setMessagesReceived((state) =&gt; [
        ...state,
        {
          message: data.message,
          username: data.username,
          __createdtime__: data.__createdtime__,
        },
      ]);
    });

    // Quita la escucha del evento al desmontar el componente
    return () =&gt; socket.off('receive_message');
  }, [socket]);

  // dd/mm/yyyy, hh:mm:ss
  function formatDateFromTimestamp(timestamp) {
    const date = new Date(timestamp);
    return date.toLocaleString();
  }

  return (
    &lt;div className={styles.messagesColumn}&gt;
      {messagesRecieved.map((msg, i) =&gt; (
        &lt;div className={styles.message} key={i}&gt;
          &lt;div style={{ display: 'flex', justifyContent: 'space-between' }}&gt;
            &lt;span className={styles.msgMeta}&gt;{msg.username}&lt;/span&gt;
            &lt;span className={styles.msgMeta}&gt;
              {formatDateFromTimestamp(msg.__createdtime__)}
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;p className={styles.msgText}&gt;{msg.message}&lt;/p&gt;
          &lt;br /&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
};

export default Messages;
</code></pre><p>Ahora, tenemos un hook <em>useEffect</em> que se ejecuta cuando sea que el evento del socket se recibe. Luego recibimos los datos del mensaje en el escucha del evento <code>receivemessage</code>. Desde ahí, ponemos el estado <code>messagesReceived</code>, el cual es un arreglo de objetos de mensaje conteniendo el mensaje, nombre de usuario del emisor, y la fecha del mensaje que fue enviado.</p><p>Importemos nuestro nuevo componentes de mensajes en la página <em>Chat</em>, y luego creemos una ruta para la página <em>Chat</em> en <code>App.js</code>:</p><pre><code class="language-jsx">// client/src/pages/chat/index.js

import styles from './styles.module.css';
import MessagesReceived from './messages';

const Chat = ({ socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { useState } from 'react';
import Home from './pages/home';
import Chat from './pages/chat';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import io from 'socket.io-client';

const socket = io.connect('http://localhost:4000');

function App() {
  const [username, setUsername] = useState('');
  const [room, setRoom] = useState('');

  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route
            path='/'
            element={
              &lt;Home
                username={username}
                setUsername={setUsername}
                room={room}
                setRoom={setRoom}
                socket={socket}
              /&gt;
            }
          /&gt;
          {/* Add this */}
          &lt;Route
            path='/chat'
            element={&lt;Chat username={username} room={room} socket={socket} /&gt;}
          /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Probemos esto, ve a la página principal y únete a una sala:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/joining-a-room.JPG" class="kg-image" alt="Joining a room as Dan" width="600" height="400" loading="lazy"></figure><p>Deberíamos ser llevados a la página <em>Chat</em>, y recibir un mensaje de bienvenida del <em>ChatBot</em>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/welcome-message.JPG" class="kg-image" alt="Welcome message received from ChatBot" width="600" height="400" loading="lazy"></figure><p>Los usuarios ahora pueden ver los mensajes que reciben. ¡Genial!</p><p>Próximo: configurar nuestra base de datos así podemos guardar los mensajes permanentemente.</p><!--kg-card-begin: html--><h2 id="como-crear-esquema-y-tabla-harperdb">Cómo crear un Esquema y una Tabla en HarperDB</h2><!--kg-card-end: html--><p>Vuelve a tu dashboard de HarperDB, y haz clic en "browser". Luego crea un nuevo esquemos llamado "realtime_chat_app". Un esquema es simplemente un grupo de tablas.</p><p>Dentro de ese esquema, crea una tabla llamada "messages", con un atributo hash de "id".</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-258.png" class="kg-image" alt="Creating our schema and table in HarperDB" width="600" height="400" loading="lazy"></figure><p>Ahora tenemos necestiamos donde almacenar los mensajes, así que creemos el componente <em>SendMessage</em>.</p><!--kg-card-begin: html--><h2 id="como-crear-componente-send-message">Cómo crear el componente Send Message (C)</h2><!--kg-card-end: html--><p>Crea el archivo <code>src/pages/chat/send-message.js</code> y agrega el siguiente código:</p><pre><code class="language-jsx">// client/src/pages/chat/send-message.js

import styles from './styles.module.css';
import React, { useState } from 'react';

const SendMessage = ({ socket, username, room }) =&gt; {
  const [message, setMessage] = useState('');

  const sendMessage = () =&gt; {
    if (message !== '') {
      const __createdtime__ = Date.now();
      // Envia un mensaje al servidor. No podemos especificar a quien enviamos el mensaje desde el frontend. Solamente podemos enviar al servidor. El servidor por lo tanto puede enviar un mensaje al resto de los usuarios en la sala
      socket.emit('send_message', { username, room, message, __createdtime__ });
      setMessage('');
    }
  };

  return (
    &lt;div className={styles.sendMessageContainer}&gt;
      &lt;input
        className={styles.messageInput}
        placeholder='Message...'
        onChange={(e) =&gt; setMessage(e.target.value)}
        value={message}
      /&gt;
      &lt;button className='btn btn-primary' onClick={sendMessage}&gt;
        Send Message
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default SendMessage;
</code></pre><p>Arriba, cuando el usuario hace clic en el botón "Send Message", un evento del socket <code>send_message</code> se emite al servidor, junto con un objeto de mensaje. Manejaremos este evento en el servidor dentro de poco.</p><p>Importa <em>SendMessage</em> en nuestra página Chat:</p><pre><code class="language-js">// src/pages/chat/index.js

import styles from './styles.module.css';
import MessagesReceived from './messages';
import SendMessage from './send-message';

const Chat = ({ username, room, socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
        &lt;SendMessage socket={socket} username={username} room={room} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><p>La página <em>Chat</em> ahora luce así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-259.png" class="kg-image" alt="Chat page now has a message input where a message can be typed and sent" width="600" height="400" loading="lazy"></figure><p>Luego necesitamos configurar nuestras variables de entorno de HarperDB así podemos comenzar a interactuar con la base de datos.</p><!--kg-card-begin: html--><h2 id="como-configurar-variables-entorno-harperdb">Cómo configurar las Variables de Entorno de HarperDB </h2><!--kg-card-end: html--><p>Para que seas capaz de guardar los mensajes en HarperDB, necesitarás tu URL de instancia de HarperDB, y tu contraseña API.</p><p>En tu dashboard de HarperDB, haz clic en tu instancia, luego ve a "config". Encontrarás tu URL de instancia, y la cabecera de Autenticación de API de tu instancia – eso es, tu contraseña "super_user" que te permite realizar cualquier solicitud a la base de datos – ¡PARA TUS OJOS SOLAMENTE!</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-263.png" class="kg-image" alt="HarperDB instance URL and API auth header" width="600" height="400" loading="lazy"></figure><p>Guardaremos estas variables en un archivo .env. <strong>Advertencia: ¡No empujes el archivo .env a Github!</strong> Este archivo no debería ser visible públicamente. Las variables se cargan a través del servidor por detrás.</p><p>Crea los siguientes archivos y agrega tu URL y contraseña de HarperDB:</p><pre><code class="language-bash">// server/.env

HARPERDB_URL="&lt;tu url va aqui&gt;"
HARPERDB_PW="Basic &lt;tu contraseña aqui&gt;"
</code></pre><p>También crearemos un archivo <code>.gitignore</code> para evitar que el .env se suba a Github, juntamente con la carpeta <code>node_modules</code>:</p><pre><code class="language-bash">// server/.gitignore

.env
node_modules
</code></pre><p><strong>Nota</strong>: ser bueno en Git y Github es 100% imprescindible para todos los desarrolladores. Revisa <a href="https://www.doabledanny.com/git-workflows">mi artículo de los flujos de trabajo de Git</a> si necesitas mejorar tu juego de Git.</p><p>O si constantemente te encuentras revisando los mismos comandos de Git, y quieres una forma rápida de verlos, y copiar/pegar los comandos – revisa mi <a href="https://doabledanny.gumroad.com/l/git-commands-cheat-sheet-pdf">hoja de trucos de los comandos de Git en PDF</a> y mi <a href="https://doabledanny.gumroad.com/l/git-cheat-sheet-poster">póster de hoja de trucos de Git físico</a>.</p><p>Finalmente, carguemos nuestras variables de entorno en nuestro servidor al agregar este código en la parte superior de nuestro archivo <code>main</code>:</p><pre><code class="language-js">// server/index.js

require('dotenv').config();
console.log(process.env.HARPERDB_URL); // quita esto despues que hayas visto que funcionó
const express = require('express');
// ...
</code></pre><!--kg-card-begin: html--><h2 id="como-permitir-a-usuarios-envien-mensajes-con-socketio">Cómo permitir a los Usuarios que se envíen mensajes con Socket.io</h2><!--kg-card-end: html--><p>En el servidor, escucharemos el evento <code>sendmessage</code>, luego enviaremos el mensaje a todos los usuarios dentro de la sala:</p><pre><code class="language-js">// server/index.js

const express = require('express');
// ...
const harperSaveMessage = require('./services/harper-save-message'); // Agrega esto

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('send_message', (data) =&gt; {
    const { message, username, room, __createdtime__ } = data;
    io.in(room).emit('receive_message', data); // Envia a todos los usuarios en la sala, incluyendo al emisor
    harperSaveMessage(message, username, room, __createdtime__) // Guarda el mensaje en la bd
      .then((response) =&gt; console.log(response))
      .catch((err) =&gt; console.log(err));
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Ahora necesitamos crear la función <code>harperSaveMessage</code>. Crea un nuevo archivo en <code>server/services/harper-save-message.js</code>, y agrega lo siguiente:</p><pre><code class="language-js">// server/services/harper-save-message.js

var axios = require('axios');

function harperSaveMessage(message, username, room) {
  const dbUrl = process.env.HARPERDB_URL;
  const dbPw = process.env.HARPERDB_PW;
  if (!dbUrl || !dbPw) return null;

  var data = JSON.stringify({
    operation: 'insert',
    schema: 'realtime_chat_app',
    table: 'messages',
    records: [
      {
        message,
        username,
        room,
      },
    ],
  });

  var config = {
    method: 'post',
    url: dbUrl,
    headers: {
      'Content-Type': 'application/json',
      Authorization: dbPw,
    },
    data: data,
  };

  return new Promise((resolve, reject) =&gt; {
    axios(config)
      .then(function (response) {
        resolve(JSON.stringify(response.data));
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

module.exports = harperSaveMessage;
</code></pre><p>Arriba, guardar los datos podria tomar un poco de tiempo, así que devolvemos una promesa el cual será resuelto si los datos se guardan satisfactoriamente, o rechazada si no se guardan.</p><p>Si te estás preguntando donde obtuve el código de arriba, HarperDB provee una sección increíble de "<a href="https://studio.harperdb.io/resources/examples/QuickStart%20Examples/Create%20dev%20Schema">ejemplos de código</a>" en su dashboard studio, el cual nos facilita la vida:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-265.png" class="kg-image" alt="HarperDB code examples" width="600" height="400" loading="lazy"></figure><p>¡Tiempo de descanso! Únete a una sala como un usuario, luego envía un mensaje. Luego ve a HarperDB y haz clic en "browse", luego haz clic en la tabla "messages". Deberías ver tú mensaje en la base de datos:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-264.png" class="kg-image" alt="Our first messages in the database" width="600" height="400" loading="lazy"></figure><p>Genial 😎. Así que, ¿ahora qué? Bueno, sería bueno si los últimos 100 mensajes enviados en la sala fueran cargados cuando un usuario se una a &nbsp;una sala, ¿no?</p><!--kg-card-begin: html--><h2 id="como-obtener-mensajes-desde-harperdb">Cómo obtener los mensajes desde HarperDB</h2><!--kg-card-end: html--><p>En el servidor, creemos una función que solicite los últimos 100 mensajes enviados en una sala particular (fíjate cómo HarperDB también nos permite usar solicitudes de SQL 👌):</p><pre><code class="language-js">// server/services/harper-get-messages.js

let axios = require('axios');

function harperGetMessages(room) {
  const dbUrl = process.env.HARPERDB_URL;
  const dbPw = process.env.HARPERDB_PW;
  if (!dbUrl || !dbPw) return null;

  let data = JSON.stringify({
    operation: 'sql',
    sql: `SELECT * FROM realtime_chat_app.messages WHERE room = '${room}' LIMIT 100`,
  });

  let config = {
    method: 'post',
    url: dbUrl,
    headers: {
      'Content-Type': 'application/json',
      Authorization: dbPw,
    },
    data: data,
  };

  return new Promise((resolve, reject) =&gt; {
    axios(config)
      .then(function (response) {
        resolve(JSON.stringify(response.data));
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

module.exports = harperGetMessages;
</code></pre><p>Llamaremos a esta función cuando sea que un usuario se una a una sala:</p><pre><code class="language-js">// server/index.js

// ...
const harperSaveMessage = require('./services/harper-save-message');
const harperGetMessages = require('./services/harper-get-messages'); // Add this

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {

    // ...

    // Agrega esto
    // Obtiene los últimos 100 mensajes enviados en la sala chat
    harperGetMessages(room)
      .then((last100Messages) =&gt; {
        // console.log('ultimos mensajes', last100Messages);
        socket.emit('last_100_messages', last100Messages);
      })
      .catch((err) =&gt; console.log(err));
  });

 // ...
</code></pre><p>Arriba, si los mensajes se solicitan de forma exitosa, emitimos un evento de Socket.io llamado <code>last_100_messages</code>. Ahora escucharemos este evento en el frontend.</p><!--kg-card-begin: html--><h2 id="como-mostrar-ultimos-100-mensajes-en-el-cliente">Cómo mostrar los últimos 100 mensajes en el Cliente</h2><!--kg-card-end: html--><p>Abajo, agregamos un hook useEffect que contiene un escucha de evento de Socket.io para el evento <code>last_100_messages</code><em>. </em>Desde ahí, los mensajes son ordenados en orden de fecha, con el más reciente al final, y el estado <code>messagesReceived</code> se actualiza.</p><p>Cuando <code>messagesReceived</code> se actualiza, un useEffect se ejecuta para desplazar el div <code>messageColumn</code> al mensaje más reciente. Esto mejora la experiencia de usuario de nuestra aplicación 👍.</p><pre><code class="language-js">// client/src/pages/chat/messages.js

import styles from './styles.module.css';
import { useState, useEffect, useRef } from 'react';

const Messages = ({ socket }) =&gt; {
  const [messagesRecieved, setMessagesReceived] = useState([]);

  const messagesColumnRef = useRef(null); // Add this

  // Se ejecuta cuando sea que un evento del socket se reciba desde el servidor
  useEffect(() =&gt; {
    socket.on('receive_message', (data) =&gt; {
      console.log(data);
      setMessagesReceived((state) =&gt; [
        ...state,
        {
          message: data.message,
          username: data.username,
          __createdtime__: data.__createdtime__,
        },
      ]);
    });

    // Quita el escucha de eventos al desmontarse el componente 
    return () =&gt; socket.off('receive_message');
  }, [socket]);

  // Agrega esto
  useEffect(() =&gt; {
    // Ultimos 100 mensajes enviados en la sala chat (solicitados desde la bd en el backend)
    socket.on('last_100_messages', (last100Messages) =&gt; {
      console.log('Last 100 messages:', JSON.parse(last100Messages));
      last100Messages = JSON.parse(last100Messages);
      // Ordena estos mensajes por __createdtime__
      last100Messages = sortMessagesByDate(last100Messages);
      setMessagesReceived((state) =&gt; [...last100Messages, ...state]);
    });

    return () =&gt; socket.off('last_100_messages');
  }, [socket]);

  // Agrega esto
  // Se desplaza al mensaje más reciente
  useEffect(() =&gt; {
    messagesColumnRef.current.scrollTop =
      messagesColumnRef.current.scrollHeight;
  }, [messagesRecieved]);

  // Agrega esto
  function sortMessagesByDate(messages) {
    return messages.sort(
      (a, b) =&gt; parseInt(a.__createdtime__) - parseInt(b.__createdtime__)
    );
  }

  // dd/mm/yyyy, hh:mm:ss
  function formatDateFromTimestamp(timestamp) {
    const date = new Date(timestamp);
    return date.toLocaleString();
  }

  return (
    // Agrega ref a este div
    &lt;div className={styles.messagesColumn} ref={messagesColumnRef}&gt;
      {messagesRecieved.map((msg, i) =&gt; (
        &lt;div className={styles.message} key={i}&gt;
          &lt;div style={{ display: 'flex', justifyContent: 'space-between' }}&gt;
            &lt;span className={styles.msgMeta}&gt;{msg.username}&lt;/span&gt;
            &lt;span className={styles.msgMeta}&gt;
              {formatDateFromTimestamp(msg.__createdtime__)}
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;p className={styles.msgText}&gt;{msg.message}&lt;/p&gt;
          &lt;br /&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
};

export default Messages;
</code></pre><!--kg-card-begin: html--><h2 id="como-mostrar-sala-y-usuarios">Cómo mostrar la Sala y los Usuarios (A)</h2><!--kg-card-end: html--><p>Hemos hecho los componentes B y C, así que terminemos todo al hacer A.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" class="kg-image" alt="The chat page split into three components" width="600" height="400" loading="lazy"></figure><p>En el servidor, cuando un usario se une a una sala, emitimos un evento <code>chatroomusers</code> que envía todos los usuarios de la sala a todos los clientes en esa sala. Escuchemos ese evento en un componente llamado <em>RoomAndUsers</em>.</p><p>Abajo, también hay un botón "Leave" que, cuando se presiona, hace la emisión de un evento <code>leaveroom</code> al servidor. Luego redirecciona el usuario devuelta a la página principal.</p><pre><code class="language-js">// client/src/pages/chat/room-and-users.js

import styles from './styles.module.css';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

const RoomAndUsers = ({ socket, username, room }) =&gt; {
  const [roomUsers, setRoomUsers] = useState([]);

  const navigate = useNavigate();

  useEffect(() =&gt; {
    socket.on('chatroom_users', (data) =&gt; {
      console.log(data);
      setRoomUsers(data);
    });

    return () =&gt; socket.off('chatroom_users');
  }, [socket]);

  const leaveRoom = () =&gt; {
    const __createdtime__ = Date.now();
    socket.emit('leave_room', { username, room, __createdtime__ });
    // Redirecciona a la pagina principal
    navigate('/', { replace: true });
  };

  return (
    &lt;div className={styles.roomAndUsersColumn}&gt;
      &lt;h2 className={styles.roomTitle}&gt;{room}&lt;/h2&gt;

      &lt;div&gt;
        {roomUsers.length &gt; 0 &amp;&amp; &lt;h5 className={styles.usersTitle}&gt;Users:&lt;/h5&gt;}
        &lt;ul className={styles.usersList}&gt;
          {roomUsers.map((user) =&gt; (
            &lt;li
              style={{
                fontWeight: `${user.username === username ? 'bold' : 'normal'}`,
              }}
              key={user.id}
            &gt;
              {user.username}
            &lt;/li&gt;
          ))}
        &lt;/ul&gt;
      &lt;/div&gt;

      &lt;button className='btn btn-outline' onClick={leaveRoom}&gt;
        Leave
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default RoomAndUsers;
</code></pre><p>Importemos este componente en la página <em>Chat</em>:</p><pre><code class="language-js">// client/src/pages/chat/index.js

import styles from './styles.module.css';
import RoomAndUsersColumn from './room-and-users'; // Add this
import SendMessage from './send-message';
import MessagesReceived from './messages';

const Chat = ({ username, room, socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      {/* Agrega esto */}
      &lt;RoomAndUsersColumn socket={socket} username={username} room={room} /&gt;

      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
        &lt;SendMessage socket={socket} username={username} room={room} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><!--kg-card-begin: html--><h2 id="como-quitar-un-usuario-de-sala-socketio">Cómo quitar a un Usuarío de una sala de Socket.io</h2><!--kg-card-end: html--><p>Socket.io provee un método <code>leave()</code> que puedes usar para quitar a un usuario de una sala de Socket.io. También estamos manteniendo un registro de nuestros usuarios en un arreglo en la memoria del servidor, así que quitaremos al usuario de ese arreglo también:</p><pre><code class="language-js">// server/index.js

const leaveRoom = require('./utils/leave-room'); // Add this

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('leave_room', (data) =&gt; {
    const { username, room } = data;
    socket.leave(room);
    const __createdtime__ = Date.now();
    // Quita a un usuario de la memoria
    allUsers = leaveRoom(socket.id, allUsers);
    socket.to(room).emit('chatroom_users', allUsers);
    socket.to(room).emit('receive_message', {
      username: CHAT_BOT,
      message: `${username} has left the chat`,
      __createdtime__,
    });
    console.log(`${username} has left the chat`);
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Ahora necesitamos crear la función <code>leaveRoom()</code>:</p><pre><code class="language-js">// server/utils/leave-room.js

function leaveRoom(userID, chatRoomUsers) {
  return chatRoomUsers.filter((user) =&gt; user.id != userID);
}

module.exports = leaveRoom;
</code></pre><p>¿Por qué poner esta función corta en una carpeta <code>utils</code> aparte, te preguntas? Porque lo estaremos usando luego y no queremos repetirnos (mantenemos nuestro código <a href="https://es.wikipedia.org/wiki/No_te_repitas">DRY</a>). </p><p>Probémoslo, abre dos ventanas lado a lado, y únete a ambos chats:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-266.png" class="kg-image" alt="Two windows chatting in realtime." width="600" height="400" loading="lazy"></figure><p>Luego haz clic en el botón leave en la ventana 2:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-267.png" class="kg-image" alt="The user is removed from the chat when they click the Leave button" width="600" height="400" loading="lazy"></figure><p>El usuario se quita del chat, y un mensaje se envía a los otros usuarios – notificándolos que se ha ido. ¡Bien!</p><!--kg-card-begin: html--><h2 id="como-agregar-escucha-evento-disconnect">Cómo agregar el Escucha de Evento Disconnect de Socket.io</h2><!--kg-card-end: html--><p>¿Qué pasa si el usuario de alguna manera se desconecta del servidor, como si su internet se cayera? Socket.io provee un escucha de evento adjunto <code>disconnects</code> para esto. Agreguémosle a nuestro servidor para quitar a un usuario de la memoria cuando se desconecta:</p><pre><code class="language-js">// server/index.js

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('disconnect', () =&gt; {
    console.log('El usuario se desconectó del chat');
    const user = allUsers.find((user) =&gt; user.id == socket.id);
    if (user?.username) {
      allUsers = leaveRoom(socket.id, allUsers);
      socket.to(chatRoom).emit('chatroom_users', allUsers);
      socket.to(chatRoom).emit('receive_message', {
        message: `${user.username} se ha desconectado del chat.`,
      });
    }
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Y ahí lo tienes – haz construido una aplicación de chat de tiempo real fullstack con un frontend de React, un backend de Node/Express, y una base de datos de HarperDB. ¡Buen trabajo!</p><p>Para la próxima, planeo en revisar nuestras <a href="https://harperdb.io/docs/custom-functions/">Funciones Personalizadas de HarperDB</a>, el cual permite a los usuarios definir sus propios endpoints de API dentro de HarperDB. Esto significa que podemos construir nuestra aplicación completa, ¡en un sólo lugar! Mira un ejemplo de cómo HarperDB está colapsando el stack <a href="https://www.harperdb.io/post/mean-stack-alternative">en este artículo</a>.</p><h2 id="un-desaf-o-para-ti-"><strong>Un desafío para ti 💪</strong></h2><p>Si refrescas la página Chat, el nombre de usuario del usuario y la sala desaparecerán. Mira si puedes prevenir que esta información se pierda cuando el usuario refresque la página. Pista: ¡el <a href="https://www.w3schools.com/html/html5_webstorage.asp">local storage</a> podría ser útil!</p><h2 id="-gracias-por-leer-"><strong>¡Gracias<strong><strong> </strong></strong>por<strong><strong> </strong></strong>leer<strong><strong>!</strong></strong></strong></h2><p>Si te pareció útil este artículo, puedes:</p><ul><li><a href="https://www.youtube.com/channel/UC0URylW_U4i26wN231yRqvA">Suscribirte a mi canal de YouTube</a>. Estaré subiendo tutoriales en profunidad y vídeos de proyectos sobre React/NextJS/Node/Express.</li><li><a href="https://twitter.com/doabledanny">Sígueme en Twitter</a> donde comparto tweets sobre mi jornada de freelancing, proyectos propios, y aprendizajes actuales.</li><li><a href="https://doabledanny.gumroad.com/">Revisa mi almacén de Gumroad</a> donde hago hojas de trucos y pósteres útiles y populares (8000 descargas al tiempo de escribir).</li><li><a href="https://www.doabledanny.com/blog/">Revisa mi blog</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo crear una aplicación Fullstack con Next.js 13 y Firebase ]]>
                </title>
                <description>
                    <![CDATA[ > Nota: Al momento de escribir este artículo (17 de Febrero de 2023), la versión 13 de Next.js fue lanzado, el 25 de Octubre de 2022 para ser exacto.  El equipo detrás de Next.js ha lanzado recientemente Next.js 13 [https://nextjs.org/blog/next-13], que tiene un montón de características nuevas como un ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-crear-una-aplicacion-full-stack-con-next-js-13-y-firebase/</link>
                <guid isPermaLink="false">66c75e5ca244b004a5d60a64</guid>
                
                    <category>
                        <![CDATA[ next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Constanza Areal ]]>
                </dc:creator>
                <pubDate>Thu, 19 Sep 2024 21:32:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/09/pexels-sevenstorm-juhaszimrus-443383--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote><strong>Nota</strong>: Al momento de escribir este artículo (17 de Febrero de 2023), la versión 13 de Next.js fue lanzado, el 25 de Octubre de 2022 para ser exacto. </blockquote><p>El equipo detrás de Next.js <a href="https://nextjs.org/blog/next-13">ha lanzado recientemente Next.js 13</a>, que tiene un montón de características nuevas como un nuevo directorio <code>app</code>, componentes para el lado del servidor y del cliente, entre otros.</p><p>En este artículo, aprenderás cómo usar el nuevo Next.js 13 y la Base de Datos Firebase para crear una aplicación <em>full stack.</em></p><p>Antes de continuar, para poder seguir el artículo necesitas tener un conocimiento básico de JavaScript, React y Next.js. Si necesitas repasar alguno de esos conceptos, aquí tienes algún recursos para principiantes:</p><ul><li><a href="https://www.freecodecamp.org/news/learn-javascript-interactive-course/">Aprende JavaScript - contenidos y curso interactivo [en inglés]</a></li><li><a href="https://www.freecodecamp.org/news/learn-react-js-in-this-free-7-hour-course/">Aprende React - curso completo [en inglés]</a></li><li><a href="https://www.freecodecamp.org/news/the-next-js-handbook/">Aprende Next.js - cuadernillo completo [en inglés]</a></li></ul><p>Si estás listo, empecemos.</p><h2 id="c-mo-crear-un-nuevo-proyecto-de-next-js-13"><strong>Cómo crear un nuevo proyecto de Next.js 13</strong></h2><p>Para instalar Next.js, necesitas tener instalado Node.js y npm/yarn instalado en tu computadora. Si no los tienes, puedes bajarlos desde sus sitios oficiales: <a href="https://nodejs.org/en/">Node.js website</a> y <a href="https://www.npmjs.com/">npm website</a> (aunque npm está incluido al instalar Node).</p><ol><li>En el directorio que quieras, inicia la terminal de tu computadora y ejecuta el siguiente comando: <code>npx create-next-app@13 --experimental-app</code>.</li><li>Elige el nombre de tu proyecto y presiona enter para crearlo y esperar a que lo instale.</li><li>Se creará un nuevo directorio con el nombre de tu proyecto con todos archivos necesarios.</li><li>Ejecuta el comando cd para cambiar al directorio nuevo: <br><code>cd my-project-name</code></li><li>Y para iniciar el servidor de desarrollo<strong>, </strong>ejecuta el siguiente comando:</li></ol><pre><code class="language-bash">// si estás usando yarn
yarn run dev

// si estás usando npm
npm run dev</code></pre><p>6. Ejecutar este comando iniciará el servidor de desarrollo para que puedas ver tu aplicación en tiempo real en <a href="http://localhost:3000/">http://localhost:3000</a>.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/02/Screenshot-2023-02-15-at-5.33.52-PM.png" class="kg-image" alt="Screenshot-2023-02-15-at-5.33.52-PM" width="600" height="400" loading="lazy"></figure><p><strong>Cómo instalar Firebase en Next.js</strong></p><p>Firebase es una plataforma Baas -<em> Backend-as-a-Service</em>, por su sigla en inglés- que ofrece servicios como autenticación, bases de datos en tiempo real, almacenamiento en la nube, análisis de datos, entre otros.</p><p>En este tutorial, usaremos Firebase como nuestra base de datos. Sigue estos pasos para crear una aplicación Firebase:</p><ol><li>En <a href="https://console.firebase.google.com/">https://console.firebase.google.com/</a>, inicia sesión con tu cuenta de Google.</li><li>Haz click en <strong>Crear proyecto</strong> y ponle un nombre. Luego, haz clic en <strong>Continuar</strong>.</li><li>En la pantalla siguiente puedes elegir si quieres habilitar métodos de análisis de datos para tu proyecto.</li><li>Haz clic en <strong>Crear proyecto.</strong></li></ol><p>Luego, necesitas crear una aplicación web. En la página de inicio, haz clic en el ícono de web para crear la aplicación:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/02/Screenshot-2023-02-15-at-5.40.33-PM.png" class="kg-image" alt="Screenshot-2023-02-15-at-5.40.33-PM" width="600" height="400" loading="lazy"></figure><p>Ponle un nombre a tu aplicación y haz clic en <strong>Registrar app:</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/02/Screenshot-2023-02-15-at-5.40.48-PM.png" class="kg-image" alt="Screenshot-2023-02-15-at-5.40.48-PM" width="600" height="400" loading="lazy"></figure><p>Copia el archivo de configuración ya que vamos a necesitarlo luego. Haz clic en continuar hasta que termines.</p><p>Una vez que estés de vuelta en la página de inicio de tu proyecto, elige un producto que quieras agregar en tu aplicación. Para este tutorial, sólo agrega <strong>Authentication</strong> y <strong>Cloud Firestore</strong>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/02/Screenshot-2023-02-15-at-6.33.34-PM.png" class="kg-image" alt="Screenshot-2023-02-15-at-6.33.34-PM" width="600" height="400" loading="lazy"></figure><p>Para <strong>Authentication</strong><em>, </em>elige <strong>Método de acceso </strong>y agrega <strong>Correo electrónico/contraseña</strong>.</p><p>Después de instalar exitosamente Firebase, ya lo podemos usar como back-end en tu aplicación de Next.js 13.</p><p>Para usar Firebase con Next.js, sigue estos pasos.</p><p>Instala la última versión del SDK de Firebase en tu proyecto Next.js ejecutando el siguiente comando en tu terminal: </p><pre><code class="language-js">yarn add firebase

// o si estás usando npm
npm install firebase
</code></pre><p>Crea un archivo <code>.env</code> en el directorio raíz de tu proyecto Next.js y copia tus archivos de configuración de Firebase (los que habías copiado antes). Debería verse así:</p><pre><code class="language-js">NEXT_PUBLIC_FIREBASE_API_KEY=api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=auth-domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=storage-bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=app-id
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=analytic-id
</code></pre><p>Luego, para que se vea todo más prolijo, en tu directorio <strong>src</strong> crea una carpeta con el nombre <strong>firebase</strong> y crea un archivo <code>config.js</code> con el siguiente código;</p><pre><code class="language-js">// Importa las funciones que necesites de los SDKs 
import { initializeApp, getApps } from "firebase/app";

const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

// Inicializa Firebase
let firebase_app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];

export default firebase_app;
</code></pre><p>Con esto ya listo, ya puedes utilizar Firebase como base de datos en tu aplicación Next.js.</p><h2 id="c-mo-instalar-authentication">Cómo instalar Authentication</h2><p>Al querer construir aplicaciones <em>fullstack</em>, lo primero en lo que pensamos es la autenticación de usuarios. Con Firebase, podemos crear una forma de registrar usuarios o de que inicien sesión muy fácilmente.</p><p>En tu directorio <strong>src &gt; firebase</strong>, crea una nueva carpeta con el nombre <strong>auth</strong>. Todo el código relacionado con la autenticación de firebase lo guardaremos aquí.</p><p>Luego, crea un archivo <code>signup.js</code> &nbsp;dentro del directorio <strong>src &gt; firebase &gt; auth </strong>con el siguiente código:</p><pre><code class="language-js">import firebase_app from "../config";
import { createUserWithEmailAndPassword, getAuth } from "firebase/auth";

const auth = getAuth(firebase_app);


export default async function signUp(email, password) {
    let result = null,
        error = null;
    try {
        result = await createUserWithEmailAndPassword(auth, email, password);
    } catch (e) {
        error = e;
    }

    return { result, error };
}

</code></pre><p>Vamos a descrifrar este código. Lo que estamos haciendo aquí es exportando la función <code>signUp()</code> que usa el método <strong>createUserWithEmailAndPassword() </strong>de Firebase para poder registrar nuevos usuarios. Luego de hacer esto, vamos a poder usar la función <code>signUp()</code> en cualquier lado de nuestra app.</p><p>En la misma carpeta, agreguemos nuestra función <code>signIn()</code>. Para hacer esto, crea un archivo <code>signin.js</code> con el siguiente código:</p><pre><code class="language-js">import firebase_app from "../config";
import { signInWithEmailAndPassword, getAuth } from "firebase/auth";

const auth = getAuth(firebase_app);

export default async function signIn(email, password) {
    let result = null,
        error = null;
    try {
        result = await signInWithEmailAndPassword(auth, email, password);
    } catch (e) {
        error = e;
    }

    return { result, error };
}
</code></pre><h3 id="c-mo-crear-las-p-ginas-para-el-inicio-de-sesi-n-y-el-registro-de-usuarios-en-next-js">Cómo crear las páginas para el inicio de sesión y el registro de usuarios en Next.js</h3><p>En Next.js 13 se puede crear nuevas páginas dentro del directorio <code>app</code>. Cada página es una carpeta con un archivo <code>page.js</code>. Para aprender más sobre cómo crear páginas, puedes leer la <a href="https://beta.nextjs.org/docs/routing/pages-and-layouts">documentación de Next.js al respecto [en inglés]</a>.</p><p>Para crear un página de registro de usuarios un nuevo archivo <code>page.js</code> en <strong>signup</strong>, dentro de tu directorio <strong>app</strong>. Luego, agrega el siguiente código: </p><pre><code class="language-jsx">'use client'
import React from "react";
import signUp from "@/firebase/auth/signup";
import { useRouter } from 'next/navigation'

function Page() {
    const [email, setEmail] = React.useState('')
    const [password, setPassword] = React.useState('')
    const router = useRouter()

    const handleForm = async (event) =&gt; {
        event.preventDefault()

        const { result, error } = await signUp(email, password);

        if (error) {
            return console.log(error)
        }

        // else successful
        console.log(result)
        return router.push("/admin")
    }
    return (&lt;div className="wrapper"&gt;
        &lt;div className="form-wrapper"&gt;
            &lt;h1 className="mt-60 mb-30"&gt;Sign up&lt;/h1&gt;
            &lt;form onSubmit={handleForm} className="form"&gt;
                &lt;label htmlFor="email"&gt;
                    &lt;p&gt;Email&lt;/p&gt;
                    &lt;input onChange={(e) =&gt; setEmail(e.target.value)} required type="email" name="email" id="email" placeholder="example@mail.com" /&gt;
                &lt;/label&gt;
                &lt;label htmlFor="password"&gt;
                    &lt;p&gt;Password&lt;/p&gt;
                    &lt;input onChange={(e) =&gt; setPassword(e.target.value)} required type="password" name="password" id="password" placeholder="password" /&gt;
                &lt;/label&gt;
                &lt;button type="submit"&gt;Sign up&lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    &lt;/div&gt;);
}

export default Page;`
</code></pre><p>Por defecto, cada página que se agrega al directorio <code>page.js</code> es un <a href="https://beta.nextjs.org/docs/rendering/server-and-client-components">componente del servidor [enlace a la documentación en inglés]</a> y por lo tanto no permite agregar interactividad de lado del servidor como un <code>onSubmit()</code> a elementos de formulario. Para poder agregar esta interactividad, tenemos que decirle a Next.js que queremos un componente Cliente. Para hacer esto, añadimos lo siguiente al comienzo del archivo y antes de importar cualquier otro elemento:</p><pre><code class="language-js">'use client'

// código del componente
</code></pre><p>Del mismo modo vamos a crear nuestra página de inicio de sesión. Para crear esta página, crea un archivo <strong>page.js</strong> en <strong>signin</strong>, dentro de tu directorio <strong>app</strong>, y agrega el siguiente código:</p><pre><code class="language-jsx">'use client'
import React from "react";
import signIn from "@/firebase/auth/signin";
import { useRouter } from 'next/navigation'

function Page() {
    const [email, setEmail] = React.useState('')
    const [password, setPassword] = React.useState('')
    const router = useRouter()

    const handleForm = async (event) =&gt; {
        event.preventDefault()

        const { result, error } = await signIn(email, password);

        if (error) {
            return console.log(error)
        }

        // sino exitoso
        console.log(result)
        return router.push("/admin")
    }
    return (&lt;div className="wrapper"&gt;
        &lt;div className="form-wrapper"&gt;
            &lt;h1 className="mt-60 mb-30"&gt;Sign up&lt;/h1&gt;
            &lt;form onSubmit={handleForm} className="form"&gt;
                &lt;label htmlFor="email"&gt;
                    &lt;p&gt;Email&lt;/p&gt;
                    &lt;input onChange={(e) =&gt; setEmail(e.target.value)} required type="email" name="email" id="email" placeholder="example@mail.com" /&gt;
                &lt;/label&gt;
                &lt;label htmlFor="password"&gt;
                    &lt;p&gt;Password&lt;/p&gt;
                    &lt;input onChange={(e) =&gt; setPassword(e.target.value)} required type="password" name="password" id="password" placeholder="password" /&gt;
                &lt;/label&gt;
                &lt;button type="submit"&gt;Sign up&lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;

    &lt;/div&gt;);
}

export default Page;
</code></pre><h3 id="c-mo-saber-si-hubo-alg-n-cambio-de-autenticaci-n">Cómo saber si hubo algún cambio de autenticación</h3><p>Mientras estás usando la aplicación, siempre vamos a querer saber si el usuario ha iniciado sesión o no. Si el usuario ha iniciado sesión, es posible crear páginas protegidas y mostrarle ciertos contenidos sólo a él. Firebase tiene el método <code>onAuthStateChanged()</code> para poder saber si hubo cambios.</p><p>Para que la información del usuario obtenida con el método mencionado arriba pueda ser usada en todo la aplicación, vamos a usar la API React Context. Crea un carpeta con el nombre <strong>context</strong> en tu directorio <strong>src. </strong>Dentro del directorio <strong>directory</strong>, crea un archivo con el nombre <code>AuthContext.js</code> y agrega el siguiente código:</p><pre><code class="language-jsx">import React from 'react';
import {
    onAuthStateChanged,
    getAuth,
} from 'firebase/auth';
import firebase_app from '@/firebase/config';

const auth = getAuth(firebase_app);

export const AuthContext = React.createContext({});

export const useAuthContext = () =&gt; React.useContext(AuthContext);

export const AuthContextProvider = ({
    children,
}) =&gt; {
    const [user, setUser] = React.useState(null);
    const [loading, setLoading] = React.useState(true);

    React.useEffect(() =&gt; {
        const unsubscribe = onAuthStateChanged(auth, (user) =&gt; {
            if (user) {
                setUser(user);
            } else {
                setUser(null);
            }
            setLoading(false);
        });

        return () =&gt; unsubscribe();
    }, []);

    return (
        &lt;AuthContext.Provider value={{ user }}&gt;
            {loading ? &lt;div&gt;Loading...&lt;/div&gt; : children}
        &lt;/AuthContext.Provider&gt;
    );
};
</code></pre><p>Con el código de arriba simplemente creamos un Provider que devuelve el objeto usuario si el usuario inició sesión. De lo contrario, sólo devolverá <code>null</code>.</p><p>Para poder usar el valor que se le pasó a <code>&lt;AuthContext.Provider&gt;</code> &nbsp;vamos a exportar <code>useAuthContext</code> desde el archivo y así usar el valor de <code>user</code>.</p><p>Antes de poder usar este contexto, necesitamos envolver todos los componentes con <code>AuthContextProvider</code>. Para hacer esto, abrimos el archivo <strong>layout.js</strong> dentro del directorio <strong>src &gt; app</strong> y editamos el código con lo siguiente:</p><pre><code class="language-jsx">'use client'
import './globals.css'
import { AuthContextProvider } from '@/context/AuthContext'

export default function RootLayout({ children }) {
  return (
    &lt;html lang="en"&gt;
      {/*
        &lt;head /&gt; contendrá los componentes devueltos por el padre más cercano
        head.js. Encuentra más información en https://beta.nextjs.org/docs/api-reference/file-conventions/head
      */}
      &lt;head /&gt;
      &lt;body&gt;
        &lt;AuthContextProvider&gt;
          {children}
        &lt;/AuthContextProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}

</code></pre><p>Ahora ya podemos crear páginas protegidas y mostrar contenido específico a usuarios distintos.</p><h3 id="c-mo-crear-p-ginas-protegidas">Cómo crear páginas protegidas</h3><p>Dentro del directorio <strong>app, </strong>crea el directorio <strong>admin &gt; page.js </strong> y agrega el siguiente código:</p><pre><code class="language-jsx">'use client'
import React from "react";
import { useAuthContext } from "@/context/AuthContext";
import { useRouter } from "next/navigation";
function Page() {
    const { user } = useAuthContext()
    const router = useRouter()

    React.useEffect(() =&gt; {
        if (user == null) router.push("/")
    }, [user])

    return (&lt;h1&gt;Solamente los usuarios que iniciaron sesión pueden ver esta página&lt;/h1&gt;);
}

export default Page;
</code></pre><p>Si el usuario es <code>null</code>, lo redireccionamos a la página inicial. De lo contrario, si no es <code>null</code>, le mostramos la página protegida.</p><h2 id="c-mo-comunicarse-con-nuestra-base-de-datos">Cómo comunicarse con nuestra base de datos</h2><p>Ahora que ya lidiamos con la parte de autenticación, podemos enfocarnos en la comunicación con nuestra base de datos. Como base de datos vamos a usar <strong>Firestore.</strong></p><p>Para seguir haciendo todo ordenado, vamos a crear un nuevo directorio llamado <strong>firebase &gt; firestore</strong>. Dentro de este directorio vamos a guardar todo el código relacionado con Firestore.</p><h3 id="c-mo-agregar-documentos-a-firestore">Cómo agregar documentos a Firestore</h3><p>Dentro del directorio <strong>firestore</strong>, crea un archivo con el nombre <code>addData.js</code> y agrega el siguiente código:</p><pre><code class="language-js">import firebase_app from "../config";
import { getFirestore, doc, setDoc } from "firebase/firestore";

const db = getFirestore(firebase_app)
export default async function addData(colllection, id, data) {
    let result = null;
    let error = null;

    try {
        result = await setDoc(doc(db, colllection, id), data, {
            merge: true,
        });
    } catch (e) {
        error = e;
    }

    return { result, error };
}
</code></pre><p>Este código ya debería serte familiar: estamos exportando una función que permite agregar información a nuestra base de datos firestore.</p><p>Ahora ya podemos usar esta función <code>addData()</code> desde cualquier componente para agregar información a nuestra base de datos:</p><pre><code class="language-js">'use client'
import addData from "@/firebase/firestore/addData";

export default function Home() {

  const handleForm = async () =&gt; {
    const data = {
      name: 'John snow',
      house: 'Stark'
    }
    const { result, error } = await addData('users', 'user-id', data)

    if (error) {
      return console.log(error)
    }
  }
  
  return (
    ...
  )
}

</code></pre><h3 id="c-mo-podemos-obtener-un-documento-desde-firestore">Cómo podemos obtener un documento desde Firestore</h3><p>Usando un método similar, podemos obtener un documento desde nuestra base de datos Firebase.</p><p>Crea un archivo <code>getData.js</code> dentro del directorio <strong>Firestore </strong>y agrega el siguiente código:</p><pre><code class="language-js">import firebase_app from "../config";
import { getFirestore, doc, getDoc } from "firebase/firestore";

const db = getFirestore(firebase_app)
export default async function getDoument(collection, id) {
    let docRef = doc(db, collection, id);

    let result = null;
    let error = null;

    try {
        result = await getDoc(docRef);
    } catch (e) {
        error = e;
    }

    return { result, error };
}

</code></pre><p>También podemos usar está función en cualquier componente que quieras.</p><h2 id="conclusi-n">Conclusión</h2><p>En este artículo, aprendimos cómo construir una aplicación <em>fullstack</em> usando Firebase y Next.js 13 para agregar autenticación e interacción con nuestra base de datos.</p><p>¡Feliz codificación!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Operaciones CRUD  – ¿Qué es CRUD? ]]>
                </title>
                <description>
                    <![CDATA[  CRUD no es una palabra. Es más bien una abreviación en inglés, que significa: - Create - crear - Read - leer - Update - actualizar - Delete - eliminar o destruir En este artículo te mostraré lo que significa  CRUD y cada una de sus siglas, también te ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/operaciones-crud-que-es-crud/</link>
                <guid isPermaLink="false">64c54dce73ccf107b75200f3</guid>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Desarrollo Backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Franciscomelov ]]>
                </dc:creator>
                <pubDate>Fri, 09 Feb 2024 12:38:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/02/crud.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p> CRUD no es una palabra. Es más bien una abreviación en inglés, que significa:</p><p>- Create - crear<br>- Read - leer<br>- Update - actualizar<br>- Delete - eliminar o destruir</p><p>En este artículo te mostraré lo que significa &nbsp;CRUD y cada una de sus siglas, también te enseñaré cómo funcionan las operaciones CRUD de crear, leer, actualizar y eliminar en el mundo real.</p><h2 id="-ndice"><strong>Índice</strong></h2><ul><li>¿Qué es CRUD?</li><li>¿Qué es la operación <code>CREATE</code> (Crear) y cómo funciona?</li><li>¿Qué es la operación <code>READ</code> (Leer) y cómo funciona?</li><li>¿Qué es la operación<code>UPDATE</code> (Actualizar) y cómo funciona?</li><li>¿Qué es la operación <code>DELETE</code> (Eliminar) y cómo funciona?</li><li>Conclusión</li></ul><h2 id="-qu-es-crud"><strong>¿Qué es CRUD?</strong></h2><p>CRUD se refiere a las cuatro operaciones básicas que una aplicación debería poder hacer- "Create - Crear", "Read - Leer", "Update - Actualizar" y "Delete - eliminar"</p><p>En una aplicación, el usuario debe de ser capaz de crear datos, poder leerlos y tener acceso a ellos desde la interfaz gráfica, actualizar o editar los datos y ser capaz de eliminarlos.</p><p>Las aplicaciones CRUD completas, consisten de 3 partes: una API (o servidor), una base de datos y una interfaz de usuario (IU/UI - user interface).</p><p>La API contiene código y métodos, la base de datos almacena y ayuda al usuario a extraer información, mientras que la interfaz de usuario ayuda a los usuarios a interactuar con la aplicación.</p><p>Puedes hacer una aplicación CRUD con cualquier lenguaje de programación existente. La aplicación no tiene que ser full-stack puedes hacer aplicaciones CRUD solo con JavaScript en el lado del cliente.</p><p>De hecho, la aplicación con la que te enseñaré cómo hacer operaciones CRUD - crear, leer, actualizar y eliminar, funciona solo con JavaScript del lado del cliente.</p><p>Cada letra en CRUD tiene un método de petición HTTP (HTTP request) correspondiente.</p><p>*Recuerda que es normal que en programación las instrucciones sean en inglés, ya que en este idioma están hechos la mayoría de los lenguajes de programación.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/01/image-2.png" class="kg-image" alt="image-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/01/image-2.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2024/01/image-2.png 635w" width="635" height="211" loading="lazy"></figure><h2 id="-cu-l-es-la-operaci-n-create-y-c-mo-funciona"><strong>¿Cuál es la operación <code>CREATE</code> y cómo funciona?</strong></h2><p>La operación CREATE (crear) como su nombre lo dice, crea un dato, este dato puede ser un usuario, información del usuario, una publicación o una tarea.</p><p>Como mencioné antes, la instrucción HTTP que ejecuta la operación <code>CREATE</code>es el método POST.</p><p>CREATE es el equivalente a <code>INSERT</code> (INSERTAR) en una base de datos SQL. En una base de datos NoSQL como MongoDB, se utiliza la instrucción <code>insert()</code>.</p><p>En la interfaz de usuario, que se muestra el siguiente GIF, se muestra cómo funciona la operación <code>CREATE</code>.</p><p>Primero se escribe el dato en la caja de texto y al dar clic en <code>Submit</code>, se crea o agrega un dato en nuestra aplicación.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/06/create-op.gif" class="kg-image" alt="create-op" width="600" height="400" loading="lazy"></figure><h2 id="-qu-es-la-operaci-n-read-y-c-mo-funciona"><strong>¿Qué es la operación <code>READ</code> y cómo funciona?</strong></h2><p>La operación <code>READ</code> (leer) nos permite tener acceso, poder ver o leer los datos o información desde la interfaz gráfica, que pueden ser desde información de los usuarios, publicaciones de redes sociales, etc.</p><p>Se puede acceder a estos datos inmediatamente después de introducirlos en la aplicación o buscarlos después. Una caja de búsqueda se implementa para permitir a los usuarios filtrar los datos que no necesitan.</p><p>El método HTTP GET es el encargado de implementar la operación <code>READ</code>.</p><p>READ es el equivalente a <code>SELECT</code> (seleccionar) en una base de datos SQL. En una base de datos NoSQL como MongoDB, para leer se utiliza el método <code>find()</code> o <code>findById()</code></p><p>En el siguiente ejemplo, en la sección "Entries" se puede ver cómo se leen o se muestran los datos previamente ingresados en la aplicación.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/06/read-operation.png" class="kg-image" alt="read-operation" width="600" height="400" loading="lazy"></figure><h2 id="-qu-es-la-operaci-n-update-y-c-mo-funciona"><strong>¿Qué es la operación <code>UPDATE</code> y cómo funciona?</strong></h2><p>La operación <code>UPDATE</code> (actualizar) nos permite modificar datos existentes o editar los datos de la aplicación.</p><p>A diferencia de <code>READ</code>, la operación <code>UPDATE</code> modifica los datos existentes, haciendo cambios a estos.</p><p>PUT y PATCH &nbsp;son los protocolos HTTP con los que se puede implementar la operación <code>UPDATE</code>, dependiendo de lo que necesites.</p><p>Utilizarás <code>PUT</code> cuando quieras modificar todo un dato, y PATCH si solo quieres modificar una parte de este.</p><p>En una base de datos SQL, se utiliza <code>UPDATE</code> para actualizar un dato, en una base de datos NoSQL como MongoDB, puedes implementar una actualización con el método <code>findByIdAndUpdate()</code>.</p><p>En el siguiente ejemplo nos muestran cómo funciona la operación <code>UPDATE</code>, dando clic en el botón "Update" podemos modificar un dato de la lista.<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/06/update-op.gif" class="kg-image" alt="update-op" width="600" height="400" loading="lazy"></figure><h2 id="-qu-es-la-operaci-n-delete-y-c-mo-funciona"><strong>¿Qué es la operación <code>DELETE</code> y cómo funciona?</strong></h2><p><code>DELETE</code> (eliminar) nos permite eliminar o deshacernos de un dato de la interfaz de usuario y de la base de datos.</p><p><code>DELETE</code> es el protocolo HTTP que nos permita implementar la operación <code>DELETE</code>.</p><p>En una base de datos SQL, <code>DELETE</code> se utiliza para eliminar un dato. En una base de datos NoSql como MongoDB, puedes implementar una función de eliminar con el método <code>findByIdAndDelete()</code>.</p><p>En el siguiente ejemplo podemos ver cómo se elimina cada elemento de la lista al dar clic en el botón "Delete"</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/06/delete-op.gif" class="kg-image" alt="delete-op" width="600" height="400" loading="lazy"></figure><h2 id="conclusi-n"><strong>Conclusión</strong></h2><p>Este artículo te mostró lo que significa CRUD, y qué hace cada una de sus funciones en una aplicación CRUD.</p><p>Puedes pensar en CRUD de la siguiente forma:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/01/image-2.png" class="kg-image" alt="image-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/01/image-2.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2024/01/image-2.png 635w" width="635" height="211" loading="lazy"></figure><ul><li>Puedes "crear" una cuenta y añadir tu información en una red social - <code>CREATE</code></li><li>Puedes acceder a la información que ingresaste y otras personas te pueden buscar – <code>READ</code></li><li>Al obtener un trabajo en Google hay que cambiar/actualizar tu información de trabajo – <code>UPDATE</code></li><li>Si te cansas de la toxicidad de las redes sociales y eliminas tu cuenta - <code>DELETE</code></li></ul><p>Para aprender a hacer tu propia aplicación CRUD - puedes ver <a href="https://www.freecodecamp.org/news/learn-crud-operations-in-javascript-by-building-todo-app/">este tutorial</a> de Joy Shaheb en FreeCodeCamp.</p><p>!No pares de programar¡👋</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ ¿Qué es un desarrollador full stack? Back end + front end = Ingeniero full stack ]]>
                </title>
                <description>
                    <![CDATA[ Si recién empiezas en la industria de la tecnología, es posible que hayas oído hablar del término Full Stack. Pero, ¿qué significa eso exactamente? En este artículo, explicaré qué es Full Stack Development, qué habilidades necesitas para convertirte en Full Stack Developer, así como el tema de las expectativas salariales, ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/que-es-un-desarrollador-full-stack-back-end-front-end-ingeniero-full-stack/</link>
                <guid isPermaLink="false">6419497555e3ad15a8936695</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Andrés  Torres ]]>
                </dc:creator>
                <pubDate>Mon, 27 Mar 2023 20:54:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/03/christina-wocintechchat-com-IxmHiUC-yOw-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/what-is-a-full-stack-developer-back-end-front-end-full-stack-engineer/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What is a Full Stack Developer? Back End + Front End = Full Stack Engineer</a>
      </p><p>Si recién empiezas en la industria de la tecnología, es posible que hayas oído hablar del término <em>Full Stack</em>. Pero, ¿qué significa eso exactamente?</p><p>En este artículo, explicaré qué es Full Stack Development, qué habilidades necesitas para convertirte en Full Stack Developer, así como el tema de las expectativas salariales, sumado a unos consejos para conseguir el trabajo.</p><h2 id="-qu-es-el-desarrollo-full-stack">¿Qué es el Desarrollo Full Stack?</h2><p>Un desarrollador Full Stack es alguien que trabaja tanto con el <strong>front-end</strong> como con el <strong>back-end</strong> de una aplicación web. El front-end es responsable de la apariencia visual del sitio web, mientras que el back-end es responsable de la lógica y la infraestructura detrás de escena del sitio.</p><p>Echemos un vistazo más de cerca a los términos front-end y back-end.</p><h3 id="desarrollo-frontend-punto-de-vista-del-cliente-">Desarrollo Frontend (Punto de vista del cliente)</h3><p>Todo en una página web, desde el logotipo hasta la barra de búsqueda, los botones, el diseño general y la forma en que el usuario interactúa con la página, fue creado por un desarrollador front-end. Los desarrolladores <strong>front-end están a cargo de la apariencia del sitio web.</strong></p><p>Los desarrolladores front-end también deben asegurarse de que el sitio web se vea bien en todos los dispositivos (teléfonos, tabletas y pantallas de computadora). Esto se llama Diseño Web Adaptable.</p><h3 id="desarrollo-back-end-punto-de-vista-del-servidor-">Desarrollo back-end (Punto de vista del servidor)</h3><p>El desarrollo de back-end se refiere a<strong> la parte de la aplicación que el usuario no ve.</strong> Los desarrolladores de back-end se centran en<strong> la lógica del sitio, creando servidores y trabajando con bases de datos y API </strong>(interfaces de programación de aplicaciones).</p><p>Por ejemplo, el<strong> contenido y el diseño</strong> de un formulario se crearían en el lado del <strong>cliente.</strong> Pero cuando los usuarios envían su <strong>información</strong>, esta se procesa en el lado del<strong> servidor (back-end)</strong>.</p><h3 id="ejemplo-de-una-aplicaci-n-full-stack-del-mundo-real">Ejemplo de una aplicación Full Stack del mundo real</h3><p>Para comprender mejor el aspecto de una aplicación de pila completa, examinemos el sitio web de<a href="https://www.freecodecamp.org/espanol/"> freeCodeCamp</a>.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-13.png" class="kg-image" alt="image-13" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/03/image-13.png 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2023/03/image-13.png 1000w, https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-13.png 1347w" sizes="(min-width: 720px) 720px" width="1347" height="602" loading="lazy"></figure><p>Todo lo que ves en la página se consideraría front-end. Todos los botones, texto, colores y diseño son trabajo del desarrollador front-end.</p><p>Cuando se trata de cosas como las credenciales de usuario, todo eso lo mantiene el back-end. Todos los procesos de inicio de sesión y la información del usuario son ejemplos de desarrollo de back-end en el trabajo.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-14.png" class="kg-image" alt="image-14" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/03/image-14.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-14.png 605w" width="605" height="625" loading="lazy"></figure><p>Si tienes curiosidad y quieres echar un vistazo al código, puedes encontrarlo en la página de <a href="https://github.com/freeCodeCamp/">código abierto de freeCodeCamp.</a></p><h2 id="habilidades-que-necesitas-en-el-desarrollo-full-stack">Habilidades que necesitas en el desarrollo Full Stack</h2><p>Echemos un vistazo a las tecnologías y herramientas que necesitará aprender para convertirse en un desarrollador de pila completa.</p><p><strong>HTML</strong><br>HTML significa lenguaje de marcado de hipertexto. HTML muestra el contenido de la página como botones, enlaces, encabezados, párrafos y listas.</p><p>Para aprender los conceptos básicos de HTML, freeCodeCamp tienes un curso acelerado de HTML para principiantes: <a href="https://www.youtube.com/watch?v=XqFR2lqBYPs">Tutorial del sitio web.</a></p><p><strong>CSS</strong><br>CSS significa hojas de estilo en cascada. CSS es responsable del estilo de su página web, incluidos los colores, los diseños y las animaciones.</p><p>El curso de <a href="https://www.freecodecamp.org/espanol/learn/2022/responsive-web-design/">diseño web adaptativo de freeCodeCamp</a> te enseñará los conceptos básicos de CSS, diseño adaptativo y accesibilidad. El <a href="https://www.freecodecamp.org/espanol/news/diseno-web-responsive-como-hacer-que-un-sitio-web-se-vea-bien-en-telefonos-y-tabletas/">diseño adaptativo </a> es esencial para crear sitios web que se vean bien en todos los dispositivos.</p><p>La accesibilidad es la práctica de asegurarse de que todos puedan usar fácilmente sus sitios web. No deseamos crear sitios web que no puedan utilizar aquellos que utilizan tecnologías de asistencia como lectores de pantalla.</p><p><strong>JavaScript</strong><br>Utiliza JavaScript con HTML y CSS para crear páginas web y aplicaciones móviles dinámicas e interactivas. Los ejemplos de JavaScript incluyen animaciones, relojes de cuenta regresiva, menús móviles desplegables y mostrar/ocultar información cuando un usuario hace clic en un elemento de la página.</p><p>Para comenzar a aprender JavaScript, puedes tomar el <a href="https://www.freecodecamp.org/espanol/learn/javascript-algorithms-and-data-structures/">curso de estructuras de datos y algoritmos de JavaScript de FreeCodeCamp</a>. A partir de ahí, puedes comenzar a crear proyectos desde mi artículo <a href="https://www.freecodecamp.org/news/javascript-projects-for-beginners/">40 proyectos de JavaScript para principiantes.</a> También están disponibles<a href="https://www.freecodecamp.org/espanol/news/aprende-javascript-curso-js-gratis-para-principiantes/"> otros cursos</a>.</p><h3 id="frameworks-bibliotecas-y-preprocesadores-de-css">Frameworks, bibliotecas y preprocesadores de CSS</h3><p>Estas herramientas se crearon para <strong>ayudar a acelerar el proceso de desarrollo</strong>. En lugar de escribir todo el CSS personalizado, puedex usar el catálogo de un marco de clases de CSS en sus páginas web.</p><p>No hay necesidad de aprenderlos todos, pero aquí hay una lista de algunas opciones:</p><ul><li><a href="https://tailwindcss.com/">Tailwind CSS</a></li><li><a href="https://getbootstrap.com/">Bootstrap</a></li><li><a href="https://bulma.io/">Bulma</a></li><li><a href="https://materializecss.com/">Materialize</a></li><li><a href="https://semantic-ui.com/">Semantic UI</a></li></ul><p>Los preprocesadores de CSS como <a href="https://sass-lang.com/">Sass</a> y <a href="https://lesscss.org/">Less</a> le permiten agregar lógica y funcionalidad a su CSS. Estas herramientas hacen que tu CSS sea limpio y fácil de usar.</p><h3 id="bibliotecas-y-marcos-de-javascript">Bibliotecas y marcos de JavaScript</h3><p>Estos marcos y bibliotecas te permiten ahorrar tiempo y hacer más con menos código.</p><h3 id="aqu-hay-algunas-opciones-populares-">Aquí hay algunas opciones populares:</h3><ul><li><a href="https://reactjs.org/">React</a></li><li><a href="https://angular.io/">Angular</a></li><li><a href="https://vuejs.org/">Vue</a></li></ul><p>No es necesario aprenderlos todos. Simplemente debes investigar qué tecnología se usa en su área y comenzar a aprenderla bien.</p><p>Aquí hay algunos recursos de aprendizaje sugeridos.</p><ul><li><a href="https://youtu.be/6Jfk8ic3KVk"> Curso de React en Youtube de freeCodeCamp</a></li><li><a href="https://www.youtube.com/watch?v=Fdf5aTYRW0E">Curso de Angular de Brad Traversy en YouTube</a></li><li><a href="https://www.youtube.com/watch?v=Fdf5aTYRW0E">Curso de Vue de Brad Traversy en </a><a href="https://www.youtube.com/watch?v=qZXt1Aom3Cs"> YouTube</a></li></ul><h3 id="bases-de-datos">Bases de Datos</h3><p>Es importante que un desarrollador Full Stack sepa cómo trabajar con bases de datos. Una base de datos en una aplicación web es un lugar para almacenar y organizar los datos de tu proyecto.</p><p>Hay muchos tipos de bases de datos para aprender, pero aquí hay algunas opciones populares.</p><ul><li><a href="https://www.freecodecamp.org/espanol/news/search?query=sql">SQL</a></li><li><a href="https://www.freecodecamp.org/news/learn-to-use-the-mysql-database/">MySQL</a></li><li><a href="https://www.freecodecamp.org/news/postgresql-full-course/">PostgreSQL</a></li><li><a href="https://www.freecodecamp.org/news/mongodb-full-course-nodejs-express-mongoose/">MongoDB</a></li></ul><p><strong>Lenguajes de Programación para Back- end</strong><br>Los lenguajes que puedes emplear para el desarrollo de back-end incluyen <strong>Java, Python, Node y PHP.</strong> No es necesario aprender todos estos lenguajes, sino concentrarse en uno para comenzar.</p><p>Aquí hay algunos recursos de aprendizaje sugeridos.</p><ul><li><a href="https://youtu.be/DLikpfc64cA">Curso de Python para principiantes de freeCodeCamp </a></li><li><a href="https://www.youtube.com/watch?v=OK_JCtrrv-c">Curso de PHP de freeCodeCamp PHP</a></li><li><a href="https://www.youtube.com/watch?v=eIrMbAQSU34">Curso de programación en Java de Mosh's Java </a></li></ul><p>También hay muchas tecnologías para el desarrollo de front-end y back-end. Aquí hay algunos populares.</p><ul><li><a href="https://www.freecodecamp.org/news/cjn-understanding-mean-stack-through-diagrams/">MEAN stack</a> (MongoDB, Express, Angular and Node)</li><li><a href="https://www.freecodecamp.org/news/create-a-mern-stack-app-with-a-serverless-backend/">MERN stack</a> (MongoDB, Express, React and Node)</li><li><a href="https://www.freecodecamp.org/learn/back-end-development-and-apis/">NodeJS</a></li><li><a href="https://www.freecodecamp.org/news/how-to-setup-a-lamp-server-on-a-local-ubuntu-linux-machine-or-vm/">LAMP stack</a> (Linux, Apache, MySQL, and PHP)</li></ul><p>En realidad hay más lenguajes de back-end para elegir, pero te sugiero que investigues los idiomas más utilizados en tu área.</p><h3 id="habilidades-de-prueba-y-depuraci-n">Habilidades de prueba y depuración</h3><p>A medida que desarrollas tu aplicación, habrá errores en el código que deben corregirse. <strong>La depuración es el acto de identificar esos errores ("bugs") y corregirlos.</strong></p><p>La prueba (mejor conocida como <strong>Testing</strong>) es otra habilidad importante para aprender. Escribir pruebas para tu código es una forma de asegurarte el mismo está haciendo lo que se supone que debe hacer.</p><p>Para una explicación más detallada sobre los diferentes tipos de pruebas, sugiero <a href="https://www.freecodecamp.org/news/types-of-software-testing/">leer este artículo.</a></p><h3 id="control-de-versiones">Control de versiones</h3><p>El control de versiones es una forma de <strong>rastrear y administrar los cambios en el código del proyecto.</strong> Git es un software popular que puede usar para rastrear su código.</p><p>Si estropeas muchas cosas en tu código, puedes<strong> usar Git para volver a una versión anterior de este</strong> en lugar de volver a escribir todo manualmente.</p><p>Aprender Git también te permite <strong>colaborar con otros en un equipo </strong>y realizar cambios en la misma base de código desde diferentes ubicaciones.</p><p>Sugeriría <strong>comenzar a <a href="https://www.youtube.com/watch?v=mBYSUUnMt9M">aprender Git </a>y usar un servicio como <a href="https://github.com/">GitHub</a> para alojar tus proyectos personales.</strong></p><h3 id="resoluci-n-de-problemas">Resolución de problemas</h3><p>La habilidad más importante para cualquier desarrollador es saber cómo resolver problemas. Empresas y clientes te buscan para aportar soluciones.</p><p>Es importante aprender a abordar un problema, dividirlo en partes más pequeñas y manejables y solucionar el problema en estas aplicaciones web.</p><h2 id="-debo-perseguir-como-objetivo-el-desarrollo-full-stack">¿Debo perseguir como objetivo el desarrollo full stack?</h2><p>Si recién estás comenzando y no puedes determinar si el desarrollo Full Stack es para tí, simplemente no se preocupes por eso tan temprano en tu aprendizaje.</p><p>Sugeriría centrarse en <strong>aprender los tres lenguajes principales de la web, que son HTML, CSS y JavaScript.</strong> A medida que avanza en su viaje, entonces puedes decidir qué camino es apropiado para tí.</p><p><strong>¿Cuánto gana un desarrollador Full Stack?</strong><br>El rango de salario para los trabajos de Full Stack completa diferirá según el lugar del mundo en el que te encuentres. Sugeriría usar sitios como <strong><a href="https://www.glassdoor.com/index.htm">Glassdoor</a>, <a href="https://www.indeed.com/">Indeed</a> y <a href="https://www.ziprecruiter.com/">ZipRecruiter </a>para buscar salarios en su área.</strong></p><p>Aquí hay ejemplos de salarios para los Estados Unidos. Estos datos fueron proporcionados por <a href="https://www.ziprecruiter.com/">ZipRecruiter</a> y muestran el promedio nacional.</p><p>Veamos primero el salario de un desarrollador junior Full Stack. <strong>Los desarrolladores junior son aquellos que han estado trabajando en la industria durante dos años o menos.</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-15.png" class="kg-image" alt="image-15" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/03/image-15.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-15.png 1000w" sizes="(min-width: 720px) 720px" width="1000" height="624" loading="lazy"></figure><p>A continuación, echemos un vistazo a los salarios de full stack de nivel medio. Estos desarrolladores <strong>han estado trabajando entre 2-4 años aproximadamente.</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-16.png" class="kg-image" alt="image-16" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/03/image-16.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-16.png 1000w" sizes="(min-width: 720px) 720px" width="1000" height="636" loading="lazy"></figure><p>Por último, echemos un vistazo a los salarios senior full stack. Estos desarrolladores han estado trabajando en la industria <strong>durante algunos años y, a menudo, sirven como mentores para desarrolladores de nivel junior y medio.</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-17.png" class="kg-image" alt="image-17" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/03/image-17.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2023/03/image-17.png 1000w" sizes="(min-width: 720px) 720px" width="1000" height="633" loading="lazy"></figure><h2 id="-c-mo-puedes-obtener-un-trabajo-como-desarrollador-full-stack"><strong><strong>¿Cómo puedes obtener un trabajo como Desarrollador<strong> Full Stack?</strong></strong></strong></h2><p>Una vez que hayas aprendido los aspectos técnicos del desarrollo Full Stack; debes concentrarte en reunir los materiales de tu solicitud de empleo. </p><p>Hay muchos recursos increíbles que pueden ayudarlo a aprender cómo obtener un trabajo en el área.</p><h3 id="recursos-para-la-elaboraci-n-de-curr-culos">Recursos para la elaboración de currículos</h3><ul><li><a href="https://www.freecodecamp.org/news/how-to-get-your-first-dev-job/">Cómo obtener tu primer trabajo de desarrollador: Perspectivas de la revisión de los currículos de quienes cambian de carrera</a></li><li><a href="https://www.freecodecamp.org/news/how-to-write-a-developer-resume-recruiters-will-read/">Cómo escribir un currículum de desarrollador que leerán los reclutadores</a></li><li><a href="https://www.freecodecamp.org/news/how-to-write-an-awesome-junior-developer-resume-in-a-few-simple-steps-316010db80ec/">Cómo escribir un impresionante currículum de desarrollador junior en unos simples pasos</a></li></ul><h3 id="recursos-para-entrevistas-t-cnicas">Recursos para entrevistas técnicas</h3><ul><li><a href="https://www.freecodecamp.org/news/how-to-prepare-for-a-technical-interview/">Cómo prepararse para una entrevista técnica</a></li><li><a href="https://www.freecodecamp.org/news/how-to-answer-any-technical-interview-question-with-example/">Cómo responder cualquier pregunta técnica de la entrevista: ejemplo incluido</a></li><li><a href="https://www.freecodecamp.org/news/what-i-learned-from-doing-60-technical-interviews-in-30-days/">Lo que aprendí al hacer más de 60 entrevistas técnicas en 30 días</a></li><li><a href="https://www.freecodecamp.org/news/is-this-the-best-book-for-coding-interview-preparation/">El mejor libro para la preparación de la entrevista de codificación técnica</a></li><li><a href="https://www.freecodecamp.org/news/interviewing-prep-tips-and-tricks/">Cómo prepararse para una entrevista técnica: consejos y trucos para ayudarlo a rendir al máximo</a></li></ul><h3 id="consejos-para-conseguir-un-trabajo">Consejos para conseguir un trabajo</h3><ul><li><a href="https://www.freecodecamp.org/news/networking-for-aspiring-developers/">Cómo obtener su primer trabajo de desarrollador a través de networking genuino e inteligente</a></li><li><a href="https://www.youtube.com/watch?v=K3B5AltcCTY">Consejos para la búsqueda de empleo en 2021</a></li><li><a href="https://www.youtube.com/watch?v=KPzFCZ_u_sY">Cómo dominar su búsqueda de empleo</a></li><li><a href="https://www.youtube.com/watch?v=SG5Sb5WTV_g">¡CÓMO USAR LINKEDIN COMO DESARROLLADOR para conseguir un trabajo en tecnología! ¡Cómo hacer networking!</a></li></ul><p>También incluiré recursos sobre cómo comenzar a trabajar independientemente si está interesado en seguir esa ruta.</p><ul><li><a href="https://www.freecodecamp.org/news/what-is-freelancing/">¿Qué es Freelance? Cómo encontrar trabajos independientes en línea y clientes en tu ciudad</a></li><li><a href="https://www.freecodecamp.org/news/free-web-design-proposal-template/">Cómo escribir propuestas independientes que atraigan a los clientes + una plantilla gratuita</a></li><li><a href="https://www.freecodecamp.org/news/freelance-web-developer-guide/">La guía completa para desarrolladores web independientes: Cómo ganar dinero a través de trabajos de programación independientes</a></li></ul><p><br>Espero que hayas disfrutado este artículo. Te deseo mucha suerte en tu camino para convertirte en un desarrollador Full Stack.</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
