propuesta · v1.0 · jul 2026

Conector de mensajería

El equipo se entera donde ya está.

Hoy Mogos escribe notificaciones que viven dentro del panel. Si nadie abre el panel, nadie se entera. Este conector saca los eventos que importan al chat donde el equipo ya trabaja — y deja que se resuelvan ahí mismo. Slack y Lark en la primera versión; Teams, Discord o lo que venga después, enchufando un archivo y sin tocar el núcleo.

Alcance
Equipo interno
Configuración
Code-first · ruteo en git
Dirección
Publica y ejecuta de vuelta
Infraestructura nueva
Ninguna · sin Redis
Riesgo
Bajo · aditivo
01La notificación que nadie ve
verificado en el código

El API ya tiene un sistema de notificaciones completo: el modelo Notification, veintinueve tipos en NotificationTypeEnum y un atajo notifyAdmins() llamado desde dieciocho sitios distintos. Funciona. El problema es dónde termina: en una fila de Postgres que solo existe si alguien entra al panel y mira la campanita.

Una cotización que vence a las seis de la tarde, un pago rechazado tres veces seguidas o un cliente pidiendo hablar con una persona no pueden depender de que alguien recuerde revisar. El equipo ya vive en un chat. Ahí es donde tiene que llegar el aviso, y ahí mismo debería poder resolverse.

Lo que este conector no es

No reemplaza las notificaciones del panel ni los correos de Postmark ni las plantillas de WhatsApp para clientes. Es un canal más, y es solo para el equipo interno. Tampoco replica los veintinueve tipos: la mayoría no merece salir. Solo sale lo que tiene una regla escrita a propósito.

02Cuatro capas, una sola dirección
arquitectura

La regla que sostiene todo lo demás: las dependencias apuntan hacia adentro. El código de negocio no conoce el conector, publica un evento. El núcleo no conoce Slack ni Lark, habla con un puerto. Solo los adaptadores, en la orilla, conocen una plataforma concreta.

Capa 1Productores ya existen · se les añade una línea
freights.servicequotations.service payments.servicecontainers.service handoff.serviceservice-quotesstorage
↓   publica un evento tipado
Capa 2Núcleo agnóstico cero tipos de Slack o Lark aquí adentro
catálogo de eventosreglas de ruteo render a Card IRoutbox y reintentos registro de accionesidentidad y autorización
↓   entrega un Card IR y un destino
interface MessagingConnector

El puerto. Seis métodos estables — es todo lo que una plataforma nueva tiene que cumplir.

Slack
v1 · completo
Lark
v1 · completo
Teams
después
Discord
después

La capa 4 es el camino de vuelta: cuando alguien aprieta un botón, cada adaptador verifica la firma de su plataforma, normaliza la interacción al mismo sobre y se la entrega al despachador. De ahí en adelante el núcleo no distingue de dónde vino.

03Un evento, dos renders nativos
diseño

Esto es lo que ve el equipo. El mismo evento del catálogo, el mismo Card IR por detrás, y dos resultados que se sienten nativos en su casa: en Slack un attachment con barra de color, en Lark una tarjeta con cabecera sólida. Nadie escribe el mensaje dos veces.

SlackCotización esperando aprobación
# mogos-cotizaciones6 miembros
m
MogosAPP11:08 a. m.
@aquí — cotización esperando aprobación desde hace 2 h 14 min.
📋 SQ-2481 · Importadora Delta C.A.
Monto
$ 12.480,00
Margen
18,4 % · bajo el umbral
Servicio
Marítimo consolidado
Vence
Hoy 6:00 p. m.
Armada por Yulimar R. · el cliente tiene 3 cotizaciones aprobadas este mes.
AprobarPedir cambios RechazarAbrir en Mogos ↗
mSolo MANAGER y ADMIN pueden aprobar
LarkEl mismo evento, misma fuente
Mogos · Cotizaciones6
m
MogosBOT11:08
📋 SQ-2481 · Importadora Delta C.A.
@todos — esperando aprobación desde hace 2 h 14 min.
Monto
$ 12.480,00
Margen
18,4 % · bajo el umbral
Servicio
Marítimo consolidado
Vence
Hoy 6:00 p. m.
Armada por Yulimar R.
AprobarPedir cambiosRechazar
mSolo MANAGER y ADMIN
Dos restricciones que cambian el contrato

