Skip to content

Inicio rápido: de cero a la primera llamada ​

English version: Quickstart — zero to first call.

Esta guía te lleva de una máquina vacía a tu primer intento de llamada con puerta de cumplimiento, de punta a punta, contra el stack local: el mismo harness docker-compose sobre el que corre la propia suite e2e de la plataforma. Cada llamada que hace es real (SIP a través de Kamailio/FreeSWITCH, filas de CDR reales, evidencia de cumplimiento real); lo único simulado es el operador telefónico del otro extremo, así que no suena ningún teléfono de verdad y nada cuesta dinero.

Vas a: levantar el stack → obtener un token de API → crear una campaña → importar deudas → poner un asiento de agente en línea → ver cómo el dialer hace el primer intento de llamada → leer el resultado durable y el veredicto de cumplimiento.

Tiempo total: ~1 hora la primera vez (casi todo es una construcción de imágenes que se hace una sola vez); ~15 minutos en las siguientes.

Listo para copiar y pegar

Cada paso es un curl ejecutable. Los ejemplos usan jq para capturar ids en variables de shell; si no lo tienes, copia los ids a mano de las respuestas JSON.

Requisitos previos (~10 min) ​

  • Linux (o macOS) con Docker + docker compose v2, make, git, curl.
  • Una cuenta de GitHub con acceso a los repositorios de DialerDigital y un token con read:packages (el harness descarga la imagen publicada de FreeSWITCH desde GHCR).
  • Puertos libres en el host: 4000 (API de core), 5060/5061/5070/5080/8021/8443/22222 (plano telecom), 55432/56379/55060/58080/58090 (mocks). Un softphone que ya corra en el host se queda con 5060: muévelo o ciérralo.

El fichero compose construye repos hermanos desde el código fuente, así que clónalos lado a lado:

bash
mkdir dd && cd dd
gh repo clone DialerDigital/integration-tests
gh repo clone DialerDigital/core
gh repo clone DialerDigital/kamailio
gh repo clone DialerDigital/rtpengine
gh repo clone DialerDigital/dashboard

docker login ghcr.io        # PAT with read:packages, for the FreeSWITCH image
cd integration-tests

Paso 1: levanta el stack (~10–30 min la primera vez, ~2 min después) ​

bash
make up-telecom

Esto arranca los mocks (Postgres, Dragonfly, un operador telefónico simulado con SIPp, STT/TTS), construye y levanta el plano telecom (Kamailio, rtpengine, FreeSWITCH, core), ejecuta las migraciones de base de datos de core y espera hasta que la API responda. La primera ejecución compila la imagen de rtpengine desde el código fuente: cuenta con 10–30 minutos. Las siguientes usan la caché de capas de Docker.

Verifica:

bash
make smoke
curl -s http://127.0.0.1:4000/healthz
# -> {"status":"ok","service":"dialer-core","version":"..."}

El dashboard del propietario también arranca, en http://127.0.0.1:8080: útil para ver el piso en vivo en un navegador mientras ejecutas los pasos siguientes.

Paso 2: obtén tu token de API (~1 min) ​

En local emites la primera clave del tenant con la semilla idempotente (en producción la primera clave se emite en el alta: ver Autenticación). La semilla crea el tenant de demostración local-dev, su clave de API, un asiento de agente en la extensión SIP 1000 y una pequeña campaña de demostración:

bash
docker compose --profile mocks --profile telecom run --rm --no-deps \
  -e DATABASE_USER=dialer -e DATABASE_PASSWORD=testpass \
  -e SEED_API_TOKEN=localdev-m2-demo-token \
  core-migrate eval 'Dialer.Release.seed()'

Fijar SEED_API_TOKEN hace el token reproducible entre ejecuciones. Exporta tus variables de sesión:

bash
API=http://127.0.0.1:4000
TOKEN=localdev-m2-demo-token

Comprueba la clave: GET /v1/me devuelve el tenant autenticado:

bash
curl -s $API/v1/me -H "Authorization: Bearer $TOKEN"
# -> {"tenant":{"id":"...","name":"local-dev","status":"active",...}}

Un 401 unauthorized aquí significa que la semilla no corrió o que el token no coincide: vuelve a ejecutar el comando de la semilla de arriba.

Paso 3: crea una campaña (~1 min) ​

