Dispatch Multicanal¶
GexCom implementa despacho de notificaciones via el Strategy Pattern: cada canal tiene su propio dispatcher que implementa INotificacionDispatcher.
Arquitectura de Dispatch¶
INotificacionDispatcher Protocol¶
class INotificacionDispatcher(Protocol):
@property
def canal(self) -> CanalNotificacion: ...
def dispatch(self, notificacion: Notificacion) -> Result[str, str]: ...
Ambos dispatchers implementan este Protocol (duck typing, sin herencia).
DispatchJob (frozen dataclass)¶
@dataclass(frozen=True)
class DispatchJob:
notificacion: Notificacion
max_retries: int = 3
attempts: int = 0
def with_attempt(self) -> DispatchJob:
return DispatchJob(
notificacion=self.notificacion,
max_retries=self.max_retries,
attempts=self.attempts + 1,
)
Inmutable — cada reintento crea una nueva instancia con with_attempt().
Canales Soportados¶
| Canal | Dispatcher | Implementacion | Estado |
|---|---|---|---|
| EmailDispatcher | smtplib + TLS | Funcional | |
| WhatsAppDispatcher | Meta Graph API | Funcional | |
| PERSONAL | — | RegistrarGestionManualUseCase | Funcional |
| TELEFONO | — | RegistrarGestionManualUseCase | Funcional |
| CORREO_POSTAL | — | RegistrarGestionManualUseCase | Funcional |
Politica de Reintentos¶
DispatchWorkerconsume jobs deasyncio.Queue- Llama a
dispatcher.send(notificacion) - Si falla y
attempts < max_retries: re-encola conjob.with_attempt() - Si falla y
attempts >= max_retries: actualiza estado aFALLIDA+ audit - Si exito: actualiza estado a
ENVIADA+ registrafecha_envio+ audit
Domain Events¶
El EventBus in-process conecta CrearNotificacionUseCase con el despacho automatico sin acoplar capas:
# CrearNotificacionUseCase publica tras commit exitoso
event_bus.publish(NotificacionCreadaEvent(
event_id=uuid.uuid4(),
occurred_at=datetime.now(UTC),
notificacion_id=saved.id,
canal=saved.canal,
created_by=saved.created_by,
))
# ServiceContainer suscribe el handler (si hay dispatcher configurado)
event_bus.subscribe(NotificacionCreadaEvent, DispatchOnNotificacionCreada(dispatch_uc))
Estado P04
EventBus activo desde P04-D1. El wiring es condicional: solo se suscribe DispatchOnNotificacionCreada cuando dispatcher_registry esta configurado en el ServiceContainer.
Nota de dominio
El handler llama dispatch_notificacion.execute() que requiere estado EN_PROCESO. Las notificaciones se crean en PENDIENTE. La auto-transicion PENDIENTE→EN_PROCESO es una decision de producto pendiente.