Saltearse al contenido

Taller 10 - Parte 2: Contexto de Entrada, Colas, Grabación y Pruebas

Implementación completa del sistema de colas con IVR, grabación automática, gestión de agentes y monitoreo en tiempo real

📞 Taller 10 - Parte 2: Contexto de Entrada, Colas, Grabación y Pruebas

Sección titulada «📞 Taller 10 - Parte 2: Contexto de Entrada, Colas, Grabación y Pruebas»

En esta segunda parte completaremos el sistema con:

Contexto de ruta entrante que simula llamadas externas
Handler de colas con grabación automática MixMonitor
Sistema de login/logout para agentes (*71-*76)
Códigos de consulta de estado de colas (*77)
Organización automática de grabaciones por fecha
Integración completa con CDR para reportes
Pruebas exhaustivas del flujo completo


📞 10.8 Dialplan: Contexto de Ruta Entrante

Sección titulada «📞 10.8 Dialplan: Contexto de Ruta Entrante»

Este contexto simula una llamada que llega desde el exterior (como un troncal SIP).

Agregar en extensions_entradas.conf:

; ============================================================================
; CONTEXTO: Ruta Entrante (Simula llamada externa)
; ============================================================================
; Este contexto simula una llamada que llega desde un troncal SIP
; En producción, aquí llegarían las llamadas del proveedor VoIP
[from-pstn]
exten => _X.,1,NoOp(=== Llamada Entrante: ${CALLERID(num)} ===)
same => n,Set(CALLERID(name)=Cliente)
; Validar horario de atención
same => n,Gosub(func-apply-open-hours,s,1)
same => n,NoOp(Estado Horario: ${HORARIO_STATUS})
; Si está cerrado, reproducir mensaje y colgar
same => n,GotoIf($["${HORARIO_STATUS}" = "CLOSED"]?fuera_horario)
; Si está abierto, ir al IVR
same => n,Goto(ivr-principal,s,1)
; === FUERA DE HORARIO ===
same => n(fuera_horario),NoOp(=== Fuera de Horario ===)
same => n,Answer()
same => n,Wait(1)
same => n,Playback(custom/fuera-horario) ; "Nuestro horario es..."
same => n,Playback(vm-goodbye)
same => n,Hangup()

🎬 10.9 Dialplan: Handler de Colas con Handlers Profesionales

Sección titulada «🎬 10.9 Dialplan: Handler de Colas con Handlers Profesionales»

Este contexto reemplaza el Queue() básico de la Parte 1 con una implementación profesional usando handlers b y B de Asterisk 22.

10.9.1 Implementación con Handlers Profesionales

Sección titulada «10.9.1 Implementación con Handlers Profesionales»

Agregar en extensions.conf o extensions_entradas.conf:

