Volver a las novedades para desarrolladores

Reservar citas con WhatsApp Flows: Cómo crear un backend con Node.js

27 de febrero de 2024DeGafi G. e Iryna Wagner

Con WhatsApp Flows, puedes crear mensajes interactivos para que las personas realicen acciones directamente en WhatsApp. Flows te permite crear pantallas para la interacción con las personas. Por ejemplo, puedes crear formularios de entrada sencillos para recopilar clientes potenciales o recibir opiniones. Además, puedes diseñar flujos complejos en varias pantallas para programar citas.

Con esta guía, aprenderás a crear una app Node.js que permita a las personas reservar citas a través de WhatsApp Flows. Crearás un flujo en la plataforma de WhatsApp Business y, luego, configurarás un webhook para recibir la respuesta del flujo y reservar la cita.

Requisitos previos

Para poder realizar este tutorial, asegúrate de contar con lo siguiente:

Crear un flujo de WhatsApp

Hay dos formas de crear un flujo de WhatsApp: con Flows Builder, al que se accede a través del administrador de WhatsApp, y con la API de flujos. En este tutorial, se utiliza Flows Builder.

Crear un flujo

En el menú de la izquierda de tu panel del administrador de WhatsApp, selecciona Herramientas de la cuenta. A continuación, haz clic en Flujos.

Gráfico del administrador de WhatsApp

Haz clic en Crear flujo en la esquina superior derecha.

Gráfico de "Crear flujo"

En el cuadro de diálogo que aparece, escribe los detalles del flujo de citas:

  • Nombre: escribe ReservaDeCitas o elige otro nombre que prefieras.
  • Categorías: selecciona Reserva de citas.
  • Plantilla: elige Reservar una cita. Utilizarás la plantilla, ya que contiene los elementos necesarios para reservar una cita. Estos elementos incluyen pantallas para los detalles de la cita, la entrada de detalles del usuario, el resumen de la cita y la pantalla con las condiciones de la empresa. Puedes personalizar aún más la plantilla para adaptarla a tu caso de uso.
Gráfico de "Reservar una cita"

Haz clic en Enviar para crear un flujo.

Puedes obtener una vista previa del flujo en la parte derecha de la interfaz de usuario de Builder. La pantalla de citas permite al usuario elegir los detalles de la cita, como el lugar y la fecha. La pantalla de detalles es donde el usuario ingresará sus datos. En la pantalla de resumen, se muestra un resumen de la reserva de la cita. La última pantalla muestra las condiciones de la empresa.

Mientras editas el flujo, este se encuentra en versión borrador. En este punto, puedes compartirlo con tu equipo únicamente para probarlo. Para compartirlo con un público amplio, tendrás que publicarlo. Sin embargo, no puedes editar el flujo una vez que lo hayas publicado. Dado que aún necesitarás agregar la URL del punto de conexión para este flujo de citas, mantenlo como borrador por el momento y procede con el siguiente paso, donde configurarás el punto de conexión.

Configurar el punto de conexión del flujo

WhatsApp Flows te permite conectarte a un punto de conexión externo. Este punto de conexión puede proporcionar datos dinámicos para tu flujo y controlar el enrutamiento. También recibe respuestas del flujo enviadas por el usuario.

Para fines de prueba, este artículo utiliza Glitch para alojar el punto de conexión. El uso de Glitch es totalmente opcional y no es necesario para utilizar WhatsApp Flows. Puedes clonar el código del punto de conexión desde GitHub y ejecutarlo en el entorno que prefieras.

Accede al código del punto de conexión en Glitch y mézclalo para obtener tu dominio único. Para hacerlo, 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 de la parte derecha de la página de Glitch.

Antes de continuar, revisemos 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 echémosle un vistazo primero.

server.js

El archivo server.js comienza configurando la app Express para utilizar el middleware express.json y analizar las solicitudes JSON entrantes. Luego, carga las variables de entorno necesarias para el punto de conexión.

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

APP_SECRET se usa para verificar las firmas. Te ayuda a comprobar si un mensaje llega a través de WhatsApp y, por ende, es seguro procesarlo. Lo agregarás al archivo .env.

