Feature Specification: Identidad compartida del paciente de CitaClara
Feature Branch: 007-identidad-paciente
Created: 2026-09-30
Status: Draft
Input: User description: "Identidad compartida del paciente de CitaClara: la identidad es pacienteId (001), nunca correo + teléfono. Define una única normalización documentada de correo y teléfono (mayúsculas, espacios, prefijo +34, formato ES) usada por el portal y por los recordatorios; una única regla de resolución de duplicados (qué pasa cuando correo + teléfono coincide con más de una ficha: denegación genérica con derivación al teléfono de la clínica, sin enumerar fichas); la sesión del portal (cookie firmada, 30 min de inactividad, «Cerrar sesión», sin persistencia); y el camino de cierre de la deuda del enlace (el enlace pasa a verificarse contra esta identidad ejecutando cancelarCita). Ningún paciente ve ni cancela citas de otra ficha en ningún canal. Incluye si se permite fusionar fichas duplicadas y quién puede hacerlo."
Clarifications
Session 2026-09-30 (normalización, sesión, enlace y fusión)
- Q1 — ¿Cuál es el algoritmo exacto de normalización? → A: correo: recortar espacios extremos, eliminar espacios interiores y pasar a minúsculas; teléfono: eliminar espacios, guiones, puntos y paréntesis, convertir prefijo
+34o0034a formato nacional de 9 dígitos. Tras normalizar, se aplica la validación estricta ES heredada de la 001 (FR-004); lo que no la cumple es «incorrecto». - Q2 — ¿Cómo se firma y se cierra la sesión del portal? → A: cookie firmada con secreto del servidor, caducidad deslizante de 30 min de inactividad renovada con cada uso; «Cerrar sesión» la invalida en servidor de inmediato; sin persistencia más allá de la cookie (nada en
localStorage, sin «recordar dispositivo»); tras caducidad o cierre se exige reidentificación completa y no quedan datos residuales. - Q3 — ¿Cómo es el enlace verificado que cierra la deuda del email? → A: token firmado atado a
citaId+pacienteId, caduca en el inicio de la cita o a las 72 h desde el envío (lo que ocurra antes); reutilizable para ver la confirmación pero la acción es idempotente víacancelarCita; antiflood de 10 intentos/hora por enlace. - Q4 — ¿Se pueden fusionar fichas duplicadas? → A: en v1 no hay fusión (ni automática ni manual): la duplicidad se resuelve denegando con derivación a la clínica. Las citas y los envíos históricos nunca se reasignan solos. La fusión queda fuera de alcance para una spec posterior.
- Q5 — ¿Qué avisos exactos se muestran? → A: los del portal, únicos para todos los canales: «No hemos encontrado esos datos; revísalos o llama a la clínica» (incorrecto) y «Tienes varias fichas con esos datos; llama a la clínica» (duplicado), ambos con el teléfono de la clínica (001 FR-001).
User Scenarios & Testing
User Story 1 — Una sola normalización para todos los canales (Priority: P1)
Recepción corrige un teléfono con espacios y +34, el paciente entra al portal con otra variante equivalente y el recordatorio llega al mismo destinatario: las tres comparaciones usan el mismo algoritmo y dan el mismo resultado.
Why this priority: Hoy el portal normaliza y los recordatorios no: el mismo paciente puede entrar pero no recibir (o viceversa). Sin algoritmo único no hay identidad compartida.
Independent Test: Se puede probar por completo con una matriz de variantes (mayúsculas, espacios, +34, 0034, guiones) sobre una ficha conocida y comprobando que todas las variantes equivalentes resuelven a la misma ficha en portal y en recordatorios.
Acceptance Scenarios:
- Given la ficha con
ana.garcia.lopez@correo.esy612345678, When el paciente tecleaAna.Garcia.Lopez@correo.esy+34 612 345 678, Then ambas variantes resuelven a la misma ficha. - Given una variante que tras normalizar no cumple el formato ES estricto, When se usa para entrar o como destinatario, Then se trata como «incorrecto» con el aviso único, sin revelar si existe.
- Given la misma variante equivalente, When se usa en el portal y en la comparación de destinatario de recordatorios, Then el resultado es idéntico en ambos canales.
User Story 2 — Duplicados que nunca filtran entre fichas (Priority: P1)
Cuando correo + teléfono coincide con más de una ficha, ningún canal muestra ni cancela nada: se deniega con aviso genérico y se deriva al teléfono de la clínica.
Why this priority: Es la garantía «cero fugas entre fichas» del portal, extendida al email. Sin ella, la colisión que bloquea el portal sigue enviando recordatorios al destinatario ambiguo.
Independent Test: Se puede probar por completo creando dos fichas gemelas (mismo correo + teléfono normalizados) y comprobando que portal y enlace deniegan sin mostrar citas de ninguna.
Acceptance Scenarios:
- Given dos fichas con el mismo correo + teléfono normalizados, When se intenta entrar al portal con ellos, Then se deniega con «Tienes varias fichas con esos datos; llama a la clínica» (+ teléfono) y no se muestra ninguna cita.
- Given las mismas fichas gemelas, When el proceso de recordatorios las evalúa, Then no genera envío a ese destinatario ambiguo y lo anota en el informe de incidencias como duplicado.
- Given cualquier denegación por duplicado, When se lee el aviso, Then no enumera ni distingue las fichas coincidentes.
User Story 3 — Sesión que caduca y cierra de verdad (Priority: P2)
La sesión del portal vive 30 min de inactividad, se renueva con cada uso y «Cerrar sesión» la invalida al instante sin dejar restos en el navegador.
Why this priority: Es la puerta del portal; sin cierre real, un móvil compartido expone citas ajenas. P2 porque presupone la identidad (US1/US2).
Independent Test: Se puede probar por completo entrando, esperando 30 min sin actividad, y comprobando que la siguiente petición exige reidentificación; y cerrando sesión y comprobando que el token anterior ya no vale.
Acceptance Scenarios:
- Given un paciente identificado, When pasan 30 minutos sin actividad, Then la siguiente petición se rechaza y exige de nuevo correo y teléfono.
- Given un paciente identificado, When pulsa «Cerrar sesión», Then el acceso se cierra al instante y reutilizar el token anterior se rechaza.
- Given una sesión cerrada o caducada, When se inspecciona el navegador, Then no quedan datos de identificación (ni cookie válida ni
localStorage).
User Story 4 — Enlace verificado que cierra la deuda (Priority: P2)
El enlace del recordatorio pasa a ser un token firmado atado a la cita y al paciente, con caducidad y antiflood, y ejecuta cancelarCita contra esta identidad.
Why this priority: Cierra la deuda consciente FR-017/Q9 de recordatorios: hoy quien reenvía el email puede cancelar citas ajenas. P2 porque requiere US1–US3 y cambia el email.
Independent Test: Se puede probar por completo generando el enlace, usándolo desde otro navegador (sin sesión), y comprobando que solo opera sobre su cita; y reenviándolo tras su caducidad y comprobando el rechazo.
Acceptance Scenarios:
- Given un enlace vigente de una cita
reservada, When se abre sin sesión de portal, Then muestra solo esa cita y permite confirmar su cancelación víacancelarCita. - Given un enlace caducado (pasado el inicio o 72 h), When se usa, Then se rechaza con aviso claro sin mutar nada.
- Given un enlace sometido a más de 10 intentos/hora, When sigue intentándose, Then se bloquea temporalmente con aviso claro.
Edge Cases
- ¿Qué pasa si el paciente tiene dos fichas con el mismo nombre pero distinto correo/teléfono? Cada ficha es una identidad distinta (
pacienteId); cada una ve solo sus citas y nunca se mezclan. - ¿Qué pasa si recepción corrige el email a uno que usa otra ficha? La corrección crea una colisión: esa combinación pasa a tratarse como duplicado (US2) hasta que recepción la resuelva por teléfono; no se fusiona nada solo.
- ¿Qué pasa si el token del enlace se usa después de que recepción marque la cita como
completada? Se rechaza por estado final víacancelarCita; la identidad no cambia las reglas de estados. - ¿Qué pasa si el paciente comparte dispositivo? «Cerrar sesión» + caducidad de 30 min limitan la exposición; no hay «recordar dispositivo» en v1.
- ¿Qué pasa si el correo contiene
+o mayúsculas legítimas? La normalización solo recorta espacios y pasa a minúsculas; el resto del local-part se conserva tal cual. - ¿Qué pasa si el teléfono tiene 9 dígitos pero no empieza por 6/7/9? Tras normalizar no cumple el formato ES estricto y se trata como «incorrecto».
Requirements
Functional Requirements
- FR-001 (identidad): La identidad del paciente MUST ser
pacienteId(ficha de la 001) en todos los canales. Ningún canal MUST usar correo + teléfono como identidad; solo como credenciales de resolución haciapacienteIdsegún FR-002…FR-004. - FR-002 (normalización): El sistema MUST normalizar correo (recortar espacios extremos, eliminar interiores, minúsculas) y teléfono (eliminar espacios, guiones, puntos, paréntesis;
+34/0034→ 9 dígitos nacionales) antes de comparar. Tras normalizar MUST aplicar la validación estricta ES de la 001 (FR-004). Portal y recordatorios MUST usar este algoritmo y MUST NOT definir el suyo. - FR-003 (incorrecto): Una combinación que no resuelve a ninguna ficha (o no normaliza a formato válido) MUST denegarse con «No hemos encontrado esos datos; revísalos o llama a la clínica» (+ teléfono de la clínica), MUST NOT revelar si correo o teléfono existen y MUST NOT mostrar ninguna cita.
- FR-004 (duplicados): Si correo + teléfono normalizados resuelven a más de una ficha, el sistema MUST denegar en todos los canales con «Tienes varias fichas con esos datos; llama a la clínica» (+ teléfono), MUST NOT enumerar ni distinguir fichas, MUST NOT generar recordatorio a ese destinatario (se anota como duplicado en el informe de incidencias) y MUST NOT mostrar ni cancelar ninguna cita.
- FR-005 (sesión): La sesión del portal MUST sostenerse en cookie firmada con secreto del servidor, caducidad deslizante de 30 min de inactividad renovada con cada uso y acción «Cerrar sesión» que la invalida en servidor al instante. MUST NOT persistir identificación más allá de la cookie (nada en
localStorage, sin «recordar dispositivo»). Tras caducidad o cierre MUST exigir reidentificación completa. - FR-006 (enlace verificado): El enlace de cancelación MUST ser un token firmado atado a
citaId+pacienteId, que caduca en el inicio de la cita o a las 72 h del envío (lo antes posible). MUST permitir ver la confirmación sin sesión pero solo opera sobre su cita víacancelarCita(005). Superados 10 intentos/hora por enlace MUST bloquearse temporalmente con aviso claro. Sustituye a la deuda FR-017/Q9 de recordatorios. - FR-007 (sin fusión en v1): El sistema MUST NOT fusionar fichas (ni auto ni manual) en v1. Las citas y envíos históricos MUST NOT reasignarse solos. La fusión queda fuera de alcance para spec posterior.
- FR-008 (avisos únicos): Los textos de FR-003/FR-004 (con teléfono de la clínica, 001 FR-001) son el catálogo único para todos los canales; ningún canal MUST redactar los suyos. Todos en español de España.
- FR-009 (alcance): Esta feature MUST NOT definir transiciones (ver 005), métricas, envíos ni auth de recepción. No crea tablas: usa
Pacientede la 001.
Key Entities
- Identidad (
Paciente.id): Ficha de la 001 (nombre, teléfono, email). Única fuente de «quién es quién»; correo + teléfono son credenciales de resolución, no identidad. - Normalización: Función documentada (FR-002) compartida por portal y recordatorios. Sin estado propio.
- Sesión del portal: Cookie firmada + revocación en servidor; 30 min deslizantes; cierre instantáneo; sin restos.
- Enlace verificado: Token firmado (
citaId+pacienteId, caducidad, antiflood) que autoriza una sola cita víacancelarCita.
Success Criteria
Measurable Outcomes
- SC-001: El 100 % de las variantes equivalentes de una matriz (mayúsculas, espacios,
+34/0034, guiones) resuelve a la misma ficha en portal y en recordatorios; las no válidas se tratan como «incorrecto» en ambos. - SC-002: El 100 % de los intentos con fichas gemelas (portal, enlace, reintento) se deniega sin mostrar ni mutar citas de ninguna ficha; el caso de recordatorios queda en incidencias como duplicado.
- SC-003: Toda sesión con 30 min sin actividad exige reidentificación; toda sesión cerrada invalida su token al instante; cero restos en navegador tras cierre/caducidad.
- SC-004: Un enlace vigente solo opera sobre su cita y sin sesión; uno caducado o con flood se rechaza sin mutar nada, en el 100 % de los intentos.
- SC-005: Cero definiciones propias de normalización, sesión o duplicados fuera de esta spec (revisión + grep por merge).
Assumptions
- Sin cuentas ni contraseñas; la clínica no gestiona altas (Sara 2026-09-29, portal Q1).
- El secreto de firma vive en el servidor y rota sin invalidar sesiones más allá de su caducidad natural; el mecanismo exacto de firma/rotación se decide en el plan.
- La validación estricta ES de fondo es la de la 001 (FR-004); aquí solo se añade la normalización previa.
- La deuda del enlace sin verificar (recordatorios FR-017/Q9) se cierra adoptando FR-006; el aviso de no reenviar se mantiene hasta entonces.
- Fusión de fichas, auth de recepción y transiciones quedan fuera (ver 005 y specs propias).