La verdad que es un poco complicado pero básicamente necesitamos lo siguiente:
- Una entrada de línea telefónica a través de un media gateway que nos haga de pasarela entre la red celular (GSM) y/o fija (PSTN).
- Un PBX, pero no cualquier PBX, tiene que ser un IPPBX porque queremos poder programarlo y no gastar ni un guaraní en adquirirlo e instalarlo. Para esto tenemos varias opciones pero mi favorita es Asterisk .
- Las voces del IVR . Son las grabaciones que se reproducirán cuando alguien llame e interactúe con la central.
- Nuestro cerebro e Internet para programar el dialplan que básicamente, es lo más dificil en todo esto.
Necesitamos saber quién es el cliente que llama, para esto tenemos su caller-id. Con ese dato podemos ir a tratar de identificarlo en una base de datos (MySQL en mi caso) buscando su nombre y dirección particular. Si no lo encontramos, lo derivamos directamente con la operadora para que le tome sus datos y lo dé de alta en el sistema, cosa que se hace siempre cuando llamas por primera vez.
Si el cliente ya está registrado, le reproducimos el menú estadísticamente más pedido o todo el menú si quieren (no es buena idea). Por ejemplo:
- Llamo y me atiende una IVR deciéndome: “Bienvenido al bar X” y me reproduce el menú.
- “Presione 1 para pizzas, 2 para lomitos, 3 para bebidas, 0 (cero) para hablar con un operador”.
- Me gustaría comer pizza y presiono 1 reproduciéndome…
- “Presione 1 para napolitana, 2 para cuatro quesos, 3 para muzarela, 4 para peperoni.”
- Me gusta la de 4 quesos y presiono 2 y me reproduce…
- “Seleccione la cantidad que desea”
- Bueno, estoy con algunas personas y quiero tres, entonces aprieto 3…
- “Usted ha pedido 3 pizzas de cuatro quesos, el precio total es, 90 mil guaraníes, presione 1 para confirmar, 2 para cambiar el pedido, 3 para salir”
- Quiero confirmar, presiono 1…
- “Su pedido ha sido procesado con éxito, adiós”, y termina la transacción. Opcionalmente, recibís un SMS que te confirma que tu pedido fue atendido y otro mensaje cuando se encuentra en tránsito, es decir, cuando el delivery guy se está yendo a tu casa en su moto.
Cuando llega un nuevo pedido se levanta un ticket o alerta que la cajera o algún encargado puede ver en su pantalla y éste se procesa como cualquier otro pedido hecho físicamente en la caja.
¿Cuánto tiempo tardé en pedir?
Escuchar toda la grabación me tomó 30 segundos o menos, tiempo decente pero, cuando me sepa de memoria el menú, al llamar puedo presionar 123 (1 + 2 + 3) de seguido y lo siguiente que escucharé será: “Usted ha pedido 3 pizzas de cuatro quesos, el precio total es, 90 mil guaraníes, presione 1 para confirmar, 2 para cambiar el pedido, 3 para salir”, entonces, presiono 1 y listo. Cuánto tardé?, menos de 10 segundos.
Como había dicho, lo más difícil de programar fue el dialplan. Tenía 4 opciones para esto, usar el seudolenguaje de programación de extensiones (no!!!), AEL (tampoco, pero lo usé solo para algunas operaciones de muy bajo nivel), AGI (mucho mejor, pero no) y FastAGI, el ganador.
Por qué fastAGI? porque utiliza sockets sobre IP lo que me permite manejar la carga en una suerte de computación distribuida, mucho mas escalable y encima elegante.
Hay demasiadas cosas por explicar sobre cómo funciona Asterisk y fastAGI. tal vez en otra ocasión me siente a hacer un ensayo, pero por ahora les dejo un snippet esencial para el manejo de pedidos, hecho en C#, para que se den una idea.
public override void Service(AGIRequest param1, AGIChannel param2)
{
try
{
Answer();
StreamFile("pizza/bienvenidos");
if (!IsClientRegistered(param1.CallerId))
{
Console.WriteLine("CallerID {0} is not registered, transferring to operator.", param1.CallerId);
StreamFile("pizza/numero_desconocido");
SetVariable("GO_TO_OPERATOR", "yes");
AddCustomer(param1.CallerId);
return;
}
SetVariable("GO_TO_OPERATOR", "no");
if (CustomerHasPendingOrders(param1.CallerId))
{
StreamOrderStatus();
if (Ask("13", "pizza/1_nuevaorden_3_salir") == ConfirmationRequest.Exit)
throw new Exception("User exited transaction");
}
MenuLevel menu;
PhoneKey pressedKey = ' ';
ConfirmationRequest confimation;
List customerChoice = new List();
bool makeMoreOrders = false;
ulong total = 0;
/*
* Say menu options
*/
while(true)
{
menu = _rootMenu;
makeMoreOrders = false;
while(true)
{
pressedKey = StreamMenu(menu);
if (menu.IsRoot && pressedKey.IsGoToOperator)
{
SetVariable("GO_TO_OPERATOR", "yes");
return;
}
if (!menu.IsRoot && pressedKey.IsGoBack)
{
menu = menu.Parent;
continue;
}
if (!menu.HasSubLevels)
{
MenuOption option = menu[pressedKey];
int quantity = AskForQuantity();
StreamCustomerOrder(option, quantity);
confimation = Ask("123", "pizza/1_confirmar_2_ordenar_3_salir");
if (confimation == ConfirmationRequest.Exit)
throw new Exception("User canceled the transaction");
else if(confimation == ConfirmationRequest.OrderMore)
makeMoreOrders = true;
customerChoice.Add(new SelectedProduct(menu[pressedKey], quantity));
total += option.Price * (ulong) quantity;
break;
}
menu = menu.GetSubMenu(pressedKey);
}
if (makeMoreOrders)
continue;
StreamFile("pizza/costo_total");
SayNumber(total.ToString());
StreamFile("pizza/guaranies");
if (Ask("13", "pizza/1_confirmar_3_salir") == ConfirmationRequest.Exit)
throw new Exception("User canceled transaction in the last minute");
foreach(SelectedProduct selection in customerChoice)
AddOrder(param1.CallerId, selection.SelectedOption.ProductID, selection.Quantity, selection.TotalPrice);
StreamFile("pizza/pedido_procesado_adios");
break;
}
}
catch(Exception ex)
{
Console.WriteLine("PizzaIVR.Service(): {0}", ex.Message);
}
Hangup();
}