Tasks: Referencia temporal compartida de CitaClara (006)
Input: Design documents from specs/006-tiempo-referencia/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/ventanas-tiempo.md, quickstart.md
Tests: YES — included per plan.md (Vitest bordes SC-002 con reloj controlado) y Constitución VI (trazabilidad test↔regla). Sin Playwright nuevo (sin UI nueva, FR-011).
Organization: Tasks grouped by user story. Cada historia es un incremento independiente y testeable.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (US1, US2, US3)
- Include exact file paths in descriptions
Path Conventions
Monolito existente: lib/ (librería pura), prisma/ (migración + semilla), app/api/ (Route Handlers), tests/unit/ + tests/integration/. Sin backend//frontend/ nuevos (plan.md Structure Decision). FR-011 prohíbe endpoints y tablas nuevas.
Phase 1: Setup (Shared Infrastructure)
Purpose: Toolchain verificado e inventario de la base existente que se reutiliza sin reinstalar nada
- T001 Verificar toolchain Node 22 + dependencias instaladas (
date-fns-tz,zod, Prisma 6, Vitest) contrapackage.jsonconnpm ls date-fns-tz zodynode --versionen repo root - T002 [P] Inventariar helpers reutilizables en
lib/tiempo.ts(ZONA_HORARIA,truncarAMinuto,formatearFechaHora,formatearHora,formatearTramo,aMinutos) y confirmar que no falta ninguna dependencia nueva (plan.md: nada nuevo que instalar) - T003 [P] Inventariar validadores reutilizables en
lib/validacion.ts(ESQUEMA_HORApara jornada) ylib/instantes.ts(instanteLocal,instanteDeEntrada) como base delib/ventanas.ts
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Migración S-06 + semilla que TODAS las historias necesitan (el diasLaborables parametriza semanasAnalitica de US2 y el denominador de US3)
⚠️ CRITICAL: No user story work can begin until this phase is complete
- T004 Añadir columna
diasLaborables Int[] @default([1, 2, 3, 4, 5])al modeloClinicaenprisma/schema.prisma(días ISO 1=lunes…7=domingo; defecto{1,2,3,4,5}; enmienda S-06 propiedad de la 001) - T005 Crear migración SQL
prisma/migrations/XXXX_add_dias_laborables_clinica/migration.sqlconALTER TABLE "Clinica" ADD COLUMN "diasLaborables" INTEGER[] NOT NULL DEFAULT '{1,2,3,4,5}'y ejecutarnpx prisma migrate dev+npx prisma generate - T006 Añadir
diasLaborablescon defecto[1,2,3,4,5]en la creación de Eleva enprisma/seed.ts(reutilizar semilla Eleva +seedVersionde la 001, sin semilla nueva; validaciónESQUEMA_HORApara jornada ya existe) - T007 [P] Añadir esquema zod
ESQUEMA_DIAS_LABORABLES(array de enteros 1–7, defecto[1,2,3,4,5], "festivos fuera de v1 → abiertos salvo exclusión explícita") enlib/validacion.ts
Checkpoint: Foundation ready — npx prisma migrate status en verde y semilla Eleva con diasLaborables; las historias pueden empezar en paralelo
Phase 3: User Story 1 — Un único «ahora» para todas las validaciones (Priority: P1) 🎯 MVP
Goal: Un único instante capturado al inicio de cada operación (reloj del servidor, truncado a minuto) usado por pasado, antelaciones y ventanas sin discrepancias entre features (FR-001/FR-002, SC-001)
Independent Test: Fijar reloj en 30/09/2026 10:00, validar alta en pasado límite + cancelación al borde de 24 h + ventana 24–48 h, avanzar el reloj 2 min a mitad de operación y comprobar que las tres usan 30/09/2026, 10:00 (QS-1; US1-AS1…AS3)
Tests for User Story 1
NOTE: Write these tests FIRST, ensure they FAIL before implementation
- T008 [P] [US1] Test unitario RN2 al minuto en
tests/unit/test_ventanas_tiempo.test.ts(QS-2: conahora = 30/09/2026, 10:00 Europe/Madrid,esPasado(09:59) ⇒ true,esPasado(10:00) ⇒ false; "el borde es estricto: solo lo anterior a «ahora» es pasado"; segundos truncados no deciden) - T009 [P] [US1] Test integración ahora-único en
tests/integration/test_ahora_unico.test.ts(QS-1: alta +cancelacionEnPlazo+enVentanaRecordatoriosen una misma operación conahorainyectado; avanzar reloj 2 min y repetir; "las tres usan el instante capturado al inicio, no el instante de cada chequeo")
Implementation for User Story 1
- T010 [US1] Implementar
capturarAhora(fecha?: Date)(=truncarAMinuto(new Date()), "reloj del servidor de aplicación", "truncar a precisión de minuto (sin redondeo)", "tolerancia cero") yesPasado(inicio, ahora)(inicio < ahora, "igual no es pasado") enlib/ventanas.ts - T011 [US1] Migrar RN2 en
app/api/citas/route.ts: sustituirSELECT now()dentro de la transacción porcapturarAhora()capturado ANTES deprisma.$transactionyesPasado(inicio, ahora)truncado a minuto (la BD usa el valor propagado, nunca su propionow(); predicado de exclusión RN1 intacto) - T012 [US1] Marcar
ahoraDeLaBaseDeDatos()enlib/api.tscomo deprecated haciacapturarAhora()delib/ventanas.ts(prohibidonew Date(),now()oCURRENT_TIMESTAMPdentro de reglas de negocio, salvo dentro decapturarAhora()) y verificar que la suite antisolape de la 001 sigue en verde (npm run test -- test_antisolape)
Checkpoint: US1 funciona y se testea sola — npm run test -- test_ventanas_tiempo test_ahora_unico en verde; cero discrepancias pasado/futuro (SC-001)
Phase 4: User Story 2 — Ventanas derivadas con bordes escritos una sola vez (Priority: P1)
Goal: Las cuatro fórmulas FR-004…FR-007 viven solo en lib/ventanas.ts; consumidoras (001, portal, recordatorios, analítica) citan por nombre sin copiar bordes (FR-010, Q3/A)
Independent Test: Cada ventana en sus bordes exactos contra esta spec: RN2 minuto exacto, 24 h 00 min dentro / 23 h 59 min fuera, 24 h y 48 h dentro, S-1…S-8 con semana en curso fuera (QS-2…QS-5; US2-AS1…AS4)
Tests for User Story 2
NOTE: Write these tests FIRST, ensure they FAIL before implementation
- T013 [P] [US2] Tests unitarios de bordes 24 h y 24–48 h en
tests/unit/test_ventanas_tiempo.test.ts(QS-3:cancelacionEnPlazo(ahora + 24 h 00 min) ⇒ true,+23 h 59 min ⇒ false,+30 días ⇒ true sin tope; QS-4: conejecucion = 01/10/2026, 00:00,+24 h ⇒ true,+48 h ⇒ true,+23 h 59 min ⇒ false,+48 h 01 min ⇒ false; "ambos bordes incluidos, precisión de minuto,Europe/Madrid") - T014 [US2] Test unitario 8 semanas cerradas en
tests/unit/test_ventanas_tiempo.test.ts(QS-5: abrir miércoles 30/09/2026,semanasAnalitica(hoy)devuelve 8 filas S-8…S-1 lunes–domingo anteriores, "la semana en curso MUST NOT entrar", cada filaS-N: DD/MM/AAAA–DD/MM/AAAAderivada sin constantes; secuencial con T013: mismo fichero)
Implementation for User Story 2
- T015 [US2] Implementar
cancelacionEnPlazo(inicio, ahora)(inicio − ahora ≥ 24 h, "borde de 24 h incluido, precisión de minuto", "sin tope superior") yenVentanaRecordatorios(inicio, ejecucion)(24 h ≤ inicio − ejecución ≤ 48 h, "ambos bordes incluidos") enlib/ventanas.ts(secuencial con T016/T017: mismo fichero) - T016 [US2] Implementar
semanasAnalitica(hoy, diasLaborables?)enlib/ventanas.ts("semana en curso = semana natural lunes–domingo que contiene hoy en Madrid, recalculada en cada llamada; marco siempre lunes–domingo con independencia dediasLaborables; historia = 8 anteriores cerradas S-8…S-1; la en curso nunca entra"; comparaciones sobre instantes UTC, DST resuelto con desplazamiento del «ahora») - T017 [US2] Implementar
etiquetarTramo(inicio, fin)(DD/MM/AAAA, HH:MM–HH:MM, delega enformatearTramoexistente) y etiquetasS-N: DD/MM/AAAA–DD/MM/AAAAcon abreviaturas es-ES enlib/ventanas.ts(FR-003/FR-009, Q4/A; zona siempreEurope/Madrid, "escribir «España (península)» es defecto")
Checkpoint: US1 + US2 funcionan independientes — bordes SC-002 al 100 % en npm run test -- test_ventanas_tiempo
Phase 5: User Story 3 — Jornada y calendario configurables con denominador honesto (Priority: P2)
Goal: La clínica configura jornada + días de apertura; la ocupación deriva su denominador de la configuración vigente y lo muestra junto al porcentaje (FR-008/FR-009, SC-003)
Independent Test: Con defecto 09:00–20:00 Lun–Vie el denominador es 26.400 min y se muestra «09:00–20:00, Lun–Vie»; reconfigurar a Lun–Sáb (vía semilla: editar prisma/seed.ts + migrar) cambia el denominador a 31.680 y se declara (QS-6; US3-AS1…AS3)
Tests for User Story 3
- T018 [P] [US3] Tests unitarios de denominador en
tests/unit/test_ventanas_tiempo.test.ts(QS-6:denominadorOcupacion("09:00","20:00") ⇒ { minutos: 26400, etiquetaJornada: "09:00–20:00, Lun–Vie" }; con Lun–Sáb ⇒{ minutos: 31680, …Sáb }distinto de 26.400; "MUST NOT hardcodear 26.400 en ningún caso, con defecto se calcula igual")
Implementation for User Story 3
- T019 [US3] Implementar
denominadorOcupacion(jornadaInicio, jornadaFin, diasLaborables?, semanas = 8)enlib/ventanas.ts(denominadorMin = minutosJornada × nºDíasLaborables × 8con la configuración vigente; "reconfiguración a mitad de periodo: vigente + declaración, sin historia"; ejemplo 660 × 6 × 8 = 31.680 con sábados) - T020 [US3] Implementar
etiquetarJornada(jornadaInicio, jornadaFin, diasLaborables)enlib/ventanas.ts(09:00–20:00, Lun–Vie, días Lun…Dom es-ES derivados de la ventana, nunca constantes; la jornada/días usados MUST indicarse visiblemente junto a ocupaciones)
Checkpoint: Todas las historias funcionan independientes — npm run test -- test_ventanas_tiempo test_ahora_unico en verde
Phase 6: Polish & Cross-Cutting Concerns
Purpose: Verificación transversal SC-004 + puertas de calidad (Constitución II/VI/VIII)
- T021 Ejecutar verificación SC-004 de cero duplicados con
grep -rn "now()\|CURRENT_TIMESTAMP\|Europe/\|España (península)\|24 *h\|48 *h\|S-[18]" --include="*.ts" lib app prisma | grep -v "lib/\(tiempo\|ventanas\).ts"en repo root (canónica D7/QS-7; esperado: cero definiciones rivales fuera delib/tiempo.ts,lib/ventanas.tsy citas006 FR-00X, cada hit se revisa a mano; "redefinir un borde o una ventana fuera de aquí es defecto", FR-010) - T022 [P] Ejecutar validación quickstart QS-1…QS-7 completa y
npm run test -- test_ventanas_tiempo test_ahora_unico test_rn2_pasado test_antisolapemásnpm run typechecken repo root (suite en verde como condición de merge; regresión antisolape 001 intacta) - T023 [P] Ejecutar
npm run lintynpm run format:checken repo root y corregir desviaciones enlib/ventanas.ts,prisma/seed.ts,app/api/citas/route.ts(textos visibles en español de España, VIII; etiquetas sin jerga, VII)
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies — can start immediately
- Foundational (Phase 2): Depends on Setup — BLOCKS all user stories (migración
diasLaborablesparametriza US2/US3) - User Stories (Phase 3+): All depend on Foundational completion
- US1 (P1) → US2 (P1, necesita
capturarAhora+esPasadode US1) → US3 (P2, necesita jornada/semana de US1/US2) - Con un solo desarrollador: orden P1 → P1 → P2
- US1 (P1) → US2 (P1, necesita
- Polish (Phase 6): Depends on all desired user stories being complete
User Story Dependencies
- User Story 1 (P1): Can start after Foundational — no dependencies on other stories
- User Story 2 (P1): Depends on US1 (
capturarAhorapara inyectarahora/ejecucion;esPasadocomo patrón de borde) — testeable sola con reloj fijado - User Story 3 (P2): Depends on US1/US2 (usa
diasLaborablesy semana natural) — testeable sola reconfigurando jornada
Within Each User Story
- Tests MUST be written and FAIL before implementation (T008/T009 antes de T010; T013/T014 antes de T015)
- Funciones puras antes que cableado (
lib/ventanas.tsantes queapp/api/citas/route.ts) - Core implementation before integration (RN2-migración T011 después de T010)
- Story complete before moving to next priority
Parallel Opportunities
- T002 + T003 (inventarios
tiempo/validacion+instantes) en paralelo - T007 (zod
diasLaborables) en paralelo con T004–T006 (migración + semilla, distinto fichero) - T008 + T009 (unit RN2 + integración ahora-único) en paralelo (distintos ficheros); T013 → T014 secuencial (mismo fichero
tests/unit/test_ventanas_tiempo.test.ts) - T015 (
cancelacion+recordatorios) secuencial con T016/T017 — mismo ficherolib/ventanas.ts, coordinar por función (crear esqueleto primero) - T022 + T023 (tests/typecheck + lint/format) en paralelo
Parallel Example: User Story 1
# Launch all tests for User Story 1 together:
Task: "Test unitario RN2 al minuto en tests/unit/test_ventanas_tiempo.test.ts (QS-2)"
Task: "Test integración ahora-único en tests/integration/test_ahora_unico.test.ts (QS-1)"
Parallel Example: User Story 2
# Launch all tests for User Story 2 together:
Task: "Tests unitarios de bordes 24 h y 24–48 h en tests/unit/test_ventanas_tiempo.test.ts (QS-3/QS-4)"
Task: "Test unitario 8 semanas cerradas en tests/unit/test_ventanas_tiempo.test.ts (QS-5)"
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup (T001–T003)
- Complete Phase 2: Foundational (T004–T007, migración S-06 + semilla)
- Complete Phase 3: User Story 1 (T008–T012,
capturarAhora+esPasado+ RN2 propagado) - STOP and VALIDATE:
npm run test -- test_ventanas_tiempo test_ahora_unico test_rn2_pasado+ QS-1/QS-2; cero discrepancias (SC-001) - Deploy/demo if ready (vocabulario mínimo consumible por la 001)
Incremental Delivery
- Setup + Foundational → base migrada y sembrada
- Add US1 → mismo «ahora» en toda operación → validar (MVP)
- Add US2 → cuatro ventanas con bordes únicos → validar QS-3…QS-5 (SC-002)
- Add US3 → denominador honesto → validar QS-6 (SC-003)
- Polish → grep SC-004 + typecheck/lint → merge
Parallel Team Strategy
Un solo agente por spec (Constitución: un agente, una spec). Dentro de la 006, con dos desarrolladores tras Foundational:
- Ambos completan Setup + Foundational juntos
- Dev A: US1 (T008–T012) → US3 (T018–T020)
- Dev B: US2 (T013–T017) tras
capturarAhorade Dev A - Juntos: Polish (T021–T023)
Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability (US1 ↔ FR-001/FR-002/FR-004; US2 ↔ FR-004…FR-007/FR-009; US3 ↔ FR-008/FR-009)
- Cada historia es independientemente completable y testeable con reloj fijado (30/09/2026 10:00), sin BD salvo regresión RN1/RN2
- Commit after each task or logical group
- FR-011: NINGUNA tabla ni endpoint nuevos — si una tarea los necesitara, es alcance fantasma (Constitución IV)
- FR-010: redefinir «ahora», zona, precisión, bordes o ventanas fuera de
lib/(tiempo|ventanas).tses defecto — el grep T021 lo hace mecánico