la primera parte de mi viaje construyendo Fernão, mi agente personal de IA. ¡Ahora es el momento de continuar la historia y sumergirnos en la segunda parte!
En esta publicación, lo guiaré a través de las últimas mejoras de Fernão, perfeccionando las funciones existentes y agregando nuevas capacidades al agente. Comencemos con lo que cambió.
¿Recuerda la función que buscaba el calendario a través de ICS (el formato de calendario universal) y extraía las tareas de mi calendario?
Esa función fue un desastre y reflejó una mala decisión arquitectónica. Los calendarios ICS no admiten el filtrado nativo, lo que significa que cada solicitud requería extraer todos los eventos del calendario y filtrarlos posteriormente. En la práctica, Fernão descargaba toda mi agenda sólo para extraer algunas reuniones relevantes.
Era como traer a casa una biblioteca entera sólo para buscar una sola frase en un libro.
Cuanto más intentaba optimizar la función, menos llegaba a ninguna parte, porque necesitaba una nueva solución de sistema. Necesitaba cambiar la forma en que el sistema recibía el calendario y NUNCA solucionaría este cuello de botella con ICS.
Así que profundicé y descubrí que Google proporciona acceso al calendario a través de una API que admite filtrado nativo. Fernão ahora sólo recupera los eventos que realmente necesita. Esto mejoró la generación del cronograma, que bajó de casi cinco minutos a unos veinte segundos.
Con esta nueva tubería implementada, también refactoricé la lógica circundante. Toda la función ahora es significativamente más limpia y rápida. Ahora tenemos una hermosa manera de recuperar los eventos del calendario a través de API:
def get_events_for_date(target_date=None, use_api=True): """ Obtiene eventos para una fecha específica de Google Calendar. Prueba la API primero (si use_api=True), recurre a ICS si la API falla. Args: target_date: objeto datetime.date para el día objetivo. Si no hay ninguno, usa hoy. use_api: si es True, prueba la API de Google Calendar primero. Si es False, usa solo ICS. Devuelve: Lista de diccionarios de eventos """ si use_api y GCAL_API_AVAILABLE: print("[GCal] Intentando usar la API de Google Calendar…") intente: eventos = get_events_for_date_api(target_date) si los eventos no son Ninguno: print(f"[GCal] {len(events)} eventos obtenidos con éxito a través de API") return events else: print("[GCal] API devolvió Ninguno. Regresando a ICS…") excepto excepción como e: print(f"[GCal] API falló con error: {e}") print("[GCal] Regresando a ICS…") # Retorno a ICS print("[GCal] Usando el método de alimentación de ICS…") return get_events_for_date_ics(target_date)
.. y aquí está nuestra búsqueda de API:
def get_events_for_date_api(target_date=None): """ Obtiene eventos para una fecha específica de Google Calendar usando la API de Calendar. Args: target_date: objeto datetime.date para el día objetivo. Si no hay ninguno, usa hoy. Devuelve: Lista de diccionarios de eventos, o Ninguno si falla la llamada API. """ service = get_calendar_service() si no es servicio: return Ninguno day_start, day_end, target_date, tz_name, local = _get_local_time_range(target_date) print(f"n[GCal API] Obteniendo eventos para {target_date.strftime('%Y-%m-%d')}") print(f" Timezone: {tz_name}") # Obtenga ID de calendario del entorno, o use el calendario primario_ids_str = os.getenv('GCAL_CALENDAR_IDS', '') if calendar_ids_str: calendar_ids = [cid.strip() para cid en calendar_ids_str.split(',')] else: calendar_ids = ['primary'] all_events =[]# Obtener de cada calendario para calendar_id en calendar_ids: intente: print(f"[GCal API] Obteniendo de calendario: {calendar_id}") # Llame a la API de calendario con timeoutlobally old_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(10) intente: events_result = service.events().list( calendarId=calendar_id, timeMin=day_start.isoformat(), timeMax=day_end.isoformat(), singleEvents=True, orderBy='startTime').execute() finalmente: socket.setdefaulttimeout(old_timeout) eventos = events_result.get('items',[]) print(f"[GCal API] Encontrado {len(events)} evento(s) en {calendar_id}") # Analizar eventos para evento en eventos: # Obtener hora de inicio start = event['start'].get('dateTime', event['start'].get('date')) end = event['end'].get('dateTime', event['end'].get('date')) # Analizar fecha y hora si 'T' en inicio: # DateTime start_dt = datetime.fromisoformat(start.replace('Z', '+00:00')) end_dt = datetime.fromisoformat(end.replace('Z', '+00:00')) # Convertir a zona horaria local start_local = start_dt.astimezone(local) end_local = end_dt.astimezone(local) start_str = start_local.strftime("%H:%M") end_str = end_local.strftime("%H:%M") else: # Evento de todo el día start_str = "00:00" end_str = "23:59" all_events.append({ "title": event.get('summary', 'Evento sin título'), "start": start_str, "end": end_str, "ubicación": event.get('ubicación', ''), "descripción": event.get('descripción', '') }) excepto Excepción como e: print(f"[GCal API] Error al obtener de {calendar_id}: {e}") continuar # Ordenar por hora de inicio all_events.sort(key=lambda x: x["start"]) print(f"[GCal API] Total de eventos: {len(all_events)}") return all_events
Más allá de las mejoras de back-end, también agregué nuevas funciones al asistente.
En la vista de programación, ahora puedo marcar las tareas como completadas. Lo bueno es que, cuando lo hago, se sincronizan automáticamente con la aplicación Microsoft To-Do.
¡Maravilloso!
Me hace pensar que, con el tiempo, muchos de nosotros terminaremos construyendo nuestros propios “sistemas operativos personales”, ensamblando flujos de trabajo a partir de nuestras herramientas preferidas, integrando lo que necesitamos y reemplazando componentes cada vez que aparezcan mejores opciones. Si las funciones se vuelven fáciles de replicar, la lealtad a plataformas específicas se debilitará.
Esto también plantea una pregunta interesante: ¿las empresas eventualmente intentarán bloquear sus sistemas restringiendo las API y las integraciones externas? Probablemente. Pero es poco probable que aislarse funcione a largo plazo. Si los usuarios no pueden conectar herramientas a sus propios flujos de trabajo, simplemente se trasladarán a otra parte (… ¿y será este un comportamiento que solo realizarán los usuarios avanzados?)
Con esta nueva capacidad en Schedule Maker, pasé a implementar otra característica para Fernão, sugerida por uno de los lectores.
Presentamos: el Task Breaker.
Task Breaker sigue un flujo de trabajo simple:
Comience con una tarea genérica grande en Microsoft To Do; Agregue contexto sobre cómo se debe dividir la tarea; Fernão descompone la tarea y construye un plan; Las tareas resultantes se guardan nuevamente en Tareas pendientes con fechas de vencimiento y luego aparecen en el asistente diario;
Así es como aparece el Task Breaker en la barra lateral:
Y aquí está la interfaz actual (aún aproximada) del Task Breaker:
Tomemos un ejemplo real. Una de las tareas más importantes en mi lista de tareas pendientes es "Documentación del proyecto en DareData". Esta no es una pequeña tarea operativa, es un proyecto estructural.
El objetivo es consolidar y formalizar el conocimiento interno de la empresa en Notion, asegurándose de que las principales preguntas sobre la empresa queden claramente respondidas en nuestro Knowledge Hub. Eso significa revisar cada departamento, identificar brechas, crear o perfeccionar páginas, estructurar la información adecuadamente y asignar una propiedad clara para cada sección.
En la práctica, esto requiere decisiones de auditoría, redacción y gobernanza. No es algo que “simplemente se hace” de una sola vez y quiero completarlo en tres semanas. Siendo realistas, puedo dedicar entre 30 minutos y una hora al día.
Este es exactamente el tipo de tarea que se beneficia de la descomposición, por lo que usaré información de contexto en Task Breaker y convertiré esta giga tarea en un plan viable:
Actualmente estoy construyendo nuestro Centro de conocimiento en Notion. Aquí está la estructura actual de Notion y departamentos: # DareData Hub ## Manual para miembros de la red [Manual para miembros de la red DareData] ## Equipos ### Ventas y asociaciones ### Marketing (marca y Gen-OS) ### Finanzas ### Entrega [Especialistas en tecnología] [Directores] [Gerentes de cuentas] Necesitaré: – Revisar cada departamento y crear las páginas que tengan sentido (busque en la web para obtener más contexto sobre qué es DareData si necesito ideas de las páginas que necesito crear en cada departamento o utilizar su conocimiento sobre lo que definitivamente debe documentarse en una empresa B2B de 130 personas). – Asegúrese de que cada página tenga un propietario – Vuelva a visitar las sugerencias registradas por mi equipo en las sugerencias; probablemente pueda elegir una o dos sugerencias cada día – Agregue algunos departamentos que no están allí: administración, miembros principales, finanzas, producto. Tengo entre 30 minutos y 1 hora para trabajar todos los días y quiero completar este proyecto antes del 7 de marzo de 2026.
Después de darle al “desglose”, Fernão comenzará a forjar:
¡Maravilloso x2!
Ahora podemos revisar las tareas y el plan generado y enviarlos a mi Microsoft To-Do. También necesito asignar a qué lista de tareas pendientes de Microsoft se asignarán:
¡Fresco! Veamos cómo se ve en la aplicación de Microsoft:
Perfecto.
Mientras probaba esto, me di cuenta de que quería ajustar dos cosas.
Primero, Fernão está destinado a ser un cronista medieval, no un guerrero de fantasía, así que cambiaré el diseño de la forja. Su ropa debe parecer medieval. En segundo lugar, en una nota más práctica, necesito un botón "Enviar todo". Enviar tareas una por una es tedioso, especialmente cuando se dividen proyectos más grandes.
Dividamos otra tarea después de realizar estos cambios:
Fernão ahora forja un chaleco medieval adecuado:
Y ahora también podemos usar el bonito botón "Enviar todo":
Probablemente eliminaré el emoji. Ese tipo de "emojificación" es muy típico del código generado por LLM en el front-end y realmente me molesta.
De todos modos, construir esta nueva característica para Fernão fue realmente satisfactorio. Si tiene ideas para funciones adicionales, ¡estoy abierto a sugerencias!
A continuación se muestra el mensaje actual que estoy usando para desglosar las tareas. Continuaré perfeccionándolo mientras experimento y observo cómo funciona en casos de uso reales.
nombre: task_breakdown descripción: divide una tarea en subtareas procesables de 20 minutos max_tokens: 4096 variables: – nombre_tarea – contexto_tarea – plantilla fecha_actual: | Eres un experto en productividad y ayudas a dividir tareas complejas en partes manejables de 20 minutos. **FECHA ACTUAL:** {current_date} **TAREA PARA DESGLOSE:** {task_name} **CONTEXTO PROPORCIONADO POR EL USUARIO:** {task_context} **TU TRABAJO:** Divida esta tarea en subtareas específicas y procesables que se pueden completar en aproximadamente 20 minutos. **REGLAS:** 1. Cada subtarea debe ser concreta y ejecutable (comienza con un verbo) 2. Cada subtarea debe tomar ~20 minutos (pueden ser de 15 a 25 minutos, pero el objetivo es 20) 3. Las subtareas deben seguir un orden lógico 4. Sea específico: evite tareas vagas como “trabajar en X” 5. Si la tarea ya es lo suficientemente pequeña, puede crear de 1 a 3 subtareas 6. Si es grande, cree > 5 subtareas 7. Considere el contexto proporcionado: utilícelo para hacer que las subtareas sean relevantes y específicas 8. **PROGRAMACIÓN:** Según el contexto del usuario (p. ej., "cada dos días", "solo fines de semana"), sugiera una fecha de vencimiento específica para cada tarea a partir de la fecha actual. **FORMATO DE SALIDA:** Devuelve SÓLO una lista rebajada de subtareas en este formato: – {nombre_tarea} – [Descripción de la subtarea] (20 min) [Vence: AAAA-MM-DD] Ejemplo: – {nombre_tarea} – Crear repositorio de proyectos (20 min) [Vence: 2026-02-13] – {nombre_tarea} – Configura la canalización de CI/CD (20 min) [Vence: 2026-02-15] NO incluya ningún otro texto o explicaciones. Sólo la lista.
Paralelamente, ya estoy trabajando en varios módulos nuevos:
Un analizador de dividendos para proyectar los ingresos de mis acciones y ETF. Un asistente de redacción para crear un plan editorial para mis escritos. Un módulo de Descuentos para consultar promociones relevantes cuando planeo una compra. Un Organizador de guitarras para estructurar y programar sesiones de práctica cada vez que recojo mi guitarra.
¡Estén atentos a los próximos módulos y espero que esto también los inspire para sus proyectos personales!