Módulo 8: Project Build Mercados Storefront

Presentación del módulo: los siete módulos, un método

Por qué este módulo existe aquí

Llegaste al final. En los siete módulos anteriores aprendiste las herramientas de React una por una, cada una con su propio foco, su propia analogía y su propio mini-proyecto. Este módulo no trae ninguna herramienta nueva: trae la integración. Su tarea es la que enfrentas el primer día de un trabajo real, cuando ya no te dan un tema aislado sino una pantalla entera y te dicen "constrúyela". Vas a construir el storefront de Mercado completo —la barra de búsqueda, la lista de productos y el carrito, vivos y coordinados— usando los siete módulos a la vez.

Detente un momento en algo que es fácil pasar por alto: aprendiste siete cosas, pero no son siete cosas. Son un solo método de construir interfaces, partido en siete lecciones para poder digerirlo. El hilo que las cose es la tesis de toda la guía: la UI es una función del estado, UI = f(estado). Describes cómo se ve la interfaz para un estado dado, y React se encarga de pintar el DOM cuando el estado cambia. Cada módulo agregó una pieza a ese método:

  • M1 — componentes. Partiste la pantalla en piezas nombradas (App, SearchBar, ProductList, ProductCard, Cart, CartItem), cada una una función que recibe props y devuelve UI. La descomposición.
  • M2 — JSX. Aprendiste a describir esa UI: expresiones {}, atributos (className, htmlFor), condicionales (&&, ternario) y listas con .map() y key. La descripción declarativa.
  • M3 — estado. Le diste memoria a un componente con useState: el estado que persiste entre renders y, al cambiar, dispara el re-render. El motor del cambio.
  • M4 — eventos. Conectaste al usuario: onClick, onChange, inputs controlados, y callbacks que suben datos hacia arriba (onAddToCart). La interacción.
  • M5 — estado derivado. Aprendiste a derivar, no guardar: la lista visible se computa del estado fuente en cada render, no se almacena. El estado mínimo.
  • M6 — efectos. Sincronizaste con el mundo externo: useEffect para cargar datos, con su array de dependencias y su cleanup. El puente al afuera.
  • M7 — levantar el estado. Pusiste el estado compartido en el ancestro común (App) y consolidaste la lógica del carrito en el cartReducer con useReducer. La coordinación.

Ninguno de esos módulos, solo, construye una app. Juntos, . Este capstone es el momento en que las siete piezas dejan de ser siete lecciones y se vuelven lo que siempre fueron: el método para construir el storefront.

Conexión con el módulo

Este es el módulo 8, y es distinto de los siete anteriores en una cosa: no introduce concepto, lo aplica todo. Por eso está armado como un proyecto que crece por capas, y cada lección es una capa que se apoya en la anterior. La lección 2 monta el andamiaje —los seis componentes compuestos, con datos fijos (M1-M2)—. La 3 le conecta la carga de datos con un efecto (M6). La 4 agrega la búsqueda controlada y la lista derivada (M3, M4, M5). La 5 mete el carrito levantado con su reducer (M3, M7). La 6 conecta los callbacks que hacen que todo responda al usuario (M4, M7). Y la 7 pule: key estable y estados vacíos (M2, M5). La lección 8 es el entregable —el App completo, la rúbrica, y la lógica ejecutada— y el cierre de la guía entera, con el mapa de hacia dónde seguir.

Y la promesa de siempre, por última vez: nada se cita de memoria, todo se ejecuta. El JSX real del AppuseState, useReducer, useEffect, dispatch, la composición— se muestra en los bloques (es la API que escribirás); la lógica pura se ejecuta en Node con solo JavaScript. El cartReducer, el getVisibleProducts que deriva la lista, el cartTotal en centavos, y un mini renderToString que pinta el storefront entero corren de verdad, y su salida literal va en cada bloque "Qué esperar".

Una analogía: el edificio terminado

Imagina que aprendiste construcción en siete cursos separados. En uno viste planos: cómo dibujar una casa en el papel, cuartos y sus límites. En otro, estructura: columnas, vigas, lo que sostiene el peso. En otro, instalación eléctrica: cómo llega la corriente y qué la enciende. En otro, plomería: cómo entra y sale el agua. En otro, acabados: pintura, pisos, lo que se ve. Cada curso te dio una habilidad real, y en cada uno construiste una maqueta de práctica de esa parte: unos planos sueltos, una columna de muestra, un tablero eléctrico de prueba.