Slack acepta el hex exacto de marca en la barra del attachment; Lark no — su cabecera solo admite colores con nombre de su propia paleta. Por eso la severidad viaja por el núcleo como palabra (info, accion, critico, exito) y cada adaptador la baja a lo que su plataforma sabe pintar.

El texto de un botón de Slack admite 75 caracteres pero trunca visualmente cerca de 30. Lark no tiene ese corte. El Card IR valida a 30 caracteres: la plataforma más restrictiva manda sobre el contrato, no al revés.

04No es una tarjeta: es el recorrido
experiencia

Una notificación llega a un canal que tiene otras diez cosas encima, se discute, alguien aprieta un botón y al día siguiente alguien más quiere el resumen sin revolver el historial. Eso es lo que hay que diseñar, no el rectángulo aislado. Este es el cliente completo, a escala.

SlackEl canal, con el hilo abierto
m
Mogos Group
Canales
# mogos-general
# mogos-operaciones 3
# mogos-cotizaciones
# mogos-pagos 1
# mogos-guardia
# mogos-almacen-china
Aplicaciones
m Mogos 2
# mogos-cotizaciones 6 miembros · Aprobaciones de cotizaciones de servicio
Hoy
m
MogosAPP9:14 a. m.
✅ SQ-2477 · aprobada
Naviera del Centro · $ 4.180,00 · aprobó Yulimar R. en 11 min
m
MogosAPP11:08 a. m.
@aquí — cotización esperando aprobación desde hace 2 h 14 min.
📋 SQ-2481 · Importadora Delta C.A.
Monto
$ 12.480,00
Margen
18,4 % · bajo el umbral
Servicio
Marítimo consolidado
Vence
Hoy 6:00 p. m.
Armada por Yulimar R.
AprobarPedir cambiosRechazar
mSolo MANAGER y ADMIN pueden aprobar
👀 2⏳ 1
CCYRJM 4 respuestas · última hace 6 min
+   Mensaje a #mogos-cotizaciones Aa 😊 @ 📎 ▶
Hilo
m
MogosAPP11:08
📋 SQ-2481 · Importadora Delta C.A. · $ 12.480,00
4 respuestas
YR
Yulimar R.11:21
El margen bajó porque el cliente pidió seguro ampliado. ¿Lo dejamos así o se lo pasamos al costo?
JM
José M.11:24
Con Delta ya lo hicimos en marzo. Que quede en 18 y lo recuperamos en el consolidado.
CC
Carlos C.11:26
Ok. Apruebo.
m
MogosAPP11:27
✅ Aprobada por Carlos Carrasquero · registrada en la bitácora

Cuando la acción viene de una tarjeta con discusión abierta, el conector responde en el hilo, no en el canal: el canal queda limpio y el contexto no se pierde. Es una opción de la regla de ruteo, responderEnHilo: true, no un comportamiento fijo.

05Las otras superficies de Slack
experiencia

Un conector serio no es solo mensajes al canal. «Pedir cambios» necesita que alguien escriba por qué; la pestaña Inicio convierte la app en un panel personal dentro de Slack; y /mogos permite consultar sin publicarle nada a nadie.

SlackModal · views.open
Pedir cambios · SQ-2481
¿Qué hay que corregir?
El margen está por debajo del umbral
Se registra como motivo estructurado para el reporte mensual.
Detalle para Yulimar R.
Sube el seguro ampliado al costo del cliente y vuelve a mandarla…
Nuevo vencimiento
Mañana, 12:00 p. m.
CancelarEnviar

Trampa real: el trigger_id del clic caduca a los 3 segundos. Si el despachador consulta la base de datos antes de abrir el modal, el modal no abre. La autorización se resuelve con identidad ya cacheada; el chequeo profundo va en el view_submission.

