Saltar al contenido

Automatizar tareas es una necesidad en cualquier aplicación web. Limpiar cachés, enviar recordatorios, generar reportes o sincronizar datos son operaciones que no queremos ejecutar manualmente. Laravel resuelve esto con su Task Scheduler, un sistema que permite definir todas las tareas programadas directamente en PHP, olvidándonos de gestionar múltiples entradas cron en el servidor.

Requisitos

Para seguir esta guía necesitas Laravel 11 o superior, ya que la configuración del scheduler se realiza en routes/console.php.

El concepto: un único cron para todo

El Task Scheduler funciona con una premisa simple: configuras una única entrada cron en tu servidor que ejecuta schedule:run cada minuto. Laravel se encarga del resto, evaluando qué tareas deben ejecutarse en ese momento.

* * * * * cd /ruta-a-tu-proyecto && php artisan schedule:run >> /dev/null 2>&1

Esta línea es todo lo que necesitas en tu servidor. A partir de aquí, todo se gestiona desde tu código PHP.

Configuración básica

Las tareas programadas se definen en routes/console.php utilizando el facade Schedule:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('model:prune')->daily();

Este código ejecutará el comando model:prune una vez al día. No necesitas tocar el servidor para añadir, modificar o eliminar tareas.

Formas de programar tareas

Laravel ofrece varios enfoques para definir qué se ejecuta.

Comandos Artisan

La forma más común. Puedes ejecutar cualquier comando Artisan, ya sea de Laravel o personalizado:

<?php

use Illuminate\Support\Facades\Schedule;

// Comandos nativos de Laravel
Schedule::command('cache:clear')->weekly();
Schedule::command('queue:prune-batches')->daily();

// Comando personalizado con argumentos
Schedule::command('emails:send --force')->dailyAt('08:00');

Closures

Para tareas simples que no justifican crear un comando:

<?php

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
use App\Models\User;

Schedule::call(function () {
    Log::info('Heartbeat: la aplicación sigue viva');
})->everyFiveMinutes();

Schedule::call(fn () => User::where('trial_ends_at', '<', now())->update(['status' => 'expired']))->daily();

Jobs

Si ya tienes jobs definidos, puedes programarlos directamente:

<?php

use Illuminate\Support\Facades\Schedule;
use App\Jobs\ProcessDailyReports;
use App\Jobs\SyncInventory;

Schedule::job(new ProcessDailyReports)->dailyAt('06:00');
Schedule::job(new SyncInventory, 'sync-queue')->everyThirtyMinutes();

El segundo parámetro permite especificar la cola donde se despachará el job.

Comandos del sistema

Para ejecutar comandos shell directamente:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();

Frecuencias de ejecución

Laravel proporciona una API expresiva para definir cuándo se ejecutan las tareas.

Frecuencias básicas

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('task:run')->everyMinute();
Schedule::command('task:run')->everyTwoMinutes();
Schedule::command('task:run')->everyFiveMinutes();
Schedule::command('task:run')->everyTenMinutes();
Schedule::command('task:run')->everyFifteenMinutes();
Schedule::command('task:run')->everyThirtyMinutes();
Schedule::command('task:run')->hourly();
Schedule::command('task:run')->hourlyAt(17); // En el minuto 17 de cada hora
Schedule::command('task:run')->everyTwoHours();
Schedule::command('task:run')->daily(); // A medianoche
Schedule::command('task:run')->dailyAt('13:00');
Schedule::command('task:run')->twiceDaily(1, 13); // A la 1:00 y 13:00
Schedule::command('task:run')->weekly();
Schedule::command('task:run')->weeklyOn(1, '8:00'); // Lunes a las 8:00
Schedule::command('task:run')->monthly();
Schedule::command('task:run')->monthlyOn(4, '15:00'); // Día 4 a las 15:00
Schedule::command('task:run')->quarterly();
Schedule::command('task:run')->yearly();

Frecuencias personalizadas con cron

Cuando necesitas más control, puedes usar expresiones cron directamente:

<?php

use Illuminate\Support\Facades\Schedule;

// Cada día a las 3:30 AM
Schedule::command('backup:run')->cron('30 3 * * *');

// Cada 5 minutos durante horario laboral (9-18h)
Schedule::command('sync:check')->cron('*/5 9-18 * * *');

Restricciones adicionales

Puedes encadenar métodos para restringir cuándo se ejecuta una tarea.

Por día de la semana

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('reports:generate')
    ->daily()
    ->weekdays(); // Solo de lunes a viernes

Schedule::command('maintenance:run')
    ->daily()
    ->sundays(); // Solo domingos

// Métodos disponibles: mondays(), tuesdays(), wednesdays(), 
// thursdays(), fridays(), saturdays(), sundays(), weekdays(), weekends()

Por horario

<?php

use Illuminate\Support\Facades\Schedule;

// Solo entre las 8:00 y las 17:00
Schedule::command('notifications:send')
    ->everyFifteenMinutes()
    ->between('8:00', '17:00');

// Excepto entre las 2:00 y las 4:00 (ventana de mantenimiento)
Schedule::command('heavy:process')
    ->hourly()
    ->unlessBetween('2:00', '4:00');

Por entorno

<?php

use Illuminate\Support\Facades\Schedule;

// Solo en producción
Schedule::command('analytics:process')->daily()->environments(['production']);

// Solo en staging y producción
Schedule::command('cache:warm')->hourly()->environments(['staging', 'production']);

Condiciones dinámicas

<?php

use Illuminate\Support\Facades\Schedule;
use App\Models\Setting;

