Volver a las novedades para desarrolladores

Concertar citas con WhatsApp Flows: crear un back-end Node.js

27 de febrero de 2024DeGafi G e Iryna Wagner

WhatsApp Flows te permite crear mensajes interactivos para los usuarios que completen acciones directamente en WhatsApp. Los flujos te permiten crear pantallas para que los usuarios interactúen; por ejemplo, puedes crear formularios de respuesta sencilla para captar clientes potenciales o recopilar opiniones. Además, tienes la opción de diseñar flujos complejos en varias pantallas para agendar una cita.

En esta guía se describen todos los pasos para crear una aplicación Node.js con la que los usuarios pueden concertar citas a través de WhatsApp Flows. Vamos a crear un flujo en la Plataforma de WhatsApp Business para después configurar un webhook con el que recibir la respuesta del flujo y concertar la cita.

Requisitos previos

Para continuar con este tutorial, asegúrate de que cumples los siguientes requisitos:

Crear un flujo de WhatsApp Flows

Hay dos formas de crear un flujo de este tipo: con el creador de flujos, al que se puede acceder desde el Administrador de WhatsApp, o con la API de flujos. En este tutorial usaremos el creador de flujos.

Crear un flujo

En el menú que encontrarás a la izquierda del panel del Administrador de WhatsApp, selecciona Herramientas de la cuenta. A continuación, haz clic en Flujos.

Imagen del Administrador de WhatsApp

Haz clic en Crear flujo, en la esquina superior derecha.

imagen de creación de flujo

Aparecerá un cuadro de diálogo donde deberás rellenar los datos para el flujo de cita:

  • Nombre: escribe Concertar cita o el que prefieras.
  • Categorías: selecciona Reserva de citas.
  • Plantilla: escoge Reservar una cita. Esta plantilla te resultará útil porque contiene todos los elementos necesarios para concertar una cita, como una pantalla con los detalles de la cita, una pantalla para que el usuario introduzca los datos, una pantalla con un resumen de la cita y una pantalla con las condiciones de la empresa. Además, puedes personalizar la plantilla para adaptarla a tus necesidades.
Imagen de reserva de una cita

Haz clic en Enviar para crear el flujo.

Puedes ver una vista previa del flujo a la derecha de la interfaz del creador de flujos. En la pantalla de la cita, el usuario puede elegir los detalles de la cita, como la ubicación y la fecha. La pantalla de detalles es donde el usuario debe introducir su información. En la pantalla de resumen se muestra la información general de la reserva. La última pantalla contiene las condiciones de la empresa.

El flujo se mantiene en estado de borrador mientras lo editas. Por el momento, solo puedes compartirlo con tu equipo para hacer pruebas. Si quieres compartirlo con otros destinatarios, tendrás que publicarlo, pero no podrás editar el flujo una vez publicado. Puesto que te falta añadir la URL del extremo para este flujo de citas, déjalo como borrador por ahora y continúa con el siguiente paso, en el que vamos a configurar el extremo.

Configurar el extremo del flujo

WhatsApp Flows te permite conectarte a un extremo externo, que puede proporcionar datos dinámicos para tu flujo y controlar el enrutamiento. También recibe las respuestas que envíen los usuarios desde el flujo.

Con el propósito de hacer pruebas, en este artículo se usa Glitch para alojar el extremo. Utilizar Glitch es totalmente opcional, y no es necesario para usar los flujos. Puedes clonar el código del extremo desde GitHub y ejecutarlo en el entorno que prefieras.

Accede al código del extremo en Glitch y crea un remix para obtener un dominio único. Para ello, haz clic en Remix en la parte superior de la página. Aparecerá un dominio único como marcador de posición en el elemento de entrada que encontrarás en la parte derecha de la página de Glitch.

Antes de continuar, veamos el código. Hay cuatro archivos JavaScript en el directorio src: encryption.js, flow.js, keyGenerator.js y server.js. El archivo de entrada es server.js, así que comencemos por él.

server.js

El archivo server.js comienza configurando la aplicación Express para que utilice el middleware express.json para analizar las peticiones JSON entrantes. A continuación, carga las variables de entorno necesarias para el extremo.