SlackPestaña Inicio · views.publish
mMogos
MensajesInicioAcerca de
Buenos días, Carlos
Lunes 28 de julio · esto es lo tuyo hoy.
3
Cotizaciones esperándote
7
Contenedores en tránsito
2
Pagos por verificar
14
Salen esta semana
Lo más urgente
📋 SQ-2481 · Importadora Delta
$ 12.480,00 · vence hoy 6:00 p. m.
AprobarVer
🛑 ORD-9917 · pago rechazado 3 veces
Comercial San Martín · $ 3.420,00 · sin dueño
Tomar el caso
m Vinculado como carlos@mogosgroup.com · rol ADMIN

Se republica sola cada vez que cambian tus pendientes. Es lo que hace que la app se sienta un producto y no un webhook.

SlackComando · respuesta efímera
# mogos-operaciones
CC
Carlos C.2:41 p. m.
/mogos flete FL-2481
m
MogosAPP2:41 p. m.
🚢 FL-2481 · marítimo consolidado
Estado
En puerto · La Guaira
Contenedor
MSKU-4417820
Clientes
7 consolidados
Free time
4 días
🔒 Solo tú puedes ver esto · Compartir en el canal
06Lark: la misma operación, otra casa
experiencia

Lark no tiene canales con almohadilla: tiene grupos con avatar, la lista lateral muestra el último mensaje y las tarjetas van dentro del flujo del chat. Y trae algo que Slack no tiene: un toast de confirmación instantánea, y campos de formulario dentro de la propia tarjeta.

LarkEl chat completo, con el toast tras la acción
m
💬Chats
📄Docs
📅Agenda
Tareas
🔍 Buscar
OP
Mogos · Operaciones 7:42
Mogos: 🚢 Contenedor en puerto · La Guaira
3
CO
Mogos · Cotizaciones 11:27
Mogos: ✅ SQ-2481 · aprobada
PG
Mogos · Pagos Ayer
Mogos: 🛑 Pago rechazado · ORD-9917
1
m
Mogos Ayer
Tu resumen del día está listo
AC
Almacén China Vie
Wei: 已装柜,明天发船
Mogos · Cotizaciones6🔔   📌   ⋯
Hoy
m
MogosBOT11:08
📋 SQ-2481 · Importadora Delta C.A.
@todos — esperando aprobación desde hace 2 h 14 min.
Monto
$ 12.480,00
Margen
18,4 % · bajo el umbral
AprobarPedir cambiosRechazar
JM
José M.11:24
Con Delta ya lo hicimos en marzo. Que quede en 18 y lo recuperamos en el consolidado.
Cotización aprobada
m
MogosBOT11:27
✅ SQ-2481 · aprobada
Monto
$ 12.480,00
Aprobó
Carlos Carrasquero
mAprobada desde Lark · 11:27
😊   Escribe un mensaje

El toast oscuro es la confirmación que Lark devuelve en el mismo cuerpo del callback, antes de que la tarjeta termine de actualizarse. Slack no tiene equivalente: allá la señal de que el clic entró es que los botones se apagan.

LarkEl formulario va dentro de la tarjeta
m
MogosBOT11:26
✏️ Pedir cambios · SQ-2481
¿Qué hay que corregir?
El margen está por debajo del umbral
Detalle para Yulimar R.
Sube el seguro ampliado al costo…
Nuevo vencimiento
Mañana, 12:00 p. m.
EnviarCancelar
mLo que escribas lo ve el grupo
Cómo lo resuelve el núcleo

Los campos se declaran una vez

La acción define sus campos en el catálogo: motivo (selección), detalle (texto) y vence (fecha). El adaptador de Slack los monta en un modal; el de Lark los inserta en la tarjeta. Nadie escribe el formulario dos veces.

Privacidad

El formulario en tarjeta de Lark es visible para todo el grupo mientras se llena. Para acciones con datos sensibles — motivos de rechazo, montos internos — la acción se marca privado: true y el adaptador de Lark abre un chat con el bot en vez de escribir en el grupo. Es una regla del núcleo, no un criterio del adaptador.

08El ruteo vive en código
code-first

Qué evento sale, a qué destino, bajo qué condición y con qué mención. Todo en TypeScript, revisable en un PR y con historia en git. Ningún evento sale por defecto: si no tiene regla, se queda en el panel y ya.

