mirror of
https://github.com/marcogll/talia_bot.git
synced 2026-01-13 13:25:19 +00:00
Merge pull request #50 from marcogll/feature/system-stabilization-audit-12843995660965804010
System Audit, Repair, and Stabilization Plan
This commit is contained in:
158
AGENTS.md
Normal file
158
AGENTS.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# AGENTS.md
|
||||
|
||||
Este documento define los agentes que componen el sistema Talia Bot y sus responsabilidades. Un "agente" es un componente de software con un propósito claro y un conjunto de responsabilidades definidas.
|
||||
|
||||
---
|
||||
|
||||
### 1. Agente Recepcionista (`main.py`)
|
||||
|
||||
Es el punto de entrada principal del sistema. Actúa como el primer filtro para todas las interacciones del usuario.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Recibir todas las actualizaciones de Telegram (mensajes de texto, comandos, clics en botones, documentos, mensajes de voz).
|
||||
- Inicializar y registrar todos los manejadores de comandos y mensajes.
|
||||
- Delegar las actualizaciones al agente o manejador correspondiente.
|
||||
- Gestionar el ciclo de vida de la aplicación.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- Comandos (`/start`, `/reset`, `/check_print_status`).
|
||||
- Recepción de documentos para el Agente de Impresión.
|
||||
- Mensajes de texto y voz para el Agente de Motor de Flujos.
|
||||
- Clics en botones para el Despachador de Botones.
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- No debe contener lógica de negocio compleja. Su única función es enrutar.
|
||||
- No debe gestionar el estado de una conversación.
|
||||
|
||||
---
|
||||
|
||||
### 2. Agente de Identidad (`identity.py`, `db.py`)
|
||||
|
||||
Este agente es responsable de conocer "quién" es el usuario y qué permisos tiene.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Consultar la base de datos `users.db` para obtener el rol de un usuario (`admin`, `crew`, `client`) a partir de su `chat_id`.
|
||||
- Proporcionar esta información a otros agentes para que puedan tomar decisiones de enrutamiento y acceso.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- Verificación de rol en el comando `/start`.
|
||||
- Verificación de permisos para acceder a flujos específicos.
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- No gestiona la lógica de las conversaciones.
|
||||
- No interactúa con APIs externas.
|
||||
|
||||
---
|
||||
|
||||
### 3. Agente de Motor de Flujos (`flow_engine.py`)
|
||||
|
||||
Es el cerebro de las conversaciones de múltiples pasos. Orquesta la interacción con el usuario basándose en definiciones declarativas.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Cargar todas las definiciones de flujo desde los archivos `.json` en el directorio `data/flows/`.
|
||||
- Gestionar el estado de la conversación de cada usuario (qué flujo está activo, en qué paso está y qué datos ha recopilado).
|
||||
- Persistir y recuperar el estado de la conversación desde la base de datos.
|
||||
- Ejecutar acciones de finalización cuando un flujo se completa (ej. generar un pitch de ventas).
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- Cualquier conversación definida en un archivo `.json` (ej. `client_sales_funnel`, `admin_create_nfc_tag`).
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- Acciones simples que no requieren una conversación de varios pasos (ej. `view_agenda`).
|
||||
- La generación del menú inicial de opciones.
|
||||
|
||||
---
|
||||
|
||||
### 4. Agente de Onboarding y Menús (`onboarding.py`)
|
||||
|
||||
Este agente es responsable de la primera impresión y de la navegación principal del usuario.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Presentar el mensaje de bienvenida y el menú de opciones principal al usuario cuando ejecuta `/start`.
|
||||
- Generar los menús de botones específicos para cada rol (`admin`, `crew`, `client`).
|
||||
- **Observación (Fallo Detectado)**: Actualmente, los menús son estáticos y hardcodeados, contrario a la documentación.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- La experiencia inicial del usuario.
|
||||
- La visualización de las opciones de primer nivel.
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- La ejecución de los flujos de conversación. Solo debe proporcionar los botones para iniciarlos.
|
||||
|
||||
---
|
||||
|
||||
### 5. Agente de Agenda (`calendar.py`, `agenda.py`, `aprobaciones.py`)
|
||||
|
||||
Gestiona toda la lógica relacionada con la programación, consulta y aprobación de eventos.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Interactuar con la API de Google Calendar.
|
||||
- Diferenciar entre la agenda de trabajo y la agenda personal. El tiempo personal debe ser tratado como bloqueado e inamovible por defecto.
|
||||
- Mostrar al administrador únicamente sus propios eventos.
|
||||
- Gestionar el estado de las solicitudes de actividades (pendiente, aprobada, rechazada).
|
||||
- Asegurar que las actividades rechazadas no vuelvan a aparecer como pendientes.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- `view_agenda`
|
||||
- `view_pending`
|
||||
- `propose_activity`
|
||||
- Aprobación y rechazo de eventos.
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- Gestión de tareas (esa es responsabilidad del Agente de Tareas).
|
||||
- Conversaciones no relacionadas con la agenda.
|
||||
|
||||
---
|
||||
|
||||
### 6. Agente de Impresión (`printer.py`)
|
||||
|
||||
Este agente gestiona la funcionalidad de impresión remota.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Recibir un archivo desde Telegram.
|
||||
- Enviar el archivo como adjunto por correo electrónico (SMTP) a la dirección de la impresora.
|
||||
- Consultar el estado de los trabajos de impresión mediante la revisión de una bandeja de entrada de correo (IMAP).
|
||||
- Informar al usuario sobre el estado de la impresión.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- Recepción de documentos.
|
||||
- `/check_print_status`
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- Cualquier interacción que no sea enviar un archivo o consultar el estado.
|
||||
|
||||
---
|
||||
|
||||
### 7. Agente de RAG y Ventas (`sales_rag.py`, `llm_engine.py`)
|
||||
|
||||
Es responsable del embudo de ventas para nuevos clientes, utilizando un modelo de lenguaje para generar respuestas personalizadas.
|
||||
|
||||
- **Responsabilidades**:
|
||||
- Ejecutar el flujo de conversación `client_sales_funnel`.
|
||||
- Recuperar información relevante de la base de conocimiento (`services.json`).
|
||||
- Construir un prompt enriquecido con el contexto del cliente y la información de los servicios.
|
||||
- Invocar al modelo de lenguaje (OpenAI) para generar una propuesta de ventas.
|
||||
- **Regla Obligatoria**: Si no se encuentra contexto relevante en la base de conocimiento, NO debe generar una respuesta genérica. Debe informar que no puede ayudar.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- `client_sales_funnel`
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- Conversaciones que no estén relacionadas con el proceso de ventas.
|
||||
|
||||
---
|
||||
|
||||
### 8. Agente de Transcripción (Whisper)
|
||||
|
||||
**Estado: Inexistente.** Este agente es requerido pero no está implementado.
|
||||
|
||||
- **Responsabilidades Futuras**:
|
||||
- Recibir un archivo de audio (mensaje de voz) de Telegram.
|
||||
- Enviar el audio a la API de Whisper para su transcripción.
|
||||
- Devolver el texto transcrito al `text_and_voice_handler` para que sea procesado por el Agente de Motor de Flujos.
|
||||
|
||||
- **Flujos que Maneja**:
|
||||
- La conversión de voz a texto dentro de cualquier flujo de conversación.
|
||||
|
||||
- **Flujos que NO Debe Manejar**:
|
||||
- La lógica de la conversación en sí.
|
||||
141
Agent_skills.md
Normal file
141
Agent_skills.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Agent_skills.md
|
||||
|
||||
Este documento detalla las capacidades técnicas, reglas de negocio y límites de cada agente definido en `AGENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
### 1. Agente Recepcionista (`main.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Framework**: `python-telegram-bot`.
|
||||
- **Manejo de Eventos**: Utiliza `CommandHandler`, `CallbackQueryHandler`, `MessageHandler` para registrar y procesar diferentes tipos de actualizaciones de Telegram.
|
||||
- **Inyección de Dependencias**: Almacena instancias compartidas (como `FlowEngine`) en el `bot_data` del contexto de la aplicación para que estén disponibles globalmente.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- El comando `/start` siempre debe borrar cualquier estado de conversación previo del usuario para asegurar un inicio limpio.
|
||||
- Los documentos (`filters.Document.ALL`) se enrutan exclusivamente al manejador de impresión.
|
||||
- Los mensajes de texto y voz se enrutan al `text_and_voice_handler`, que a su vez los delega al motor de flujos.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No implementa lógica de reintentos para comandos fallidos.
|
||||
- No contiene estado. El estado se gestiona en la base de datos a través de otros agentes.
|
||||
|
||||
---
|
||||
|
||||
### 2. Agente de Identidad (`identity.py`, `db.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Base de Datos**: `SQLite`.
|
||||
- **Conexión**: Utiliza `sqlite3` para conectarse a la base de datos `users.db`.
|
||||
- **Modelo de Datos**: La tabla `users` contiene `chat_id` (INTEGER, PRIMARY KEY) y `role` (TEXT).
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- Un `chat_id` solo puede tener un rol.
|
||||
- Si un usuario no se encuentra en la base de datos, se le asigna el rol de `client` por defecto.
|
||||
- Los roles válidos son `admin`, `crew`, y `client`. Cualquier otro valor se trata como `client`.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No gestiona la adición o eliminación de usuarios. Esto debe hacerse manualmente en la base de datos por ahora.
|
||||
- No ofrece permisos granulares, solo los tres roles definidos.
|
||||
|
||||
---
|
||||
|
||||
### 3. Agente de Motor de Flujos (`flow_engine.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Serialización**: Lee y parsea archivos `.json` para definir la estructura de las conversaciones.
|
||||
- **Persistencia de Estado**: Utiliza `SQLite` para almacenar y recuperar el estado de la conversación de cada usuario en la tabla `conversations`.
|
||||
- **Arquitectura**: Máquina de estados finitos donde cada paso de la conversación es un estado.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- Cada flujo debe tener un `id` único y una clave `role` que restringe su acceso.
|
||||
- Los pasos se ejecutan en orden secuencial según su `step_id`.
|
||||
- Al final de un flujo, se puede invocar una función de "finalización" (ej. `generate_sales_pitch`) para procesar los datos recopilados.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No soporta bifurcaciones complejas (condicionales) en los flujos. La lógica es estrictamente lineal.
|
||||
- No tiene un mecanismo de "timeout". Las conversaciones pueden permanecer activas indefinidamente hasta que el usuario las complete o las reinicie.
|
||||
|
||||
---
|
||||
|
||||
### 4. Agente de Onboarding y Menús (`onboarding.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Framework**: `python-telegram-bot`.
|
||||
- **UI**: Construye menús utilizando `InlineKeyboardMarkup` y `InlineKeyboardButton`.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- Debe mostrar un menú específico para cada uno de los tres roles (`admin`, `crew`, `client`).
|
||||
- **Fallo Actual**: Los menús son estáticos y no se generan a partir de los flujos disponibles, lo que impide el acceso a flujos que sí existen.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No puede mostrar menús que dependan del estado del usuario (ej. un botón diferente si el usuario ya ha completado una tarea).
|
||||
|
||||
---
|
||||
|
||||
### 5. Agente de Agenda (`calendar.py`, `agenda.py`, `aprobaciones.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Integraciones**: API de Google Calendar (`googleapiclient`).
|
||||
- **Autenticación**: Utiliza una cuenta de servicio de Google Cloud con un archivo `google_key.json`.
|
||||
- **Manejo de Fechas**: Utiliza la librería `datetime` para manejar zonas horarias y rangos de tiempo.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- El tiempo personal (del `PERSONAL_GOOGLE_CALENDAR_ID`) es prioritario y se considera "ocupado" e inamovible.
|
||||
- Los eventos propuestos por el `crew` solo deben enviarse a Google Calendar después de ser aprobados por un `admin`.
|
||||
- Una vez que una actividad es rechazada, su estado debe persistir y no debe volver a mostrarse como "pendiente".
|
||||
- **Fallo Actual**: El código ignora los diferentes IDs de calendario y solo usa uno genérico, rompiendo la separación entre personal y trabajo.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No puede modificar eventos existentes, solo crearlos.
|
||||
- La lógica de aprobación es binaria (aprobar/rechazar) y no permite re-programación o negociación.
|
||||
|
||||
---
|
||||
|
||||
### 6. Agente de Impresión (`printer.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Protocolos de Red**: `SMTP` para enviar correos y `IMAP` para leerlos.
|
||||
- **Librerías**: `smtplib` y `imaplib` de la biblioteca estándar de Python.
|
||||
- **Manejo de Archivos**: Descarga temporalmente los archivos de Telegram al disco antes de enviarlos.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- Solo los usuarios con rol `admin` pueden usar esta función (verificación de permisos).
|
||||
- El estado se determina buscando palabras clave (ej. "completed", "failed") en los asuntos de los correos no leídos.
|
||||
|
||||
- **Límites Claros**:
|
||||
- No maneja la impresión de imágenes, solo documentos.
|
||||
- No tiene un sistema de cola. Las solicitudes se procesan de forma síncrona.
|
||||
|
||||
---
|
||||
|
||||
### 7. Agente de RAG y Ventas (`sales_rag.py`, `llm_engine.py`)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Integraciones**: API de OpenAI.
|
||||
- **Procesamiento de Lenguaje Natural (PLN)**: Realiza una búsqueda simple de palabras clave en `services.json` para encontrar contexto relevante.
|
||||
- **Generación de Prompts**: Construye un prompt detallado para el LLM a partir de una plantilla y los datos recopilados del usuario.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- La regla "sin contexto, no hay respuesta" es obligatoria. Si la búsqueda en `services.json` no arroja resultados, el agente debe detenerse.
|
||||
- **Fallo Actual**: Esta regla no se está aplicando, lo que resulta in respuestas genéricas.
|
||||
|
||||
- **Límites Claros**:
|
||||
- El mecanismo de "retrieval" (recuperación) es una búsqueda de palabras clave, no un sistema de embeddings vectoriales. Su precisión es limitada.
|
||||
- No mantiene memoria de interacciones pasadas con el cliente.
|
||||
|
||||
---
|
||||
|
||||
### 8. Agente de Transcripción (Whisper)
|
||||
|
||||
- **Capacidades Técnicas**:
|
||||
- **Integraciones Futuras**: API de OpenAI (Whisper).
|
||||
- **Manejo de Medios**: Deberá poder descargar archivos de audio de Telegram y enviarlos como una solicitud `multipart/form-data`.
|
||||
|
||||
- **Reglas de Negocio**:
|
||||
- Debe ser capaz de manejar diferentes formatos de audio si Telegram los proporciona.
|
||||
- Debe tener un manejo de errores para cuando la transcripción falle.
|
||||
|
||||
- **Límites Claros**:
|
||||
- **Estado Actual**: No implementado. El `text_and_voice_handler` contiene una respuesta placeholder.
|
||||
- No está diseñado para la traducción, solo para la transcripción del idioma hablado.
|
||||
101
plan_de_pruebas.md
Normal file
101
plan_de_pruebas.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Plan de Pruebas: Estabilización de Talia Bot
|
||||
|
||||
Este documento describe el plan de pruebas paso a paso para verificar la correcta funcionalidad del sistema Talia Bot después de la fase de reparación.
|
||||
|
||||
---
|
||||
|
||||
### 1. Configuración y Entorno
|
||||
|
||||
- **Qué se prueba**: La correcta carga de las variables de entorno.
|
||||
- **Pasos a ejecutar**:
|
||||
1. Asegurarse de que el archivo `.env` existe y contiene todas las variables definidas in `.env.example`.
|
||||
2. Prestar especial atención a `WORK_GOOGLE_CALENDAR_ID` y `PERSONAL_GOOGLE_CALENDAR_ID`.
|
||||
3. Iniciar el bot.
|
||||
- **Resultado esperado**: El bot debe iniciarse sin errores relacionados con variables de entorno faltantes. Los logs de inicio deben mostrar que la aplicación se ha iniciado correctamente.
|
||||
- **Qué indica fallo**: Un crash al inicio, o errores en los logs indicando que una variable de entorno `None` o vacía está siendo utilizada donde no debería.
|
||||
|
||||
---
|
||||
|
||||
### 2. Routing por Rol de Usuario
|
||||
|
||||
- **Qué se prueba**: Que cada rol de usuario (`admin`, `crew`, `client`) vea el menú correcto y solo las opciones que le corresponden.
|
||||
- **Pasos a ejecutar**:
|
||||
1. **Como Admin**: Enviar el comando `/start`.
|
||||
2. **Como Crew**: Enviar el comando `/start`.
|
||||
3. **Como Cliente**: Enviar el comando `/start`.
|
||||
- **Resultado esperado**:
|
||||
- **Admin**: Debe ver el menú de administrador, que incluirá las opciones "Revisar Pendientes", "Agenda", y las nuevas opciones reparadas ("Imprimir Archivo", "Capturar Idea").
|
||||
- **Crew**: Debe ver el menú de equipo con "Proponer actividad" y "Ver estatus de solicitudes".
|
||||
- **Cliente**: Debe ver el menú de cliente con "Agendar una cita" y "Información de servicios".
|
||||
- **Qué indica fallo**: Cualquier rol viendo un menú que no le corresponde, o la ausencia de las opciones esperadas.
|
||||
|
||||
---
|
||||
|
||||
### 3. Flujos de Administrador Faltantes
|
||||
|
||||
- **Qué se prueba**: La visibilidad y funcionalidad de los flujos de "Imprimir Archivo" y "Capturar Idea".
|
||||
- **Pasos a ejecutar**:
|
||||
1. **Como Admin**: Presionar el botón "Imprimir Archivo" (o su equivalente) en el menú.
|
||||
2. **Como Admin**: Presionar el botón "Capturar Idea" en el menú.
|
||||
- **Resultado esperado**:
|
||||
- Al presionar "Imprimir Archivo", el bot debe iniciar el flujo de impresión, pidiendo al usuario que envíe un documento.
|
||||
- Al presionar "Capturar Idea", el bot debe iniciar el flujo de captura de ideas, haciendo la primera pregunta definida en `admin_idea_capture.json`.
|
||||
- **Qué indica fallo**: Que los botones no existan en el menú, o que al presionarlos no se inicie el flujo de conversación correspondiente.
|
||||
|
||||
---
|
||||
|
||||
### 4. Lógica de Agenda y Calendario
|
||||
|
||||
- **Qué se prueba**: La correcta separación de agendas y el tratamiento del tiempo personal.
|
||||
- **Pasos a ejecutar**:
|
||||
1. **Preparación**: Crear un evento en el `PERSONAL_GOOGLE_CALENDAR_ID` que dure todo el día de hoy, llamado "Día Personal". Crear otro evento en el `WORK_GOOGLE_CALENDAR_ID` para hoy a las 3 PM, llamado "Reunión de Equipo".
|
||||
2. **Como Admin**: Presionar el botón "Agenda" en el menú.
|
||||
- **Resultado esperado**: El bot debe responder mostrando *únicamente* el evento "Reunión de Equipo" a las 3 PM. El "Día Personal" no debe ser visible, pero el tiempo que ocupa debe ser tratado como no disponible si se intentara agendar algo.
|
||||
- **Qué indica fallo**: La agenda muestra el evento "Día Personal", o muestra eventos de otros calendarios que no son el de trabajo del admin.
|
||||
|
||||
---
|
||||
|
||||
### 5. Persistencia en Rechazo de Actividades
|
||||
|
||||
- **Qué se prueba**: Que una actividad propuesta por el equipo y rechazada por el admin no vuelva a aparecer como pendiente.
|
||||
- **Pasos a ejecutar**:
|
||||
1. **Como Crew**: Iniciar el flujo "Proponer actividad" y proponer una actividad para mañana.
|
||||
2. **Como Admin**: Ir a "Revisar Pendientes". Ver la actividad propuesta.
|
||||
3. **Como Admin**: Presionar el botón para "Rechazar" la actividad.
|
||||
4. **Como Admin**: Volver a presionar "Revisar Pendientes".
|
||||
- **Resultado esperado**: La segunda vez que se revisan los pendientes, la lista debe estar vacía o no debe incluir la actividad que fue rechazada.
|
||||
- **Qué indica fallo**: La actividad rechazada sigue apareciendo en la lista de pendientes.
|
||||
|
||||
---
|
||||
|
||||
### 6. RAG (Retrieval-Augmented Generation) y Whisper
|
||||
|
||||
- **Qué se prueba**: La regla de negocio "sin contexto, no hay respuesta" del RAG y la nueva funcionalidad de transcripción de voz.
|
||||
- **Pasos a ejecutar**:
|
||||
1. **RAG**:
|
||||
a. **Como Cliente**: Iniciar el flujo de ventas.
|
||||
b. Cuando se pregunte por la idea de proyecto, responder con un texto que no contenga ninguna palabra clave relevante de `services.json` (ej: "quiero construir una casa para mi perro").
|
||||
2. **Whisper**:
|
||||
a. **Como Cliente**: Iniciar el flujo de ventas.
|
||||
b. Cuando se pregunte por el nombre, responder con un mensaje de voz diciendo tu nombre.
|
||||
- **Resultado esperado**:
|
||||
- **RAG**: El bot debe responder con un mensaje indicando que no puede generar una propuesta con esa información, en lugar de dar una respuesta genérica.
|
||||
- **Whisper**: El bot debe procesar el mensaje de voz, transcribirlo, y continuar el flujo usando el nombre transcrito como si se hubiera escrito.
|
||||
- **Qué indica fallo**:
|
||||
- **RAG**: El bot da un pitch de ventas genérico o incorrecto.
|
||||
- **Whisper**: El bot responde con el mensaje "Voice message received (transcription not implemented yet)" o un error.
|
||||
|
||||
---
|
||||
|
||||
### 7. Comandos Slash
|
||||
|
||||
- **Qué se prueba**: La funcionalidad de los comandos básicos, incluyendo el inexistente `/abracadabra`.
|
||||
- **Pasos a ejecutar**:
|
||||
1. Enviar el comando `/start`.
|
||||
2. Iniciar una conversación y luego enviar `/reset`.
|
||||
3. Enviar un comando inexistente como `/abracadabra`.
|
||||
- **Resultado esperado**:
|
||||
- `/start`: Muestra el menú de bienvenida correspondiente al rol.
|
||||
- `/reset`: El bot responde "Conversación reiniciada" y borra el estado actual del flujo.
|
||||
- `/abracadabra`: Telegram o el bot deben indicar que el comando no es reconocido.
|
||||
- **Qué indica fallo**: Que los comandos `/start` o `/reset` no funcionen como se espera. (No se espera que `/abracadabra` funcione, por lo que un fallo sería que *hiciera* algo).
|
||||
71
reparacion_vs_refactor.md
Normal file
71
reparacion_vs_refactor.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Plan de Reparación vs. Refactorización
|
||||
|
||||
Este documento distingue entre las reparaciones críticas ya implementadas y propone un plan de refactorización incremental para estabilizar y mejorar la arquitectura del sistema Talia Bot a largo plazo.
|
||||
|
||||
---
|
||||
|
||||
### Parte 1: Reparaciones Críticas (Ya Implementadas)
|
||||
|
||||
Las siguientes acciones se tomaron como medidas de reparación inmediata para solucionar los problemas más urgentes y restaurar la funcionalidad básica.
|
||||
|
||||
1. **Visibilidad de Flujos de Admin**:
|
||||
- **Fix**: Se modificó `onboarding.py` para generar el menú de administrador de forma dinámica, leyendo los flujos disponibles del `FlowEngine`. Se añadieron las claves `name` y `trigger_button` a los archivos JSON de los flujos para permitir esto.
|
||||
- **Impacto**: Los administradores ahora pueden ver y acceder a todos los flujos que tienen asignados, incluyendo "Capturar Idea" e "Imprimir Archivo".
|
||||
|
||||
2. **Lógica de Agenda y Privacidad**:
|
||||
- **Fix**: Se actualizó `agenda.py` para que utilice las variables de entorno `WORK_GOOGLE_CALENDAR_ID` y `PERSONAL_GOOGLE_CALENDAR_ID`.
|
||||
- **Impacto**: El bot ahora muestra correctamente solo los eventos de la agenda de trabajo del administrador, mientras que trata el tiempo en la agenda personal como bloqueado, protegiendo la privacidad y asegurando que la disponibilidad sea precisa.
|
||||
|
||||
3. **Implementación de Transcripción (Whisper)**:
|
||||
- **Fix**: Se añadió una función `transcribe_audio` a `llm_engine.py` y se integró en el `text_and_voice_handler` de `main.py`.
|
||||
- **Impacto**: El bot ya no ignora los mensajes de voz. Ahora puede transcribirlos y usarlos como entrada para los flujos de conversación, sentando las bases para una interacción multimodal completa.
|
||||
|
||||
4. **Guardarraíl del RAG de Ventas**:
|
||||
- **Fix**: Se eliminó la lógica de fallback en `sales_rag.py`. Si no se encuentran servicios relevantes para la consulta de un cliente, el agente se detiene.
|
||||
- **Impacto**: El bot ya no genera respuestas de ventas genéricas o irrelevantes. Ahora cumple la regla obligatoria de "sin contexto, no hay respuesta", mejorando la calidad y la fiabilidad de sus interacciones con clientes.
|
||||
|
||||
---
|
||||
|
||||
### Parte 2: Plan de Refactorización Incremental (Propuesta)
|
||||
|
||||
Las reparaciones anteriores han estabilizado el sistema, pero la auditoría reveló debilidades arquitectónicas que deben abordarse para asegurar la mantenibilidad y escalabilidad futuras. Se propone el siguiente plan incremental.
|
||||
|
||||
#### Incremento 1: Gestión de Estado y Base de Datos
|
||||
|
||||
- **Problema**: La lógica de la base de datos está dispersa. La persistencia del estado de las aprobaciones es frágil, lo que causa que actividades rechazadas reaparezcan.
|
||||
- **Propuesta**:
|
||||
1. **Centralizar Acceso a DB**: Crear un gestor de contexto en `db.py` para manejar las conexiones y cursores, asegurando que las conexiones siempre se cierren correctamente.
|
||||
2. **Refactorizar Aprobaciones**: Rediseñar la lógica en `aprobaciones.py`. Introducir una tabla `activity_proposals` en la base de datos con un campo `status` (ej. `pending`, `approved`, `rejected`).
|
||||
3. **Implementar DAO (Data Access Object)**: Crear clases o funciones específicas para interactuar con cada tabla (`users`, `conversations`, `activity_proposals`), en lugar de escribir consultas SQL directamente en la lógica de negocio.
|
||||
- **Riesgos**: Mínimos. Este cambio es interno y no debería afectar la experiencia del usuario, pero requiere cuidado para no corromper la base de datos.
|
||||
- **Beneficios**: Solucionará permanentemente el bug de las actividades rechazadas. Hará que el manejo de la base de datos sea más robusto y fácil de mantener.
|
||||
|
||||
#### Incremento 2: Abstracción de APIs Externas (Fachada)
|
||||
|
||||
- **Problema**: Las llamadas directas a APIs externas (Google, OpenAI, Vikunja) están mezcladas con la lógica de negocio, lo que hace que el código sea difícil de probar y de cambiar.
|
||||
- **Propuesta**:
|
||||
1. **Crear un Módulo `clients`**: Dentro de `modules`, crear un nuevo directorio `clients`.
|
||||
2. **Implementar Clientes API**: Mover toda la lógica de interacción directa con las APIs a clases dedicadas dentro de este nuevo módulo (ej. `google_calendar_client.py`, `openai_client.py`). Estas clases manejarán la autenticación, las solicitudes y el formato de las respuestas.
|
||||
3. **Actualizar Módulos de Negocio**: Modificar los módulos como `agenda.py` y `llm_engine.py` para que usen estos clientes, en lugar de hacer llamadas directas.
|
||||
- **Riesgos**: Moderados. Requiere refactorizar una parte significativa del código. Se deben realizar pruebas exhaustivas para asegurar que las integraciones no se rompan.
|
||||
- **Beneficios**: Desacopla la lógica de negocio de las implementaciones de las API. Permite cambiar de proveedor (ej. de OpenAI a Gemini) con un impacto mínimo. Facilita enormemente las pruebas unitarias al permitir "mockear" los clientes API.
|
||||
|
||||
#### Incremento 3: Sistema de Routing y Comandos Explícito
|
||||
|
||||
- **Problema**: El `button_dispatcher` en `main.py` es un monolito que mezcla lógica de flujos, acciones simples y lógica de aprobación. Es difícil de seguir y propenso a errores a medida que se añaden más botones. El comando `/abracadabra` no funciona porque no hay un sistema claro para registrar comandos "secretos" o de un solo uso.
|
||||
- **Propuesta**:
|
||||
1. **Registro de Comandos**: Crear un patrón de registro explícito. Cada módulo podría tener una función `register_handlers(application)` que se llama desde `main.py`.
|
||||
2. **Separar Despachador**: Dividir el `button_dispatcher` en funciones más pequeñas y específicas. Una podría manejar los callbacks de los flujos, otra los de acciones simples, etc.
|
||||
3. **Implementar `/abracadabra`**: Usando el nuevo sistema de registro, crear un comando simple en `admin.py` para la funcionalidad de `/abracadabra` y registrarlo en `main.py`.
|
||||
- **Riesgos**: Bajos. Los cambios son principalmente organizativos.
|
||||
- **Beneficios**: Mejora radicalmente la legibilidad y mantenibilidad del `main.py`. Crea un sistema claro y escalable para añadir nuevos comandos y botones.
|
||||
|
||||
---
|
||||
|
||||
### Orden Recomendado
|
||||
|
||||
Se recomienda seguir el orden de los incrementos propuestos:
|
||||
|
||||
1. **Gestión de Estado y DB**: Es la base. Un manejo de datos sólido es fundamental para todo lo demás.
|
||||
2. **Abstracción de APIs**: Abordar esto primero hará que el siguiente paso sea más limpio.
|
||||
3. **Sistema de Routing**: Con la lógica de negocio y los datos bien estructurados, refactorizar el enrutamiento será mucho más sencillo.
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"id": "admin_idea_capture",
|
||||
"role": "admin",
|
||||
"trigger_button": "💡 Capturar Idea",
|
||||
"name": "💡 Capturar Idea",
|
||||
"trigger_button": "capture_idea",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": 0,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"id": "admin_print_file",
|
||||
"role": "admin",
|
||||
"trigger_button": "🖨️ Imprimir",
|
||||
"name": "🖨️ Imprimir Archivo",
|
||||
"trigger_button": "print_file",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": 0,
|
||||
|
||||
@@ -33,6 +33,7 @@ from talia_bot.modules.vikunja import vikunja_conv_handler, get_projects_list, g
|
||||
from talia_bot.modules.printer import send_file_to_printer, check_print_status
|
||||
from talia_bot.db import setup_database
|
||||
from talia_bot.modules.flow_engine import FlowEngine
|
||||
from talia_bot.modules.llm_engine import transcribe_audio
|
||||
|
||||
from talia_bot.scheduler import schedule_daily_summary
|
||||
|
||||
@@ -101,7 +102,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.info(f"Usuario {chat_id} inició conversación con el rol: {user_role}")
|
||||
|
||||
# Obtenemos el texto y los botones de bienvenida desde el módulo de onboarding
|
||||
response_text, reply_markup = onboarding_handle_start(user_role)
|
||||
response_text, reply_markup = onboarding_handle_start(user_role, flow_engine)
|
||||
|
||||
# Respondemos al usuario
|
||||
await update.message.reply_text(response_text, reply_markup=reply_markup)
|
||||
@@ -120,9 +121,25 @@ async def text_and_voice_handler(update: Update, context: ContextTypes.DEFAULT_T
|
||||
|
||||
user_response = update.message.text
|
||||
if update.message.voice:
|
||||
# Here you would add the logic to transcribe the voice message
|
||||
# For now, we'll just use a placeholder
|
||||
user_response = "Voice message received (transcription not implemented yet)."
|
||||
voice = update.message.voice
|
||||
temp_dir = 'temp_files'
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
file_path = os.path.join(temp_dir, f"{voice.file_id}.ogg")
|
||||
|
||||
try:
|
||||
voice_file = await context.bot.get_file(voice.file_id)
|
||||
await voice_file.download_to_drive(file_path)
|
||||
logger.info(f"Voice message saved to {file_path}")
|
||||
|
||||
user_response = transcribe_audio(file_path)
|
||||
logger.info(f"Transcription result: '{user_response}'")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during voice transcription: {e}")
|
||||
user_response = "Error al procesar el mensaje de voz."
|
||||
finally:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
result = flow_engine.handle_response(user_id, user_response)
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
import datetime
|
||||
import logging
|
||||
from talia_bot.modules.calendar import get_events
|
||||
from talia_bot.config import WORK_GOOGLE_CALENDAR_ID, PERSONAL_GOOGLE_CALENDAR_ID
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_agenda():
|
||||
"""
|
||||
Obtiene y muestra la agenda del usuario para el día actual desde Google Calendar.
|
||||
Diferencia entre eventos de trabajo (visibles) y personales (bloqueos).
|
||||
"""
|
||||
try:
|
||||
logger.info("Obteniendo agenda...")
|
||||
@@ -18,24 +20,34 @@ async def get_agenda():
|
||||
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_of_day = start_of_day + datetime.timedelta(days=1)
|
||||
|
||||
logger.info(f"Buscando eventos desde {start_of_day} hasta {end_of_day}")
|
||||
events = get_events(start_of_day, end_of_day)
|
||||
logger.info(f"Buscando eventos de trabajo en {WORK_GOOGLE_CALENDAR_ID} y personales en {PERSONAL_GOOGLE_CALENDAR_ID}")
|
||||
|
||||
if not events:
|
||||
logger.info("No se encontraron eventos.")
|
||||
return "📅 *Agenda para Hoy*\n\nNo tienes eventos programados para hoy."
|
||||
# Obtener eventos de trabajo (para mostrar)
|
||||
work_events = get_events(start_of_day, end_of_day, calendar_id=WORK_GOOGLE_CALENDAR_ID)
|
||||
|
||||
# Obtener eventos personales (para comprobar bloqueos, no se muestran)
|
||||
personal_events = get_events(start_of_day, end_of_day, calendar_id=PERSONAL_GOOGLE_CALENDAR_ID)
|
||||
|
||||
if not work_events and not personal_events:
|
||||
logger.info("No se encontraron eventos de ningún tipo.")
|
||||
return "📅 *Agenda para Hoy*\n\nTotalmente despejado. No hay eventos de trabajo ni personales."
|
||||
|
||||
agenda_text = "📅 *Agenda para Hoy*\n\n"
|
||||
for event in events:
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
# Formatear la hora si es posible
|
||||
if "T" in start:
|
||||
time_str = start.split("T")[1][:5]
|
||||
else:
|
||||
time_str = "Todo el día"
|
||||
|
||||
summary = event.get("summary", "(Sin título)")
|
||||
agenda_text += f"• *{time_str}* - {summary}\n"
|
||||
if not work_events:
|
||||
agenda_text += "No tienes eventos de trabajo programados para hoy.\n"
|
||||
else:
|
||||
for event in work_events:
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
if "T" in start:
|
||||
time_str = start.split("T")[1][:5]
|
||||
else:
|
||||
time_str = "Todo el día"
|
||||
|
||||
summary = event.get("summary", "(Sin título)")
|
||||
agenda_text += f"• *{time_str}* - {summary}\n"
|
||||
|
||||
if personal_events:
|
||||
agenda_text += "\n🔒 Tienes tiempo personal bloqueado."
|
||||
|
||||
logger.info("Agenda obtenida con éxito.")
|
||||
return agenda_text
|
||||
|
||||
@@ -32,3 +32,25 @@ def get_smart_response(prompt):
|
||||
except Exception as e:
|
||||
# Si algo sale mal, devolvemos el error
|
||||
return f"Ocurrió un error al comunicarse con OpenAI: {e}"
|
||||
|
||||
def transcribe_audio(audio_file_path):
|
||||
"""
|
||||
Transcribes an audio file using OpenAI's Whisper model.
|
||||
|
||||
Parameters:
|
||||
- audio_file_path: The path to the audio file.
|
||||
"""
|
||||
if not OPENAI_API_KEY:
|
||||
return "Error: OPENAI_API_KEY is not configured."
|
||||
|
||||
try:
|
||||
client = openai.OpenAI(api_key=OPENAI_API_KEY)
|
||||
|
||||
with open(audio_file_path, "rb") as audio_file:
|
||||
transcript = client.audio.transcriptions.create(
|
||||
model="whisper-1",
|
||||
file=audio_file
|
||||
)
|
||||
return transcript.text
|
||||
except Exception as e:
|
||||
return f"Error during audio transcription: {e}"
|
||||
|
||||
@@ -4,14 +4,22 @@
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
def get_admin_menu():
|
||||
def get_admin_menu(flow_engine):
|
||||
"""Crea el menú de botones principal para los Administradores."""
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("👑 Revisar Pendientes", callback_data='view_pending')],
|
||||
[InlineKeyboardButton("📅 Agenda", callback_data='view_agenda')],
|
||||
[InlineKeyboardButton(" NFC", callback_data='start_create_tag')],
|
||||
[InlineKeyboardButton("▶️ Más opciones", callback_data='admin_menu')],
|
||||
]
|
||||
|
||||
# Dynamic buttons from flows
|
||||
if flow_engine:
|
||||
for flow in flow_engine.flows:
|
||||
if flow.get("role") == "admin" and "trigger_button" in flow and "name" in flow:
|
||||
button = InlineKeyboardButton(flow["name"], callback_data=flow["trigger_button"])
|
||||
keyboard.append([button])
|
||||
|
||||
keyboard.append([InlineKeyboardButton("▶️ Más opciones", callback_data='admin_menu')])
|
||||
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
def get_admin_secondary_menu():
|
||||
@@ -41,14 +49,14 @@ def get_client_menu():
|
||||
]
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
def handle_start(user_role):
|
||||
def handle_start(user_role, flow_engine=None):
|
||||
"""
|
||||
Decide qué mensaje y qué menú mostrar según el rol del usuario.
|
||||
"""
|
||||
welcome_message = "Hola, soy Talía. ¿En qué puedo ayudarte hoy?"
|
||||
|
||||
if user_role == "admin":
|
||||
menu = get_admin_menu()
|
||||
menu = get_admin_menu(flow_engine)
|
||||
elif user_role == "crew":
|
||||
menu = get_crew_menu()
|
||||
else:
|
||||
|
||||
@@ -41,19 +41,19 @@ def generate_sales_pitch(user_query, collected_data):
|
||||
relevant_services = find_relevant_services(user_query, services)
|
||||
|
||||
if not relevant_services:
|
||||
# Fallback to all services if no specific keywords match
|
||||
context_str = "Aquí hay una descripción general de nuestros servicios:\n"
|
||||
for service in services:
|
||||
context_str += f"- **{service['service_name']}**: {service['description']}\n"
|
||||
else:
|
||||
context_str = "Según tus necesidades, aquí tienes algunos de nuestros servicios y ejemplos de lo que podemos hacer:\n"
|
||||
for service in relevant_services:
|
||||
context_str += f"\n**Servicio:** {service['service_name']}\n"
|
||||
context_str += f"*Descripción:* {service['description']}\n"
|
||||
if "work_examples" in service:
|
||||
context_str += "*Ejemplos de trabajo:*\n"
|
||||
for example in service["work_examples"]:
|
||||
context_str += f" - {example}\n"
|
||||
logger.warning(f"No se encontraron servicios relevantes para la consulta: '{user_query}'. No se generará respuesta.")
|
||||
return ("Gracias por tu interés. Sin embargo, con la información proporcionada no he podido identificar "
|
||||
"servicios específicos que se ajusten a tu necesidad. ¿Podrías describir tu proyecto con otras palabras "
|
||||
"o dar más detalles sobre lo que buscas?")
|
||||
|
||||
context_str = "Según tus necesidades, aquí tienes algunos de nuestros servicios y ejemplos de lo que podemos hacer:\n"
|
||||
for service in relevant_services:
|
||||
context_str += f"\n**Servicio:** {service['service_name']}\n"
|
||||
context_str += f"*Descripción:* {service['description']}\n"
|
||||
if "work_examples" in service:
|
||||
context_str += "*Ejemplos de trabajo:*\n"
|
||||
for example in service["work_examples"]:
|
||||
context_str += f" - {example}\n"
|
||||
|
||||
prompt = (
|
||||
f"Eres Talía, una asistente de ventas experta y amigable. Un cliente potencial llamado "
|
||||
|
||||
Reference in New Issue
Block a user