orlando kuan
Hice lo mismo pero python ocupando Fast-api, por si alguien le interesa. DarkCodePE/whatsapp-webhook-fast-api: This is a fast-api integration with whatsapp webhook.
Andres Cuervo
Buenisimo !
Carlos Ruminaus
Gracias Orlando!
Cuando un bot de WhatsApp empieza a crecer, mantener toda la lógica en un solo archivo deja de ser viable. Aplicar una arquitectura modelo vista controlador (MVC) te permite separar responsabilidades, escalar el proyecto y trabajar en equipo sin pisarte con otros desarrolladores. Esto es clave si tu bot va a integrar nuevos servicios, tipos de mensajes o funciones de la API de Meta.
¿Por qué separar la lógica de un bot de WhatsApp en varios archivos?
Un archivo único como server.js funciona al inicio, pero se vuelve un cuello de botella cuando llegan nuevas funciones. Al dividir el código por dominio, sabes exactamente dónde está cada error y dónde tocar para agregar mejoras.
La idea es simple: cada archivo tiene un objetivo claro. Si mañana necesitas agregar un nuevo tipo de mensaje, vas directo a WhatsAppService y modificas solo ahí. Nada más.
¿Qué es la arquitectura MVC en un bot? Es un patrón que separa el código en modelos, vistas y controladores. En este bot se traduce en carpetas como
config,controllers,routesyservices, cada una con una responsabilidad específica.
¿Cómo se estructura el proyecto dentro de la carpeta src?
Dentro de src se reparte la lógica que antes vivía en server.js [2:00]. La nueva estructura queda así:
webhookController, encargado de recibir y responder solicitudes hacia y desde Meta.messageHandler y WhatsAppService, que ejecutan acciones específicas con la API.Esta separación por dominio facilita identificar errores y trabajar en equipo sin que varias personas modifiquen el mismo archivo a la vez.
¿Qué hace el archivo app.js como punto de entrada?
app.js es ahora la raíz de la aplicación [4:30]. Inicializa Express, carga las variables de entorno desde config, conecta las rutas del webhook y levanta el servidor con un mensaje de log que confirma dónde se está ejecutando.
Es un archivo corto, limpio, sin lógica de negocio. Su único trabajo es orquestar.
¿Cómo funcionan las rutas y el webhook controller?
El archivo de rutas tiene dos acciones clave [5:30]:
router.post('/webhook'): dirige los mensajes entrantes hacia webhookController.handleIncoming.router.get('/webhook'): ejecuta verifyWebhook para validar la conexión con Meta usando un token.El webhookController concentra entonces dos responsabilidades. La primera es filtrar los mensajes que llegan desde la API y decidir qué hacer con ellos. La segunda es responder al handshake de verificación que Meta exige para confirmar que tu servidor es legítimo.
¿Cómo se manejan los mensajes con messageHandler y WhatsAppService?
Dentro de services viven las dos piezas que ejecutan el trabajo real con la API de WhatsApp [7:00].
messageHandler recibe el mensaje ya filtrado y revisa su tipo. Si es de tipo texto, prepara una respuesta y dispara dos acciones a través de WhatsAppService: replicar el mensaje al usuario y marcarlo como leído.
WhatsAppService está construido como una clase. Su método sendMessage recibe el número del usuario, el cuerpo del mensaje y el messageId. Dentro de un bloque try/catch arma la solicitud HTTP a la API usando el apiToken que viene desde config, envía la información y maneja cualquier error.
¿Qué hace el WhatsAppService? Es la clase que ejecuta llamadas directas a la API de WhatsApp. Envía mensajes, marca como leídos y centraliza la autenticación con el token, para que ningún otro archivo tenga que conocer esos detalles.
¿Qué ventajas trae esta estructura para escalar el bot?
Pensar la arquitectura desde el inicio te ahorra rehacer el proyecto más adelante. Algunos beneficios concretos:
apiToken desde un solo lugar.Hay código que todavía se repite entre sendMessage y markAsRead, y eso se puede optimizar más adelante refactorizando los métodos hacia una función común de solicitud HTTP. Es parte del proceso: primero separas, luego depuras.
El archivo server.js en la raíz se mantiene como referencia, pero deja de ser funcional. La aplicación ahora se sirve desde app.js. Compáralos lado a lado y observa cómo cada bloque encontró su lugar. ¿Tú estructurarías las carpetas de otra forma? Cuéntalo en los comentarios.
orlando kuan
Hice lo mismo pero python ocupando Fast-api, por si alguien le interesa. DarkCodePE/whatsapp-webhook-fast-api: This is a fast-api integration with whatsapp webhook.
Andres Cuervo
Buenisimo !
Carlos Ruminaus
Gracias Orlando!
Carlos Sebastian Gracia Yañez
Me hubiera gustado mas que de a poco construyéramos el modelo vista controlador explicando el paso a paso en vez de solo dar el recorrido. ni modo, toca hacer clon de una repo
jeison jose yamarte naranjo
hola , te recomiendo hacer clon pero escribelo tu mismo, y le puedes preguntar chat gpt cada parte eso ayudara bastante
Oscar Rubio
Alguien me puede ayudar con este error.
Despues de crear los archivos del MVC, al ejecutar el comando npm run start, me abre el archivo de nodemon.js y no se ejecuta el programa
Daniel Esteban Martínez Herrera
es en formato json no js
aquí se le puede cambiar el tipo de formato
Matias Ezequiel Rivero
Hola, por la imagen, también te falta cerrar el objeto con la llave de cierre " } "
Marcelo J. Gracia
me tome el trabajo de ver 2 veces este clase..
linea por linea.. incluso comparando archivos de los recursos y no logro hacer que el boot, me responda..
me marca el msg enviado pero no me hace el reply!.--
El error lo tengo en la clase WhastsAppService, en el sendMesage
Error sending message: {"message":"Request failed with status code 400","name":"AxiosError","stack":"AxiosError: Request failed with status code 400\n at settle (file:///home/marcelo/code/WhatsApp%20API/medPets/medpetsboot/node_modules/axios/lib/core/settle.js:19:12)\
Oscar Ivan Lozoya Rodríguez
Note dos pequeños errores que me hicieron jugar un buen rato con el código, les paso los tips:
1.- el código que se muestra puede que no compile ...nodemon la extensión correcta .json para mi caso y funciono
2.-el mensaje genera error 400 y no se hace el echo... quite el contexto
" context: { message_Id: messageId, }, " que estaba dentro del data
con estos cambios ya se responde con la nueva estructura, me quedo pendiente el marcar el mensaje como leído, pero eso se vera en próximas clases.. espero, saludos.
que
Sabina de la Serna Espinosa
que significa el error 400
Deivis Montenegro
Excelente clase!
Francisco Manuel Sanchez Morales
Comentario solo para promocionar Deno jaja Pero con Deno no es necesario usar axios, nodemon, y el uso de env es sencillo 🦕 igual estoy usando Hono como alternativa a express hasta ahora todo bien 👍🏼
Diego Felipe Sánchez Gómez
No me retorna el mensaje de echo
Diego Ivan Cundapi Leon
Es facil perderce en esta parte, venimos de instrucciones que ya cambiaron a la actualidad, en mi caso tuve que investigar en otros medios para llegar hasta aquí.
para que no se centren en codificar usen la IA de VS code, denle un objetivo, contexto, el resultado esperado y que pruebe si todavia funciona el servidor
Matias Ezequiel Rivero
Hola, buenas, alguien anda por esta clase/curso, ha cambiado bastante las versiones de la API (v25.0), la documentación y requisitos. Si alguno de los que este cursando esta parte le interesa podemos apoyarnos para actualizar las configuraciones a las actuales. Gracias. Saludos.
Matias Ezequiel Rivero
Actualmente 26-4-2026 no me deja configurar el webhook sin tener la aplicación publicada. Y para eso me pide configurar la img de la app, la categoría y una URL de intrucciones para la eliminación de datos del usuario (sin esto no deja publicar).
pedro martinez cruz
A mi en lo personal no me han checado varias cosas y de plano me ayudo con Claude.
Jefry David Concohá Castellanos
el peor curso que tienen en platzi no pierdan el tiempo
Salome Arboleda
muy aburridor este curso en serio, sale de la nada con cosas q uno ni entiende porque también tienen mucho afán, deberían actualizar esto
José Álvarez
Esta clase es la de calcular la masa del sol. Veniamos bien hasta que de pronto tengo que ir a revisar codigo en github para continuar el curso. No entiendo porque hacen estas cosas. Grabame 3 clases mas y hagamoslo en video...
ANDRES ALFONSO MIRA MEJIA
✅
Andrés Cardona
Algo no funciona, no está escuchando el puerto 3000, ayuda por favor
German Moreno Leiva
cambia a puerto 4000
Christian Suarez
Lo repliqué a .Net!!
Christian Suarez
Hay que usar ngrok para simular la salida a internet de localhost!
Ulqernesh Karvenae
Leidy Tatiana Rico Gamboa
el video da este mensaje, No se pudo cargar el vídeo debido a un fallo del servidor o de la red o porque el formato no es compatible., que se hace hay?
JUAN JOSE VILLA SORIA
Para crear las carpetas mencionadas en el video, simplemente debes estructurar tu proyecto de la siguiente manera:
src (source).src, crea las siguientes carpetas:
configcontrollersroutesservicesCada carpeta tendrá su propio archivo que manejará la lógica correspondiente. Por ejemplo, en config puedes tener un archivo para las variables de entorno, en controllers los controladores de tu bot y así sucesivamente. Esto te ayudará a mantener tu código organizado y escalable.
import axios from 'axios'; import config from '../config/env.js'; class WhatsAppService { async sendMessage({ to , type, content }) { try { await axios({ method: 'POST', url: `https://graph.facebook.com/${config.API_VERSION}/${config.BUSINESS_PHONE}/messages`, headers: { Authorization: `Bearer ${config.API_TOKEN}`, }, data: { messaging_product: 'whatsapp', to, type, ...content }, }); } catch (error) { console.error('Error sending message:', error); } } async markAsRead(messageId) { try { await axios({ method: 'POST', url: `https://graph.facebook.com/${config.API_VERSION}/${config.BUSINESS_PHONE}/messages`, headers: { Authorization: `Bearer ${config.API_TOKEN}`, }, data: { messaging_product: 'whatsapp', status: 'read', message_id: messageId, }, }); } catch (error) { console.error('Error marking message as read:', error); } } } export default new WhatsAppService(); ``````js import whatsappService from './whatsappService.js'; class MessageHandler { async handleIncomingMessage(message) { const isTypeText = message?.type === 'text' if (message?.text.body.includes('img')) { const content = { image: { link: "https://generative-ai-ultisaer.s3.sa-east-1.amazonaws.com/images/breaking_free_from_chains_converted.jpg", caption: "Cosmic Void Darkness" } } await whatsappService.sendMessage({ to: message.from, type: 'image', content}) await whatsappService.markAsRead(message.id); return } if (isTypeText) { const content = { text: { body: `Echo: ${message.text.body}` } } await whatsappService.sendMessage({ to: message.from, type: 'text', content}); await whatsappService.markAsRead(message.id); return } } } export default new MessageHandler(); ```Lo que se puede hacer es que al enviar un mensaje la responsabilidad caiga en el messageHandler haciendo mas modular y permitiendo enviar imgs y videos 