Pero un edificio no es ninguna de esas maquetas. Un edificio es las siete cosas juntas, en el orden correcto, sosteniéndose entre sí: los planos guían la estructura, la estructura aloja las instalaciones, las instalaciones se esconden tras los acabados, y el resultado es un lugar donde alguien vive. El día que construyes tu primer edificio de verdad no aprendes una octava habilidad; ordenas las siete que ya tienes y las ves colaborar. Ese día entiendes que nunca fueron siete oficios: eran siete fases de uno solo, el de construir.

Este módulo es ese día. Los siete módulos de la guía fueron tus siete cursos; sus mini-proyectos, tus maquetas de práctica. El storefront de Mercado es tu primer edificio: los componentes son los cuartos (M1), el JSX son los planos que los describen (M2), el estado es la corriente que hace que las cosas prendan y cambien (M3), los eventos son los interruptores que el usuario toca (M4), el estado derivado es no guardar dos veces lo que puedes calcular (M5), los efectos son las tuberías que conectan con el afuera (M6), y levantar el estado es poner el tablero central donde todos los cuartos leen la misma corriente (M7). Hoy no aprendes una fase nueva; ordenas las siete y ves el edificio en pie.

El caso: el storefront de Mercado, completo

Ya conoces cada pieza; esta es la primera vez que las ves todas en el mismo árbol, con el rol de cada módulo anotado. El App es la raíz: es dueño del estado (query y cart) y del efecto que carga los products. De ahí bajan los datos por props a los hijos, y suben los callbacks:

flowchart TD
    App["App<br/>estado: query (useState) · cart (useReducer)<br/>efecto: cargar products al montar"]
    App -- "query" --> SB["SearchBar<br/>input controlado"]
    App -- "products (derivados por query)" --> PL["ProductList"]
    App -- "items = cart" --> Cart["Cart"]
    PL --> PC1["ProductCard"]
    PL --> PC2["ProductCard"]
    Cart --> CI1["CartItem"]
    Cart --> CI2["CartItem"]
    SB -. "onSearch(text)" .-> App
    PC1 -. "onAddToCart(product)" .-> App
    CI1 -. "onRemoveFromCart(id)" .-> App

Lee el árbol módulo por módulo. La descomposición en seis componentes es M1. Que cada uno se describa con JSX y liste con .map() es M2. Que el App tenga query y cart como estado es M3. Que la SearchBar sea un input controlado y que los callbacks (onSearch, onAddToCart, onRemoveFromCart) suban por las flechas punteadas es M4. Que la lista que baja a ProductList esté derivada de query (no guardada) es M5. Que los products se carguen al montar es M6. Y que query y cart vivan arriba, en el App (el ancestro común de los hermanos que los tocan), y bajen por props, es M7. Un solo diagrama, los siete módulos.

Recuerda los datos, que son los de siempre. Un producto (Product) tiene id, name, priceCents (el precio en centavos, entero: 2599), category e inStock; al mostrarlo lo formateamos con formatPrice(2599)"$25.99". El carrito es un array de líneas { id, name, priceCents, qty }. El estado fuente es corto —query y cart—; todo lo demás (la lista visible, el total, "¿está vacío?") se deriva.

Ejemplo trabajado: el storefront corriendo (el teaser)

Antes de armar nada por capas, veamos el destino: el storefront entero, renderizado a HTML, salido de Node. Este es el resultado de componer los seis componentes y pedirle al mini renderToString que pinte el árbol con datos fijos —el catálogo completo y un carrito vacío—. No te preocupes por el código todavía (lo construimos capa por capa desde la lección 2); mira lo que produce, para tener el destino claro:

// (el App completo compuesto de sus seis componentes + el mini renderToString;
//  lo construimos entero en las lecciones 2-7. Aqui, solo su salida.)
console.log(renderToString(App({ products: PRODUCTS, query: '', cart: [] })));

Qué esperar. Al correr el storefront con Node, la salida es exactamente esta:

