Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Chatbot Basado en Reglas - Sesión 1 | |
| Curso: Diseño e Implementación de Chatbots | |
| Este chatbot funciona mediante coincidencia de patrones y reglas predefinidas. | |
| Los mensajes y flujos están en el archivo flujos_conversacion.json | |
| """ | |
| import json | |
| import re | |
| import difflib | |
| import random | |
| import unicodedata | |
| from pathlib import Path | |
| class ChatbotReglas: | |
| """Chatbot basado en reglas con dos flujos de conversación""" | |
| def __init__(self, archivo_flujos='instructions.json'): | |
| """ | |
| Inicializa el chatbot cargando los flujos desde JSON | |
| Args: | |
| archivo_flujos: Ruta al archivo JSON con los flujos | |
| """ | |
| self.archivo_flujos = archivo_flujos | |
| self.cargar_flujos() | |
| self.contexto = { | |
| 'nombre_usuario': None, | |
| 'flujo_actual': None, | |
| 'ultima_intencion': None, | |
| 'historial': [] | |
| } | |
| def cargar_flujos(self): | |
| """Carga los flujos de conversación desde el archivo JSON""" | |
| try: | |
| ruta = Path(__file__).parent / self.archivo_flujos | |
| with open(ruta, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| self.config = data['configuracion'] | |
| self.flujos = data['flujos'] | |
| print("✓ Flujos de conversación cargados correctamente\n") | |
| except FileNotFoundError: | |
| print(f"❌ Error: No se encontró el archivo {self.archivo_flujos}") | |
| exit(1) | |
| except json.JSONDecodeError: | |
| print(f"❌ Error: El archivo {self.archivo_flujos} no es un JSON válido") | |
| exit(1) | |
| def normalizar_texto(self, texto): | |
| """ | |
| Normaliza el texto del usuario para mejorar coincidencias | |
| Args: | |
| texto: Texto a normalizar | |
| Returns: | |
| Texto normalizado (minúsculas, sin tildes, sin puntuación extra) | |
| """ | |
| # Convertir a minúsculas | |
| texto = texto.lower() | |
| # Eliminar tildes | |
| texto = ''.join( | |
| c for c in unicodedata.normalize('NFD', texto) | |
| if unicodedata.category(c) != 'Mn' | |
| ) | |
| # Remover puntuación excesiva pero mantener espacios | |
| texto = re.sub(r'[^\w\s]', '', texto) | |
| # Normalizar espacios | |
| texto = ' '.join(texto.split()) | |
| return texto | |
| def calcular_similitud(self, texto1, texto2): | |
| """ | |
| Calcula la similitud entre dos textos usando difflib | |
| Args: | |
| texto1: Primer texto | |
| texto2: Segundo texto | |
| Returns: | |
| Score de similitud entre 0.0 y 1.0 | |
| """ | |
| return difflib.SequenceMatcher(None, texto1, texto2).ratio() | |
| def buscar_mejor_intencion(self, mensaje_usuario): | |
| """ | |
| Busca la mejor intención que coincida con el mensaje del usuario | |
| Args: | |
| mensaje_usuario: Mensaje del usuario (ya normalizado) | |
| Returns: | |
| Tupla (intencion, score, flujo_nombre) o (None, 0, None) | |
| """ | |
| mejor_intencion = None | |
| mejor_score = 0 | |
| mejor_flujo = None | |
| # Buscar en todos los flujos | |
| for nombre_flujo, flujo in self.flujos.items(): | |
| for intencion in flujo['intenciones']: | |
| # Calcular similitud con cada patrón | |
| for patron in intencion['patrones']: | |
| patron_normalizado = self.normalizar_texto(patron) | |
| # Similitud general | |
| score = self.calcular_similitud(mensaje_usuario, patron_normalizado) | |
| # Bonus si la palabra clave está contenida exactamente | |
| if patron_normalizado in mensaje_usuario: | |
| score = max(score, 0.8) | |
| # Bonus si todas las palabras del patrón están en el mensaje | |
| palabras_patron = patron_normalizado.split() | |
| palabras_mensaje = mensaje_usuario.split() | |
| if all(palabra in palabras_mensaje for palabra in palabras_patron): | |
| score = max(score, 0.85) | |
| if score > mejor_score: | |
| mejor_score = score | |
| mejor_intencion = intencion | |
| mejor_flujo = nombre_flujo | |
| return mejor_intencion, mejor_score, mejor_flujo | |
| def seleccionar_respuesta(self, intencion): | |
| """ | |
| Selecciona una respuesta aleatoria de la intención | |
| Args: | |
| intencion: Diccionario con la intención | |
| Returns: | |
| String con la respuesta | |
| """ | |
| respuestas = intencion['respuestas'] | |
| respuesta = random.choice(respuestas) | |
| # Agregar sugerencia si existe | |
| if 'siguiente_sugerencia' in intencion: | |
| respuesta += f"\n\n💡 {intencion['siguiente_sugerencia']}" | |
| return respuesta | |
| def procesar_mensaje(self, mensaje_usuario): | |
| """ | |
| Procesa el mensaje del usuario y genera una respuesta | |
| Args: | |
| mensaje_usuario: Mensaje del usuario | |
| Returns: | |
| Respuesta del bot, o None si debe terminar | |
| """ | |
| # Normalizar mensaje | |
| mensaje_normalizado = self.normalizar_texto(mensaje_usuario) | |
| # Guardar en historial | |
| self.contexto['historial'].append(mensaje_usuario) | |
| # Buscar mejor intención | |
| intencion, score, flujo = self.buscar_mejor_intencion(mensaje_normalizado) | |
| # Decidir respuesta | |
| if score >= self.config['umbral_similitud']: | |
| # Actualizar contexto | |
| self.contexto['ultima_intencion'] = intencion['id'] | |
| self.contexto['flujo_actual'] = flujo | |
| # Verificar acciones especiales | |
| if 'accion_especial' in intencion: | |
| if intencion['accion_especial'] == 'terminar': | |
| return self.seleccionar_respuesta(intencion), True | |
| return self.seleccionar_respuesta(intencion), False | |
| else: | |
| # No se entendió el mensaje | |
| return self.config['mensaje_no_entendido'], False | |
| def ejecutar(self): | |
| """Ejecuta el loop principal del chatbot""" | |
| print("=" * 70) | |
| print("CHATBOT BASADO EN REGLAS - CURSO DE CHATBOTS") | |
| print("=" * 70) | |
| print(self.config['mensaje_bienvenida']) | |
| print("=" * 70) | |
| # Loop principal | |
| while True: | |
| try: | |
| # Capturar input del usuario | |
| mensaje = input("\n🧑 Tú: ").strip() | |
| # Validar input vacío | |
| if not mensaje: | |
| continue | |
| # Verificar comando de salida directo | |
| if mensaje.lower() in ['salir', 'exit', 'quit']: | |
| print(f"\n🤖 Bot: {self.config['mensaje_despedida']}") | |
| break | |
| # Procesar mensaje | |
| respuesta, debe_terminar = self.procesar_mensaje(mensaje) | |
| # Mostrar respuesta | |
| print(f"\n🤖 Bot: {respuesta}") | |
| # Terminar si es necesario | |
| if debe_terminar: | |
| break | |
| except KeyboardInterrupt: | |
| print(f"\n\n🤖 Bot: {self.config['mensaje_despedida']}") | |
| break | |
| except Exception as e: | |
| print(f"\n❌ Error interno: {e}") | |
| print("Por favor, intenta de nuevo.") | |
| print("\n" + "=" * 70) | |
| print("Conversación terminada. ¡Gracias por usar el chatbot!") | |
| print("=" * 70) | |
| def mostrar_estadisticas(self): | |
| """Muestra estadísticas de la conversación""" | |
| print("\n📊 Estadísticas de la conversación:") | |
| print(f" - Mensajes del usuario: {len(self.contexto['historial'])}") | |
| print(f" - Último flujo usado: {self.contexto['flujo_actual']}") | |
| print(f" - Última intención: {self.contexto['ultima_intencion']}") | |
| def main(): | |
| """Función principal""" | |
| # Crear instancia del chatbot | |
| bot = ChatbotReglas() | |
| # Ejecutar | |
| bot.ejecutar() | |
| # Mostrar estadísticas (opcional) | |
| # bot.mostrar_estadisticas() | |
| if __name__ == "__main__": | |
| main() | |