notifier/routing/routes.ts
export const ROUTES: Route[] = [

  // operación diaria · informativo, sin ruido
  { on: 'freight.arrived_port', to: [canal('operaciones')] },

  // solo las cotizaciones que de verdad requieren a un manager
  {
    on:      'service_quote.pending_approval',
    to:      [canal('cotizaciones')],
    cuando:  (p) => p.amountUsd >= 5_000 || p.marginPct < 20,
    mencion: 'aqui',
    agrupar: (p) => `quote:${p.quoteId}`,   // una sola tarjeta por cotización
    responderEnHilo: true,
  },

  // el mismo evento, dos destinos, y silencio por cliente
  {
    on:       'payment.verification_failed',
    to:       [canal('pagos'), canal('guardia')],
    cuando:   (p) => p.attempts >= 3,
    mencion:  'canal',
    silencio: { ventanaMin: 60, por: (p) => p.clientId },
  },

]
Destinos

El nombre lógico en código, el identificador afuera

Las rutas nombran canal('pagos'). El identificador real de cada canal —y sobre todo el token del bot— se resuelve en arranque contra el proveedor de secretos y se cachea en memoria. Cambiar de canal no es un despliegue.

Un secreto por plataforma

Slack y Lark no comparten destino

El mismo nombre lógico resuelve a slack/pagos y lark/pagos. Una ruta puede publicar en las dos a la vez durante una migración, y apagarse en una sin tocar la otra.

09El Card IR: el objeto neutro
el corazón

Aquí el sistema es agnóstico o no lo es. El renderer convierte un evento en bloques semánticos — ni Block Kit, ni tarjeta de Lark. Seis tipos de bloque cubrieron las cuatro tarjetas de este documento, y cubrirían las siguientes veinte.

notifier/card/card.types.ts
export type Card = {
  severidad: 'info' | 'accion' | 'critico' | 'exito'
  titulo:    string
  mencion?:  'aqui' | 'canal' | { usuarios: string[] }
  bloques:   Bloque[]
}

export type Bloque =
  | { t: 'texto';    md: string }
  | { t: 'campos';   items: { k: string; v: string }[] }
  | { t: 'linea' }
  | { t: 'pie';      items: string[] }
  | { t: 'enlace';   texto: string; url: string }
  | { t: 'acciones'; items: Accion[] }

export type Accion =
  | { tipo: 'enlace';  texto: string; url: string }
  | { tipo: 'ejecuta'
      clave: AccionKey                 // del registro de acciones
      texto: string                  // ≤ 30 · Slack trunca ahí
      estilo?: 'primario' | 'peligro'
      confirmar?: string
      campos?: CampoFormulario[] }     // modal en Slack, en tarjeta en Lark
Cómo baja cada bloque a su plataforma
Bloque neutroSlackLark
severidadcolor hex exacto en el attachmentplantilla con nombre: turquoise · orange · red · green
titulobloque headercabecera de la tarjeta
textosection con mrkdwnelemento markdown
campossection fields a dos columnascolumn_set de dos columnas
lineadividerhr
piecontextnote
accionesactions · hasta 25 elementos por bloquemódulo action
10El puerto: seis métodos
la frontera

Esto es todo lo que hay que implementar para enchufar Teams, Discord o lo que aparezca. Un archivo, sin tocar el núcleo.

notifier/port/messaging-connector.interface.ts
export interface MessagingConnector {
  readonly plataforma: Plataforma

  // ── ida ──────────────────────────────────────────────────
  resolverDestino(ref: DestinoRef): Promise<string>
  enviar(destino: string, card: Card): Promise<EnvioRef>
  actualizar(ref: EnvioRef, card: Card): Promise<void>

  // ── vuelta ───────────────────────────────────────────────
  verificarFirma(req: PeticionCruda): boolean
  parsearAccion(req: PeticionCruda): SobreDeAccion
  acuse(sobre: SobreDeAccion, card?: Card): RespuestaHttp

