Biblioteca · Asistentes y bots de mensajería

Gestores de chatbots: NLP, intent routing y escalamiento inteligente a una persona

Creador60 minActualizado: octubre de 2026
58 de 105 en la biblioteca

Módulo: 19. Voice & Real-Time AI | Tiempo: ~25 min de teoría + 35 min de práctica


Lo esencial

🎨 Imagínalo así: un gestor de chatbot es el controlador aéreo de un aeropuerto. Cada mensaje de un cliente es un avión que pide aterrizar. El controlador decide al instante: este vuelo pregunta por un precio (pista A), este es un pasajero molesto (pista B), este es un cliente VIP con una compra grande (pista C: urgente, a control manual). Sin controlador hay caos y choques. Con él, cada avión recibe la instrucción correcta y aterriza donde debe.

La mayoría de los emprendedores construye chatbots así: el bot responde preguntas frecuentes y ya. Es como un aeropuerto con una sola pista. En esta lección vas a aprender a construir un sistema con enrutamiento inteligente: el bot entiende la intención, extrae los datos clave y sabe cuándo puede responder solo y cuándo debe pasar la conversación de inmediato a una persona.


Conceptos clave

  • Intent classification: detectar automáticamente la intención del usuario (pregunta, queja, compra, pedir a un operador) con Claude Haiku o con reglas
  • Entity extraction: extraer datos concretos del mensaje: nombres, montos, fechas, claves de producto
  • Intent routing: la lógica que dirige el diálogo: reglas (palabras clave) vs clasificación con IA vs un enfoque híbrido
  • Escalation triggers: el conjunto de condiciones en las que el bot pasa la conversación a una persona: enojo del cliente, petición directa, pregunta compleja, monto grande
  • State management: guardar el contexto de la conversación entre mensajes con Redis o Supabase
  • Multi-channel unification: una misma lógica de enrutamiento para WhatsApp, Telegram y un widget web
  • Fallback handling: qué hacer cuando el bot no entendió la intención: pedir una aclaración, ofrecer opciones o escalar de inmediato
  • Métricas de calidad: escalation rate (% pasado a operadores), resolution rate (% resuelto por el bot), CSAT (la calificación del cliente)

Teoría

Qué es la clasificación de intenciones y para qué sirve

Cuando un cliente escribe "quiero devolver un producto", no es solo texto. Es una intención (devolución) que requiere un guion de respuesta concreto. Cuando escribe "¿¡ustedes están bien!?", es una señal de alarma: el cliente está molesto, es riesgoso que responda el bot y hace falta una persona.

La clasificación de intenciones es poner automáticamente una etiqueta de intención a cada mensaje que llega. Sin eso, el bot trabaja como un cajero novato: no entiende qué le piden e intenta responder a todo con la misma plantilla.

Categorías básicas de intención para un bot de negocio:

  • FAQ: preguntas sobre precios, condiciones de envío, horario
  • Complaint: queja, problema, algo negativo
  • Purchase: interés en comprar, pedir asesoría
  • Support: pregunta técnica, problema con un pedido
  • Human request: petición explícita de hablar con un operador ("quiero hablar con un asesor")
  • Out of scope: algo que no viene al caso, spam

Cómo funciona la clasificación: tres enfoques

Enfoque 1: reglas (keywords)

El más simple. Un diccionario de palabras clave por intención. "devol", "reembolso" → devolución. "precio", "cuánto cuesta" → FAQ de precio. Rápido, barato, predecible. La desventaja: es frágil. "Quiero devolverles el favor recomendándolos" se marcaría como devolución por error.

Enfoque 2: clasificación con IA (Claude Haiku)

Le pasamos el mensaje al modelo y le pedimos que lo clasifique. Es más preciso, entiende el contexto y el sarcasmo. Cuesta más que las reglas (pagas por tokens) y es un poco más lento. A cambio, se equivoca bastante menos con el lenguaje real. El costo por mensaje es pequeño y se calcula por tokens: precios vigentes en la página Lo vigente.

Enfoque 3: híbrido (recomendado)

Primero revisamos las reglas: si coinciden con mucha certeza, usamos la regla (barato). Si no, mandamos el mensaje a Claude Haiku (preciso). Es el mejor equilibrio entre velocidad y costo para bots con mucho tráfico.

