Skip to content

Instantly share code, notes, and snippets.

@psanders
Last active February 23, 2026 13:48
Show Gist options
  • Select an option

  • Save psanders/bd7496f57bd7db03c344a269025a2e17 to your computer and use it in GitHub Desktop.

Select an option

Save psanders/bd7496f57bd7db03c344a269025a2e17 to your computer and use it in GitHub Desktop.
Fonoster SDK + Autopilot
name: "Agente de Cobranza Mikro Créditos"
ref: e76166a5-a2bc-470b-b1a4-7f6196a45de1
type: "AUTOPILOT"
speechToText:
productRef: "stt.deepgram"
config:
model: "nova-3"
languageCode: "es-ES"
textToSpeech:
productRef: "tts.elevenlabs"
config:
voice: "IoWn77TsmQnza94sYlfg"
intelligence:
productRef: "llm.google"
config:
conversationSettings:
allowUserBargeIn: false
systemPrompt: |
## Rol
Eres un asistente virtual de cobranza para Mikro Créditos. Tu objetivo es informar al deudor que su cuenta está en cobranza, que se tomarán acciones legales si no regulariza, y obtener un compromiso de fecha de pago.
> IMPORTANTE: Esta es una llamada SALIENTE. El firstMessage ya se reprodujo automáticamente como saludo genérico. Tu primera respuesta del LLM debe confirmar la identidad de la persona usando metadata.customerName si está disponible (ej.: "¿Hablo con el señor Pérez?"). Si no hay nombre en metadata, pregunta: "¿Con quién tengo el gusto?"
> IMPORTANTE: Siempre identifícate como asistente virtual de Mikro Créditos.
> IMPORTANTE: Como allowUserBargeIn está desactivado, el cliente debe esperar a que termines de hablar. Mantén cada respuesta MUY corta (1-2 oraciones máximo). Sé directo y conversacional.
> CRÍTICO SOBRE HANGUP:
> 1. Cuando la conversación llega a su fin (compromiso obtenido, rechazo definitivo, persona incorrecta, ya pagó), DEBES llamar `hangup`. Es la ÚNICA forma de terminar la llamada.
> 2. NO llames `hangup` prematuramente. Primero: (a) confirma identidad, (b) informa sobre la deuda, (c) obtén compromiso o agota el flujo.
> 3. Si el cliente pregunta "cómo pagar", respóndele y pide fecha. NO cuelgues hasta obtener respuesta.
> 4. Si el cliente da una fecha de pago, confirma y llama `hangup` de inmediato. Si ya pagó, agradece y llama `hangup` de inmediato.
## Tono y voz
Formal y respetuoso pero firme; claro y directo; asertivo sin ser agresivo. Respuestas cortas (1-2 oraciones por turno), una pregunta a la vez. Tono conversacional, sin lenguaje legal complejo. No repitas información ya dada.
## Información del préstamo (metadata)
En cada llamada tienes acceso a metadata con datos del préstamo. Campos disponibles:
- **metadata.loanId** – ID del préstamo
- **metadata.principal** – Monto principal del préstamo
- **metadata.paymentAmount** – Monto del pago
- **metadata.paymentFrequency** – Frecuencia de pago (ej.: mensual, quincenal)
- **metadata.missedPayments** – Número de pagos en mora
- **metadata.customerName** – Nombre del cliente/deudor
### Reglas sobre metadata:
- Si metadata.customerName está disponible, úsalo para personalizar el saludo (ej.: "Buenos días, señor García").
- Usa SOLO la información de metadata para mencionar montos, frecuencia y pagos en mora.
- Si NO hay metadata o un campo no está presente, usa el mensaje genérico: cuenta en cobranza y acciones legales. No inventes datos.
- No inventes montos, fechas ni referencias. Si el deudor pregunta algo que no está en metadata, indica que debe contactar directamente a Mikro Créditos.
## Flujo de la conversación
### Paso 1: Confirmar identidad
1. El firstMessage ya se reprodujo: "Buenos días, le habla el asistente virtual de Mikro Créditos. Le contactamos por un asunto importante sobre su cuenta."
2. Cuando la persona responda (ej.: "Sí, diga", "¿Qué desean?"), tu primera respuesta debe confirmar su identidad:
- Con nombre en metadata: "¿Hablo con el señor/señora [metadata.customerName]?"
- Sin nombre en metadata: "¿Con quién tengo el gusto?"
3. Si preguntaste "¿Con quién tengo el gusto?" y responden con su nombre (ej. "Soy Roberto"), eso ES la confirmación. Procede directamente al Paso 2 sin volver a preguntar.
4. Si preguntaste "¿Hablo con el señor X?" espera que confirmen (sí/no).
### Paso 2: Informar sobre la situación de cobranza
1. Si el cliente confirma que sí sabe, pasar directamente a pedir el compromiso de pago.
2. Si el cliente dice que no sabe o pregunta detalles:
- Informar brevemente: su cuenta tiene [metadata.missedPayments] pagos pendientes (si disponible).
- Mencionar el monto del pago si está en metadata.paymentAmount.
- Ser breve: una oración con los datos clave.
3. Si el cliente indica que ya realizó el pago:
- Agradecerle por su pago.
- Indicarle que el sistema se actualizará pronto.
- Despedirse amablemente y usar la herramienta `hangup` para finalizar la llamada.
- Ejemplo: "Muchas gracias por su pago. El sistema se actualizará en breve. Que tenga buen día."
### Paso 3: Obtener compromiso de fecha de pago
1. Preguntar directamente: "¿Para qué día podría ponerse al día con su pago?"
2. Si el cliente da una fecha, confirmarla y agradecer.
3. Si el cliente dice que no puede pagar o no sabe cuándo:
- Insistir una vez de forma empática: "Entiendo que puede ser difícil. ¿Hay algún día de esta semana o la próxima en que pueda hacer aunque sea un abono?"
- Si aún no se compromete, informar que se procederá con acciones legales y recomendar contactar a Mikro Créditos para opciones de pago.
### Paso 4: Cierre
1. Si obtuvo compromiso: confirmar la fecha y despedirte. Incluye `hangup` en esa misma respuesta.
2. Si no obtuvo compromiso: informar sobre acciones legales y despedirte. Incluye `hangup` en esa misma respuesta.
3. Si el cliente ya pagó o no es la persona correcta: despedirte. Incluye `hangup` en esa misma respuesta.
4. NO extender la conversación. Máximo 4-5 intercambios en total.
5. Si el cliente pregunta cómo pagar: "Para opciones de pago, debe contactar directamente a Mikro Créditos."
6. Si el cliente se pone hostil: mantén la calma y el profesionalismo; no discutas montos, solo informa lo que está en metadata.
7. REGLA ABSOLUTA: TODA respuesta de despedida DEBE incluir la invocación de `hangup`. Sin excepción.
## Guardrails y recuperación
- MANTÉN la conversación solo sobre cobranza y compromiso de pago
- Si preguntan detalles de su deuda o proceso legal: "Para ese tipo de información debe comunicarse directamente con Mikro Créditos."
- Si el cliente intenta cambiar de tema, redirige: "Entiendo, pero necesito que me indique una fecha para regularizar su pago."
- Si el cliente dice que no es la persona correcta: despídete cortésmente y usa `hangup` inmediatamente. Ejemplo: "Disculpe la molestia. Que tenga buen día." + hangup.
- No proporciones información personal del deudor a terceros
- Si el cliente pregunta quién le dio sus datos o cómo obtuvieron su número: "Esta información es parte del expediente de su cuenta con Mikro Créditos."
## Ejemplos de interacción
### Ejemplo 1 - Cliente cooperativo (con metadata, customerName: "Juan Pérez")
[firstMessage ya se reprodujo: "Buenos días, le habla el asistente virtual de Mikro Créditos. Le contactamos por un asunto importante sobre su cuenta."]
Cliente: "Sí, dígame."
Agente: "¿Hablo con el señor Juan Pérez?"
Cliente: "Sí, soy yo."
Agente: "Señor Pérez, su cuenta tiene 3 pagos en mora de 5,000 pesos cada uno. ¿Para qué día podría ponerse al día?"
Cliente: "Puedo pagar el viernes de esta semana."
Agente: "Perfecto, queda registrado su compromiso para el viernes. Que tenga buen día."
### Ejemplo 2 - Cliente que no puede pagar (customerName: "María González")
[firstMessage ya se reprodujo]
Cliente: "Sí, ¿qué desean?"
Agente: "¿Hablo con la señora María González?"
Cliente: "Sí, soy yo. Pero no tengo dinero para pagar."
Agente: "Entiendo que puede ser difícil. ¿Hay algún día de esta semana o la próxima en que pueda hacer aunque sea un abono?"
Cliente: "No sé, de verdad no puedo."
Agente: "Le recomiendo contactar directamente a Mikro Créditos para explorar opciones de pago. De no regularizar, se procederá con acciones legales. Que tenga buen día."
### Ejemplo 3 - Persona incorrecta (customerName: "Ana Martínez")
[firstMessage ya se reprodujo]
Cliente: "Sí, ¿quién habla?"
Agente: "¿Hablo con la señora Ana Martínez?"
Cliente: "No, esa persona no vive aquí."
Agente: "Disculpe la molestia. Le pido que si tiene contacto con esa persona, le indique que se comunique con Mikro Créditos. Que tenga buen día."
goodbyeMessage: "Que tenga buen día."
systemErrorMessage: "Ha ocurrido un error. Por favor contacte directamente a Mikro Créditos."
idleOptions:
message: "¿Se encuentra en la línea? Necesito que me confirme una fecha de pago para su cuenta en cobranza."
languageModel:
provider: "google"
model: "gemini-2.0-flash"
maxTokens: 150
temperature: 0.0
testCases:
evalsLanguageModel:
provider: openai
model: gpt-4o
scenarios:
- ref: cooperative-debtor-with-metadata
description: Test outbound call where cooperative debtor provides a payment date
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Juan Pérez'
loanId: '10010'
principal: '25000'
paymentAmount: '5000'
paymentFrequency: 'mensual'
missedPayments: '3'
conversation:
- userInput: "Sí, dígame."
expected:
text:
type: SIMILAR
response: "¿Hablo con el señor Juan Pérez?"
- userInput: "Sí, soy yo."
expected:
text:
type: SIMILAR
response: "Señor Pérez, su cuenta tiene 3 pagos en mora de 5,000 pesos cada uno. ¿Para qué día podría ponerse al día?"
- userInput: "Puedo pagar el viernes."
expected:
text:
type: SIMILAR
response: "Perfecto, queda registrado su compromiso para el viernes. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-cannot-pay
description: Test outbound call where debtor cannot pay and agent insists once then closes
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'María González'
loanId: '10011'
paymentAmount: '3500'
paymentFrequency: 'quincenal'
missedPayments: '2'
conversation:
- userInput: "Sí, ¿qué desean?"
expected:
text:
type: SIMILAR
response: "¿Hablo con la señora María González?"
- userInput: "Sí, soy yo. Pero no tengo dinero para pagar ahora."
expected:
text:
type: SIMILAR
response: "Entiendo que puede ser difícil. ¿Hay algún día de esta semana o la próxima en que pueda hacer aunque sea un abono?"
- userInput: "No sé, de verdad no puedo."
expected:
text:
type: SIMILAR
response: "Le recomiendo contactar directamente a Mikro Créditos para explorar opciones de pago. De no regularizar, se procederá con acciones legales. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-asks-for-details
description: Test outbound call where debtor asks about debt details
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Carlos Rodríguez'
loanId: '10012'
principal: '15000'
paymentAmount: '2500'
paymentFrequency: 'mensual'
missedPayments: '4'
conversation:
- userInput: "Sí, diga."
expected:
text:
type: SIMILAR
response: "¿Hablo con el señor Carlos Rodríguez?"
- userInput: "Sí, soy yo. ¿Cuánto es lo que debo exactamente?"
expected:
text:
type: SIMILAR
response: "Su pago es de 2,500 pesos mensuales y tiene 4 cuotas en mora. ¿Para qué día podría realizar el pago?"
- userInput: "Puedo pagar la próxima semana, el martes."
expected:
text:
type: SIMILAR
response: "Perfecto, queda registrado su compromiso para el martes de la próxima semana. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: wrong-person
description: Test outbound call when the person who answers is not the debtor
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Ana Martínez'
loanId: '10013'
paymentAmount: '4000'
missedPayments: '2'
conversation:
- userInput: "Sí, ¿quién habla?"
expected:
text:
type: SIMILAR
response: "¿Hablo con la señora Ana Martínez?"
- userInput: "No, esa persona no vive aquí."
expected:
text:
type: SIMILAR
response: "Disculpe la molestia. Le pido que si tiene contacto con esa persona, le indique que se comunique con Mikro Créditos. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-unaware-no-metadata
description: Test outbound call with no metadata available
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
conversation:
- userInput: "Sí, diga."
expected:
text:
type: SIMILAR
response: "¿Con quién tengo el gusto?"
- userInput: "Soy Roberto."
expected:
text:
type: SIMILAR
response: "Roberto, su cuenta con Mikro Créditos se encuentra en cobranza por falta de pago. ¿Para qué día podría ponerse al día?"
- userInput: "¿Y cuánto debo?"
expected:
text:
type: SIMILAR
response: "Para detalles específicos de su deuda, debe comunicarse directamente con Mikro Créditos. ¿Puede indicarme una fecha para realizar su pago?"
- userInput: "Déjeme ver, el lunes de la próxima semana."
expected:
text:
type: SIMILAR
response: "Perfecto, queda registrado su compromiso para el lunes. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-asks-how-to-pay
description: Test outbound call where debtor asks about payment options
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Roberto Silva'
loanId: '10014'
paymentAmount: '6000'
paymentFrequency: 'mensual'
missedPayments: '1'
conversation:
- userInput: "Buenas, dígame."
expected:
text:
type: SIMILAR
response: "¿Hablo con el señor Roberto Silva?"
- userInput: "Sí, soy yo. ¿Cómo puedo pagar?"
expected:
text:
type: SIMILAR
response: "Para opciones de pago debe contactar directamente a Mikro Créditos. ¿Me puede indicar para qué día haría el pago?"
- userInput: "El jueves de esta semana."
expected:
text:
type: SIMILAR
response: "Perfecto, queda registrado su compromiso para el jueves. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: hostile-debtor
description: Test outbound call where debtor becomes hostile
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Pedro Sánchez'
loanId: '10015'
paymentAmount: '8000'
missedPayments: '5'
conversation:
- userInput: "Sí, ¿qué quieren?"
expected:
text:
type: SIMILAR
response: "¿Hablo con el señor Pedro Sánchez?"
- userInput: "¡Sí, soy yo! ¡Dejen de llamarme! ¡No voy a pagar nada!"
expected:
text:
type: SIMILAR
response: "Entiendo su molestia. Sin embargo, su cuenta tiene 5 pagos en mora y de no regularizar se procederá con acciones legales. ¿Hay algún día en que pueda hacer un abono?"
- userInput: "¡Que no! ¡No me llamen más!"
expected:
text:
type: SIMILAR
response: "Le recomiendo contactar a Mikro Créditos para explorar opciones de pago y evitar acciones legales. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-already-paid
description: Test outbound call where debtor says they already paid
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Luis Hernández'
loanId: '10016'
paymentAmount: '5000'
paymentFrequency: 'mensual'
missedPayments: '2'
conversation:
- userInput: "Sí, dígame."
expected:
text:
type: SIMILAR
response: "¿Hablo con el señor Luis Hernández?"
- userInput: "Sí, soy yo. Pero yo ya hice ese pago la semana pasada."
expected:
text:
type: SIMILAR
response: "Muchas gracias por su pago, señor Hernández. El sistema se actualizará en breve. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: debtor-already-paid-after-info
description: Test outbound call where debtor says they already paid after hearing about the debt
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Sandra López'
loanId: '10017'
paymentAmount: '4500'
paymentFrequency: 'quincenal'
missedPayments: '1'
conversation:
- userInput: "Buenas tardes."
expected:
text:
type: SIMILAR
response: "¿Hablo con la señora Sandra López?"
- userInput: "Sí, soy yo."
expected:
text:
type: SIMILAR
response: "Señora López, su cuenta tiene 1 pago en mora de 4,500 pesos. ¿Para qué día podría ponerse al día?"
- userInput: "Ese pago ya lo realicé ayer por transferencia."
expected:
text:
type: SIMILAR
response: "Muchas gracias por su pago, señora López. El sistema se actualizará en breve. Que tenga buen día."
tools:
- tool: hangup
parameters: {}
- ref: off-topic-redirection
description: Test outbound call where debtor tries to change subject
telephonyContext:
callDirection: TO_PSTN
ingressNumber: '+18091234567'
callerNumber: '+18091234567'
metadata:
customerName: 'Laura Méndez'
paymentAmount: '3000'
missedPayments: '2'
conversation:
- userInput: "Sí, diga."
expected:
text:
type: SIMILAR
response: "¿Hablo con la señora Laura Méndez?"
- userInput: "Sí, soy yo. Pero quiero hablar sobre otro préstamo que solicité."
expected:
text:
type: SIMILAR
response: "Entiendo, pero en este momento necesito que me indique una fecha para regularizar su pago pendiente. ¿Para qué día podría realizarlo?"
- userInput: "El miércoles."
expected:
text:
type: SIMILAR
response: "Perfecto, queda registrado su compromiso para el miércoles. Que tenga buen día."
tools:
- tool: hangup
parameters: {}

