Quickstart — 003 Recordatorios de cita

Matriz test↔FR/SC (Constitución VI). APP_URL=http://localhost:3000, TZ=Europe/Madrid.

1. Preparar

npm run db:up
npx prisma migrate deploy
npm run seed

2. Proceso diario (US1: FR-001/002/003/015 → SC-001/SC-002)

# Automático (cron 00:00 Europe/Madrid):
npm run recordatorios
# Manual (misma garantía, FR-015; para el piloto y SC-001/SC-002):
curl -s -X POST http://localhost:3000/api/recordatorios/ejecutar \
  -H 'cookie: <sesion-recepcion>' | jq
ls datos/salida-correo/   # recordatorio-<citaId>-AAAAMMDD-HHMM.eml
# Re-ejecutar N veces → cero .eml nuevos, cero envíos nuevos (SC-002: diff del dir + count EnvioRecordatorio)

Casos: cita a 30 h → 1 envío; a 5 días → 0; cancelada/completada/no_asistida → 0; bordes 24 h y 48 h justos → dentro; 23:59 → fuera; cita movida y en ventana → 1 envío nuevo + histórico intacto; servicio cambiado → nuevo con servicio vigente + precio congelado; email corregido y en ventana → nuevo al email correcto; cita <24 h → 0 y sigue reservada; proceso no ejecutado y cita ya <24 h → 0 para siempre.

3. Contenido (US2: FR-004/005/012 → SC-003) y cancelación (FR-006/007/009 → SC-004/SC-005)

Abrir un .eml: clínica, 30/09/2026, 10:00–10:45, profesional, servicio, 40,00 €, teléfono, enlace /cancelar/<id>?token=, aviso de no reenviar, sin SMS/WhatsApp (grep -ri 'sms\|whatsapp' app lib → 0, SC-007).

# En plazo (>24 h): confirmar → cancelada + tramo libre (re-reservable, RN1 en verde)
# Fuera (<24 h: cita a 23 h y a 24 h justas con reloj controlado): 23 h → «fuera de plazo, llama a la clínica al …» y sigue reservada; 24 h justas → aplica
# Doble confirmación → segunda «Esta cita ya está cancelada», sin efectos

4. Señal en ficha (FR-011) e incidencias (FR-016 → SC-009)

curl -s http://localhost:3000/api/citas/<id>/recordatorio -H 'cookie: <sesion>' | jq
# {"estado":"enviado","texto":"Recordatorio enviado el 29/09/2026 a las 00:05"} | {"estado":"pendiente","texto":"Recordatorio pendiente"}
cat datos/salida-correo/INCIDENCIAS-*.txt   # citas sin email válido con motivo; sin .eml; sin cambio de estado

5. Verificaciones globales

npm test            # unit + integración (incl. concurrente Promise.all → 1 aplicada / 1 envío)
npm run typecheck && npm run lint && npm run format:check
npm run comprobar:es
ls datos/salida-correo/ | wc -l   # == count EnvioRecordatorio (SC-008)