const { APP_SECRET, PRIVATE_KEY, PASSPHRASE, PORT = "3000" } = process.env;

APP_SECRET se utiliza en la verificación de firmas. Te ayuda a comprobar si un mensaje llega desde WhatsApp y, por tanto, es seguro procesarlo. Añádelo al archivo .env.

Para acceder a tu APP_SECRET, navega hasta el panel de control de la aplicación en Meta for Developers. En el panel de navegación izquierdo, en Configuración de la aplicación, elige Información básica. Haz clic en Mostrar debajo de Clave secreta de la aplicación y cópiala. Después, regresa a Glitch, abre el archivo .env y crea una variable con el nombre APP_SECRET y el valor de la clave secreta que has copiado.

PRIVATE_KEY ayuda a descifrar los mensajes recibidos. PASSPHRASE se usa para verificar la clave privada. Además de la clave privada, necesitarás la clave pública correspondiente para cargarla más adelante. No uses nunca aquí las claves privadas de tus cuentas de producción. Crea una clave privada temporal para probarla en Glitch, y luego sustitúyela por tu clave de producción en tu propia infraestructura.

Genera el par de claves pública-privada ejecutando el siguiente comando en el terminal de Glitch. Reemplaza <your-passphrase> por el código de acceso que hayas escogido. Haz clic en la pestaña TERMINAL situada en la parte inferior de la página para acceder al terminal de Glitch.

node src/keyGenerator.js <your-passphrase>

Copia el código de acceso y la clave privada y pégalos en el archivo .env. Haz clic en el archivo que tiene la etiqueta .env en la barra lateral izquierda y luego en ✏️ Plain text (texto sin formato) en la parte superior. No lo edites directamente desde la IU, porque se dañaría el formato de tu clave.

Una vez configuradas las variables de entorno, copia la clave pública que has generado y súbela con la API Graph.

El archivo server.js también contiene un extremo POST que realiza distintas acciones:

  • Comprueba que la clave privada esté presente:
       if (!PRIVATE_KEY) { throw new Error('Private key is empty. Please check your env variable "PRIVATE_KEY".'); }
  • Valida la firma de la solicitud con la función isRequestSignatureValid que se encuentra al final del archivo:
if(!isRequestSignatureValid(req)) { // Return status code 432 if request signature does not match. // To learn more about return error codes visit: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • Descifra los mensajes entrantes con la función decryptRequest que se encuentra en el archivo encryption.js:
      let decryptedRequest = null; try { decryptedRequest = decryptRequest(req.body, PRIVATE_KEY, PASSPHRASE); } catch (err) { console.error(err); if (err instanceof FlowEndpointException) { return res.status(err.statusCode).send(); } return res.status(500).send(); } const { aesKeyBuffer, initialVectorBuffer, decryptedBody } = decryptedRequest; console.log("💬 Decrypted Request:", decryptedBody);
  • Decide qué pantalla del flujo se muestra al usuario. Más adelante, veremos en detalle la función getNextScreen.

const screenResponse = await getNextScreen(decryptedBody);

       console.log("👉 Response to Encrypt:", screenResponse);
  • Cifra la respuesta que se enviará al usuario:
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));

encryption.js

Este archivo contiene la lógica para cifrar y descifrar los mensajes intercambiados por motivos de seguridad. En este tutorial no nos centraremos en el funcionamiento del archivo.

keyGenerator.js

Este archivo ayuda a generar las claves privada y pública, como hemos visto antes. Al igual que sucede con el archivo encryption.js, en este tutorial no vamos a ver el archivo keyGenerator.js en detalle.

flow.js

La lógica para gestionar el flujo se aloja en este archivo. Comienza con un objeto al que se le asigna el nombre SCREEN_RESPONSES. El objeto contiene identificadores de pantalla con sus detalles correspondientes, como los datos predefinidos que se usan en los intercambios de datos. Este objeto se genera desde el creador de flujos en “...” > Extremo > Fragmentos > Respuestas. En el mismo objeto, también tienes otro identificador, SUCCESS, que se envía de nuevo al dispositivo cliente cuando el flujo se completa correctamente. Con esta acción se cierra el flujo.

