7 F7. Mini-proyecto: la UI de Taskflow
Todo lo de la Sección I junto: componentes, estado y llamadas a tu API
Vas a construir el frontend de Taskflow completo: una lista de tareas que se carga desde la API, un input para crear nuevas, y los estados loading/error — en React. Es el puente: cuando llegues a la Sección II ya tendrás un cliente real esperando tu backend.
7.1 El problema
La Sección I te dio las piezas por separado: runtime, lenguaje, DOM, frameworks, llamadas a APIs, build. Ahora las juntamos en algo real: una UI que consume GET /tasks y POST /tasks — los endpoints que el libro construye en la Parte de backend.
El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.
El DOM es la página como árbol de objetos vivos: HTML es el papel, el DOM es lo que JavaScript puede tocar. element.textContent = "hola" cambia el DOM y el navegador repinta — el HTML original no se entera.
Un framework es un esqueleto de aplicación ya decidido: te da la estructura (rutas, validación, errores) y tú llenas la lógica. Diferencia con librería: la librería la llamas tú; el framework te llama a ti.
El build (construcción) es el proceso que convierte tu código fuente en algo ejecutable/entregable: compilar TypeScript a JS, empaquetar el frontend, armar la imagen Docker. Lo que se despliega es el resultado del build, no tu código crudo.
7.2 Cómo lo resuelve un equipo
Antes de escribir, el equipo define el contrato que la UI espera (el mismo que el backend promete):
GET /api/tasks → 200 [{ id, title, done }]
POST /api/tasks {title} → 201 { id, title, done }
Con el contrato claro, la UI se escribe sin backend todavía — contra un mock o contra la API cuando exista. Ese es el punto: el contrato permite que frontend y backend se construyan en paralelo.
Concurrencia = manejar muchas cosas en progreso (atender 1000 requests intercalando). Paralelismo = ejecutar varias a la vez en varios núcleos. Un camarero con 10 mesas es concurrente; 10 camareros son paralelos.
Un mock es un doble de pruebas: finge ser el servicio externo (Stripe, SMTP) para que el test no cobre ni mande emails. Se mockea en la frontera (tu adaptador), nunca la BD — fingir la BD es mentir el test.
7.3 Implementación
La app completa en React — estado, loading, error, lista y formulario:
Un array es una colección ordenada de elementos accedidos por posición: ["a","b","c"][0] es "a" (se cuenta desde 0). Python las llama listas, Go slices — misma idea, distinto acento.
El estado es todo lo que el programa recuerda: las variables, la sesión, lo que muestra la UI. Stateless (sin estado) = el servidor no recuerda nada entre requests — cada request trae todo lo necesario.
import { useState, useEffect } from "react";
const API = "/api"; // mismo-origen en producción; o VITE_API_URL
export default function TasksPage() {
const [tasks, setTasks] = useState([]);
const [title, setTitle] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { // cargar al montar
fetch(`${API}/tasks`)
.then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.then(setTasks)
.catch(setError)
.finally(() => setLoading(false));
}, []);
async function addTask(e) {
e.preventDefault();
const res = await fetch(`${API}/tasks`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
});
const created = await res.json();
setTasks((ts) => [...ts, created]); // agregar al estado → re-render
setTitle("");
}
if (loading) return <p>Cargando…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<form onSubmit={addTask}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<button>Agregar</button>
</form>
<ul>{tasks.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
</div>
);
}
Qué estás viendo: todo lo de la sección junto — useState (estado), useEffect (cargar al montar), fetch con res.ok, loading/error como parte de la UI, y setTasks que repinta solo.
Construye un componente React "TasksPage" que consuma mi API REST:
- GET /api/tasks devuelve [{id, title, done}]
- POST /api/tasks con body {title} devuelve la tarea creada (201)
Requisitos: useState para tasks/title/loading/error, useEffect para
cargar al montar, formulario controlado para crear, estados de
loading/error visibles en la UI, y base URL desde variable de entorno.
Usa fetch nativo — sin librerías extra — y explica cada hook que uses.
7.4 ¿Sin backend todavía?
Tres formas de desarrollar la UI sin que la API exista:
| Alternativa | Cómo | Cuándo |
|---|---|---|
| Mock en el frontend | const tasks = [...] local |
UI primero, API después |
| Mock server (json-server, MSW) | Un archivo JSON que simula la API | Desarrollo real en paralelo |
| La API real | La construyes en la Sección II | Cuando llegues ahí — encaja directo |
MSW (Mock Service Worker) es la favorita profesional: intercepta fetch y responde mocks — tu código de producción no cambia nada.
Producción (prod) es el entorno real: donde están los usuarios, los datos que importan y las consecuencias. Todo lo demás — local, staging — existe para que los errores ocurran antes de llegar ahí.
7.5 Errores comunes
- fetch en el body del componente (sin useEffect): se dispara en cada render — bucle infinito.
Un bucle repite una acción por cada elemento o hasta cumplir una condición: for task in tasks hace algo con cada tarea. map y filter son bucles disfrazados de funciones — transforman/filtran colecciones sin for explícito.
setTasks([...tasks, created])vstasks.push(): mutar no repinta en React — siempre crear array nuevo.- Olvidar
e.preventDefault()en el form: la página recarga entera.
7.6 Ejercicio
- Levanta esta página contra un array mock local primero — que funcione sin API.
- Cuando tengas la API (Sección II), cambia solo la base URL y verifica que todo sigue igual — eso es “contrato cumplido”.
Una función es una receta reutilizable: recibe ingredientes (parámetros), hace pasos y devuelve un plato (return). La escribes una vez y la llamas mil veces: add(2, 3) → 5.
7.7 Mini reto
Agrega el checkbox done: click marca la tarea hecha. ¿Qué endpoint necesitas? (Ya lo diseñaste en el Cap. 3: PATCH /tasks/{id}.)
Ejercicio 1. useState([{id:1,title:"a",done:false}]) inicial + el useEffect comentado — la UI funciona sin red; al conectar la API, solo cambia API.
Mini reto. PATCH /api/tasks/{id} con {done: true} → luego setTasks(ts => ts.map(t => t.id===id ? {...t, done:true} : t)). El patrón: mutación al servidor + actualización optimista del estado.
7.8 Vocabulario técnico del capítulo
Una variable es una caja con nombre donde guardas un valor: let total = 42 guarda el 42 bajo el nombre total. Puedes leerla y cambiarla después (total = 50). const = caja que no se puede reemplazar.
null (TS), None (Py), nil (Go) = “aquí no hay valor”. Es la respuesta a “¿qué devuelvo cuando no hay nada?” — y la fuente del bug más famoso de la historia (su inventor lo llamó “el error del billón de dólares”). Por eso el código revisa if x is not None.
return hace dos cosas a la vez: devuelve el resultado Y termina la función — lo que esté debajo nunca corre. return task = “aquí está el plato, salgo de la cocina”.
Asíncrono = empezar algo sin esperar sentado a que termine: pides la pizza (async) y sigues trabajando; cuando llega, te avisan. Lo opuesto a síncrono (esperar parado). Vital cuando la espera es larga: red, disco, bases de datos.
Un diccionario (dict en Python, map en Go, objeto en JS) guarda parejas clave→valor: {"ana": 30}. Es la estructura que más aparece en JSON — un objeto JS es un diccionario.
Un módulo es un archivo de código que exporta cosas para que otros archivos las importen. Es la unidad de organización: en vez de un archivo gigante, el código vive en módulos con responsabilidad propia.
Un entorno es una instancia completa donde corre tu app con su propia config y datos: local (tu máquina), staging (réplica de prueba), producción (la real). Cada entorno tiene sus propias llaves y su propia base de datos.
Una variable de entorno es configuración que vive fuera del código: DATABASE_URL, JWT_SECRET. El mismo binario corre en dev y prod con distintas vars — los secretos nunca se escriben en el código.
Un servidor es una computadora que espera peticiones y las responde 24/7. Físicamente no es nada mágico: es una máquina (a veces una VM alquilada) corriendo tu programa, con la diferencia de que está siempre encendida y conectada.