🎨 Imagínalo así: el enrutamiento híbrido es como la banda clasificadora del correo. Las cartas normales van por la banda automática (reglas). Los paquetes raros van con un clasificador de carne y hueso (IA). Mucho rendimiento y pocos errores.

Entity extraction: lo que esconde el texto

Además de la intención, importa extraer datos concretos. El cliente escribe: "quiero pedir 3 cajas para el viernes a Av. Juárez 15". Aquí hay:

  • Cantidad: 3
  • Fecha límite: viernes
  • Dirección: Av. Juárez 15

Sin extracción de entidades, el bot responde "ok, lo registramos" y pierde toda la información. Con ella, pasa los datos al CRM, revisa el inventario y dice si llegamos para el viernes.

Para extraer entidades también usamos Claude o regex. Claude maneja mejor las frases imprecisas ("pasado mañana", "unos cien dólares más o menos").

Escalation triggers: cuándo pasar a una persona

Es la parte más importante del sistema. Si escalas mal (muy poco), el cliente se va enojado. Si escalas demasiado, el operador se ahoga en preguntas sencillas.

El escalamiento inteligente se basa en varias señales:

1. Petición explícita de un operador "quiero hablar con un asesor", "pásame con una persona", "llámenme": el bot escala de inmediato, sin preguntas.

2. Detector de frustración Analizamos el tono: mayúsculas, signos de exclamación, palabras indicadoras ("pésimo", "horrible", "nunca más", "devuélvanme mi dinero"). Claude Haiku regresa un sentiment score. Si pasa del umbral, se escala.

3. Solicitud compleja La intención no se reconoció 2 veces seguidas. La pregunta requiere información de varios sistemas. Es una pregunta legal o financiera (según las reglas de la empresa).

4. Prospecto de alto valor El monto de la solicitud > $N. El cliente pregunta por el plan corporativo. Mencionó a la competencia: hace falta atención personal.

5. Fallback del sistema Después de 3 intentos fallidos de responder, se escala automáticamente. Es mejor pasarlo a una persona que desesperar al cliente con un interminable "no entendí".

State management: la memoria de la conversación

🎨 Imagínalo así: una buena mesera recuerda lo que pediste aunque se haya distraído con otras mesas. Un bot sin estado es como una mala mesera: pregunta "¿en qué le puedo ayudar?" cada vez, incluso a mitad de la conversación.

Para guardar el estado usamos Redis (rápido, para las sesiones activas) o Supabase (persistente, para el historial). Las conversaciones con clientes contienen datos personales: guarda solo lo necesario, limita el tiempo de conservación y no le mandes al modelo nada de más. En el estado guardamos:

python
{
    "session_id": "uuid",
    "user_id": "telegram_chat_id",
    "history": [...],       # los últimos N mensajes
    "current_intent": "complaint",
    "entities": {"order_id": "12345"},
    "frustration_score": 0.3,
    "escalated": False,
    "turns_without_resolution": 1
}

Con cada mensaje nuevo actualizamos el estado y decidimos el enrutamiento tomando en cuenta todo el historial, no solo la última frase.

Multicanal: un motor, muchos canales

La lógica de intent routing no debe estar atada a Telegram ni a WhatsApp. La arquitectura correcta:

Código
[Telegram] → adapter → [Intent Engine] → action → [Telegram response]
[WhatsApp] → adapter → [Intent Engine] → action → [WhatsApp response]
[Web Widget] → adapter → [Intent Engine] → action → [Web response]

El adaptador normaliza el mensaje entrante a un formato único. El Intent Engine funciona igual para todos. La respuesta se arma en el formato de cada canal. Así puedes atender 3 canales con un solo código.

Alternativas: Botpress, Dialogflow, Rasa

Botpress: plataforma open source con un constructor visual de diálogos. Buena para árboles de diálogo complejos, con NLU integrado. Elígela si tu equipo no es técnico y necesitas un editor visual.

Dialogflow (Google): una solución enterprise con un NLU potente. Se integra bien con Google Workspace. Más cara y con dependencia del proveedor. Elígela para proyectos corporativos grandes.

Rasa: open source con la máxima flexibilidad. Requiere experiencia en ML. Elígela si necesitas instalarla en tus propios servidores y control total de los datos.