Las campañas se crean en draft y se inician de forma explícita. Dos ajustes propios del laboratorio, ambos honestos sobre lo que hacen:

  • calling_window_* totalmente abierta: es política de la campaña, no la ley: la puerta de horas de silencio del motor de cumplimiento ([08:00, 21:00) en la zona horaria del consumidor) sigue aplicándose de forma independiente en cada marcación.
  • abandonment_threshold: 0.9: si corres sin cabeza (sin softphone que conteste, siguiente paso), los puentes al agente fallan y cuentan como abandoned; el valor por defecto real de 3 % congelaría el pacing tras el primero. Mantén el valor por defecto en cualquier despliegue real.
bash
CID=$(curl -s -X POST $API/v1/campaigns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "quickstart-'$(date +%s)'",
    "calling_window_start": "00:00:00",
    "calling_window_end": "23:59:59",
    "default_timezone": "America/Chicago",
    "abandonment_threshold": 0.9,
    "caller_ids": ["+13125550199"]
  }' | jq -r .campaign.id)
echo $CID

campaign_type vale predictive por defecto: el runner hace el pacing y marca por sí solo una vez iniciada. Consulta el esquema de campaña para ver todos los campos.

Paso 4: importa deudas (~2 min) ​

Los leads entran por la importación JSON masiva: idempotente (las deudas se upsertan por external_ref), con tolerancia a fallos por elemento. Importamos dos a propósito:

  1. un contacto de línea fija que el motor puede marcar, y
  2. un contacto móvil sin consentimiento TCPA registrado, que el motor debe rechazar: ese rechazo es tu primer veredicto de cumplimiento.

Una cosa que hay que acertar: la puerta de horas de silencio lee la zona horaria del contacto. Elige una donde ahora mismo sea entre las 08:00 y las 21:00 hora local:

bash
TZ=America/Chicago date +%H:%M   # inside 08:00-21:00? use it. Otherwise try
TZ=Europe/Madrid date +%H:%M     # ...or Asia/Tokyo, Pacific/Auckland, etc.
LEAD_TZ=America/Chicago          # <- your pick
bash
curl -s -X POST $API/v1/debts/import \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "debts": [
      {
        "external_ref": "quickstart-0001",
        "consumer_ref": "quickstart-consumer-1",
        "debt_type": "other",
        "amount_cents": 50000,
        "currency": "USD",
        "campaign_id": "'$CID'",
        "contacts": [
          { "phone_e164": "+13125551001", "line_type": "landline",
            "timezone": "'$LEAD_TZ'", "us_state": "IL", "is_primary": true }
        ]
      },
      {
        "external_ref": "quickstart-0002",
        "consumer_ref": "quickstart-consumer-2",
        "debt_type": "other",
        "amount_cents": 75000,
        "currency": "USD",
        "campaign_id": "'$CID'",
        "contacts": [
          { "phone_e164": "+13125551002", "line_type": "mobile",
            "timezone": "'$LEAD_TZ'", "us_state": "IL", "is_primary": true }
        ]
      }
    ]
  }'
# -> {"imported":2,"failed":[]}

external_ref es la clave del contador de frecuencia de la Reg F: mantenla estable y única por cuenta. Detalles en la guía de la API REST.

Comprobación opcional: previsualiza el siguiente lead marcable sin marcar:

bash
curl -s $API/v1/campaigns/$CID/next-lead -H "Authorization: Bearer $TOKEN"
# -> {"lead":{"debt_ref":"quickstart-0001","contact_phone":"+13125551001",...}}

Paso 5: pon un asiento de agente en línea (~1 min) ​

La semilla ya creó un asiento en la extensión SIP 1000. Regístralo para que las llamadas conectadas tengan dónde puentear:

bash
curl -s -X POST $API/v1/agents/checkin \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sip_extension": "1000", "device_mode": "external"}'
# -> {"agent":{...,"presence":"available"}, "sip":{...}}

Dos formas de ejecutar el siguiente paso:

  • Sin cabeza (sin audio, totalmente por script). No registres ningún dispositivo. La pata del operador telefónico igualmente se completa; el puente al asiento falla y el intento se registra como abandoned. Aun así obtienes una fila durable de CDR con su instantánea de cumplimiento: sirve para una primera ejecución.
  • Escúchalo. Registra un softphone SIP como 1000 / contraseña localdev123 contra el perfil interno de FreeSWITCH en <your-host-LAN-IP>:5070, y contesta cuando suene: el intento se registra como answered_human. Los detalles de configuración están en el README-LOCAL.md del harness («Zoiper setup»).

Paso 6: inicia la campaña y mira la primera llamada (~3 min) ​

bash
curl -s -X POST $API/v1/campaigns/$CID/start \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"gateway": "default", "batch_size": 2, "tick_ms": 1000}'
# -> {"campaign":{...,"state":"running"}}

