Qué hace este dialplan
La mayoría de los ejemplos de menús de Asterisk se quedan en "oprima 1 para ventas". Un negocio necesita el resto: un segundo idioma, horarios de atención en la zona horaria correcta, feriados que cambian de fecha cada año, teléfonos de escritorio que suenan juntos, un celular que suena después sin que el buzón de voz de la operadora se quede con la llamada, mensajes de voz que llegan por correo electrónico y una forma de probar todo sin esperar a Navidad. Esta guía lo construye contexto por contexto y al final entrega el archivo completo.
El flujo de la llamada, en términos sencillos:
- La llamada llega desde la troncal SIP y se contesta. Quien llama desde un código de área de Quebec oye primero el francés; todos los demás oyen primero el inglés, con el otro idioma a una tecla de distancia (8 para francés, 9 para inglés).
- El dialplan revisa si hay un feriado o un cierre puntual y luego el horario de atención. Quien llama fuera de horario oye un mensaje de cerrado con una opción de urgencia; quien llama en horario oye el menú.
- El menú ofrece clientes nuevos (1), trabajos en curso (2), un submenú de cuentas (3), horario y dirección (4), la oficina (0) y tres extensiones directas (601 a 603). Tres intentos inválidos o en silencio llevan a quien llama con una persona, nunca a una llamada cortada.
- Cada destino hace sonar teléfonos de escritorio en conjunto, luego un celular por la troncal con un filtro de "oprima 1 para aceptar" y luego el buzón de voz, que se envía por correo electrónico con la grabación adjunta.
Cada opción de aplicación y cada valor predeterminado que se menciona aquí se verificó hoy en docs.asterisk.org, cuyas páginas de referencia se generan a partir de la rama de Asterisk 23,2 y en el código fuente de Asterisk en GitHub siempre que decimos "el código fuente". La página de versiones del proyecto indica actualmente que 22 (soporte a largo plazo, LTS) y 23 tienen soporte completo y que 24 es una versión preliminar.1 Todos los números de teléfono, PIN y direcciones de correo electrónico de los archivos son ficticios.
Cuatro comportamientos que las páginas de referencia no mencionan, todos leídos en el código fuente: un rango de GotoIfTime incluye su último minuto; TESTTIME se ignora en los includes por horario; un aviso en francés que falta se sustituye por el de inglés; y un aviso de menú que falta no termina la llamada, diga lo que diga la página de BackGround.
Contextos, extensiones y prioridades
El dialplan vive en extensions.conf y se divide en contextos, encabezados de sección entre corchetes. La documentación los describe como la forma de "enforce security boundaries between the various parts of our dialplan" (establecer límites de seguridad entre las distintas partes del dialplan).7 Esa es la principal herramienta de diseño aquí: las llamadas de la troncal solo pueden llegar a los contextos de menú, y nada de lo que oprima quien llama puede llegar a un contexto que marque hacia afuera con dígitos elegidos por esa persona.
Dentro de un contexto, cada línea es exten => número,prioridad,aplicación(parámetros). Las prioridades se ejecutan en orden y un hueco detiene la llamada: en el ejemplo de la propia documentación, con prioridades 1, 2 y 4, "Asterisk would execute priorities one and two, but would then terminate the call, because it couldn't find priority number three" (Asterisk ejecutaría las prioridades uno y dos, pero luego terminaría la llamada porque no encontraría la prioridad tres). Escribir n ("next", siguiente) en lugar de un número evita ese problema, same => evita repetir la extensión y una etiqueta como n(menu) nombra una prioridad a la que se puede volver.7
Cuando llegan dígitos, Asterisk busca primero las extensiones explícitas, luego los patrones, luego los contextos incluidos y por último los switches.7 Los patrones empiezan con un guion bajo: X es cualquier dígito, Z es de 1 a 9, N es de 2 a 9, los corchetes forman un conjunto, . coincide con uno o más caracteres restantes y ! con cero o más. Gana el patrón más específico.8 Dos advertencias de la misma página dan forma a este archivo. Primero, los comodines "match more than just digits" (coinciden con algo más que dígitos) y "a malicious caller might try to use these wildcards to bypass security boundaries" (quien llama con malas intenciones podría usar esos comodines para saltarse los límites de seguridad), por lo que las extensiones directas de abajo se enumeran una por una y no como patrón. Segundo, si una extensión se queda sin prioridades, Asterisk puede pasar a la misma prioridad de un patrón menos específico; en el ejemplo de la página, la extensión 6410 dice "nueve ocho siete" y luego "cinco cuatro tres dos uno", y se recomienda terminar las extensiones con Hangup().8 Todas las opciones de menú de aquí lo hacen.
El archivo empieza con las secciones [general] y [globals]. Con autofallthrough en su valor predeterminado, una extensión que se queda sin nada que hacer termina "with BUSY, CONGESTION or HANGUP depending on Asterisk's best guess" (con BUSY, CONGESTION o HANGUP según la mejor estimación de Asterisk). clearglobalvars=yes importa más de lo que parece: sin esa opción, "global variables will persist through reloads, and even if deleted" (las variables globales persisten tras las recargas, incluso si se borran) del archivo, de modo que cambiar el celular de guardia y recargar seguiría haciendo sonar el anterior.9 Las variables globales se declaran en [globals] y se leen como cualquier otra variable.10
; extensions.conf: a bilingual small-business auto attendant.
; Written against the Asterisk 22 LTS and 23 documentation. Every number is fictional.
[general]
static=yes
writeprotect=no
clearglobalvars=yes ; edits to [globals] take effect on "dialplan reload"
[globals]
BIZ_TZ=America/Toronto ; zoneinfo name used by every time test below
BIZ_DID=+15145550100 ; the number customers dial
TRUNK=mytrunk ; name of the PJSIP endpoint for the SIP trunk
OFFICE=PJSIP/101&PJSIP/102 ; desk phones that ring together
SERVICE_DESK=PJSIP/103
ACCOUNTS_DESK=PJSIP/104
OWNER_MOBILE=+15145550143
ONCALL_MOBILE=+15145550142 ; whoever carries the after-hours phone this week
Nuestra opinión: ponga cada número de teléfono y cada dispositivo en [globals]. Quien cambie el celular de guardia de esta semana debe editar una sola línea, no buscar entre los menús.
Adónde llegan las llamadas de la troncal
; ---- 1. Where calls from the trunk land (the endpoint's context=from-trunk) ----
[from-trunk]
; The extension is the user part of the INVITE's request URI. Providers format it
; differently (+15145550100, 15145550100, 5145550100): check once with
; "pjsip set logger on" and keep only the form yours sends.
exten => +15145550100,1,Goto(ivr-entry,s,1)
exten => 15145550100,1,Goto(ivr-entry,s,1)
La opción context de un endpoint PJSIP es el "Dialplan context for inbound sessions" (contexto del dialplan para las sesiones entrantes) y su valor predeterminado es default.11 Defínala de forma explícita en el endpoint de la troncal (context=from-trunk) y no deje en un contexto llamado default nada que pueda hacer una llamada saliente: un endpoint que olvidó configurar termina allí.
La extensión que Asterisk busca es la parte de usuario del request URI del INVITE; si está vacía, chan_pjsip usa s. Si la extensión no existe en el contexto del endpoint, la llamada no se enruta a ningún otro lugar: el código fuente registra que fue "rejected because extension not found in context" (rechazada porque la extensión no se encontró en el contexto).12 Cada proveedor escribe el número de forma distinta, así que mire un INVITE real (vea la sección de pruebas) y conserve solo el formato que envía el suyo.
Los menús empiezan en la extensión s. La documentación aclara que s no es una extensión "catch-all" (que atrape todo), solo el lugar donde empiezan las llamadas analógicas y los macros, y que resulta cómoda justamente porque "can't be easily dialed" (no se puede marcar fácilmente) desde BackGround() o WaitExten().13 Quien llama no puede volver por accidente al inicio de un menú oprimiendo teclas.
Contestar, elegir el idioma y revisar el calendario
; ---- 2. Entry: answer, choose the first language, check the calendar ----
[ivr-entry]
exten => s,1,Answer()
same => n,Set(CIDNUM=${CALLERID(num)})
same => n,Set(NPA=${CIDNUM:-10:3}) ; area code from the last 10 digits
same => n,Set(CHANNEL(language)=${IF(${DIALPLAN_EXISTS(quebec-npa,${NPA},1)}?fr:en)})
same => n,Verbose(2,Call from ${CIDNUM}, area code ${NPA}, language ${CHANNEL(language)})
same => n,Gosub(sub-closed-today,s,1)
same => n,GotoIf($[${GOSUB_RETVAL} = 1]?closed,s,1)
same => n,GotoIfTime(08:00-16:59,mon-fri,*,*,${BIZ_TZ}?ivr-main,s,1)
same => n,GotoIfTime(09:00-11:59,sat,*,*,${BIZ_TZ}?ivr-main,s,1)
same => n,Goto(closed,s,1)
; A lookup table, not a menu: nothing includes this context, so no caller can reach it.
; Quebec area codes from the NANPA NPA report of 26 Oct 2026.
[quebec-npa]
exten => 263,1,NoOp()
exten => 273,1,NoOp() ; assigned; NANPA lists an in-service date of 27 Feb 2027
exten => 354,1,NoOp()
exten => 367,1,NoOp()
exten => 418,1,NoOp()
exten => 438,1,NoOp()
exten => 450,1,NoOp()
exten => 468,1,NoOp()
exten => 514,1,NoOp()
exten => 579,1,NoOp()
exten => 581,1,NoOp()
exten => 819,1,NoOp()
exten => 873,1,NoOp()
Answer(). Contestar primero deja el canal activo antes de que ocurra cualquier otra cosa. "By default, Asterisk will wait for media for up to 500 ms" (por omisión, Asterisk espera medios hasta 500 ms), y la opción i contesta sin esperar.14 BackGround() contestaría la llamada de todos modos, a menos que se use su opción n,2 pero un Answer() explícito mantiene claro el orden de los eventos cuando se lee un rastro.
El primer idioma. ${CIDNUM:-10:3} toma los últimos diez dígitos y luego los primeros tres de esos, de modo que +15145550188 y 5145550188 dan ambos 514: un desplazamiento negativo cuenta desde el final y la longitud limita el resultado.15 DIALPLAN_EXISTS() devuelve 1 si existen el contexto, la extensión y la prioridad, y 0 si no,16 IF() elige un valor según eso,17 y CHANNEL(language) es de lectura y escritura: es el "language for sounds played" (idioma de los sonidos que se reproducen).18 Un número anónimo o demasiado corto simplemente no pasa la búsqueda y recibe inglés.
Una tabla de consulta como contexto. [quebec-npa] nunca se incluye ni se salta a él, así que nadie que llame puede llegar allí; existe solo para consultarlo. Los códigos provienen del informe de códigos de área de NANPA con fecha del 26 de octubre de 2026, que enumera doce códigos de Quebec en servicio y un decimotercero, el 273, asignado con fecha de entrada en servicio del 27 de febrero de 2027.19 Ya está en la tabla, de modo que no hace falta editar el dialplan en esa fecha. Nuestra página de códigos de área de Quebec mantiene la misma lista.
La regla del código de área sirve para un negocio fuera de Quebec con clientes en Quebec. Para un negocio en Quebec, la Office québécois de la langue française indica que, según la Carta de la lengua francesa, los mensajes de bienvenida de un negocio deben estar en francés y que, cuando se usa otro idioma, el francés debe tener prioridad; su ejemplo para una línea central de atención al cliente empieza en francés, da una breve instrucción en inglés y vuelve al francés.20 En ese caso, defina CHANNEL(language)=fr para todas las personas que llaman y conserve la opción en inglés. Esta es la orientación publicada por el organismo regulador, no asesoría legal.
El calendario. Los cierres se revisan antes que el horario, porque un feriado que cae en martes sigue siendo martes. La prueba de cierre es una subrutina que termina con Return(1) o Return(0); el valor regresa en GOSUB_RETVAL.21 El horario de atención son dos líneas GotoIfTime; si ninguna coincide, la llamada pasa a closed. Ambas se explican en la sección de horarios más abajo.
El menú: BackGround, WaitExten, i, t y reintentos
; ---- 3. The main menu (open hours) ----
[ivr-main]
exten => s,1,Set(TRIES=0)
same => n,Set(TIMEOUT(digit)=3) ; pause allowed between digits of 601
same => n(menu),Background(custom/main-menu) ; sounds/en/custom/ or sounds/fr/custom/
same => n,WaitExten(6) ; nothing after this, so a timeout goes to t
exten => 1,1,Verbose(2,Menu 1: new customer)
same => n,Gosub(sub-ring,s,1(${OFFICE},20,${OWNER_MOBILE},20,100))
same => n,Hangup()
exten => 2,1,Verbose(2,Menu 2: existing job)
same => n,Gosub(sub-ring,s,1(${SERVICE_DESK},20,${ONCALL_MOBILE},25,200))
same => n,Hangup()
exten => 3,1,Goto(ivr-accounts,s,1) ; a sub-menu
exten => 4,1,Background(custom/hours-and-address) ; a key press skips ahead
same => n,Goto(s,menu)
exten => 0,1,Gosub(sub-ring,s,1(${OFFICE},25,,,100))
same => n,Hangup()
exten => 8,1,Set(CHANNEL(language)=fr) ; "Pour le service en français, faites le 8"
same => n,Goto(s,menu)
exten => 9,1,Set(CHANNEL(language)=en) ; "For service in English, press 9"
same => n,Goto(s,menu)
; Dial-by-extension, listed one by one: no wildcard pattern a caller can feed.
exten => 601,1,Gosub(sub-ring,s,1(PJSIP/101,20,,,100))
same => n,Hangup()
exten => 602,1,Gosub(sub-ring,s,1(PJSIP/102,20,,,100))
same => n,Hangup()
exten => 603,1,Gosub(sub-ring,s,1(PJSIP/103,20,,,200))
same => n,Hangup()
exten => i,1,GotoIf($[${INC(TRIES)} >= 3]?giveup,1)
same => n,Playback(option-is-invalid)
same => n,Goto(s,menu)
exten => t,1,GotoIf($[${INC(TRIES)} >= 3]?giveup,1)
same => n,Playback(are-you-still-there)
same => n,Goto(s,menu)
exten => giveup,1,Verbose(2,No valid choice after ${TRIES} attempts)
same => n,Gosub(sub-ring,s,1(${OFFICE},20,,,100))
same => n,Hangup()
Por qué BackGround y no Playback. BackGround() reproduce sus archivos "while waiting for an extension to be dialed by the calling channel" (mientras espera que el canal que llama marque una extensión), y un dígito oprimido a media frase lleva a quien llama directo a esa extensión.2 Playback() es el que no se puede interrumpir: su propia referencia remite a BackGround "for playing sound files that are interruptible" (para reproducir archivos de sonido que se pueden interrumpir).22 Un menú construido con Playback obliga a cada persona que llama de nuevo a escuchar la grabación completa. Usamos Playback solo para avisos cortos (los de entrada no válida y "¿sigue ahí?"), y la opción 4 usa BackGround por la misma razón que el menú.
Conviene conocer las opciones de BackGround: s omite la reproducción si el canal no está contestado, n reproduce sin contestar, p hace el aviso ininterrumpible y m significa "only break if a digit hit matches a one digit extension in the destination context" (interrumpir solo si el dígito oprimido coincide con una extensión de un dígito en el contexto de destino).2 No usamos m: impediría que el 6 de la extensión 601 interrumpa el aviso.
WaitExten. BackGround deja de escuchar cuando termina el aviso, y la documentación dice que WaitExten() "should be used" (debe usarse) para seguir esperando.2 Su argumento está en segundos y puede ser fraccionario; su opción m(class) reproduce música en espera y d un tono de marcar mientras espera.23 Sin argumento usa el tiempo de espera de respuesta,24 que por omisión es de 10 segundos.25
Qué pasa al agotarse el tiempo depende de lo que siga a WaitExten. El código fuente revisa, en orden: si la extensión tiene otra prioridad, registra "Timeout ... continuing" y la ejecuta; si no, pasa a t; si no, y existe una extensión e, genera una excepción RESPONSETIMEOUT; si no, registra una advertencia y cuelga.3 Por eso WaitExten es la última prioridad de cada menú aquí: si pone algo después, t nunca se ejecuta.
i y t. Cuando los dígitos ingresados durante BackGround o WaitExten no coinciden con nada en el contexto, la llamada va a i; cuando quien llama no ingresa nada y no quedan más prioridades, va a t. e puede atrapar i, t y T en un solo lugar.13 El tutorial de la documentación muestra el rastro verbose que verá: "Invalid extension '3' in context 'demo-menu'" y luego las prioridades de i.26 Los dos avisos usados aquí, option-is-invalid y are-you-still-there, no están en el conjunto básico de sonidos: el tutorial indica que los avisos de sus menús provienen de los paquetes de sonidos adicionales,29 y el paquete adicional en francés contiene ambos.30
Tiempos entre dígitos. El tiempo de espera de dígitos es "the maximum amount of time permitted between digits" (el tiempo máximo permitido entre dígitos), 5 segundos por omisión, y "if an extension typed in is valid, it will not have to timeout to be tested" (si la extensión ingresada es válida, no hay que esperar el tiempo de espera para probarla).25 El código fuente añade un detalle que decide cómo se numera un menú: cuando un solo dígito oprimido durante BackGround es una extensión completa y ninguna extensión más larga podría empezar con él, Asterisk salta de inmediato; si una más larga pudiera empezar con él, espera.3 Con las extensiones 601 a 603, oprimir 6 espera más dígitos, que es lo que queremos. Si hubiéramos usado extensiones 1XX, cada persona que oprimiera 1 esperaría el tiempo de espera de dígitos antes de que ocurriera algo. Esa es también la razón por la que TIMEOUT(digit) se baja a 3 segundos.
El contador de reintentos. INC() "increments the value of a variable, while returning the updated value" (incrementa el valor de una variable y devuelve el valor actualizado); su referencia advierte que INC(${MyVAR}) "is wrong, as INC expects the variable name" (es incorrecto, porque INC espera el nombre de la variable).27 TRIES se define en la prioridad 1, antes de la etiqueta menu, de modo que volver al ciclo no lo reinicia, y i y t lo comparten. En el tercer fallo, quien llama pasa a giveup, una extensión con nombre que ningún teclado puede marcar, que hace sonar la oficina y luego toma un mensaje. Nuestra opinión: nunca cuelgue a una persona confundida. Tres intentos y una persona es el valor predeterminado correcto para un negocio pequeño.
Un motivo más para probar cada Goto: si un destino no existe, Asterisk prueba i en el contexto actual, luego h, y si no cuelga; si salta a un contexto que no existe, no se puede encontrar ninguno de los dos.28
Horario de atención, zonas horarias y feriados
GotoIfTime. La sintaxis es GotoIfTime(times,weekdays,mdays,months,[timezone]?labeliftrue:labeliffalse); si se omite la etiqueta elegida, la ejecución simplemente continúa.31 El analizador del código fuente acepta nombres o números para días y meses, rangos que dan la vuelta (fri-mon) y varios rangos unidos con &, y guarda un bit por minuto.4 El comentario del archivo de muestra según el cual los minutos son "fine-grained only down to the closest even minute" (precisos solo hasta el minuto par más cercano) está desactualizado: el código dice "1-minute per bit" (un minuto por bit).94
Después de llenar un rango, el código fuente marca también el minuto final ("Do the last one"). Así que 08:00-17:00 todavía coincide a las 17:00:59. Para dejar de atender el menú a las cinco en punto, escriba 08:00-16:59, como hace este archivo.4
Zonas horarias. El quinto campo es un nombre zoneinfo como America/Toronto; el código fuente lo pasa a la conversión de hora y, sin quinto campo, la hora se evalúa en la zona del propio servidor.4 Nuestra opinión: páselo siempre, desde una sola variable global. Los servidores suelen estar configurados en UTC, y un negocio con oficinas en dos zonas lo necesita de todos modos.
Por qué no includes por horario. Asterisk también puede asociar un horario a un include: include => daytime,9:00-17:00,mon-fri,*,*, con una zona horaria opcional como quinto campo.9 Funciona, pero los includes se buscan después de las extensiones propias del contexto,7 la documentación advierte contra los "overlapping patterns or duplicate extensions among included contexts" (patrones superpuestos o extensiones duplicadas entre contextos incluidos),32 y hay un problema práctico: no se pueden probar. La función TESTTIME() fija una hora alternativa "to test dialplan timing conditions at times other than the current time" (para probar condiciones de horario del dialplan en horas distintas de la actual),33 y GotoIfTime la lee,3 pero un include por horario se compara solo con el reloj actual.5 Mantenemos toda la lógica de horarios en GotoIfTime para poder probarla.
La referencia de TESTTIME dice que la fecha está "in ISO 8601 format"; el analizador del código fuente lee YYYY/MM/DD HH:MM:SS seguido de un nombre de zona opcional, con diagonales.3334 Las extensiones de prueba del contexto interno usan esa forma.
; ---- 8. Holidays and one-off closures. Returns 1 if closed all day today. ----
[sub-closed-today]
exten => s,1,Set(TODAY=${STRFTIME(${EPOCH},${BIZ_TZ},%Y-%m-%d)})
same => n,GotoIf(${DB_EXISTS(closed/${TODAY})}?yes) ; CLI: database put closed 2026-12-24 1
; Fixed dates recur every year: the time specification has no year field.
same => n,GotoIfTime(*,*,1,jan,${BIZ_TZ}?yes) ; New Year's Day
same => n,GotoIfTime(*,*,25,dec,${BIZ_TZ}?yes) ; Christmas Day
same => n,GotoIfTime(*,mon,1-7,sep,${BIZ_TZ}?yes) ; Labour Day (CA) and Labor Day (US)
; Canada (federal list; add your province's days)
same => n,GotoIfTime(*,mon,18-24,may,${BIZ_TZ}?yes) ; Victoria Day: Monday before 25 May
same => n,GotoIfTime(*,*,1,jul,${BIZ_TZ}?yes) ; Canada Day
same => n,GotoIfTime(*,mon,2,jul,${BIZ_TZ}?yes) ; 2 July when 1 July is a Sunday
same => n,GotoIfTime(*,mon,8-14,oct,${BIZ_TZ}?yes) ; Thanksgiving: second Monday
same => n,GotoIfTime(*,*,26,dec,${BIZ_TZ}?yes) ; Boxing Day
; United States (federal list; delete what you do not observe)
same => n,GotoIfTime(*,mon,25-31,may,${BIZ_TZ}?yes) ; Memorial Day: last Monday
same => n,GotoIfTime(*,*,4,jul,${BIZ_TZ}?yes) ; Independence Day
same => n,GotoIfTime(*,thu,22-28,nov,${BIZ_TZ}?yes) ; Thanksgiving: fourth Thursday
same => n,Return(0)
same => n(yes),Return(1)
Feriados fijos y móviles. Una especificación de tiempo tiene cuatro campos y una zona opcional, y no tiene año,4 de modo que una fecha como el 25 de diciembre se repite cada año. Un feriado definido como "el enésimo día de la semana" se convierte en un día de la semana más una ventana de siete días, porque el mes, el día del mes y el día de la semana deben coincidir todos.4 En Estados Unidos, el Día de los Caídos (Memorial Day) es "the last Monday in May" (el último lunes de mayo), el Día del Trabajo (Labor Day) "the first Monday in September" (el primer lunes de septiembre) y el Día de Acción de Gracias "the fourth Thursday in November" (el cuarto jueves de noviembre).35 En Canadá, el Día de la Reina Victoria es "the first Monday immediately preceding May 25" (el primer lunes inmediatamente anterior al 25 de mayo) y el Día de Canadá es el 1 de julio, salvo que "When July 1 is a Sunday, July 2 is a legal holiday" (cuando el 1 de julio es domingo, el 2 de julio es feriado legal); esa regla necesita una línea, el lunes 2 de julio, porque el 2 de julio es lunes exactamente cuando el 1 de julio es domingo.37 Las leyes federales que leímos no fijan la fecha del Día de Acción de Gracias canadiense; la lista de la Canada Revenue Agency indica el lunes 12 de octubre para 2026, el segundo lunes.3638
Las listas de aquí son un punto de partida, no una recomendación sobre qué días cerrar. Los feriados generales del Código del Trabajo de Canadá incluyen también el Viernes Santo, el Día Nacional de la Verdad y la Reconciliación (30 de septiembre) y el Día del Recuerdo,36 la lista federal de Estados Unidos tiene cinco días más,35 y las provincias y los estados añaden los suyos.
Feriados que se mueven y cierres puntuales. El Viernes Santo sigue a la Pascua (3 de abril en 2026, según la CRA),38 así que no se puede escribir como regla recurrente. La primera línea de la subrutina se ocupa de él y de cada tormenta de nieve: STRFTIME(epoch,timezone,format) da formato a la fecha de hoy en la zona del negocio,39 ${EPOCH} es la hora Unix actual,40 y DB_EXISTS() devuelve 1 si una clave existe en la base de datos de Asterisk.41 Quien administra el sistema cierra un día desde la CLI con database put closed 2026-12-24 1 y lo reabre con database del closed 2026-12-24.42 Sin recargar, sin editar archivos.
Grupos de timbrado y respaldo secuencial
; ---- 6. Ring desks together, then one mobile, then voicemail ----
; Gosub(sub-ring,s,1(desks,seconds,mobile,seconds,mailbox)); leave a field empty to skip it.
[sub-ring]
exten => s,1,Set(DESKSTATUS=NONE)
same => n,GotoIf($["${ARG1}" = ""]?mobile)
same => n,Dial(${ARG1},${ARG2}) ; without g, returns only if nobody connected
same => n,Set(DESKSTATUS=${DIALSTATUS})
same => n,Verbose(2,Desk phones: ${DIALSTATUS})
same => n(mobile),GotoIf($["${ARG3}" = ""]?vm)
same => n,Dial(PJSIP/${ARG3}@${TRUNK},${ARG4},f(${BIZ_DID})U(sub-screen^${CIDNUM}))
same => n,Verbose(2,Mobile leg: ${DIALSTATUS}) ; still ANSWER after a refused screen
same => n(vm),VoiceMail(${ARG5}@default,${IF($["${DESKSTATUS}" = "BUSY"]?b:u)})
same => n,Return()
Timbrado simultáneo. Dial() acepta dispositivos unidos con &. Todos suenan; "As soon as one of the requested channels answers" (en cuanto contesta uno de los canales solicitados), se conecta a quien llama y "All other channels that were requested will then be hung up" (todos los demás canales solicitados se cuelgan).43 (Copie con cuidado el ejemplo en paralelo de la propia referencia: dice Dial(PJSIP/alice&PJIP/bob,45), con PJSIP mal escrito.43) Para marcar un endpoint PJSIP por nombre, PJSIP/101 usa el primer contacto del AOR del mismo nombre; PJSIP_DIAL_CONTACTS() hace sonar todos los contactos; y PJSIP/número@mytrunk envía el número al contacto de la troncal.45
Dé siempre un tiempo de espera. Si no lo hace, la referencia dice que "defaults to 136 years" (por omisión son 136 años).43 La opción r indica el timbre a quien llama "even if the called party isn't actually ringing" (aunque la parte llamada en realidad no esté sonando), y m(class) reproduce música en espera hasta que alguien contesta.43
Respaldo secuencial y para qué sirve DIALSTATUS. Dial "will continue if no requested channels can be called, or if the timeout expires" (continúa si no se puede llamar a ningún canal solicitado o si vence el tiempo de espera). Si alguien contesta, la llamada se conecta, y solo la opción g haría volver a quien llama al dialplan cuando el otro lado cuelga.43 Así que en esta subrutina, llegar a la línea posterior a un Dial ya significa que nadie se conectó, y la siguiente etapa puede ejecutarse sin probar nada. DIALSTATUS registra el motivo: CHANUNAVAIL (por ejemplo, el endpoint "is not registered", no está registrado), CONGESTION, NOANSWER, BUSY (la referencia señala que algunos teléfonos envían 486 Busy cuando No Molestar está activado), ANSWER, CANCEL y algunos valores de privacidad.43 Si los teléfonos de la oficina están desconectados, el primer Dial regresa de inmediato con CHANUNAVAIL y el celular suena enseguida. Conservamos el resultado de los escritorios para elegir el saludo del buzón, el mismo patrón de ocupado o no disponible que usa el tutorial de la documentación.24
Por qué el celular es una segunda etapa. Dial cuelga los demás tramos en cuanto uno contesta,43 y el código fuente los cuelga antes de ejecutar la subrutina de filtro que se describe a continuación.44 Si un celular sonara junto con los teléfonos de escritorio y el buzón de voz de su operadora contestara, los teléfonos de escritorio dejarían de sonar antes de que el filtro pudiera rechazar ese buzón. Hacer sonar primero los escritorios y después el celular evita el problema.
Desvío a celulares: identificador de llamadas y filtro
; ---- 7. Screen the mobile leg. Runs on the mobile's channel after it answers. ----
[sub-screen]
exten => s,1,Set(CHANNEL(language)=en) ; the employee's language, not the caller's
same => n,Playback(custom/screen-intro) ; "Business call from"
same => n,SayDigits(${ARG1:-10})
same => n,Read(ACCEPT,custom/screen-press-1,1,,2,4)
same => n,GotoIf($["${ACCEPT}" = "1"]?accept)
same => n,Set(GOSUB_RESULT=CONTINUE) ; drop this leg; the caller goes on to voicemail
same => n(accept),Return()
Qué número ve el celular. Para cada tramo nuevo, Dial copia la identidad de quien llama en la información de línea conectada (connected line) de ese tramo, a menos que la opción f fuerce un valor.44 chan_pjsip construye el encabezado From saliente a partir de esa identidad de línea conectada, y el ajuste from_user de un endpoint reemplaza entonces la parte de usuario (y elimina el nombre para mostrar) haga lo que haga el dialplan.12 La referencia describe from_user como el "Username to use in From header for requests to this endpoint" (nombre de usuario para el encabezado From de las solicitudes a este endpoint).11 Por lo tanto, de forma predeterminada el celular del empleado muestra el número del cliente.
Que su troncal transmita un identificador de llamadas que no sea uno de sus números es política del proveedor; la propia referencia de Dial señala que "some PSTNs do not allow CallerID to be set to anything other than the numbers assigned to you" (algunas redes telefónicas públicas no permiten fijar el CallerID en nada que no sean los números asignados a usted).43 No probamos la política de ningún proveedor para esta guía. El archivo usa f(${BIZ_DID}), que la referencia describe como "force the CallerID sent to x" (forzar el CallerID enviado a x).43 En el código fuente se aplica a todos los tramos, no solo a las llamadas desviadas, y fija la presentación en permitida salvo que la opción u diga otra cosa.44 El celular muestra el número del negocio y el filtro lee en voz alta el del cliente.
Dos ajustes de la troncal interactúan con esto. Si quien llama ocultó su número, chan_pjsip lo escribe en el encabezado From solo cuando el trust_id_outbound del endpoint lo permite,12 que la referencia describe como si Asterisk "will send private identification information to the endpoint" (enviará información privada de identificación al endpoint).11 Y send_pai (enviar un encabezado P-Asserted-Identity) está en no por omisión; algunos proveedores lo piden.11
Filtro con U(). La opción U(x^arg) ejecuta una subrutina en el canal llamado "before connecting to the calling channel" (antes de conectarlo con el canal que llama), empezando en la extensión s, prioridad 1, del contexto indicado; los argumentos se separan con ^. La rutina puede definir GOSUB_RESULT como ABORT, CONGESTION, BUSY, CONTINUE ("Hangup the called party and allow the calling party to continue dialplan execution at the next priority", es decir, cuelga a la parte llamada y permite que quien llama siga en la siguiente prioridad) o GOTO.43 Aquí el empleado oye "Business call from" (llamada de negocio de) y los últimos diez dígitos del cliente; SayDigits() los pronuncia en el idioma del canal.47 Luego Read() espera un dígito, con dos intentos y un tiempo de espera de 4 segundos.46 Una persona oprime 1; un buzón de voz de la operadora no, así que el tramo se descarta y quien llama continúa al buzón de la empresa en lugar del personal del empleado.
El número del cliente se pasa como argumento porque el filtro se ejecuta en el canal del celular, que no ve las variables de quien llama: solo las variables definidas con prefijo _ o __ las heredan los canales creados a partir del actual.48
Cuando el filtro define CONTINUE, DIALSTATUS sigue indicando ANSWER: el código fuente registra ANSWER cuando el tramo contesta y la rama CONTINUE no lo cambia. Un resultado BUSY, en cambio, sí fija DIALSTATUS en BUSY.44 No ramifique con DIALSTATUS = ANSWER después de un Dial filtrado; esta subrutina no lo necesita, por la razón dada en la sección de grupos de timbrado.
Nuestra opinión: mantenga el filtro corto. Quien llama está esperando mientras suena, y un empleado que contesta mientras maneja necesita una instrucción, no tres.
Buzón de voz y buzón de voz por correo electrónico
; voicemail.conf
[general]
format=wav49|wav ; the first format is the one attached to the email
attach=yes
[email protected]
fromstring=Phone system
emailsubject=New voicemail from ${VM_CALLERID} (${VM_DUR})
emailbody=Mailbox ${VM_MAILBOX} (${VM_NAME}) has a new ${VM_DUR} message from ${VM_CALLERID}, left ${VM_DATE}.\n\nThe recording is attached.\n
emaildateformat=%A %d %B %Y, %H:%M
mailcmd=/usr/sbin/sendmail -t ; the built-in default; needs a working mail transfer agent
maxsecs=180
minsecs=3
maxlogins=3
[zonemessages]
eastern=America/Toronto|'vm-received' Q 'digits/at' IMp
[default]
; mailbox => PIN,name,email(s) separated by |,pager email,options separated by |
100 => 4826,Office,[email protected]|[email protected],,tz=eastern
200 => 7391,Service,[email protected],,tz=eastern
300 => 5150,Accounts,[email protected],,tz=eastern|delete=yes
VoiceMail(). La sintaxis es VoiceMail(mailbox@context,options). u reproduce el saludo de no disponible, b el de ocupado, s omite las instrucciones y U marca el mensaje como urgente. Durante el saludo, 0 salta a la extensión o y * a la extensión a del contexto actual, y el resultado queda en VMSTATUS (SUCCESS, USEREXIT o FAILED). Una línea merece atención: "Dialplan execution will stop if the specified mailbox does not exist." (la ejecución del dialplan se detendrá si el buzón indicado no existe).49 Un error de dedo en el número de un buzón es una llamada perdida, así que pruebe todos. El archivo de muestra añade que oprimir 0 para una operadora requiere operator=yes, que está desactivado por omisión, y una extensión o.52 Nosotros lo dejamos desactivado.
VoiceMailMain() permite al dueño de un buzón escuchar mensajes y grabar saludos; sin buzón lo pide, y sin contexto usa default.50 Su opción s omite el PIN, y la documentación aconseja "exercise extreme caution" (extremar la precaución) con ella.51 Aquí solo se alcanza desde los teléfonos del personal, en *98.
La línea de buzón es <mailbox>=<password>,<name>,<email>,<pager_email>,<options>; varias direcciones de correo se separan con |, y un PIN con prefijo de signo menos no puede ser cambiado por el usuario.52 Asterisk reescribe este archivo por sí mismo cuando un usuario cambia su PIN,52 así que manténgalo fuera de cualquier herramienta de configuración que lo sobrescriba.
Ajustes de correo. attach=yes adjunta la grabación; el formato adjunto es "the first format specified in the format parameter" (el primer formato indicado en el parámetro format), salvo que un buzón defina attachfmt.52 wav49 es GSM en un contenedor WAV y wav es linear firmado en WAV,54 así que el primero es pequeño y el segundo es más grande y sencillo. El archivo de muestra advierte que cambiar la lista de formatos en un sistema que ya contiene mensajes obliga a convertir esos mensajes a mano.52 emailsubject y emailbody aceptan variables como ${VM_NAME}, ${VM_DUR}, ${VM_MAILBOX}, ${VM_CALLERID} y ${VM_DATE}, y el cuerpo se limita a 8,190 caracteres.52 maxmsg es por omisión de 100 mensajes por carpeta y minsecs no tiene mínimo;52 nosotros fijamos tres segundos para que los clics de quien cuelga no lleguen a la bandeja de nadie. tz elige una zona de [zonemessages] para la fecha que se anuncia antes de cada mensaje.52
mailcmd es por omisión /usr/sbin/sendmail -t.52 app_voicemail escribe el mensaje en un archivo temporal y ejecuta el comando en segundo plano, ( mailcmd < tmp ; rm -f tmp ) &,53 de modo que el dialplan nunca sabe si el correo salió. El servidor necesita un agente de transferencia de correo (MTA) funcional, y su registro es donde se ve un fallo. Tenga cuidado con delete=yes, que borra el mensaje de voz del servidor "after notification" (después de la notificación) y está pensado para quien quiere el buzón de voz solo por correo:52 combinado con un envío fallido, el mensaje se pierde. Lo usamos en un buzón para mostrar la opción; actívelo solo cuando la entrega de correo esté monitoreada.
Avisos: archivos, formatos y cómo grabar los suyos
Dónde están los archivos. Los sonidos se leen del subdirectorio sounds del directorio de datos, /var/lib/asterisk por omisión.55 Las aplicaciones reciben nombres de archivo sin extensión y Asterisk elige el archivo.222 Por eso cada conjunto de avisos personalizados de este archivo existe dos veces: sounds/en/custom/main-menu.wav y sounds/fr/custom/main-menu.wav, ambos reproducidos por BackGround(custom/main-menu).
Cómo el idioma elige el archivo. La documentación explica que el idioma del canal se divide en los guiones bajos y se usa para construir nombres de directorio, del más específico al menos específico, hasta llegar al propio sounds.56 El código fuente añade un último paso que la página omite: si no encuentra nada, prueba el idioma predeterminado.6 Ese es en, salvo que asterisk.conf indique otra cosa.57 En la práctica, una persona que llama en francés y llega a un aviso que usted no grabó en francés oye el de inglés en lugar de silencio, lo cual es mejor que nada y aun así es un defecto. Escuche cada aviso en ambos idiomas.
Un aviso que falta no termina la llamada. La referencia de BackGround dice "If one of the requested sound files does not exist, call processing will be terminated." (si uno de los archivos de sonido solicitados no existe, se terminará el procesamiento de la llamada).2 El código fuente hace algo distinto: registra una advertencia, deja de reproducir, define BACKGROUNDSTATUS como FAILED y regresa normalmente, de modo que quien llama cae en WaitExten en silencio.3 En ambos casos la experiencia de quien llama es mala, así que revise BACKGROUNDSTATUS o, mejor aún, verifique los archivos antes de recargar.
Formatos. Cuando existen varios formatos del mismo aviso, Asterisk "will automatically pick the least CPU intensive format that is available" (elegirá automáticamente el formato disponible que menos CPU consuma).58 El WAV linear firmado a 8 kHz es wav, el linear firmado sin procesar a 16 kHz es .sln16, y µ-law y A-law los maneja el módulo de formato PCM; la documentación sugiere sox para producir archivos linear firmado a partir de WAV.54 Para una grabación de estudio, -r fija la tasa de muestreo, -c los canales, -b los bits por muestra, -e signed-integer la codificación y -t raw escribe datos sin encabezado:63
sox studio-take.wav -r 8000 -c 1 -b 16 /var/lib/asterisk/sounds/en/custom/main-menu.wav
sox studio-take.wav -t raw -r 16000 -c 1 -b 16 -e signed-integer /var/lib/asterisk/sounds/en/custom/main-menu.sln16
Nuestra opinión: proporcione el WAV de 8 kHz para cada aviso, añada .sln16 si sus teléfonos usan un códec de banda ancha y, si su troncal es solo G.711 µ-law, una copia .ulaw ahorra una conversión en cada llamada.
Grabar por teléfono. El contexto interno graba avisos con Record(), cuya sintaxis es Record(filename.format,silence,maxduration,options); # termina la grabación, el resultado queda en RECORD_STATUS, y core show file formats enumera los formatos que su compilación puede escribir.64 El tutorial de la propia documentación graba en/custom-menu.gsm y luego reproduce custom-menu,65 y el código fuente crea los directorios de una ruta relativa dentro de sounds/.66 La extensión de grabación está protegida con VMAuthenticate(), que verifica el PIN del buzón indicado en voicemail.conf.67 Nuestra opinión: una grabación por teléfono está bien para un aviso de feriado; grabe el menú principal con un micrófono decente en una sala silenciosa.
Los avisos de serie. Los avisos distribuidos con Asterisk tienen licencia Creative Commons Attribution-Share Alike 3.0.62 La selección de sonidos de la compilación ofrece inglés, francés y otros idiomas en conjuntos básicos y adicionales, y de los paquetes de avisos solo el conjunto básico en inglés en GSM viene activado por omisión,61 así que el francés hay que seleccionarlo o descargarlo: los paquetes básico y adicional en francés se publican en nueve formatos, de alaw a wav.59 El archivo CREDITS del conjunto básico en francés enumera a sus traductores y patrocinadores, entre ellos dos empresas canadienses, y su lista de avisos incluye las instrucciones del buzón de voz ("Veuillez laissez votre message après le bip sonore...").60
El menú bilingüe
El menú es bilingüe sin una sola línea en francés en el dialplan. CHANNEL(language) decide de qué directorio sale cada aviso,18 así que el mismo BackGround(custom/main-menu) reproduce la grabación en inglés, que termina con "Pour le service en français, faites le 8", o la grabación en francés, que termina con "For service in English, press 9". Las teclas 8 y 9 cambian el idioma y repiten el menú. Reservar ambas teclas en ambos idiomas deja ocho opciones, de 0 a 7, que es el mismo esquema que recomienda nuestra guía del menú bilingüe.
Otras formas de definir el idioma: la opción language de un endpoint PJSIP lo fija para todos los canales de ese endpoint,11 lo cual sirve para un número separado en francés en su propio endpoint, y el argumento langoverride de BackGround fuerza un idioma para un solo aviso.2 SayDigits() sigue el idioma del canal,47 y también lo hacen las instrucciones de serie del buzón de voz, que existen en el conjunto básico en francés.60 El saludo del buzón no: es una grabación hecha por el dueño del buzón, así que grábela en ambos idiomas, primero el francés si está en Quebec.20
Nuestra opinión: escriba ambos guiones antes de grabar cualquiera de los dos, mantenga paralelas las etiquetas de las opciones y pida a un hablante nativo de la variedad que usan sus clientes que lea la versión en francés. La página de enrutamiento de llamadas del sitio explica qué idioma debe ir primero.
Nota: este es un ejemplo para quien opera su propio Asterisk. Los menús del servicio de Auto Attendant están disponibles en inglés, francés y español, hasta los tres en una misma línea.
Submenús con Goto, rutinas con Gosub
; ---- 4. A sub-menu: accounts ----
[ivr-accounts]
exten => s,1,Set(TRIES=0)
same => n(menu),Background(custom/accounts-menu)
same => n,WaitExten(6)
exten => 1,1,Gosub(sub-ring,s,1(${ACCOUNTS_DESK},25,,,300))
same => n,Hangup()
exten => 2,1,Playback(custom/payment-options)
same => n,Goto(s,menu)
exten => *,1,Goto(ivr-main,s,menu) ; star: back to the main menu
exten => i,1,GotoIf($[${INC(TRIES)} >= 3]?ivr-main,giveup,1)
same => n,Playback(option-is-invalid)
same => n,Goto(s,menu)
exten => t,1,GotoIf($[${INC(TRIES)} >= 3]?ivr-main,giveup,1)
same => n,Playback(are-you-still-there)
same => n,Goto(s,menu)
Pasar de un menú a otro es un Goto: quien llama no vuelve adonde estaba. La lógica repetida (hacer sonar un equipo, filtrar, revisar feriados) es un Gosub, que "replaces (is recommended in place of, and deprecates) the Macro application" (reemplaza a la aplicación Macro, se recomienda en su lugar y la declara obsoleta), recibe argumentos que aparecen como ${ARG1}, ${ARG2} y así sucesivamente, y debe terminar con Return() si ha de regresar.68 El menú de cuentas tiene sus propios i y t, una extensión * para volver (el menú del día de la propia documentación usa * como nombre de extensión),65 y al tercer fallo reutiliza el giveup del menú principal.
TRIES es una variable de canal, así que acompaña a quien llama de un contexto a otro; el menú de cuentas la reinicia al entrar para que quien necesitó dos intentos en el menú principal no se quede corto en el submenú. Volver con Goto(ivr-main,s,menu) cae después del reinicio del menú principal, así que la cuenta continúa desde ahí.
Nuestra opinión: dos niveles como máximo. Un submenú vale la pena cuando sus opciones, de otro modo, llevarían el menú principal a más de cinco o seis opciones; con más de dos niveles, quien llama deja de escuchar y oprime 0.
El extensions.conf completo
Las piezas anteriores, en orden, más el contexto del personal: llamadas entre escritorios, acceso al buzón de voz, grabación de avisos y las extensiones de prueba que se usan en la siguiente sección.
; ---- 9. Staff phones (their endpoints use context=internal) ----
[internal]
exten => _10X,1,Dial(PJSIP/${EXTEN},30) ; desk to desk
same => n,Hangup()
exten => *98,1,VoiceMailMain(@default) ; asks for mailbox number and PIN
exten => *71,1,Gosub(sub-record,s,1(en/custom/main-menu))
same => n,Hangup()
exten => *72,1,Gosub(sub-record,s,1(fr/custom/main-menu))
same => n,Hangup()
exten => *90,1,Goto(ivr-entry,s,1) ; the menu, as now
exten => *91,1,Set(TESTTIME()=2026/12/25 10:00:00 America/Toronto) ; Christmas morning
same => n,Goto(ivr-entry,s,1)
exten => *92,1,Set(TESTTIME()=2026/10/27 22:00:00 America/Toronto) ; a Tuesday night
same => n,Goto(ivr-entry,s,1)
[sub-record]
exten => s,1,Answer()
same => n,VMAuthenticate(100@default) ; only someone with the office PIN
same => n,Record(${ARG1}.wav,3,120) ; # to stop; 3 s silence or 120 s also stop
same => n,Wait(1)
same => n,Playback(${ARG1})
same => n,Return()
Cada endpoint de un teléfono del personal usa context=internal; el endpoint de la troncal usa context=from-trunk. Si esos teléfonos también hacen llamadas hacia afuera, lea nuestra página sobre llamadas de emergencia antes de escribir esa parte: las reglas de Estados Unidos sobre marcar el 911 desde sistemas multilínea tratan justamente de ese dialplan saliente, que esta guía no cubre. El archivo completo:
; extensions.conf: a bilingual small-business auto attendant.
; Written against the Asterisk 22 LTS and 23 documentation. Every number is fictional.
[general]
static=yes
writeprotect=no
clearglobalvars=yes ; edits to [globals] take effect on "dialplan reload"
[globals]
BIZ_TZ=America/Toronto ; zoneinfo name used by every time test below
BIZ_DID=+15145550100 ; the number customers dial
TRUNK=mytrunk ; name of the PJSIP endpoint for the SIP trunk
OFFICE=PJSIP/101&PJSIP/102 ; desk phones that ring together
SERVICE_DESK=PJSIP/103
ACCOUNTS_DESK=PJSIP/104
OWNER_MOBILE=+15145550143
ONCALL_MOBILE=+15145550142 ; whoever carries the after-hours phone this week
; ---- 1. Where calls from the trunk land (the endpoint's context=from-trunk) ----
[from-trunk]
; The extension is the user part of the INVITE's request URI. Providers format it
; differently (+15145550100, 15145550100, 5145550100): check once with
; "pjsip set logger on" and keep only the form yours sends.
exten => +15145550100,1,Goto(ivr-entry,s,1)
exten => 15145550100,1,Goto(ivr-entry,s,1)
; ---- 2. Entry: answer, choose the first language, check the calendar ----
[ivr-entry]
exten => s,1,Answer()
same => n,Set(CIDNUM=${CALLERID(num)})
same => n,Set(NPA=${CIDNUM:-10:3}) ; area code from the last 10 digits
same => n,Set(CHANNEL(language)=${IF(${DIALPLAN_EXISTS(quebec-npa,${NPA},1)}?fr:en)})
same => n,Verbose(2,Call from ${CIDNUM}, area code ${NPA}, language ${CHANNEL(language)})
same => n,Gosub(sub-closed-today,s,1)
same => n,GotoIf($[${GOSUB_RETVAL} = 1]?closed,s,1)
same => n,GotoIfTime(08:00-16:59,mon-fri,*,*,${BIZ_TZ}?ivr-main,s,1)
same => n,GotoIfTime(09:00-11:59,sat,*,*,${BIZ_TZ}?ivr-main,s,1)
same => n,Goto(closed,s,1)
; A lookup table, not a menu: nothing includes this context, so no caller can reach it.
; Quebec area codes from the NANPA NPA report of 26 Oct 2026.
[quebec-npa]
exten => 263,1,NoOp()
exten => 273,1,NoOp() ; assigned; NANPA lists an in-service date of 27 Feb 2027
exten => 354,1,NoOp()
exten => 367,1,NoOp()
exten => 418,1,NoOp()
exten => 438,1,NoOp()
exten => 450,1,NoOp()
exten => 468,1,NoOp()
exten => 514,1,NoOp()
exten => 579,1,NoOp()
exten => 581,1,NoOp()
exten => 819,1,NoOp()
exten => 873,1,NoOp()
; ---- 3. The main menu (open hours) ----
[ivr-main]
exten => s,1,Set(TRIES=0)
same => n,Set(TIMEOUT(digit)=3) ; pause allowed between digits of 601
same => n(menu),Background(custom/main-menu) ; sounds/en/custom/ or sounds/fr/custom/
same => n,WaitExten(6) ; nothing after this, so a timeout goes to t
exten => 1,1,Verbose(2,Menu 1: new customer)
same => n,Gosub(sub-ring,s,1(${OFFICE},20,${OWNER_MOBILE},20,100))
same => n,Hangup()
exten => 2,1,Verbose(2,Menu 2: existing job)
same => n,Gosub(sub-ring,s,1(${SERVICE_DESK},20,${ONCALL_MOBILE},25,200))
same => n,Hangup()
exten => 3,1,Goto(ivr-accounts,s,1) ; a sub-menu
exten => 4,1,Background(custom/hours-and-address) ; a key press skips ahead
same => n,Goto(s,menu)
exten => 0,1,Gosub(sub-ring,s,1(${OFFICE},25,,,100))
same => n,Hangup()
exten => 8,1,Set(CHANNEL(language)=fr) ; "Pour le service en français, faites le 8"
same => n,Goto(s,menu)
exten => 9,1,Set(CHANNEL(language)=en) ; "For service in English, press 9"
same => n,Goto(s,menu)
; Dial-by-extension, listed one by one: no wildcard pattern a caller can feed.
exten => 601,1,Gosub(sub-ring,s,1(PJSIP/101,20,,,100))
same => n,Hangup()
exten => 602,1,Gosub(sub-ring,s,1(PJSIP/102,20,,,100))
same => n,Hangup()
exten => 603,1,Gosub(sub-ring,s,1(PJSIP/103,20,,,200))
same => n,Hangup()
exten => i,1,GotoIf($[${INC(TRIES)} >= 3]?giveup,1)
same => n,Playback(option-is-invalid)
same => n,Goto(s,menu)
exten => t,1,GotoIf($[${INC(TRIES)} >= 3]?giveup,1)
same => n,Playback(are-you-still-there)
same => n,Goto(s,menu)
exten => giveup,1,Verbose(2,No valid choice after ${TRIES} attempts)
same => n,Gosub(sub-ring,s,1(${OFFICE},20,,,100))
same => n,Hangup()
; ---- 4. A sub-menu: accounts ----
[ivr-accounts]
exten => s,1,Set(TRIES=0)
same => n(menu),Background(custom/accounts-menu)
same => n,WaitExten(6)
exten => 1,1,Gosub(sub-ring,s,1(${ACCOUNTS_DESK},25,,,300))
same => n,Hangup()
exten => 2,1,Playback(custom/payment-options)
same => n,Goto(s,menu)
exten => *,1,Goto(ivr-main,s,menu) ; star: back to the main menu
exten => i,1,GotoIf($[${INC(TRIES)} >= 3]?ivr-main,giveup,1)
same => n,Playback(option-is-invalid)
same => n,Goto(s,menu)
exten => t,1,GotoIf($[${INC(TRIES)} >= 3]?ivr-main,giveup,1)
same => n,Playback(are-you-still-there)
same => n,Goto(s,menu)
; ---- 5. Closed: nights, weekends, holidays ----
[closed]
exten => s,1,Background(custom/closed) ; hours, "press 2 if it is urgent", or wait
same => n,WaitExten(5)
exten => 2,1,Verbose(2,After-hours urgent line)
same => n,Gosub(sub-ring,s,1(,,${ONCALL_MOBILE},30,200))
same => n,Hangup()
exten => t,1,VoiceMail(100@default,u)
same => n,Hangup()
exten => i,1,Goto(t,1)
; ---- 6. Ring desks together, then one mobile, then voicemail ----
; Gosub(sub-ring,s,1(desks,seconds,mobile,seconds,mailbox)); leave a field empty to skip it.
[sub-ring]
exten => s,1,Set(DESKSTATUS=NONE)
same => n,GotoIf($["${ARG1}" = ""]?mobile)
same => n,Dial(${ARG1},${ARG2}) ; without g, returns only if nobody connected
same => n,Set(DESKSTATUS=${DIALSTATUS})
same => n,Verbose(2,Desk phones: ${DIALSTATUS})
same => n(mobile),GotoIf($["${ARG3}" = ""]?vm)
same => n,Dial(PJSIP/${ARG3}@${TRUNK},${ARG4},f(${BIZ_DID})U(sub-screen^${CIDNUM}))
same => n,Verbose(2,Mobile leg: ${DIALSTATUS}) ; still ANSWER after a refused screen
same => n(vm),VoiceMail(${ARG5}@default,${IF($["${DESKSTATUS}" = "BUSY"]?b:u)})
same => n,Return()
; ---- 7. Screen the mobile leg. Runs on the mobile's channel after it answers. ----
[sub-screen]
exten => s,1,Set(CHANNEL(language)=en) ; the employee's language, not the caller's
same => n,Playback(custom/screen-intro) ; "Business call from"
same => n,SayDigits(${ARG1:-10})
same => n,Read(ACCEPT,custom/screen-press-1,1,,2,4)
same => n,GotoIf($["${ACCEPT}" = "1"]?accept)
same => n,Set(GOSUB_RESULT=CONTINUE) ; drop this leg; the caller goes on to voicemail
same => n(accept),Return()
; ---- 8. Holidays and one-off closures. Returns 1 if closed all day today. ----
[sub-closed-today]
exten => s,1,Set(TODAY=${STRFTIME(${EPOCH},${BIZ_TZ},%Y-%m-%d)})
same => n,GotoIf(${DB_EXISTS(closed/${TODAY})}?yes) ; CLI: database put closed 2026-12-24 1
; Fixed dates recur every year: the time specification has no year field.
same => n,GotoIfTime(*,*,1,jan,${BIZ_TZ}?yes) ; New Year's Day
same => n,GotoIfTime(*,*,25,dec,${BIZ_TZ}?yes) ; Christmas Day
same => n,GotoIfTime(*,mon,1-7,sep,${BIZ_TZ}?yes) ; Labour Day (CA) and Labor Day (US)
; Canada (federal list; add your province's days)
same => n,GotoIfTime(*,mon,18-24,may,${BIZ_TZ}?yes) ; Victoria Day: Monday before 25 May
same => n,GotoIfTime(*,*,1,jul,${BIZ_TZ}?yes) ; Canada Day
same => n,GotoIfTime(*,mon,2,jul,${BIZ_TZ}?yes) ; 2 July when 1 July is a Sunday
same => n,GotoIfTime(*,mon,8-14,oct,${BIZ_TZ}?yes) ; Thanksgiving: second Monday
same => n,GotoIfTime(*,*,26,dec,${BIZ_TZ}?yes) ; Boxing Day
; United States (federal list; delete what you do not observe)
same => n,GotoIfTime(*,mon,25-31,may,${BIZ_TZ}?yes) ; Memorial Day: last Monday
same => n,GotoIfTime(*,*,4,jul,${BIZ_TZ}?yes) ; Independence Day
same => n,GotoIfTime(*,thu,22-28,nov,${BIZ_TZ}?yes) ; Thanksgiving: fourth Thursday
same => n,Return(0)
same => n(yes),Return(1)
; ---- 9. Staff phones (their endpoints use context=internal) ----
[internal]
exten => _10X,1,Dial(PJSIP/${EXTEN},30) ; desk to desk
same => n,Hangup()
exten => *98,1,VoiceMailMain(@default) ; asks for mailbox number and PIN
exten => *71,1,Gosub(sub-record,s,1(en/custom/main-menu))
same => n,Hangup()
exten => *72,1,Gosub(sub-record,s,1(fr/custom/main-menu))
same => n,Hangup()
exten => *90,1,Goto(ivr-entry,s,1) ; the menu, as now
exten => *91,1,Set(TESTTIME()=2026/12/25 10:00:00 America/Toronto) ; Christmas morning
same => n,Goto(ivr-entry,s,1)
exten => *92,1,Set(TESTTIME()=2026/10/27 22:00:00 America/Toronto) ; a Tuesday night
same => n,Goto(ivr-entry,s,1)
[sub-record]
exten => s,1,Answer()
same => n,VMAuthenticate(100@default) ; only someone with the office PIN
same => n,Record(${ARG1}.wav,3,120) ; # to stop; 3 s silence or 120 s also stop
same => n,Wait(1)
same => n,Playback(${ARG1})
same => n,Return()
Con el voicemail.conf de la sección de buzón de voz, ese es todo el sistema. Ninguno de los dos archivos contiene una contraseña de la troncal; esa vive en pjsip.conf.
Probarlo antes que sus clientes
Primero una aclaración: no hemos ejecutado este archivo exacto en un servidor en producción para esta guía. Cada línea se verificó contra la documentación y el código fuente, no se ejecutó, así que haga las verificaciones de abajo en su propio sistema antes de apuntarle un número.
La consola. Conéctese al servidor en ejecución con asterisk -r, o con asterisk -rvvv para empezar con verbosidad 3;69 core set verbose 3 hace lo mismo desde adentro.70 El primer paso de diagnóstico de la documentación es fijar la verbosidad en tres o más y observar mientras se hacen llamadas, y usar dialplan show <context> y dialplan show <exten>@<context> para ver exactamente qué se ejecutará.71 Después de editar, dialplan reload vuelve a leer el archivo.72 Para ver la documentación de una aplicación en la versión que realmente ejecuta, use core show application Dial; voicemail show users enumera los buzones que Asterisk cargó.73
La troncal. pjsip set logger on imprime el tráfico SIP,74 lo que le muestra el request URI que envía su proveedor (y por tanto la extensión que necesita [from-trunk]) y el encabezado From que llevan sus tramos a celulares.
Llamadas sin un segundo teléfono. channel originate PJSIP/101 extension s@ivr-entry hace sonar el teléfono de escritorio 101 y, cuando usted contesta, lo deja en el menú como si hubiera llamado. Las llamadas originadas tienen un tiempo de espera de 30 segundos y, sin contexto, el comando usa default.75 Desde un teléfono del personal, *90 hace lo mismo; *91 y *92 ejecutan el menú con TESTTIME fijado en la mañana de Navidad y en una noche de martes, lo que ejercita la lista de feriados y la ruta de cerrado sin cambiar el reloj del servidor.333 El cierre en AstDB usa la fecha real, así que pruébelo con database put closed <today> 1 y quítelo después.42
Registros. El logger.conf de ejemplo de la documentación escribe avisos, advertencias, errores, salida verbose, DTMF y eventos de fax en un archivo llamado full.76 Conserve el DTMF en él: cuando un cliente dice "oprimí 2 y no pasó nada", el registro muestra lo que realmente oprimió.
| Escenario | Cómo provocarlo | Qué debería pasar |
|---|---|---|
| Abierto, inglés | *90 una mañana entre semana | Menú en inglés; 1 hace sonar 101 y 102 juntos |
| Llamada desde Quebec | Llamar desde un celular 514 o 418 | Primero el menú en francés; 9 cambia a inglés |
| Entradas no válidas | Oprimir 5 tres veces | "Invalid" dos veces y luego suena la oficina |
| Silencio | No oprimir nada | "Are you still there" dos veces y luego suena la oficina |
| Extensión directa | Marcar 602 | Solo suena 102; buzón 100 después de 20 s |
| Nadie en los escritorios | Opción 1, sin respuesta | Escritorios por 20 s, luego el celular con el filtro |
| Buzón del celular | Opción 2; dejar que el celular pase a su buzón de voz | El filtro no recibe 1; quien llama llega al buzón 200 |
| Fuera de horario | *92 | Mensaje de cerrado; 2 hace sonar el celular de guardia |
| Feriado | *91 | Mensaje de cerrado |
| Cierre puntual | database put closed <today> 1 | Mensaje de cerrado; borrar la clave después |
| Correo electrónico | Dejar un mensaje en cada buzón | Correo con adjunto; sin errores en el registro de correo |
| Aviso faltante | Renombrar un aviso en francés | Se reproduce el de inglés; restaurar el archivo |
Desplace la tabla hacia los lados para ver todas las columnas.
Nuestra opinión: ejecute toda la tabla después de cada cambio en el dialplan, no solo la línea que editó, y llame al número real desde un teléfono externo una vez al mes.
Cuándo esta no es la herramienta adecuada
Asterisk es una buena opción para esto si ya lo ejecuta, si el menú debe llegar a teléfonos de escritorio en su propia red o si necesita un comportamiento que ningún servicio alojado ofrece. El costo es el servidor que rodea al dialplan: seguridad SIP, actualizaciones dentro de una serie de versiones con soporte,1 monitoreo, entrega de correo y copias de seguridad, mientras el negocio tenga el número.
Si lo que necesita es un menú que desvíe a celulares y tome mensajes, un servicio alojado da menos trabajo. Puede construir el mismo flujo con una API de voz programable (nuestras guías de Twilio lo hacen) o usar una operadora automática (auto attendant) alojada como la nuestra. Para más sobre cómo operar Asterisk, vea las guías de Asterisk.
Lecturas recomendadas
Artículos de investigación más largos sobre este tema (en inglés), cada uno con sus fuentes y un PDF descargable.
Artículos sobre este tema
Fuentes
Cada afirmación sobre normas o precios en esta página enlaza a su fuente primaria, a menudo en inglés. Las normas y los precios cambian: verifique antes de confiar en una cifra.
- Documentación de Asterisk, "Asterisk Versions" (series de versiones, periodos de soporte LTS y estándar, tabla de estado actual). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/About-the-Project/Asterisk-Versions/ - Documentación de Asterisk, referencia de la aplicación BackGround() (sinopsis, BACKGROUNDSTATUS, opciones s, n, m, p, langoverride, context). Generada a partir de la rama de Asterisk 23. Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/BackGround/ - Código fuente de Asterisk, main/pbx_builtins.c (pbx_builtin_answer, pbx_builtin_waitexten, pbx_builtin_background, pbx_builtin_gotoiftime). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/pbx_builtins.c - Código fuente de Asterisk, main/pbx_timing.c (ast_build_timing, get_timerange, get_range, ast_check_timing2: análisis y evaluación de las especificaciones de tiempo). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/pbx_timing.c - Código fuente de Asterisk, main/pbx_include.c (include_valid: cómo se evalúa un include restringido por horario). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/pbx_include.c - Código fuente de Asterisk, main/file.c (fileexists_test y fileexists_core: orden de búsqueda por idioma de los archivos de sonido). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/file.c - Documentación de Asterisk, "Contexts, Extensions, and Priorities" (sintaxis, prioridad n, same =>, etiquetas, orden de búsqueda del dialplan). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Contexts-Extensions-and-Priorities/ - Documentación de Asterisk, "Pattern Matching" (X, Z, N, conjuntos, . y !, orden de clasificación, advertencia sobre el paso al patrón siguiente, coincidencia por Caller ID). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Pattern-Matching/ - Código fuente de Asterisk, configs/samples/extensions.conf.sample (opciones de [general] static, writeprotect, autofallthrough, clearglobalvars; sintaxis del include por horario). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/configs/samples/extensions.conf.sample - Documentación de Asterisk, "Global Variables Basics". Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Variables/Global-Variables-Basics/ - Documentación de Asterisk, referencia de configuración del módulo res_pjsip (opciones de endpoint context, language, from_user, send_pai, trust_id_inbound, trust_id_outbound; default_from_user global). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Module_Configuration/res_pjsip/ - Código fuente de Asterisk, res/res_pjsip_session.c (get_destination: extensión entrante tomada del request URI; set_from_header: From saliente a partir de la identidad de línea conectada y from_user). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/res/res_pjsip_session.c - Documentación de Asterisk, "Special Asterisk Dialplan Extensions" (a, e, h, i, o, s, t, T). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Special-Dialplan-Extensions/ - Documentación de Asterisk, referencia de la aplicación Answer() (espera de medios de 500 ms, opción i). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Answer/ - Documentación de Asterisk, "Manipulating Variables Basics" (sintaxis de subcadenas con omisión y longitud). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Variables/Manipulating-Variables-Basics/ - Documentación de Asterisk, referencia de la función DIALPLAN_EXISTS(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/DIALPLAN_EXISTS/ - Documentación de Asterisk, referencia de la función IF(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/IF/ - Documentación de Asterisk, referencia de la función CHANNEL() (language: "R/W language for sounds played"). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/CHANNEL/ - NANPA, informe NPA (CSV), fecha del archivo 10/26/2026: códigos de área de Quebec, indicadores y fechas de entrada en servicio, incluido el 273 (asignado, entrada en servicio el 27-Feb-2027). Fecha del archivo: 26 de octubre de 2026
https://reports.nanpa.com/public/npa_report.csv - Office québécois de la langue française, Vitrine linguistique, "Système de réponse vocale interactif, répondeur et boîte vocale" (mensajes de bienvenida y Carta de la lengua francesa). Consultado el 26 de octubre de 2026
https://vitrinelinguistique.oqlf.gouv.qc.ca/23273/la-redaction-et-la-communication/communications-telephoniques/systeme-de-reponse-vocale-interactif-repondeur-et-boite-vocale - Documentación de Asterisk, referencia de la aplicación Return() (GOSUB_RETVAL). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Return/ - Documentación de Asterisk, referencia de la aplicación Playback(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Playback/ - Documentación de Asterisk, referencia de la aplicación WaitExten() (segundos, opciones m y d). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/WaitExten/ - Documentación de Asterisk, "Background and WaitExten Applications" (tutorial; tiempo de espera de respuesta predeterminado; ejemplo de buzón para ocupado o no disponible). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Auto-attendant-and-IVR-Menus/Background-and-WaitExten-Applications/ - Documentación de Asterisk, referencia de la función TIMEOUT() (absolute; digit, 5 s por omisión; response, 10 s por omisión). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/TIMEOUT/ - Documentación de Asterisk, "Handling Special Extensions" (tutorial con i y t, y el rastro verbose de una entrada no válida). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Auto-attendant-and-IVR-Menus/Handling-Special-Extensions/ - Documentación de Asterisk, referencia de la función INC(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/INC/ - Documentación de Asterisk, referencia de la aplicación Goto() (los destinos inexistentes pasan a i, luego a h y después cuelgan). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Goto/ - Documentación de Asterisk, "Creating a Simple IVR Menu" (indica que los avisos del ejemplo están en los paquetes de sonidos adicionales). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Auto-attendant-and-IVR-Menus/Creating-a-Simple-IVR-Menu/ - Proyecto Asterisk, asterisk-extra-sounds-fr-gsm-current.tar.gz (lista de archivos leída: contiene option-is-invalid y are-you-still-there). Descargado el 26 de octubre de 2026
https://downloads.asterisk.org/pub/telephony/sounds/asterisk-extra-sounds-fr-gsm-current.tar.gz - Documentación de Asterisk, referencia de la aplicación GotoIfTime() (sintaxis con zona horaria opcional; si se omite la etiqueta, la ejecución continúa). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/GotoIfTime/ - Documentación de Asterisk, "Include Statements Basics" (patrones que se superponen entre contextos incluidos). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Include-Statements/Include-Statements-Basics/ - Documentación de Asterisk, referencia de la función TESTTIME(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/TESTTIME/ - Código fuente de Asterisk, main/pbx.c (testtime_write: el formato de fecha que TESTTIME realmente analiza). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/pbx.c - 5 U.S.C. § 6103, Holidays (feriados federales legales y sus reglas de fecha), a través del Legal Information Institute. Consultado el 26 de octubre de 2026
https://www.law.cornell.edu/uscode/text/5/6103 - Código del Trabajo de Canadá (Canada Labour Code), R.S.C. 1985, c. L-2, s. 166 (definición de "general holiday"). Consultado el 26 de octubre de 2026
https://laws-lois.justice.gc.ca/eng/acts/L-2/section-166.html - Holidays Act, R.S.C. 1985, c. H-5 (Día de Canadá, Día de la Reina Victoria). Consultado el 26 de octubre de 2026
https://laws-lois.justice.gc.ca/eng/acts/H-5/FullText.html - Canada Revenue Agency, "Public holidays" (fechas de 2026, incluidos el Viernes Santo, 3 de abril, y el Día de Acción de Gracias, 12 de octubre). Consultado el 26 de octubre de 2026
https://www.canada.ca/en/revenue-agency/services/tax/public-holidays.html - Documentación de Asterisk, referencia de la función STRFTIME(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/STRFTIME/ - Documentación de Asterisk, "Asterisk Standard Channel Variables" (EPOCH). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Variables/Channel-Variables/Asterisk-Standard-Channel-Variables/ - Documentación de Asterisk, referencia de la función DB_EXISTS(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Functions/DB_EXISTS/ - Código fuente de Asterisk, main/db.c (uso en la CLI de database put, get, del, deltree, show). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/main/db.c - Documentación de Asterisk, referencia de la aplicación Dial() (marcación en paralelo, tiempo de espera predeterminado, valores de DIALSTATUS, opciones f, g, m, r, U y GOSUB_RESULT). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Dial/ - Código fuente de Asterisk, apps/app_dial.c (la línea conectada se copia a cada nuevo tramo; opción f; los tramos se cuelgan antes de ejecutar la subrutina U; DIALSTATUS después de GOSUB_RESULT). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/apps/app_dial.c - Documentación de Asterisk, "Dialing PJSIP Channels" (endpoint, PJSIP_DIAL_CONTACTS, número@troncal). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/Dialing-PJSIP-Channels/ - Documentación de Asterisk, referencia de la aplicación Read() (maxdigits, attempts, timeout). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Read/ - Documentación de Asterisk, referencia de la aplicación SayDigits(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/SayDigits/ - Documentación de Asterisk, referencia de la aplicación Set() (herencia de variables con _ y __). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Set/ - Documentación de Asterisk, referencia de la aplicación VoiceMail() (opciones b, u, s, U; teclas 0 y *; VMSTATUS). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/VoiceMail/ - Documentación de Asterisk, referencia de la aplicación VoiceMailMain(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/VoiceMailMain/ - Documentación de Asterisk, descripción general de "Voicemail" (advertencia sobre la opción s). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Applications/Voicemail/ - Código fuente de Asterisk, configs/samples/voicemail.conf.sample (format, attach, attachfmt, maxmsg, minsecs, operator, delete, emailsubject, emailbody, mailcmd, zonemessages, formato de la línea de buzón). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/configs/samples/voicemail.conf.sample - Código fuente de Asterisk, apps/app_voicemail.c (sendmailAIL por omisión y cómo se ejecuta mailcmd). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/apps/app_voicemail.c - Documentación de Asterisk, "Asterisk Audio and Video Capabilities" (tabla de formatos, wav y wav49, tasas de linear firmado, sox). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Operation/Asterisk-Audio-and-Video-Capabilities/ - Documentación de Asterisk, "Directory and File Structure" (astdatadir y el directorio de sonidos). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Fundamentals/Directory-and-File-Structure/ - Documentación de Asterisk, "Sound Prompt Searching based on Channel Language". Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Internationalization-and-Localization/Sound-Prompt-Searching-based-on-Channel-Language/ - Código fuente de Asterisk, configs/samples/asterisk.conf.sample (defaultlanguage = en). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/configs/samples/asterisk.conf.sample - Documentación de Asterisk, "Asterisk Sounds Packages" (el formato que menos CPU consume se elige automáticamente). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Internationalization-and-Localization/Asterisk-Sounds-Packages/ - Descargas del proyecto Asterisk, directorio de sonidos (paquetes en francés, básico y adicional, en alaw, g722, g729, gsm, siren7, siren14, sln16, ulaw y wav). Consultado el 26 de octubre de 2026
https://downloads.asterisk.org/pub/telephony/sounds/ - Proyecto Asterisk, asterisk-core-sounds-fr-gsm-current.tar.gz, versión 1.6.1 (archivo CREDITS y textos de los avisos en core-sounds-fr.txt). Descargado el 26 de octubre de 2026
https://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-fr-gsm-current.tar.gz - Código fuente de Asterisk, sounds/sounds.xml (opciones de paquetes de sonido de menuselect y cuáles vienen activadas por omisión). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/sounds/sounds.xml - Documentación de Asterisk, "Voice Prompts and Music on Hold License". Consultado el 26 de octubre de 2026
https://docs.asterisk.org/About-the-Project/License-Information/Voice-Prompts-and-Music-on-Hold-License/ - Página del manual de SoX, sox(1): opciones -r, -c, -b, -e y -t (espejo de páginas man de Linux; el manual HTML del sitio del proyecto devolvió 404 hoy). Consultado el 26 de octubre de 2026
https://linux.die.net/man/1/sox - Documentación de Asterisk, referencia de la aplicación Record() (formato, silencio, maxduration, opciones, RECORD_STATUS). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Record/ - Documentación de Asterisk, "Record Application" (tutorial: grabar un aviso de menú personalizado y un menú del día con una extensión *). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Auto-attendant-and-IVR-Menus/Record-Application/ - Código fuente de Asterisk, apps/app_record.c (create_destination_directory: las rutas relativas se crean dentro de sounds/). rama master, consultado el 26 de octubre de 2026
https://raw.githubusercontent.com/asterisk/asterisk/master/apps/app_record.c - Documentación de Asterisk, referencia de la aplicación VMAuthenticate(). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/VMAuthenticate/ - Documentación de Asterisk, "Gosub" (descripción de las subrutinas: reemplaza a Macro, argumentos ARG1..ARGn, Return). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Dialplan/Subroutines/Gosub/ - Documentación de Asterisk, "Running Asterisk" (consola remota con -r, verbosidad con -rvvv). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Operation/Running-Asterisk/ - Documentación de Asterisk, "Basic Logging Commands" (core set verbose, core set debug). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Operation/Logging/Basic-Logging-Commands/ - Documentación de Asterisk, "Making a Phone Call" (verbosidad tres o más; dialplan show para diagnosticar). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Making-a-Phone-Call/ - Documentación de Asterisk, "Creating Dialplan Extensions" (dialplan reload). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Deployment/Basic-PBX-Functionality/Creating-Dialplan-Extensions/ - Documentación de Asterisk, "CLI Syntax and Help Commands" (core show application, voicemail show users). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Operation/Asterisk-Command-Line-Interface/CLI-Syntax-and-Help-Commands/ - Documentación de Asterisk, "Asterisk PJSIP Troubleshooting Guide" (pjsip set logger on). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/Asterisk-PJSIP-Troubleshooting-Guide/ - Documentación de Asterisk, "Creating and Manipulating Channels from the CLI" (channel originate, tiempo de espera de 30 segundos). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Operation/Asterisk-Command-Line-Interface/Creating-and-Manipulating-Channels-from-the-CLI/ - Documentación de Asterisk, "Logging Configuration" (niveles de logger.conf, incluido dtmf). Consultado el 26 de octubre de 2026
https://docs.asterisk.org/Configuration/Core-Configuration/Logging-Configuration/
