Arquitectura Tecnica de Canales

Documentacion tecnica detallada sobre como funcionan los canales de comunicacion internamente

arquitecturacanalesemailwhatsappplatformbufferingtecnicodesarrollo

Arquitectura Tecnica de Canales

Esta documentacion describe en detalle como funciona internamente el sistema de canales de comunicacion, incluyendo el ruteo de mensajes, buffering de emails y la integracion con agentes de automatizacion.

Vision General del Sistema

Componentes Principales

ComponenteResponsabilidad
InboxServiceRuteo de mensajes, gestion de hilos, canales
CandidateEmailServiceEnvio de emails, buffering, templates
EmailServiceEnvio bajo nivel via AWS SES
TwilioServiceWhatsApp y SMS via Twilio
WebSocketServiceNotificaciones en tiempo real
AgentGraphOrquestacion de agentes de automatizacion

Flujo de Canales

                    ┌──────────────────────────────────────┐
                    │           MENSAJE ENTRANTE           │
                    └──────────────────────────────────────┘
                                      │
                    ┌─────────────────┼─────────────────┐
                    ▼                 ▼                 ▼
              ┌─────────┐      ┌───────────┐     ┌──────────┐
              │ EMAIL   │      │ WHATSAPP  │     │ PLATFORM │
              │ (SES)   │      │ (Twilio)  │     │ (WebSocket)│
              └────┬────┘      └─────┬─────┘     └─────┬────┘
                   │                 │                 │
                   └─────────────────┼─────────────────┘
                                     ▼
                    ┌──────────────────────────────────────┐
                    │           InboxService               │
                    │  - Identifica candidato              │
                    │  - Busca/crea hilo                   │
                    │  - Detecta bot activo                │
                    │  - Rutea a agente o humano           │
                    └──────────────────────────────────────┘

Canales Soportados

Tabla de Canales

CanalCodigoProveedorDireccionBot Support
EmailemailAWS SESBidireccionalSi
WhatsAppwhatsappTwilioBidireccionalSi
SMSsmsTwilioBidireccionalSi
PlataformaplatformWebSocketBidireccionalSi
ChatbotchatbotWebSocketBidireccionalSi
ComentariocommentInternoSolo salidaNo
SistemasystemInternoSolo salidaNo

Ruteo de Mensajes por Canal

Logica de Seleccion de Canal

El sistema determina el canal de respuesta usando configuredChannel:

// En InboxService y BotConversationGraph
const configuredChannel = botState?.channel || "chatbot";

if (configuredChannel === "whatsapp" && thread.candidate?.phone) {
  // Enviar via WhatsApp
} else if (configuredChannel === "email" && thread.candidate?.email) {
  // Enviar via Email (con buffering)
} else {
  // Default: platform/chatbot
}

Prioridad de Canal

  1. Canal configurado en botState - Establecido al iniciar el agente
  2. Canal del ultimo mensaje - Si no hay botState.channel
  3. Fallback a chatbot - Si no hay informacion de canal

Establecimiento del Canal

Cuando un agente inicia, AgentExecutionService establece el canal:

// En AgentExecutionService.executeReclutadorAction()
(thread.botState as any).channel = channel; // "email", "whatsapp", o "chatbot"

Buffering de Emails para Bots

Problema que Resuelve

Cuando un bot envia multiples mensajes consecutivos (ej: saludo + pregunta), sin buffering cada mensaje seria un email separado. El buffering los combina en uno solo.

Arquitectura del Buffer

┌─────────────────────────────────────────────────────────────────────┐
│                     CandidateEmailService                           │
│                                                                     │
│  emailBuffer: Map<executionId, QueuedBotEmail[]>                    │
│                                                                     │
│  ┌─────────────────┐    ┌─────────────────┐                        │
│  │ Execution 500   │    │ Execution 501   │                        │
│  │ ┌─────────────┐ │    │ ┌─────────────┐ │                        │
│  │ │ Msg 1: Hola │ │    │ │ Msg 1: Info │ │                        │
│  │ │ Msg 2: ?    │ │    │ └─────────────┘ │                        │
│  │ │ Msg 3: Test │ │    └─────────────────┘                        │
│  │ └─────────────┘ │                                               │
│  └─────────────────┘                                               │
└─────────────────────────────────────────────────────────────────────┘

