Todo menú telefónico es la misma disculpa. Marque 1 para ventas. Marque 2 para soporte. Marque 3 para volver a escuchar estas opciones.
Es una tabla de ruteo, leída en voz alta, a la velocidad del habla. Quien llama ya sabe lo que quiere. Solo tiene que esperar a que le reciten tu organigrama y después adivinar en qué rama entra alguien como él.
La alternativa no es un menú mejor. Es ningún menú: se contesta el teléfono, la persona dice lo que necesita, y del otro lado algo entiende, resuelve, y pone a un humano cuando de verdad hace falta un humano.
Eso es un agente de voz, y es más o menos una tarde de trabajo.
Lo que te cuesta de verdad la centralita vieja
No el alquiler de la línea. Otras tres cosas.
Llamadas abandonadas. Cada capa de menú pierde gente. Cuelgan y prueban con un competidor, o eligen la rama equivocada y caen con alguien que no los puede ayudar, que es peor porque ahora dos personas perdieron el tiempo.
Ningún registro de lo que la gente quería. Un IVR sabe que marcaron 2. No sabe que estaban preguntando por el reembolso de una orden de marzo. Esa intención es lo más valioso de la llamada y tu central telefónica la tira a la basura cada vez.
No puede hacer nada. Un menú rutea. No puede consultar una reserva, cotizar un precio ni actualizar una dirección. Todo termina en un traspaso a una persona, incluida la mitad de las llamadas que esa persona va a resolver leyendo un campo de una pantalla.
Un agente de voz arregla las tres por la misma razón: es una conversación, así que escucha la intención, y tiene tools, así que puede actuar sobre ella.
Lo que vas a construir
| El menú viejo | Un agente de voz de Zavu |
|---|---|
| Marque 1 para ventas | Quien llama dice lo que quiere |
| Un árbol que mantienes en un portal | Un prompt en tu repo, con version control |
| Rutea y se desentiende | Consulta y responde durante la llamada |
| Transfiere a ciegas | Transfiere cuando tu prompt lo dice, al número que definiste |
| Sabe que marcaron un dígito | Transcripción completa, duración y costo por llamada |
El agente no es un producto aparte del que contesta WhatsApp. Es el mismo agente con un bloque voice: mismo prompt, mismo modelo, mismas tools, misma knowledge base.
Antes de empezar
Tres requisitos, y uno tarda un día.
- Un sender con número de teléfono. Ese número es al que llama la gente.
- Voice Agents habilitado para tu team. Está en beta y se prende por team, así que los endpoints de voz devuelven
403hasta que alguien lo activa. Escribe a hi@zavu.dev y pídelo. - Balance positivo. Las llamadas se cobran por minuto conectado. Las llamadas entrantes solo se contestan mientras tu balance está sobre cero, que es un modo de falla que conviene conocer antes de que pase un lunes.
Quién hace qué
Tú pones la cuenta, el número y el criterio sobre cuándo debe atender una persona. Tu coding agent escribe y publica el resto.
| Tú | Tu coding agent |
|---|---|
| Creas la cuenta de Zavu | Encuentra o crea el sender |
Corres zavu login y autorizas | Escribe defineAgent con el bloque de voz |
| Pides que habiliten Voice Agents | Escribe las tools que llama durante la llamada |
| Apruebas comprar un número | Setea los secrets y deploya |
| Atiendes la primera llamada de prueba | Lee la transcripción y corrige el prompt |
La única parte que tipeas
bashnpx skills add zavudev/zavu-skills npx zavudev login
El primer comando instala las skills de Zavu en tu coding agent. Claude Code, Cursor, Copilot, Codex y unos 40 más están soportados; el installer te pregunta cuáles. Una de esas skills es voice-agent, que es lo que evita que tu agente invente campos de configuración que no existen.
login abre el browser, eliges el proyecto y la key queda en ~/.zavu/credentials.json. De aquí en adelante describes.
Después describes el agente
textConstruye un voice agent de Zavu para mi clínica en mi sender actual. Que conteste en español, diga los horarios, busque una cita por número de teléfono, la reagende, y transfiera a +14155551234 cuando pidan una persona o suenen molestos. Tope de doce minutos. Deploya.
No necesitas conocer los comandos de abajo. Están para que reconozcas lo que pasa por la pantalla, y para que notes si se saltó un paso.
bashnpx zavudev whoami # qué proyecto estoy por tocar npx zavudev senders list # el número al que va a llamar la gente npx zavudev fn secrets set SENDER_ID "jn76vnxet8g5nq661by3v06y1581bmmn" npx zavudev deploy
Si prefieres arrancar de algo probado, hay un catálogo de agentes de voz listos:
bashnpx zavudev agents catalog
textid name voice tools category fermi Fermi, Lead Qualification yes 5 sales curie Curie, Customer Support yes 5 support kepler Kepler, Video Call Booking yes 2 frontDesk hopper Hopper, Lead Capture and Booking yes 5 sales aria Aria, Multi-channel concierge yes 3 frontDesk atlas Atlas, Product Expert yes 0 support
npx zavudev agents pull curie --sender "$SENDER_ID" baja uno a tu repo como TypeScript editable. Es un punto de partida que es tuyo, no una caja negra que alquilas.
Lee lo que escribió
tsimport { defineAgent, defineTool } from "@zavudev/functions" defineAgent({ senderId: process.env.SENDER_ID!, name: "Clínica Norte", provider: "zavu", model: "openai/gpt-4o-mini", channels: ["voice", "whatsapp"], voice: { enabled: true, model: "openai/gpt-4o", greeting: "Clínica Norte, buenos días. ¿En qué le puedo ayudar?", language: "es", interruptible: true, maxCallDurationMinutes: 12, maxIdleSeconds: 30, transferPhoneNumber: "+14155551234", voicemailAction: "hangup", }, prompt: "Eres la recepción de Clínica Norte. Responde en español, una o dos frases. Transfiere a una persona cuando lo pidan, o cuando estén molestos.", })
Cinco campos deciden si la llamada se siente humana.
interruptible es barge-in. Quien llama puede cortar al agente a mitad de frase y este se calla a escuchar. Déjalo prendido. Un agente que habla por encima de la gente es exactamente lo que todos ya odian de las llamadas automáticas.
maxIdleSeconds corta la llamada después de silencio, para que un teléfono boca abajo en una mochila no te facture doce minutos.
maxCallDurationMinutes es el freno duro. Existe porque una conversación que salió mal suele salir mal por mucho rato.
voicemailAction define qué pasa cuando en una llamada saliente contesta una contestadora: hangup en silencio, o leave_message y hablar. Dejar un mensaje cuando querías hablar con una persona es cómo un agente útil se vuelve una molestia.
transferPhoneNumber es la salida de emergencia, y merece su propia sección.
La transferencia es por lo que te juzgan
Si defines transferPhoneNumber, el agente recibe una tool de transferencia. Además siempre recibe una de colgar, así que puede terminar por su cuenta una llamada que ya terminó.
Dos cosas para saber, y la segunda sorprende.
La transferencia solo existe si el sender también tiene un número desde el cual llamar. Si defines el destino y te olvidas del número del sender, el agente se queda sin con qué escalar y sin forma de decirlo.
Una transferencia reenvía la llamada. No le explica nada al humano. Tu colega atiende un teléfono que suena sin idea de que quien llama ya pasó noventa segundos explicándose. Si quieres que lo sepa, eso lo construyes tú: la transcripción completa está en el registro de la llamada y en el webhook call.completed, así que puedes mandar un resumen a Slack, WhatsApp o tu helpdesk en el momento de la transferencia. Dilo también en el prompt, para que el agente avise qué va a pasar en vez de quedarse mudo.
Dale algo que hacer
Un menú rutea. Esta es la parte que hace que un agente valga más que un menú:
tsdefineTool({ name: "find_appointment", description: "Busca la próxima cita de un paciente por número de teléfono.", parameters: { type: "object", properties: { phone: { type: "string", description: "Número en formato E.164" } }, required: ["phone"], }, handler: async ({ phone }) => { const res = await fetch("https://api.clinica.example/citas?phone=" + phone) if (!res.ok) return { found: false, reason: "lookup_failed" } return { found: true, ...(await res.json()) } }, })
Vale decirlo explícito: las tools sí corren en voz. En el path de texto plano no, que es lo que confunde a la gente en la dirección contraria. Un agente de voz puede consultar cosas de verdad a mitad de frase, y esa es toda la razón para apuntar uno a tu número de teléfono.
Prueba el handler solo, antes de cualquier llamada:
bashnpx zavudev fn invoke --tool find_appointment --args '{"phone":"+14155551234"}'
Eso corre en tu máquina, contra código sin commitear, en menos de un segundo.
Escúchalo antes que un cliente
Dos chequeos distintos, que atrapan fallas distintas.
bashnpx zavudev agents test --sender "$SENDER_ID" --message "quiero cambiar mi cita del martes"
Esto ejercita el cerebro: prompt, modelo, knowledge base. No se entrega nada, no se cobra nada, no queda registro. También imprime warnings sobre lo que un dry run no puede demostrar, y en un agente de voz el más útil es que ese path no llama tools aunque la llamada sí las va a llamar.
Después llámate a ti mismo:
bashnpx zavudev calls create --to "+56TUPROPIONUMERO"
Tu teléfono suena y el agente te habla. Ninguna otra cosa te va a decir que el saludo dura tres segundos de más, o que la voz lee un número de teléfono como un entero gigante. Presta atención a la interrupción: háblale encima a propósito y fíjate si se calla.
Después lee la llamada:
bashnpx zavudev calls list --limit 5 npx zavudev calls get <callId>
list muestra dirección, estado, duración y por qué terminó cada llamada. get devuelve la transcripción turno por turno. Esa transcripción es lo que tu central telefónica nunca te dio, y de ahí sale la próxima versión del prompt.
Después entrégale el loop:
textLee la transcripción de mis últimas tres llamadas. Todos preguntaron por estacionamiento y el agente no sabía. Agrégalo a la knowledge base, acorta el saludo, y vuelve a deployar.
También llama hacia afuera
El mismo agente coloca llamadas:
bashnpx zavudev calls create --to "+56912345678" --language es-ES --max-minutes 5
Recordatorios de cita, ventanas de entrega, un pago que falló. --language acepta auto para seguir el idioma de quien contesta. voicemailAction define qué pasa cuando atiende una máquina, que en campañas salientes es la mayoría de las veces.
Salientes es también donde un agente deja de ser una comodidad y pasa a ser algo que puede molestar a miles de personas rápido. Recordatorios que la gente pidió, sí. Llamadas en frío, no. El consentimiento y los horarios permitidos son tu responsabilidad, y ni el modelo ni la API los van a respetar por ti.
Qué queda de cada llamada
Se disparan cuatro eventos de webhook: call.initiated, call.answered, call.completed, call.failed. Todos llevan el id de la llamada, dirección, from, to, estado, duración y por qué terminó.
Los dos terminales además llevan cost, en USD, después de que la llamada se cobró. Ese no es el número crudo del carrier: es lo que se te facturó de verdad, telefonía y pipeline de voz juntos, así que puedes poner un precio real al lado de una conversación real.
Cómo se cobra esto en Zavu
La voz es por minuto conectado. El pipeline administrado son $0.0625 por minuto más telefonía, contados desde que atienden hasta que cortan. Los modelos estándar están cubiertos por esa tarifa; un modelo premium suma su propio costo por tokens. Al colocar la llamada se reserva un estimado corto y se liquida contra la duración real al terminar.
El plan cubre todo lo de alrededor. Free es $0, Hobby son $25 al mes, y de ahí escala. Cada plan incluye una cuota de números, tres en Hobby, y el primer número de Estados Unidos es gratis para cada team. Los números extra cuestan alrededor de un dólar al mes cada uno.
Construir es gratis. Skills, CLI, SDKs, deploy y cada redeploy. agents test no cobra. fn invoke corre local. Lo que pagas es una llamada conectada.
El número para quedarse: una llamada de cinco minutos en el pipeline administrado son unos treinta centavos más telefonía. Compáralo con lo que cuesta una persona esos mismos cinco minutos, y después con lo que cuesta una llamada abandonada, que es el orden que importa.
Las tarifas se mueven. La página de precios es la que está al día.
Tres reglas que mantienen esto honesto
Nunca finjas un éxito. Una tool que reporta una cita reagendada que nunca reagendó hace que el agente le diga a una persona real, en voz alta y con tono seguro, que su martes está resuelto. Devuelve la falla. El agente la va a decir, y quien llamó puede hacer algo al respecto. Esto es peor en voz que en cualquier otro lado, porque nadie relee una llamada telefónica.
Avisa que vas a transferir. Silencio seguido de tono suena a que te cortaron. Una frase, después la transferencia.
Un dry run no es una llamada. agents test demuestra que el prompt es sensato. No demuestra que el saludo suene bien, que la voz pronuncie el nombre de tu empresa, ni que el agente se calle cuando lo interrumpen. Eso solo lo demuestra una llamada real, así que haz la primera a tu propio teléfono.
Dónde te deja esto
Tu número de teléfono ahora contesta, entiende, consulta algo real y pone a una persona cuando corresponde. El comportamiento vive en un archivo al que le puedes hacer diff, con una transcripción por llamada y un costo al lado.
El menú nunca fue el producto. Era el compromiso que aceptaste porque el software no podía sostener una conversación. Ahora puede.