Files
open-daycare/specs/02-kids-list-and-profile.md
T

115 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC 02 — Niños: listado y perfil (`/kids` y `/kids/[slug]`)
> **Estado:** Verificada (2026-10-20)
> **Depende de:** SPEC 01
> **Fecha:** 2026-10-20
> **Objetivo:** Implementar las plantillas `references/pantallas/ninos.dc.html` y `references/pantallas/perfil-nino.dc.html` como `/kids` y `/kids/[slug]`, visualmente idénticas, con mock de 8 niños con ficha completa y buscador funcional.
## Alcance
**In:**
- Página `/kids` (`app/kids/page.tsx`): sidebar con "Niños" activo, encabezado (eyebrow "GESTIÓN" + título "Niños"), botón "Agregar niño" (→ `/agregar-nino`), buscador y grilla de 2 columnas con las 8 tarjetas de la plantilla.
- Buscador **funcional** (filtro client-side): filtra por nombre mientras se escribe, el contador "8 niños" se actualiza y muestra estado vacío si no hay coincidencias.
- Página `/kids/[slug]` (`app/kids/[slug]/page.tsx`): breadcrumb "Volver a Niños" (→ `/kids`), avatar 84px + nombre + "3 años · Sala Soles", botón "Editar" (→ `/agregar-nino`), recuadro naranja "Alergias y notas" (solo si el niño tiene notas), tarjeta de datos (nacimiento / sala / ingreso), columna derecha con botón "Resumen del día" (→ `/resumen-dia`), tarjeta "PADRES VINCULADOS" (badges ACTIVA/PENDIENTE) y link "Vincular otro padre" (→ `/vincular-padre`).
- Las 8 fichas de perfil existen: cada slug renderiza los datos propios del niño (nombre, avatar, alergias, fechas y padres), coherentes con la lista.
- Mock tipado en `app/data/kids.ts` con los 8 niños de la plantilla (Mateo con los datos exactos del perfil; los otros 7 con datos inventados coherentes con su fila de la lista).
- Componentes nuevos en `app/components/`: `KidCard.tsx` (tarjeta de la lista) y `KidsBrowser.tsx` (cliente: buscador + contador + grilla + estado vacío).
- Update de `app/components/Sidebar.tsx`: el link "Niños" pasa de `/ninos` a `/kids` (única referencia existente; SPEC 01 la cableó así).
- Hover de tarjeta según plantilla: borde `#F2A78E` + `translateY(-2px)` con transición de 150ms.
**Out of scope (para specs futuras):**
- Páginas `/agregar-nino`, `/vincular-padre` y `/resumen-dia` (hoy dan 404 y está bien).
- Alta/edición real de niños y vinculación real de padres.
- Autenticación, base de datos y persistencia.
- Responsive móvil y dark mode.
## Modelo de datos
```ts
// app/data/kids.ts
export type ParentStatus = "activa" | "pendiente";
export interface Parent {
name: string; // "Lucía Fernández"
role: string; // "Mamá" | "Papá"
initial: string; // "L"
bg: string; // "#C9B6E8"
status: ParentStatus; // badge ACTIVA (verde) | PENDIENTE (amarillo)
}
export interface Kid {
slug: string; // "mateo-fernandez"
name: string; // "Mateo Fernández"
initial: string; // "M"
avatar: { bg: string; text: string }; // { bg: "#A9D9E8", text: "#1F7A93" }
age: number; // 3 → se renderiza "3 años"
allergies: string[]; // ["MANÍ"] — badges de la lista (vacos = sin badge)
allergyNote?: string; // texto del recuadro del perfil (si existe → se muestra)
birthDate: string; // "12 mar 2022"
room: string; // "Soles"
admission: string; // "feb 2025"
parents: Parent[];
}
```
- Los 8 niños con sus colores exactos de la plantilla: Mateo `#A9D9E8/#1F7A93`, Sofía `#F4B8CC/#C44A7A`, Benjamín `#B9DEC4/#3E8B62`, Valentina `#F4DC8E/#9A7B1E`, Tomás `#C9B6E8/#7B5FC0`, Emma `#F4B8CC/#C44A7A`, Lucas `#A9D9E8/#1F7A93`, Olivia `#B9DEC4/#3E8B62`.
- Regla del badge lateral en la lista (deducida de la plantilla): sin padres → badge "VINCULAR" (`#F9D2DE`/`#C56486`); con alergia → primera alergia (`#FBD8CC`/`#D9684A`); resto → chevron `#CBB89F`.
- Padres de Mateo (exactos de la plantilla): Lucía Fernández (Mamá, `#C9B6E8`, activa) y Diego Fernández (Papá, `#A9C7E8`, pendiente "invitación enviada").
Convenciones heredadas de SPEC 01: colores como hex exactos en utilities Tailwind arbitrarias; cero atributos `style`; todo server components salvo `KidsBrowser`.
## Plan de implementación
1. **Mock** — crear `app/data/kids.ts` con los tipos y los 8 niños. Test manual: `npm run dev` compila.
2. **KidCard** — crear `app/components/KidCard.tsx`: avatar circular, nombre, "X años · N padre(s) vinculado(s)", badge según la regla y link a `/kids/[slug]` con hover de plantilla. Test manual: se ve correcta suelta en cualquier página temporal.
3. **KidsBrowser (cliente)** — crear `app/components/KidsBrowser.tsx`: input "Buscar niño…", fila "SALA SOLES + contador + línea divisoria", grilla `grid-cols-2` filtrada por nombre (comparación sin mayúsculas ni acentos vía `normalize("NFD")`) y mensaje de estado vacío.
4. **Página /kids** — crear `app/kids/page.tsx`: Sidebar `activeHref="/kids"`, encabezado, botón "Agregar niño" y `<KidsBrowser kids={kids}>`. Test manual: `http://localhost:3000/kids` muestra las 8 tarjetas y el filtro funciona.
5. **Sidebar** — en `app/components/Sidebar.tsx` cambiar el href de "Niños" de `/ninos` a `/kids`. Test manual: el link navega y ninguna página queda rota.
6. **Página /kids/[slug]** — crear `app/kids/[slug]/page.tsx` con `generateStaticParams` y `notFound()`: en Next.js 16 `params` es una Promise (`const { slug } = await params`). Layout de dos columnas (columna izquierda flexible + derecha fija de 300px), ficha completa, recuadro de alergias condicionado y tarjeta de padres con badges ACTIVA/PENDIENTE. Test manual: `/kids/mateo-fernandez` replica la plantilla y `/kids/valentina-soto` muestra su ficha sin recuadro de alergias ni padres.
7. **Fidelidad visual** — comparación lado a lado de ambas pantallas contra las plantillas a ancho ≥1280px; ajustar diferencias. Capturas de verificación en `.playwright-mcp/`.
## Criterios de aceptación
- [x] `npm run dev` levanta sin errores y `/kids` muestra eyebrow "GESTIÓN", título "Niños", botón "Agregar niño", buscador y las 8 tarjetas en grilla de 2 columnas. *(Verificado 2026-10-20 en `localhost:3001` a 1440×900; consola limpia tras el opt-out `instant = false`. Computed: h1 "Niños" Fredoka 30px, `grid-template-columns: 393px 393px`, 8 tarjetas. Captura: `.playwright-mcp/verify-kids-final.png`.)*
- [x] `npm run build` completa sin errores, incluyendo el prerender de `/kids` y de los 8 slugs. *(Next.js 16.4.2 + Turbopack: compilación y TypeScript OK, 14 páginas estáticas — `/kids` ○ y los 8 slugs listados como estáticos.)*
- [x] Escribir "sof" en el buscador deja solo la tarjeta de Sofía Méndez, el contador se actualiza y escribir algo sin coincidencias muestra el estado vacío. *(Computed: "sof" → 1 tarjeta "Sofía Méndez" y contador "1 niño"; "zzzqx" → 0 tarjetas, mensaje "No hay niños que coincidan con la búsqueda." y "0 niños".)*
- [x] Escribir "sofia" (sin acento) también encuentra a Sofía Méndez. *(Computed: "sofia" sin acento devuelve exactamente esa tarjeta.)*
- [x] Cada tarjeta enlaza con `<Link>` al slug de su niño (8 hrefs distintos) y el badge es correcto: Mateo "MANÍ", Tomás "LACTOSA", Valentina "VINCULAR", resto chevron. *(Computed: 8 hrefs `/kids/<slug>` únicos; badges correctos; `next/link` importado en `KidCard` y 0 `<a>` planos en los archivos nuevos.)*
- [x] `/kids/mateo-fernandez` replica la plantilla: breadcrumb, avatar 84px "M", nombre, "3 años · Sala Soles", botón "Editar", recuadro "Alergias y notas" con el texto exacto ("Alergia al maní. Evitar frutos secos. Lleva inhalador en la mochila."), filas nacimiento/sala/ingreso, botón "Resumen del día" y tarjeta "PADRES VINCULADOS" con Lucía (ACTIVA, verde) y Diego (PENDIENTE, amarillo) + "Vincular otro padre". *(Computed: breadcrumb → `/kids`, avatar 84×84 Fredoka "M", texto de alergias literal, filas "12 mar 2022 / Soles / feb 2025", badges `rgb(207,235,216)`/`rgb(62,155,108)` y `rgb(247,231,166)`/`rgb(154,123,30)`. Captura: `.playwright-mcp/verify-perfil-final.png`; comparación visual contra `perfil-nino.dc.html` sin diferencias.)*
- [x] Los otros 7 slugs muestran la ficha propia de cada niño (nombre, avatar, edad, padres y alergias coherentes con la lista); Valentina no tiene recuadro de alergias ni padres listados. *(Fetch del HTML SSR de cada slug: nombre, inicial de avatar y edad·sala correctos; recuadro de alergias solo en Tomás; filas de padres 1/2/0/1/1/1/1 según la lista; Valentina sin recuadro y sin badges.)*
- [x] `/kids/no-existe` responde 404. *(HTTP 404 real con página de not-found; patrón `generateStaticParams` + `notFound()` tras `await params` validado contra la doc de Next.js (Context7) y `node_modules/next/dist/docs/`.)*
- [x] En el sidebar, "Niños" apunta a `/kids` y aparece activo (fondo `#FBE3D8`, texto `#D9583C`) tanto en `/kids` como en `/kids/[slug]`; en `/` sigue activo "Feed". *(Computed en `/kids`, `/kids/tomas-diaz` y `/`: activo "Niños" con `rgb(251,227,216)`/`rgb(217,88,60)` en lista y ficha; en `/` el activo es "Feed" y "Niños" queda inactivo `rgb(110,99,89)`.)*
- [x] Los links futuros del perfil navegan con `<Link>`: Editar → `/agregar-nino`, Resumen del día → `/resumen-dia`, Vincular otro padre → `/vincular-padre` (hoy 404, aceptado). *(Computed: los 3 hrefs presentes en el DOM vía `next/link`; destinos 404 según lo acordado.)*
- [x] A ancho ≥1280px ambas pantallas coinciden con las plantillas: lista con contenido centrado de 880px y perfil de 820px con columna derecha de 300px. *(Computed a 1440px: lista `max-width: 880px` centrada (left 404 = 248 de sidebar + 156), perfil 820px y columna 300px. Comparación visual lado a lado contra `ninos.dc.html` y `perfil-nino.dc.html` sin diferencias: `.playwright-mcp/spec02-ref-ninos.png` vs `.playwright-mcp/spec02-app-ninos-v2.png`, `.playwright-mcp/spec02-ref-perfil.png` vs `.playwright-mcp/spec02-app-perfil.png`.)*
- [x] Los archivos nuevos no usan atributos `style`; todo es Tailwind con valores arbitrarios exactos. *(Grep `style=` en `app/`: 0 resultados; `tsc --noEmit` y `eslint` limpios sobre los 6 archivos.)*
## Decisiones
- **Sí:** rutas `/kids` y `/kids/[slug]` — decisión explícita del usuario aunque el sidebar decía `/ninos`; se actualiza `Sidebar.tsx` (única referencia, así ningún criterio de SPEC 01 queda huérfano sin notarlo).
- **Sí:** los 8 niños con ficha completa en `app/data/kids.ts` — el perfil es una página por slug real, no un placeholder clonado.
- **Sí:** datos inventados para los otros 7 perfiles, pero coherentes con la lista (misma edad, misma cantidad de padres, mismas alergias); solo Mateo conserva los datos literales de la plantilla.
- **Sí:** buscador funcional con un único componente cliente (`KidsBrowser`) — es la primera pieza interactiva del repo y queda aislada; el resto sigue siendo server components.
- **Sí:** búsqueda solo por nombre, insensible a mayúsculas y acentos — la plantilla dice "Buscar niño…" y en español los acentos son el caso de uso obvio.
- **Sí:** regla de badge deducida de la plantilla (sin padres → VINCULAR, con alergia → alergia, resto → chevron) — los 3 casos existen en el diseño original.
- **Sí:** recuadro de alergias solo cuando `allergyNote` existe — la plantilla no define cómo se ve un perfil sin alergias y omitirlo es lo más fiel.
- **Sí:** perfil compuesto directo en `page.tsx` sin componentes de un solo uso — solo se extraen `KidCard` y `KidsBrowser`, que se reutilizan o aíslan estado.
- **Sí:** `generateStaticParams` + `notFound()` — mock estático: los 8 slugs se prerenderizan y cualquier otro slug da 404 real.
- **No:** filtrar por sala, edad o alergia — no está en la plantilla.
- **No:** páginas `/agregar-nino`, `/vincular-padre` ni `/resumen-dia` — specs futuras.
## Riesgos
| Riesgo | Mitigación |
| --- | --- |
| El buscador cliente rompe la convención server-only del repo | `KidsBrowser` aísla el estado; `KidCard` sigue siendo puro y reutilizable |
| Búsqueda lenta o hidratación inconsistente al filtrar | Filtrado en memoria sobre 8 items; el grid se deriva del mismo array tipado |
| Links a páginas inexistentes confunden en la verificación | Documentado en Out of scope y en criterios: 404 esperado, igual que SPEC 01 |
## Qué **no** está en esta spec
- Páginas agregar/editar niño, vincular padre y resumen del día — cada una en su propia spec.
- Autenticación, base de datos y persistencia.
- Responsive móvil y dark mode.