TUTORIAL: Cómo crear un chatbot para generar cuentas de cobro por prestación de servicios

0 comments

Recientemente escribí Cómo ahorrar US$20.000 o más automatizando un proceso que es un dolor de cabeza, en el cual analizo el problema que motiva a crear ese chatbot, y el ahorro, que le puede generar a la empresa que lo implementa. Además, hablo sobre  el incremento en la productividad de los agentes y  de los transportistas.

En este artículo muestro cómo crear ese Superbot capaz de ahorrarte miles de dólares, esta vez, usando el framework de Twnel.

Este video muestra el Superbot en acción (por si no los has visto)

El Superbot per se es muy sencillo. El flujo es:

Flujo del superbot

  1. Al activar el flujo, este envía un mensaje al usuario saludando y explicando de qué se trata.
  2. Luego el flujo le pide a un sistema de información, a través de un API, los valores a pagar al proveedor del servicio.

    Para generar esta información en tiempo real, la empresa puede utilizar otras automatizaciones convencionales (Superbots) para capturar la ubicación (geolocalización – para validar que el transportista o prestador del servicio está donde debe estar) y el momento de llegada a entregar (o a prestar el servicio);  el momento en que terminó la tarea, incluyendo anotaciones y novedades, fotos de facturas firmadas, etc.
  3. El flujo le presenta estos valores al proveedor del servicio, y le pregunta si está de acuerdo.
  4. Si está de acuerdo le pide que firme, usando un input type de firma. 
  5. El flujo hace un llamado a otro servicio a través de un API.

    Este servicio, en nuestro ejemplo, creado con Google Apps Script, usa una plantilla en Google Docs, crea una copia y reemplaza los datos del proveedor del servicio (nombre, identificación, valor a pagar por servicios, bonificaciones, etc) y agrega la firma capturada en el paso (4). Luego genera un PDF de esa copia con toda la información relevante, que almacena en un folder particular en Google Drive.

    Finalmente el servicio borra la copia del Google Doc.
  6. El llamado al API retorna el URL del PDF para que el proveedor del servicio lo pueda ver.
  7. En caso que el transportista no esté de acuerdo con el valor calculado por la empresa, el Superbot transfiere la comunicación a un agente.

Antes de crear el Superbot en el Bot Visual Builder de Twnel, necesitamos crear los servicios y los webhooks descritos en los pasos (2) y (5). 

Hay varias formas de lograr eso:

  1. NO-CODE. Este enfoque consiste en crear los webhooks (los endpoints de los APIs) usando plataformas de integración no-code. Existen varias plataformas aptas para lograr lo que necesitamos aquí.

    Estas plataformas permiten conectar tus aplicaciones y automatizar tus flujos de trabajo. Las más conocidas son:
    1. Zapier, que es tal vez la más conocida
    2. Integromat, que a mi modo de ver es más poderosa y menos costosa.En otros artículos te mostraré como usar ambas para crear estas automatizaciones.

      También en otros artículos describiré otras opciones además de Zapier e Integromat

  2. Usando Google Apps Script. Google Apps Script (GAS) es parte de Google Workspace (antes conocida como G-Suite).  

En este artículo mostraré como usar GAS.

Google Apps Script es una plataforma de desarrollo de aplicaciones rápida que agiliza y facilita la creación de aplicaciones comerciales que se integran con Google Workspace.

Escribe código en JavaScript moderno y tiene acceso a bibliotecas integradas para las aplicaciones favoritas de Google Workspace como Google Sheets, Google Docs, Gmail, Calendar, Drive y más. 

No hay nada que instalar. Apps Script incluye un editor de código directamente en tu navegador y sus scripts se ejecutan en los servidores de Google.

Proceso de generación del PDF en GAS

