Chatbot multilingüe con detección automática de idioma: arquitectura y errores que cometemos
Detectar el idioma no es el problema. El problema es qué haces cuando el usuario mezcla español e inglés en el mismo mensaje.
title: "Chatbot multilingüe con detección automática de idioma: arquitectura y errores que cometemos" slug: chatbot-multilingue-deteccion-idioma-n8n excerpt: "Cuando montamos el primer chatbot multilingüe, usamos solo un LLM para detectar el idioma de cada mensaje. Era preciso pero caro. Esta es la arquitectura híbrida que terminamos usando: franc para el 70% de los casos, GPT-4o-mini solo cuando hace falta." category: tecnico tags: [chatbot, multilingue, n8n, idioma, franc, gpt-4o-mini, arquitectura] author: Flexum date: 2026-06-03
Chatbot multilingüe con detección automática de idioma: arquitectura y errores que cometemos
La primera vez que tuvimos que montar un chatbot multilingüe, hicimos lo más obvio: enviamos cada mensaje a GPT-4o-mini con el prompt "detect the language of this message and respond only with the ISO 639-1 code". Funcionaba perfectamente. El problema es que enviábamos una llamada a la API de OpenAI por cada mensaje, aunque el mensaje fuera en español, el idioma mayoritario, y aunque el bot ya supiera que ese usuario estaba en español desde el primer intercambio.
Con un volumen de 500 conversaciones al día, eso son miles de llamadas innecesarias al mes. Pequeñas en coste unitario, pero acumuladas se notan.
Esta es la arquitectura que terminamos usando y el razonamiento detrás de cada decisión.
Por qué el idioma importa más de lo que parece
Para clientes con negocio local y audiencia 100% hispanohablante, el idioma no es un problema. Pero hay muchos más casos de los que parece donde sí importa:
Turismo y hostelería: Hoteles, restaurantes en zonas costeras, agencias de viajes. En temporada alta, el 30-50% de los mensajes entrantes pueden ser en inglés, francés, alemán o italiano.
E-commerce con envío internacional: Si vendes a Europa o Latinoamérica, vas a recibir mensajes en idiomas que no esperabas.
Servicios B2B con clientes internacionales: Consultoras, agencias, proveedores de software con clientes en varios países.
Empresas con plantilla internacional: Algunos de nuestros clientes tienen equipos donde conviven hablantes de catalán, castellano e inglés, y quieren que las herramientas internas funcionen en los tres idiomas.
Zonas con idiomas cooficiales: En Cataluña, País Vasco y Galicia, es frecuente recibir mensajes en catalán, euskera o gallego. Responder en castellano sin más puede crear fricciones innecesarias.
Dos enfoques para detectar el idioma
Antes de la arquitectura híbrida, evaluamos dos enfoques puros:
Enfoque 1: franc (librería de detección estadística)
franc es una librería de JavaScript (también disponible para Node.js) que detecta el idioma de un texto usando n-gramas y modelos estadísticos. No llama a ninguna API externa: todo se ejecuta localmente.
Ventajas: instantáneo, sin coste, sin latencia de red. Desventajas: necesita un mínimo de texto para ser fiable (funciona mal con menos de 20-30 caracteres), no distingue bien entre idiomas similares (español vs portugués, serbio vs croata), y puede confundirse con textos muy cortos o con muchos nombres propios.
Enfoque 2: LLM (GPT-4o-mini o similar)
Enviar el texto a un LLM con una instrucción de detección de idioma.
Ventajas: muy preciso incluso con textos muy cortos, entiende contexto y mezclas de idiomas, puede manejar alfabetos no latinos sin problema. Desventajas: tiene un coste (pequeño por llamada pero acumulable), añade latencia (200-800ms por llamada), y es un recurso externo que puede fallar.
La arquitectura híbrida: franc primero, LLM solo cuando hace falta
La solución correcta combina ambos. La lógica es:
- Si la sesión ya tiene un idioma detectado, usar ese y no re-detectar.
- Si no hay idioma en sesión, intentar con franc.
- Si franc devuelve un resultado con confianza alta (>0.7) y el idioma está en nuestra lista de soportados, usar ese resultado.
- Si franc no tiene confianza suficiente (mensaje corto, idioma ambiguo), llamar a GPT-4o-mini.
- Guardar el idioma detectado en la sesión para el resto de la conversación.
Con este enfoque, en un escenario típico de atención al cliente en español con turistas internacionales, el 70-80% de los mensajes los resuelve franc localmente. Solo el 20-30% restante (mensajes cortos, idiomas ambiguos, mezclas) van al LLM.
Implementación paso a paso en n8n
Paso 1: Comprobar si la sesión ya tiene idioma
Al inicio del flujo de procesamiento de mensajes, antes de hacer cualquier detección, consultamos la sesión del usuario:
// Nodo Code - Check Session Language
const phone = $json.from; // número de teléfono del usuario
const sessions = $('Load Sessions').all(); // nodo previo que carga sesiones de Sheets/DB
const userSession = sessions.find(s => s.json.phone === phone);
if (userSession && userSession.json.language) {
// Ya sabemos el idioma, saltamos la detección
return {
skipDetection: true,
language: userSession.json.language,
phone: phone
};
}
return {
skipDetection: false,
language: null,
phone: phone,
textToDetect: $json.text
};
Si skipDetection es true, el flujo salta directamente al procesamiento del mensaje. Solo si es false continuamos con la detección.
Paso 2: Detección con franc
franc no está disponible como nodo nativo en n8n, pero puedes usarlo en un nodo Code siempre que esté instalado en tu entorno de n8n. Si usas n8n en Docker, necesitas añadir franc al contenedor o usar la función $helpers.httpRequest para llamar a un microservicio propio.
La alternativa más práctica para n8n es usar langdetect o implementar una versión simplificada del algoritmo en JavaScript puro. Aquí usamos una llamada a un endpoint propio que encapsula franc:
// Nodo HTTP Request - Llamada al microservicio de detección
// El microservicio está en tu servidor, no en APIs externas
const text = $json.textToDetect;
// Si el texto es muy corto, franc no será fiable
if (!text || text.length < 20) {
return {
skipFranc: true,
reason: 'text_too_short'
};
}
const response = await $helpers.httpRequest({
method: 'POST',
url: 'http://localhost:3001/detect-language',
body: { text }
});
return {
skipFranc: false,
francResult: response.language, // código ISO 639-3
francConfidence: response.confidence,
supportedLanguages: ['spa', 'eng', 'fra', 'deu', 'ita', 'por', 'cat']
};
El microservicio de franc es una API Express minimalista:
const franc = require('franc');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/detect-language', (req, res) => {
const { text } = req.body;
const results = franc.all(text, { minLength: 10 });
if (!results || results.length === 0) {
return res.json({ language: 'und', confidence: 0 });
}
const [topLang, topScore] = results[0];
res.json({ language: topLang, confidence: topScore });
});
app.listen(3001);
Paso 3: Evaluar el resultado de franc
// Nodo Code - Evaluar franc
const francResult = $json.francResult;
const confidence = $json.francConfidence;
const supported = $json.supportedLanguages;
// Mapa de ISO 639-3 a ISO 639-1
const iso3to1 = {
'spa': 'es', 'eng': 'en', 'fra': 'fr', 'deu': 'de',
'ita': 'it', 'por': 'pt', 'cat': 'ca', 'eus': 'eu',
'glg': 'gl', 'rus': 'ru', 'ara': 'ar'
};
const needsLLM = (
$json.skipFranc ||
confidence < 0.7 ||
!supported.includes(francResult) ||
francResult === 'und' // undetermined
);
if (!needsLLM && iso3to1[francResult]) {
return {
language: iso3to1[francResult],
method: 'franc',
needsLLM: false
};
}
return {
language: null,
needsLLM: true,
textToDetect: $('Paso 1').item.json.textToDetect
};
Paso 4: Fallback a GPT-4o-mini
Solo llega aquí si franc no fue suficiente:
// Nodo Code - GPT-4o-mini Language Detection
const text = $json.textToDetect;
const response = await $helpers.httpRequest({
method: 'POST',
url: 'https://api.openai.com/v1/chat/completions',
headers: {
'Authorization': `Bearer ${$env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: {
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: 'You detect the language of text messages. Respond ONLY with the ISO 639-1 two-letter code. If the message is too short or ambiguous, respond with the most likely language. Supported: es, en, fr, de, it, pt, ca. If none match, respond with "es".'
},
{
role: 'user',
content: text
}
],
max_tokens: 3,
temperature: 0
}
});
const lang = response.choices[0].message.content.trim().toLowerCase().substring(0, 2);
const validLangs = ['es', 'en', 'fr', 'de', 'it', 'pt', 'ca'];
return {
language: validLangs.includes(lang) ? lang : 'es',
method: 'llm'
};
Paso 5: Guardar en sesión
Independientemente del método usado, el idioma detectado se guarda en la sesión del usuario. A partir de ahí, todos los mensajes siguientes de esa conversación usarán ese idioma sin re-detectar.
Casos extremos: los que siempre te sorprenden
Mensajes con mezcla de idiomas
Un usuario catalanoparlante que a veces mezcla castellano y catalán: "Hola, vull fer una reserva per dijous, son 4 personas, ¿podéis acomodarlos?". franc dará una confianza baja porque hay dos idiomas mezclados. El LLM detectará correctamente catalán como idioma principal.
Alfabetos diferentes
Un cliente ruso que escribe en cirílico, un cliente árabe en árabe. franc maneja bien estos casos porque el propio alfabeto es ya una señal fortísima. Para estos casos, franc suele tener confianza alta incluso con textos cortos.
Lo que sí hay que preparar: la respuesta en el idioma del cliente. Si no tienes respuestas preparadas en ruso o árabe, hay que decidir la política (responder en inglés, responder con un mensaje genérico de "atendemos en español, inglés y francés").
Catalán vs español
Este es el caso más complicado en el contexto español. franc distingue catalán y español con razonable precisión si el texto tiene 30+ caracteres. Con textos cortos ("Hola, una reserva") es muy difícil porque comparten muchas palabras.
Estrategia práctica: si el negocio opera en Cataluña, configurar el idioma por defecto como catalán cuando la detección sea ambigua entre català y castellano, y dejar que el usuario corrija ("Puedo responderte en catalán o en castellano, ¿cuál prefieres?").
Mensajes de una sola palabra
"Hola", "Ok", "Sí", "Thanks", "Merci". Imposibles de detectar con fiabilidad estadística. El LLM tampoco puede hacer mucho con "Ok" en términos de idioma.
Solución: para mensajes de menos de 5 caracteres o una sola palabra, no intentar detectar y usar el idioma de la sesión si existe, o el idioma por defecto del negocio si no hay sesión.
Tabla de mensajes ambiguos reales
Estos son mensajes reales que recibimos y cómo los clasifica el sistema:
| Mensaje | franc | Confianza | LLM | Resultado | |---|---|---|---|---| | "Hola" | und | 0 | es (default) | es | | "Ok gracias" | spa | 0.65 | - | LLM → es | | "I need a table for 4" | eng | 0.91 | - | en | | "Je voudrais réserver" | fra | 0.96 | - | fr | | "Bon dia, vull reservar" | cat | 0.82 | - | ca | | "Hola, vull reservar una taula" | cat | 0.78 | - | ca | | "Buenas tardes quiero reservar" | spa | 0.94 | - | es | | "Thanks" | und | 0 | en | en | | "Si" | und | 0 | LLM → ambiguo | es (default) | | "Привет, хочу забронировать" | rus | 0.99 | - | ru |
Análisis de costes: el impacto real del enfoque híbrido
Supongamos 1.000 conversaciones al mes, con un promedio de 5 mensajes por conversación = 5.000 mensajes.
Enfoque LLM puro: 5.000 llamadas a GPT-4o-mini para detección de idioma. Con ~150 tokens de entrada y 3 de salida por llamada, aproximadamente 750.000 tokens de entrada. A 0,15 $/millón de tokens de entrada para GPT-4o-mini: ~0,11 $/mes en detección de idioma. Parece insignificante, pero es un uso innecesario.
Enfoque híbrido:
- 70% de mensajes resueltos por franc: 3.500 mensajes sin coste
- Solo el primer mensaje de cada conversación necesita detección si usamos sesiones
- 1.000 primeras detecciones: franc resuelve ~700, LLM maneja ~300
- Coste real: 300 llamadas × ~0,000022 $ = ~0,007 $/mes
La diferencia en coste es en este volumen pequeña, pero el principio escala. A 50.000 conversaciones al mes, la diferencia entre enfoques se vuelve significativa. Y más importante: al usar sesiones, el bot no re-detecta el idioma en cada mensaje, lo que mejora la experiencia y reduce latencia.
Qué no hacer
Flujos separados por idioma: La tentación es crear un flujo n8n para español, otro para inglés, otro para francés. Es un error de mantenimiento grave: cualquier cambio en la lógica del bot hay que replicarlo en N flujos. Mucho mejor un único flujo donde el idioma es un parámetro que afecta qué texto se envía.
Usar Google Translate para las respuestas: El bot detecta el idioma y luego envía respuestas en español traducidas automáticamente con Google Translate. El resultado suena robótico y a veces incorrecto. Mucho mejor tener las respuestas clave del bot escritas por personas nativas en cada idioma soportado, y para idiomas de bajo volumen, usar GPT-4o-mini con un prompt en el idioma correcto.
No gestionar el cambio de idioma: Si un usuario empieza en inglés pero en el tercer mensaje escribe en español (pasa), el bot debería adaptarse. Añade lógica para detectar cambios de idioma y actualizar la sesión.
Fundador de Flexum. Lleva más de ocho años ayudando a pymes a implementar automatizaciones con n8n, bots conversacionales y desarrollo a medida.
¿Quieres que lo implementemos para ti?
También te puede interesar
Cómo montamos un bot de WhatsApp para reservas de restaurante (y por qué dejamos de usar formularios)
El restaurante tenía un formulario de reservas que nadie rellenaba. Todos llamaban. El bot…
Seguimiento automático de leads con email e IA: cómo lo hacemos sin parecer un robot
Los emails de seguimiento automático acaban en spam porque parecen automáticos. El truco n…
Agentes con LLM en n8n: cómo funcionan y qué hemos conseguido con ellos
Un agente no es un chatbot que responde preguntas. Es un proceso que toma decisiones, llam…