  // ── capacidades opcionales · el núcleo pregunta antes ────
  enviarEfimero?(destino: string, usuario: string, card: Card): Promise<void>
  abrirFormulario?(sobre: SobreDeAccion, campos: CampoFormulario[]): Promise<void>
  publicarPanel?(usuario: string, card: Card): Promise<void>
}

SobreDeAccion es el mismo objeto venga de donde venga: { claveAccion, payload, usuarioPlataforma, envioRef, plataforma }. A partir de ese punto el núcleo no distingue Slack de Lark.

Cómo se usa una capacidad opcional

Nunca a ciegas. El núcleo pregunta if (conector.enviarEfimero) y, si no existe, cae a la alternativa declarada en el catálogo — en Lark, un mensaje directo del bot. Así una plataforma pobre degrada en vez de romperse. Es el mismo patrón que ya usa el módulo messaging del repo con sendListMessage en Instagram.

11El clic y los tres segundos
verificado en la documentación

Slack y Lark coinciden en algo que define la arquitectura: hay 3 segundos para responder o el usuario ve un error en su pantalla. Aprobar una cotización toca base de datos, dispara correos y quizá un WhatsApp — eso no cabe. Por eso el despachador es ack-first: acusa recibo de inmediato y hace el trabajo después.

1
Verifica la firma ~40 ms

Slack: HMAC-SHA256 sobre v0:timestamp:body con el signing secret. Lark: verificación de token y descifrado AES. Si falla, 401 y no se lee nada más del cuerpo.

2
Normaliza al sobre común ~10 ms

El payload crudo se convierte en SobreDeAccion. De aquí en adelante nadie sabe de qué plataforma vino.

3
Resuelve la identidad ~60 ms

El identificador de Slack o Lark se traduce a un usuario de Mogos vía ConnectorIdentity. Si no está vinculado, se responde con el aviso de vinculación y se guarda la acción pendiente.

4
Autoriza por rol ~20 ms

Se comprueba el rol del usuario de Mogos, con datos cacheados. Estar en el canal no es permiso para aprobar nada.

5
Acusa recibo corte a los 3 s

Responde 200 con la tarjeta en «procesando» y los botones ya retirados. En Lark, además, el toast. Todo lo anterior cabe holgadamente en menos de 300 ms.

6
Ejecuta y reescribe fuera del reloj

Corre el handler real contra el servicio de negocio y llama actualizar() con la tarjeta final: verde si salió, roja con el motivo si falló.

Por qué el orden importa

Retirar los botones en el acuse, antes de ejecutar, es lo que resuelve la carrera de dos managers aprobando la misma cotización al mismo tiempo: el segundo clic llega a una tarjeta que ya no tiene botones. Sin eso, un conector interactivo es una fuente de datos corruptos.

12«¿Y tú quién eres?»
el primer día

Alguien aprieta «Aprobar» y el conector no sabe quién es dentro de Mogos. Esto le pasa a todo el equipo el primer día, así que hay que diseñarlo, no improvisarlo.

SlackEfímero · nadie más lo ve
m
MogosAPP11:26 a. m.
🔗 Falta vincular tu cuenta
Para aprobar cotizaciones necesito saber quién eres dentro de Mogos. Es una vez y toma diez segundos.
Vincular mi cuenta ↗
mAl volver, apruebo SQ-2481 automáticamente
🔒 Solo tú puedes ver esto

Detalle pequeño, dignidad grande: nadie más ve que no estabas vinculado.

La acción no se pierde

Se guarda y se ejecuta al volver

El intento se guarda como acción pendiente con su clave de idempotencia. El botón lleva a un enlace de un solo uso que caduca en 10 minutos; al autenticarse en Mogos se escribe la fila de ConnectorIdentity y se ejecuta la acción original. El usuario aprieta un botón una sola vez, aunque el sistema le haya tenido que pedir permiso en el medio.

Vincular no es autorizar

Identidad y permiso son cosas distintas

Vincularse solo dice quién eres. Si tu usuario de Mogos es OPERADOR, los botones de aprobar te van a seguir diciendo que no. La membresía del canal y el rol nunca se mezclan.

13El canal no es una identidad
seguridad

