Traducción. Autoritativo: fs-msg-0001-channel-cascade.md.
Cuando esto se entregue, cualquier módulo podrá preguntar “¿puedo notificar a esta persona sobre esto, y cómo?” y recibir una respuesta que ya considera nuestro estado operativo, la configuración del tenant y las decisiones de la propia persona.
Contexto
Todo módulo necesita notificar a alguien, y sin una respuesta única cada uno inventa la suya — uno verifica consentimiento, otro olvida quiet hours, un tercero envía igual porque su mensaje le pareció importante. Así termina una plataforma con cuatro caminos de entrega y un reclamo. ADR-019 lo convierte en una función pura sobre cuatro niveles de configuración y un conjunto de compuertas del destinatario. Que sea pura importa:resolveChannels no hace I/O, así que se puede
testear exhaustivamente, y un cambio de reglas es un cambio en una función probada y no en cuatro
lugares.
La fila del canal y el puerto existen desde el día uno aunque el adaptador llegue después (DEC-E1).
Alcance (normativo)
- Los cuatro niveles: plataforma, tenant, módulo, enrutamiento de evento.
resolveChannels(evento, tenant, módulo, destinatario)como función pura enpackages/core.- Compuertas del destinatario: consentimiento, supresión, centro de preferencias, y para marketing quiet hours y frequency caps.
- Categoría por entrada de enrutamiento:
transactional | marketing | product. - Una razón tipada para cada exclusión, registrada cuando un mensaje no se envía.
- Endpoints de gestión para cada nivel.
Fuera de alcance (normativo)
- Renderizar — FS-MSG-0002. La resolución decide si y dónde, no qué.
- Encolar y entregar — FS-MSG-0005 y FS-MSG-0006.
- El almacenamiento del consentimiento, que es de
corey acá se lee, nunca se escribe. - Fallback entre canales (push falla → email), que es F2 y queda anotado (DEC-E4).
Comportamiento (normativo)
- La resolución corre los niveles en orden, y cualquiera que diga no la termina: plataforma operativa → tenant habilitado y configurado → módulo habilitado → existe enrutamiento del evento.
- Luego las compuertas del destinatario: consentimiento para ese canal ∧ no suprimido ∧ el centro de preferencias lo permite ∧ (solo marketing) quiet hours y frequency caps.
- Transaccional no es opt-out (DEC-E3). Pasa el centro de preferencias, quiet hours y caps, pero nunca pasa la supresión: un rebote duro significa que la dirección no funciona, y la categoría no cambia eso.
- Cada “no” devuelve una razón tipada, y la razón se registra. “No enviado” sin razón hace imposible el soporte e imposible responder una pregunta de cumplimiento.
- La función es pura: sin base de datos, sin caché, sin leer el reloj. Sus entradas son el evento, la configuración ya cargada y el snapshot del destinatario.
- Las quiet hours se evalúan en la zona horaria del destinatario cuando se conoce, cayendo a la del tenant. Una regla de quiet hours en la zona equivocada es peor que ninguna.
- El resultado es una lista de canales, no uno. Un evento puede ir legítimamente a in-app y email.
- PROHIBIDO: cualquier camino de envío que no llame a esta función · una configuración de tenant que desactive la verificación de supresión o de consentimiento · una categoría que salte la cascada.
Datos (normativo)
API (normativo)
simulate responde “si este evento ocurriera para este contacto ahora, qué enviaríamos y por qué no
el resto” — la herramienta de soporte más útil del módulo.
Eventos (normativo)
Ninguno emitido. Esta feature consume los eventos de todos los módulos y decide qué sigue.Criterios de aceptación (normativo)
- 100% de cobertura de ramas en
resolveChannels, con un fixture por nivel y por compuerta. - Un destinatario suprimido no recibe nada, incluido transaccional.
- Quien optó por salir de marketing sigue recibiendo transaccional.
- Las quiet hours se evalúan en la zona del destinatario, verificado cruzando la línea de cambio de fecha.
- Cada exclusión devuelve una razón tipada distinta, y la razón queda registrada.
- Desactivar un canal a nivel de módulo detiene los envíos de ese módulo sin afectar a otros.
simulateno escribe nada y devuelve tanto los canales resueltos como las razones del resto.- Negativo: ninguna configuración desactiva la compuerta de consentimiento ni la de supresión.
Ejecución
Un solo slice, comando síncrono. La función pura vive enpackages/core junto al evaluador de
acumulación; las tablas y endpoints en backend/api. Parte de TS-002.