La función getNextScreen contiene la lógica que orienta al extremo sobre qué datos del flujo mostrar al usuario. Comienza extrayendo los datos necesarios del mensaje descifrado.

const { screen, data, version, action, flow_token } = decryptedBody;

Los extremos de WhatsApp Flows suelen recibir tres solicitudes:

Encontrarás más información en la documentación del extremo.

La función gestiona las notificaciones de error y comprobación de estado mediante instrucciones if y responde en consecuencia, como se muestra en el siguiente fragmento:

// handle health check request if (action === "ping") { return { version, data: { status: "active", }, }; } // handle error notification if (data?.error) { console.warn("Received client error:", data); return { version, data: { acknowledged: true, }, }; }
        

Cuando un usuario hace clic en el botón de llamada a la acción (CTA) del flujo, se inicia una acción INIT, que devuelve la pantalla de la cita junto con los datos. También desactiva los campos desplegables de ubicación, fecha y hora para garantizar que el usuario rellene todos los campos.

Por ejemplo, el campo desplegable de fecha solo se activa una vez rellenado el de ubicación. La activación y desactivación de los campos se gestiona cuando se recibe una solicitud data_exchange.

// handle initial request when opening the flow and display APPOINTMENT screen if (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // these fields are disabled initially. Each field is enabled when previous fields are selected is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

En el caso de las acciones data_exchange, se usa una estructura switch case para determinar qué datos se devuelven en función del identificador de pantalla. Si el identificador es APPOINTMENT, los campos desplegables solo se activan si se han seleccionado los campos desplegables anteriores.

// Each field is enabled only when previous fields are selected is_location_enabled: Boolean(data.department), is_date_enabled: Boolean(data.department) && Boolean(data.location), is_time_enabled: Boolean(data.department) && Boolean(data.location) && Boolean(data.date)

Para las pantallas de detalles, los títulos de las propiedades de los objetos de datos, como ubicación y departamento, se extraen del objeto SCREEN_RESPONSES.APPOINTMENT.data. Este código asume que hay una coincidencia válida, por lo que debes tener en cuenta que puede lanzar un error si no se encuentra ningún objeto que coincida.

Ahora, toma una instancia del objeto ubicación. La selección del objeto de ubicación en concreto se determina igualando la propiedad id de los objetos de la matriz con el valor de data.location.

const departmentName = SCREEN_RESPONSES.APPOINTMENT.data.department.find( (dept) => dept.id === data.department ).title; const locationName = SCREEN_RESPONSES.APPOINTMENT.data.location.find( (loc) => loc.id === data.location ).title; const dateName = SCREEN_RESPONSES.APPOINTMENT.data.date.find( (date) => date.id === data.date

).title;

A continuación, los valores se concatenan y se devuelven en la respuesta para mostrar la pantalla de resumen.