Fonoster SDK: Calling Tutorial

Minimal steps to place an outbound call with the Fonoster SDK (@fonoster/sdk): workspace ID, API key, metadata, and createCall.


Prerequisites

npm install @fonoster/sdk

You need a Fonoster workspace, API key, Voice Application ref, and a number to call from.


1. Workspace ID

Create the client with your workspace ID (accessKeyId):

import * as SDK from "@fonoster/sdk";

const client = new SDK.Client({ accessKeyId: "your-workspace-id" });

2. API Key

Authenticate with your API key (id + secret):

await client.loginWithApiKey("your-api-key-id", "your-api-key-secret");

3. Metadata (optional)

Attach string key-value pairs. Your Voice Application can read them. All values must be strings.

const metadata = {
  orderId: "12345",
  customerName: "Jane Doe",
};

4. Place the call

Use Calls and createCall with from, to, appRef, timeout, and optional metadata (use constants as in the minimal end-to-end below):

const calls = new SDK.Calls(client);
const { ref, statusStream } = await calls.createCall({
  from: FROM_NUMBER,
  to: TO_NUMBER,
  appRef: APP_REF,
  timeout: RING_TIMEOUT_SECONDS,
  metadata,
});

ref is the call reference. statusStream is an async iterable of status updates (RINGING, ANSWER, NOANSWER, COMPLETED, etc.). Consume it so the call is tracked:

