diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5dbf8fb --- /dev/null +++ b/AGENTS.md @@ -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í. diff --git a/Agent_skills.md b/Agent_skills.md new file mode 100644 index 0000000..1958b74 --- /dev/null +++ b/Agent_skills.md @@ -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. diff --git a/plan_de_pruebas.md b/plan_de_pruebas.md new file mode 100644 index 0000000..187c1b2 --- /dev/null +++ b/plan_de_pruebas.md @@ -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). diff --git a/reparacion_vs_refactor.md b/reparacion_vs_refactor.md new file mode 100644 index 0000000..1950479 --- /dev/null +++ b/reparacion_vs_refactor.md @@ -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. diff --git a/talia_bot/data/flows/admin_idea_capture.json b/talia_bot/data/flows/admin_idea_capture.json index bd564d9..101bfb2 100644 --- a/talia_bot/data/flows/admin_idea_capture.json +++ b/talia_bot/data/flows/admin_idea_capture.json @@ -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, diff --git a/talia_bot/data/flows/admin_print_file.json b/talia_bot/data/flows/admin_print_file.json index cdf9a63..a0b5f85 100644 --- a/talia_bot/data/flows/admin_print_file.json +++ b/talia_bot/data/flows/admin_print_file.json @@ -1,7 +1,8 @@ { "id": "admin_print_file", "role": "admin", - "trigger_button": "🖨️ Imprimir", + "name": "🖨️ Imprimir Archivo", + "trigger_button": "print_file", "steps": [ { "step_id": 0, diff --git a/talia_bot/main.py b/talia_bot/main.py index 10dba6c..4b8d235 100644 --- a/talia_bot/main.py +++ b/talia_bot/main.py @@ -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) diff --git a/talia_bot/modules/agenda.py b/talia_bot/modules/agenda.py index 3c6265c..df26218 100644 --- a/talia_bot/modules/agenda.py +++ b/talia_bot/modules/agenda.py @@ -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 diff --git a/talia_bot/modules/llm_engine.py b/talia_bot/modules/llm_engine.py index 26b89ab..c8f4194 100644 --- a/talia_bot/modules/llm_engine.py +++ b/talia_bot/modules/llm_engine.py @@ -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}" diff --git a/talia_bot/modules/onboarding.py b/talia_bot/modules/onboarding.py index 795b5b7..bd2d9d4 100644 --- a/talia_bot/modules/onboarding.py +++ b/talia_bot/modules/onboarding.py @@ -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: diff --git a/talia_bot/modules/sales_rag.py b/talia_bot/modules/sales_rag.py index dba5dda..b439a57 100644 --- a/talia_bot/modules/sales_rag.py +++ b/talia_bot/modules/sales_rag.py @@ -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 "