const appointment = `${departmentName} at ${locationName} ${dateName} at ${data.time}`; const details = `Name: ${data.name} Email: ${data.email} Phone: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // return the same fields sent from client back to submit in the next step ...data, }, };
        

Una vez enviada la pantalla de resumen desde el cliente, se envía una respuesta de confirmación al dispositivo cliente para marcar el flujo como completado. El flow_token es un identificador único que puedes configurar al enviar el flujo al usuario.

// send success response to complete and close the flow return { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

La pantalla de condiciones no contiene datos para intercambiar, por lo que el extremo no la gestiona.

Añadir el extremo al flujo

En la parte superior derecha de la página de Glitch, haz clic en el icono del menú de los tres puntos verticales y selecciona Copy Link (copiar enlace) para copiar la URL. También puedes obtener el enlace si haces clic en Share (compartir) en la parte superior derecha.

Accede al editor de flujos. Haz clic en Configurar en el panel marrón que encontrarás en la parte superior del editor.

Aparecerá un mensaje emergente que te permite configurar el URI del extremo, el número de teléfono de la empresa y la aplicación en Meta for Developers. Cuando hayas configurado lo necesario, lleva a cabo una comprobación de estado. En primer lugar, selecciona Solicitar datos, en Solicitar datos de la primera pantalla, en la configuración de la vista previa interactiva y ejecuta esta vista. De este modo se envía una solicitud al extremo para recuperar los datos de la primera pantalla, a la vez que se verifica que el extremo esté disponible y que hayas llevado a cabo una comprobación de estado.

A continuación, publica el flujo haciendo clic en el menú de los tres puntos horizontales (...) y selecciona Publicar. De esta forma se enviará una solicitud de comprobación de estado a tu extremo con action === "ping" para verificar que el extremo esté configurado antes de publicarlo.

Imagen del extremo

Probar el flujo

Cuando hayas terminado de configurarlo todo, vuelve a activar la vista previa interactiva en la interfaz de usuario del creador de WhatsApp para probar el flujo. En la ventana emergente que aparece, selecciona el número de teléfono y elige la opción Solicitar datos en Solicitar datos de la primera pantalla. Haz clic en el icono X para cerrar el flujo y empezar a probarlo de nuevo desde el botón de llamada a la acción.

Abre el registro de Glitch haciendo clic en la pestaña LOGS (registros). Haz clic en Clear (borrar) para borrarlo. Después, vuelve a la vista previa de la IU del creador de WhatsApp. Haz clic en Vista previa del flujo. Verás algo parecido a lo que se muestra a continuación:

Imagen de la vista previa del flujo

Ahora regresa a los registros de Glitch, donde verás una acción INIT, el identificador del flujo y otros detalles en la solicitud descifrada. También hay una respuesta al cifrado que se envía de nuevo al flujo del usuario una vez seleccionado el campo desplegable del departamento.

Imagen de la solicitud descifrada

Continúa y selecciona el departamento. Fíjate en cómo is_location_enabled está establecido en true y la acción ha cambiado a data_exchange.

Imagen de data_exchange

Sigue probando el flujo y observa los cambios de datos en los registros de Glitch. Se generarán registros similares cuando los usuarios interactúen con el flujo desde sus dispositivos móviles.

En la siguiente sección, vamos a crear un webhook que envíe un mensaje de confirmación al usuario cuando reserve una cita.

Configurar el webhook

Cuando un usuario completa el flujo, se envía un mensaje que marca el flujo como completado al webhook suscrito. Desde este webhook, notificarás al usuario que ha reservado la cita correctamente mediante un mensaje en el chat. Como con el extremo, también vamos a usar Glitch para las pruebas. Puedes acceder al código y hacer un remix aquí.

Utilizar Glitch es totalmente opcional, y no es necesario para usar los flujos. Puedes clonar el código del webhook desde GitHub y ejecutarlo en el entorno que prefieras.

Establecer las variables de entorno

Para establecer las variables de entorno, abre el archivo .env en Glitch. Establece VERIFY_TOKEN en la cadena que prefieras, FLOW_ID en el identificador de tu flujo y GRAPH_API_TOKEN en el identificador de acceso de la cuenta de WhatsApp Business. Puedes obtener el identificador de acceso en el panel de tu aplicación en Meta for Developers haciendo clic en Configuración de la API en la sección WhatsApp del panel de navegación izquierdo.

Imagen de la configuración de la API

En la página que se muestra, haz clic en el botón Copiar debajo de la tarjeta Identificador de acceso temporal. Pega la clave en tu archivo .env.

Suscribir el webhook en el panel de Meta

En tu cuenta de Meta for Developers, haz clic en el menú Configuración, debajo de WhatsApp, en el panel de navegación izquierdo.

Imagen de configuración

En el apartado Webhook, haz clic en Editar. En el cuadro de diálogo que se abre, pega la URL de Glitch que has copiado y añádele /webhook en el campo URL de devolución de llamada. En el campo Identificador de verificación, añade el token de la variable TOKEN en tu archivo .env. Cuando hayas acabado, haz clic en Verificar y guardar. El cuadro de diálogo se cerrará y volverás a la pantalla principal. Haz clic en Administrar y comprueba el campo Mensajes. Tu webhook ya está listo.

Guía sobre el código de webhook

El código contiene dos rutas: POST /webhook y GET /webhook. La ruta GET gestiona las solicitudes de verificación de webhook comparando el identificador proporcionado con un identificador de verificación predefinido y respondiendo con los códigos de estado adecuados y un identificador de comprobación.

const verify_token = process.env.VERIFY_TOKEN; // Parse params from the webhook verification request let mode = req.query["hub.mode"]; let token = req.query["hub.verify_token"]; let challenge = req.query["hub.challenge"]; if (mode && token) { if (mode === "subscribe" && token === verify_token) { console.log("WEBHOOK_VERIFIED"); res.status(200).send(challenge); } else { res.sendStatus(403); } }

La ruta POST /webhook procesa las notificaciones de webhook entrantes. Las solicitudes de webhook pueden tener distintas cargas útiles. De este modo, el código siguiente lee el mensaje y el número de teléfono de la empresa accediendo a los campos de la solicitud de forma segura en caso de que no se hayan definido.

const message = req.body.entry?.[0]?.changes[0]?.value?.messages?.[0]; const business_phone_number_id =

req.body.entry?.[0].changes?.[0].value?.metadata?.phone_number_id;

A continuación, comprueba si la solicitud entrante es para un mensaje de tipo "text" que contenga la palabra “appointment” (“cita”). Si el mensaje contiene esta palabra, se envía el flujo al usuario. El mensaje del flujo se envía con flow_action: "data_exchange", lo que significa que el flujo hará una petición INIT al extremo cuando se lance para obtener la pantalla inicial y los datos.

if ( message.type === "text" && // for demo purposes, send the flow message whenever a user sends a message containing "appointment" message.text.body.toLowerCase().includes("appointment") ) { // send flow message as per the docs here https://developers.facebook.com/docs/whatsapp/flows/gettingstarted/sendingaflow#interactive-message-parameters await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, type: "interactive", interactive: { type: "flow", header: { type: "text", text: "Hello there 👋", }, body: { text: "Ready to transform your space? Schedule a personalized consultation with our expert team!", }, footer: { text: "Click the button below to proceed", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // replace flow_token with a unique identifier for this flow message to track it in your endpoint & webhook flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Book an appointment", flow_action: "data_exchange", }, }, }, }, }); } ...

Si el mensaje entrante no es de tipo "text", el código comprueba si es "interactive". Si el mensaje entrante es del tipo interactivo "nfm_reply", significa que se trata de una respuesta del flujo. Entonces, devuelve al usuario el mensaje “You’ve successfully booked an appointment” (“Has reservado una cita correctamente”).

... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // send confirmation message await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, text: { body: "You've successfully booked an appointment" }, }, }); } ...

A continuación, marca el mensaje entrante como leído para que el usuario vea las marcas azules.

... // mark incoming message as read await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", status: "read", message_id: message.id, }, }); ...
        

Experiencia del usuario

En este ejemplo, el usuario envía un mensaje a tu número que contiene la palabra “appointment” (“cita”) y, a continuación, recibe el mensaje del flujo. También puedes configurarlo para que envíe el flujo después de otra interacción o como plantilla de mensaje.

El usuario recibirá un mensaje del flujo con un botón de llamada a la acción para concertar una cita, donde podrá introducir sus datos. Cuando complete el flujo, recibirá un mensaje de confirmación.

Imagen de envío del flujo al usuario

En esta guía, has aprendido a configurar un flujo de WhatsApp para programar citas de forma cómoda. Has usado la interfaz de usuario del creador de flujos para crear un formulario con el que recopilar los datos de la cita facilitados por los usuarios.

Gracias a los flujos, no tendrás que redirigir a los usuarios a un sitio web externo para reservar citas, lo que mejora la experiencia del cliente. El proceso es muy sencillo y permite a los usuarios hacer las reservas directamente desde WhatsApp. Además de para concertar citas, puedes usar los flujos de WhatsApp para recopilar opiniones del servicio de atención al cliente, o ayudar a los usuarios a inscribirse en promociones o listas de correo. Los flujos de WhatsApp también te ofrecen la posibilidad de conectarlos con API externas u otras aplicaciones en tu extremo.

Crear flujos de WhatsApp es muy sencillo con la interfaz de usuario del creador de flujos, aunque también puedes usar la API de flujos para crearlos mediante lenguaje de programación. Si quieres obtener más información, consulta la documentación de WhatsApp Flows.