Claude directo (nuestro enfoque): la mejor opción para fundadores en solitario y equipos pequeños. Arranque rápido, costos bajos, flexibilidad. Haiku para clasificar, Sonnet para respuestas complejas.

Monitoreo: tres métricas clave

Escalation rate: el porcentaje de conversaciones que pasan a un operador. No hay una norma única: depende del nicho y de lo complejas que sean las preguntas. Si casi todas las conversaciones van al operador, el bot no sirve. Si casi ninguna, quizá el bot no escala cuando debería.

Resolution rate: el porcentaje de preguntas que el bot resuelve sin operador. Pon tu meta a partir de tu propio piloto, no con cifras ajenas. Sube conforme mejora la base de conocimiento.

CSAT (Customer Satisfaction Score): la calificación del cliente al terminar el diálogo (1-5). Mídela por separado para las sesiones con bot y con operador. La diferencia te muestra dónde está el punto débil.

El tablero de monitoreo lo armamos en Grafana o con un simple comando /stats en el chat del dueño.

Código: pipeline de intent routing en Python + FastAPI

python
import os
import json
import redis
from fastapi import FastAPI, Request
from anthropic import Anthropic

app = FastAPI()
client = Anthropic()
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

ESCALATION_PHRASES = [
    "hablar con un asesor", "operador", "una persona",
    "pásame con", "call me", "llámenme"
]

FRUSTRATION_WORDS = [
    "pésimo", "horrible", "nunca", "estafadores",
    "devuélvanme mi dinero", "engaño", "una vergüenza"
]


def get_session(session_id: str) -> dict:
    data = r.get(f"session:{session_id}")
    if data:
        return json.loads(data)
    return {
        "history": [],
        "frustration_score": 0.0,
        "turns_without_resolution": 0,
        "escalated": False,
        "current_intent": None
    }


def save_session(session_id: str, state: dict):
    r.setex(f"session:{session_id}", 3600, json.dumps(state))


def check_escalation_rules(message: str, state: dict) -> tuple[bool, str]:
    """Reglas de escalamiento: revisión rápida sin IA."""
    msg_lower = message.lower()
    
    # Petición explícita de un operador
    if any(phrase in msg_lower for phrase in ESCALATION_PHRASES):
        return True, "explicit_request"
    
    # Detector de frustración por palabras
    frustration_hit = sum(1 for w in FRUSTRATION_WORDS if w in msg_lower)
    if frustration_hit >= 2:
        return True, "frustration_detected"
    
    # Frustración acumulada en la sesión
    if state["frustration_score"] > 0.7:
        return True, "cumulative_frustration"
    
    # El bot no pudo ayudar 3 veces seguidas
    if state["turns_without_resolution"] >= 3:
        return True, "repeated_fallback"
    
    return False, ""


def classify_intent(message: str, history: list) -> dict:
    """Claude Haiku clasifica la intención y extrae las entidades."""
    history_text = "\n".join([
        f"{m['role']}: {m['content']}" for m in history[-4:]
    ])
    
    response = client.messages.create(
        model="claude-haiku-4-5",   # revisa que el modelo siga disponible en la API; modelos vigentes: la página «Lo vigente»
        max_tokens=300,
        system="""Eres un clasificador de intenciones para el chatbot de una tienda en línea.
Regresa un JSON con los campos:
- intent: uno de [faq_price, faq_delivery, faq_return, complaint, purchase_intent, order_status, out_of_scope]
- confidence: 0.0-1.0
- entities: un objeto con los datos extraídos (order_id, amount, date, product)
- sentiment: positive/neutral/negative
- frustration_score: 0.0-1.0

Solo JSON, sin explicaciones.""",
        messages=[{
            "role": "user",
            "content": f"Historial del diálogo:\n{history_text}\n\nMensaje nuevo: {message}"
        }]
    )
    
    try:
        return json.loads("".join(b.text for b in response.content if b.type == "text"))
    except Exception:
        return {
            "intent": "out_of_scope",
            "confidence": 0.0,
            "entities": {},
            "sentiment": "neutral",
            "frustration_score": 0.3
        }


