OpenDayCare es un prototipo frontend para la gestión de una guardería ficticia,
Sala Soles. Este repositorio también funciona como laboratorio de desarrollo
asistido por agentes, MCPs y Spec Driven Development (SDD).
La aplicación se construye a partir de especificaciones versionadas, skills reutilizables y validaciones realizadas por agentes. La interfaz actual incluye un feed diferenciado para personal y familias, el listado de niños y sus perfiles, la vinculación de padres mediante invitaciones y persistencia PostgreSQL tenant-scoped.
- Next.js
16.4.3con App Router. - React
19.2.8. - TypeScript estricto.
- Tailwind CSS
4y CSS Modules. - Fredoka y Nunito mediante
next/font/google. - npm como gestor de paquetes.
- Alias
@/*apuntando asrc/*.
Requiere una versión de Node.js compatible con Next.js 16. Después, instala las dependencias y arranca el servidor:
npm install
npm run devEl servidor de desarrollo y el servidor de producción usan explícitamente el
puerto 3000. Abre http://localhost:3000.
Si el puerto está ocupado, Next.js no debe iniciar otro servidor en un puerto aleatorio. Libera primero el proceso que lo usa en PowerShell:
Get-NetTCPConnection -LocalPort 3000 -State Listen |
Select-Object -ExpandProperty OwningProcess -Unique |
Stop-Process -ForceDespués vuelve a ejecutar npm run dev. Todas las rutas de App Router usan el
mismo origen y puerto; no se configura un puerto diferente por ruta.
Comandos disponibles:
| Comando | Uso |
|---|---|
npm run dev |
Servidor de desarrollo |
npm run build |
Build de producción |
npm run start |
Servidor con el build generado |
npm run lint |
Ejecución de ESLint |
npm run db:migrate:dry-run |
Comprueba migraciones pendientes en el proyecto Supabase remoto enlazado |
npm run db:migrate |
Aplica migraciones al proyecto Supabase remoto enlazado |
npm run db:seed |
Ejecuta explícitamente supabase/seed.sql en el proyecto Supabase remoto enlazado |
La migración actual y supabase/seed.sql pertenecen exclusivamente al baseline
de un proyecto Supabase nuevo, después de que SPEC 34 adapte el runtime. Está
terminantemente prohibido ejecutar npm run db:migrate o npm run db:seed contra
el proyecto Supabase remoto actualmente enlazado o contra cualquier proyecto que
contenga datos históricos.
Antes de ejecutar cualquiera de los dos comandos, verifica el proyecto enlazado y enlaza explícitamente el proyecto nuevo:
Los comandos de migración requieren autenticación con Supabase y un proyecto enlazado previamente:
npx supabase login
npx supabase link --project-ref <NEW_PROJECT_REF>
npm run db:migrate:dry-run
npm run db:migrate
npm run db:seeddb:migrate solo aplica los archivos de supabase/migrations/. El seed no se
ejecuta implícitamente durante una migración: db:seed es la única operación que
ejecuta supabase/seed.sql, dentro de su propia transacción. No se debe ejecutar
db:seed hasta comprobar que el enlace apunta al proyecto nuevo correcto.
La estructura y el seed se validaron el 2026-11-06 contra el PostgreSQL local de
Supabase. La migración 20261106215827_database_baseline.sql se aplicó sobre una
base vacía y supabase/seed.sql se ejecutó dos veces.
Conteos observados después del segundo ciclo:
| Entidad | Filas |
|---|---|
daycares |
1 |
roles |
4 |
people |
1 |
credential |
1 |
people_roles |
3 |
rooms |
3 |
staff_rooms |
3 |
| Total | 16 |
Relaciones verificadas:
- Caro Giménez (
caro@opendaycare.com) está activa y tiene el cargoMaestra. - Sus roles en el daycare son
admin,personalyteacher. - Está asignada a
Soles,GirasolesyPatitosmediantestaff_rooms. person_rolecontiene exactamenteadmin,personal,teacheryparent.feed_overviewno existe.- La segunda ejecución no añadió filas en
people_rolesnistaff_rooms.
Credencial de bootstrap para el primer acceso en el proyecto nuevo:
Email: caro@opendaycare.com
Password: OpenDayCare1!
El seed guarda el hash bcrypt existente para esa contraseña. Esta credencial es solo de bootstrap y debe rotarse antes de usar el proyecto en un entorno real. La validación no creó niños, padres, invitaciones, publicaciones, comentarios, reacciones, media ni históricos.
La integración con @jmlq/mailer es exclusivamente server-only y se controla
mediante la fachada mailerClient de Infrastructure. Next.js carga las variables desde
los archivos .env*; la aplicación no inicializa dotenv por separado.
Define la configuración SMTP en un archivo local ignorado por Git, por ejemplo
.env.local, usando .env.template como referencia:
APP_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:3000
MAILER_EMAIL=example@gmail.com
MAILER_SECRET_KEY=app_password_here
MAILER_FROM=example@gmail.com
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_SECURE=false
MAIL_TEMPLATE_EXTENSION=htmlMAIL_PORT debe ser un entero positivo y MAIL_SECURE acepta únicamente
true o false. Las plantillas se cargan exclusivamente desde la carpeta raíz
templates/, que debe existir en runtime. APP_URL debe ser un origen absoluto
HTTP o HTTPS y se utiliza para construir los enlaces de activación sin depender
de cabeceras de petición.
Las variables del mailer se validan durante el bootstrap de la aplicación. Una
configuración ausente o inválida impide que el servidor se considere listo. Las
credenciales reales nunca se versionan: .gitignore permite únicamente el archivo
de ejemplo .env.template.
La plantilla templates/parent-invitation.html forma parte de los archivos
necesarios en runtime. Un despliegue que empaquete solo el output de Next.js,
como output: "standalone", debe incluir también la carpeta templates/.
/home: feed del personal o feed familiar filtrado según el rol autenticado./: redirect permanente a/home./auth/login: inicio de sesión./auth/parent-invitation: inicio de activación de una invitación./auth/parent-invitation/[token]: activación de una invitación de padre./api/auth/[...nextauth]: Route Handler de NextAuth v4 para credenciales y sesiones JWT./kids: listado de ocho niños con búsqueda local y badges de alergias./kids/[slug]: perfil de un niño con información básica, notas médicas y padres vinculados./kids/edit/[id]: edición de un niño por ID./kids/new: alta de un niño./kids/[slug]/invite-parent: invitación de otro padre desde un perfil./family-feed: feed familiar./family-feed/day-summaryy/family-feed/account: entradas de compatibilidad internas del feed./posts/[postId],/posts/newy/posts/[postId]/edit: publicaciones./posts/[postId]/comments/newy/posts/[postId]/comments/[commentId]/edit: comentarios./kids/[slug]con un slug desconocido: página 404 propia.
La configuración transversal se centraliza en src/config/server/environment.ts.
Los consumidores reciben únicamente getters tipados por sección:
getApplicationEnvironment()getAuthEnvironment()getMailerEnvironment()getCloudinaryEnvironment()getSupabaseEnvironment()
Configura estas variables en .env.local o en el gestor de secretos del entorno,
fuera de Git:
SUPABASE_DB_HOST=aws-0-us-east-1.pooler.supabase.com
SUPABASE_DB_PORT=5432
SUPABASE_DB_USERNAME=postgres.<PROJECT_REF>
SUPABASE_DB_PASSWORD=your_database_password
RUN_REMOTE_DB_TESTS=falseLa configuración regional server-only requiere una zona horaria IANA. Para Ecuador continental usa:
APP_LOCALE=es-EC
APP_TIME_ZONE=America/GuayaquilLas fechas PostgreSQL recibidas como instantes se normalizan a YYYY-MM-DD en
esta zona antes de mapearse al dominio. APP_LOCALE controla el idioma y
formato regional de las fechas mostradas.
La aplicación usa PostgreSQL mediante postgres.js y el Session Pooler IPv4.
SUPABASE_DB_PASSWORD es server-only y nunca debe exponerse en variables
NEXT_PUBLIC_*. bootstrap.ts valida todo el entorno y configura, en orden,
databaseClient, mailerClient e imageStorageClient. Después ejecuta
SELECT 1 mediante el cliente PostgreSQL para comprobar el readiness de la base.
src/instrumentation.ts es el adaptador mínimo del lifecycle de Next.js y carga
dinámicamente src/bootstrap.ts solo en el runtime Node.js. El bootstrap es
idempotente y detiene la preparación del servidor ante cualquier error de
configuración o conexión.
Esta entrega no implementa Supabase Auth ni cambia bcrypt, cookies o la estrategia
JWT. La migración estructural se versiona en supabase/migrations/ y el seed
independiente en supabase/seed.sql. Auth,
Staff y General usan PostgreSQL como persistencia runtime a través de sus
composiciones server-only; no existe fallback JSON ni traducción de IDs legacy.
Tampoco realiza conexiones SMTP ni operaciones de Cloudinary durante el
arranque. La refactorización de NextAuth mantiene Credentials, sesiones JWT,
cookies, redirects y permisos sin cambios; su API server-side se consume desde
@/composition/auth.
Las rutas legacy /login, /kids/:id/edit, /auth/link-parent, las rutas
antiguas de Posts, /activate-account y /auth/activate-account responden 404.
El redirect de compatibilidad / es el único redirect externo conservado.
El personal con rol personal puede crear invitaciones desde “Vincular otro
padre” en /kids/[slug]. El servidor valida nombre, email y parentesco, genera
un código criptográficamente aleatorio de ocho caracteres sin símbolos ambiguos
y fija un vencimiento de siete días. La persona se crea como parent/pending y
la invitación se persiste bajo transacción PostgreSQL antes de contactar al mailer;
no se crea ParentKid durante este flujo.
Los fallos SMTP conservan los registros pendientes y muestran un error
recuperable. Un reintento de una invitación pendiente sin envío confirmado
reutiliza el código vigente o rota código y vencimiento si ya expiró. Los
envíos exitosos actualizan sentAt y redirigen al perfil con
?invitation=sent. Los padres autenticados son redirigidos a /home.
La aplicación usa PostgreSQL como única fuente runtime. Las pruebas remotas se
ejecutan con RUN_REMOTE_DB_TESTS=true, dentro de transacciones con rollback.
Mientras exista una única guardería operativa, CurrentDaycareProvider obtiene
provisionalmente su daycareId desde PostgreSQL para completar el login;
posteriormente se incorporará la selección explícita de guardería.
Runtime source: PostgreSQL/Supabase. Los 11 JSON son snapshots inmutables de migración y no forman parte del runtime ni se escriben durante la ejecución.
- Detener el tráfico autenticado y conservar los logs de readiness sin incluir credenciales, headers ni datos de respuestas.
- Si falla una operación de negocio, dejar que la transacción PostgreSQL la revierta; no restaurar JSON ni activar un fallback híbrido.
- Si falla el despliegue, volver a la última versión compatible con PostgreSQL y mantener la misma configuración de base de datos. No volver a una versión que use IDs JSON como fuente runtime.
- Comprobar
SELECT 1, ejecutar los smoke tests y exigir un nuevo login. Las sesiones antiguas o sinpersistenceSource: "postgres"permanecen inválidas. - Reanudar tráfico solo después de confirmar aislamiento por
daycareIdy que las escrituras remotas de prueba siguen usando rollback.
La activación de una invitación se realiza posteriormente desde
/auth/parent-invitation/[token]: crea o actualiza las credenciales, activa la persona,
crea la relación ParentKid y marca acceptedAt de forma coordinada. Un fallo
revierte la transacción PostgreSQL y conserva la invitación pendiente.
La autenticación usa next-auth@4.24.15 con Credentials Provider, sesiones JWT
de siete días y cookies HttpOnly. La API server-side se expone mediante
src/composition/auth/, mientras que la implementación concreta permanece en
Infrastructure. La configuración obtiene sus valores desde
src/config/server/environment.ts.
Añade estas variables a tu archivo local .env o .env.local, usando
.env.template como referencia:
AUTH_SECRET=replace_with_a_random_secret
AUTH_SESSION_MAX_AGE_SECONDS=604800
AUTH_SESSION_COOKIE_NAME=next-auth.session-token
AUTH_SESSION_COOKIE_HTTP_ONLY=true
AUTH_SESSION_COOKIE_SECURE=false
AUTH_SESSION_COOKIE_SAME_SITE=lax
AUTH_SESSION_COOKIE_PATH=/
AUTH_SIGN_IN_PATH=/auth/loginNEXTAUTH_URL debe coincidir con el origen usado por el servidor. En local es
http://localhost:3000; en otros entornos debe usar el origen público HTTPS
correspondiente. PORT no se configura en .env porque Next.js necesita
resolver el puerto antes de cargar las variables de la aplicación.
AUTH_SECRET debe reemplazarse por un secreto aleatorio real fuera del entorno
local. AUTH_SESSION_COOKIE_SECURE debe ser true cuando la aplicación se sirve
exclusivamente sobre HTTPS.
El personal con rol personal puede acceder a /kids/** y conserva el feed de
personal en /home. Los padres con rol parent reciben un feed familiar en
/home, limitado a los niños vinculados y a sus salas, y no pueden acceder a
las rutas de Kids. Las personas inexistentes, inactivas o con un rol cambiado
son expulsadas de la sesión.
El feed familiar combina publicaciones dirigidas a los niños relacionados y anuncios de sus salas, elimina anuncios duplicados, mantiene el orden por fecha y no expone contenido de otras familias. Incluye identidad del padre, niños vinculados, cierre de sesión y un estado vacío seguro cuando no existen relaciones. No muestra el composer ni la navegación exclusiva de personal.
Estas credenciales existen únicamente para pruebas locales del prototipo:
| Usuario | Password | Rol | |
|---|---|---|---|
| Caro Giménez | caro@opendaycare.com |
OpenDayCare1! |
personal |
Los demás hashes mock-hash-* de los fixtures no son contraseñas válidas.
No reutilices la clave demo ni agregues credenciales reales al repositorio.
La aplicación separa routing, casos de uso, dominio, adapters, presentación y
composición. El prefijo físico src se oculta en imports mediante @/*:
open-daycare/
├── src/
│ ├── app/ Routing y adapters de entrada de Next.js
│ │ ├── (staff)/ Rutas de personal sin segmento público
│ │ ├── (general)/ Rutas generales y Posts
│ │ ├── auth/ Rutas públicas de autenticación
│ │ └── api/auth/[...nextauth]/ Route Handler de NextAuth
│ ├── application/ Use cases, commands, queries, DTOs y ports
│ ├── domain/ Entidades, invariantes y reglas puras
│ ├── config/ Configuración runtime y server-only tipada
│ ├── infrastructure/ Repositorios, proveedores y adapters server-only
│ ├── presentation/ UI, layout, navegación, presenters y view models
│ ├── composition/ Factories que ensamblan Application e Infrastructure
│ ├── bootstrap.ts Inicialización server-only idempotente
│ ├── instrumentation.ts Adaptador del lifecycle de Next.js
│ ├── types/ Ampliaciones TypeScript de NextAuth
│ └── ... Entrypoints y colaboradores de ruta
├── public/ Recursos públicos
├── templates/ Plantillas HTML requeridas en runtime
├── supabase/
│ ├── migrations/ Única migración estructural versionada
│ ├── seed.sql Seed bootstrap independiente y explícito
│ └── config.toml Configuración de Supabase CLI
├── specs/ Contratos funcionales versionados
├── .agents/skills/ Skills del flujo SDD
├── .opencode/ Agentes y comandos personalizados
├── references/ Prototipos HTML y capturas visuales
└── .playwright-mcp/ Evidencia de validaciones visuales
Los modelos de dominio viven en src/domain/. PostgreSQL es la única fuente de
datos runtime y solo se accede mediante adapters server-only. src/presentation/ no depende de Infrastructure ni
Composition; src/application/ no depende de Infrastructure, Next.js ni React;
src/domain/ no depende de otras capas ni de proveedores externos. Las páginas
y Server Actions de src/app/ delegan en casos de uso ensamblados por
src/composition/. Los Client Components reciben DTOs mínimos proyectados por
el servidor y no importan Infrastructure ni configuración privada.
flowchart TD
U[Usuario o equipo] --> I[npm install]
I --> R[npm run dev]
R --> APP["OpenDayCare<br/>Next.js App Router"]
APP --> ROUTES["Rutas en src/app/"]
ROUTES --> PRESENTATION["src/presentation<br/>UI y navegación"]
ROUTES --> COMPOSITION["src/composition<br/>Composition Root"]
COMPOSITION --> APPLICATION["src/application<br/>casos de uso y ports"]
APPLICATION --> DOMAIN["src/domain<br/>reglas y modelos"]
COMPOSITION --> INFRA["src/infrastructure<br/>server-only"]
INFRA --> POSTGRES["persistence/postgres<br/>adapters PostgreSQL"]
POSTGRES --> POOLER["Supabase Session Pooler<br/>postgres.js"]
COMPOSITION --> AUTH["composition/auth"]
AUTH --> NEXTAUTH["infrastructure/auth<br/>NextAuth"]
INSTRUMENTATION["src/instrumentation.ts"] -->|NEXT_RUNTIME === nodejs| NODE_INSTRUMENTATION["src/instrumentation-node.ts"]
NODE_INSTRUMENTATION -->|import dinámico| BOOTSTRAP["src/bootstrap.ts"]
BOOTSTRAP --> ENV["src/config/server/environment.ts"]
BOOTSTRAP --> BOOT_COMPOSITION["composition/bootstrap"]
BOOTSTRAP --> DB_COMPOSITION["composition/database"]
BOOT_COMPOSITION --> DB["databaseClient"]
BOOT_COMPOSITION --> MAILER["mailerClient"]
BOOT_COMPOSITION --> IMAGE["imageStorageClient"]
DB_COMPOSITION --> CHECK["checkDatabaseConnection"]
CHECK --> DB_PORT["DatabaseConnectionChecker"]
DB --> POSTGRES
POSTGRES --> READINESS["SELECT 1<br/>readiness"]
MAILER --> JMLQ["JMLQ/Nodemailer"]
JMLQ --> TEMPLATE["templates/<br/>parent-invitation.html"]
JMLQ -. no arranque SMTP .-> SMTP["Servidor SMTP"]
IMAGE --> CLOUDINARY["Cloudinary provider"]
CLOUDINARY -. no arranque remoto .-> CLOUDINARY_API["Cloudinary API"]
ENV -. getters tipados .-> DB
ENV -. getters tipados .-> MAILER
ENV -. getters tipados .-> IMAGE
U --> SPEC["/spec"]
SPEC --> FILE["specs/NN-slug.md"]
FILE --> HUMAN["Revisión y aprobación humana"]
HUMAN --> IMPL["/spec-impl"]
IMPL --> GIT["Git<br/>rama spec-NN-slug"]
GIT --> CODE["Código en src/"]
CODE --> VALIDATE["/spec-acceptance-validator"]
VALIDATE --> AGENT[Agente personalizado]
AGENT --> PW[Playwright MCP]
PW --> ART[".playwright-mcp/<Feature>/"]
AGENT --> C7["Context7 MCP<br/>documentación actual"]
El diagrama se mantiene alineado con la arquitectura actual: las rutas no
construyen adapters, Application solo conoce ports y Composition ensambla las
implementaciones. El flujo de arranque es independiente del flujo de requests;
instrumentation.ts carga el bootstrap únicamente en Node.js. Mailer y
Cloudinary se configuran durante el arranque, pero el readiness de base se
verifica mediante PostgreSQL con SELECT 1. Las migraciones se ejecutan de forma
explícita mediante Supabase CLI y no forman parte del bootstrap.
Una spec es el contrato que describe una funcionalidad antes de escribir su código. Define el objetivo, alcance, modelo de datos, plan de implementación, criterios de aceptación, decisiones y riesgos.
Las specs se almacenan en specs/ con el formato NN-slug.md. Sus estados son:
Draft: definición inicial.In review: revisión del contenido.Approved: lista para implementación.Implement: implementación realizada y aceptación en validación.Implemented: implementación y aceptación completadas.Obsolete: reemplazada o descartada.
1. /spec "nueva funcionalidad"
└─> preguntas, alcance, decisiones y specs/NN-slug.md (Draft)
2. Revisión humana
└─> cambio manual del estado a Approved
3. /spec-impl NN-slug
└─> rama spec-NN-slug
└─> implementación por pasos con pausas para revisar diffs
4. /spec-acceptance-validator NN-slug
└─> criterios verificados, correcciones necesarias y evidencia
5. Revisión final humana
└─> estado Implemented, commit, merge y push según el flujo del equipo
specs/.spec-config.yml controla la creación de ramas:
AutoCreateBranch: trueCon true, /spec-impl crea y cambia automáticamente a spec-NN-slug. Con
false, solicita confirmación antes de tocar Git. El agente no debe hacer
commit, push o merge automáticamente.
Está configurado en opencode.json como servidor local habilitado:
npx -y @playwright/mcp@latest
Se usa para comprobar páginas renderizadas, responsive, accesibilidad,
interacciones, navegación, consola, red y payloads. Sus capturas y artefactos
se guardan en .playwright-mcp/<Feature>/.
Está configurado a nivel de usuario y se utiliza para consultar documentación actualizada de frameworks, SDKs, APIs y herramientas. Su configuración privada y cualquier credencial quedan fuera del repositorio.
Las skills del proyecto están bajo .agents/skills/ y su procedencia se registra
en skills-lock.json.
Diseña specs mediante preguntas guiadas. Ayuda a cerrar alcance, datos, integración, UX, decisiones y criterios antes de generar el archivo Markdown. No implementa código.
Uso:
/spec <descripción breve de la funcionalidad>
Implementa una spec únicamente cuando su estado es Approved. Gestiona la rama
de la spec, muestra el plan y trabaja un paso a la vez, esperando revisión del
diff entre pasos.
Uso:
/spec-impl 04-kid-allergy-tags
/spec-impl 04
/spec-impl kid-allergy-tags
Agente definido en
.opencode/agent/spec-acceptance-validator.md. Revisa cada criterio de
aceptación individualmente, usa Playwright cuando corresponde, ejecuta las
comprobaciones relevantes y corrige solo incumplimientos necesarios. Marca un
criterio como verificado únicamente cuando existe evidencia.
Comando definido en .opencode/command/spec-acceptance-validator.md:
/spec-acceptance-validator <spec>
Cada spec puede trabajarse en su propia rama con la convención:
spec-NN-slug
El archivo specs/.spec-config.yml define si la rama se crea automáticamente.
Los cambios se revisan mediante diffs antes de confirmar commits. Git conserva
la relación entre la spec, su implementación y la evidencia de aceptación.
references/ contiene prototipos HTML y capturas que sirven como referencia de
diseño. No es código de runtime. Las pantallas aún no implementadas no implican
que exista una ruta funcional en la aplicación.
Para conocer las reglas completas de arquitectura, estilos, Server Components,
Client Components y verificación, consulta AGENTS.md.
Los componentes visuales compartidos viven en src/presentation/ui/ y se
consumen mediante su barrel público src/presentation/ui/index.ts. No contienen
datos de negocio: reciben contenido, variantes, destinos y className mediante
props.
StaffSidebar compone estos componentes para mantener una interfaz consistente
en escritorio y móvil:
flowchart LR
BARREL["src/presentation/ui/index.ts"]
BARREL --> BRAND[Brand]
BARREL --> AVATAR[Avatar]
BARREL --> BUTTON[Button]
BARREL --> LINK[LinkButton]
CONFIG[staffNavigationConfig] --> SIDEBAR[StaffSidebar]
BRAND -->|Identidad y sala| SIDEBAR
AVATAR -->|Perfil del personal| SIDEBAR
BUTTON -->|Acciones presentacionales| SIDEBAR
LINK -->|Navegación real| NAV[NavigationControl]
BUTTON -->|Opciones sin href| NAV
NAV --> SIDEBAR
SIDEBAR --> DESKTOP[Sidebar de escritorio]
SIDEBAR --> MOBILE[Navegación inferior móvil]
HOME["src/app/(staff)/home/page.tsx"] -->|navigation config| SIDEBAR
KIDS["src/app/(staff)/kids/layout.tsx"] -->|activeSection: children| SIDEBAR
Dentro de NavigationControl, LinkButton se usa cuando una opción tiene un
destino real y Button cuando la opción es presentacional. El mismo control se
reutiliza en la navegación lateral de escritorio y en la navegación inferior
móvil.
Badge y SearchField también forman parte del catálogo UI reutilizable, pero
se utilizan en otras features y no dentro de StaffSidebar.