Aplicación de una sola página (SPA) en React 19 + Vite. Gestiona una lista de tareas con persistencia local, métricas en vivo y la identidad visual de APROS.
![]() |
![]() |
| Progreso recalculado al completar | Responsive (390px) |
Capturas generadas con Playwright (npx playwright test screenshots --project=chromium).
Autor: Juan Ordoñez.
SPA cliente, sin backend. Las tareas se crean, completan y eliminan en el navegador
y se persisten en localStorage, de modo que sobreviven a recargas. El estado base
arranca con tres tareas provistas por el requerimiento. El build de producción sale
sin warnings (≈196 kB JS / 62 kB gzip, ≈5 kB CSS).
Funcionalidad:
- Alta de tareas con validación (no acepta texto vacío ni solo espacios).
- Toggle de completado con estado visual (tachado + checkbox de marca).
- Borrado por tarea.
- Panel de métricas: completadas / total y barra de progreso.
Separación estricta de responsabilidades. Toda la lógica de negocio y la
persistencia viven en un Custom Hook, src/hooks/useTodos.js. Los componentes de
src/components/ son funcionales y solo se ocupan de presentación: reciben datos y
callbacks por props y no tocan localStorage ni mantienen estado de dominio.
useTodos() ──> { todos, addTodo, toggleTodo, deleteTodo, totalTodos, completedTodos }
│
├─ TodoApp (contenedor; invoca el hook y reparte props)
├─ TodoForm (input controlado + validación .trim())
├─ TodoStats (métricas + barra de progreso)
└─ TodoList ─ TodoItem (render de la colección / tarjeta individual)
Mutaciones inmutables: addTodo usa spread, toggleTodo reemplaza solo el objeto
afectado ({ ...t, completada: !t.completada }) y deleteTodo filtra a un arreglo
nuevo. Nunca se muta el estado anterior.
Rendimiento:
- Lazy initialization de
useState(cargarEstadoInicial): la lectura delocalStorageocurre una sola vez, no en cada render. useMemoparacompletedTodos, que solo se recalcula cuando cambiatodos.- Persistencia vía un
useEffectdependiente detodos; lectura y escritura envueltas entry/catchpara tolerar modo privado o cuota agotada sin romper la app. Si el JSON guardado está corrupto, cae a los datos iniciales.
IDs derivados de Math.max(...ids) + 1 en lugar de length + 1, para no colisionar
tras eliminar.
Suite E2E con Playwright sobre Chromium y WebKit (10 ejecuciones: 5 specs
× 2 motores). El ciclo de vida del servidor lo gestiona la propiedad webServer de
playwright.config.js: levanta npm run dev antes de las pruebas y lo apaga al
terminar, con reuseExistingServer: !process.env.CI. Cada test corre en un contexto
aislado, así que localStorage arranca vacío y la carga inicial es determinista.
| # | Spec | Qué certifica |
|---|---|---|
| 1 | Carga inicial | Renderiza las 3 tareas iniciales y métricas 1 / 3 (33%) |
| 2 | Inserción | Agrega tarea, limpia el input controlado e incrementa el total |
| 3 | Toggle | Completar recalcula métricas en vivo (1 → 2, 67%) y aplica el tachado |
| 4 | Borrado | Elimina el nodo del DOM y reduce el total |
| 5 | Persistencia | Tras page.reload() la tarea agregada sigue presente (hook ↔ localStorage) |
npm run test:e2e
# 10 passedRequiere Node 18+.
git clone https://github.com/Dabji/apros-react-todo.git
cd apros-react-todo
npm install
npm run dev # servidor de desarrollo (http://localhost:5173/apros-react-todo/)Pruebas:
npx playwright install chromium webkit # primera vez
npm run test:e2e # corre la suite completa
npx playwright show-report # informe HTML del último runEl despliegue se automatiza con gh-pages:
npm run deploypredeploy corre automáticamente antes de deploy (convención de npm). El comando
compila el proyecto y sube el contenido de dist/ a la rama remota gh-pages. Para
que las rutas de los assets resuelvan en el subdirectorio del repo,
vite.config.js fija base: '/apros-react-todo/'.
Tras el primer npm run deploy, activar GitHub Pages en Settings → Pages → Source:
deploy from branch → gh-pages / root. URL resultante:
https://dabji.github.io/apros-react-todo/
- TypeScript: tipar
Todoy la firma del hook para detectar errores en compilación. - Sub-tareas y prioridades: extender el modelo con
parentIdy un campoprioridad, más filtros/orden en la UI. - GitHub Actions: workflow que corra
npm run test:e2een cada PR y publique agh-pagesal hacer merge enmain. - Edición inline del texto de una tarea y deshacer borrado.
- Migración del store a
useReducero Zustand si el estado crece.
apros-react-todo/
├── public/favicon.svg
├── src/
│ ├── components/ TodoApp · TodoForm · TodoList · TodoItem · TodoStats
│ ├── hooks/ useTodos.js
│ ├── styles/ app.css
│ ├── App.jsx · main.jsx · index.css
├── tests/ todo.spec.js · screenshots.spec.js
├── docs/screenshots/
├── playwright.config.js
├── vite.config.js
└── package.json


