Crear un chatbot simple con Python es un proyecto excelente para principiantes porque combina entrada de datos, strings, condiciones, bucles, funciones, diccionarios, archivos y pruebas. El bot de esta guía se ejecuta en el terminal y responde mediante reglas explícitas. No necesita un servicio externo ni un modelo de aprendizaje automático.
Los chatbots basados en reglas funcionan bien cuando las preguntas posibles son limitadas y predecibles, por ejemplo en un menú de ayuda, un asistente de estudio, una herramienta de información o una sección de preguntas frecuentes. También ofrecen una base clara para comprender sistemas conversacionales más avanzados.
Cómo funciona
El programa sigue un ciclo:
- Leer un mensaje.
- Normalizar el texto.
- Identificar una intención o palabra clave.
- Seleccionar una respuesta.
- Mostrarla.
- Continuar hasta recibir una orden de salida.
Esta estructura combina entrada, procesamiento, salida y repetición. La guía de lógica de programación con Python y la introducción a algoritmos explican estos conceptos.
Crear el primer bucle
print("Bot: Hola. Escribe 'salir' para terminar.")
while True:
mensaje = input("Tú: ")
if mensaje.casefold() == "salir":
print("Bot: Hasta luego.")
break
print("Bot: Todavía estoy aprendiendo.")El bucle continúa hasta ejecutar break. La guía de bucles en Python amplía while, break y continue.
Normalizar la entrada
Las personas pueden escribir mayúsculas, espacios adicionales o combinaciones diferentes. Crea una función:
def normalizar(texto: str) -> str:
return " ".join(
texto.strip().casefold().split()
)
print(normalizar(" HOLA BOT "))La función elimina espacios extremos, reduce espacios repetidos y normaliza mayúsculas de forma adecuada para comparaciones.
Añadir reglas exactas
MENSAJES_SALIDA = {"salir", "adiós", "hasta luego"}
def obtener_respuesta(mensaje: str) -> str:
texto = normalizar(mensaje)
if texto in {"hola", "buenas", "hey"}:
return "Hola. ¿En qué puedo ayudarte?"
if texto == "cómo estás":
return "Estoy funcionando correctamente."
if texto in MENSAJES_SALIDA:
return "Hasta luego."
return "No entendí ese mensaje."Un set es adecuado para frases equivalentes. La guía de sets en Python explica pertenencia y valores únicos.
Conectar la función al programa
def main() -> None:
print("Bot: Hola. Escribe 'salir' para terminar.")
while True:
mensaje = input("Tú: ")
respuesta = obtener_respuesta(mensaje)
print(f"Bot: {respuesta}")
if normalizar(mensaje) in MENSAJES_SALIDA:
break
if __name__ == "__main__":
main()El bloque if __name__ == "__main__" permite importar las funciones desde otro archivo sin iniciar la conversación automáticamente.
Buscar palabras clave
def obtener_respuesta(mensaje: str) -> str:
texto = normalizar(mensaje)
if texto in {"hola", "buenas", "hey"}:
return "Hola. ¿En qué puedo ayudarte?"
if "horario" in texto or "abierto" in texto:
return "Atendemos de lunes a viernes."
if "precio" in texto or "coste" in texto:
return "Indica qué producto deseas consultar."
if "curso" in texto:
return "Tenemos cursos para principiantes."
if texto in MENSAJES_SALIDA:
return "Hasta luego."
return "Pregunta por horarios, precios o cursos."Las búsquedas por fragmentos pueden producir coincidencias accidentales. Para un proyecto mayor, divide el texto en palabras y utiliza reglas más específicas.
Organizar intents con diccionarios
INTENTS = {
"saludo": {
"palabras": {"hola", "buenas", "hey"},
"respuestas": [
"Hola. ¿En qué puedo ayudarte?",
"Buenas. ¿Qué deseas consultar?",
],
},
"horario": {
"palabras": {"horario", "abierto", "cierre"},
"respuestas": [
"Atendemos de lunes a viernes."
],
},
"precios": {
"palabras": {"precio", "coste", "tarifa"},
"respuestas": [
"Indica el producto que deseas consultar."
],
},
}Los diccionarios agrupan datos relacionados. Consulta la comparación de estructuras de datos de Python.
Detectar una intención
def detectar_intent(mensaje: str) -> str | None:
palabras = set(normalizar(mensaje).split())
for nombre, datos in INTENTS.items():
if palabras & datos["palabras"]:
return nombre
return NoneEl operador & calcula la intersección. Una intersección no vacía significa que existe al menos una palabra coincidente.
Variar respuestas
import random
def obtener_respuesta(mensaje: str) -> str:
texto = normalizar(mensaje)
if texto in MENSAJES_SALIDA:
return "Hasta luego."
intent = detectar_intent(mensaje)
if intent is None:
return "No entendí. Prueba otra pregunta."
return random.choice(INTENTS[intent]["respuestas"])La variación hace la conversación menos repetitiva. La documentación oficial del módulo random explica choice() y otras operaciones. No utilices respuestas aleatorias para información crítica si las alternativas no son equivalentes.
Cargar intents desde JSON
Separar contenido y código facilita el mantenimiento. Crea intents.json:
{
"saludo": {
"palabras": ["hola", "buenas", "hey"],
"respuestas": [
"Hola. ¿En qué puedo ayudarte?",
"Buenas. ¿Qué deseas consultar?"
]
},
"horario": {
"palabras": ["horario", "abierto", "cierre"],
"respuestas": [
"Atendemos de lunes a viernes."
]
}
}Cárgalo y valida la estructura:
import json
from pathlib import Path
def cargar_intents(ruta: str | Path) -> dict:
with Path(ruta).open("r", encoding="utf-8") as archivo:
datos = json.load(archivo)
if not isinstance(datos, dict):
raise ValueError("El JSON debe contener un objeto")
for nombre, intent in datos.items():
if "palabras" not in intent or "respuestas" not in intent:
raise ValueError(f"Intent inválido: {nombre}")
intent["palabras"] = set(intent["palabras"])
return datosLa documentación oficial del módulo json explica lectura y escritura. Para rutas, consulta pathlib en Python.
Manejar errores de configuración
def main() -> None:
try:
intents = cargar_intents("intents.json")
except FileNotFoundError:
print("No se encontró la configuración.")
return
except (json.JSONDecodeError, ValueError) as error:
print(f"Configuración inválida: {error}")
return
# Iniciar conversación.Captura errores esperados y muestra instrucciones útiles. La guía de try y except explica este patrón.
Recordar un nombre temporalmente
estado = {"nombre": None}
mensaje = input("Tú: ")
texto = normalizar(mensaje)
if texto.startswith("me llamo "):
estado["nombre"] = mensaje.strip()[9:].strip().title()
print(f"Bot: Encantado, {estado['nombre']}.")Este estado desaparece al cerrar el programa. Para memoria duradera necesitas un archivo o base de datos y reglas sobre privacidad, retención y eliminación.
Probar la lógica
def test_normalizar_espacios():
assert normalizar(" HOLA BOT ") == "hola bot"
def test_mensaje_salida():
assert obtener_respuesta("salir") == "Hasta luego."
def test_detectar_horario():
assert detectar_intent("¿Cuál es el horario?") == "horario"La guía de Pytest para principiantes ayuda a organizar estas pruebas. Cuando existen respuestas aleatorias, comprueba que la respuesta pertenece a una lista permitida.
Convertir el motor en otra interfaz
La lógica puede reutilizarse en:
- una página web;
- una aplicación de escritorio;
- una API;
- un bot de Telegram;
- un widget de ayuda.
Mantén la entrada y salida separadas de la detección de intents para evitar duplicación.
Limitaciones
Un bot de palabras clave no comprende realmente el lenguaje. Puede fallar con sinónimos, errores de escritura, negación y contexto. Estas limitaciones son aceptables cuando el alcance es pequeño y las respuestas deben ser predecibles.
En un uso público, informa que se trata de un sistema automático y ofrece una alternativa de soporte humano.
Errores frecuentes
- Crear una cadena enorme de condiciones.
- No normalizar mayúsculas y espacios.
- No ofrecer una respuesta de respaldo.
- Mezclar contenido y código.
- No definir una salida clara.
- Guardar datos personales sin necesidad.
- No probar conflictos entre intents.
Conclusión
Un chatbot simple con Python reúne habilidades fundamentales en un proyecto práctico. Empieza con un bucle y reglas exactas, añade normalización, diccionarios de intents, JSON, estado y pruebas. Mantén el motor de respuestas independiente de la interfaz para reutilizarlo después en una web, un bot de mensajería o una aplicación de escritorio.





