Implementation Plan: Núcleo de agenda de CitaClara (001)
Branch: 001-nucleo-agenda | Date: 2026-09-29 | Spec: specs/001-nucleo-agenda/spec.md
Input: Feature specification from specs/001-nucleo-agenda/spec.md + user input: «Selecciona un stack tecnológico moderno que permita crear una aplicación con una apariencia profesional y adaptada al estilo del año 2026».
Note: Este plan decide stack y formatos (la constitución no los impone). Toda la UI y textos en español de España.
Summary
Construir el núcleo de agenda (ver → crear → cerrar + acceso con clave + semilla Eleva) como monolito web full-stack Next.js 15 (React 19, TypeScript estricto) + Tailwind CSS 4 + shadcn/ui, con Prisma 6 + PostgreSQL 16 como única persistencia. RN1 cero solapes se garantiza en dos capas: restricción de exclusión Postgres (EXCLUDE USING gist sobre rango temporal por profesional para citas vigentes) + validación de aplicación en transacción serializable; RN2 sin pasado con un único now() de BD. Dinero en céntimos enteros, tiempo en timestamptz mostrado en Europe/Madrid con formato es-ES. Semilla determinista versionada con seedVersion=1. Tests Vitest + Playwright con prueba antisolape concurrente obligatoria en verde antes de merge.
Technical Context
Language/Version: TypeScript 5.6 en modo strict (incl. noUncheckedIndexedAccess), Node.js 22 LTS.
Primary Dependencies: Next.js 15 (App Router, Route Handlers, Server Components), React 19, Tailwind CSS 4, shadcn/ui sobre Radix UI, Prisma 6, date-fns-tz + Intl (es-ES, EUR, Europe/Madrid), zod para validación, bcryptjs (hash clave panel, aunque v1 sea compartida) o argon2.
Storage: PostgreSQL 16 (única BD). Esquema Prisma con tablas Clinica, Profesional, Servicio, Paciente, Cita. Restricción de exclusión nativa para RN1 + índices (profesionalId, inicio). Migraciones Prisma versionadas. Semilla SQL/TS determinista (prisma/seed.ts, seedVersion=1).
Testing: Vitest (unit + integración de Route Handlers + concurrente antisolape), Playwright (e2e: agenda día en portátil 1440px y móvil 390px, alta <60s, desenlace <15s), Intl assertions para 40,00 € y 29/09/2026, 10:00–10:45.
Target Platform: Web responsive desplegada como contenedor Node 22 (Docker + Compose con Postgres 16). Navegadores modernos 2026 (Chrome/Edge/Firefox/Safari últimas 2 versiones). Sin app nativa en la 001.
Project Type: web-application monolítica (frontend + backend en el mismo Next.js; sin backend//frontend/ separados).
Performance Goals: API agenda del día objetivo p95 <200 ms en local con ~1000 citas (objetivo no bloqueante, sin tarea de medición en la 001); render agenda distinguible en <10 s sin formación —medición manual— (SC-001); alta válida <60 s y desenlace <15 s —medición manual— (SC-004); 100 % rechazo de solapes y de inicios en pasado (SC-002/SC-003).
Constraints: Español de España en todo texto visible y error (VIII); importes en céntimos enteros con redondeo mitad-hacia-arriba y formato 40,00 € (II); fechas Europe/Madrid con día/mes/año + hora/minutos (II); jornada configurable por clínica por defecto 09:00–20:00; auth v1: clave compartida hasheada, sin bloqueo ni límite de intentos (deuda consciente); prohibido eliminar fichas con citas; nombre de servicio único por clínica; fuera de alcance: acceso paciente, recordatorios, analítica, pagos (IV).
Scale/Scope: 1–N clínicas; 2–5 profesionales por clínica (3 en semilla); ~40 pacientes; 10 semanas de citas (~500–1200 filas); decenas de reservas concurrentes como peor caso. Un solo despliegue basta para la 001.
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- I. Spec First: todo comportamiento (FR-001…FR-020 + 5 clarificaciones 2026-09-29) vive en
spec.md; este plan no añade comportamiento, solo stack y diseño. Propietario pendiente enspecs/MAPA.md(crear si no existe en/speckit.tasks). - II. Exactitud numérica y temporal: céntimos enteros + redondeo documentado en plan/research/data-model;
timestamptz+Europe/Madrid+ ejemplos límite40,00 €/29/09/2026, 10:00–10:45. Sin creative accounting. - III. Cero solapes: exclusión Postgres + test concurrente obligatorio (Vitest
Promise.alldoble reserva mismo hueco → máximo 1). Solape = defecto crítico bloqueante. - IV. Simplicidad / cero alcance fantasma: monolito Next.js (no microservicios, no Redis/colas, no Auth provider, no i18n multi-idioma, no analítica/pagos/recordatorios). Cada tabla/campo/endpoint traza a un FR.
- V. Datos reproducibles:
seedVersion=1, generador PRNG con semilla fija (p. ej.mulberry32), historia 8+2 semanas relativa a fecha de carga, tolerancia ±2 documentada, regeneración bit a bit. - VI. Tests con la spec: matriz trazable test↔regla (SC-001…SC-006 ↔ FR/RN); suite en verde + antisolape en verde como condición de merge.
- VII. Interfaz clara y moderna: Tailwind 4 + shadcn/ui, lenguaje de clínica pequeña, contraste WCAG AA, responsive portátil+móvil verificado con Playwright, sin jerga técnica.
- VIII. Español de España:
es-ESen UI, errores, seed y docs; validación de teléfono/email españoles;€pospuesto con coma decimal.
Gates: PASS — sin violaciones que justificar. Complexity Tracking queda vacío.
Justificaciones IV (adiciones trazadas, no alcance fantasma): (1) bandera activo en Profesional/Servicio implementa el «MUST NOT eliminar» de FR-003 sin borrado físico; (2) GET /api/semilla es auxiliar de verificabilidad de FR-019/SC-005 exigido por Constitución V, no funcionalidad de clínica; (3) códigos FUERA_JORNADA/DIA_PARTIDO aplican FR-013 (tramo íntegro en jornada/día).
Project Structure
Documentation (this feature)
specs/001-nucleo-agenda/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
│ ├── agenda-api.md # Route Handlers REST + esquemas zod
│ └── ui-agenda.md # Contrato UI agenda del día (props/estados)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
Source Code (repository root)
citaclara/
├── app/
│ ├── (panel)/
│ │ ├── agenda/ # vista día por profesional (US1)
│ │ ├── citas/nueva/ # alta sin solapes ni pasado (US2)
│ │ └── acceso/ # clave de clínica (US4)
│ ├── api/
│ │ ├── agenda/route.ts # GET ?profesionalId&fecha
│ │ ├── citas/route.ts # POST crear (RN1+RN2)
│ │ ├── citas/[id]/estado/route.ts # PATCH desenlace (US3)
│ │ ├── fichas/ # profesionales/servicios/pacientes CRUD básico
│ │ └── auth/panel/route.ts
│ ├── layout.tsx
│ └── globals.css # Tailwind 4 + tokens shadcn
├── components/ui/ # shadcn (button, dialog, calendar, toast…)
├── lib/
│ ├── dinero.ts # céntimos ↔ «40,00 €»
│ ├── tiempo.ts # Europe/Madrid ↔ «29/09/2026, 10:00»
│ ├── validacion.ts # zod: teléfono/email ES, jornada, unicidad servicio
│ ├── agenda.ts # cálculo fin, contigüidad, solape minuto común
│ └── auth.ts # clave panel v1 (hash + compare, sin bloqueo)
├── prisma/
│ ├── schema.prisma
│ ├── migrations/
│ └── seed.ts # Clínica Eleva + seedVersion=1
├── tests/
│ ├── unit/ # dinero, tiempo, solape, estados
│ ├── contract/ # zod/route schemas ↔ FR
│ └── integration/ # antisolape concurrente, RN2, semilla
├── e2e/ # Playwright: agenda, alta, desenlace, acceso
├── docker-compose.yml # app + postgres:16
└── Dockerfile
Structure Decision: Monolito Next.js 15 App Router en citaclara/ (repo sdd-course-udemy como contenedor; la app vive en tema5/citaclara/). Sin split backend/frontend: los Route Handlers son el backend y los Server Components la UI. Prisma es la única capa de acceso a datos (sin Repository pattern extra — simplicidad IV).
Complexity Tracking
Fill ONLY if Constitution Check has violations that must be justified