Primero, necesitamos crear una estructura de archivos en Google Drive:

  1. Crear un carpeta que se llame CreatePDFs (o el nombre que prefieran)
  2. Crear 2 carpetas dentro de esa:
    PDFs (o como lo lo quieran llamar) para almacenar todos los archivos PDF que generen.
    TEMP FILES (o como lo quieran llamar) para almacenar los archivos temporales, mientras se crean los PDFs. Una vez estos son creados, el script borra esos archivos temporales.
  3. Colocar la plantilla de la factura creada con Google Docs en la carpeta CreatePDFs. (esto no es obligatorio, pero es útil por organización)
  4. Opcionalmente colocar el Google Sheet de donde se sacan los datos también en esa carpeta (CreatePDFs). En ese Google Sheets se almacena la información a pagar a cada persona en esa semana. Esto puede ser generado automáticamente con base en la información de entregas o servicios prestados que se captura usando otros Superbots.

    La estructura de los folderes quedaria asi:

    Y el archivo de valores a pagar en Google Sheets es algo como esto:

    Hoja con datos a pagar en la semana

    Plantilla en Google Docs

    Puedes usar esta plantilla haciendo una copia y modificando lo que creas conveniente.

    Instrucciones paso a paso

    Crear el webhook que se necesita para obtener los valores a pagar

    Comencemos con el script que trae los valores a pagar (paso 2 en el flujo)

    El código completo de ese script es:

    function doGet(e) {
      return handleRequest(e, "GET");
    }
    
    function handleRequest(e, request) {
      // Service for returning text content from a script
      var output = ContentService.createTextOutput();
      
      // Abrir un spreadsheet usando su id
      var ss = SpreadsheetApp.openById("ID-DEL-GOOGLE-SHEET")
    
      // Obtener una referencia a la hoja de interes especifica por su nombre
      var sheet = ss.getSheetByName("para cobro");
      
      var telefono = Number(e.parameter.phone);
      
      var values = sheet.getDataRange().getValues();
      var obj = {"nombre": ""};
      
      for(i=1; i<values.length; i++) {
        var tel = Number(values[i][0]);
    
        if(tel === telefono) {
          obj.fecha = values[i][2];
          obj.total = values[i][11];
          obj.nombre = values[i][4];
          obj.empresa = values[i][5];
          obj.valor_metas = values[i][9];
          obj.valor_servicio = values[i][10];
        }
      }
    
      var response ;
      if(obj.nombre == "") response = "No aparece nada por pagar a nombre suyo."
      else response = "Según nuestros registros a la fecha: *" + obj.fecha +"* le debemos por\n- valor de servicios *" + formatStringCurrency(obj.valor_servicio) + "* \n- bonos por metas *" + formatStringCurrency(obj.valor_metas) +  "* \n- TOTAL *" + formatStringCurrency(obj.total) + "* ";
      
      var callback = e.parameters.callback;
      if(callback === undefined) {
        output.setContent(JSON.stringify(response));
      } else {
        output.setContent(callback + "(" +JSON.stringify(response) + ")");
      }
      
      output.setMimeType(ContentService.MimeType.JSON);
      
      return output;
    }
    
    function formatStringCurrency(val) {
      const valor = Number(val);
      return "$"+parseFloat(valor.toFixed(2)).toLocaleString().replace(/\.([0-9])$/, ".$10")
    }
    

    Crear el webhook que se necesita para enviar el URL de la firma y generar el PDF de la factura

    Veamos ahora, en este video, el código (JavaScript) que necesitamos en GAS.

    Y el código del script que crea el PDF es:

    function doPost(e) {
      return handleRequest(e);
    }
    
    function handleRequest(e) {
      // Service for returning text content from a script
      const output = ContentService.createTextOutput();
      
      // open your spreadsheet by passing id of spreadsheet
      const ss = SpreadsheetApp.openById(ID-DEL-GOOGLE-SHEET)
      const sheet = ss.getSheetByName("para cobro");
      const values = sheet.getDataRange().getValues();
    
      const jsonString = e.postData.contents;
      const jsonData = JSON.parse(jsonString);
    
      // data enviada por el bot
      const phone = jsonData.phone;
      const firmaURL = jsonData.firma;
    
      // tomar los datos del sheet para hacer la factura 
      // lookup por el phone
      const factura = {};
    
      for(let i=0; i<values.length; i++) {
        const tel = values[i][0];
        
        if(tel == phone) {
          factura.phone = phone;
          factura.ciudad = values[i][1];
          factura.fecha = values[i][2];
          factura.semana = values[i][3];
          factura.conductor = values[i][4];
          factura.empresa = values[i][5];
          factura.cedula = values[i][6];
          factura.n_factura = values[i][7];
          factura.valor_arriendo = values[i][8];
          factura.meta_viajes = values[i][9];
          factura.valor_servicio = values[i][10];
          factura.valor_total_servicio = values[i][11];
          factura.direccion = values[i][12];
          break;
        }
      }
    
      // El template del documento
      const docTemplateFile = DriveApp.getFileById("1ng1elBJSjsVEXhzUaUuic_cmqC2t2t0Ef-vSVRHTY8w");
    
      // Folder temporal para almacenar las facturas (doc) antes de convertirlas a PDF
      const tempFolder = DriveApp.getFolderById("1Ro4HdSQ6C-zk_P4BKxm8c7wU5oQnh6Op");
    
      // Folder donde se almacenan los PDFs - debe ser publico para que los conductores los puedan ver en un bot
      const pdfFolder = DriveApp.getFolderById("1CCg7ydN3j0k-wta8e0Sf1HjZlySf2tGm");
      pdfFolder.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);
    
      Logger.log(factura);
      Logger.log(firmaURL);
      Logger.log(docTemplateFile);
      Logger.log(tempFolder);
      Logger.log(pdfFolder);
    
      const respuesta = createPDF(factura, firmaURL, docTemplateFile, tempFolder, pdfFolder);
      const msg = "La factura fue generada";
    
      response = {"msg": msg, "pdfURL": respuesta, "status": 200};
    
      //Logger.log(response);
      
      const callback = e.parameters.callback;
      if(callback === undefined) {
        output.setContent(JSON.stringify(response));
      } else {
        output.setContent(callback + "(" +JSON.stringify(response) + ")");
      }
      
      output.setMimeType(ContentService.MimeType.JSON);
      
      return output;
    }
    
    function createPDF(factura, firmaURL, docTemplateFile, tempFolder, pdfFolder) {
      // hacer copia del template en un nuevo doc temporal
      const tempFile = docTemplateFile.makeCopy(tempFolder);
      const tempDocFile = DocumentApp.openById(tempFile.getId());
    
      // obtener el texto del body del documento
      const body = tempDocFile.getBody();
    
      // reemplazar los textos 
      // cada texto a reeplazar debe ser unico. Por lo tanto, si un
      // valor (ej. conductor) debe aparecer mas de una vez en el PDF,
      // se deben hacer 2 o mas asiganciones 
      try {
        body.replaceText("<<Telefono>>", factura.phone);
        body.replaceText("<<Ciudad>>", factura.ciudad);
        body.replaceText("<<TODAY>>", Utilities.formatDate(new Date(), "America/Bogota", "dd/MM/YY"));
        body.replaceText("<<conductor>>", factura.conductor);
        body.replaceText("<<Empresa>>", factura.empresa);
        body.replaceText("<<Empresa2>>", factura.empresa);
        body.replaceText("<<Cedula>>", factura.cedula);
        body.replaceText("<<Cedula2>>", factura.cedula);
        body.replaceText("<<Factura>>", factura.n_factura);
        body.replaceText("<<Semana>>", factura.semana);
        body.replaceText("<<Valor arriendo>>", formatStringCurrency(factura.valor_arriendo));
        body.replaceText("<<Meta viajes semanal>>", formatStringCurrency(factura.meta_viajes));
        body.replaceText("<<Valor servicio>>", formatStringCurrency(factura.valor_servicio));
        body.replaceText("<<Valor total servicio>>", formatStringCurrency(factura.valor_total_servicio));
        body.replaceText("<<Direccion>>", factura.direccion);
      }catch(e) {
        Logger.log(e instanceof Error) // true
        return e // false in the client when tested with console.log(e instanceof Error)
      }
    
      // incluir firma
      if (firmaURL != undefined && firmaURL != "") {
        const imageBlob = UrlFetchApp.fetch(firmaURL).getBlob()
    
        replaceTextWithImage(body, "<<firma>>", imageBlob, 200);
      }
    
      // salvar y cerrar el documento
      tempDocFile.saveAndClose();
    
      // crear el PDF
      const pdfContentBlob = tempFile.getAs(MimeType.PDF);
      const pdfFileName = factura.conductor + " - " + factura.fecha;
      const facturaPDF = pdfFolder.createFile(pdfContentBlob).setName(pdfFileName);
    
      // obtener el URL del PDF para poder pasarlo al bot
      const id = facturaPDF.getId();
      const baseUrl = "https://drive.google.com/uc?export=view&id=";
      const pubUrl = baseUrl+id; // the public URL
      
      // borrar el doc temporal
      tempFolder.removeFile(tempFile);
    
      return pubUrl;
    }
    
    // funcion para reemplazar un texto por firma
    function replaceTextWithImage(body, searchText, image, width) {
      const firma = body.findText(searchText);
      const elemento = firma.getElement();
      elemento.asText().setText("");
      var img = elemento.getParent().asParagraph().insertInlineImage(0, image);
      if (width && typeof width == "number") {
        var w = img.getWidth();
        var h = img.getHeight();
        img.setWidth(width);
        img.setHeight(width * h / w);
      }
    }
    
    // funcion para formatear valores monetarios
    function formatStringCurrency(val) {
      const valor = Number(val);
      return "$"+parseFloat(valor.toFixed(2)).toLocaleString().replace(/\.([0-9])$/, ".$10")
    }

    Crear el flujo del chatbot usando los “endpoints” generados durante la creación de los scripts

    Así se ve el flujo en el Visual Bot Builder

    Como ves es muy sencillo.

    Como esto también es más fácil mostrarlo en vídeo, aquí va…

    ¿Cómo te pareció?

    Déjanos tus preguntas y sugerencias de más paso a pasos en los comentarios! 

    Te notificaremos cuando publiquemos
    nuevos artículos, casos de uso,
    podcasts y webinars.

    Autor: Gilbert Mizrahi

    Head of Growth – Twnel

    Siempre en busca de retos por resolver
    Copy link
    Powered by Social Snap