De VAPI a Retell: la migración que se llevó media arquitectura
El botón verde del teléfono
Si has visitado marcosramirez.dev recientemente, habrás visto un botón flotante verde en la esquina inferior derecha. Es un botón de llamada. Lo pulsas y te contesta un agente de voz: Lucía. Te puede explicar qué hago, qué servicios ofrezco, responder preguntas frecuentes o ayudarte a concertar una cita. Lo que no se ve desde fuera es todo lo que hubo que construir, y reconstruir, para que ese botón funcionase. Esta es esa historia.
El plan inicial: VAPI
Cuando decidí añadir un agente de voz a la web, la primera opción fue VAPI. VAPI es una plataforma para construir agentes de voz impulsados por Inteligencia Artificial: conectas tu LLM, defines la personalidad del agente y tienes una infraestructura lista para gestionar llamadas en tiempo real con menos de 500ms de latencia. Más de 1.000 millones de llamadas procesadas, más de 2,5 millones de agentes desplegados. La propuesta es sólida.
La integración en una web Astro era sencilla: instalar @vapi-ai/web, instanciar la clase, llamar a vapi.start() en el click del botón. Tres estados: idle (verde), loading (amarillo), active (rojo). Click para llamar, click para colgar. Todo en el cliente.
Funcionaba. Pero tenía un problema que no tardé nada en ver.
El problema: la clave de API en el cliente
Para que el SDK de VAPI funcione en el navegador necesita tu API key. Y esa API key vive en el JavaScript que se descarga cualquier visitante de tu página. Sí. Expuesta. En el bundle. Para muchos casos eso puede ser aceptable (hay plataformas que diseñan explícitamente para este flujo), pero en cuanto empiezas a pensar en costes de llamadas y en quién podría extraer esa clave y usarla para sus propios agentes, el confort desaparece.
La alternativa: Retell
Retell AI es una plataforma para agentes de voz conversacionales con ±600ms de latencia, cumplimiento HIPAA y SOC2 Type II. Competidora directa de VAPI, pero con diferencias técnicas relevantes. La más importante para mi caso: Retell no está atado a OpenAI. VAPI en la práctica empuja hacia los modelos de OpenAI, que es su integración más madura y documentada. Retell es agnóstico: puedes conectar cualquier LLM compatible, incluidos modelos abiertos como Llama, Mistral o cualquier proveedor que soporte la interfaz estándar. Para quien ya trabaja con Anthropic, DeepSeek o modelos locales vía OpenRouter, eso cambia el escenario completamente. La seguridad fue un motivo para cambiar, pero no el único. Retell es técnicamente superior en varios frentes: flexibilidad de LLM, arquitectura de autenticación correcta y una latencia más consistente en producción. Pero donde más se nota la diferencia es en las herramientas de desarrollo del agente. Retell tiene una capa de gestión del asistente (a nivel de system prompt, turn-taking, interrupciones, manejo de silencio) que VAPI no alcanza. Configurar cómo se comporta el agente en conversación, cómo gestiona los turnos de voz y cómo responde a situaciones inesperadas es notablemente más preciso en Retell. Para quien construye agentes que tienen que sonar naturales en una llamada real, eso importa y mucho. La diferencia arquitectónica en el SDK web está en cómo se inicia una llamada. Retell no expone la API key al cliente. El flujo es:
- El navegador llama a tu propio endpoint (
/api/retell/create-call) - Tu servidor hace la petición a Retell con la API key (que solo existe en el servidor)
- Retell devuelve un access token de un solo uso
- El cliente usa ese token para iniciar la llamada La API key nunca sale de tu servidor. El cliente solo ve un token efímero. Es más complejo. Pero es correcto.
El efecto dominó
Aquí empieza la parte interesante. Porque migrar de VAPI a Retell no fue solo cambiar una dependencia.
Paso 1: necesito SSR
Para tener un endpoint /api/retell/create-call en Astro necesito rutas de servidor. Astro por defecto genera sitios estáticos: todo se compila a HTML/CSS/JS en build time y no hay servidor que ejecute código en runtime. Necesitaba cambiar a modo output: 'server' (SSR).
Eso requería un adaptador. En mi caso, @astrojs/cloudflare, porque el sitio está desplegado en Cloudflare.
Paso 2: Cloudflare Pages no era suficiente
Una vez añadido el adaptador y configurado SSR, desplegué en Cloudflare Pages (donde ya estaba el sitio) y la ruta /api/retell/create-call devolvía 404.
El problema: Cloudflare Pages tiene soporte SSR, pero con limitaciones. Para rutas de API que dependen de secrets de entorno en tiempo de ejecución, Pages puede ser caprichoso. La solución correcta era migrar a Cloudflare Workers, que es el entorno en el que @astrojs/cloudflare funciona de forma nativa y sin sorpresas.
Paso 3: migración de Pages a Workers
Cloudflare Workers es el runtime de Cloudflare: código JavaScript (o WebAssembly) que se ejecuta en el edge, cerca del usuario. Es lo que Pages usa internamente para sus funciones, pero con acceso completo y sin las restricciones de la abstracción. La migración implicó:
- Configurar
wrangler.jsonpara Workers - Ajustar el output de Astro para Workers (
output: 'server',prerender = falseen las rutas de API) - Mover los secrets de Retell a las variables de entorno de Workers
- Resolver un problema de trailing slash en la ruta del endpoint (Cloudflare Workers es estricto con las URLs y requería
/api/retell/create-call/con barra final)
Paso 4: el componente RetellDialer
El componente VapiButton se convirtió en RetellDialer. Mismos tres estados (idle, loading, active), misma lógica de click para llamar y colgar, pero el flujo de inicio cambia: antes llamaba directamente a vapi.start(), ahora hace un fetch('/api/retell/create-call/'), recibe el token y lo pasa al SDK de Retell para iniciar la sesión.
Las clases CSS también se renombraron de .vapi-btn a .retell-btn. Pequeño detalle, pero el histórico de git queda limpio.
El resultado
El botón verde sigue ahí. Lucía sigue contestando. Para el visitante, nada ha cambiado. Por dentro, la arquitectura es más correcta:
- La API key de Retell nunca llega al navegador
- El sitio corre en Cloudflare Workers con SSR real
- Las rutas de API tienen acceso a secrets de entorno en tiempo de ejecución
- El componente tiene estados claros y código limpio ¿Mereció la pena la semana de trabajo extra? Sí. No porque VAPI fuera malo, sino porque la arquitectura resultante es la correcta para este caso de uso. Cualquier agente de voz en producción con costes reales por llamada necesita control sobre quién puede iniciar esas llamadas.
Lo que aprendí
Una dependencia puede definir tu arquitectura. VAPI funcionaba con un sitio estático. Retell requería SSR. SSR requería Cloudflare Workers. Cada decisión de plataforma arrastra implicaciones que no siempre son obvias en el README.
El flujo de tokens es la forma correcta de hacer esto. Cualquier SDK que te pide exponer una API key en el cliente debería hacerte pensar dos veces. Si tienes costes variables por uso (como ocurre con las llamadas de voz), esa clave expuesta puede salir muy cara.
Cloudflare Workers merece la pena. La migración desde Pages fue más limpia de lo esperado. El despliegue con wrangler es predecible, el edge es rápido y los secrets de entorno funcionan exactamente como uno espera. Sin sorpresas.
Preguntas frecuentes
Preguntas frecuentes
¿Qué diferencia hay entre VAPI y Retell AI?
Las dos son plataformas para agentes de voz con Inteligencia Artificial. La diferencia técnica principal en el SDK web está en el modelo de autenticación: VAPI puede funcionar con la API key directamente en el cliente, mientras que Retell genera access tokens efímeros desde un endpoint de servidor. Retell también presume de ±600ms de latencia y cumplimiento HIPAA/SOC2.
¿Necesito SSR para usar Retell en Astro?
Sí, si quieres proteger tu API key. El flujo de Retell requiere un endpoint de servidor que haga la petición con la clave y devuelva un token al cliente. En Astro eso implica output: ‘server’ y un adaptador para tu plataforma de despliegue.
¿Por qué Cloudflare Workers y no Pages?
Cloudflare Pages soporta funciones, pero con algunas limitaciones en el acceso a secrets en runtime cuando se usa con @astrojs/cloudflare en modo servidor completo. Workers es el entorno nativo del adaptador y funciona sin sorpresas. Si partes de cero con Astro + Cloudflare + SSR, ve directamente a Workers.
¿Puedo hacer esto mismo en otro framework?
Sí. El patrón —endpoint de servidor que genera un token, cliente que usa el token— es agnóstico del framework. En Next.js sería un Route Handler, en Nuxt un server route, en SvelteKit un endpoint en +server.ts. Lo que cambia es el adaptador para el proveedor de despliegue.
Compártelo si te ha resultado útil. Si necesitas montar algo parecido en tu empresa, puedo ayudarte. ¿Has integrado algún agente de voz en una web? ¿Qué plataforma usas? Cuéntame. Y… ¡hasta aquí por hoy!
Artículos relacionados
Gemini 3.5 Flash en el I/O 2026: ¿merece la pena el cambio?
Google acaba de anunciar Gemini 3.5 Flash en el Google I/O 2026. Lo he estado mirando con lupa porque llevo meses usando Gemini 3.0 Flash como modelo base en mis agentes de voz: es barato, rapidísimo y sigue instrucciones como nadie. La pregunta es si 3.5 mejora lo suficiente como para justificar el cambio, o si es uno de esos saltos de versión que solo cambia el número.
OpenCode: Manual Completo - Guía Definitiva del Agente de Coding
Manual completo de OpenCode: instalación, configuración, modelos, agentes, MCP, GitHub integration y casos de uso prácticos. La guía más detallada del agente de coding open-source.
OpenCode: Review Completa y Comparativa con sus Competidores
Después de probar Claude Code, Cursor, Aider y otros agentes de Inteligencia Artificial para programación, encontré OpenCode: la mejor alternativa open-source con TUI avanzada. Te cuento por qué se convirtió en mi herramienta principal y cómo se compara con toda la competencia.