; ============================================================================
; CONTEXTO: Handler Pre-Bridge para Grabación Automática
; ============================================================================
; Se ejecuta automáticamente cuando un agente contesta
; Aprovecha los handlers 'b' de Queue() en Asterisk 22
[queue-pre-bridge]
exten => s,1,NoOp(=== Pre-Bridge: Iniciando Grabación ===)
same => n,Set(QUEUE_AGENT=${MEMBERINTERFACE})
same => n,Set(QUEUE_CALLER=${CALLERID(num)})
same => n,Set(TIMESTAMP=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)})
; Construir ruta YYYY/MM/DD dentro de la carpeta de la cola
same => n,Set(RECORDING_ROOT=/var/spool/asterisk/monitor)
same => n,Set(RECORDING_SUBDIR=${STRFTIME(${EPOCH},,%Y/%m/%d)})
same => n,Set(RECORDING_PATH=${RECORDING_ROOT}/${QUEUE_DEPT}/${RECORDING_SUBDIR})
same => n,System(/usr/bin/env mkdir -p ${RECORDING_PATH})
; Generar nombre de archivo con datos del CDR
same => n,Set(RECORDING_FILE=queue-${QUEUE_DEPT}-${QUEUE_CALLER}-${TIMESTAMP})
same => n,Set(RECORDING_FULLPATH=${RECORDING_PATH}/${RECORDING_FILE}.wav)
; Registrar handler de hangup para detener MixMonitor al finalizar
same => n,Set(CHANNEL(hangup_handler_push)=queue-hangup,s,1(${RECORDING_FULLPATH}))
; Iniciar grabación con MixMonitor (solo cuando agente contesta)
same => n,MixMonitor(${RECORDING_FULLPATH},b)
; Guardar en CDR para integración con PostgreSQL (Taller 8)
same => n,Set(CDR(userfield)=${RECORDING_FULLPATH})
same => n,Set(CDR(accountcode)=QUEUE-${QUEUE_NAME})
; Log para debugging
same => n,NoOp(Grabación iniciada: ${RECORDING_FULLPATH})
same => n,Return()
; ============================================================================
; CONTEXTO: Handler de Hangup para colas
; ==========================================================================
; Se ejecuta en el canal del agente cuando termina la llamada
[queue-hangup]
exten => s,1,NoOp(=== Hangup: Finalizando Grabación ===)
same => n,StopMixMonitor()
same => n,NoOp(Grabación finalizada - Status: ${DIALSTATUS} - Archivo: ${ARG1})
same => n,Return()
; ============================================================================
; CONTEXTO: Handler de Colas con Gosub
; ============================================================================
; Usa handlers profesionales b/B de Queue()
[cola-handler]
; === COLA DE VENTAS ===
exten => ventas,1,NoOp(=== Entrada a Cola Ventas ===)
same => n,Set(__QUEUE_NAME=cola-ventas)
same => n,Set(__QUEUE_DEPT=Ventas)
same => n,Gosub(func-setup-queue-call,s,1)
; Queue con handler b() para pre-bridge en el canal del agente
same => n,Queue(${QUEUE_NAME},tTb(queue-pre-bridge^s^1))
same => n,Gosub(func-handle-queue-exit,s,1)
same => n,Hangup()
; === COLA DE SOPORTE ===
exten => soporte,1,NoOp(=== Entrada a Cola Soporte ===)
same => n,Set(__QUEUE_NAME=cola-soporte)
same => n,Set(__QUEUE_DEPT=Soporte)
same => n,Gosub(func-setup-queue-call,s,1)
; Queue con handler b() para pre-bridge en el canal del agente
same => n,Queue(${QUEUE_NAME},tTb(queue-pre-bridge^s^1))
same => n,Gosub(func-handle-queue-exit,s,1)
same => n,Hangup()
; ============================================================================
; FUNCIONES AUXILIARES PARA COLAS
; ============================================================================
[func-setup-queue-call]
exten => s,1,NoOp(=== Configurando llamada de cola ===)
same => n,Set(__QUEUE_START_TIME=${EPOCH})
same => n,Set(__QUEUE_CALLER=${CALLERID(num)})
same => n,Return()
[func-handle-queue-exit]
exten => s,1,NoOp(=== Manejando salida de cola: ${QUEUESTATUS} ===)
same => n,GotoIf($["${QUEUESTATUS}" = "TIMEOUT"]?timeout)
same => n,GotoIf($["${QUEUESTATUS}" = "FULL"]?full)
same => n,GotoIf($["${QUEUESTATUS}" = "JOINEMPTY"]?no-agents)
same => n,GotoIf($["${QUEUESTATUS}" = "LEAVEEMPTY"]?no-agents)
same => n,Goto(end)
same => n(timeout),Playback(queue-timeout)
same => n,Goto(end)
same => n(full),Playback(queue-full)
same => n,Goto(end)
same => n(no-agents),Playback(vm-nobodyavail)
same => n,Goto(end)
same => n(end),Return()

Ahora que tenemos el handler profesional, debemos actualizar el IVR para usarlo.

Opción A: Usar Goto() al Handler (Más Simple)

Sección titulada «Opción A: Usar Goto() al Handler (Más Simple)»

Editar extensions_entradas.conf (del IVR de la Parte 1):