Para acceder a tu APP_SECRET, dirígete al panel de tu app en Meta for Developers. En la parte izquierda, en Configuración de la app, elige la opción Básico. En la sección Clave secreta de la app, haz clic en Mostrar y copia la clave secreta. Luego, vuelve a Glitch, abre el archivo .env y crea una variable con el nombre APP_SECRET y el valor de la clave secreta que copiaste anteriormente.

PRIVATE_KEY te permite descifrar los mensajes que recibes. PASSPHRASE se utilizará para verificar la clave privada. Además de la clave privada, también necesitarás la clave pública correspondiente, la cual cargarás más adelante. Nunca uses en este punto las claves privadas de tus cuentas de producción. Crea una clave privada temporal para probarla en Glitch y, luego, reemplázala 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> con tu frase de contraseña designada. Accede a la terminal de Glitch haciendo clic en la pestaña TERMINAL en la parte inferior de la página.

node src/keyGenerator.js <your-passphrase>

Copia la frase de contraseña y la clave privada y pégalas en el archivo .env. Haz clic en el archivo .env en la barra lateral izquierda y, luego, en ✏️ Texto sin formato en la parte superior. No lo edites directamente desde la interfaz de usuario, ya que se romperá el formato de las claves.

Después de configurar las variables de entorno, copia la clave pública que generaste y sube la clave pública mediante la API Graph.

El archivo server.js también tiene un punto de conexión POST que realiza diferentes pasos:

  • Comprueba que la clave privada esté presente:
       if (!PRIVATE_KEY) { throw new Error('La clave privada está vacía. Verifica tu variable de entorno "PRIVATE_KEY".'); }
  • Valida la firma de la solicitud utilizando el la función isRequestSignatureValid que se encuentra al final del archivo:
if(!isRequestSignatureValid(req)) { // Devuelve el código de estado 432 si la firma de la solicitud no coincide. // Para obtener más información sobre los códigos de error de retorno, visita: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • Descifra los mensajes entrantes utilizando 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("💬 Solicitud descifrada:", decryptedBody);
  • Decide qué pantalla de flujo mostrar al usuario. Más adelante, verás la función getNextScreen en detalle.

const screenResponse = await getNextScreen(decryptedBody);

       console.log("👉 Respuesta a cifrar:", 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. Este tutorial no se centrará en el funcionamiento del archivo.

keyGenerator.js

Este archivo ayuda a generar las claves privada y pública, como viste anteriormente. Al igual que con el archivo encryption.js, este tutorial no profundizará en el archivo keyGenerator.js.

flow.js

La lógica para administrar el flujo se aloja en este archivo. Comienza con un objeto con el nombre SCREEN_RESPONSES. El objeto contiene identificadores de pantalla con sus detalles correspondientes, como los datos predefinidos utilizados en los intercambios de datos. Este objeto se genera en Flow Builder en "..." > Punto de conexión > Fragmentos > Respuestas. En el mismo objeto, también tienes otro identificador, SUCCESS, que se envía de vuelta al dispositivo cliente cuando el flujo se completa con éxito. Esto cierra el flujo.

La función getNextScreen contiene la lógica que guía al punto de conexión 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 puntos de conexión de WhatsApp Flows suelen recibir tres solicitudes:

Puedes encontrar los detalles en la documentación del punto de conexión.

La función se encarga de las notificaciones de comprobación de estado y de error mediante expresiones if y responde en consecuencia, como se indica en el siguiente fragmento de código:

// administra la solicitud de comprobación de estado si (action === "ping") { return { version, data: { status: "activo", }, }; } // administra la notificación de error si (data?.error) { console.warn("Error del cliente recibido:", 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 ejecuta una acción INIT. Esta acción vuelve a mostrar la pantalla de citas junto con los datos. También desactiva los menús desplegables de la ubicación, la fecha y la hora para que el usuario complete todos los campos.

Por ejemplo, el menú desplegable de la fecha solo se activa cuando se completa el de la ubicación. La activación y desactivación de los campos se lleva a cabo cuando se recibe una solicitud data_exchange.

// administra la solicitud inicial cuando se abre el flujo y muestra CITA en la pantalla si (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // estos campos se desactivaron inicialmente. Un campo está activado cuando se seleccionaron los campos anteriores is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

En el caso de las acciones data_exchange, se utiliza una estructura de selección para determinar qué datos devolver en función del identificador de pantalla. Si el identificador de pantalla es APPOINTMENT, los campos desplegables se activan solo cuando se seleccionan los campos desplegables anteriores.

// Un campo está activado cuando se seleccionaron los campos anteriores 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 la pantalla DETALLES, los títulos de las propiedades de los objetos de datos, como la ubicación y el departamento, se extraen del objeto SCREEN_RESPONSES.APPOINTMENT.data. Este código asume que existe una coincidencia válida, por lo que puede devolver un error si no se encuentra ningún objeto coincidente.

Ahora, toma una instancia del objeto de ubicación. La selección del objeto de ubicación específico se determina al hacer coincidir la propiedad id de los objetos del conjunto 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 RESUMEN.

const appointment = `${departmentName} en ${locationName} ${dateName} a las ${data.time}`; const details = `Name: ${data.name} Correo electrónico: ${data.email} Teléfono: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // devuelve los mismos campos enviados por el cliente para enviarlos en el siguiente paso ...data, }, };
        

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

// envía una respuesta de ejecución exitosa para completar y finalizar el flujo de retorno { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

La pantalla CONDICIONES no tiene datos que intercambiar, por lo que el punto de conexión no la administra.

Agregar el punto de conexión al flujo

En la esquina superior derecha de la página de Glitch, puedes copiar la URL haciendo clic en el icono de menú desplegable y seleccionando Copiar enlace. También puedes obtener el enlace haciendo clic en Compartir en la parte superior derecha.

Accede al editor de flujos. Haz clic en Configurar en el banner marrón que se encuentra en la parte superior del editor.

Aparecerá una ventana emergente que te permitirá configurar la URI del punto de conexión, el número de teléfono de la empresa y la app en Meta for Developers. Después de realizar las configuraciones correspondientes, realiza una comprobación de estado. Primero, ejecuta la vista previa interactiva y asegúrate de seleccionar Solicitar datos, en Solicitar datos en la primera pantalla, en la configuración de la vista previa interactiva. De este modo, se envía una solicitud al punto de conexión para recuperar los datos de la primera pantalla, con lo que se verifica que el punto de conexión está disponible y que se llevó a cabo una comprobación de estado.

Luego, publica el flujo. Para ello, haz clic en el menú (...) y selecciona Publicar. Esto enviará una solicitud de comprobación de estado a tu punto de conexión con action === "ping" para verificar que el punto de conexión esté configurado antes de publicar el flujo.

Gráfico del punto de conexión

Probar el flujo

Después de finalizar con la configuración, vuelve a activar la vista previa interactiva en la interfaz de usuario de WhatsApp Builder 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 en la primera pantalla. Cierra el flujo haciendo clic en el icono X para probarlo de nuevo desde el botón de CTA.

Abre el registro de Glitch haciendo clic en la pestaña REGISTROS. Para borrarlo, haz clic en Borrar. Luego, regresa a la vista previa de la interfaz de usuario de WhatsApp Builder. Haz clic en Vista previa del flujo. Aparecerá algo similar a lo siguiente:

Gráfico de vista previa del flujo

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

Gráfico de solicitud descifrada

Selecciona el departamento. Observa cómo is_location_enabled se configuró como true y la acción cambió a data_exchange.

Gráfico de data_exchange

Continúa 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 próxima sección, crearás 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 al webhook correspondiente marcando la finalización del flujo. Desde este webhook, enviarás una notificación al usuario sobre la reserva exitosa de su cita con un mensaje en el chat. De forma similar al punto de conexión, también utilizarás Glitch para las pruebas. Puedes acceder al código y mezclarlo aquí.

El uso de Glitch es totalmente opcional y no es necesario para utilizar WhatsApp Flows. Puedes clonar el código del webhook desde GitHub y ejecutarlo en el entorno que prefieras.

Definir las variables de entorno

Para definir las variables de entorno, abre el archivo .env en Glitch. Configura VERIFY_TOKEN con cualquier cadena que prefieras, FLOW_ID con el identificador de tu flujo y GRAPH_API_TOKEN con el token de acceso de tu cuenta de WhatsApp Business. Puedes obtener el token de acceso desde el panel de tu app en Meta for Developers al hacer clic en Configuración de la API en la sección WhatsApp del panel de navegación izquierdo.

Gráfico de configuración de la API

En la página que aparece, haz clic en el botón Copiar que se encuentra en la tarjeta Token de acceso temporario. Pega la clave en tu archivo .env.

Suscribir el webhook en el panel de Meta

En tu cuenta en Meta for Developers, haz clic en el menú Configuración que se encuentra en la sección WhatsApp en el panel de navegación izquierdo.

Gráfico de configuración

En la tarjeta Webhook, haz clic en Editar. En el cuadro de diálogo que se abre, pega la URL de Glitch que copiaste y agrégale /webhook en el campo URL de devolución de llamada. En el campo Token de verificación, agrega el token de la variable VERIFY_TOKEN de tu archivo .env. Al finalizar, haz clic en Verificar y guardar. El cuadro de diálogo se cerrará y aparecerá la pantalla principal. Haz clic en Administrar y marca el campo mensajes. Tras seguir estos pasos, tu webhook estará listo.

Guía sobre el código del Webhook

El código contiene dos rutas: POST /webhook y GET /webhook. La ruta GET administra las solicitudes de verificación del webhook mediante la comprobación del token proporcionado con un token de verificación predefinido. Luego, responde con los códigos de estado adecuados y un token de desafío.

const verify_token = process.env.VERIFY_TOKEN; // Analiza los parámetros de la solicitud de verificación del webhook 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 webhook entrantes. Las solicitudes de webhook pueden tener diferentes cargas. De este modo, el código que aparece a continuación lee el mensaje y el número de teléfono de la empresa accediendo a los campos de solicitud de forma segura en caso de que no estén definidos.

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;

Luego, comprueba si la solicitud entrante es para un mensaje de tipo "text" que contenga la palabra "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 solicitud INIT al punto de conexión cuando se inicie para obtener la pantalla inicial y los datos.

if ( message.type === "text" && // para fines de demostración, envía el mensaje del flujo siempre que un usuario envíe un mensaje que contenga "cita" message.text.body.toLowerCase().includes("appointment") ) { // envía el mensaje del flujo según los documentos que figuran aquí 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: "¡Hola! 👋", }, body: { text: "¿Listo para transformar tu espacio? ¡Programa una consulta personalizada con nuestro equipo de expertos!", }, footer: { text: "Haz clic en el botón de abajo para continuar", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // reemplaza flow_token con un identificador único para este mensaje de flujo a fin de realizar un seguimiento del mismo en tu punto de conexión y webhook flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Programa una cita", flow_action: "data_exchange", }, }, }, }, }); } ...

Si el tipo de mensaje entrante no es "text", el código comprueba si el tipo de mensaje es "interactive." Un tipo interactivo "nfm_reply" significa que el mensaje entrante es una respuesta del flujo. Luego, envía de vuelta un mensaje "Reservaste una cita con éxito" al usuario.

... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // Enviar mensaje de confirmación 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: "Has reservado una cita exitosamente" }, }, }); } ...

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

... // marca el mensaje entrante como leído 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, }, }); ...
        

La experiencia del usuario

En este ejemplo, el usuario envía un mensaje a tu número que contiene la palabra "cita" y, luego, recibe el mensaje del flujo. También puedes optar por enviar el flujo después de una interacción diferente o como una plantilla de mensaje.

El usuario recibirá un mensaje de flujo con un botón de CTA para reservar una cita, donde podrá proporcionar sus datos. Luego, recibirá un mensaje de confirmación cuando complete el flujo.

Gráfico de envío del flujo al usuario

En esta guía, aprendiste a configurar un flujo de WhatsApp para programar citas de manera simple. Con la UI de Flow Builder, creaste un formulario para recopilar los datos de la cita de los usuarios.

Los flujos eliminan la necesidad de redireccionar a los usuarios a un sitio web externo para reservar citas, lo que mejora la experiencia del cliente. El proceso es sencillo y permite a los usuarios realizar reservas directamente en WhatsApp. Además de para programar citas, puedes usar WhatsApp Flows para recopilar comentarios sobre el servicio de atención al cliente o ayudar a los usuarios a registrarse en promociones o listas de correo. WhatsApp Flows también ofrece la posibilidad de conectarse con API externas u otras apps en tu punto de conexión.

Crear flujos de WhatsApp es muy sencillo con la UI de Flow Builder. Sin embargo, también puedes utilizar la API de flujos para crear flujos mediante programación. Para obtener más información, consulta la documentación de WhatsApp Flows.