From 9a83fb93bbf83f8760738db48b3a5d9d4c166b1a Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Thu, 18 Dec 2025 09:24:54 -0600 Subject: [PATCH 1/2] feat: Add asynchronous support and improved logging/error handling for calendar functions, introduce a calendar debug script, and refactor role-based menu logic. --- app/google_calendar.py | 13 +++++++++-- app/main.py | 18 ++++++++++++-- app/modules/agenda.py | 49 ++++++++++++++++++++++++--------------- app/modules/onboarding.py | 4 +--- app/modules/vikunja.py | 4 ++-- test_calendar_debug.py | 27 +++++++++++++++++++++ 6 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 test_calendar_debug.py diff --git a/app/google_calendar.py b/app/google_calendar.py index 690b75a..24b6203 100644 --- a/app/google_calendar.py +++ b/app/google_calendar.py @@ -3,11 +3,14 @@ # Permite buscar espacios libres y crear eventos. import datetime +import logging from google.oauth2 import service_account from googleapiclient.discovery import build from googleapiclient.errors import HttpError from config import GOOGLE_SERVICE_ACCOUNT_FILE, CALENDAR_ID +logger = logging.getLogger(__name__) + # Configuración de los permisos (SCOPES) para acceder al calendario SCOPES = ["https://www.googleapis.com/auth/calendar"] @@ -120,6 +123,7 @@ def get_events(start_time, end_time, calendar_id=CALENDAR_ID): Obtiene la lista de eventos entre dos momentos. """ try: + logger.info(f"Llamando a la API de Google Calendar para {calendar_id}") events_result = ( service.events() .list( @@ -131,7 +135,12 @@ def get_events(start_time, end_time, calendar_id=CALENDAR_ID): ) .execute() ) - return events_result.get("items", []) + events = events_result.get("items", []) + logger.info(f"Se obtuvieron {len(events)} eventos de la API.") + return events except HttpError as error: - print(f"Ocurrió un error al obtener eventos: {error}") + logger.error(f"Ocurrió un error al obtener eventos: {error}") + return [] + except Exception as e: + logger.error(f"Error inesperado al obtener eventos: {e}") return [] diff --git a/app/main.py b/app/main.py index 594c2e9..c1eff85 100644 --- a/app/main.py +++ b/app/main.py @@ -2,6 +2,7 @@ # Este es el archivo principal del bot. Aquí se inicia todo y se configuran los comandos. import logging +import asyncio from telegram import Update from telegram.ext import ( Application, @@ -87,12 +88,25 @@ async def button_dispatcher(update: Update, context: ContextTypes.DEFAULT_TYPE) } if query.data in simple_handlers: - response_text = simple_handlers[query.data]() + handler = simple_handlers[query.data] + logger.info(f"Ejecutando simple_handler para: {query.data}") + if asyncio.iscoroutinefunction(handler): + response_text = await handler() + else: + response_text = handler() + logger.info(f"Respuesta obtenida para {query.data}") await query.edit_message_text(text=response_text, parse_mode='Markdown') elif query.data in complex_handlers: - response_text, reply_markup = complex_handlers[query.data]() + handler = complex_handlers[query.data] + logger.info(f"Ejecutando complex_handler para: {query.data}") + if asyncio.iscoroutinefunction(handler): + response_text, reply_markup = await handler() + else: + response_text, reply_markup = handler() + logger.info(f"Respuesta obtenida para {query.data}") await query.edit_message_text(text=response_text, reply_markup=reply_markup, parse_mode='Markdown') elif query.data.startswith(('approve:', 'reject:')): + logger.info(f"Ejecutando acción de aprobación: {query.data}") response_text = handle_approval_action(query.data) await query.edit_message_text(text=response_text, parse_mode='Markdown') elif query.data == 'start_create_tag': diff --git a/app/modules/agenda.py b/app/modules/agenda.py index ffb4a19..a7f4b71 100644 --- a/app/modules/agenda.py +++ b/app/modules/agenda.py @@ -3,31 +3,42 @@ # Permite obtener y mostrar las actividades programadas para el día. import datetime +import logging from google_calendar import get_events -def get_agenda(): +logger = logging.getLogger(__name__) + +async def get_agenda(): """ Obtiene y muestra la agenda del usuario para el día actual desde Google Calendar. """ - now = datetime.datetime.utcnow() - start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0) - end_of_day = start_of_day + datetime.timedelta(days=1) + try: + logger.info("Obteniendo agenda...") + now = datetime.datetime.now(datetime.timezone.utc) + start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0) + end_of_day = start_of_day + datetime.timedelta(days=1) - events = get_events(start_of_day, end_of_day) + logger.info(f"Buscando eventos desde {start_of_day} hasta {end_of_day}") + events = get_events(start_of_day, end_of_day) - if not events: - return "📅 *Agenda para Hoy*\n\nNo tienes eventos programados para hoy." + if not events: + logger.info("No se encontraron eventos.") + return "📅 *Agenda para Hoy*\n\nNo tienes eventos programados para hoy." - 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" + 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" - return agenda_text + logger.info("Agenda obtenida con éxito.") + return agenda_text + except Exception as e: + logger.error(f"Error al obtener la agenda: {e}") + return "❌ Error al obtener la agenda. Por favor, intenta de nuevo más tarde." diff --git a/app/modules/onboarding.py b/app/modules/onboarding.py index 4a05f2d..567e7d8 100644 --- a/app/modules/onboarding.py +++ b/app/modules/onboarding.py @@ -55,9 +55,7 @@ def handle_start(user_role): """ welcome_message = "Hola, soy Talía. ¿En qué puedo ayudarte hoy?" - if user_role == "owner": - menu = get_owner_menu() - elif user_role == "admin": + if user_role in ["owner", "admin"]: menu = get_admin_menu() elif user_role == "team": menu = get_team_menu() diff --git a/app/modules/vikunja.py b/app/modules/vikunja.py index b97651d..57557ca 100644 --- a/app/modules/vikunja.py +++ b/app/modules/vikunja.py @@ -13,8 +13,8 @@ from telegram.ext import ( ContextTypes, ) -from app.config import VIKUNJA_API_URL, VIKUNJA_API_TOKEN -from app.permissions import is_admin +from config import VIKUNJA_API_URL, VIKUNJA_API_TOKEN +from permissions import is_admin # Configuración del logger logger = logging.getLogger(__name__) diff --git a/test_calendar_debug.py b/test_calendar_debug.py new file mode 100644 index 0000000..aa27442 --- /dev/null +++ b/test_calendar_debug.py @@ -0,0 +1,27 @@ +import datetime +import sys +import os + +# Add app directory to path +sys.path.append(os.path.join(os.getcwd(), 'app')) + +from google_calendar import get_events +from config import CALENDAR_ID + +def test_get_events(): + print(f"Testing with CALENDAR_ID: {CALENDAR_ID}") + now = datetime.datetime.now(datetime.timezone.utc) + start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0) + end_of_day = start_of_day + datetime.timedelta(days=1) + + print(f"Fetching events from {start_of_day.isoformat()} to {end_of_day.isoformat()}...") + try: + events = get_events(start_of_day, end_of_day) + print(f"Found {len(events)} events.") + for event in events: + print(f"- {event.get('summary')} at {event['start'].get('dateTime', event['start'].get('date'))}") + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + test_get_events() From 25d349f1151be7d9f58a6847608ece2550c34854 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Thu, 18 Dec 2025 09:31:39 -0600 Subject: [PATCH 2/2] docs: Replace general development task tracking with a specific bot correction plan. --- tasks.md | 60 +++++++++++++++----------------------------------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/tasks.md b/tasks.md index c7ccbb3..71d661d 100644 --- a/tasks.md +++ b/tasks.md @@ -1,50 +1,22 @@ -# Talía Development Tasks +# Plan de Corrección del Bot -This file tracks the development tasks for the Talía project. +Este plan detalla los pasos necesarios para que el bot sea funcional, responda a los botones y no se bloquee. -## Phase 1: Project Scaffolding +## 1. Corrección de Bloqueos (Agenda) +- [ ] **Hacer asíncronas las llamadas a Google Calendar**: En `app/main.py`, usar `asyncio.to_thread` o un ejecutor para las funciones que bloquean el hilo principal (como `get_agenda` que llama a `get_events`). +- [ ] **Optimizar `button_dispatcher`**: Asegurar que el despachador de botones maneje correctamente tanto funciones síncronas como asíncronas sin bloquearse. -- [x] Create `tasks.md` to track project development. -- [x] Create the basic directory structure (`app`, `app/modules`). -- [x] Create placeholder files in the root directory (`docker-compose.yml`, `Dockerfile`, `.env.example`). -- [x] Create placeholder files in the `app` directory. -- [x] Create placeholder files in the `app/modules` directory. +## 2. Corrección de Botones y Flujos (ConversationHandlers) +- [ ] **Vincular botones a conversaciones**: Modificar `app/modules/create_tag.py` y otros módulos para que sus `ConversationHandler` tengan como punto de entrada (`entry_points`) el `CallbackQueryHandler` del botón correspondiente. +- [ ] **Limpiar `button_dispatcher`**: Eliminar las llamadas manuales a inicios de conversación dentro del despachador, dejando que los `ConversationHandler` se encarguen de capturar sus propios botones. -## Phase 2: Core Logic Implementation +## 3. Robustez y Errores +- [ ] **Manejador Global de Errores**: Añadir `application.add_error_handler` en `app/main.py` para capturar cualquier fallo y loguearlo, evitando que el bot se quede "pensando" sin dar respuesta. +- [ ] **Verificación de Roles**: Asegurar que `onboarding.py` siempre entregue el menú correcto y que los IDs en `.env` estén bien cargados. -- [x] Implement `main.py` as the central orchestrator. -- [x] Implement `config.py` to handle environment variables. -- [x] Implement `permissions.py` for role-based access control. -- [x] Implement `webhook_client.py` for n8n communication. +## 4. Gestión de Procesos +- [ ] **Evitar Conflictos**: Implementar una verificación al inicio de `main.py` para asegurar que no haya otra instancia del bot corriendo con el mismo token. -## Phase 3: Module Implementation - -- [x] Implement `onboarding.py` module. -- [x] Implement `agenda.py` module. -- [x] Implement `citas.py` module. -- [x] Implement `equipo.py` module. -- [x] Implement `aprobaciones.py` module. -- [x] Implement `servicios.py` module. -- [x] Implement `admin.py` module. -- [x] Add `/print` command for authorized users. - -## Phase 4: Integrations - -- [x] Implement `calendar.py` for Google Calendar integration. -- [x] Implement `llm.py` for AI-powered responses. -- [x] Implement `scheduler.py` for daily summaries. - -## Log - -### 2024-05-22 - -- Created `tasks.md` to begin tracking development. -- Completed initial project scaffolding. -- Implemented the core logic for the bot, including the central orchestrator, permissions, and onboarding. -- Implemented the `agenda` and `citas` modules. -- Implemented the conversational flow for proposing and approving activities. -- Completed Phase 3 by implementing all modules and refactoring the main dispatcher. - -### 2024-05-23 - -- Add `/print` command for authorized users. +## 5. Verificación Final +- [ ] Probar cada botón del menú Admin: Agenda, Pendientes, Crear Tag, Más opciones. +- [ ] Verificar que el bot responda "No hay eventos" o la lista de eventos sin colgarse.