gateway: "default" es la troncal del operador telefónico simulado del stack local: el runner envía un originate ESL real a FreeSWITCH, que marca al operador telefónico simulado SIPp: este contesta, reproduce ~3 s de medios y cuelga. Sondea los contadores en vivo (el mismo payload se emite cada ~2 s por el piso en vivo WebSocket):

bash
curl -s $API/v1/campaigns/$CID/stats -H "Authorization: Bearer $TOKEN"

En unos ~20 segundos deberías ver dialed ≥ 1 (el lead de línea fija) y blocked ≥ 1 (el lead móvil que rechazó la puerta de consentimiento). Si dialed se queda en 0 y blocked sube, la zona horaria de tu lead está fuera de las horas de silencio: vuelve al Paso 4.

Paso 7: lee el resultado y el veredicto de cumplimiento (~3 min) ​

La fila del CDR: solo-anexar, una fila por intento, con la decisión de cumplimiento congelada en el momento de marcar:

bash
curl -s "$API/v1/call_attempts?campaign_id=$CID&limit=10" \
  -H "Authorization: Bearer $TOKEN"

Mira el primer intento: disposition (answered_human si contestó un softphone, abandoned en la ruta sin cabeza), started_at/answered_at/ended_at, hangup_cause y, lo que importa en una auditoría, compliance_snapshot: la evidencia congelada de la decisión que permitió esta marcación, inmune a cambios posteriores en los datos.

La evidencia del rechazo: el lead móvil nunca se marcó, y ese rechazo también es una fila durable, escrita antes de saltar el lead:

bash
curl -s "$API/v1/stats/blocked?campaign_id=$CID" -H "Authorization: Bearer $TOKEN"
# -> {"total":1,"by_gate":{"tcpa_no_consent":1},"daily":[...]}

Ese par (la llamada que hiciste con su instantánea congelada, y la llamada que no hiciste con su motivo) es el modelo de evidencia de la plataforma en miniatura. Consulta Errores y cumplimiento y los endpoints de evidencia de cumplimiento (informe de violaciones evitadas, paquete de defensa por deuda).

Paso 8: apaga todo (~1 min) ​

bash
curl -s -X POST $API/v1/campaigns/$CID/stop -H "Authorization: Bearer $TOKEN"
curl -s -X POST $API/v1/agents/checkout \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"sip_extension": "1000"}'
make down        # stop the whole stack (state persists in the compose volumes)

Solución de problemas ​

SíntomaCausa probable / arreglo
401 unauthorized en todas las llamadasLa semilla no corrió, o TOKEN no coincide con SEED_API_TOKEN: repite el Paso 2.
stats es null en las estadísticas de la campañaEl runner no está levantado: ¿devolvió 200 el POST .../start?
dialed: 0, blocked subiendoPuerta de horas de silencio: la zona horaria del lead está fuera de 08:00–21:00 locales. Reimporta con una zona horaria de día (los leads bloqueados reintentan tras un enfriamiento, 5 min por defecto).
Ninguna fila de CDRPuede que core aún no tenga enlace ESL con FreeSWITCH: make logs, busca «ESL ready».
make up-telecom falla por puertosAlgo ocupa un puerto telecom: sudo ss -lunp | grep -E ':5060|:5070|:5080|:8021'.
Falla la descarga de la imagen de FreeSWITCHdocker login ghcr.io con un PAT que tenga read:packages.

Adónde ir después ​

  • Autenticación: rotación de claves, autenticación WebSocket, modelo de tenancy.
  • Guía de la API REST: dispositions tipadas (registra una promesa de pago en tu primera llamada), callbacks, supervisión, DIDs.
  • Eventos WebSocket: sustituye el sondeo del Paso 6 por push.
  • El make demo del harness: una demostración de punta a punta, por script, del ciclo vendible completo sobre este mismo stack.

Endpoints de producción ​

Todo lo anterior se corresponde 1:1 con producción, con tres diferencias:

https://api.usa.dialerdigital.com    # production (USA cell)
  • Claves: las claves de API de producción (dd_...) se emiten en el alta y las rotas tú mismo con /v1/api_keys: no hay semilla.
  • Operador telefónico: sin simulación: las llamadas salen por la configuración de operador telefónico aprovisionada en tu cuenta, a números reales.
  • Lo que está en juego: corren las mismas puertas de cumplimiento, pero contra consumidores reales. Los atajos de laboratorio de esta guía (ventana de llamadas totalmente abierta, umbral de abandono 0.9) no tienen cabida ahí.

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.