Portafolio profesional

Construyendo un bot que cualifica leads con IA sin perder la cabeza

Cómo automatizé la tediosa evaluación de leads usando un bot de Telegram y LLMs, manteniendo el historial de cada conversación y logueando todo en Google Sheets.

Artículo

# Construyendo un bot que cualifica leads con IA sin perder la cabeza ## El problema En Orbyn, nuestra empresa de automatización e IA, recibíamos leads por distintos canales: formularios web, correos, mensajes de LinkedIn y, sobre todo, Telegram. El proceso era manual y caótico: yo o alguien del equipo tenía que leer cada mensaje, evaluar si el prospecto encajaba con nuestro Perfil de Cliente Ideal (ICP), hacer preguntas de seguimiento si faltaba información y finalmente decidir si calificaba o no. Todo esto mientras intentábamos mantener un tono amable y profesional. El problema no era solo el tiempo perdido, sino la inconsistencia. A veces, en un día ajetreado, pasaba por alto detalles clave como el tamaño de la empresa o la ubicación geográfica. Otras veces, las conversaciones se perdían entre chats y teníamos que empezar de cero. Necesitábamos un sistema que actuara como una secretaria conversacional disponible 24/7, que aplicara criterios objetivos y mantuviera un registro impecable de cada interacción. ## Por qué Python + Hugging Face Podría haber usado una plataforma de chatbots "no-code" o servicios de automatización de marketing. Pero necesitaba control total sobre la lógica de negocio, la capacidad de integrarme directamente con nuestras herramientas internas (Google Sheets como fuente de verdad) y, sobre todo, quería que el sistema aprendiera y se adaptara con nosotros. Las soluciones tradicionales son rígidas: si mañana nuestro ICP cambia o queremos añadir un nuevo criterio, tengo que reconfigurar flujos enteros. Elegí Hugging Face como proveedor de LLM por una razón concreta: quería evitar el vendor lock-in de OpenAI. Con la API de Hugging Face Chat Completions, podía cambiar de modelo (usé DeepSeek-V3.2) con solo modificar una línea de configuración. Además, su pricing por token es transparente y me permitía monitorizar costos en tiempo real—algo crucial cuando estás escalando conversaciones. `python-telegram-bot` fue la elección obvia para el lado del bot: es la librería más madura, totalmente asíncrona, y su arquitectura basada en handlers se adapta perfectamente a flujos conversacionales complejos. ## Arquitectura ```mermaid flowchart LR User["Usuario Telegram"] API["Telegram Bot API"] Bot["main.py<br/>bot/handler.py"] LLM["qualifier/<br/>prompt.py + client.py"] Model["models/lead.py"] Sheets["sheets/client.py"] User -- mensaje --> API API -- polling --> Bot Bot --> LLM LLM --> Model Model --> Sheets Bot -- respuesta --> User ``` **Decisión 1: Contexto por hilo de conversación** En lugar de tratar cada mensaje como un evento aislado, el bot mantiene en memoria un historial completo por `chat_id`. Cuando llega un nuevo mensaje, se envía a Hugging Face no solo el contenido actual, sino los últimos 5 intercambios de ese mismo hilo. Esto le permite al LLM entender el flujo de la conversación y hacer preguntas coherentes. **Decisión 2: Respuestas estructuradas con Pydantic** Forzamos al LLM a que siempre devuelva un JSON con un esquema específico definido en `models/lead.py`. Usamos Pydantic para validar que cada respuesta tenga los campos `accion` (qualified/needs_info/disqualified), `razonamiento`, `campos_faltantes` y `preguntas`. Esto transforma la salida impredecible de un LLM en datos procesables por nuestro sistema. **Decisión 3: Logging atómico a Google Sheets** Cada interacción—sin importar el resultado—genera una nueva fila en la hoja de cálculo. Cada fila incluye un `conversacion_id` único que agrupa todos los mensajes de un mismo hilo. Esto nos permite reconstruir conversaciones completas y analizar patrones. **Decisión 4: Monitoreo de tokens por conversación** Cada llamada a la API de Hugging Face devuelve metadatos de uso de tokens. Los registro junto con cada interacción para poder calcular costos por lead y detectar prompts ineficientes. ## El trade-off que nadie te cuenta El mayor trade-off fue entre simplicidad y resiliencia. Mi primera implementación mantenía el historial de conversaciones en memoria dentro del objeto bot. Funcionaba perfectamente... hasta que el bot se reiniciaba. Perdías todo el contexto de conversaciones activas y los usuarios recibían respuestas sin memoria. La solución "correcta" sería usar una base de datos persistente como Redis. Pero eso añadía complejidad operacional: otro servicio que mantener, monitorizar, hacer backup. En su lugar, implementé un sistema híbrido: el historial se mantiene en memoria, pero cada interacción se persiste inmediatamente en Google Sheets. Si el bot se reinicia, al recibir un nuevo mensaje de un chat conocido, busca en Sheets las últimas filas de ese `conversacion_id` y reconstruye el contexto. No es perfecto (hay una pequeña latencia), pero es simple, no requiere infraestructura adicional y cumple con el requisito principal: no perder información valiosa. ```python # Fragmento de bot/handler.py - Reconstrucción de contexto async def get_conversation_context(chat_id): """Recupera el historial desde Sheets si se perdió en memoria""" if chat_id in bot.conversation_cache: return bot.conversation_cache[chat_id] # Buscar últimas filas para este chat_id en Sheets rows = await sheets_client.get_last_rows(chat_id, limit=5) context = [{"role": "user" if i%2==0 else "assistant", "content": row["mensaje"]} for i, row in enumerate(rows)] bot.conversation_cache[chat_id] = context return context ``` ## Resultado - **Reducción del 90% del trabajo manual**: Ya no tengo que leer y evaluar cada lead inicial. El bot maneja la primera interacción y solo me escalas conversaciones que ya están pre-cualificadas. - **Consistencia perfecta en criterios**: Cada lead se evalúa contra los mismos 4 puntos del ICP, sin excepciones ni sesgos humanos. - **Trazabilidad completa**: Puedo filtrar en Google Sheets por `conversacion_id` y ver exactamente cómo evolucionó cada oportunidad, qué preguntas hizo el bot y cómo respondió el prospecto. - **Costo controlado**: Monitorizando el uso de tokens por conversación, identificamos que prompts más concisos reducían costos en un 40% sin afectar la calidad. ## Lo que haría diferente Implementaría un sistema de circuit breaker para las llamadas a la API de Hugging Face. Hubo un par de ocasiones donde la API respondía lentamente o fallaba, y el bot se quedaba bloqueado esperando una respuesta. Si empezara de nuevo, añadiría un timeout configurable y un fallback a una respuesta predeterminada ("needs_info") cuando el LLM no responda en tiempo razonable. La experiencia del usuario es más importante que tener una evaluación perfecta en cada mensaje. --- ¿Querés profundizar en algún componente? Contactame o revisá el código en [https://github.com/juanalbarracin/lead-qualifier](https://github.com/juanalbarracin/lead-qualifier).