// Ejecutar solo si se cumple una condición
Schedule::command('emails:promotional')
    ->daily()
    ->when(fn () => Setting::get('promotional_emails_enabled'));

// Saltar si se cumple una condición
Schedule::command('sync:external')
    ->hourly()
    ->skip(fn () => app()->isDownForMaintenance());

Control de ejecución

Estas opciones son críticas en entornos de producción.

Evitar solapamientos

Si una tarea tarda más de lo esperado, podrías terminar con múltiples instancias ejecutándose simultáneamente:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('reports:heavy')->hourly()->withoutOverlapping();

Por defecto, el bloqueo expira a las 24 horas. Puedes personalizarlo:

<?php

use Illuminate\Support\Facades\Schedule;

// El bloqueo expira a los 30 minutos
Schedule::command('reports:heavy')->hourly()->withoutOverlapping(30);

Ejecutar en un solo servidor

En entornos con múltiples servidores, evita que la misma tarea se ejecute en todos:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('payments:process')->daily()->onOneServer();

Esto requiere que uses database, memcached, dynamodb o redis como driver de caché.

Ejecutar en segundo plano

Para tareas pesadas que no deben bloquear el scheduler:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:database')->daily()->runInBackground();

Modo mantenimiento

Por defecto, las tareas no se ejecutan cuando la aplicación está en modo mantenimiento. Para forzar la ejecución:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('health:check')->everyMinute()->evenInMaintenanceMode();

Salida y monitorización

Guardar salida en archivos

<?php

use Illuminate\Support\Facades\Schedule;

// Sobrescribir el archivo
Schedule::command('sync:run')->daily()->sendOutputTo('/var/log/sync.log');

// Añadir al archivo
Schedule::command('sync:run')->daily()->appendOutputTo('/var/log/sync.log');

Notificaciones por email

<?php

use Illuminate\Support\Facades\Schedule;

// Enviar siempre la salida
Schedule::command('reports:generate')
    ->daily()
    ->emailOutputTo('[email protected]');

// Enviar solo si falla
Schedule::command('reports:generate')
    ->daily()
    ->emailOutputOnFailure('[email protected]');

Requiere configurar el sistema de correo de Laravel.

Hooks: antes y después

Los hooks permiten ejecutar código adicional alrededor de tus tareas.

<?php

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;

Schedule::command('import:products')
    ->daily()
    ->before(function () {
        Log::info('Iniciando importación de productos');
    })
    ->after(function () {
        Log::info('Importación de productos completada');
    });

Hooks según resultado

<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;

Schedule::command('critical:task')
    ->hourly()
    ->onSuccess(function () {
        Http::post('https://monitoring.example.com/webhook', [
            'status' => 'success',
            'task' => 'critical:task',
        ]);
    })
    ->onFailure(function () {
        Log::critical('La tarea crítica ha fallado');
    });

Webhooks integrados

Laravel incluye métodos para notificar a URLs directamente:

<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run')
    ->daily()
    ->pingBefore('https://monitoring.example.com/start')
    ->thenPing('https://monitoring.example.com/end')
    ->pingOnSuccess('https://monitoring.example.com/success')
    ->pingOnFailure('https://monitoring.example.com/failure');

Desarrollo local

Ejecutar el scheduler manualmente

Durante el desarrollo, puedes ejecutar el scheduler una vez:

php artisan schedule:run

Simular la ejecución continua

Para simular el comportamiento del cron en local:

php artisan schedule:work

Este comando ejecuta el scheduler cada minuto hasta que lo detengas con Ctrl+C.

Listar tareas programadas

Para ver todas las tareas configuradas:

php artisan schedule:list

Esto muestra cada tarea con su próxima ejecución programada.

Probar una tarea específica

php artisan schedule:test

Este comando interactivo te permite seleccionar y ejecutar una tarea específica.

Ejemplo práctico completo

Un archivo routes/console.php típico en producción podría verse así:

<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
use App\Jobs\ProcessAnalytics;

// Mantenimiento básico
Schedule::command('model:prune')->daily()->at('01:00');
Schedule::command('queue:prune-batches')->daily()->at('01:30');
Schedule::command('cache:prune-stale-tags')->hourly();

// Backups - solo en producción, en un solo servidor
Schedule::command('backup:run')
    ->dailyAt('02:00')
    ->environments(['production'])
    ->onOneServer()
    ->withoutOverlapping()
    ->pingOnSuccess(config('services.monitoring.backup_url'));

// Sincronización externa - horario laboral
Schedule::command('sync:crm')
    ->everyFifteenMinutes()
    ->between('08:00', '20:00')
    ->weekdays()
    ->withoutOverlapping(10)
    ->onFailure(fn () => Log::error('Fallo en sincronización CRM'));

// Reportes diarios
Schedule::command('reports:daily')
    ->dailyAt('07:00')
    ->runInBackground()
    ->emailOutputOnFailure('[email protected]');

// Health check
Schedule::call(fn () => Http::timeout(5)->get(config('app.url') . '/health'))
    ->everyMinute()
    ->evenInMaintenanceMode()
    ->onFailure(fn () => Log::critical('Health check fallido'));

// Job programado
Schedule::job(new ProcessAnalytics)
    ->hourly()
    ->environments(['production']);

Conclusión

El Task Scheduler de Laravel elimina la complejidad de gestionar crontabs en el servidor. Con una única entrada cron y código PHP expresivo, puedes controlar completamente cuándo y cómo se ejecutan tus tareas automatizadas. Las opciones de control como withoutOverlapping() y onOneServer() son especialmente valiosas en producción, donde la robustez es prioritaria.

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje