Improve intent detection: keyword layer + better Gemini prompt

- Add detectarIntencionKeyword() with regex patterns in Colombian Spanish
  that runs before Gemini (instant, no API cost)
- Gemini is now only called for ambiguous text
- Improve Gemini prompt with real examples and informal Spanish
- Replace generic "no entendí" with actionable buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro
2026-07-14 15:25:39 +00:00
co-authored by Claude Sonnet 4.6
parent a21675b12c
commit e43fe4043f
2 changed files with 81 additions and 18 deletions
+56 -6
View File
@@ -352,13 +352,24 @@ class PublicChat extends Component
return;
}
$accionIA = $this->detectarIntencionIA($texto);
$accion = $this->detectarIntencion($texto);
if ($accionIA) {
$this->clickBoton($accionIA['action'], $accionIA['data'] ?? []);
if ($accion) {
$this->clickBoton($accion['action'], $accion['data'] ?? []);
} else {
$this->guardarMensajeBot($this->convId, "No entendi tu mensaje. Usa los botones o escribe lo que deseas:");
$this->mostrarMenuPrincipal($this->convId);
ChatMessage::create([
'conversation_id' => $this->convId,
'tipo' => 'bot',
'tipo_ui' => 'buttons',
'contenido' => "No entendí bien tu mensaje. Puedes escribir cosas como:\n\"ver planes\", \"hacer recarga\", \"mis credenciales\", \"ver promociones\" o usar los botones:",
'payload' => ['botones' => [
['label' => 'Ver planes', 'action' => 'servicios.listar', 'data' => []],
['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []],
['label' => 'Mis credenciales','action' => 'credenciales.listar','data' => []],
['label' => 'Menú principal', 'action' => 'menu.principal', 'data' => []],
]],
'leido' => true,
]);
}
$this->cargarMensajes();
@@ -1252,8 +1263,47 @@ class PublicChat extends Component
}
// ─────────────────────────────────────────────────────────
// IA: detectar intencion
// ─────────────────────────────────────────────────────────
// Detección de intención: keywords primero, Gemini como fallback
// ─────────────────────────────────────────────────────────
private function detectarIntencion(string $texto): ?array
{
return $this->detectarIntencionKeyword($texto)
?? $this->detectarIntencionIA($texto);
}
private function detectarIntencionKeyword(string $texto): ?array
{
$t = mb_strtolower(trim($texto));
$patrones = [
// Recarga de saldo
'recarga.iniciar' => '/recarg|recargar|cargar\s*(saldo|cuenta|dinero|plata)|abonar|depositar|agregar\s*(saldo|dinero|plata)|quiero\s*poner\s*saldo/',
// Ver servicios / planes
'servicios.listar' => '/servicio|plan(es)?|cat[aá]logo|qu[eé]\s*(hay|tienen|tienes)|ver\s*plan|netflix|disney|hbo|prime|spotify|crunchyroll|paramount|streaming|aplicacion|aplicaci[oó]n/',
// Promociones
'promo.listar' => '/promo(ci[oó]n)?|oferta|descuento|especial/',
// Credenciales / acceso
'credenciales.listar' => '/credencial|contrase[ñn]a|clave|password|acceso|usuario|mi\s*cuenta|mis\s*datos\s*de\s*acceso|c[oó]mo\s*(entro|accedo|me\s*conecto)/',
// Historial de compras
'historial.ver' => '/historial|compra(s)?|pedido(s)?|lo\s*que\s*(compr[eé]|he\s*comprado)|mis\s*compras/',
// Perfil
'perfil.ver' => '/perfil|mis\s*datos|mi\s*informaci[oó]n|mis\s*datos\s*personales/',
// Asesor humano
'asesor.solicitar' => '/asesor|agente|persona|humano|soporte|ayuda\s*(de\s*una\s*persona)?|hablar\s*con\s*(alguien|una\s*persona|un\s*asesor)/',
// Menú / saludo
'menu.principal' => '/^(hola|buenas?|buenos?|hey|men[uú]|inicio|principal|volver|regresar|empezar|comenzar|start)[\s!.]*$/',
];
foreach ($patrones as $action => $pattern) {
if (preg_match($pattern, $t)) {
return ['action' => $action, 'data' => []];
}
}
return null;
}
private function detectarIntencionIA(string $texto): ?array
{
+25 -12
View File
@@ -58,25 +58,38 @@ class GeminiIntentService
$promptExtra = ChatConfig::get('gemini_prompt_extra', '');
return <<<PROMPT
Eres un clasificador de intenciones para una tienda de streaming (peliculas, series).
Eres un clasificador de intenciones para SirPremium, una tienda colombiana de streaming (Netflix, Disney+, HBO, Spotify, etc.).
Los usuarios escriben en español latinoamericano informal, con jerga colombiana y errores ortográficos.
{$promptExtra}
Acciones validas (devuelve EXACTAMENTE una de estas en el campo "action"):
- menu.principal (usuario quiere ver el menu, inicio, ayuda general)
- servicios.listar (quiere ver servicios, planes, catalogo, que hay disponible)
- promo.listar (quiere ver promociones, ofertas, descuentos)
- recarga.iniciar (quiere recargar saldo, agregar dinero, cargar cuenta)
- credenciales.listar (quiere ver sus credenciales, usuario, contrasena, cuenta, acceso)
- historial.ver (quiere ver historial, compras anteriores, que ha comprado)
- perfil.ver (quiere ver su perfil, sus datos, su informacion)
- asesor.solicitar (quiere hablar con una persona, agente, asesor, humano, soporte)
Acciones válidas devuelve EXACTAMENTE una de estas:
- menu.principal saludo, "hola", "inicio", "volver", "menú", pedir ayuda general
- servicios.listar ver planes, catálogo, qué servicios hay, "quiero Netflix", "qué tienen", "cuánto vale", preguntar por precios
- promo.listar ver promociones, ofertas, combos, descuentos, "hay algo especial"
- recarga.iniciar recargar saldo, cargar dinero, "quiero poner saldo", "cómo recargo", "agregar plata", "depositar"
- credenciales.listar ver contraseña, cómo entrar, "no puedo acceder", "dame el usuario", "mis datos de Netflix", "cómo me conecto"
- historial.ver historial de compras, "qué he comprado", "mis pedidos", "mis servicios activos"
- perfil.ver ver perfil, mis datos personales, cambiar información
- asesor.solicitar hablar con persona, "quiero un asesor", "necesito ayuda de alguien", "soporte humano"
Ejemplos de clasificación:
"hacer recarga" recarga.iniciar
"quiero recargar" recarga.iniciar
"cargar saldo" recarga.iniciar
"poner plata" recarga.iniciar
"ver planes" servicios.listar
"qué tienen de Netflix" servicios.listar
"hay promociones" promo.listar
"mis contraseñas" credenciales.listar
"no puedo entrar a mi cuenta" credenciales.listar
"hablar con alguien" asesor.solicitar
Texto del usuario: "{$texto}"
Responde UNICAMENTE con un JSON valido, sin markdown, sin explicacion:
Responde ÚNICAMENTE con JSON válido, sin markdown, sin explicación:
{"action": "la_accion", "data": {}}
Si no puedes determinar la intencion con certeza, usa: {"action": "menu.principal", "data": {}}
Si el texto es completamente ambiguo o irrelevante, usa: {"action": "menu.principal", "data": {}}
PROMPT;
}