1 de abril de 2026 • 8 min de lectura
Especialista en IA y Automatización
Diseño sistemas de comunicación impulsados por IA. Mi trabajo se centra en agentes de voz, chatbots de WhatsApp, asistentes de IA y automatización de flujos, construidos principalmente sobre Twilio, n8n y LLM modernos como OpenAI y Claude. En los últimos 7 años he entregado más de 30 proyectos de automatización que manejan más de 250 000 interacciones mensuales.
Si te gusta el contenido que hago, puedes suscribirte y recibir información valiosa por correo. No se envía spam, solo novedades sobre publicaciones interesantes o contenido especializado del que hablo.
La barrera de idioma en los call centers es un problema resuelto. La mayoría todavía no lo sabe, o cree que hace falta middleware caro y seis meses de integración. No es así. Te cuento cómo armé un sistema de traducción en tiempo real en los dos sentidos, con Twilio Flex, Studio, TaskRouter y la Realtime API de OpenAI. Y ngrok para exponer el desarrollo local a internet.
Cuando un cliente llama hablando español, el agente escucha inglés. Cuando el agente responde en inglés, el cliente escucha español. Los dos lados van en vivo, en streaming, y ninguno tiene que hacer nada distinto. Sin música de espera, sin "aguarde mientras lo comunicamos con un traductor".
Vamos a ver cómo funciona.
El corazón del sistema es una sola aplicación Node.js sentada en el medio de todo. Tiene tres tareas:
Para lograrlo necesitás dos números de teléfono.
El primero es el número de cara al cliente. Cuando alguien llama, Twilio Studio corre un flow que captura la llamada y la conecta a tu aplicación por WebSocket. El segundo es el número del lado del agente, que maneja la otra pata de la llamada y también se conecta a tu aplicación. Los dos números mandan y reciben audio de tu server en paralelo.
El motivo de los dos números es que los media streams de Twilio funcionan por pata de llamada. Necesitás conexiones WebSocket separadas para capturar lo que dice quien llama y lo que dice el agente. Tu app queda en el medio, traduciendo en las dos direcciones.
Además de eso:
Cuando un cliente llama al número principal, corre Studio. Le aparece un menú: 1 para inglés, 2 para español. Esa selección se guarda y la aplicación la usa como idioma de origen. Simple, pero importante: la app necesita saber en qué dirección traducir.
Desde ahí, Studio redirige la llamada a tu URL de ngrok. Tu app la recibe, crea una conferencia de Twilio o una llamada proxy, y conecta la segunda pata, el número del agente, con el agente disponible en Flex. TaskRouter toma la task y la asigna a un worker libre.
El agente acepta la llamada en la UI de Flex. Las dos patas quedan en vivo. Tus conexiones WebSocket transmiten el audio de cada lado. OpenAI traduce y devuelve audio. Ese audio se reproduce en la pata opuesta.
El repositorio incluye un JSON que importás directo en Studio. No hace falta armarlo desde cero.
Andá a Studio > Flows > Create new flow e importá el JSON. El flow tiene dos ramas según la tecla que aprieten. La rama 1 marca la llamada como origen inglés. La rama 2, como origen español. Las dos terminan redirigiendo a tu URL de ngrok con el host reemplazado. Dejá el resto del path igual, cambiá solo el host.
Con el flow publicado, asignalo al número uno, el de cara al cliente, como handler de las llamadas entrantes.
Para el número dos, el del agente, configurá su webhook apuntando también a tu URL de ngrok. Mismo reemplazo de host, misma lógica. A ese número lo va a llamar tu app por código para crear la segunda pata.
Flex arma un workspace y workflows por defecto en el primer login. Para este proyecto la lógica de ruteo es simple: cualquier task va a cualquier worker disponible. O sea que tu Target Worker Expression en TaskRouter tiene que ser 1 == 1, que matchea a todos.
Andá a TaskRouter > Workspaces > Workflows, abrí tu workflow y confirmá la expresión de filtro. Después andá a Queues y verificá que tu cola apunte a la misma expresión. Si tu cuenta de agente aparece en "Matching Workers", está listo.
El SID que necesitás de acá arranca con WW. Copialo, va en tu .env como FLEX_WORKFLOW_SID.
Todo lo que conecta las piezas vive en tu .env:
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
CALLER_NUMBER= # Customer-facing number
FLEX_NUMBER= # Agent-side number
FLEX_WORKFLOW_SID= # WW... SID from TaskRouter
OPENAI_API_KEY=
PORT=5050
DOMAIN= # ngrok domain WITHOUT https://
FORWARD_AUDIO_BEFORE_TRANSLATION=false
Un par de cosas para marcar:
Los números de teléfono van en formato internacional completo con el prefijo +, sin espacios ni paréntesis. +12025551234, no (202) 555-1234.
La variable DOMAIN no lleva https://. El mismo string de dominio se usa para los webhooks HTTPS y para las conexiones WebSocket (wss://). Si le ponés el prefijo, la URL del WebSocket se rompe.
La API key de OpenAI necesita acceso a Whisper. Si le acotás los permisos y bloqueás los modelos de audio, la transcripción falla en silencio y vas a perder una hora debuggeando lo que no es. Dejá el acceso a Whisper habilitado.
FORWARD_AUDIO_BEFORE_TRANSLATION viene en false. Si lo ponés en true, reproduce el audio original en los dos lados junto con la traducción. Sirve para algunos casos, molesta en desarrollo cuando te escuchás dos veces con un delay.
Flex tiene tres planes. Los relevantes para un proyecto así:
Para desarrollo y producción de bajo volumen, el plan gratis sobra. Las limitaciones de analítica no importan para lo que estamos armando. Tenés la UI completa del agente, presencia en tiempo real y todas las features de TaskRouter.
Cuando tu app recibe audio de una pata de la llamada, se lo manda a la Realtime API de OpenAI. La API devuelve el audio traducido como stream. Tu app lo inyecta en la pata opuesta con <Play> de Twilio o con inyección de media stream.
El detalle clave: esto no es "grabo la oración entera y después traduzco". Es traducción en streaming. La latencia se nota, pero no rompe la conversación. En una prueba en vivo ("Hola, soy Gonzalo, llamo porque mi cuenta está bloqueada"), el audio traducido llegó lo bastante rápido como para que la charla se sintiera natural. No perfecto, pero usable. Es el tipo de latencia que le atribuís a una mala conexión, no a una capa de traducción.
El caso de uso son los call centers con clientes en varios países. Contratás agentes que hablan un idioma y atienden a clientes que hablan otros. Sin necesidad de personal bilingüe.
Pero el caso que más me interesa es revenderlo como servicio. Si trabajás con pymes que empezaron a expandirse afuera, esto es una solución llave en mano que desplegás sobre su cuenta de Twilio actual. La infraestructura ya está. Vos sumás una capa de middleware y un segundo número.
El sistema también se extiende solo. El menú de selección de idioma es un widget de Studio. Sumar un tercer idioma es una tercera rama. Español, inglés, portugués. Misma arquitectura, más ramas.
Un par de cosas con las que me choqué en la implementación y conviene saber de antemano:
La URL de ngrok cambia en cada reinicio. Cada vez que reiniciás ngrok, salvo que tengas plan pago con dominio fijo, te da una URL nueva. Eso implica actualizar el webhook de los dos números y el redirect del flow de Studio. Molesto. Pagá el dominio fijo o usá otra solución de tunneling en producción.
El setup inicial de Flex es automático pero lento. Cuando creás tu cuenta de Flex, Twilio provisiona el workspace, los canales y la config por defecto de TaskRouter en segundo plano. Dale unos minutos antes de tocar nada. Si falta algo, esperá y refrescá.
Las conexiones WebSocket se caen. En producción vas a necesitar lógica de reconexión. En desarrollo no molesta, pero con volumen real un WebSocket caído significa que un lado de la llamada recibe audio sin traducir y sin aviso.
El repositorio está acá. Incluye el JSON del flow de Studio, el template del .env y el código de la aplicación. Si venís siguiendo la serie de llamadas salientes, los patrones te van a resultar familiares. Misma estructura de webhook, mismo setup de ngrok, distinto payload en el WebSocket.
-Gonza
Descubre cuánto te está costando tu sistema de comunicaciones.
Obtén la auditoría de comunicaciones