; === OPCIÓN 1: VENTAS ===
exten => 1,1,NoOp(=== Opción 1: Ventas ===)
same => n,Playback(custom/audio1)
same => n,Gosub(func-apply-open-hours,s,1)
same => n,GotoIf($["${HORARIO_STATUS}" = "OPEN"]?open:closed)
same => n(open),Goto(cola-handler,ventas,1) ; Usar handler profesional
same => n(closed),Playback(custom/audio3)
same => n,Hangup()
; === OPCIÓN 2: SOPORTE ===
exten => 2,1,NoOp(=== Opción 2: Soporte ===)
same => n,Playback(custom/audio2)
same => n,Gosub(func-apply-open-hours,s,1)
same => n,GotoIf($["${HORARIO_STATUS}" = "OPEN"]?open:closed)
same => n(open),Goto(cola-handler,soporte,1) ; Usar handler profesional
same => n(closed),Playback(custom/audio3)
same => n,Hangup()

Opción B: Queue() Directo con Handler (Alternativa)

Sección titulada «Opción B: Queue() Directo con Handler (Alternativa)»

Si prefieres mantener el Queue() en el IVR pero con grabación:

; === OPCIÓN 1: VENTAS ===
exten => 1,1,NoOp(=== Opción 1: Ventas ===)
same => n,Playback(custom/audio1)
same => n,Gosub(func-apply-open-hours,s,1)
same => n,GotoIf($["${HORARIO_STATUS}" = "OPEN"]?open:closed)
same => n(open),Set(__QUEUE_NAME=cola-ventas)
same => n,Set(__QUEUE_DEPT=Ventas)
same => n,Queue(${QUEUE_NAME},tTb(queue-pre-bridge^s^1)) ; Con handler b()
same => n,Playback(vm-nobodyavail)
same => n,Hangup()
same => n(closed),Playback(custom/audio3)
same => n,Hangup()

🔐 10.10 Dialplan: Login/Logout de Agentes

Sección titulada «🔐 10.10 Dialplan: Login/Logout de Agentes»

Los agentes necesitan poder loguearse y desloguearse de las colas (editar archivo extensions_anexos.conf).

Agregar en el contexto [anexos-internos]:

; ============================================================================
; CÓDIGOS DE FUNCIONES PARA AGENTES
; ============================================================================
; === LOGIN A COLA DE VENTAS (*71) ===
exten => *71,1,NoOp(=== Login a Cola de Ventas ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,AddQueueMember(cola-ventas,${AGENT_INTERFACE})
same => n,NoOp(Resultado: ${AQMSTATUS})
same => n,GotoIf($["${AQMSTATUS}" = "ADDED"]?success:error)
same => n(success),Playback(agent-loginok)
same => n,Hangup()
same => n(error),Playback(an-error-has-occurred)
same => n,Hangup()
; === LOGOUT DE COLA DE VENTAS (*72) ===
exten => *72,1,NoOp(=== Logout de Cola de Ventas ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,RemoveQueueMember(cola-ventas,${AGENT_INTERFACE})
same => n,NoOp(Resultado: ${RQMSTATUS})
same => n,GotoIf($["${RQMSTATUS}" = "REMOVED"]?success:error)
same => n(success),Playback(agent-loggedoff)
same => n,Hangup()
same => n(error),Playback(an-error-has-occurred)
same => n,Hangup()
; === LOGIN A COLA DE SOPORTE (*73) ===
exten => *73,1,NoOp(=== Login a Cola de Soporte ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,AddQueueMember(cola-soporte,${AGENT_INTERFACE})
same => n,NoOp(Resultado: ${AQMSTATUS})
same => n,GotoIf($["${AQMSTATUS}" = "ADDED"]?success:error)
same => n(success),Playback(agent-loginok)
same => n,Hangup()
same => n(error),Playback(an-error-has-occurred)
same => n,Hangup()
; === LOGOUT DE COLA DE SOPORTE (*74) ===
exten => *74,1,NoOp(=== Logout de Cola de Soporte ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,RemoveQueueMember(cola-soporte,${AGENT_INTERFACE})
same => n,NoOp(Resultado: ${RQMSTATUS})
same => n,GotoIf($["${RQMSTATUS}" = "REMOVED"]?success:error)
same => n(success),Playback(agent-loggedoff)
same => n,Hangup()
same => n(error),Playback(an-error-has-occurred)
same => n,Hangup()
; === PAUSAR EN TODAS LAS COLAS (*75) ===
exten => *75,1,NoOp(=== Pausar Agente en Todas las Colas ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,PauseQueueMember(,${AGENT_INTERFACE})
same => n,Playback(custom/agent-pause) ; Graba este audio con *6500
same => n,Hangup()
; === DESPAUSAR EN TODAS LAS COLAS (*76) ===
exten => *76,1,NoOp(=== Despausar Agente en Todas las Colas ===)
same => n,Set(AGENT_INTERFACE=PJSIP/${CHANNEL(endpoint)})
same => n,UnpauseQueueMember(,${AGENT_INTERFACE})
same => n,Playback(custom/agent-unpause) ; Graba este audio con *6500
same => n,Hangup()
; === CONSULTAR ESTADO DE COLAS (*77) ===
exten => *77,1,NoOp(=== Consultar Estado de Colas ===)
same => n,Answer()
same => n,Playback(queue-thereare)
same => n,SayNumber(${QUEUE_WAITING_COUNT(cola-ventas)})
same => n,Playback(queue-callswaiting)
same => n,Playback(in-the-queue)
same => n,Playback(digits/1) ; "Ventas"
same => n,Wait(1)
same => n,Playback(queue-thereare)
same => n,SayNumber(${QUEUE_WAITING_COUNT(cola-soporte)})
same => n,Playback(queue-callswaiting)
same => n,Playback(in-the-queue)
same => n,Playback(digits/2) ; "Soporte"
same => n,Hangup()

💡 Resumen de Códigos

CódigoFunción
*71Login a Cola de Ventas
*72Logout de Cola de Ventas
*73Login a Cola de Soporte
*74Logout de Cola de Soporte
*75Pausar en todas las colas
*76Despausar en todas las colas
*77Consultar estado de colas
77777Simular llamada entrante

⚙️ 10.11 Recargar Configuración y Verificar

Sección titulada «⚙️ 10.11 Recargar Configuración y Verificar»
Terminal window
asterisk -rx "dialplan reload"

Verificar que no haya errores:

Terminal window
asterisk -rx "dialplan show from-pstn"
asterisk -rx "dialplan show ivr-principal"
asterisk -rx "dialplan show cola-handler"
asterisk -rx "dialplan show internal" | grep -A 5 "*7"
Terminal window
asterisk -rx "queue show"

Salida esperada:

cola-ventas has 0 calls (max 25) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 60s
No Members
No Callers
cola-soporte has 0 calls (max 15) in 'fewestcalls' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 60s
No Members
No Callers
Terminal window
asterisk -rx "moh show classes"

Desde extensión 1001:

  1. Marca *71 → Login a cola-ventas
  2. Escucha “Agent login OK”

Desde extensión 1004:

  1. Marca *73 → Login a cola-soporte
  2. Escucha “Agent login OK”

Verificar en CLI:

Terminal window
asterisk -rx "queue show cola-ventas"

Salida esperada:

cola-ventas has 0 calls (max 25) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 60s
Members:
PJSIP/1001 (dynamic) (Not in use) has taken no calls yet
No Callers

Desde extensión 1002:

  1. Marca 77777 → Simula llamada entrante
  2. Escucha mensaje de bienvenida
  3. Marca 1 → Opción Ventas
  4. Entra a cola-ventas
  5. Escucha música en espera
  6. El agente 1001 recibe la llamada
  7. Contesta y conversa
  8. Cuelga

Verificar grabación:

Terminal window
# Buscar archivo de grabación
find /var/spool/asterisk/monitor -name "*.wav" -type f -mmin -5
# Ver detalles (ajusta "Ventas" por el departamento correspondiente)
ls -lh /var/spool/asterisk/monitor/Ventas/$(date +%Y/%m/%d)/

Verificar CDR:

Terminal window
asterisk -rx "cdr show active"

10.12.3.- Prueba 3: Múltiples Llamadas (Estrategia rrmemory)

Sección titulada «10.12.3.- Prueba 3: Múltiples Llamadas (Estrategia rrmemory)»

Loguear 3 agentes en cola-ventas:

  • Desde 1001: *71
  • Desde 1002: *71
  • Desde 1003: *71

Generar 3 llamadas entrantes:

  • Desde 1004: 77777 → Marca 1 (Ventas)
  • Desde 1005: 77777 → Marca 1 (Ventas)
  • Desde softphone externo: 77777 → Marca 1 (Ventas)

Observar distribución:

  • Llamada 1 → 1001
  • Llamada 2 → 1002
  • Llamada 3 → 1003
  • Llamada 4 → 1001 (vuelve al inicio - round robin)

Desde extensión 1001:

  1. Marca *75 → Pausar
  2. Escucha “Agent pause”

Generar llamada:

  • Desde 1004: 77777 → Marca 1
  • La llamada NO va a 1001 (está pausado)
  • Va a 1002 o 1003

Despausar:

  • Desde 1001: *76 → Despausar
  • Ahora sí recibe llamadas

Desde cualquier extensión:

  1. Marca *77
  2. Escucha: “Hay X llamadas esperando en cola 1”
  3. Escucha: “Hay X llamadas esperando en cola 2”

Para probar el comportamiento fuera de horario, tienes 3 opciones:

Opción A: Cambiar Hora del Sistema (Recomendado para pruebas)

Sección titulada «Opción A: Cambiar Hora del Sistema (Recomendado para pruebas)»
Terminal window
# Ver hora actual
date
# Cambiar temporalmente a hora fuera de horario (ej: 20:00)
date -s "20:00:00"
# Probar
# Marca 77777 desde una extensión
# Deberías escuchar mensaje de fuera de horario
# Restaurar hora correcta
ntpdate pool.ntp.org
# O manualmente:
date -s "HH:MM:SS"

Opción B: Modificar Temporalmente GotoIfTime

Sección titulada «Opción B: Modificar Temporalmente GotoIfTime»

Editar la función de horarios:

Terminal window
nano /etc/asterisk/extensions_functions.conf

Cambiar:

[func-apply-open-hours]
exten => s,1,NoOp(=== Validando Horario de Atención ===)
same => n,GotoIfTime(09:00-18:00,mon-fri,*,*?open)
same => n,GotoIfTime(09:00-13:00,sat,*,*?open)

Por (forzar siempre cerrado):

[func-apply-open-hours]
exten => s,1,NoOp(=== Validando Horario de Atención - MODO PRUEBA ===)
same => n,GotoIfTime(00:00-00:01,sun,*,*?open) ; Imposible: solo 1 min el domingo

Recargar y probar:

Terminal window
asterisk -rx "dialplan reload"
# Marca 77777
# Escucha mensaje de fuera de horario

Restaurar:

Terminal window
# Volver a poner los horarios originales
nano /etc/asterisk/extensions_functions.conf
asterisk -rx "dialplan reload"

Opción C: Agregar Código de Prueba Temporal

Sección titulada «Opción C: Agregar Código de Prueba Temporal»

Agregar en contexto [internal]:

; === CÓDIGO DE PRUEBA: SIMULAR FUERA DE HORARIO ===
exten => 9001,1,NoOp(=== Prueba Fuera de Horario ===)
same => n,Set(HORARIO_STATUS=CLOSED) ; Forzar cerrado
same => n,Goto(from-pstn,123456789,1)

Probar:

Terminal window
asterisk -rx "dialplan reload"
# Marca 9001 (en lugar de 77777)
# Deberías escuchar mensaje de fuera de horario

🗄️ 10.13 Integración con PostgreSQL (Taller 8)

Sección titulada «🗄️ 10.13 Integración con PostgreSQL (Taller 8)»

Si completaste el Taller 8, tus grabaciones ya están integradas con CDR/CEL en PostgreSQL.

Las grabaciones se guardan automáticamente en el campo userfield del CDR:

-- Conectar a PostgreSQL
-u postgres psql aulaswitch
-- Ver últimas llamadas con grabación
SELECT
calldate,
src,
dst,
duration,
disposition,
userfield as recording_path
FROM cdr
WHERE userfield LIKE '%queue-%'
ORDER BY calldate DESC
LIMIT 10;

Salida esperada:

calldate | src | dst | duration | disposition | recording_path
---------------------------+----------+------+----------+-------------+------------------------------------------
2025-01-19 14:30:22-05 | 1002 | 1001 | 125 | ANSWERED | /var/spool/asterisk/monitor/2025/01/19/queue-Ventas-1002-20250119-143022.wav
2025-01-19 14:25:15-05 | 1004 | 1005 | 89 | ANSWERED | /var/spool/asterisk/monitor/2025/01/19/queue-Soporte-1004-20250119-142515.wav

Llamadas de cola por día:

SELECT
DATE(calldate) as fecha,
COUNT(*) as total_llamadas,
AVG(duration) as duracion_promedio,
COUNT(CASE WHEN disposition = 'ANSWERED' THEN 1 END) as atendidas,
COUNT(CASE WHEN disposition = 'NO ANSWER' THEN 1 END) as no_atendidas
FROM cdr
WHERE userfield LIKE '%queue-%'
AND DATE(calldate) = CURRENT_DATE
GROUP BY DATE(calldate);

Top agentes por llamadas atendidas:

SELECT
dst as agente,
COUNT(*) as llamadas_atendidas,
AVG(duration) as duracion_promedio,
MAX(duration) as llamada_mas_larga
FROM cdr
WHERE userfield LIKE '%queue-%'
AND disposition = 'ANSWERED'
AND DATE(calldate) = CURRENT_DATE
GROUP BY dst
ORDER BY llamadas_atendidas DESC;

Llamadas con grabación disponible:

SELECT
calldate,
src as llamante,
dst as agente,
duration as duracion_seg,
SUBSTRING(userfield FROM 'queue-([^-]+)') as departamento,
userfield as archivo_audio
FROM cdr
WHERE userfield LIKE '%queue-%'
AND disposition = 'ANSWERED'
ORDER BY calldate DESC
LIMIT 20;
-- Crear vista para análisis de colas
CREATE OR REPLACE VIEW v_queue_performance AS
SELECT
DATE(calldate) as fecha,
SUBSTRING(userfield FROM 'queue-([^-]+)') as departamento,
src as llamante,
dst as agente,
duration,
billsec,
disposition,
CASE
WHEN disposition = 'ANSWERED' THEN 'Atendida'
WHEN disposition = 'NO ANSWER' THEN 'No contestada'
WHEN disposition = 'BUSY' THEN 'Ocupado'
ELSE 'Otro'
END as estado,
userfield as recording_path
FROM cdr
WHERE userfield LIKE '%queue-%';
-- Usar la vista
SELECT * FROM v_queue_performance
WHERE fecha = CURRENT_DATE
ORDER BY calldate DESC;

Terminal window
# Ver todas las colas
asterisk -rx "queue show"
# Ver cola específica
asterisk -rx "queue show cola-ventas"
# Ver solo miembros
asterisk -rx "queue show cola-ventas" | grep -A 10 "Members:"
# Ver solo llamantes
asterisk -rx "queue show cola-ventas" | grep -A 10 "Callers:"
Terminal window
# Ver todos los canales
asterisk -rx "core show channels"
# Ver canales de colas
asterisk -rx "core show channels" | grep Queue
Terminal window
# Ver archivos de grabación del día
ls -lht /var/spool/asterisk/monitor/$(date +%Y/%m/%d)/ | head -10
# Ver tamaño total de grabaciones
du -sh /var/spool/asterisk/monitor/$(date +%Y/%m/%d)/
# Reproducir última grabación (requiere sox)
apt install sox -y
play $(find /var/spool/asterisk/monitor -name "*.wav" -type f -mmin -5 | head -1)
Terminal window
# Ver logs de colas
tail -f /var/log/asterisk/queue_log
# Ver logs completos
tail -f /var/log/asterisk/full | grep -i queue

Terminal window
asterisk -rx "queue show cola-ventas"

Interpretar salida:

cola-ventas has 2 calls (max 25) in 'rrmemory' strategy (35s holdtime, 180s talktime), W:0, C:15, A:2, SL:86.7% within 60s
  • 2 calls: 2 llamadas actualmente en cola
  • 35s holdtime: Tiempo promedio de espera
  • 180s talktime: Duración promedio de llamadas
  • W:0: 0 llamadas esperando (Weight)
  • C:15: 15 llamadas completadas
  • A:2: 2 llamadas abandonadas
  • SL:86.7%: 86.7% atendidas en menos de 60s

El archivo /var/log/asterisk/queue_log contiene eventos detallados:

Terminal window
tail -20 /var/log/asterisk/queue_log

Formato:

timestamp|uniqueid|queue|agent|event|data1|data2|data3|data4|data5

Eventos principales:

  • ENTERQUEUE: Llamada entra a cola
  • CONNECT: Agente contesta
  • COMPLETEAGENT: Agente cuelga
  • COMPLETECALLER: Llamante cuelga
  • ABANDON: Llamante cuelga antes de ser atendido
  • ADDMEMBER: Agente se loguea
  • REMOVEMEMBER: Agente se desloguea
  • PAUSE: Agente se pausa
  • UNPAUSE: Agente se despausa

Si tienes PostgreSQL configurado (Taller 8):

-- Llamadas de hoy por cola
SELECT
userfield,
COUNT(*) as total_llamadas,
AVG(duration) as duracion_promedio,
SUM(duration) as duracion_total
FROM cdr
WHERE DATE(calldate) = CURRENT_DATE
AND userfield LIKE '%queue-%'
GROUP BY userfield;
-- Top 5 agentes por llamadas atendidas
SELECT
dst as agente,
COUNT(*) as llamadas_atendidas,
AVG(duration) as duracion_promedio
FROM cdr
WHERE DATE(calldate) = CURRENT_DATE
AND disposition = 'ANSWERED'
AND dst IN ('1001','1002','1003','1004','1005')
GROUP BY dst
ORDER BY llamadas_atendidas DESC
LIMIT 5;

Verificar:

Terminal window
# ¿Está logueado?
asterisk -rx "queue show cola-ventas" | grep PJSIP/1001
# ¿Está pausado?
# Buscar "(paused)" en la salida
# ¿Está registrado el dispositivo?
asterisk -rx "pjsip show endpoints" | grep 1001

Solución:

Terminal window
# Despausar
asterisk -rx "queue unpause member PJSIP/1001 queue cola-ventas"
# O desde el teléfono
# Marca *76

Verificar:

Terminal window
asterisk -rx "moh show classes"
ls -lh /var/lib/asterisk/moh/

Solución:

Terminal window
# Recargar MOH
asterisk -rx "moh reload"
# Verificar permisos
chown -R asterisk:asterisk /var/lib/asterisk/moh/

Verificar:

Terminal window
# ¿Existe el directorio?
ls -ld /var/spool/asterisk/monitor/
# ¿Tiene permisos?
ls -l /var/spool/asterisk/monitor/

Solución:

Terminal window
# Crear directorio y dar permisos
mkdir -p /var/spool/asterisk/monitor
chown -R asterisk:asterisk /var/spool/asterisk/monitor
chmod 755 /var/spool/asterisk/monitor

Verificar dialplan:

Terminal window
asterisk -rx "dialplan show from-pstn"
asterisk -rx "dialplan show ivr-principal"

Ver logs:

Terminal window
tail -f /var/log/asterisk/full

⚡ 10.16 Optimizaciones y Mejores Prácticas

Sección titulada «⚡ 10.16 Optimizaciones y Mejores Prácticas»

Crear script de limpieza:

Terminal window
nano /usr/local/bin/cleanup-recordings.sh

Contenido:

#!/bin/bash
# Eliminar grabaciones mayores a 90 días
find /var/spool/asterisk/monitor -name "*.wav" -type f -mtime +90 -delete
find /var/spool/asterisk/monitor -type d -empty -delete
echo "$(date): Limpieza completada" >> /var/log/asterisk/cleanup.log

Dar permisos y programar:

Terminal window
chmod +x /usr/local/bin/cleanup-recordings.sh
crontab -e

Agregar:

0 2 * * * /usr/local/bin/cleanup-recordings.sh
Terminal window
nano /usr/local/bin/backup-queue-log.sh

Contenido:

#!/bin/bash
TIMESTAMP=$(date +%Y%m%d)
cp /var/log/asterisk/queue_log /root/backups/queue_log.$TIMESTAMP
gzip /root/backups/queue_log.$TIMESTAMP

Crear script de monitoreo:

Terminal window
nano /usr/local/bin/monitor-queues.sh

Contenido:

#!/bin/bash
THRESHOLD=10
for QUEUE in cola-ventas cola-soporte; do
WAITING=$(asterisk -rx "queue show $QUEUE" | grep "has" | awk '{print $3}')
if [ "$WAITING" -gt "$THRESHOLD" ]; then
echo "ALERTA: Cola $QUEUE tiene $WAITING llamadas esperando" | mail -s "Alerta de Cola" admin@techsolutions.com
fi
done

Central telefónica completa con IVR, horarios y colas
Handlers profesionales b/B para grabación automática (Asterisk 22)
IVR con Background() para respuesta inmediata
Sistema de grabación de audios (*6500) para personalización
2 colas profesionales con estrategias optimizadas
Uso de Gosub() en lugar de Goto() (consistente con Taller 5)
Sistema de login/logout para agentes (*71-*77)
Grabación solo de llamadas atendidas (ahorra espacio)
Integración completa con PostgreSQL (Taller 8)
Variables heredadas (__VARIABLE) para handlers
Contexto internal consistente con talleres previos

  • /etc/asterisk/pjsip_wizard.conf → 5 extensiones con plantilla
  • /etc/asterisk/queues.conf → Colas con configuración profesional
  • /etc/asterisk/musiconhold.conf → MOH por departamento
  • /etc/asterisk/extensions_functions.conf → Horarios + Grabación audios
  • /etc/asterisk/extensions_entradas.conf → IVR + Handlers + Funciones
  • /etc/asterisk/extensions.conf → Configuración principal

🎯 Handlers b en Queue() → Grabación solo cuando agente contesta
🎯 Background() en IVR → Entrada DTMF durante reproducción
🎯 Gosub() en lugar de Goto() → Funciones reutilizables y retorno limpio
🎯 Variables heredadas __VAR → Disponibles en todos los contextos
🎯 Integración PostgreSQL → Reportes avanzados con SQL
🎯 Sistema de audios interno → Sin dependencias externas
🎯 Organización modular → Archivos separados por función

Aspecto❌ Método Antiguo✅ Método Profesional
GrabaciónMixMonitor manualHandler b automático
Contextocall-centerinternal (consistente)
IVRPlayback() bloqueanteBackground() interactivo
FlujoGoto() sin retornoGosub() con Return()
VariablesLocales se pierden__VAR heredadas
AudiosSoftware externoSistema interno (*6500)

🎓 Taller 11: Dashboard de Colas en Tiempo Real

Agregaremos al panel web del Taller 9 un dashboard de monitoreo que muestre:

  • Estado de colas (llamadas en espera, tiempo promedio)
  • Estado de agentes (disponible, ocupado, pausado, tiempo en llamada)
  • Llamadas activas (quién está atendiendo a quién)
  • Métricas en vivo (llamadas atendidas, abandonadas, SL)
  • Control remoto (pausar/despausar agentes desde la web)

Usaremos AMI (Asterisk Manager Interface) para eventos en tiempo real y Server-Sent Events (SSE) para actualización del dashboard.


Has construido una central telefónica profesional usando las mejores prácticas de Asterisk 22:

Handlers modernos para control granular
Arquitectura modular fácil de mantener
Integración completa con base de datos
Consistencia técnica con talleres previos
Lista para producción con todas las funcionalidades


FIN DEL TALLER 10 - CENTRAL TELEFÓNICA COMPLETA 🚀