Cómo Crear un Script de Procesamiento de Llamadas

Introducción

Los scripts de procesamiento de llamadas, también denominados Route Point, son una nueva y potente función de V20. Permiten capturar llamadas y procesarlas mediante código C# estándar, lo que ofrece posibilidades ilimitadas para analizar una llamada y aplicar una lógica personalizada. He aquí algunos ejemplos:

  • Analizar el identificador de llamadas y asignar agentes específicos.
  • Realizar búsquedas de clientes basadas en el identificador de llamadas y enrutarlas en consecuencia.
  • Comprobar la hora y la fecha y procesar una llamada en consecuencia.
  • Comprobar la fecha y reproducir un aviso basado en la fecha.

Llamada a un Script de Procesamiento de Llamadas

Antes de escribir un script debe pensar dónde y cómo quiere que se active este script. Puede configurar un código de marcación para el mismo, o desviar las llamadas al mismo desde mediante la asignación de un DID o el desvío al script basado en una variable en particular.

¡En la actualización 2, podrá activar la llamada en cada llamada entrante que llegue a una troncal SIP!

Una vez que haya decidido cómo se activará el script, tendrá que filtrar las llamadas y escribir su lógica de procesamiento de llamadas.

Descripción General de la API de Procesamiento de Llamadas

La API consta de tres métodos principales que son pasar llamadas al nuevo destino.

Método grupo Task<CallControlResult> RouteToAsync(this ActiveConnection ac,<Destination>)

Este método crea una ruta vinculada a la conexión especificada ac.

  • ac - la conexión activa propiedad de RoutePoint. Cuando el nuevo destino responde su conexión sustituye a ac. (participación de RoutePoint).
  • El script debe manejar los fallos de la tarea. El script puede simplemente llamar a MyCall.Return para terminar su propia conexión con el llamante.
  • El script puede intentar construir tantas rutas como requiera, pero la primera ruta respondida cancelará todas las demás y reemplazará la participación de RoutePoint en la llamada (terminará la llamada para RoutePoint).
  • RouteToAsync puede ejecutarse en cualquier estado de la conexión RoutePoint. Por lo tanto, el punto de ruta puede realizar enrutamiento en segundo plano cuando se comunica con el Llamante (reproducir mensajes, manejar DTMF, etc.).
  • Cuando la tarea tiene éxito, la llamada se revoca desde RoutePoint (MyCall se desconecta) y el nuevo destino continúa el manejo.

Método grupo Task<CallControlResult> DivertAsync(this ActiveConnection ac,<Destination>)

Este método desvía la llamada al nuevo destino sin establecer (contestar) la conexión (timbrando en RoutePoint).

  • Si ya se ha establecido una llamada con RoutePoint, el método fallará y se deberá utilizar RouteToAsyc/ReplaceWithAsync.
  • La conexión activa (en estado de timbrado) propiedad de RoutePoint será reemplazada por un nuevo destino y RoutePoint se desconectará de la llamada.
  • Este método es útil si el punto de ruta no necesita interactuar con el llamante.
  • Si una tarea ha fallado, el script puede continuar manejando la conexión con el llamante.
  • Cuando la tarea tiene éxito, la llamada se revoca desde RoutePoint (se termina la conexión MyCall) y el nuevo destino comienza a gestionar la llamada. El script RoutePoint pasa al modo Wrap Up y debe terminar su propia tarea.

Método grupo Task<CallControlResult> ReplaceWithAsync(this ActiveConnection ac,<Destination>)

También conocido como método de "transferencia ciega".

  • Solo se permite en modo conectado (RoutePoint acepta la llamada e interactúa con el usuario).
  • La llamada se pondrá en espera.
  • La tarea fallará si el destino no es alcanzable.
  • El script puede continuar procesando la llamada tras el fallo de la tarea (si la persona que llama sigue conectada con RoutePoint).
  • Cuando una tarea tiene éxito, la conexión del script (ICallHandler.MyCall) finaliza y el script se desvincula del manejo de la llamada (el nuevo participante se encargará de la llamada).

Ejemplo de Script de Procesamiento de Llamadas