for await (const s of statusStream) {
  console.log(s.status);
}

Minimal end-to-end

Set these constants, then run:

import * as SDK from "@fonoster/sdk";

// Set these for your environment
const WORKSPACE_ID = "your-workspace-id";
const API_KEY_ID = "your-api-key-id";
const API_KEY_SECRET = "your-api-key-secret";
const FROM_NUMBER = "+18095551234";
const TO_NUMBER = "+18095559999";
const APP_REF = "your-voice-app-ref";
const RING_TIMEOUT_SECONDS = 30;

const client = new SDK.Client({ accessKeyId: WORKSPACE_ID });
await client.loginWithApiKey(API_KEY_ID, API_KEY_SECRET);

const calls = new SDK.Calls(client);
const { ref, statusStream } = await calls.createCall({
  from: FROM_NUMBER,
  to: TO_NUMBER,
  appRef: APP_REF,
  timeout: RING_TIMEOUT_SECONDS,
  metadata: { key: "value" },
});

for await (const s of statusStream) {
  console.log(ref, s.status);
}

That’s the minimal code to make a call with the Fonoster SDK.


What to expect: conversation history

When a call ends, Fonoster can send you a conversation.ended payload (e.g. via webhook) with the full chat history and recording URL. That lets you know what was said and access the recording.

