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) entema5/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.exampleconDATABASE_URL=postgresql://citaclara:citaclara@localhost:5432/citaclarayTZ=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"entema5/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)entema5/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 €, nunca40 €ni40.00€) entema5/citaclara/lib/dinero.ts - T012 [P] Implementar utilidades de tiempo (
timestamptz, zonaEurope/Madrid, formato29/09/2026, 10:00–10:45, duración en minutos) entema5/citaclara/lib/tiempo.ts - T013 [P] Implementar esquemas zod (paciente exige nombre+teléfono+email con formatos ES estrictos; servicio
duracionMinentero positivo,precioCentimos≥0; jornada configurable por defecto09:00/20:00) entema5/citaclara/lib/validacion.ts - T014 [P] Implementar lógica de agenda (
fin = inicio + duracionMinvigente al crear; solape = al menos un minuto común, contigüidadfin = iniciopermitida;cancelada/no_asistidano bloquean) entema5/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, PRNGmulberry32(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 semanascompletada~82 % /no_asistida~10 % /cancelada~8 % ±2, 2 semanas futurasreservada, sin solapes vigentes, entradas truncadas a minuto) entema5/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,
400si fecha inválida) entema5/citaclara/tests/contract/test_agenda_get.test.ts - T020 [P] [US1] Test unitario cálculo huecos libres y contigüidad (
fin = iniciono es solape) entema5/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 defecto09:00–20:00, devuelve citas contramoTextotipo05/10/2026, 10:00–10:45+huecosLibres) entema5/citaclara/app/api/agenda/route.ts - T023 [US1] Implementar vista
<AgendaDia profesionalId fecha />con estadoscargando/lista/vacia(La jornada está libre)/error, tarjeta por cita contramoTexto, servicio, paciente yEstadoentema5/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 €, fechas05/10/2026, 10:00–10:45) entema5/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
fincalculado; 409HUECO_OCUPADO; 422PASADO/FUERA_JORNADA(FR-013)/DIA_PARTIDO(FR-013); 404 ficha inexistente) entema5/citaclara/tests/contract/test_citas_post.test.ts - T026 [P] [US2] Test integración antisolape concurrente (
Promise.alldoble reserva mismo profesional/hueco → máximo 1 creada, cero solapes vigentes) entema5/citaclara/tests/integration/test_antisolape.test.ts - T027 [P] [US2] Test integración RN2 (inicio estrictamente anterior a
now()⇒422, precisión de minuto) entema5/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/citasen transacción serializable (trunca entradas a minuto; usa úniconow()de BD para RN2; chequeo previo para mensajeEse tramo ya está ocupado; captura violación exclusión23P01como409; nacereservada; guarda inicio+fin; rechaza tramo fuera de jornada o a caballo entre días con422FR-013) entema5/citaclara/app/api/citas/route.ts - T030 [US2] Implementar formulario
Nueva cita(profesional/servicio/paciente/día/hora, muestrafincalculado, propaga erroresEse 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) entema5/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_INVALIDAcon aviso es-ES;no_asistidasolo desdereservada) entema5/citaclara/tests/contract/test_citas_estado.test.ts - T032 [P] [US3] Test integración desenlace (
canceladalibera tramo y permite re-reserva; finales inmutables) entema5/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(soloreservada → completada|cancelada|no_asistida, rechaza resto con409,canceladasale del predicado de exclusión) entema5/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) entema5/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/panel200/401 genérico sin bloqueo;GET /api/semillaversion 1, 3 profesionales, 4 servicios, 38–42 pacientes) entema5/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) entema5/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) entema5/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 incorrectagenérico) entema5/citaclara/app/api/auth/panel/route.ts - T041 [P] [US4] Implementar pantalla
/acceso(formulario clave, aviso es-ES, redirige a agenda) entema5/citaclara/app/(panel)/acceso/page.tsx - T042 [P] [US4] Implementar CRUD básico profesionales (nombre admite duplicados;
activopor defectotruecomo implementación del MUST NOT eliminar de FR-003; prohibidoDELETEsi tiene citas ⇒409) entema5/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);duracionMinentero positivo;precioCentimos≥0 con4000=40,00 €;activopor defectotruecomo implementación del MUST NOT eliminar de FR-003; cambiar duración no reescribe citas existentes) entema5/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 ⇒
422es-ES) entema5/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) entema5/citaclara/app/api/semilla/route.ts - T052 [US4] Documentar gestión de jornada en la 001 (jornada configurable vía semilla
jornadaInicio/jornadaFinpor defecto 09:00–20:00; sin endpoint de gestión — fuera de alcance 001; FR-013 solo exige aplicar y rechazarFUERA_JORNADA) entema5/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 €conIntl.NumberFormat es-ES/EUR;29/09/2026, 10:00–10:45conIntl.DateTimeFormat es-ESenEurope/Madrid) entema5/citaclara/tests/unit/test_formato.test.ts - T047 [P] Tests unitarios de transiciones de estado (nace
reservada; soloreservada→final;no_asistidasolo desdereservada) entema5/citaclara/tests/unit/test_estados.test.ts - T048 Comprobación
es-ES(scriptcomprobar:es: grep sobreapp/,components/,lib/que falla si encuentra literales visibles fuera de es-ES contra lista de patronesTODO|FIXME+ revisión manual; etiquetas de estado según glosarioui-agenda.md) víatema5/citaclara/package.jsonscriptcomprobar:es - T049 Ejecutar validación
quickstart.mdcompleta (arranque, tabla US1–US4, suitesnpm test+test:e2e) entema5/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 entema5/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) entema5/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)
- Complete Phase 1: Setup
- Complete Phase 2: Foundational (CRITICAL - blocks all stories)
- Complete Phase 3: User Story 1
- STOP and VALIDATE: Test User Story 1 independently (agenda María/Jorge en 1440px+390px)
- Deploy/demo if ready
Incremental Delivery
- Complete Setup + Foundational → Foundation ready
- Add User Story 1 → Test independently → Deploy/Demo (MVP!)
- Add User Story 2 → Test independently → Deploy/Demo (agenda que crece, RN1+RN2)
- Add User Story 3 → Test independently → Deploy/Demo (cierre operativo)
- Add User Story 4 → Test independently → Deploy/Demo (puerta + fichas + semilla)
- Each story adds value without breaking previous stories
Parallel Team Strategy
With multiple developers:
- Team completes Setup + Foundational together
- 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)
- 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