Metodos del Buffer

MetodoDescripcion
queueBotEmail(params, executionId)Agrega mensaje al buffer
hasQueuedEmails(executionId)Verifica si hay mensajes pendientes
flushBotEmails(executionId)Envia todos los mensajes como un email
transferQueuedEmails(from, to)Transfiere buffer entre ejecuciones
clearQueuedEmails(executionId)Limpia buffer sin enviar

Flujo de Buffering

1. Bot genera mensaje 1
   └── queueBotEmail(msg1, exec500) → buffer[500] = [msg1]

2. Bot genera mensaje 2
   └── queueBotEmail(msg2, exec500) → buffer[500] = [msg1, msg2]

3. Bot entra en estado de espera
   └── flushBotEmails(exec500)
       ├── Combina [msg1, msg2] en un solo email
       ├── Envia email con template multi-mensaje
       └── Crea UN ThreadMessage con contenido concatenado

Contenido del ThreadMessage

Cuando hay multiples mensajes, el contenido se guarda concatenado:

[Maria]: ¡Hola Juan! Soy Maria de ACME Corp...

[Maria]: ¿Cual es tu expectativa salarial?

Transferencia Parent-Child

Escenario

Cuando un agente padre invoca un agente hijo (ej: trigger_agent), los emails del hijo deben agregarse al buffer del padre para enviarlos juntos.

Flujo de Transferencia

┌──────────────────────────────────────────────────────────────┐
│                    Agente Padre (Exec 500)                   │
│  ┌──────────────┐                                            │
│  │ Buffer: [A]  │ ◄─────────────────────────────────────┐   │
│  └──────────────┘                                       │   │
│        │                                                │   │
│        ▼                                                │   │
│  trigger_agent(waitForCompletion: true)                 │   │
│        │                                                │   │
│        ▼                                                │   │
│  ┌──────────────────────────────────────────────────┐  │   │
│  │           Agente Hijo (Exec 501)                 │  │   │
│  │  ┌──────────────┐                                │  │   │
│  │  │ Buffer: [B,C]│                                │  │   │
│  │  └──────────────┘                                │  │   │
│  │        │                                         │  │   │
│  │        ▼ (al completar)                          │  │   │
│  │  transferQueuedEmails(501, 500) ─────────────────┼──┘   │
│  └──────────────────────────────────────────────────┘      │
│                                                             │
│  Buffer final padre: [A, B, C]                             │
│        │                                                    │
│        ▼ (al entrar en wait o completar)                   │
│  flushBotEmails(500) → Email con 3 mensajes                │
└──────────────────────────────────────────────────────────────┘

Codigo de Transferencia

En AgentGraph.completeNode() y executeActionNode():

const eventData = execution?.eventData as any;
const parentExecutionId = eventData?.parentExecutionId;
const canInheritLock = eventData?.canInheritLock; // true si waitForCompletion=true

if (parentExecutionId && canInheritLock) {
  // Transferir al padre
  candidateEmailService.transferQueuedEmails(state.executionId, parentExecutionId);
} else {
  // Flush independiente
  await candidateEmailService.flushBotEmails(state.executionId);
}

Historial de Conversacion

Guardado en Execution

Los mensajes se guardan en execution.conversationHistory para mostrar en el modal de detalles:

// En processTemplateBasedConversation
if (botState.executionId) {
  const execution = await executionRepo.findOne({ where: { id: botState.executionId } });

  // Mensaje del usuario
  execution.addMessage({
    role: "user",
    content,
    channel: channel as any,
    timestamp: new Date().toISOString(),
  });

  // Respuesta del bot
  if (result.response) {
    execution.addMessage({
      role: "agent",
      content: result.response,
      channel: channel as any,
      timestamp: new Date().toISOString(),
    });
  }

  await executionRepo.save(execution);
}

Estructura del Historial