<main class="storefront">
  <div class="search-bar">
    <label for="product-search">Search products</label>
    <input id="product-search" type="search" class="search-input" placeholder="Search products..." value="" />
  </div>
  <section class="product-list">
    <article class="product-card">
      <h3 class="product-name">Wireless Mouse</h3>
      <p class="product-price">$25.99</p>
      <span class="product-stock">In stock</span>
      <button class="add-btn">Add to cart</button>
    </article>
    <article class="product-card">
      <h3 class="product-name">Mechanical Keyboard</h3>
      <p class="product-price">$89.00</p>
      <span class="product-stock">Out of stock</span>
      <span class="badge badge-out">Sold out</span>
      <button class="add-btn" disabled>Add to cart</button>
    </article>
    <article class="product-card">
      <h3 class="product-name">USB-C Hub</h3>
      <p class="product-price">$34.99</p>
      <span class="product-stock">In stock</span>
      <button class="add-btn">Add to cart</button>
    </article>
    <article class="product-card">
      <h3 class="product-name">Laptop Stand</h3>
      <p class="product-price">$45.00</p>
      <span class="product-stock">In stock</span>
      <button class="add-btn">Add to cart</button>
    </article>
    <article class="product-card">
      <h3 class="product-name">Desk Lamp</h3>
      <p class="product-price">$19.99</p>
      <span class="product-stock">In stock</span>
      <button class="add-btn">Add to cart</button>
    </article>
  </section>
  <aside class="cart">
    <h2 class="cart-title">Your cart</h2>
    <p class="empty-state">Your cart is empty</p>
    <p class="cart-total">Total: $0.00</p>
  </aside>
</main>

Ese bloque de HTML es el destino de este módulo, y ya reconoces cada parte. El <main class="storefront"> es el App (M1) componiendo (M1) sus tres zonas. El <div class="search-bar"> con su <input> y su value="" es la SearchBar controlada (M4), lista para reflejar el query (M3). La <section class="product-list"> es el ProductList recorriendo los productos con .map() (M2): cinco <article>, uno por producto; el Mechanical Keyboard, agotado, muestra su badge "Sold out" con && (M2) y su botón disabled (M2). El <aside class="cart"> es el Cart, mostrando su estado vacío ("Your cart is empty") porque el carrito arranca sin items (M5, M7), con el total en $0.00 (formateado de centavos). Cuando el usuario empiece a buscar y a agregar, ese HTML cambiará solo, porque será una función del estado —pero eso es lo que armaremos en las próximas seis lecciones—.

Nota que este teaser tiene el catálogo estático y el carrito vacío: es la "foto fija" (lección 2). El resto del módulo le da vida: la carga (3), la búsqueda que filtra (4), el carrito que crece (5), los callbacks que conectan (6) y el pulido (7).

El plan de armado, capa por capa

Guarda esta ruta; es cómo cada lección agrega una capa hasta el storefront completo:

Capa                              Leccion   Modulos que integra
────────────────────────────────  ────────  ────────────────────────────────────
El andamiaje del arbol            L2        M1 (componentes) + M2 (JSX, listas)
Cargar los productos (efecto)     L3        M6 (useEffect + cleanup)
Busqueda controlada + derivada    L4        M3 (estado) + M4 (controlado) + M5 (derivar)
El carrito con reducer            L5        M3 + M7 (levantar + cartReducer/useReducer)
Conectar los callbacks            L6        M4 (callbacks que suben) + M7 (una fuente)
Pulido: key estable, vacios       L7        M2 (key) + M5 (derivar el estado vacio)
────────────────────────────────  ────────  ────────────────────────────────────
El entregable + cierre de guia    L8        TODO, ejecutado + hacia donde seguir

La frontera: qué cierra aquí y qué sigue

Este módulo cierra la guía de fundamentos de React. Todo lo que aprendiste basta para construir el storefront de Mercado como app de cliente, y ahí termina el alcance de esta guía. Lo que sigue vive en las guías hermanas del ecosistema Fullstack, y este capstone te deja justo en la puerta de cada una:

  • Producción: routing, SSR/SSG, data fetching en el servidornextjs-app-router. Aquí el fetch de productos se simula en el cliente con useEffect (y viste sus trampas: Loading..., race conditions). Next.js lo hace en el servidor, sin ese trabajo manual.
  • Estado global y datos del servidor a escalafrontend-state-and-data. Aquí el query y el cart viven en el App con useState/useReducer. Cuando el estado lo necesita medio árbol (Context) o hay que cachear y sincronizar con el servidor (React Query/SWR), esa es la otra guía.
  • Estilos y design systemsui-systems-and-design-implementation. Aquí las className (product-card, badge-out) son solo marcadores; Tailwind, tokens y componentes de diseño se enseñan allá.
  • Performance, build y deployfullstack-performance-and-deployment. Optimizar re-renders (memo), dividir el bundle y publicar la app.