Un botón en un chat es una superficie de ejecución expuesta a internet. Se trata como tal.

RiesgoCómo se cierra
Petición falsificadaVerificación de firma por adaptador antes de leer nada del cuerpo. Slack: HMAC-SHA256 con el signing secret. Lark: token y AES. Sin firma válida no se parsea siquiera.
RepeticiónSe rechaza cualquier petición con marca de tiempo de más de 5 minutos y se lleva registro de los identificadores de interacción ya vistos.
Escalada de privilegiosTabla ConnectorIdentity. Sin vínculo no hay acción, y el rol que manda es el de Mogos. Pertenecer al canal no autoriza nada.
Fuga de datosEl renderer nunca vuelca el payload completo: solo los campos que el evento declara. Montos y nombres de cliente sí; documentos, cédulas y tokens nunca.
Doble ejecuciónBotones retirados en el acuse más clave de idempotencia por par (envío, acción). El segundo clic no encuentra nada que apretar.
TrazabilidadCada acción ejecutada desde el chat queda en la bitácora con el usuario real, la plataforma y la marca de tiempo. Auditable igual que si se hubiera hecho en el panel.
14Outbox, sin Redis
fiabilidad

El repo no tiene BullMQ, ni Redis, ni bus de eventos — son llamadas directas a servicios, Prisma y @nestjs/schedule. Esta propuesta no los introduce: una tabla y el cron que ya está instalado alcanzan de sobra para el volumen de un equipo interno.

prisma/schema.prisma
model ConnectorDelivery {
  id             String    @id @default(uuid()) @db.Uuid
  eventKey       String
  dedupeKey      String    @unique
  plataforma     ConnectorPlatformEnum
  destino        String
  card           Json
  estado         DeliveryStatusEnum @default(PENDIENTE)
  intentos       Int       @default(0)
  proximoIntento DateTime?
  refExterna     String?   // ts de Slack · message_id de Lark
  ultimoError    String?
  creadoEn       DateTime  @default(now())
  enviadoEn      DateTime?

  @@index([estado, proximoIntento])
  @@map("connector_deliveries")
}
Idempotencia

dedupeKey único

Se compone de evento : entidad : plataforma : destino. Un reintento del cron jamás duplica la tarjeta, y dos productores que publiquen lo mismo colapsan en una sola fila.

Reintentos

Backoff exponencial

Un cron cada 30 segundos toma lo pendiente. Reintentos a 1, 4, 16 y 60 minutos. Al quinto fallo pasa a FALLIDO y avisa en el canal de guardia — el conector reporta sus propias caídas.

Actualización diferida

refExterna guardada

Es lo que permite reescribir una tarjeta días después: cambió el estado del flete y la tarjeta original se actualiza sola, sin publicar una nueva.

Cuándo sí haría falta una cola

El umbral, dicho de una vez

Si el volumen pasara de unos pocos miles de entregas al día o hiciera falta paralelismo real entre instancias, tocaría mover el outbox a una cola. Hoy no es el caso, y meterlo antes sería infraestructura sin problema que resolver.

15Enchufar una plataforma nueva
la prueba

La prueba de que la abstracción sirve. Esto es todo lo que costaría meter Teams mañana:

1 · crear teams.connector.ts con los seis métodos 2 · registrarlo en el módulo 3 · guardar su token y sus identificadores de canal 4 · añadir 'teams' a los destinos de las rutas que lo quieran
Cambios en el núcleo: cero

Ni en el catálogo de eventos, ni en las reglas de ruteo, ni en el renderer, ni en el outbox, ni en el despachador de acciones, ni en un solo servicio de negocio. Si meter una plataforma obliga a tocar el núcleo, el diseño falló — y se arregla ahí, no en el adaptador.

Lo que cada plataforma sí y no puede

Esta tabla es el contrato del puerto: lo que está en las dos es obligatorio; lo que está en una sola es capacidad opcional.

