Implementación Técnica: Chatbot de Captura de Leads (Determinístico)
Este documento describe la arquitectura, el modelo de decisión y la operación de un chatbot de captura de leads. El sistema evita depender de un LLM externo para el flujo principal de atención y captura: utiliza una máquina de estados orientada por intenciones, con validaciones explícitas y respuestas controladas por configuración.
En muchas Pymes, cuando la lógica de negocio es acotada y el objetivo es capturar demanda comercial, incorporar un LLM no siempre es un requisito técnico. En esos casos, un enfoque deterministico suele ofrecer mejor control de respuesta, costos estables y menor riesgo operativo.
1. Problema que resuelve
En preventa (consultas frecuentes, derivación a asesor y toma de contacto), la dificultad técnica está en clasificar bien la intención y cerrar datos obligatorios de contacto.
- Clasificar correctamente intención en mensajes breves y ambiguos.
- Completar datos obligatorios para handoff comercial.
- Evitar respuestas no autorizadas (precio, plazos, especificaciones no confirmadas).
- Mantener latencia y costo estables bajo picos de tráfico.
Bajo estas restricciones, el diseño determinístico ofrece una mejor relación señal/ruido que un flujo 100% generativo.
2. Arquitectura del sistema
El sistema se organiza en tres capas:
- Frontend (Next.js/React)
- Renderiza historial de conversación.
- Emite texto libre y
ui_payloadde quick replies. - Aplica UX de “typing” para humanizar respuestas instantáneas.
- API (FastAPI)
- Endpoint de chat.
- Coordinación con router de intención.
- Persistencia de leads al completar intentos de contacto.
- Motor de decisión (HybridRouter)
- Clasifica entrada con regex + keywords + umbrales de confianza.
- Administra estado conversacional (
IntentState). - Ejecuta estrategia de preguntas por slots hasta completar intención.
La separación evita acoplar UX, reglas de negocio y persistencia en una sola capa, y permite evolucionar cada componente con bajo impacto cruzado.
3. Modelo de decisión del HybridRouter
El router implementa un enfoque híbrido con prioridad explícita:
- Patrones regex (máxima precisión semántica en frases esperadas).
- Coincidencia por keywords (recuperación para variaciones léxicas).
- Fallback de baja confianza (evita respuestas incorrectas cuando no hay evidencia).
3.1. Scoring
El scoring privilegia evidencia fuerte:
def _score_entry(self, *, text: str, patterns: tuple[re.Pattern[str], ...], keywords: tuple[str, ...]) -> tuple[float, list[str]]:
# 1) Regex: intención altamente confiable
for pattern in patterns:
match = pattern.search(text)
if match:
score = 1.0 if match.start() == 0 and match.end() == len(text) else 0.95
return score, [f"regex:{pattern.pattern}"]
# 2) Keywords: evidencia parcial, útil para recall
if keywords:
hits = sum(1 for kw in keywords if kw.lower() in text.lower())
if hits:
kw_score = min(0.9, 0.45 + (hits / len(keywords)) * 0.55)
return kw_score, [f"keywords:{hits}/{len(keywords)}"]
return 0.0, []
El resultado incorpora trazabilidad (reasons) para debugging operativo y auditoría de clasificación.
3.2. Máquina de estados por intención
Cuando una intención entra en modo “en progreso”, la conversación deja de ser un simple turno-respuesta y pasa a un flujo transaccional:
@dataclass
class IntentState:
intent_id: str
slots: dict[str, str]
last_asked_slot: str | None
updated_at: float
Esto permite:
- Reanudar conversación en múltiples turnos.
- Validar y normalizar slot por slot.
- Evitar pérdida de contexto entre mensajes cortos.
4. Configuración declarativa de intenciones
La lógica de negocio se define por configuración, en lugar de repartirse en múltiples condicionales dentro del código. Cada intención especifica:
- Señales de activación (
patterns,keywords). - Slots requeridos y su orden.
- Validadores y quick replies.
- Plantilla final de cierre.
IntentConfig(
id="contacto_humano",
patterns=(r"\b(hablar|contactar)\s+con\s+asesor\b", ...),
slots=(
SlotConfig(
name="medio_contacto",
question="¿Preferís por Mail o WhatsApp?",
quick_replies=(
QuickReplyConfig("wa", "WhatsApp", "slot:medio_contacto:whatsapp"),
QuickReplyConfig("ml", "Mail", "slot:medio_contacto:mail"),
),
),
SlotConfig(name="dato_contacto", question="Por favor escribí tu número o mail:"),
),
final_template="¡Listo! Un asesor te contactará por {medio_contacto} al {dato_contacto}.",
)
4.1. Normalización de entrada
El sistema consume dos canales:
- Texto libre del usuario.
ui_payloademitido por botones.
Los payloads reducen entropía en la extracción de slots críticos y mejoran la tasa de completitud en embudos comerciales.
5. Integración API y persistencia de leads
Cuando una intención se completa, el backend dispara side-effects de negocio de forma explícita.
if decision.intent_completed and decision.matched_id == "contacto_humano":
slots = decision.intent_slots or {}
repo.store_contact_lead(
source="chatbot",
email=slots.get("dato_contacto") if "@" in slots.get("dato_contacto") else None,
phone=slots.get("dato_contacto") if "@" not in slots.get("dato_contacto") else None,
metadata={"slots": slots},
)
La condición de disparo evita persistir leads incompletos y hace idempotente la integración a nivel de intención finalizada.
6. Seguridad y superficie de riesgo
6.1. Riesgos reducidos frente a LLM remoto
Al no depender de generación libre para respuestas de negocio, se minimizan:
- Prompt injection para desviar políticas de respuesta.
- Exposición accidental de prompts del sistema.
- Variabilidad no controlada en salida ante inputs adversariales.
- Dependencia de costos variables por token.
6.2. Riesgos que sí siguen presentes
Un diseño determinístico no elimina riesgos generales de aplicación:
- Spam o abuso volumétrico en endpoints.
- Inyección en capas de persistencia si faltan validaciones.
- PII mal gestionada en logs.
- Configuraciones de regex ambiguas que produzcan falsos positivos.
Por eso se recomienda complementar con rate limiting, sanitización, políticas de retención y monitoreo por intención.
7. Operación, observabilidad y SLOs
Para operar este enfoque en producción, conviene medir al menos:
intent_match_rate: porcentaje de mensajes con intención clasificada por encima del umbral.slot_completion_rate: porcentaje de conversaciones que completan todos los slots requeridos.lead_conversion_rate: conversaciones iniciadas que terminan en lead persistido.fallback_rate: frecuencia de rutas de baja confianza.p95_latency_ms: latencia del endpoint de chat.
SLO típico para PyME en etapa inicial:
p95_latency_ms < 400para ruta determinística.slot_completion_rate > 70%en intención de contacto.fallback_rate < 15%en tráfico orgánico estabilizado.
Los umbrales exactos deben calibrarse por vertical y calidad de tráfico.
8. Trade-offs y límites del enfoque
Este modelo es superior cuando el dominio es acotado y altamente operacional (captura de lead, FAQ estable, triage inicial). No es la mejor opción para:
- Soporte abierto con alta variabilidad semántica.
- Tareas de redacción o reasoning multi-documento.
- Conversaciones largas donde el usuario espera respuestas creativas o explicativas complejas.
Una arquitectura híbrida realista es: determinístico para embudo comercial y derivación; generativo solo en zonas de bajo riesgo y con guardrails explícitos.
9. Conclusión técnica
Para muchas Pymes, el costo/beneficio de un chatbot de leads no mejora por agregar un LLM externo en el flujo principal. Un motor determinístico bien diseñado entrega:
- Comportamiento auditable.
- Costo predecible.
- Menor superficie de ataque asociada a prompts.
- Mejor control de calidad comercial en cada respuesta.
Más que discutir “IA sí o no”, conviene ajustar la complejidad técnica al objetivo de negocio y al nivel de confiabilidad que necesita la operación.