Este ejemplo muestra cómo crear un punto de ruta personalizado y cómo programarlo:

  1. Estructura básica del código C# suministrado a RoutePoint.
  2. Uso básico de los métodos de extensión TCX.PBXAPI.CallControlAPI para la interfaz ActiveConnection (ICall).
  3. Trabajo básico con el objeto MyCall suministrado por CallFlowScriptingCore.
  4. Trabajo básico con la configuración (parámetros PBX).
  5. Utilización del método de extensión RouteToAsyc del objeto ActiveConnection.

Un ejemplo de la funcionalidad de RoutePoint:

  • El punto de ruta sólo acepta llamadas enviadas mediante transferencia ciega desde el teléfono (Extensión). Las llamadas directas se rechazan.
  • Cualquier extensión puede transferir simultáneamente un número ilimitado de llamadas a este RoutePoint (cada llamada se maneja por separado).
  • La llamada se devuelve transcurridos 15 segundos al remitente (extensión) directamente (no se aplica desvío). Si la llamada devuelta no se contesta en 15 segundos, la llamada se cancela y se repite de nuevo en 15 segundos.
  • La persona que llama escucha música en espera tal y como está configurado para Aparcamiento en PBX.

Cómo Ejecutarlo

  • Cree un RoutingPoint con cualquier número - por ejemplo #101 - y establezca la propiedad RoutePoint.ScriptCode al texto de abajo (la interfaz de usuario todavía no permite crear puntos de enrutamiento con código personalizado escrito a mano) requiere algún tipo de archivo zip, que no es realmente necesario para un script simple).
  • RoutePoint debería aparecer en la lista correspondiente ("Aplicaciones CFD" en este momento) con punto verde [la compilación no debería fallar para este código]).
  • Después:
  • Si alguna extensión transfiere su llamada al número #101, la llamada se devolverá en 15 segundos.
  • La persona que llama escuchará música en espera tal y como está configurada para Aparcamiento. (El script utiliza esta configuración, pero el código puede modificarse para generar otro contenido para la persona que llama).
  • Si la llamada devuelta no es contestada, el RoutePoint lo intentará de nuevo (y de nuevo) 15 segundos después del intento anterior hasta que la persona que llama no abandone la llamada, o el transmitente original conteste (o su llamada sea descolgada).

Comentarios del Código

  • El código del objeto "scripted" se basa en (uso, implementos y/o heredados).
  • Espacio de nombre CallFlow
  • CallFlow.ICall
  • CallFlow.ICallHandler
  • CallFlow.ICallHandlerEx
  • CallFlow.ScriptBase<T>
  • La clase "Script object" debe heredar de CallFlow.ScriptBase<T> e implementar todos los métodos abstractos necesarios para su instancia.
  • El objeto se está ejecutando cuando el ScriptingHost ejecuta el controlador de llamadas utilizando el método ICallHandler.Start.
  • El script debe finalizar con ICall.Return explícitamente.
  • La implementación preferida del método ICall.Start es "async void" que ejecuta la tarea independiente (debe capturar todas las excepciones).
  • La implementación del script debe controlar únicamente el objeto MyCall expuesto por el Scripting Host. Es el único objeto de la sesión de script de llamada.
  • Cuando finaliza MyCall (participación del punto de ruta en la llamada), la implementación del script debe concluir y finalizar su sesión.
  • El script de flujo de llamadas no es una forma de monitorizar la configuración del sistema o cualquier recurso externo.
  • No es una forma de controlar todas las llamadas del sistema.
  • Es solo una lógica RoutePoint, que puede integrarse con otros flujos de llamadas.
  • En otras palabras: el script maneja una de las llamadas que están conectadas con RoutePoint, pero nunca inicia una nueva llamada.
  • La API de enrutamiento está encapsulada en la clase estática TCX.PBXAPI.CallControlAPI que expone métodos de extensión para:
  • TCX.Configuration.ActiveCannection
  • TCX.Configuration.DN
  • TCX.Configuration.RegistrarRecord

Código de Ejemplo

#nullable disable

using CallFlow;

using System;

using System.Threading;

using System.Threading.Tasks;

using TCX.Configuration;

using TCX.PBXAPI;