No necesitas nada de eso para terminar el storefront. Lo mencionamos para que sepas que el camino sigue, y en qué orden.

Errores comunes

Encarar el capstone como si fuera un tema nuevo. Qué pasa: al ver "proyecto final", alguien siente que le falta aprender algo más antes de poder construirlo, y se traba buscando un concepto que no existe. Por qué pasa: los mini-proyectos anteriores integraban un módulo; este integra siete, y esa suma se siente como material nuevo. Cómo detectarlo: relees los módulos buscando "la pieza que falta". Cómo corregirlo: no falta ninguna pieza. El capstone es ordenar lo que ya sabes, no aprender más. Si sabes descomponer, describir con JSX, dar estado, conectar eventos, derivar, cargar con un efecto y levantar el estado, tienes todo. El trabajo es de integración y orden, no de contenido nuevo.

Querer construir todo de una vez. Qué pasa: se intenta escribir el App completo —estado, efecto, derivación, reducer, callbacks, pulido— en una sola sentada, y a la mitad todo está a medias y nada funciona, así que es imposible saber qué está mal. Por qué pasa: la app final se ve como un bloque monolítico. Cómo detectarlo: tienes cincuenta líneas escritas y ni una que puedas probar sola. Cómo corregirlo: arma por capas, como este módulo. Monta el árbol estático y compruébalo; agrega la carga y compruébala; agrega la búsqueda y compruébala; y así. Cada capa se prueba antes de la siguiente. Es exactamente el orden de las lecciones 2 a 7, y no es casualidad.

Guardar lo que se puede derivar (recaer en el hábito). Qué pasa: al armar el storefront, se crea un useState para la "lista visible" o para el "total del carrito", y se sincroniza a mano en cada handler. Por qué pasa: bajo la presión de "hacer el proyecto completo", se olvida el criterio del módulo 5 y se guarda de más. Cómo detectarlo: tienes que llamar a un setVisible o setTotal desde varios lugares, y algo se desincroniza. Cómo corregirlo: el estado fuente es corto (query y cart); la lista visible, el total y "¿está vacío?" se derivan en el render. El capstone no es excusa para abandonar el criterio: es donde más se nota que derivar mantiene la app coherente.

Ejercicios

Ejercicio 1 — Mapea cada zona a su módulo. Mira el HTML del teaser. Para cada una de estas tres zonas, di qué módulo(s) de la guía la hacen posible: (a) el <input ... value="" /> de la search-bar; (b) los cinco <article class="product-card"> de la product-list; (c) el <p class="empty-state">Your cart is empty</p> del cart.

Ver solución
  • (a) El input controlado: es un componente (SearchBar, M1) descrito con JSX y su atributo value como expresión (M2); ese value viene del estado query (M3) y responde al tecleo con onChange como input controlado (M4). En el teaser value="" porque el query inicial es vacío.
  • (b) Las tarjetas: el ProductList recorre los productos con .map() y una key estable (M2), renderizando un ProductCard por producto —composición (M1)—. La lista que recibe está derivada de products y query (M5), y products se cargó al montar con un efecto (M6).
  • (c) El estado vacío del carrito: el Cart muestra "Your cart is empty" con un condicional (M2) porque el cartlevantado en el App y manejado por el cartReducer (M7)— está vacío; que "está vacío" no se guarda, se deriva de la longitud del carrito (M5).

Casi cada pixel del storefront toca varios módulos a la vez. Esa es la integración del capstone.

Ejercicio 2 — Ordena las capas. Te dan estos seis pasos desordenados para construir el storefront: (i) conectar onAddToCart para que agregue al carrito; (ii) montar el árbol de componentes con datos fijos; (iii) derivar la lista visible de query; (iv) cargar los productos con un efecto; (v) poner el cart con su reducer en el App; (vi) agregar los estados vacíos y la key. Ordénalos de forma que cada paso se apoye en el anterior, y justifica el primero y el último.

Ver solución

