Saltearse al contenido

Presentación 5: Dialplan Avanzado

El arte de escribir lógica limpia, modular y mantenible en Asterisk 22

✍️ El Arte de Escribir Lógica Limpia y Mantenible

Sección titulada «✍️ El Arte de Escribir Lógica Limpia y Mantenible»

🔄 De Comandos a Lógica: El Salto a un Dialplan Profesional

Sección titulada «🔄 De Comandos a Lógica: El Salto a un Dialplan Profesional»

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,3,Hangup()
; Forma moderna y limpia
exten => 1001,1,NoOp(Llamando)
same => n,Dial(PJSIP/1001)
same => n,Hangup()

  • 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)
same => n,Hangup()

  • 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.
[from-trunk]
exten => s,1,NoOp(Inicio de llamada entrante)
same => n,Dial(PJSIP/1001,30)
same => n,Hangup()
exten => i,1,Playback(invalid)
same => n,Hangup()
exten => t,1,Playback(vm-goodbye)
same => n,Hangup()
exten => h,1,NoOp(Canal colgado; guardando CDR personalizado)

#️⃣ Patrones del plan de marcado (pattern matching)

Sección titulada «#️⃣ Patrones del plan de marcado (pattern matching)»

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)

🧠 La Herramienta Más Poderosa: Las Variables

Sección titulada «🧠 La Herramienta Más Poderosa: Las Variables»
  • 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})

Las expresiones dentro de $[...] permiten operaciones aritméticas y lógicas. Recuerda entrecomillar strings.

  • Aritmética: + - * / %
  • Comparación: = != > >= < <=
  • Lógicos: && || !
; Incremento de contador
same => n,Set(CNT=0)
same => n,Set(CNT=$[${CNT} + 1])
; Comparación segura de strings
same => n,GotoIf($["${CALLERID(num)}" = "12345"]?vip,1)

🧮 Funciones útiles del dialplan (strings y listas)

Sección titulada «🧮 Funciones útiles del dialplan (strings y listas)»
  • 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
; Longitud y validación
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)})
; Dejar solo dígitos
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)


✂️ Manipulación de Datos: Corte y Formateo de Cadenas

Sección titulada «✂️ Manipulación de Datos: Corte y Formateo de Cadenas»

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

🔀 Tomando Decisiones: Lógica Condicional

Sección titulada «🔀 Tomando Decisiones: Lógica Condicional»
  • 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)
same => n,Hangup()
; Ejecutar acción condicional simple
same => n,ExecIf($["${CALLERID(num)}" = "12345"]?Playback(welcome-vip-customer))

🧰 Reutilizando Código: Gosub() vs Macro() (deprecado)

Sección titulada «🧰 Reutilizando Código: Gosub() vs Macro() (deprecado)»

Gosub(contexto,exten,prio(args)) permite subrutinas claras, eficientes y con buen manejo de variables. Macro() está deprecado y no se recomienda.

[anexos-internos]
exten => _100X,1,Dial(PJSIP/${EXTEN},20)
same => n,Gosub(sub-voicemail,s,1(${EXTEN},${DIALSTATUS}))
same => n,Hangup()
; Subrutina de Voicemail
[sub-voicemail]
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,Return()
same => n(busy),Voicemail(${ARG1},b)
same => n,Return()
same => n(unavail),Voicemail(${ARG1},u)
same => n,Return()

🧼 El Arte de la Limpieza: Buenas Prácticas

Sección titulada «🧼 El Arte de la Limpieza: Buenas Prácticas»
  • 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