CapacidadSlackLarkEn el puerto
Tarjeta con bloquesBlock KitInteractive cardobligatorio
Botones de acciónactions · hasta 25módulo actionobligatorio
Actualizar en sitiochat.updaterespuesta del callbackobligatorio
Hilosnativosrespuestas en hiloobligatorio
Color exacto de marcahex en el attachmentsolo paleta con nombreseveridad como palabra
Mensaje efímerochat.postEphemeralse resuelve con chat privadoopcional
Formulariomodal · views.opencampos en la tarjetadeclarativo
Panel personalpestaña Iniciosin equivalente directoopcional
Confirmación previaobjeto confirmsegunda tarjetaopcional
Aviso instantáneose apagan los botonestoast en el callbackopcional
Ventana de respuesta3 segundos3 segundosack-first
16Entrega en cuatro fases
plan

Cada fase deja algo funcionando en producción. Ninguna depende de que la siguiente exista, así que el proyecto se puede detener en cualquier corte sin dejar nada a medias.

Fase 1

Núcleo y Slack, solo salida

  • Catálogo, ruteo y Card IR
  • Outbox y cron de reintentos
  • Adaptador de Slack: enviar y actualizar
  • Tres eventos reales de punta a punta
El equipo ya recibe avisos
Fase 2

Lark

  • Adaptador de Lark contra el mismo puerto
  • Sin tocar el núcleo — es la prueba de fuego
  • Se corrige lo que la abstracción no previó
Queda demostrado que es agnóstico
Fase 3

Acciones interactivas

  • Endpoints entrantes y verificación de firma
  • ConnectorIdentity y vinculación de cuentas
  • Despachador ack-first y registro de acciones
  • Aprobar y rechazar desde el chat
Se resuelve sin abrir el panel
Fase 4

Cobertura

  • Se migran los eventos que valgan la pena
  • Modal, pestaña Inicio y comandos
  • Resúmenes diarios y silenciamientos
El conector es la vía por defecto
17Decisiones tomadas por defecto
pendientes de confirmar

Tres cosas quedaron sin respuesta explícita durante el diseño. Están resueltas con el criterio más conservador para no bloquear la escritura del plan, pero cualquiera de las tres se puede cambiar sin rehacer nada. Si una de estas no te cuadra, dilo antes de empezar la fase 1.

DecisiónSe asumióPor qué, y qué costaría cambiarla
Dónde vive el núcleo apps/api/src/notifier Sigue el patrón del repo, usa Prisma directo y arranca sin preparar nada. Hoy el API es el único que necesita publicar. Si más adelante agent, delivery o warehouse tuvieran que publicar sin pasar por el API, el catálogo y el renderer se mueven a packages/ sin tocar adaptadores.
Primeros eventos cotización esperando aprobación · pago rechazado · cliente pidiendo agente Los tres tienen dueño claro y cuestan dinero si nadie los ve a tiempo, que es exactamente lo que justifica sacarlos del panel. Cambiar la lista es editar el catálogo y las rutas: horas, no días.
Dónde viven los secretos variables de entorno, con el resolvedor abstraído El repo hoy no usa Supabase Vault en ningún lado; los secretos están en Render. Meter Vault ahora sería infraestructura nueva en la fase 1. Como todo pasa por un resolvedor de destinos, migrar a Vault después toca un solo archivo.

Un núcleo que no sabe de Slack, y una orilla que sí.

Todo lo caro de este sistema —el catálogo, el ruteo, el render, el outbox, la identidad, la autorización— se escribe una sola vez y no vuelve a tocarse. Lo que cambia por plataforma es un archivo de seis métodos. Esa es la única razón por la que meter Lark después de Slack, o Teams después de Lark, cuesta un día y no un trimestre.

Riesgo

Bajo y aditivo. Nada de lo que existe cambia de comportamiento. Si el conector se cae, el panel sigue igual que hoy.

Infraestructura

Ninguna nueva. Una tabla, un cron y dos endpoints. Sin Redis, sin colas, sin servicios extra.

Primer corte útil

Fase 1. Tres eventos llegando a Slack, con reintentos y sin duplicados.

Lo que falta

Confirmar las tres decisiones de la sección 17 antes de arrancar.

Mogos Group · conector de mensajería · propuesta v1.0 Julio 2026 · Slack y Lark verificados contra su documentación