namespace dummy

{

    public class ParkingRoutePointSample : ScriptBase<ParkingRoutePointSample>

    {

        async Task<CallControlResult> ProcessAutoPickup(RoutePoint sp, DestinationStruct returnTo, CancellationToken token)

        {

            while (true)

                try

                {

                    return await Task.Delay(TimeSpan.FromSeconds(15), token).ContinueWith(x =>

                    {

                        MyCall.Trace("{0} - automatic redirection of the call from {1}.{2} to '{3}'", MyCall.DN, MyCall.Caller?.CallerID, MyCall.Caller?.DN, returnTo);

                        return MyCall.RouteToAsync(new RouteRequest

                        {

                            RouteTarget = returnTo,

                            TimeOut = TimeSpan.FromSeconds(15) //will ring until failure

                        }

                        );

                    }

                    , TaskContinuationOptions.NotOnCanceled).Unwrap();

                }

                catch (OperationFailed ex)

                {

                    MyCall.Trace("Automatic redirection failed: {0}", ex.TheResult);

                    MyCall.Trace("Continue hold call from {0}({1}) on {2}", MyCall.Caller?.CallerID, MyCall.Caller?.DN, MyCall.DN);

                    continue;

                }

        }

        PhoneSystem ps = null;  

        /// <summary>

        ///

        /// </summary>

        public override async void Start()

        {

            await Task.Run(async () =>

            {

                try

                {

                    MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}");

                    MyCall.Debug($"Incoming connection {MyCall}");

                    ps = MyCall.PS as PhoneSystem;

                    CallControlResult lastresult = null;

                    DN referredBy = null;

                    RoutePoint thisPark = null;

                    string callerID = "";

                    DN callerDN = null;

                    bool scriptCompleted = true;

                    try

                    {

                        referredBy = MyCall.ReferredByDN?.GetFullSnapshot() as Extension;

                        thisPark = MyCall.DN?.Clone() as RoutePoint;

                        callerID = MyCall.Caller?.CallerID;

                        callerDN = MyCall.Caller?.DN?.Clone() as DN;

                        MyCall.Trace(

                            "Parked call from {0}({1}) on {2}", callerID, callerDN, thisPark

                        );

                        if (referredBy == null)

                        {

                            MyCall.Trace("{0} rejects call from {1}. Reason: No referrer specified", thisPark, callerDN);

                            return;

                        }

                        var cancelationToken = new CancellationTokenSource();

                        MyCall.OnTerminated += () =>

                        {

                            cancelationToken.Cancel();

                        };

                        lastresult = await MyCall.AssureMedia().ContinueWith(

                            x =>

                            {

                                if(!string.IsNullOrWhiteSpace(ps.GetParameterValue("PARK_MOH_SOURCE")))

                                    MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("PARK_MOH_SOURCE") });

                                else

                                    MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("MUSICONHOLDFILE") });

                                return ProcessAutoPickup(thisPark, new DestinationStruct(referredBy), cancelationToken.Token);

                            }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap();

                    }

                    catch (PBXIsNotConnected ex)

                    {

                        MyCall.Error($"Call control API is not available:\n{ex}");

                        scriptCompleted = false;

                    }

                    catch (TaskCanceledException)

                    {

                        MyCall.Trace($"Call was disconnected from parking place");

                    }

                    catch (Exception ex)

                    {

                        MyCall.Error($"Parking failure:\n{ex}");

                        scriptCompleted = false;

                    }

                    finally

                    {

                        try

                        {

                            MyCall.Info("Call from {0}({1}) parked by {2} on {3} finished with result={4}", callerID, callerDN, referredBy, thisPark, lastresult?.ToString() ?? "terminated");

                        }

                        catch (Exception ex)

                        {

                            MyCall.Error($"SharedParkingFlow finalize exception {ex}");

                        }

                        MyCall.Return(scriptCompleted);

                    }

                }

                catch

                {

                    MyCall.Return(false);

                }

            });

        }

    }

}

Ver También

Última Actualización

Este documento se actualizó por última vez el 5 de Marzo de 2024

https://www.3cx.es/docs/manual/script-procesamiento-llamada/