## Supuestos adoptados en F7

| Decisión | Supuesto adoptado | Alternativa descartada | Quién debe validar |
|---|---|---|---|
| Alcance estrictamente arquitectónico | Se implementó exactamente lo que el plan delimita: interfaz `CrmAdapterInterface`, `NullCrmAdapter` (activo por defecto, no hace nada), `HubSpotCrmAdapter` (esqueleto, lanza `NotImplementedException` en cada método), `SyncToCrmJob`, 5 eventos de dominio (`AppointmentCreated`/`Completed`/`Cancelled` ya existían desde F4/F5; `TherapistCreated`/`TherapistMembershipChanged` nuevos de F7), y el binding condicional `CRM_ADAPTER=null\|hubspot` en `AppServiceProvider`. **Ninguna llamada HTTP real a HubSpot, ningún mapeo de campos, ningún webhook de entrada** — tal como marca "Fuera de alcance" del plan. | Empezar a mapear campos reales de HubSpot o hacer una llamada HTTP de prueba — descartado explícitamente por el plan y porque la pregunta de negocio "¿HubSpot o solve360?" (docs/preguntas-Verena.md pregunta 4) sigue sin resolver; construir solo la arquitectura no depende de esa respuesta. | — (alcance ya fijado por el plan). |
| `TherapistMembershipChanged` solo se dispara si `mitglied` cambia realmente | Se usa `$therapeut->wasChanged('mitglied')` tras `save()` en `EditTherapeut`, no un disparo incondicional en cada edición — evita ruido innecesario en la cola/CRM por cambios no relacionados (ej. cambiar el teléfono no debería re-sincronizar la membresía). | Disparar el evento en cada `afterSave()` sin condición — descartado por generar sincronizaciones CRM innecesarias. | — (decisión técnica razonable, no requiere validación de negocio). |
| Auto-discovery de listeners, no registro manual | `SyncAppointmentToCrm` y `SyncTherapistToCrm` usan varios métodos `handle*()` por clase (uno por evento), detectados automáticamente por Laravel 11 vía reflexión (`Str::is('handle*', ...)`, confirmado leyendo el código fuente del framework). Mismo patrón ya establecido en F4, donde registrar manualmente con `Event::listen()` había duplicado el envío de emails. | Registrar los listeners manualmente en `AppServiceProvider::boot()` — descartado por el precedente ya documentado en docs/supuestos-F4.md (bug real de duplicación). | — (patrón ya validado, se reutiliza). |
| `SyncToCrmJob` con dos "modos" (contacto vs. actividad) | Un único Job con dos named constructors estáticos (`forContact()`/`forActivity()`) en vez de dos clases de Job separadas — evita duplicar la lógica de resolución del adaptador (`CrmAdapterInterface` inyectado en `handle()`) para dos operaciones que solo difieren en qué método del adaptador invocan. | Dos Jobs separados (`SyncContactToCrmJob`, `SyncActivityToCrmJob`) — descartado por ser una duplicación innecesaria dado que el plan ya especifica una única interfaz con dos métodos. | — (decisión técnica). |

## Actualización (2026-08-04): CRM real confirmado — HubSpot

El cliente confirmó que el CRM real será **HubSpot** (pregunta 4 de `docs/preguntas-Verena.md`,
ya resuelta). Esto NO estaba en el alcance de F7 tal como se ejecutó — el plan marca
explícitamente "ninguna llamada HTTP a la API de HubSpot" como fuera de alcance de esta fase. La
implementación real (autenticación, mapeo de campos, sincronización de contactos/actividades) es
trabajo nuevo, todavía sin fase asignada en el plan ni iniciado. Sigue pendiente decidir con
Verena qué pasa con la integración solve360 que producción usa hoy en vivo (¿convivencia durante
una transición, o corte directo?) antes de empezar esa implementación.

Este archivo se revisa con Verena antes del UAT (F9).
