✅ Función de Horarios
Implementar validación reutilizable de horarios comerciales
Construcción de una central profesional con menú IVR multinivel, validación de horarios y distribución de llamadas mediante colas
✅ Función de Horarios
Implementar validación reutilizable de horarios comerciales
✅ IVR Multinivel
Crear menú de opciones con navegación intuitiva
✅ Colas Profesionales
Configurar dos colas (ventas y soporte) con estrategias diferentes
✅ Gestión Dinámica
Login/logout automático de agentes en colas
✅ Música en Espera
Configurar MOH personalizada por departamento
✅ Grabación Automática
Integrar MixMonitor para todas las llamadas
✅ Integración CDR
Conectar con PostgreSQL para reportes avanzados
✅ Arquitectura Modular
Diseño escalable y mantenible para producción
La empresa TechSolutions necesita modernizar su atención telefónica. Actualmente tienen un número único donde todos contestan, generando caos y llamadas perdidas.
Horario de atención:
Menú IVR:
Colas especializadas:
Agentes asignados:
Requisitos adicionales:
Antes de empezar, siempre hacemos backup. Esta es una práctica obligatoria en producción.
# Crear directorio de backup con timestamp mkdir -p /root/backups/taller10TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Backup de archivos que modificaremos cp /etc/asterisk/extensions.conf /root/backups/taller10/extensions.conf.$TIMESTAMP cp /etc/asterisk/queues.conf /root/backups/taller10/queues.conf.$TIMESTAMP cp /etc/asterisk/musiconhold.conf /root/backups/taller10/musiconhold.conf.$TIMESTAMP cp /etc/asterisk/pjsip.conf /root/backups/taller10/pjsip.conf.$TIMESTAMP
# Verificar backupsls -lh /root/backups/taller10/# Crear script de restauración rápida nano /root/backups/restore-taller10.sh#!/bin/bash# Script de restauración rápidaBACKUP_DIR="/root/backups/taller10"TIMESTAMP=$1
if [ -z "$TIMESTAMP" ]; then echo "Uso: $0 TIMESTAMP" echo "Timestamps disponibles:" ls -1 $BACKUP_DIR/*.conf.* | sed 's/.*\.\([0-9_]*\)/\1/' | sort -u exit 1fi
cp $BACKUP_DIR/extensions.conf.$TIMESTAMP /etc/asterisk/extensions.conf cp $BACKUP_DIR/queues.conf.$TIMESTAMP /etc/asterisk/queues.conf cp $BACKUP_DIR/musiconhold.conf.$TIMESTAMP /etc/asterisk/musiconhold.conf cp $BACKUP_DIR/pjsip.conf.$TIMESTAMP /etc/asterisk/pjsip.conf
asterisk -rx "core reload"echo "Restauración completada con backup: $TIMESTAMP" chmod +x /root/backups/restore-taller10.shNecesitamos 5 extensiones para nuestros agentes. Usaremos PJSIP Wizard para una configuración limpia, escalable y profesional.
asterisk -rx "pjsip show endpoints" | grep -E "1001|1002|1003|1004|1005"Primero, aseguramos que pjsip.conf solo tenga configuración global y de transporte:
nano /etc/asterisk/pjsip.confVerifica que contenga (o agrégalo si falta):
[global]type=globaluser_agent=TechSolutions-CallCenterdefault_realm=techsolutions.local
[transport-udp]type=transportprotocol=udpbind=0.0.0.0:5060local_net=192.168.1.0/24 ; Ajusta a tu red localexternal_media_address=TU_IP_PUBLICAexternal_signaling_address=TU_IP_PUBLICAAhora creamos la configuración de agentes usando el Wizard:
nano /etc/asterisk/pjsip_wizard.confAgregar al final (o crear el archivo si no existe):
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; AGENTES CALL CENTER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[phone-template](!)type=wizard ; Declara una plantilla reutilizabletransport=transport-udp ; Hereda el transporte definido en pjsip.confaccepts_auth=yes ; Genera objeto auth para autenticaciónaccepts_registrations=yes ; Permite que los softphones se registrenendpoint/allow=!all,ulaw,alaw,opus ; Códecs habilitados (orden de prioridad)endpoint/context=internal ; Contexto de dialplan para agentesendpoint/dtmf_mode=rfc4733 ; Tonos DTMF confiables para IVRendpoint/rtp_symmetric=yes ; Solución para NAT en RTPendpoint/force_rport=yes ; Reescribe puerto para clientes NATendpoint/rewrite_contact=yes ; Asegura Contact correctoendpoint/direct_media=no ; Mantén audio en Asterisk (para grabación)aor/max_contacts=1 ; Un dispositivo por extensiónaor/qualify_frequency=30 ; Verifica disponibilidad cada 30s
; === AGENTES DE VENTAS ===
[1001](phone-template)endpoint/context=cat1 ; Contexto para agentes de ventasendpoint/mailboxes=1001@defaultendpoint/callerid="Juan Pérez - Ventas <1001>"inbound_auth/username=1001inbound_auth/password=Ventas1001!
[1002](phone-template)endpoint/context=cat2endpoint/mailboxes=1002@defaultendpoint/callerid="María García - Ventas <1002>"inbound_auth/username=1002inbound_auth/password=Ventas1002!
[1003](phone-template)endpoint/context=cat3endpoint/mailboxes=1003@defaultendpoint/callerid="Carlos López - Ventas <1003>"inbound_auth/username=1003inbound_auth/password=Ventas1003!
; === AGENTES DE SOPORTE ===
[1004](phone-template)endpoint/context=cat3 ; Contexto para agentes de soporteendpoint/mailboxes=1004@defaultendpoint/callerid="Ana Martínez - Soporte <1004>"inbound_auth/username=1004inbound_auth/password=Soporte1004!
[1005](phone-template)endpoint/context=cat3endpoint/mailboxes=1005@defaultendpoint/callerid="Luis Rodríguez - Soporte <1005>"inbound_auth/username=1005inbound_auth/password=Soporte1005! asterisk -rx "pjsip reload"El Wizard generó automáticamente los objetos. Verifica uno por uno:
1. Verificar endpoint:
asterisk -rx "pjsip show endpoint 1001"Salida esperada:
Endpoint: 1001/1001 Not in use 0 of inf OutAuth: 1001-oauth/1001 Aor: 1001 1 Transport: transport-udp udp 0 0 0.0.0.0:5060 Identify: 1001/10012. Verificar auth:
asterisk -rx "pjsip show auth 1001-iauth"3. Verificar aor:
asterisk -rx "pjsip show aor 1001"4. Verificar todos los endpoints:
asterisk -rx "pjsip show endpoints" | grep -E "1001|1002|1003|1004|1005"Salida esperada:
1001/1001 Not in use 0 of inf 1002/1002 Not in use 0 of inf 1003/1003 Not in use 0 of inf 1004/1004 Not in use 0 of inf 1005/1005 Not in use 0 of infConfigura al menos 2 softphones (Zoiper, Linphone, etc.) con estas credenciales:
Agente 1001 (Ventas):
Agente 1004 (Soporte):
Verificar registro exitoso:
asterisk -rx "pjsip show contacts"Deberías ver:
Contact: 1001/sip:1001@192.168.1.100:5060 Avail 3.456Contact: 1004/sip:1004@192.168.1.101:5060 Avail 2.123Las colas necesitan música mientras los llamantes esperan. Configuraremos MOH con archivos del sistema.
Asterisk incluye archivos de audio por defecto:
ls -lh /var/lib/asterisk/moh/Deberías ver archivos como:
macroform-cold_day.wavmacroform-robot_dity.wavmacroform-the_simplicity.wavSi instalamos con otro usuario, debemos cambiar los permisos, en caso de root no es necesario
# chown -R asterisk:asterisk /var/lib/asterisk/moh/# chmod -R 644 /var/lib/asterisk/moh/*.wavEditar /etc/asterisk/musiconhold.conf:
nano /etc/asterisk/musiconhold.confReemplazar todo el contenido con:
; /etc/asterisk/musiconhold.conf; Configuración de Música en Espera para Call Center
[general]
; === CLASE DEFAULT (Para todas las colas) ===[default]mode=filesdirectory=/var/lib/asterisk/mohsort=random ; Reproduce las pistas en orden aleatoriosort=alpha
; === CLASE PARA VENTAS (Música más energética) ===[ventas]mode=filesdirectory=/var/lib/asterisk/mohsort=random ; Reproduce las pistas en orden aleatoriosort=alpha
; === CLASE PARA SOPORTE (Música más relajante) ===[soporte]mode=filesdirectory=/var/lib/asterisk/mohsort=random ; Reproduce las pistas en orden aleatoriosort=alpha asterisk -rx "moh reload" asterisk -rx "moh show classes"Salida esperada:
Class: default Mode: files Directory: /var/lib/asterisk/mohClass: ventas Mode: files Directory: /var/lib/asterisk/mohClass: soporte Mode: files Directory: /var/lib/asterisk/mohAhora sí podemos probar la música en espera. El comando llamará al anexo 1001 y reproducirá MOH:
# Probar música llamando al anexo 1001 asterisk -rx "originate PJSIP/1001 application MusicOnHold default"Probar otras clases de MOH:
# Probar clase "ventas" asterisk -rx "originate PJSIP/1001 application MusicOnHold ventas"
# Probar clase "soporte" asterisk -rx "originate PJSIP/1001 application MusicOnHold soporte"Ahora configuramos las dos colas: cola-ventas y cola-soporte.
Editar /etc/asterisk/queues.conf:
nano /etc/asterisk/queues.confReemplazar todo el contenido con:
; /etc/asterisk/queues.conf; Configuración de Colas para TechSolutions Call Center
[general]; === CONFIGURACIÓN GLOBAL ===
; Guardar miembros dinámicos en AstDB (persisten al reiniciar)persistentmembers = yes
; Distribuir llamadas en paralelo (no serial)autofill = yes
; Tipo de grabación (usaremos MixMonitor)monitor-type = MixMonitor
; Compartir lastcall entre colas (para wrapuptime)shared_lastcall = yes
; === PLANTILLA REUTILIZABLE ===[plantilla-cola](!); Timeout para contestar (20 segundos)timeout = 20
; Retry en X segundos si no contestaretry = 5
; Wrapuptime: segundos después de colgar antes de nueva llamadawrapuptime = 15
; Autopause: pausar agente si no contesta; yes = solo en esta cola, all = en todas las colasautopause = yes
; Autopausedelay: esperar X segundos antes de autopausarautopausedelay = 60
; Ringinuse: NO llamar a agentes que ya están en llamadaringinuse = no
; Joinempty: permitir entrar a cola aunque no haya agentesjoinempty = yes
; Leavewhenempty: NO sacar al llamante si todos se deslogueanleavewhenempty = no
; Timeoutpriority: priorizar timeout de config sobre dialplantimeoutpriority = conf
; Anuncios periódicosannounce-frequency = 30announce-holdtime = yesannounce-position = yesperiodic-announce-frequency = 60
; Service Level: llamadas atendidas en X segundosservicelevel = 60
; Máximo de llamadas en esperamaxlen = 20
; Variables en canalsetinterfacevar = yessetqueueentryvar = yessetqueuevar = yes
; === COLA DE VENTAS ===[cola-ventas](plantilla-cola); Estrategia: Round Robin con memoria (distribución equitativa)strategy = rrmemory
; Música específica para ventasmusicclass = ventas
; Anuncio de bienvenida (se reproduce al entrar);announce = custom/bienvenida-ventas
; Máximo de llamadas en esperamaxlen = 25
; Wrapuptime más corto para ventas (ritmo rápido)wrapuptime = 10
; === COLA DE SOPORTE ===[cola-soporte](plantilla-cola); Estrategia: Fewest Calls (menos llamadas atendidas)strategy = fewestcalls
; Música específica para soportemusicclass = soporte
; Anuncio de bienvenida;announce = custom/bienvenida-soporte
; Máximo de llamadas en esperamaxlen = 15
; Wrapuptime más largo para soporte (casos complejos)wrapuptime = 20💡 Diferencias entre las Colas
cola-ventas:
strategy = rrmemory → Distribución equitativa entre agenteswrapuptime = 10 → Ritmo rápido, ventas son más cortasmaxlen = 25 → Más capacidad (ventas suelen tener más volumen)cola-soporte:
strategy = fewestcalls → Prioriza al agente con menos cargawrapuptime = 20 → Más tiempo para documentar casos técnicosmaxlen = 15 → Menos capacidad (soporte es más especializado) asterisk -rx "module reload app_queue.so" 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 CallersCrearemos funciones reutilizables en un archivo modular siguiendo las buenas prácticas del Taller 7.
Horarios de TechSolutions:
La función func-apply-open-hours retornará:
OPEN si estamos en horarioCLOSED si estamos fuera de horarioCrear el archivo de funciones auxiliares:
nano /etc/asterisk/extensions_functions.confContenido completo:
; ============================================================================; FUNCIÓN: Validación de Horarios de Atención (Versión con GotoIfTime); ============================================================================; Esta función verifica si estamos en horario de atención; Retorna: OPEN o CLOSED en la variable HORARIO_STATUS; Uso: Gosub(func-apply-open-hours,s,1)
[func-apply-open-hours]exten => s,1,NoOp(=== Validando Horario de Atención ===) same => n,Set(HORARIO_STATUS=CLOSED) ; Por defecto: cerrado
; Verificar horarios con GotoIfTime (más simple y legible) ; Formato: GotoIfTime(horas,días_semana,días_mes,meses?etiqueta)
; Lunes a Viernes (mon-fri): 9:00 - 18:00 same => n,GotoIfTime(09:00-17:59,mon-fri,*,*?open)
; Sábado (sat): 9:00 - 13:00 same => n,GotoIfTime(09:00-12:59,sat,*,*?open)
; Si no coincide con ningún horario, queda CLOSED same => n,Goto(closed)
same => n(open),Set(HORARIO_STATUS=OPEN) same => n,NoOp(Resultado: ABIERTO) same => n,Return()
same => n(closed),NoOp(Resultado: CERRADO) same => n,Return()
; ============================================================================; FUNCIÓN ALTERNATIVA: Validación con STRFTIME (Más control manual); ============================================================================; Misma funcionalidad pero con lógica manual usando variables; Útil si necesitas lógica más compleja o debugging detallado
[func-apply-open-hours-manual]exten => s,1,NoOp(=== Validando Horario (Versión Manual) ===) same => n,Set(HORARIO_STATUS=CLOSED) same => n,Set(DIA_SEMANA=${STRFTIME(${EPOCH},,%u)}) ; 1=Lun, 7=Dom same => n,Set(HORA_ACTUAL=${STRFTIME(${EPOCH},,%H%M)}) ; Formato: HHMM same => n,NoOp(Día: ${DIA_SEMANA}, Hora: ${HORA_ACTUAL})
; === LUNES A VIERNES (1-5): 9:00 - 18:00 === same => n,GotoIf($[${DIA_SEMANA} >= 1 & ${DIA_SEMANA} <= 5]?check_weekday:check_saturday)
same => n(check_weekday),NoOp(Verificando horario de Lunes a Viernes) same => n,GotoIf($[${HORA_ACTUAL} >= 0900 & ${HORA_ACTUAL} < 1800]?open:closed)
; === SÁBADO (6): 9:00 - 13:00 === same => n(check_saturday),GotoIf($[${DIA_SEMANA} = 6]?check_saturday_hours:closed) same => n(check_saturday_hours),NoOp(Verificando horario de Sábado) same => n,GotoIf($[${HORA_ACTUAL} >= 0900 & ${HORA_ACTUAL} < 1300]?open:closed)
; === DOMINGO (7): CERRADO === same => n(closed),NoOp(Resultado: CERRADO) same => n,Return()
same => n(open),Set(HORARIO_STATUS=OPEN) same => n,NoOp(Resultado: ABIERTO) same => n,Return()Editar /etc/asterisk/extensions.conf:
nano /etc/asterisk/extensions.confVerificar que tenga esta estructura (agregar lo que falte):
[general]static=yes ; Si es 'yes', Asterisk no sobrescribe este archivowriteprotect=no ; Ajusta a 'yes' si deseas impedir cambios desde CLIautofallthrough=yes ; Evita que la llamada quede colgada si una extensión termina sin Hangupclearglobalvars=no ; Mantiene las variables globales entre recargas de dialplanpriorityjumping=no ; Recomendado: evita saltos implícitos de prioridad
[globals]IDTRUNK=TU_NUMERO_DID ; Valor que reutilizaremos como CallerID de la troncalCONTEXT_ENTRADAS=from-trunk ; Contexto centralizado para llamadas entrantesCONTEXT_CALLCENTER=internal ; Contexto para agentes del call center
#include extensions_anexos.conf#include extensions_salidas.conf#include extensions_funciones.conf#include extensions_categorias.conf#include extensions_entradas.confCrea el archivo /etc/asterisk/extensions_entradas.conf:
touch /etc/asterisk/extensions_entradas.conf asterisk -rx "dialplan reload" asterisk -rx "dialplan show func-apply-open-hours"Antes de crear el IVR, necesitamos grabar los audios personalizados. Usaremos un método simple y efectivo que permite grabar desde cualquier extensión.
Un IVR profesional requiere mensajes claros y personalizados:
Editar /etc/asterisk/extensions_functions.conf y agregar al final:
; ============================================================================; SISTEMA DE GRABACIÓN DE AUDIOS PARA IVR; ============================================================================; Permite grabar audios personalizados desde cualquier extensión
[func-grabar-audios]; === GRABACIÓN DE AUDIOS (*6500) ===exten => *6500,1,NoOp(=== Sistema de Grabación de Audios IVR ===) same => n,Answer() same => n,Wait(1) same => n,Playback(beep) same => n,Record(custom/audio%d.wav,3,30,k) ; Presionar # para finalizar same => n,Wait(1) same => n,Playback(beep) same => n,Playback(${RECORDED_FILE}) ; Reproduce lo grabado same => n,NoOp(Audio grabado: ${RECORDED_FILE}) same => n,Wait(1) same => n,Hangup()
; === ESCUCHAR AUDIOS GRABADOS ===; Para audios de 1 dígito: *01, *02, *03, etc.exten => _*0X,1,NoOp(=== Reproducir Audio ${EXTEN:2} ===) same => n,Answer() same => n,Playback(custom/audio${EXTEN:2}) same => n,Wait(1) same => n,Hangup()
; Para audios de 2 dígitos: *11, *12, *20, etc.exten => _*[12]X,1,NoOp(=== Reproducir Audio ${EXTEN:1} ===) same => n,Answer() same => n,Playback(custom/audio${EXTEN:1}) same => n,Wait(1) same => n,Hangup()Editar /etc/asterisk/extensions_categorias.conf y asegurar que se incluyan las nuevas categorías
; En extensions_categorias.conf - MANTENER estructura existente[cat1] ; Categoría con todos los permisosinclude => anexos-internos ; Ya existe desde talleres previosinclude => funciones-voicemail ; Ya existe desde Taller 6include => salidas-celular ; Ya existe desde Taller 5include => salidas-fijoinclude => salidas-ldninclude => salidas-ldi
[cat2] ; Categoría solo con permisos internos y colasinclude => anexos-internosinclude => funciones-voicemailinclude => salidas-fijoinclude => salidas-celular
[cat3] ; Categoría básicainclude => anexos-internosinclude => funciones-voicemailinclude => salidas-celular# Crear directorio para audios personalizadosmkdir -p /var/lib/asterisk/sounds/custom
# Dar permisos correctos (si es necesario)# chown -R asterisk:asterisk /var/lib/asterisk/sounds/custom# chmod 755 /var/lib/asterisk/sounds/custom# Recargar dialplanasterisk -rx "dialplan reload"
# Verificar que la extensión existeasterisk -rx "dialplan show func-grabar-audios"Paso 1: Preparar el guión
Antes de grabar, escribe el texto que dirás:
| Audio | Código | Contenido Sugerido |
|---|---|---|
audio0 | *00 | ”Bienvenido a TechSolutions. Presione 1 para ventas, 2 para soporte técnico, 3 para administración, o 0 para hablar con la operadora” |
audio1 | *01 | ”Por favor espere, lo estamos comunicando con el departamento de ventas” |
audio2 | *02 | ”Por favor espere, lo estamos comunicando con soporte técnico” |
audio3 | *03 | ”Nuestro horario de atención es de lunes a viernes de 9 de la mañana a 6 de la tarde, y sábados de 9 de la mañana a la una de la tarde. Por favor llame en ese horario” |
Paso 2: Grabar cada audio
# Desde tu softphone (ej: extensión 1001)# 1. Marca *6500# 2. Escucha el tono (beep)# 3. Habla claramente el mensaje# 4. Presiona # cuando termines# 5. Escucha la reproducción para verificar# 6. Anota el número de audio asignadoPaso 3: Verificar grabaciones
# Listar audios grabadosls -lh /var/lib/asterisk/sounds/custom/
# Reproducir desde CLI (requiere sox)apt install sox -yplay /var/lib/asterisk/sounds/custom/audio0.wavPaso 4: Probar desde teléfono
# Marca *00 para escuchar audio0# Marca *01 para escuchar audio1# Marca *02 para escuchar audio2# etc.Para este taller, graba al menos estos 4 audios:
audio0 (*00): Mensaje de bienvenida y menú principalaudio1 (*01): "Comunicando con ventas"audio2 (*02): "Comunicando con soporte"audio3 (*03): Mensaje de horario cerradoAhora creamos el menú IVR usando los audios que grabamos y aprovechando Background() para respuesta inmediata.
Crear el archivo para el IVR y manejo de llamadas:
nano /etc/asterisk/extensions_entradas.confContenido completo:
; ============================================================================; CONTEXTO: IVR Principal con Background Profesional; ============================================================================; Menú de opciones con respuesta inmediata durante reproducción; Opciones: 1=Ventas, 2=Soporte, 3=Administración, 0=Operadora
[ivr-principal]exten => s,1,NoOp(=== IVR Principal - Caller: ${CALLERID(all)} ===) same => n,Answer() same => n,Wait(1)
; Configurar timeouts para mejor experiencia same => n,Set(TIMEOUT(digit)=3) ; 3 segundos entre dígitos same => n,Set(TIMEOUT(response)=5) ; 5 segundos para empezar a marcar same => n,Set(IVR_RETRIES=0) same => n,Goto(ivr-menu)
; === MENÚ PRINCIPAL === same => n(ivr-menu),Set(IVR_RETRIES=$[${IVR_RETRIES} + 1]) same => n,GotoIf($[${IVR_RETRIES} > 3]?ivr-timeout)
; Background permite entrada DTMF durante reproducción same => n,Background(custom/audio0) ; Audio grabado: menú principal same => n,WaitExten(5)
; Si no presiona nada, repetir same => n,Playback(invalid) same => n,Wait(1) same => n,Goto(ivr-menu)
; === OPCIÓN 1: VENTAS ===exten => 1,1,NoOp(=== Opción 1: Ventas ===) same => n,Playback(custom/audio1) ; "Comunicando con ventas" 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},tT,,,300) same => n,Playback(vm-nobodyavail) same => n,Hangup() same => n(closed),Playback(custom/audio3) ; Mensaje fuera de horario same => n,Hangup()
; === OPCIÓN 2: SOPORTE ===exten => 2,1,NoOp(=== Opción 2: Soporte Técnico ===) same => n,Playback(custom/audio2) ; "Comunicando con soporte" same => n,Gosub(func-apply-open-hours,s,1) same => n,GotoIf($["${HORARIO_STATUS}" = "OPEN"]?open:closed) same => n(open),Set(__QUEUE_NAME=cola-soporte) same => n,Set(__QUEUE_DEPT=Soporte) same => n,Queue(${QUEUE_NAME},tT,,,300) same => n,Playback(vm-nobodyavail) same => n,Hangup() same => n(closed),Playback(custom/audio3) ; Mensaje fuera de horario same => n,Hangup()
; === OPCIÓN 3: ADMINISTRACIÓN ===exten => 3,1,NoOp(=== Opción 3: Administración ===) same => n,Playback(pls-wait) same => n,Dial(PJSIP/1001,30,tT) ; Llamar directamente same => n,Playback(vm-nobodyavail) same => n,Hangup()
; === OPCIÓN 0: OPERADORA ===exten => 0,1,NoOp(=== Opción 0: Operadora ===) same => n,Playback(pls-wait) same => n,Dial(PJSIP/1001,30,tT) ; Llamar a operadora same => n,Playback(vm-nobodyavail) same => n,Hangup()
; === MANEJO DE ERRORES PROFESIONAL ===exten => t,1,NoOp(=== Timeout en IVR ===) same => n,Goto(s,ivr-menu)
exten => i,1,NoOp(=== Opción Inválida ===) same => n,Playback(invalid) same => n,Wait(1) same => n,Goto(s,ivr-menu)
exten => s-timeout,n(ivr-timeout),NoOp(=== Demasiados Intentos ===) same => n,Playback(vm-nobodyavail) same => n,Playback(goodbye) same => n,Hangup()
; === MARCACIÓN DIRECTA DE EXTENSIONES ===; Permite marcar extensiones directamente desde el IVRexten => _1XXX,1,NoOp(=== Marcación Directa: ${EXTEN} ===) same => n,Dial(PJSIP/${EXTEN},20,tT) same => n,Playback(vm-nobodyavail) same => n,Hangup() asterisk -rx "dialplan reload" asterisk -rx "dialplan show ivr-principal" asterisk -rx "dialplan show func-apply-open-hours" asterisk -rx "dialplan show func-grabar-audios"internal usado consistentemente# Verificar extensiones asterisk -rx "pjsip show endpoints" | wc -l
# Verificar MOH asterisk -rx "moh show classes" | grep "Class:"
# Verificar colas asterisk -rx "queue show" | grep "has 0 calls"
# Verificar dialplan asterisk -rx "dialplan show ivr-principal" | head -5 asterisk -rx "dialplan show func-apply-open-hours" | head -5🔧 Infraestructura Base
5 extensiones PJSIP con contexto internal consistente
🎵 Sistema de Audio
MOH especializada + Sistema de grabación de audios IVR
🏢 Colas Profesionales
2 colas con estrategias optimizadas (rrmemory y fewestcalls)
⏰ Validación de Horarios
Función reutilizable con GotoIfTime (método moderno)
📞 IVR con Background()
Respuesta inmediata durante reproducción + manejo de errores robusto
🎙️ Grabación de Audios
Sistema simple (*6500) para audios personalizados del IVR
💾 Backup System
Sistema de respaldos y restauración para producción
🔗 Arquitectura Modular
Archivos separados siguiendo convenciones de talleres previos
/etc/asterisk/pjsip_wizard.conf → 5 extensiones con plantilla reutilizable/etc/asterisk/musiconhold.conf → 3 clases de MOH/etc/asterisk/queues.conf → Colas ventas y soporte/etc/asterisk/extensions_functions.conf → Horarios + Grabación de audios/etc/asterisk/extensions_entradas.conf → IVR profesional/etc/asterisk/extensions.conf → Configuración principal e includes✅ Contexto internal consistente (corregido de call-center)
✅ Background() en lugar de Playback() para respuesta inmediata
✅ Timeouts configurados para mejor experiencia de usuario
✅ Validación de horarios integrada en cada opción del IVR
✅ Sistema de grabación interno sin software externo
✅ Manejo de errores profesional con máximo de intentos
✅ Marcación directa de extensiones desde el IVR
🎓 Parte 2 del Taller
En la Parte 2 completaremos el sistema con:
¡Continúa con la Parte 2 para completar tu central telefónica! 🚀