12 KiB
12 KiB
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.htmlyreferences/pantallas/perfil-nino.dc.htmlcomo/kidsy/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.tscon 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) yKidsBrowser.tsx(cliente: buscador + contador + grilla + estado vacío). - Update de
app/components/Sidebar.tsx: el link "Niños" pasa de/ninosa/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-padrey/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
- Mock — crear
app/data/kids.tscon los tipos y los 8 niños. Test manual:npm run devcompila. - 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. - KidsBrowser (cliente) — crear
app/components/KidsBrowser.tsx: input "Buscar niño…", fila "SALA SOLES + contador + línea divisoria", grillagrid-cols-2filtrada por nombre (comparación sin mayúsculas ni acentos víanormalize("NFD")) y mensaje de estado vacío. - Página /kids — crear
app/kids/page.tsx: SidebaractiveHref="/kids", encabezado, botón "Agregar niño" y<KidsBrowser kids={kids}>. Test manual:http://localhost:3000/kidsmuestra las 8 tarjetas y el filtro funciona. - Sidebar — en
app/components/Sidebar.tsxcambiar el href de "Niños" de/ninosa/kids. Test manual: el link navega y ninguna página queda rota. - Página /kids/[slug] — crear
app/kids/[slug]/page.tsxcongenerateStaticParamsynotFound(): en Next.js 16paramses 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-fernandezreplica la plantilla y/kids/valentina-sotomuestra su ficha sin recuadro de alergias ni padres. - 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 devlevanta sin errores y/kidsmuestra 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 enlocalhost:3001a 1440×900; consola limpia tras el opt-outinstant = false. Computed: h1 "Niños" Fredoka 30px,grid-template-columns: 393px 393px, 8 tarjetas. Captura:.playwright-mcp/verify-kids-final.png.)npm run buildcompleta sin errores, incluyendo el prerender de/kidsy 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/linkimportado enKidCardy 0<a>planos en los archivos nuevos.) /kids/mateo-fernandezreplica 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", badgesrgb(207,235,216)/rgb(62,155,108)yrgb(247,231,166)/rgb(154,123,30). Captura:.playwright-mcp/verify-perfil-final.png; comparación visual contraperfil-nino.dc.htmlsin 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-existeresponde 404. (HTTP 404 real con página de not-found; patróngenerateStaticParams+notFound()trasawait paramsvalidado contra la doc de Next.js (Context7) ynode_modules/next/dist/docs/.)- En el sidebar, "Niños" apunta a
/kidsy aparece activo (fondo#FBE3D8, texto#D9583C) tanto en/kidscomo en/kids/[slug]; en/sigue activo "Feed". (Computed en/kids,/kids/tomas-diazy/: activo "Niños" conrgb(251,227,216)/rgb(217,88,60)en lista y ficha; en/el activo es "Feed" y "Niños" queda inactivorgb(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íanext/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: 880pxcentrada (left 404 = 248 de sidebar + 156), perfil 820px y columna 300px. Comparación visual lado a lado contraninos.dc.htmlyperfil-nino.dc.htmlsin diferencias:.playwright-mcp/spec02-ref-ninos.pngvs.playwright-mcp/spec02-app-ninos-v2.png,.playwright-mcp/spec02-ref-perfil.pngvs.playwright-mcp/spec02-app-perfil.png.) - Los archivos nuevos no usan atributos
style; todo es Tailwind con valores arbitrarios exactos. (Grepstyle=enapp/: 0 resultados;tsc --noEmityeslintlimpios sobre los 6 archivos.)
Decisiones
- Sí: rutas
/kidsy/kids/[slug]— decisión explícita del usuario aunque el sidebar decía/ninos; se actualizaSidebar.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
allergyNoteexiste — 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.tsxsin componentes de un solo uso — solo se extraenKidCardyKidsBrowser, 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-padreni/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.