Ir al contenido principal
Agentes IA programados: automatiza sin pulsar send

Agentes IA programados: automatiza sin pulsar send

AI Integration
8 min readPor Daily Miranda Pardo

Construiste el agente. Funciona bien. Extrae datos, genera informes, manda emails. El problema: cada vez que quieres que actúe, alguien tiene que pulsar un botón o mandar un mensaje.

Eso no es automatización. Es un acceso directo glorificado.

El siguiente nivel no es construir un agente más inteligente — es un agente que actúa solo, en el momento correcto, aunque sean las tres de la mañana y nadie esté mirando.

El agente que vive a demanda no escala

La mayoría de implementaciones de agentes IA en producción comparten el mismo patrón: el usuario envía un prompt, el agente responde. Es útil, pero limita todo el potencial de la automatización real.

Los casos de uso más valiosos en una empresa no son los conversacionales — son los recurrentes:

  • El informe de cierre del día que alguien tiene que acordarse de pedir
  • La sincronización de datos entre sistemas que se hace a mano cada noche
  • El resumen semanal de KPIs que se pospone porque siempre hay cosas urgentes
  • La detección de anomalías en facturación que nadie ve hasta que el cliente llama

Todos estos casos tienen algo en común: el valor no está en la respuesta al usuario, está en que ocurra sin que nadie lo pida.

Para eso existen los agentes programados. Y construirlos tiene más matices de lo que parece.

La trampa del cron directo

El error más común al implementar un agente con cron: ejecutar el agente directamente dentro del handler del cron.

// ❌ El patrón que parece correcto pero rompe en producción
export async function GET() {
  // Esta función muere si el agente tarda más de 60s en Vercel Hobby
  // o más de 5min en Vercel Pro — sin aviso, sin retry
  const result = await runFullAgentWorkflow(); // 3-8 minutos reales
  return Response.json({ result });
}

El cron de Vercel dispara una petición HTTP. Esa petición tiene los mismos límites de timeout que cualquier función serverless. Si tu agente tarda cuatro minutos en procesar facturas de un mes, el cron muere silenciosamente a los 60 segundos en el plan Hobby — o a los 300 segundos en Pro — sin error visible, sin reintento, sin registro de que algo falló.

El lunes llega el responsable de contabilidad y el informe no está. Nadie sabe por qué. Tú revisas Vercel y ves que la función invocation completion rate bajó al 0%. La mañana que alguien lo nota es la mañana que algo ya se perdió.

La arquitectura correcta: cron → enqueue → worker

El cron no ejecuta el agente. El cron encola el trabajo. El worker lo procesa sin límite de tiempo.

CRON (08:00 UTC)
  └─▶ API Route /api/cron/daily-report
        └─▶ INSERT job_queue (status: 'pending')
              └─▶ Worker externo / Edge Function larga
                    └─▶ Agente IA (sin límite de tiempo)
                          └─▶ Resultado en Supabase

Este desacoplamiento garantiza tres cosas:

  1. El cron siempre termina en menos de 200ms — nunca timeout
  2. El agente puede tardar lo que necesite
  3. Si el agente falla, el job queda con status: 'error' y puedes reintentar

Configuración de cron en Vercel

// vercel.json
{
  "crons": [
    {
      "path": "/api/cron/daily-report",
      "schedule": "0 8 * * 1-5"
    },
    {
      "path": "/api/cron/nightly-sync",
      "schedule": "0 2 * * *"
    }
  ]
}

El handler del cron — solo encola, nunca ejecuta

// app/api/cron/daily-report/route.ts
import { headers } from 'next/headers';
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export const runtime = 'nodejs';

export async function GET() {
  // Verifica que la llamada viene de Vercel, no de cualquiera
  const authHeader = headers().get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD

  // Evita duplicados si el cron se dispara dos veces
  const { data: existing } = await supabase
    .from('agent_jobs')
    .select('id, status')
    .eq('type', 'daily_report')
    .eq('run_date', today)
    .maybeSingle();

  if (existing) {
    return Response.json({
      message: 'Job already scheduled or completed',
      status: existing.status,
    });
  }

  // Encola el trabajo — el handler termina aquí
  const { error } = await supabase.from('agent_jobs').insert({
    type: 'daily_report',
    run_date: today,
    status: 'pending',
    payload: { date: today, recipients: ['cfo@empresa.com'] },
  });

  if (error) {
    console.error('Failed to enqueue job:', error);
    return Response.json({ error: 'Enqueue failed' }, { status: 500 });
  }

  return Response.json({ message: 'Job enqueued', date: today });
}

Idempotencia: el problema que nadie piensa hasta que ocurre

Los crons no son deterministas. Vercel puede disparar el mismo cron dos veces si hay un problema de red o de reloj. AWS hace lo mismo. Cualquier sistema de tareas distribuido hace lo mismo.