def generate_bot_response(intent: str, message: str, entities: dict, history: list) -> str:
    """Generamos la respuesta según la intención."""
    
    intent_prompts = {
        "faq_price": "Responde la pregunta sobre el precio del producto. Si no hay un producto concreto, pide que lo aclare.",
        "faq_delivery": "Responde sobre el envío: de 2 a 5 días, gratis en compras desde $50.",
        "faq_return": "Responde sobre devoluciones: 14 días sin dar explicaciones, se necesita el comprobante.",
        "complaint": "Recibe la queja con empatía. Pide los detalles necesarios para resolverla.",
        "purchase_intent": "Ayuda con la compra. Aclara los detalles y ofrece agregarlo al carrito.",
        "order_status": "Pide el número de pedido si no lo dio.",
    }
    
    prompt = intent_prompts.get(intent, "Di con amabilidad que no entendiste la pregunta y ofrece opciones.")
    
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=200,
        system=f"Eres un asistente amable de una tienda en línea. {prompt}. Responde corto: 1-3 oraciones.",
        messages=[{"role": "user", "content": message}]
    )
    return "".join(b.text for b in response.content if b.type == "text")


async def notify_operator(session_id: str, message: str, reason: str, state: dict):
    """Aviso al operador por Telegram (o por el canal que use tu equipo)."""
    import httpx
    
    bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
    operator_chat_id = os.environ.get("OPERATOR_CHAT_ID")
    
    history_preview = "\n".join([
        f"{'👤' if m['role']=='user' else '🤖'} {m['content']}"
        for m in state["history"][-3:]
    ])
    
    text = (
        f"🚨 Escalamiento | {reason}\n"
        f"Sesión: {session_id}\n\n"
        f"Últimos mensajes:\n{history_preview}\n\n"
        f"Último: {message}"
    )
    
    async with httpx.AsyncClient() as http:
        await http.post(
            f"https://api.telegram.org/bot{bot_token}/sendMessage",
            json={"chat_id": operator_chat_id, "text": text}
        )


@app.post("/chat")
async def chat_endpoint(request: Request):
    body = await request.json()
    session_id = body["session_id"]
    message = body["message"]
    
    state = get_session(session_id)
    
    # Revisión rápida de las reglas de escalamiento
    should_escalate, escalation_reason = check_escalation_rules(message, state)
    
    if should_escalate and not state["escalated"]:
        state["escalated"] = True
        state["history"].append({"role": "user", "content": message})
        save_session(session_id, state)
        await notify_operator(session_id, message, escalation_reason, state)
        return {
            "response": "Entendido. Te paso con un asesor: te responderá en menos de 5 minutos.",
            "escalated": True,
            "reason": escalation_reason
        }
    
    # Clasificación de la intención con IA
    classification = classify_intent(message, state["history"])
    intent = classification.get("intent", "out_of_scope")
    confidence = classification.get("confidence", 0.0)
    
    # Actualizamos el estado
    state["current_intent"] = intent
    state["frustration_score"] = max(
        state["frustration_score"],
        classification.get("frustration_score", 0.0)
    )
    
    # Poca certeza: lo contamos como no resuelto
    if confidence < 0.5 or intent == "out_of_scope":
        state["turns_without_resolution"] += 1
    else:
        state["turns_without_resolution"] = 0
    
    # Generamos la respuesta
    bot_response = generate_bot_response(
        intent, message,
        classification.get("entities", {}),
        state["history"]
    )
    
    # Guardamos el historial
    state["history"].append({"role": "user", "content": message})
    state["history"].append({"role": "assistant", "content": bot_response})
    state["history"] = state["history"][-20:]  # Guardamos los últimos 20 mensajes
    save_session(session_id, state)
    
    return {
        "response": bot_response,
        "intent": intent,
        "confidence": confidence,
        "escalated": False
    }

Este código corre como un servicio de FastAPI. Un bot de Telegram, un adaptador de WhatsApp o un widget web mandan solicitudes POST a /chat y reciben una respuesta con la intención, la certeza y la marca de escalamiento.


Práctica

Paso 1: preparar el entorno (5 min)

bash
mkdir chatbot-manager && cd chatbot-manager
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn anthropic redis httpx python-dotenv

# archivo .env
echo "ANTHROPIC_API_KEY=your_key" >> .env
echo "TELEGRAM_BOT_TOKEN=your_bot_token" >> .env
echo "OPERATOR_CHAT_ID=your_chat_id" >> .env

# Levantamos Redis en local (o usamos Redis Cloud)
docker run -d -p 6379:6379 redis:alpine

Paso 2: configura la base de conocimiento para las FAQ (10 min)

