BG-01 · ONLINE
[ DESCARGAR CV ]
REGISTRO
REG-010
SECCIÓN
S-ING · INGENIERÍA
ARCHIVADO
2026.10.18
CONSULTA
7 MIN
GOARQUITECTURAMONGODB

Cuatro canales, un núcleo: bots multicanal con arquitectura hexagonal

En Hibot Chat participé en la red de bots e integraciones que conecta cuatro canales de mensajería (WhatsApp, Facebook, Instagram y Telegram) con el núcleo de la plataforma. El sistema mueve más de 5.000 conversaciones al día y opera 24/7. Este registro explica cómo se estructura un backend así y por qué la arquitectura hexagonal es la que mejor le sienta.

Cuatro APIs, cuatro idiomas

Cada plataforma tiene su forma de entregar un mensaje: webhooks con formatos distintos, identificadores de usuario con reglas propias, adjuntos que llegan como URL en una y como referencia a descargar en otra, botones y plantillas que solo existen en algunas. Si esa diversidad llega al código que decide qué hacer con cada conversación, cada regla de negocio termina llena de condiciones por canal, y añadir uno nuevo obliga a tocarlo todo.

El núcleo no conoce los canales

En una arquitectura hexagonal el núcleo define lo que necesita mediante interfaces (los puertos) y cada tecnología externa se conecta por un adaptador. Aquí el núcleo de ruteo solo entiende un mensaje normalizado y una forma de responder. No importa el SDK de ninguna plataforma.

SEGMENTO DE CÓDIGO · GO
// Ilustrativo: el puerto de salida que el núcleo usa para responder.
// El núcleo no sabe si detrás hay WhatsApp o Telegram.
type Channel interface {
    Send(ctx context.Context, to Recipient, msg Outgoing) error
}

// El mensaje que entra al núcleo, ya traducido por el adaptador del canal.
type Incoming struct {
    Channel        ChannelID // whatsapp, facebook, instagram, telegram
    ConversationID string
    From           Recipient
    Text           string
    Attachments    []Attachment
    ReceivedAt     time.Time
}

Cada canal tiene su adaptador de entrada, que recibe el webhook y lo traduce a Incoming, y su adaptador de salida, que implementa Channel con la API de esa plataforma. El núcleo recibe sus dependencias al arrancar y nunca importa un paquete de un canal concreto.

Normalizar sin perder información

Normalizar no es quedarse con el mínimo común. Lo que todos los canales comparten (texto, remitente, conversación, adjuntos) va en campos tipados. Lo que es propio de una plataforma se queda en el adaptador, que sabe cómo representarlo al responder. Así el núcleo trabaja con un modelo estable y cada canal conserva lo que lo hace distinto.

La ventaja se ve en las pruebas: las reglas de ruteo se prueban con mensajes normalizados y un Channel falso, sin levantar ninguna integración real.

Cuando el canal no responde

Cada plataforma impone límites de uso y, de vez en cuando, falla. En un diseño hexagonal esa responsabilidad vive en el adaptador, que es quien conoce las reglas de su canal: cuántas peticiones admite, qué errores son temporales y cuáles no. El núcleo pide enviar un mensaje; el adaptador decide cómo entregarlo sin saturar la plataforma, y la caída de un canal no arrastra a los otros tres.

Métricas de conversación

Con las conversaciones guardadas en MongoDB, las métricas salen de pipelines de agregación en la propia base: volumen por canal, conversaciones por día, tiempos de respuesta. Calcularlas en la base, en vez de traer los documentos a la aplicación, mantiene el cálculo cerca de los datos.

SEGMENTO DE CÓDIGO · JS
// Ilustrativo: conversaciones por canal y por día.
db.messages.aggregate([
  { $match: { receivedAt: { $gte: ISODate('2022-01-01') } } },
  { $group: {
      _id: { channel: '$channel', day: { $dateTrunc: { date: '$receivedAt', unit: 'day' } } },
      conversations: { $addToSet: '$conversationId' },
  } },
  { $project: { conversations: { $size: '$conversations' } } },
  { $sort: { '_id.day': 1 } },
]);

Lecciones

  • Cuando varias integraciones hacen lo mismo de formas distintas, la diferencia debe quedarse en el borde del sistema, no en la lógica de negocio.
  • Un modelo de mensaje normalizado hace que añadir un canal sea escribir un adaptador, no reescribir el núcleo.
  • Los límites de uso y las caídas son reglas de cada canal: viven en su adaptador.
  • Las reglas de ruteo se prueban sin integraciones reales si el núcleo solo depende de interfaces.