Tasks: Núcleo de agenda de CitaClara (001)

Input: Design documents from specs/001-nucleo-agenda/ Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ Tests: Incluidas porque la constitución VI + plan + quickstart las exigen explícitamente (suite verde + antisolape concurrente en verde = puerta de merge). Vitest + Playwright. Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies)
  • [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
  • Include exact file paths in descriptions

Path Conventions

Monolito Next.js 15 en tema5/citaclara/ (repo contenedor sdd-course-udemy): app/, components/, lib/, prisma/, tests/, e2e/ en la raíz de tema5/citaclara/.


Phase 1: Setup (Shared Infrastructure)

Purpose: Project initialization and basic structure

  • T001 Crear proyecto Next.js 15 + React 19 + TypeScript 5.6 strict (noUncheckedIndexedAccess) en tema5/citaclara/package.json
  • T002 [P] Configurar Tailwind CSS 4 + tokens shadcn en tema5/citaclara/app/globals.css
  • T003 [P] Configurar lint/format/test (ESLint, Prettier, Vitest, Playwright) en tema5/citaclara/package.json
  • T004 Crear tema5/citaclara/docker-compose.yml (app Node 22 + postgres:16, puertos 3000/5432, TZ=Europe/Madrid)
  • T005 Crear tema5/citaclara/Dockerfile (contenedor Node 22 para la app Next.js)
  • T006 Crear tema5/citaclara/.env.example con DATABASE_URL=postgresql://citaclara:citaclara@localhost:5432/citaclara y TZ=Europe/Madrid
  • T007 Instalar dependencias base (next, react, prisma, zod, date-fns-tz, bcryptjs, shadcn/Radix) en tema5/citaclara/package.json
  • T008 Crear layout raíz con lang="es-ES" en tema5/citaclara/app/layout.tsx

Phase 2: Foundational (Blocking Prerequisites)

Purpose: Core infrastructure that MUST be complete before ANY user story can be implemented

⚠️ CRITICAL: No user story work can begin until this phase is complete

  • T009 Crear esquema Prisma con modelos Clinica/Profesional/Servicio/Paciente/Cita en tema5/citaclara/prisma/schema.prisma (FR-001: el modelo soporta N clínicas; en la 001 solo opera Eleva, sin CRUD de clínicas)
  • T010 Crear migración con restricción de exclusión RN1 EXCLUDE USING gist (profesionalId WITH =, tstzrange(inicio, fin) WITH &&) WHERE (estado IN ('reservada','completada')) e índices (profesionalId, inicio) en tema5/citaclara/prisma/migrations/0001_nucleo_agenda/migration.sql
  • T011 [P] Implementar utilidades de dinero en céntimos (guardar/operar en céntimos enteros; redondeo mitad-hacia-arriba; mostrar 40,00 €, nunca 40 € ni 40.00€) en tema5/citaclara/lib/dinero.ts
  • T012 [P] Implementar utilidades de tiempo (timestamptz, zona Europe/Madrid, formato 29/09/2026, 10:00–10:45, duración en minutos) en tema5/citaclara/lib/tiempo.ts
  • T013 [P] Implementar esquemas zod (paciente exige nombre+teléfono+email con formatos ES estrictos; servicio duracionMin entero positivo, precioCentimos ≥0; jornada configurable por defecto 09:00/20:00) en tema5/citaclara/lib/validacion.ts
  • T014 [P] Implementar lógica de agenda (fin = inicio + duracionMin vigente al crear; solape = al menos un minuto común, contigüidad fin = inicio permitida; cancelada/no_asistida no bloquean) en tema5/citaclara/lib/agenda.ts
  • T015 [P] Implementar auth v1 (hash bcrypt/argon2 de clave de panel, compare en servidor, sin límite ni bloqueo, aviso genérico) en tema5/citaclara/lib/auth.ts
  • T016 Implementar semilla determinista Eleva (seedVersion=1, PRNG mulberry32(seed=1): Clínica Eleva jornada 09:00–20:00, 3 profesionales María/Jorge fisioterapia + Lucía nutrición, 4 servicios sesión fisio 45′ 4000 / primera visita fisio 60′ 5000 / consulta nutrición 30′ 3500 / primera nutrición 45′ 4500, 38–42 pacientes ES, 8 semanas completada ~82 % / no_asistida ~10 % / cancelada ~8 % ±2, 2 semanas futuras reservada, sin solapes vigentes, entradas truncadas a minuto) en tema5/citaclara/prisma/seed.ts
  • T017 Configurar cliente Prisma singleton en tema5/citaclara/lib/db.ts
  • T018 [P] Instalar componentes shadcn base (button, dialog, calendar, toast, form) en tema5/citaclara/components/ui/

Checkpoint: Foundation ready - user story implementation can now begin in parallel


Phase 3: User Story 1 - Agenda del día por profesional (Priority: P1) 🎯 MVP

Goal: Recepción elige día + profesional y distingue tramos ocupados (cita con servicio/paciente) de huecos libres en jornada 09:00–20:00.

Independent Test: Abrir /agenda con semilla, elegir día y cada profesional; citas aparecen en su tramo y resto se ve libre; día vacío muestra La jornada está libre; legible en 1440px y 390px sin manual.

Tests for User Story 1

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T019 [P] [US1] Test contrato GET /api/agenda (orden por inicio, incluye vigentes+finales, resto como libre, 400 si fecha inválida) en tema5/citaclara/tests/contract/test_agenda_get.test.ts
  • T020 [P] [US1] Test unitario cálculo huecos libres y contigüidad (fin = inicio no es solape) en tema5/citaclara/tests/unit/test_agenda_huecos.test.ts
  • T021 [P] [US1] Test e2e agenda María con citas / Jorge vacío en 1440px y 390px en tema5/citaclara/e2e/agenda-dia.spec.ts

Implementation for User Story 1

  • T022 [P] [US1] Implementar GET /api/agenda?profesionalId&fecha (filtra por profesional y día dentro de jornada configurable por defecto 09:00–20:00, devuelve citas con tramoTexto tipo 05/10/2026, 10:00–10:45 + huecosLibres) en tema5/citaclara/app/api/agenda/route.ts
  • T023 [US1] Implementar vista <AgendaDia profesionalId fecha /> con estados cargando/lista/vacia (La jornada está libre)/error, tarjeta por cita con tramoTexto, servicio, paciente y Estado en tema5/citaclara/app/(panel)/agenda/page.tsx
  • T024 [US1] Aplicar estilos responsive AA (portátil 1440px columna+detalle, móvil 390px lista apilada, letra ≥16px, toques ≥44px, importes 40,00 €, fechas 05/10/2026, 10:00–10:45) en tema5/citaclara/app/(panel)/agenda/page.tsx

Checkpoint: At this point, User Story 1 should be fully functional and testable independently


Phase 4: User Story 2 - Alta de cita sin solapes ni pasado (Priority: P1)

Goal: Crear cita (profesional+servicio+paciente+inicio, fin = inicio + duración vigente) rechazando solapes (RN1) y pasado (RN2) con avisos es-ES.

Independent Test: Crear válida → 201 reservada; solapada (≥1 min común) → 409 Ese tramo ya está ocupado; contigua (fin = inicio) → 201; pasado → 422 No se pueden crear citas en el pasado; doble POST concurrente mismo hueco → máximo 1 201.

Tests for User Story 2

  • T025 [P] [US2] Test contrato POST /api/citas (201 válida con fin calculado; 409 HUECO_OCUPADO; 422 PASADO/FUERA_JORNADA (FR-013)/DIA_PARTIDO (FR-013); 404 ficha inexistente) en tema5/citaclara/tests/contract/test_citas_post.test.ts
  • T026 [P] [US2] Test integración antisolape concurrente (Promise.all doble reserva mismo profesional/hueco → máximo 1 creada, cero solapes vigentes) en tema5/citaclara/tests/integration/test_antisolape.test.ts
  • T027 [P] [US2] Test integración RN2 (inicio estrictamente anterior a now() ⇒ 422, precisión de minuto) en tema5/citaclara/tests/integration/test_rn2_pasado.test.ts
  • T028 [P] [US2] Test e2e alta válida <60s, solapada, pasada en tema5/citaclara/e2e/alta-cita.spec.ts

Implementation for User Story 2

  • T029 [US2] Implementar POST /api/citas en transacción serializable (trunca entradas a minuto; usa único now() de BD para RN2; chequeo previo para mensaje Ese tramo ya está ocupado; captura violación exclusión 23P01 como 409; nace reservada; guarda inicio+fin; rechaza tramo fuera de jornada o a caballo entre días con 422 FR-013) en tema5/citaclara/app/api/citas/route.ts
  • T030 [US2] Implementar formulario Nueva cita (profesional/servicio/paciente/día/hora, muestra fin calculado, propaga errores Ese tramo ya está ocupado / No se pueden crear citas en el pasado / Fuera de la jornada 09:00–20:00 / La cita debe pertenecer a un único día) en tema5/citaclara/app/(panel)/citas/nueva/page.tsx

Checkpoint: At this point, User Stories 1 AND 2 should both work independently


Phase 5: User Story 3 - Registrar el desenlace de la cita (Priority: P2)

Goal: Marcar reservada → completada | cancelada | no_asistida (no_asistida solo desde reservada por incomparecencia); finales inmutables; cancelada libera tramo.

Independent Test: reservada→completada/cancelada/no_asistida → 200; cualquier cambio desde final o entre finales → 409; cancelada deja hueco reservable; cada acción <15s.

Tests for User Story 3

  • T031 [P] [US3] Test contrato PATCH /api/citas/[id]/estado (200 a final; 409 TRANSICION_INVALIDA con aviso es-ES; no_asistida solo desde reservada) en tema5/citaclara/tests/contract/test_citas_estado.test.ts
  • T032 [P] [US3] Test integración desenlace (cancelada libera tramo y permite re-reserva; finales inmutables) en tema5/citaclara/tests/integration/test_desenlace.test.ts
  • T033 [P] [US3] Test e2e desenlace <15s por cita + ilegal con aviso en tema5/citaclara/e2e/desenlace.spec.ts

Implementation for User Story 3

  • T034 [US3] Implementar PATCH /api/citas/[id]/estado (solo reservada → completada|cancelada|no_asistida, rechaza resto con 409, cancelada sale del predicado de exclusión) en tema5/citaclara/app/api/citas/[id]/estado/route.ts
  • T035 [US3] Añadir acciones por tarjeta reservada (Completar/Cancelar/No se presentó con confirmación, llaman al PATCH) en tema5/citaclara/app/(panel)/agenda/page.tsx

Checkpoint: All user stories should now be independently functional (ver → crear → cerrar)


Phase 6: User Story 4 - Entrar con la clave de la clínica y partir de datos conocidos (Priority: P3)

Goal: Acceso con clave de panel (auth v1 deuda consciente) + gestión básica de fichas + semilla Eleva reproducible, todo en es-ES.

Independent Test: Clave correcta entra; incorrecta → 401 Clave incorrecta sin revelar claves; GET /api/semilla devuelve version:1 idéntico tras reset; fichas: servicio duplicado → 409, DELETE con citas → 409 No se puede eliminar: tiene citas asociadas, paciente incompleto → 422.

Tests for User Story 4

  • T036 [P] [US4] Test contrato auth + semilla (POST /api/auth/panel 200/401 genérico sin bloqueo; GET /api/semilla version 1, 3 profesionales, 4 servicios, 38–42 pacientes) en tema5/citaclara/tests/contract/test_auth_semilla.test.ts
  • T037 [P] [US4] Test integración semilla determinista (doble seed ⇒ mismos tramos/estados/repartos ~82/10/8 ±2) en tema5/citaclara/tests/integration/test_semilla.test.ts
  • T038 [P] [US4] Test contrato fichas (servicio duplicado por clínica ⇒ 409 SERVICIO_DUPLICADO; DELETE con citas ⇒ 409 FICHA_CON_CITAS; paciente sin/inválido ⇒ 422) en tema5/citaclara/tests/contract/test_fichas.test.ts
  • T039 [P] [US4] Test e2e acceso + semilla en tema5/citaclara/e2e/acceso-semilla.spec.ts

Implementation for User Story 4

  • T040 [P] [US4] Implementar POST /api/auth/panel (cookie de sesión de clínica, 401 Clave incorrecta genérico) en tema5/citaclara/app/api/auth/panel/route.ts
  • T041 [P] [US4] Implementar pantalla /acceso (formulario clave, aviso es-ES, redirige a agenda) en tema5/citaclara/app/(panel)/acceso/page.tsx
  • T042 [P] [US4] Implementar CRUD básico profesionales (nombre admite duplicados; activo por defecto true como implementación del MUST NOT eliminar de FR-003; prohibido DELETE si tiene citas ⇒ 409) en tema5/citaclara/app/api/profesionales/route.ts
  • T043 [P] [US4] Implementar CRUD básico servicios (nombre obligatorio y único por clínica, índice único (clinicaId, nombre); duracionMin entero positivo; precioCentimos ≥0 con 4000 = 40,00 €; activo por defecto true como implementación del MUST NOT eliminar de FR-003; cambiar duración no reescribe citas existentes) en tema5/citaclara/app/api/servicios/route.ts
  • T044 [P] [US4] Implementar CRUD básico pacientes (nombre/telefono/email obligatorios con validación ES estricta, guarda tildes/ñ tal cual, admite duplicados; sin/inválido ⇒ 422 es-ES) en tema5/citaclara/app/api/pacientes/route.ts
  • T045 [US4] Implementar GET /api/semilla (version:1 + resumen conteos, pacientes como rango 38–42; auxiliar de verificabilidad FR-019/SC-005 según Constitución V) en tema5/citaclara/app/api/semilla/route.ts
  • T052 [US4] Documentar gestión de jornada en la 001 (jornada configurable vía semilla jornadaInicio/jornadaFin por defecto 09:00–20:00; sin endpoint de gestión — fuera de alcance 001; FR-013 solo exige aplicar y rechazar FUERA_JORNADA) en tema5/citaclara/prisma/seed.ts

Checkpoint: Puerta de entrada + datos reproducibles listos; las 4 historias funcionan de punta a punta


Phase 7: Polish & Cross-Cutting Concerns

Purpose: Improvements that affect multiple user stories

  • T046 [P] Tests unitarios de formato (40,00 € con Intl.NumberFormat es-ES/EUR; 29/09/2026, 10:00–10:45 con Intl.DateTimeFormat es-ES en Europe/Madrid) en tema5/citaclara/tests/unit/test_formato.test.ts
  • T047 [P] Tests unitarios de transiciones de estado (nace reservada; solo reservada→final; no_asistida solo desde reservada) en tema5/citaclara/tests/unit/test_estados.test.ts
  • T048 Comprobación es-ES (script comprobar:es: grep sobre app/, components/, lib/ que falla si encuentra literales visibles fuera de es-ES contra lista de patrones TODO|FIXME + revisión manual; etiquetas de estado según glosario ui-agenda.md) vía tema5/citaclara/package.json script comprobar:es
  • T049 Ejecutar validación quickstart.md completa (arranque, tabla US1–US4, suites npm test + test:e2e) en tema5/citaclara/specs/001-nucleo-agenda/quickstart.md
  • T050 Revisión de simplicidad IV (sin tablas/endpoints fuera de data-model.md/contracts/, sin acceso paciente/recordatorios/analítica/pagos; excepciones justificadas: activo, GET /api/semilla — ver plan) — revisión manual con checklist en tema5/citaclara/specs/001-nucleo-agenda/quickstart.md
  • T051 Endurecer manejo de errores y avisos es-ES (Ese tramo ya está ocupado, No se pueden crear citas en el pasado, Clave incorrecta, No se puede eliminar: tiene citas asociadas) en tema5/citaclara/lib/validacion.ts

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies - can start immediately
  • Foundational (Phase 2): Depends on Setup completion - BLOCKS all user stories
  • User Stories (Phase 3+): All depend on Foundational phase completion
    • User stories can then proceed in parallel (if staffed)
    • Or sequentially in priority order (P1 → P2 → P3)
  • Polish (Final Phase): Depends on all desired user stories being complete

User Story Dependencies

  • User Story 1 (P1): Can start after Foundational (Phase 2) - No dependencies on other stories
  • User Story 2 (P1): Can start after Foundational (Phase 2) - Usa semilla de Phase 2 para fichas existentes; integrable con US1 (aparece en agenda) pero testable vía API
  • User Story 3 (P2): Can start after Foundational (Phase 2) - Necesita citas (las crea US2 o la semilla); testable con citas sembradas sin US2
  • User Story 4 (P3): Can start after Foundational (Phase 2) - Auth + fichas + semilla endpoint; independiente de US1–US3

Within Each User Story

  • Tests MUST be written and FAIL before implementation
  • Models before services (ya en Foundational)
  • Services before endpoints
  • Core implementation before integration
  • Story complete before moving to next priority

Parallel Opportunities

  • All Setup tasks marked [P] can run in parallel
  • All Foundational tasks marked [P] can run in parallel (within Phase 2)
  • Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
  • All tests for a user story marked [P] can run in parallel
  • Fichas CRUD (T042/T043/T044) can run in parallel (ficheros distintos)
  • Different user stories can be worked on in parallel by different team members

Parallel Example: User Story 1

# Launch all tests for User Story 1 together:
Task: "Test contrato GET /api/agenda en tests/contract/test_agenda_get.test.ts"
Task: "Test unitario huecos libres en tests/unit/test_agenda_huecos.test.ts"
Task: "Test e2e agenda en e2e/agenda-dia.spec.ts"

# Launch route + vista base together after tests fail:
Task: "Implementar GET /api/agenda en app/api/agenda/route.ts"

Parallel Example: User Story 4

# Launch all fichas + auth + semilla tests together:
Task: "Test auth+semilla en tests/contract/test_auth_semilla.test.ts"
Task: "Test semilla determinista en tests/integration/test_semilla.test.ts"
Task: "Test fichas en tests/contract/test_fichas.test.ts"
Task: "Test e2e acceso en e2e/acceso-semilla.spec.ts"

# Launch implementations together (different files):
Task: "POST /api/auth/panel en app/api/auth/panel/route.ts"
Task: "CRUD profesionales en app/api/profesionales/route.ts"
Task: "CRUD servicios en app/api/servicios/route.ts"
Task: "CRUD pacientes en app/api/pacientes/route.ts"

Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 1: Setup
  2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
  3. Complete Phase 3: User Story 1
  4. STOP and VALIDATE: Test User Story 1 independently (agenda María/Jorge en 1440px+390px)
  5. Deploy/demo if ready

Incremental Delivery

  1. Complete Setup + Foundational → Foundation ready
  2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
  3. Add User Story 2 → Test independently → Deploy/Demo (agenda que crece, RN1+RN2)
  4. Add User Story 3 → Test independently → Deploy/Demo (cierre operativo)
  5. Add User Story 4 → Test independently → Deploy/Demo (puerta + fichas + semilla)
  6. Each story adds value without breaking previous stories

Parallel Team Strategy

With multiple developers:

  1. Team completes Setup + Foundational together
  2. Once Foundational is done:
    • Developer A: User Story 1 (agenda)
    • Developer B: User Story 2 (alta RN1/RN2)
    • Developer C: User Story 3 (desenlace)
    • Developer D: User Story 4 (acceso + fichas + semilla)
  3. Stories complete and integrate independently

Notes

  • [P] tasks = different files, no dependencies
  • [Story] label maps task to specific user story for traceability
  • Each user story should be independently completable and testable
  • Verify tests fail before implementing
  • Commit after each task or logical group
  • Stop at any checkpoint to validate story independently
  • Fuera de alcance 001 (no implementar): acceso del paciente, recordatorios, analítica/informes, pagos online (FR-020)
  • Puerta de merge: suite verde + antisolape concurrente verde + textos es-ES + revisión simplicidad