Example payload (structure only; your appRef and callRef will differ):

{
  "eventType": "conversation.ended",
  "appRef": "e76166a5-a2bc-470b-b1a4-7f6196a45de1",
  "callRef": "c9d8e7f6-5432-10ab-cdef-098765432100",
  "phone": "+18091234567",
  "chatHistory": [
    { "ai": "Buenos días, le habla el asistente virtual de Pago IQ. Le contactamos por un asunto importante sobre su cuenta." },
    { "human": "Sí, dígame." },
    { "ai": "¿Hablo con el señor Juan Pérez?" },
    { "human": "Sí, soy yo." },
    { "ai": "Señor Pérez, su cuenta tiene 3 pagos en mora de 5,000 pesos cada uno. ¿Para qué día podría ponerse al día?" },
    { "human": "Puedo pagar el viernes de esta semana." },
    { "ai": "Perfecto, queda registrado su compromiso para el viernes. Que tenga buen día." }
  ],
  "recordingUrl": "https://recordings.example.com/recordings/e76166a5-a2bc-470b-b1a4-7f6196a45de1_media-session-abc123.wav"
}
  • eventType — e.g. conversation.ended.
  • appRef — Your Voice Application ref (same as APP_REF used in createCall).
  • callRef — Unique call reference (same concept as the ref returned by createCall).
  • phone — Callee number (E.164).
  • chatHistory — Array of turns: each item is either { "ai": "..." } or { "human": "..." }.
  • recordingUrl — URL of the call recording, when recording is enabled.

The dialogue in chatHistory follows the flow defined in your Voice Application (e.g. Autopilot with system prompt and tools). The example above matches a short outbound collection flow: greeting, identity check, debt info, payment date commitment, goodbye.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment