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

12 KiB
Raw Blame History

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

// 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

  • 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.)
  • 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.)
  • 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".)
  • Escribir "sofia" (sin acento) también encuentra a Sofía Méndez. (Computed: "sofia" sin acento devuelve exactamente esa tarjeta.)
  • 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.)
  • /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.)
  • 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.)
  • /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/.)
  • 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).)
  • 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.)
  • 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.)
  • 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.