Si tu agente no es idempotente, el doble disparo produce el doble de trabajo: dos emails al CFO, dos PDFs idénticos en el bucket, dos filas en la base de datos con el mismo informe.

La solución es una combinación de clave de idempotencia (tipo + fecha) y lock en base de datos:

// lib/agent-jobs.ts
export async function claimJob(
  supabase: ReturnType<typeof createClient>,
  jobId: string
): Promise<boolean> {
  // UPDATE atómico: solo avanza si está en 'pending'
  const { data, error } = await supabase
    .from('agent_jobs')
    .update({ status: 'running', started_at: new Date().toISOString() })
    .eq('id', jobId)
    .eq('status', 'pending') // Condición de lock
    .select('id')
    .maybeSingle();

  if (error || !data) {
    // Otro worker ya reclamó el job
    return false;
  }

  return true;
}

El worker solo procesa si el UPDATE devuelve una fila. Si dos workers compiten por el mismo job, uno obtiene data y el otro obtiene null. Sin race conditions, sin trabajo duplicado.

El agente worker — sin prisa, sin límite

El worker es una función separada que se ejecuta fuera del ciclo HTTP. Puede ser un Edge Function de larga duración, una función de Inngest, una tarea de QStash o un proceso en Railway. Lo importante es que no tiene los límites del handler del cron.

// lib/workers/daily-report.worker.ts
import { anthropic } from '@anthropic-ai/sdk';

export async function processDailyReport(job: AgentJob) {
  const { date, recipients } = job.payload;

  // 1. Recupera datos del día desde Supabase
  const metrics = await fetchDailyMetrics(date);

  // 2. Genera el análisis con el LLM
  const analysis = await anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 2000,
    messages: [{
      role: 'user',
      content: `Analiza estas métricas del día ${date} y genera un resumen ejecutivo en español para el equipo de dirección. Destaca anomalías y tendencias.\n\nMétricas:\n${JSON.stringify(metrics, null, 2)}`,
    }],
  });

  const summary = analysis.content[0].type === 'text'
    ? analysis.content[0].text
    : '';

  // 3. Genera PDF y envía emails
  await generateAndSendReport({ date, summary, metrics, recipients });

  return { summary, metricsCount: metrics.length };
}

Observabilidad: saber que el agente de las 3AM funcionó

Si el agente falla a las 3 de la mañana y nadie lo ve, ¿falló? Para tu CFO, sí — el informe no está.

Necesitas tres cosas:

1. Registro de cada ejecución en base de datos:

create table agent_jobs (
  id          uuid        default gen_random_uuid() primary key,
  type        text        not null,
  run_date    date        not null,
  status      text        default 'pending',
  payload     jsonb       not null default '{}',
  result      jsonb,
  error_msg   text,
  started_at  timestamptz,
  finished_at timestamptz,
  created_at  timestamptz default now(),
  unique (type, run_date)
);

2. Alertas en caso de error — un webhook a Slack o un email al equipo técnico cuando status = 'error':

async function notifyJobFailure(job: AgentJob, error: Error) {
  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: `⚠️ Agente cron fallido: *${job.type}* (${job.run_date})\n\`${error.message}\``,
    }),
  });
}

3. Dashboard mínimo — una query en Supabase que muestra el histórico de ejecuciones. Con esto, un vistazo de 10 segundos cada mañana confirma que todo funcionó.

Cuándo usar Vercel Cron vs servicios externos

Vercel Cron funciona bien para agentes que:

  • Encolan trabajo en menos de 5 segundos
  • No necesitan más de una ejecución simultánea
  • Tienen granularidad de minutos (no segundos)

Para casos más complejos — múltiples intentos, colas con prioridad, workflows de varios pasos — considera Inngest o QStash de Upstash. Ambos se integran bien con el stack de integración IA en Next.js que usamos en DAILYMP.

La diferencia clave: Vercel Cron dispara una petición HTTP y no sabe si el trabajo se completó. Inngest y QStash gestionan el estado, los reintentos y los fallos de forma nativa — son la elección correcta cuando el volumen o la criticidad del proceso lo justifica.

El resultado: procesos que no dependen de que alguien recuerde

Un agente programado correctamente no requiere disciplina del equipo. El informe llega porque el sistema lo genera, no porque alguien lo pidió. La sincronización ocurre porque está en el calendario del servidor, no en la lista de tareas de una persona.

Este es exactamente el tipo de arquitectura que implementamos en los proyectos de automatización con agentes IA en DAILYMP: sistemas que funcionan solos, con observabilidad real y sin depender de que nadie pulse un botón.

Si tienes un proceso recurrente en tu empresa que todavía depende de que alguien lo recuerde, hay una versión automatizada esperando.

Cuéntame cuál es tu proceso →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.