El orden es el de las lecciones 2 a 7: (ii) → (iv) → (iii) → (v) → (i) → (vi).

  • Primero (ii), montar el árbol con datos fijos, porque no puedes conectar estado, efectos ni callbacks a componentes que todavía no existen. Necesitas el andamiaje —la "foto fija"— antes de darle vida. Es lo más barato de probar y lo que sostiene todo lo demás.
  • Luego (iv) cargar los productos (para tener datos reales que mostrar), (iii) derivar la lista de la búsqueda, (v) meter el carrito con su reducer, e (i) conectar los callbacks que hacen que agregar funcione.
  • Último (vi), el pulido (key estable, estados vacíos), porque son refinamientos sobre una app que ya funciona: no tiene sentido pulir estados vacíos de una lista que aún no se filtra, ni cuidar la key de tarjetas que aún no tienen estado local. El pulido va al final, cuando ya hay algo que pulir.

La regla: estructura antes que comportamiento, comportamiento antes que refinamiento.

Ejercicio 3 — ¿De esta guía o de la siguiente? Para cada requisito que Mercado podría pedir, di si lo resuelves con lo de esta guía (este capstone) o si es de una guía hermana (y cuál): (a) que al agregar un producto el carrito y su total se actualicen al instante; (b) que la URL cambie a /product/p1 al abrir un producto y se pueda compartir; (c) que el tema claro/oscuro lo lean botones y tarjetas por todo el árbol; (d) que el catálogo se renderice en el servidor para cargar más rápido.

Ver solución
  • (a) Esta guía. Estado (cart) + reducer + total derivado + una fuente de verdad levantada en el App. Es exactamente lo que construye este capstone (M3, M5, M7).
  • (b) nextjs-app-router. URLs, routing y páginas compartibles son cosa del App Router. Aquí no hay routing.
  • (c) frontend-state-and-data. Un dato que necesita medio árbol en muchas ramas → Context (o estado global). Aquí lo mencionamos; se enseña allá.
  • (d) nextjs-app-router. Renderizar en el servidor (SSR/Server Components) para acelerar la carga. Aquí el render es de cliente; el fetch se simula con useEffect.

La regla mental: construir la UI interactiva de una tienda con estado local/levantado es de esta guía; routing, SSR, estado global a escala y datos del servidor son de las hermanas. Este capstone te deja en la puerta de todas.

Resumen y siguiente paso

En esta presentación viste el sentido del capstone: los siete módulos de la guía no son siete temas sueltos, sino un solo método de construir una app React —UI = f(estado)— partido en siete para poder aprenderlo. Recorriste cada módulo (componentes, JSX, estado, eventos, derivar, efectos, levantar el estado) y su rol en el storefront, anclaste la idea con el edificio terminado (siete fases de un oficio, no siete oficios), y viste el destino: el HTML completo del storefront salido de Node, con cada zona mapeada a su módulo. Tienes el plan de armado por capas (lecciones 2 a 7) y la frontera con las guías de Fullstack que siguen.

Antes de avanzar deberías poder: nombrar los siete módulos y qué aporta cada uno al storefront; leer el árbol de componentes y ubicar dónde vive el estado y qué se deriva; explicar por qué se arma por capas (estructura → comportamiento → refinamiento); y distinguir qué resuelve esta guía de qué resuelven las hermanas.

La lección 2 empieza a construir: el andamiaje del árbol. Montarás los seis componentes y los compondrás con datos fijos —el estado y los eventos llegan en las capas siguientes—, y ejecutarás el App completo en Node para ver salir el HTML del storefront entero, catálogo y carrito. Es la "foto fija" sobre la que todo lo demás se apoya: primero la estructura, y después la vida.

Recursos

  • React, "Thinking in React" — react.dev/learn/thinking-in-react. El ensayo canónico que convierte una pantalla en una app React en pasos ordenados (descomponer, construir una versión estática, encontrar el estado mínimo, decidir dónde vive, conectar los eventos). Es, literalmente, el método de este capstone. En inglés.
  • React, "Tutorial: Tic-Tac-Toe" — react.dev/learn/tutorial-tic-tac-toe. El tutorial oficial que construye una app completa de principio a fin integrando componentes, estado, eventos y levantar el estado —el mismo tipo de integración que harás aquí, en otro caso—. En inglés.
  • React, "Adding Interactivity" — react.dev/learn/adding-interactivity. El repaso de estado y eventos que el storefront pone en práctica. Útil para consolidar M3-M4 antes de armar. En inglés.
  • React, "Managing State" — react.dev/learn/managing-state. El repaso de estado derivado, levantar el estado y reducers —lo que el carrito y la lista derivada integran (M5, M7)—. En inglés.