03. ECode: Compras Inteligentes en Telegram con Gemini NLP
En el comercio electrónico moderno, el cliente no quiere esperar. El éxito de plataformas como Shopify o Vercel radica en la velocidad; sin embargo, otra tendencia está cambiando el panorama: el comercio conversacional. En lugar de abrir un navegador, navegar por menús y pasar por un carrito tradicional, ¿por qué no comprar enviando un simple mensaje de texto en tu aplicación de mensajería preferida?
En esta entrega, exploraremos cómo construimos ECode, el chatbot asistente de compras de DevMart en Telegram, utilizando Inteligencia Artificial. Descubriremos cómo el backend interpreta el lenguaje natural del usuario y analizaremos tres patrones clave de diseño de software que garantizan la resiliencia y el rendimiento del webhook bajo cualquier circunstancia.
🤖 El Cerebro del Chatbot: Extracción de Entidades con Gemini AI
Para que un bot de Telegram sea verdaderamente útil, debe dejar de lado los rígidos comandos del estilo /comprar_producto_1 y entender frases humanas como: "Quiero comprar 2 teclados mecánicos" o "¿Pueden mostrarme el catálogo?".
En lugar de construir analizadores sintácticos rígidos o usar expresiones regulares propensas a fallos, en DevMart integramos el SDK oficial de Google Gen AI (@google/genai) y la API del modelo Gemini 3.1 Flash Lite.
El modelo actúa como un extractor de entidades en tiempo real. Mediante un prompt estructurado de sistema en nlp.service.js, obligamos a la IA a retornar exclusivamente un objeto JSON estructurado con la intención y variables del usuario:
// src/services/nlp.service.js
export const callingGeminiApi = async (rawText) => {
const prompt = `
Actúa como un extractor de entidades para un backend de e-commerce.
Analiza el texto del usuario en Telegram y extrae los datos estructurados.
Devuelve ESTRICTAMENTE un JSON con esta estructura exacta:
{
"intent": "buy" | "inquiry" | "link_account" | "verify_otp" | "view_cart" | "list_products" | "favorites" | "other" | "help",
"product": "nombre del producto" (o null),
"quantity": numero entero (por defecto 1),
"email": "correo electrónico válido" (o null),
"otp": "código numérico de 6 dígitos" (o null)
}
... (Reglas de clasificación de intención) ...
`;
const response = await ai.models.generateContent({
model: "gemini-3.1-flash-lite",
contents: prompt,
config: { responseMimeType: 'application/json' }
});
return JSON.parse(response.text);
}
¿Por qué gemini-3.1-flash-lite?
Porque para tareas de clasificación de texto y estructuración de JSON (donde la latencia es crítica), los modelos gigantes como Ultra son demasiado lentos y costosos. El modelo Flash Lite nos ofrece un tiempo de respuesta inferior a 1 segundo por mensaje a una fracción del costo, ideal para flujos conversacionales interactivos.
🛡️ Tres Patrones de Resiliencia en el Webhook de Telegram
Integrar un webhook público que recibe eventos externos requiere precauciones estrictas. Durante el desarrollo del chatbot de DevMart, implementamos tres soluciones clave de ingeniería en el backend para resolver problemas típicos del API de Telegram:
sequenceDiagram
participant Telegram
participant Middleware as validateTelegramPayload
participant Controller as handleTelegramMessage
participant DB as Prisma (DB)
Telegram->>Middleware: POST /webhook/telegram (update_id: 101)
Note over Middleware: Verifica Secret Token
Note over Middleware: Verifica Idempotencia (101)
Middleware->>Controller: Pasa validación
Controller->>Telegram: Envía inmediatamente 200 OK (Libera canal)
Note over Controller: Lógica lenta (Gemini NLP + DB)
Controller->>DB: Busca Cliente & Actualiza Carrito
Controller->>Telegram: Envía respuesta final al chat (HTML/Texto)
1. Liberación Inmediata de la Conexión (Async Loopback)
Telegram tiene una regla estricta: si tu webhook tarda más de 30 segundos en responder con un código de estado HTTP 200 OK, Telegram da por perdida la conexión, aborta el request y vuelve a enviar el mismo mensaje una y otra vez. Esto puede saturar tu servidor en segundos.
Dado que llamar a la API de Gemini y consultar la base de datos puede tardar de 1 a 3 segundos, lo primero que hace el controlador en telegram.controller.js al recibir la petición es liberar la conexión:
// src/controllers/telegram.controller.js
export const handleTelegramMessage = async (request, response) => {
try {
// 🚀 Liberamos a Telegram inmediatamente
response.status(200).send('OK');
const { message } = request.body;
if (!message || !message.text) return;
// ... (A partir de aquí, el procesamiento continúa en segundo plano de forma asíncrona)
}
// ...
}
2. Idempotencia en el Middleware
Para evitar que un mensaje se procese dos veces (por ejemplo, si el cliente presiona el botón dos veces o hay retraso en la red), implementamos un sistema de idempotencia en validateTelegramPayload.js.
Cada mensaje de Telegram incluye un identificador único llamado update_id. El backend guarda los identificadores procesados recientemente en un Set en memoria. Si un update_id duplicado llega antes de limpiar la memoria, es ignorado de inmediato:
// src/utils/validateTelegramPayload.js
const processedUpdates = new Set();
export const validateTelegramPayload = (request, response, next) => {
const body = request.body;
const updateId = body.update_id;
if (processedUpdates.has(updateId)) {
console.log(`[Telegram] Update ${updateId} ya procesado, ignorando.`);
return response.status(200).send('OK'); // Respondemos OK pero no hacemos nada
}
processedUpdates.add(updateId);
next();
};
💡 Consejo de negocio: La idempotencia evita errores críticos como cobrar o añadir unidades duplicadas al carrito por fallos temporales de conexión.
3. Fallback de Envío de Mensaje Recursivo en Texto Plano
Telegram permite enriquecer el texto del chat con negritas y cursivas usando etiquetas HTML. Sin embargo, si cometes un pequeño error de sintaxis al formatear (por ejemplo, abrir una etiqueta <b> sin cerrarla), la API de Telegram rechazará la petición HTTP completa con un error 400 Bad Request y el usuario nunca recibirá el mensaje.
Para solucionar esto, en telegram.service.js implementamos un patrón fallback recursivo que elimina el formato si la API da error:
// src/services/telegram.service.js
export const sendMessageToTelegram = async (chatId, text, isRetry = false) => {
try {
const payload = { chat_id: chatId, text: text };
if (!isRetry) payload.parse_mode = 'HTML';
const response = await api.post('/sendMessage', payload);
return response.data;
} catch (error) {
// Si falla por formato HTML incorrecto
if (!isRetry && error.response?.status === 400) {
console.warn("⚠️ Telegram rechazó HTML. Activando Fallback a texto plano...");
// Expresión regular que remueve etiquetas HTML
const plainText = text.replace(/<[^>]*>?/gm, '');
// Llamada recursiva garantizada
return await sendMessageToTelegram(chatId, plainText, true);
}
console.error("❌ ERROR CRÍTICO AL ENVIAR MENSAJE:", error.message);
}
}
🔗 Vinculación con Mini Apps de Telegram
Cuando un usuario interactúa con el bot por primera vez, este le solicita vincular su cuenta web ingresando su correo electrónico. Tras verificar un código OTP enviado por correo electrónico, si el usuario aún no existe en el sistema web, el bot detecta esta condición y le facilita el registro de inmediato mediante una Mini App de Telegram:
if (!customerEmailExists) {
await sendMessageToTelegram(chatId, "\nPor favor, regístrate primero en nuestra página web.");
const webAppUrl = `${process.env.FRONTEND_URL}/telegram-signup`;
const payload = {
chat_id: chatId,
text: "👋 ¡Hola! Para comprar, necesitas una cuenta. Créala en 10 segundos aquí:",
reply_markup: {
inline_keyboard: [[
{
text: "📝 Crear Cuenta Rápida",
web_app: { url: webAppUrl } // 💡 Abre la Mini App
}
]]
}
};
await axios.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, payload);
return;
}
Al pulsar en 📝 Crear Cuenta Rápida, el cliente se registra dentro de Telegram usando la web móvil incrustada en la app de mensajería (Mini App). El registro almacena su telegramId de forma transparente y le permite reanudar sus compras conversacionales instantáneamente.
En el siguiente post de la bitácora, analizaremos los aspectos de seguridad del backend y el despliegue serverless rentable en Google Cloud Run.
📅 Publicado en la Bitácora de Desarrollo de DevMart. Ver Índice General