From 96542bcd6104a3bcedb3c42c2bf1f10c6ac0d5cb Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Thu, 18 Dec 2025 00:23:11 -0600 Subject: [PATCH] feat: Implement dynamic Google Calendar agenda, and configure OpenAI model, Calendly link, and daily summary schedule. --- .env.example | 3 +++ app/calendar.py | 22 ++++++++++++++++++++++ app/config.py | 9 +++++++++ app/llm.py | 6 +++--- app/modules/agenda.py | 39 +++++++++++++++++++++++++-------------- app/modules/citas.py | 8 ++++---- app/scheduler.py | 13 ++++++++++--- 7 files changed, 76 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index c7ef4e9..6c97a4e 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,7 @@ GOOGLE_SERVICE_ACCOUNT_FILE= CALENDAR_ID= N8N_WEBHOOK_URL= OPENAI_API_KEY= +OPENAI_MODEL=gpt-3.5-turbo +DAILY_SUMMARY_TIME=07:00 +CALENDLY_LINK=https://calendly.com/user/appointment-link TIMEZONE=America/Mexico_City diff --git a/app/calendar.py b/app/calendar.py index 00d38c1..64e25c3 100644 --- a/app/calendar.py +++ b/app/calendar.py @@ -113,3 +113,25 @@ def create_event(summary, start_time, end_time, attendees, calendar_id=CALENDAR_ except HttpError as error: print(f"Ocurrió un error al crear el evento: {error}") return None + + +def get_events(start_time, end_time, calendar_id=CALENDAR_ID): + """ + Obtiene la lista de eventos entre dos momentos. + """ + try: + events_result = ( + service.events() + .list( + calendarId=calendar_id, + timeMin=start_time.isoformat(), + timeMax=end_time.isoformat(), + singleEvents=True, + orderBy="startTime", + ) + .execute() + ) + return events_result.get("items", []) + except HttpError as error: + print(f"Ocurrió un error al obtener eventos: {error}") + return [] diff --git a/app/config.py b/app/config.py index 708268c..dedf942 100644 --- a/app/config.py +++ b/app/config.py @@ -28,5 +28,14 @@ N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL") # Llave de la API de OpenAI para usar modelos de lenguaje (como GPT) OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +# Modelo de OpenAI a utilizar (ej. gpt-3.5-turbo, gpt-4) +OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo") + +# Hora del resumen diario (formato HH:MM) +DAILY_SUMMARY_TIME = os.getenv("DAILY_SUMMARY_TIME", "07:00") + +# Enlace de Calendly para agendar citas +CALENDLY_LINK = os.getenv("CALENDLY_LINK", "https://calendly.com/user/appointment-link") + # Zona horaria por defecto para el manejo de fechas y horas TIMEZONE = os.getenv("TIMEZONE", "America/Mexico_City") diff --git a/app/llm.py b/app/llm.py index 547c11f..a67b178 100644 --- a/app/llm.py +++ b/app/llm.py @@ -2,7 +2,7 @@ # Este script se encarga de la comunicación con la inteligencia artificial de OpenAI. import openai -from config import OPENAI_API_KEY +from config import OPENAI_API_KEY, OPENAI_MODEL def get_smart_response(prompt): """ @@ -19,9 +19,9 @@ def get_smart_response(prompt): # Creamos el cliente de OpenAI client = openai.OpenAI(api_key=OPENAI_API_KEY) - # Solicitamos una respuesta al modelo GPT-3.5-turbo + # Solicitamos una respuesta al modelo configurado response = client.chat.completions.create( - model="gpt-3.5-turbo", + model=OPENAI_MODEL, messages=[ {"role": "system", "content": "Eres un asistente útil."}, {"role": "user", "content": prompt}, diff --git a/app/modules/agenda.py b/app/modules/agenda.py index e921066..ae39dcd 100644 --- a/app/modules/agenda.py +++ b/app/modules/agenda.py @@ -2,21 +2,32 @@ # Este módulo se encarga de manejar las peticiones relacionadas con la agenda. # Permite obtener y mostrar las actividades programadas para el día. +import datetime +from calendar import get_events + def get_agenda(): """ - Obtiene y muestra la agenda del usuario para el día actual. - - Por ahora, esta función devuelve una agenda de ejemplo fija. - El plan es conectarla con Google Calendar para que sea real. + Obtiene y muestra la agenda del usuario para el día actual desde Google Calendar. """ - # TODO: Obtener la agenda dinámicamente desde Google Calendar. - agenda_text = ( - "📅 *Agenda para Hoy*\n\n" - "• *10:00 AM - 11:00 AM*\n" - " Reunión de Sincronización - Proyecto A\n\n" - "• *12:30 PM - 1:30 PM*\n" - " Llamada con Cliente B\n\n" - "• *4:00 PM - 5:00 PM*\n" - " Bloque de trabajo profundo - Desarrollo Talía" - ) + 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) + + events = get_events(start_of_day, end_of_day) + + if not events: + 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" + return agenda_text diff --git a/app/modules/citas.py b/app/modules/citas.py index a05d66e..9996c02 100644 --- a/app/modules/citas.py +++ b/app/modules/citas.py @@ -2,16 +2,16 @@ # Este módulo maneja la programación de citas para los clientes. # Permite a los usuarios obtener un enlace para agendar una reunión. +from config import CALENDLY_LINK + def request_appointment(): """ Proporciona al usuario un enlace para agendar una cita. - Por ahora devuelve un enlace de ejemplo a Calendly. - La idea es que sea un enlace dinámico generado por n8n. + Usa el enlace configurado en las variables de entorno. """ - # TODO: Integrar con un servicio real o un flujo de n8n para dar un enlace personalizado. response_text = ( "Para agendar una cita, por favor utiliza el siguiente enlace: \n\n" - "[Enlace de Calendly](https://calendly.com/user/appointment-link)" + f"[Agendar Cita]({CALENDLY_LINK})" ) return response_text diff --git a/app/scheduler.py b/app/scheduler.py index 8a2ce1d..371190a 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -6,7 +6,7 @@ from datetime import time from telegram.ext import ContextTypes import pytz -from config import OWNER_CHAT_ID, TIMEZONE +from config import OWNER_CHAT_ID, TIMEZONE, DAILY_SUMMARY_TIME from modules.agenda import get_agenda # Configuramos el registro de eventos (logging) para ver qué pasa en la consola @@ -53,8 +53,15 @@ def schedule_daily_summary(application) -> None: # Configuramos la zona horaria (ej. America/Mexico_City) tz = pytz.timezone(TIMEZONE) - # Programamos la tarea para que corra todos los días a las 7:00 AM - scheduled_time = time(hour=7, minute=0, tzinfo=tz) + # Obtenemos la hora y minutos desde la configuración (ej. "07:00") + try: + hour, minute = map(int, DAILY_SUMMARY_TIME.split(':')) + except ValueError: + logger.error(f"Formato de DAILY_SUMMARY_TIME inválido: {DAILY_SUMMARY_TIME}. Usando 07:00 por defecto.") + hour, minute = 7, 0 + + # Programamos la tarea para que corra todos los días a la hora configurada + scheduled_time = time(hour=hour, minute=minute, tzinfo=tz) job_queue.run_daily( send_daily_summary,