{
  "conversationHistory": [
    {
      "role": "agent",
      "content": "¡Hola! Soy Maria...",
      "channel": "email",
      "timestamp": "2026-01-13T10:00:00Z"
    },
    {
      "role": "user",
      "content": "$50,000 mensuales",
      "channel": "email",
      "timestamp": "2026-01-13T10:05:00Z"
    },
    {
      "role": "agent",
      "content": "¿Cuando podrias comenzar?",
      "channel": "email",
      "timestamp": "2026-01-13T10:05:01Z"
    }
  ]
}

Estados de Ejecucion y Canales

Estados Relevantes

EstadoDescripcionFlush de Emails
RUNNINGEjecutando accionesNo flush
WAITINGEsperando respuesta/delaySi flush
COMPLETEDFlujo terminadoSi flush
FAILEDError en ejecucionClear buffer

Distincion Importante

  • WAITING: Para acciones no-conversacionales (delay, wait_for_response de agente regular)
  • RUNNING: Para bots conversacionales (reclutador) que estan activamente chateando
const isConversationalBot = action.actionType === "reclutador";
if (!isConversationalBot) {
  // Set WAITING - triggers flush
  await executionRepo.update(state.executionId, { status: ExecutionStatus.WAITING });
} else {
  // Stay RUNNING - no flush yet
}

Flujo Completo de Email con Bot

Secuencia Detallada

1. TRIGGER: Candidato aplica a vacante
   └── EventBus emite APPLICATION_CREATED

2. AGENT START: Agente se activa
   └── AgentExecutionService crea ejecucion
   └── Establece botState.channel = "email"

3. BOT GREETING:
   └── BotConversationGraph genera saludo
   └── InboxService detecta channel = "email"
   └── queueBotEmail(saludo, execId)

4. BOT QUESTION:
   └── BotConversationGraph genera pregunta
   └── queueBotEmail(pregunta, execId)

5. WAIT FOR RESPONSE:
   └── shouldWait = true
   └── AgentGraph.executeActionNode detecta wait
   └── flushBotEmails(execId)
   └── Email enviado con [saludo, pregunta]
   └── ThreadMessage creado con contenido concatenado

6. USER REPLY (via email):
   └── SES recibe email
   └── SESInboundService procesa
   └── InboxService.routeMessageToReclutadorBot
   └── Agregacion de 2s (BullMQ)

7. BOT PROCESSES:
   └── processTemplateBasedConversation
   └── Guarda en execution.conversationHistory
   └── queueBotEmail(respuesta, execId)

8. CONTINUE OR COMPLETE:
   └── Si hay mas preguntas: volver a 4
   └── Si completo: flushBotEmails + resumeExecution

Debugging y Logs

Logs Importantes

[CandidateEmailService] Queued email for execution 500, queue size: 1
[CandidateEmailService] Queued email for execution 500, queue size: 2
[CandidateEmailService] flushBotEmails called for execution 500
[CandidateEmailService] Flushing 2 queued emails for execution 500:
  [1] "¡Hola Juan! Soy Maria..."
  [2] "¿Cual es tu expectativa salarial?"

Archivo de Log

Los eventos de buffering se registran en /tmp/email-buffer.log:

2026-01-13T10:00:00.123Z [CandidateEmailService] Queued email for execution 500...
2026-01-13T10:00:00.456Z [CandidateEmailService] flushBotEmails called...

Compatibilidad entre Canales

Garantias de Compatibilidad

AspectoComportamiento
WhatsAppUsa sendWhatsApp() directamente, sin buffering
PlatformUsa createMessage() directamente, sin buffering
EmailUsa buffering via queueBotEmail() y flushBotEmails()
FallbackSi canal no definido, usa platform/chatbot

Por Que Solo Email Tiene Buffering

  • Email: Multiples mensajes rapidos = spam en inbox del candidato
  • WhatsApp: Mensajes separados son naturales en chat
  • Platform: WebSocket en tiempo real, mejor experiencia con mensajes individuales

Proximos Pasos

¿No encontraste lo que buscabas?

Nuestro equipo de soporte está listo para ayudarte.

Contactar Soporte