feat: Audit, repair, and stabilize bot architecture

This commit addresses critical issues in the Talia Bot system, including fixing missing admin flows, correcting agenda privacy logic, implementing voice message transcription, and enforcing RAG guardrails.

Key changes include:
- Modified the onboarding module to dynamically generate admin menus from available JSON flows, making all admin functions accessible.
- Updated the agenda module to correctly use separate work and personal Google Calendar IDs, ensuring privacy and accurate availability.
- Implemented audio transcription using the OpenAI Whisper API, replacing placeholder logic and enabling multimodal interaction.
- Reworked the sales RAG module to prevent it from generating generic responses when it lacks sufficient context.

Additionally, this commit introduces comprehensive documentation as requested:
- `AGENTS.md`: Defines the roles and responsibilities of each system agent.
- `Agent_skills.md`: Details the technical capabilities and business rules for each agent.
- `plan_de_pruebas.md`: Provides a step-by-step test plan to verify the fixes.
- `reparacion_vs_refactor.md`: Outlines the immediate repairs performed and proposes a strategic, incremental plan for long-term architectural improvements.
This commit is contained in:
google-labs-jules[bot]
2025-12-21 20:29:55 +00:00
parent 9dc13dacb1
commit 81efa4babd
11 changed files with 571 additions and 39 deletions

158
AGENTS.md Normal file
View 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
View 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
View 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
View 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.

View File

@@ -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,

View File

@@ -1,7 +1,8 @@
{
"id": "admin_print_file",
"role": "admin",
"trigger_button": "🖨️ Imprimir",
"name": "🖨️ Imprimir Archivo",
"trigger_button": "print_file",
"steps": [
{
"step_id": 0,

View File

@@ -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)

View File

@@ -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,17 +20,24 @@ 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:
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"))
# Formatear la hora si es posible
if "T" in start:
time_str = start.split("T")[1][:5]
else:
@@ -37,6 +46,9 @@ async def get_agenda():
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
except Exception as e:

View File

@@ -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}"

View File

@@ -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:

View File

@@ -41,11 +41,11 @@ 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:
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"