| US3/acceso-ok |
Entrar con correo+teléfono de la semilla |
POST /api/portal/sesion correcto → abrir /portal/mis-citas |
entra y solo ve sus citas (SC-003) |
| US3/incorrecto |
Combinación inexistente |
POST /api/portal/sesion incorrecto |
401 «No hemos encontrado esos datos; revísalos o llama a la clínica al …», sin citas |
| US3/duplicado |
Dos fichas con mismo correo+teléfono normalizados |
crear gemela → POST /api/portal/sesion |
409 «Tienes varias fichas con esos datos; llama a la clínica al …», cero citas (SC-007) |
| US3/caducidad |
30 min sin actividad |
entrar → avanzar reloj 31 min → GET /api/portal/citas |
401 SIN_SESION, exige correo+teléfono (SC-008) |
| US3/cierre |
«Cerrar sesión» |
DELETE /api/portal/sesion → reintentar GET con el token anterior |
401, cookie limpiada, cero restos (SC-008) |
| US1/secciones |
Ficha con historia + futuras |
GET /api/portal/citas |
enCurso/proximas (asc)/anteriores (desc), partición sin solapes, fila 08/10/2026, 10:00–10:45 · sesión fisio · María · reservada (SC-001) |
| US1/vacia |
Ficha solo con historia |
GET /api/portal/citas |
«No tienes citas en curso» y «No tienes próximas citas», historial visible, sin citas ajenas |
| US1/responsive |
390 px y 1440 px |
Playwright e2e/portal-paciente.spec.ts |
legible sin manual ni jerga, botón pulsable sin zoom (SC-006) |
| US2/en-plazo |
reservada con inicio − ahora ≥ 24 h |
confirmar → PATCH /api/portal/citas/[id]/cancelar { confirmar: true } |
200 aplicada, sale de Próximas, entra en Anteriores como cancelada, tramo libre y reutilizable por recepción (POST /api/citas mismo profesional/tramo, SC-005) |
| US2/fuera-plazo |
reservada con inicio − ahora < 24 h |
mismo PATCH |
422 «Ya no se puede cancelar por internet; llama a la clínica al …», sigue reservada (SC-004) |
| US2/final |
completada/cancelada/no_asistida |
ver fila (sin botón) + PATCH directo |
sin botón; el PATCH se rechaza sin mutar (SC-003) |
| US2/doble |
Doble clic sobre la misma cita |
dos PATCH seguidos |
uno aplicada, otro YA_CANCELADA, sin dobles efectos (SC-003) |
| US2/en-curso |
reservada con inicio ≤ ahora < fin |
ver sección «En curso» |
arriba, «en curso», tramo resaltado, sin «Cancelar», con aviso de teléfono |
| SC-006/dinero |
Cualquier vista del portal |
inspeccionar GET y páginas |
cero importes visibles (FR-013 como guarda) |
| Greps |
unicidad de capas |
rg "update.*estado.*cancelada\|toLowerCase\|normaliz" lib app --glob '!lib/{cancelacion,identidad}.ts' y rg "now\(\)\|CURRENT_TIMESTAMP" app/api/portal lib/portal.ts |
única transición en lib/cancelacion.ts, única normalización en lib/identidad.ts, cero now() fuera de capturarAhora() |