⚡ CULTIVA IA · Automatizaciones

Integración de Calendarios

Referencia técnica completa para conectar agentes IA con Google Calendar y Microsoft Outlook vía API REST

🗓️ Google Calendar API v3 📆 Microsoft Graph API 🔐 OAuth 2.0 / Azure AD ⚙️ TypeScript / Python 🤖 Agente CULTIVA IA
v3
Google Calendar
OAuth 2.0 + Service Account
Graph
Microsoft Outlook
Azure AD + Client Secret
~10
QPS Límite Google
10.000 / 10min Graph API
7/3
Días Webhook Max
Google 7d · Outlook 3d
🏢

Caso Real: Agente de Onboarding — CULTIVA IA

Automatización completa de reservas para el equipo comercial

SOLICITUD DEL CLIENTE
"Agenda un onboarding de 1h con Finca Rural El Molino (info@elmolino.es) la semana del 16-20 jun 2026. Alba y Marc deben estar disponibles. Google Meet."
PARTICIPANTES
👩
Alba Ros
alba@cultivaia.com · Google Workspace
Google
👨
Marc Pont
marc@cultivaia.com · Microsoft 365
Outlook
🏡
Finca Rural El Molino
info@elmolino.es · cliente externo
externo
FLUJO DEL AGENTE
1
Llama a freebusy.query con ambos calendarios (16–20 jun, 09:00–18:00 Madrid)
2
Calcula ventanas libres comunes con buffer de 30 min · prioriza slots de mañana
3
Crea evento en Google Calendar con conferenceDataVersion: 1 (Meet link automático)
4
Envía invitaciones a todos (sendUpdates: 'all') incluyendo cliente
5
Devuelve confirmación con link Meet, fecha y duración en Slack/CRM

Google Calendar API v3

Autenticación OAuth 2.0 y operaciones CRUD de eventos

🔐 Autenticación OAuth 2.0
Contexto de usuario + Service Account para servidor
typescript google-auth.ts
import { google } from 'googleapis';

// OAuth 2.0 — contexto de usuario
const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  process.env.GOOGLE_REDIRECT_URI
);

const { tokens } = await oauth2Client.getToken(authCode);
oauth2Client.setCredentials(tokens);
const calendar = google.calendar({
  version: 'v3', auth: oauth2Client
});

// Service Account — acceso servidor a servidor
const auth = new google.auth.GoogleAuth({
  keyFile: '/path/service-account.json',
  scopes: ['https://www.googleapis.com/auth/calendar'],
  clientOptions: { subject: 'alba@cultivaia.com' },
});
📊 Consulta Free/Busy
Verificar disponibilidad antes de crear el evento
typescript check-availability.ts
// Consultar disponibilidad semana 16-20 jun
const { data } = await calendar.freebusy.query({
  requestBody: {
    timeMin: '2026-06-16T09:00:00+02:00',
    timeMax: '2026-06-20T18:00:00+02:00',
    timeZone: 'Europe/Madrid',
    items: [
      { id: 'alba@cultivaia.com' },
      { id: 'marc@cultivaia.com' },
    ],
  },
});

// Bloques ocupados por persona
const albaBusy = data.calendars['alba@cultivaia.com'].busy;
const marcBusy = data.calendars['marc@cultivaia.com'].busy;

// → buscar ventana de 1h libre para ambos
const slot = findCommonSlot(albaBusy, marcBusy, 60);
✨ Crear Evento con Google Meet
Onboarding Finca Rural El Molino — invitación automática con enlace Meet
typescript create-onboarding.ts
const event = await calendar.events.insert({
  calendarId: 'primary',
  conferenceDataVersion: 1,          // activa creación automática de Meet
  sendUpdates: 'all',                  // notifica a todos los asistentes
  requestBody: {
    summary: 'Onboarding CULTIVA IA · Finca Rural El Molino',
    description: 'Sesión de onboarding inicial.\n\nAgenda:\n- Diagnóstico digital\n- Propuesta de automatizaciones\n- Siguiente paso',
    start: { dateTime: '2026-06-17T10:00:00', timeZone: 'Europe/Madrid' },
    end:   { dateTime: '2026-06-17T11:00:00', timeZone: 'Europe/Madrid' },
    attendees: [
      { email: 'alba@cultivaia.com',  displayName: 'Alba Ros',  responseStatus: 'accepted' },
      { email: 'marc@cultivaia.com',  displayName: 'Marc Pont',  responseStatus: 'accepted' },
      { email: 'info@elmolino.es',    displayName: 'Finca El Molino' },
    ],
    conferenceData: {
      createRequest: {
        requestId: 'cultiva-molino-' + Date.now(),
        conferenceSolutionKey: { type: 'hangoutsMeet' },
      },
    },
    reminders: {
      useDefault: false,
      overrides: [
        { method: 'email', minutes: 1440 },   // 24h antes
        { method: 'popup', minutes: 15 },     // 15min antes
      ],
    },
  },
});