Crea el archivo knowledge_base.json con las respuestas a las preguntas típicas de tu negocio. Estructura:

json
{
  "faq_price": "Nuestros productos empiezan desde $10. Catálogo actualizado: shop.example.com/catalog",
  "faq_delivery": "Envío en 2-5 días hábiles. Gratis en compras desde $50.",
  "faq_return": "Devoluciones dentro de 14 días. Se necesita el comprobante y el empaque original. Regresamos el dinero en 3-5 días."
}

Reemplaza los intent_prompts del código con las respuestas reales de tu base de conocimiento.

Paso 3: arranca y prueba el enrutamiento (10 min)

bash
uvicorn main:app --reload --port 8000

Prueba con curl o Postman:

bash
# Pregunta normal
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test1", "message": "¿cuánto cuesta el envío?"}'

# Prueba de escalamiento
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test2", "message": "esto es PÉSIMO, ¡¡devuélvanme mi dinero ya!!"}'

# Petición explícita de un operador
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test3", "message": "quiero hablar con una persona"}'

Comprueba que el aviso al operador llega cuando hay escalamiento.

Paso 4: conéctalo a un bot de Telegram (8 min)

Telegram es el canal más fácil para un primer prototipo: su Bot API es gratuita y no requiere aprobación. Para WhatsApp hace falta la API de WhatsApp Business de Meta; el adaptador se arma con la misma idea.

python
# telegram_adapter.py
from telegram.ext import Application, MessageHandler, filters
import httpx, os

async def handle_message(update, context):
    session_id = str(update.effective_chat.id)
    message = update.message.text
    
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "http://localhost:8000/chat",
            json={"session_id": session_id, "message": message}
        )
        data = resp.json()
    
    await update.message.reply_text(data["response"])

app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()

Paso 5: configura el monitoreo (5 min)

Agrega un endpoint /stats para ver rápido las métricas desde Redis:

python
@app.get("/stats")
async def get_stats():
    keys = r.keys("session:*")
    total = len(keys)
    escalated = sum(1 for k in keys if json.loads(r.get(k)).get("escalated"))
    return {
        "total_sessions": total,
        "escalated": escalated,
        "escalation_rate": f"{escalated/total*100:.1f}%" if total else "0%",
        "active_last_hour": total  # simplificado
    }

Herramientas y recursos

Herramienta Para qué Precio
Claude Haiku Intent classification, entity extraction Pago por tokens; a octubre de 2026: $1 / $5 por millón de tokens (entrada / salida)
Redis Guardar el estado de las sesiones (rápido) Gratis instalado por tu cuenta; en la nube, según el plan del proveedor
Supabase Historial persistente de los diálogos Tiene un nivel gratuito con límites
FastAPI Backend para los endpoints de webhook Gratis, open source
Botpress Constructor visual de diálogos (alternativa) Tiene plan gratuito; planes en su sitio
Dialogflow NLU enterprise (cuando necesitas una plataforma) Pago por uso; planes en el sitio de Google Cloud
Rasa En tus servidores, control máximo Gratis, open source
Telegram Bot API Canal + avisos a operadores Gratis
WhatsApp Business Platform Canal principal para clientes en Latinoamérica Condiciones y precios en el sitio de Meta
Grafana Tablero de monitoreo de métricas Gratis instalado por tu cuenta

Stack recomendado para empezar: FastAPI + Claude Haiku + Redis + Telegram: se arma rápido y cuesta poco. Calcula el gasto en el modelo así: dos llamadas a Haiku por mensaje (clasificación y respuesta) × tokens × precio por millón de tokens. Haz un piloto con un centenar de mensajes y revisa usage en las respuestas de la API.


Conclusiones clave

"Un bot sin intent routing es un cajero que a todo responde 'el número tres'. Un bot con routing es un controlador que sabe a dónde mandar cada solicitud."

"La regla de escalamiento es sencilla: si dudas, pásalo a una persona. Es mejor gastar tiempo del operador que perder un cliente por una mala respuesta del bot."

"El escalation rate es el KPI honesto de tu bot. Si se dispara, el bot no funciona. Si es casi cero, quizá escala muy poco y los clientes se van sin decir nada."


Siguiente lección

→ Analítica de producto: PostHog, Mixpanel e insights inteligentes

La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso