## Supuestos adoptados en F3

| Decisión | Supuesto adoptado | Alternativa descartada | Quién debe validar |
|---|---|---|---|
| Formato del token HMAC | `base64url(email) . '.' . timestamp . '.' . hash_hmac('sha256', "email\|timestamp", APP_KEY)`. El plan dice literalmente "Genera token: HMAC-SHA256(email + "\|" + timestamp, APP_KEY)", pero un HMAC por sí solo no es reversible — hace falta acompañarlo del email y el timestamp para poder verificarlo. Se codifican ambos junto a la firma en un único string apto para query string. | Guardar el token generado en BD (tabla `portal_tokens`) y solo pasar un ID opaco por URL. Más simple de rotar/revocar, pero el plan pide explícitamente HMAC-SHA256 sin mencionar persistencia — se optó por la interpretación stateless literal. | Verena / dev — pendiente confirmar si el token lo genera Verena manualmente o se auto-genera al registrar un terapeuta (pregunta ya listada como pendiente en el plan). |
| ¿Quién genera el token? | **No implementado en F3** — no hay ningún flujo en el panel admin que genere y envíe el enlace `/de/buchen?token=...` a un terapeuta todavía. Se implementó `TherapistTokenService::generate()` como servicio reutilizable, listo para conectar cuando se resuelva la pregunta pendiente. | Añadir ya un botón "Generar enlace" en `TherapeutResource` sin tener la respuesta de Verena sobre el flujo exacto (registro automático vs. envío manual). | Verena — pregunta explícita pendiente del plan. |
| Arquitectura de componentes Livewire | El plan lista 5 componentes (BookingCalendar, AnamnesisForm, FileUpload, BookingConfirmation, AppointmentManagement). Se implementaron como **2 componentes reales**: `BuchenWizard` (cubre los 4 primeros como pasos de un único asistente cohesionado) y `AppointmentManagement` (separado, por ser una vista/función independiente de la reserva). | Crear los 4 componentes del asistente como clases Livewire separadas comunicándose por eventos cruzados. Se descartó por añadir acoplamiento y complejidad de wiring sin beneficio funcional real, en línea con el principio de CLAUDE.md de no introducir abstracciones innecesarias. | Dev — decisión técnica de diseño, no de negocio. Toda la funcionalidad descrita está presente. |
| Prefijo `/{locale}/` de las rutas públicas | Fijado explícitamente a `de` en `routes/portal.php`, en vez de resolverlo dinámicamente con `LaravelLocalization::setLocale()`. Se descubrió que ese helper solo lee el segmento de la URL en el momento en que se registran las rutas — funciona en PHP-FPM tradicional (una request = un proceso), pero falla en procesos persistentes (tests, `artisan serve`, Octane) donde las rutas se registran una única vez al arrancar. Con un solo idioma soportado no hay pérdida de funcionalidad. | Depender de la resolución dinámica de mcamara/laravel-localization tal cual, aceptando que los tests de rutas fallarían. | Dev — hallazgo técnico. Revisar si se añade un segundo idioma al portal en el futuro (el plan no lo contempla, ver docs/supuestos-F6.md). |
| Límite de citas para terapeutas no-socios | Implementado en el portal (`BuchenWizard::nonMemberLimitReached`) como límite de citas **totales acumuladas** (`Termin::where('status','!=','cancelada')->count() >= einstellungen.limite_citas_no_socio`), no por día — distinto del límite síncrono de F2B (2 citas/día, general, ver docs/supuestos-F2B.md). Con el límite alcanzado, el botón de nueva reserva queda oculto pero el terapeuta conserva acceso a "Meine Termine" (test crítico del plan). | Que el límite de no-socios fuera también por día, unificándolo con el límite general de F2B. | Verena — el plan mismo distingue ambos conceptos como preguntas separadas pendientes. |
| Almacenamiento de documentos subidos | Se usa `spatie/laravel-medialibrary` (ya en el stack) sobre el disco `local` (privado, `storage/app/private`, nunca `public`) en vez de construir gestión de archivos a medida. Colección `documentos` en `Termin`, con `acceptsMimeTypes` limitado a PDF/JPEG/EPS/DOC/DOCX. | Guardar los archivos con `Storage::putFile()` a mano y una tabla propia de documentos. Se descartó por reinventar lo que Media Library ya resuelve (visibilidad, colecciones, límites de tipo). | Dev — decisión técnica, coincide con el stack ya aprobado. |
| URL firmada para documentos | **Implementado en F3.5 (2026-08-03)**, ver docs/supuestos-F3.5.md fila "Campo Unterlagen (adjuntar ficheros) en Termin". `TerminMediaDownloadController` + ruta `termin-media.download` (`URL::temporarySignedRoute()`, 30 min) + `TerminPolicy::view()`. | — (ya resuelto). | — (cerrado). |
| "Modificar" cita en `AppointmentManagement` | Solo se implementó "cancelar" (libera el slot inmediatamente, ya cubierto por F8 "Flujo de cancelación"). "Modificar" queda fuera de alcance — el terapeuta puede cancelar y reservar de nuevo. | Implementar edición completa de una cita ya reservada desde el portal (cambiar fecha/asesor/anamnesis) sin criterio claro del plan sobre qué es editable. | Dev / Verena — simplificación razonable por alcance/tiempo; revisar si el negocio lo necesita antes de F9. |
| DSGVO / anonimización | El checkbox de aceptación de política de datos es obligatorio antes de confirmar la reserva (`dsgvoAccepted` con regla `accepted`). El aviso de anonimización es obligatorio antes de que se acepte cualquier archivo — se valida también en servidor (`updatedUploads()` descarta los archivos si no se ha marcado el checkbox), no solo ocultando el campo en el HTML. | Confiar solo en la UI (campo de subida deshabilitado) sin revalidar en servidor. | — (ya resuelto, checklist de seguridad F3 cumplido). |
| Mensajes de error del token | "Token expirado" tiene un mensaje específico ("Link abgelaufen"); cualquier otro fallo (firma inválida, formato incorrecto, terapeuta no encontrado) comparte el mismo mensaje genérico ("Zugriff nicht möglich"), para no revelar si un email existe en el sistema (checklist de seguridad F3: "no exponer si el email existe"). | Dar un mensaje distinto para cada tipo de fallo (más útil para depurar, pero filtra información a un atacante). | — (decisión de seguridad ya tomada, coincide con el checklist del plan). |

Este archivo se revisa con Verena antes del UAT (F9). Si algún supuesto está mal, se corrige antes de F9.