console.log('✅ Evento creado:', event.data.htmlLink);
console.log('📹 Meet link:', event.data.conferenceData?.entryPoints?.[0]?.uri);
// → https://meet.google.com/abc-defg-hij

Microsoft Graph Calendar API

Azure AD OAuth 2.0 y operaciones avanzadas (findMeetingTimes)

🔐 Autenticación Azure AD
ClientSecretCredential para acceso servidor
typescript graph-auth.ts
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import { TokenCredentialAuthenticationProvider }
  from '@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials';

const credential = new ClientSecretCredential(
  process.env.AZURE_TENANT_ID,
  process.env.AZURE_CLIENT_ID,
  process.env.AZURE_CLIENT_SECRET
);

const authProvider = new TokenCredentialAuthenticationProvider(
  credential, { scopes: ['https://graph.microsoft.com/.default'] }
);

const graphClient = Client.initWithMiddleware({ authProvider });
// → listo para llamadas a /users/{id}/events
🧠 findMeetingTimes (smart scheduling)
Outlook sugiere automáticamente los mejores slots
typescript find-slots.ts
const suggestions = await graphClient
  .api(`/users/${marcUserId}/findMeetingTimes`)
  .post({
    attendees: [{
      emailAddress: { address: 'alba@cultivaia.com' },
      type: 'required',
    }],
    timeConstraint: { timeslots: [{
      start: { dateTime: '2026-06-16T09:00:00', timeZone: 'Romance Standard Time' },
      end:   { dateTime: '2026-06-20T18:00:00', timeZone: 'Romance Standard Time' },
    }]},
    meetingDuration: 'PT1H',
    maxCandidates: 5,
    minimumAttendeePercentage: 100,
  });

// suggestions.meetingTimeSuggestions[0].meetingTimeSlot
// → { start: { dateTime: '2026-06-17T08:00:00', ... } }
🔄

Referencia RRULE — Eventos Recurrentes

Patrones de recurrencia estándar iCalendar para Google Calendar

Diario (30 veces)
RRULE:FREQ=DAILY;COUNT=30
Semanal L/X/V
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
Quincenal martes
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU
Mensual día 1
RRULE:FREQ=MONTHLY;BYMONTHDAY=1
2º martes de cada mes
RRULE:FREQ=MONTHLY;BYDAY=2TU
Semanal hasta fecha
RRULE:FREQ=WEEKLY;BYDAY=MO;UNTIL=20261231T000000Z
💡
Para Outlook usa objeto recurrence estructurado con pattern (daily/weekly/monthly) + range (startDate/endDate/numberOfOccurrences).
⚖️

Comparativa Google Calendar vs Outlook

Diferencias clave para elegir la integración adecuada

Funcionalidad Google Calendar Outlook / Graph
Autenticación Google OAuth 2.0 · Service Account Azure AD OAuth 2.0 · Client Secret
Videollamada integrada hangoutsMeet via conferenceData isOnlineMeeting: true (Teams)
Consulta disponibilidad freebusy.query getSchedule / findMeetingTimes
Webhooks (duración máx.) Push notifications · 7 días Subscriptions · 3 días
Eventos recurrentes RRULE strings (iCalendar) Objeto recurrence estructurado
Smart scheduling No nativo (implementar manualmente) findMeetingTimes con ranking
Sync eficiente Sync tokens (incremental) Delta queries (incremental)
Límite de tasa ~10 QPS por usuario 10.000 req / 10 min por app/tenant
Librerías oficiales googleapis @microsoft/microsoft-graph-client
📋

Buenas Prácticas del Agente

Reglas críticas para una integración robusta en producción

🌍
Siempre especifica timeZone — nunca confíes en el timezone del servidor. Usa Europe/Madrid o el TZ del cliente explícitamente.
Free/busy antes de crear — comprueba disponibilidad real antes de insertar cualquier evento. No doble-bookear.
🔔
Renovación de webhooks — Google (7d) y Outlook (3d) expiran. Implementa un job de renovación automática o perderás notificaciones.
📅
ISO 8601 para todo — nunca parsees fechas como strings manualmente. Usa new Date().toISOString() o date-fns.
🔄
singleEvents: true en Google Calendar — expande recurrentes para listar instancias individuales correctamente.
⏱️
Buffer entre reuniones — back-to-back es un problema de UX. Añade 30 min de buffer al buscar slots libres.
📨
Notifica siempre — usa sendUpdates: 'all'. Los cambios silenciosos causan confusión y faltas de asistencia.
📈
Sync incremental — usa sync tokens (Google) o delta queries (Outlook) para polling eficiente; evita listar todos los eventos en cada llamada.
Resultado para CULTIVA IA: el agente de onboarding consulta freebusy de Alba y Marc, encuentra el slot del martes 17 jun 10:00–11:00, crea el evento con Meet link y envía invitaciones automáticas a los 3 asistentes — todo sin intervención humana.
⚠️
Webhook renewal: si usas notificaciones push, programa un job cada 6 días (Google) o 2 días (Outlook) para renovar las suscripciones antes de que expiren.