⏱️ Información de la Presentación
Duración: 45 minutos | Nivel: Intermedio | Modalidad: Teoría con ejemplos prácticos
Un dialplan de producción puede crecer a cientos o miles de líneas. Si no es modular y claro, se vuelve frágil e inmanejable. Nuestro objetivo: código limpio, modular y reutilizable .
Recordatorio de la sintaxis fundamental: exten => nombre,prioridad,aplicacion(argumentos)
; Forma antigua y verbosa
exten => 1001,1,NoOp(Llamando)
exten => 1001,2,Dial(PJSIP/1001)
exten => 1001,1,NoOp(Llamando)
same => n,Dial(PJSIP/1001)
Las prioridades determinan el orden de ejecución dentro de una misma exten.
La primera prioridad debe ser 1. Las siguientes pueden numerarse (2, 3, …) o declararse como n (next).
same => n,Aplicación(...) reutiliza la última exten declarada y agrega el siguiente paso, evitando repetir el número y el nombre.
exten => 200,1,NoOp(Inicia flujo)
same => n,Playback(hello-world)
same => n,Goto(etiqueta-final)
same => n,Hangup() ; no se ejecuta porque se salta con Goto
same => n(etiqueta-final),NoOp(Finalizando)
s (start): punto de entrada cuando no hay dígitos marcados (p. ej., llamadas entrantes a un contexto).
i (invalid): se ejecuta cuando el patrón marcado no coincide con ninguna exten válida.
t (timeout): se dispara cuando expira el tiempo de espera de marcación sin recibir más dígitos.
h (hangup): corre cuando el canal cuelga; útil para limpieza o métricas.
exten => s,1,NoOp(Inicio de llamada entrante)
same => n,Dial(PJSIP/1001,30)
exten => i,1,Playback(invalid)
exten => t,1,Playback(vm-goodbye)
exten => h,1,NoOp(Canal colgado ; guardando CDR personalizado)
Para declarar patrones usa el prefijo _ en el nombre de la extensión.
_X → un dígito 0-9
_Z → un dígito 1-9
_N → un dígito 2-9
. → uno o más dígitos adicionales (greedy)
! → coincidencia inmediata (match-as-soon-as-possible)
[ ] → clases de caracteres o rangos: [123], [2-5]
Ordena tus patrones del más específico al más genérico para evitar coincidencias inesperadas.
; Marcación interna a anexos 1000-1999
exten => _1XXX,1,Dial(PJSIP/${EXTEN})
; Salidas: celular nacional con prefijo 9 (Perú)
exten => _9XXXXXXXX,1,Dial(PJSIP/${EXTEN}@mi-troncal)
; Internacional: 00 + cualquier longitud
exten => _00.,1,NoOp(Llamada internacional a ${EXTEN})
; Servicio corto 1XX (112, 123, etc.)
exten => _1[0-9][0-9],1,NoOp(Servicio corto)
; Emergencia; con `!` evitamos esperar más dígitos si el usuario marca rápido
exten => _911!,1,NoOp(Emergencia inmediata)
Variables de canal (built-in): ${EXTEN}, ${CALLERID(num)}, ${CALLERID(name)}, ${DIALSTATUS}
Variables personalizadas: con Set()
same => n,Set( MI_VARIABLE =HolaMundo)
same => n,NoOp(El valor es: ${MI_VARIABLE})
Ámbitos de variables
[globals] define variables globales disponibles en todos los contextos: MYTRUNK=mi-proveedor
Variables de canal: viven solo durante la llamada (Set())
Variables locales a subrutinas: usa ${ARG1}, ${ARG2} en Gosub
Las expresiones dentro de $[...] permiten operaciones aritméticas y lógicas. Recuerda entrecomillar strings.
Aritmética: + - * / %
Comparación: = != > >= < <=
Lógicos: && || !
same => n,Set( CNT =$[${CNT} + 1])
; Comparación segura de strings
same => n,GotoIf($[ "${CALLERID(num)}" = "12345" ]?vip,1)
LEN(string): longitud de una cadena
STRREPLACE(str,find,rep[,limit]): reemplazo de subcadenas
REGEX(regex,string): devuelve 1 si coincide, 0 si no
FIELDQTY(string,delim): cantidad de campos separados por delim
CUT(var,delim,field): extrae un campo (0-index)
FILTER(chars,string): filtra dejando solo chars
same => n,Set( L =${LEN(${EXTEN})})
same => n,GotoIf($[${L} < 7]?short,1)
; Normalizar número: quitar guiones
same => n,Set( NORM =${STRREPLACE(${EXTEN},-,,)})
; Validar prefijo con REGEX (empieza en 9 y 8 dígitos más)
same => n,GotoIf($[${REGEX( "^9[0-9]{8}$" ${NORM})} = 1]?cel,1)
; Partir por "," y tomar tercer campo
same => n,Set( CAMPO3 =${CUT(VAR, "," ,2)})
same => n,Set( ONLYDIGITS =${FILTER(0123456789,${EXTEN})})
IFTIME(times,weekdays,mdays,months?true:false): evalúa por ventana temporal
IF(cond?true:false): operador ternario en funciones
RAND(min,max): número aleatorio entero (en algunas versiones RANDOM())
; Ruta distinta en horario de oficina
same => n,GotoIfTime(09:00-18:00,mon-fri,*,*?oficina,1)
; O equivalente con función IFTIME
same => n,Set( RUTA =${IFTIME(09:00-18:00,mon-fri,*,*?oficina:ivr)})
same => n,Goto(${RUTA},1)
; Selección aleatoria de operador 1001/1002
same => n,Set( R =${RAND(1,2)})
same => n,GotoIf($[${R}=1]?1001,1:1002,1)
Compatibilidad de funciones
Algunas funciones pueden variar por versión (p. ej., RAND/RANDOM). Verifica con *CLI> core show functions y core show function <NOMBRE>.
Usa ${VARIABLE:<offset>:<length>} para extraer partes.
exten => _9XXXXXXX,1,NoOp(Usuario marcó ${EXTEN})
same => n,Set( NUMERO_SIN_PREFIJO =${EXTEN:1})
same => n,NoOp(Enviando al proveedor: ${NUMERO_SIN_PREFIJO})
same => n,Dial(PJSIP/${NUMERO_SIN_PREFIJO}@mi-troncal)
Formas admitidas:
${VAR:offset} → desde offset hasta el final
${VAR:offset:length} → desde offset por length caracteres
offset puede ser negativo: cuenta desde el final de la cadena
Si length excede la longitud disponible, Asterisk devuelve lo que haya sin error
Casos de uso típicos:
Remover prefijos de marcación (9, 0, 00)
Tomar últimos dígitos (validaciones, máscaras)
Extraer códigos de país/área para ruteo
; Quitar 0 inicial: si el usuario marca 0+numero
same => n,Set( N =${EXTEN})
same => n,ExecIf($[ "${N:0:1}" = "0" ]?Set( N =${N:1}))
; Tomar los últimos 4 dígitos del CallerID
same => n,Set( LAST4 =${CALLERID(num):-4})
same => n,NoOp(Ultimos 4: ${LAST4})
; Extraer código de país (asumiendo 00 prefijo internacional)
exten => _00.,1,NoOp(Llamada internacional: ${EXTEN})
same => n,Set( CP =${EXTEN:2:2}) ; 2 dígitos tras 00 (ej: 51 Perú)
same => n,Set( NUM =${EXTEN:4}) ; resto del número sin 00CP
same => n,NoOp( CP =${CP} NUM =${NUM})
; Normalizar: si empieza en +, quitarlo; si empieza en 00, cambiar a +
same => n,Set( D =${EXTEN})
same => n,ExecIf($[ "${D:0:1}" = "+" ]?Set( D =${D:1}))
same => n,ExecIf($[ "${D:0:2}" = "00" ]?Set( D =+${D:2}))
; Cortes encadenados: quitar prefijo 9 y tomar últimos 8 dígitos
same => n,Set( TEMP =${EXTEN:1})
same => n,Set( ULT8 =${TEMP:-8})
; Evitar desbordes: funciona aunque la longitud sea menor
same => n,Set( SEG =${EXTEN:50:10}) ; devuelve vacío si no alcanza
GotoIf(condición?etiqueta_ok:etiqueta_ko)
ExecIf(condición?aplicación(args))
exten => 100,1,Set( CONTADOR =5)
same => n(bucle),NoOp(El contador es ${CONTADOR})
same => n,Set( CONTADOR =$[${CONTADOR} - 1])
same => n,GotoIf($[${CONTADOR} > 0]?bucle)
same => n,Playback(blastoff)
; Ejecutar acción condicional simple
same => n,ExecIf($[ "${CALLERID(num)}" = "12345" ]?Playback(welcome-vip-customer))
Gosub(contexto,exten,prio(args)) permite subrutinas claras, eficientes y con buen manejo de variables. Macro() está deprecado y no se recomienda.
exten => _100X,1,Dial(PJSIP/${EXTEN},20)
same => n,Gosub(sub-voicemail,s,1(${EXTEN},${DIALSTATUS}))
exten => s,1,NoOp(Subrutina de Voicemail para Buzón ${ARG1} con estado ${ARG2})
same => n,GotoIf($[ "${ARG2}" = "BUSY" ]?busy)
same => n,GotoIf($[ "${ARG2}" = "NOANSWER" ]?unavail)
same => n(busy),Voicemail(${ARG1},b)
same => n(unavail),Voicemail(${ARG1},u)
Modulariza: usa #include para separar anexos, salidas, funciones, etc.
DRY: convierte lógica repetida en subrutinas Gosub.
Variables: centraliza valores que cambian (troncales, timeouts) en [globals].
Comentarios y trazas: documenta el porqué y usa NoOp() generosamente.
🚀 Taller 5 - CallerID Dinámico con AGI y Python
¿Listos para llevar el dialplan a producción? En el próximo taller, desacoplaremos lógica usando AGI en Python para ruletear dinámicamente el CallerID de salida y modularizaremos el dialplan con #include.
👉 Ir al Taller 5: CallerID Dinámico con AGI y Python