> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Programación de tareas

> Aprende a gestionar de forma elegante las tareas periódicas con el scheduler de Laravel.

## Qué es la programación de tareas

Tradicionalmente, cada tarea periódica requería añadir manualmente una entrada al cron del servidor.
El problema es que la definición vivía fuera del código fuente, sin control de versiones y con la necesidad de acceder por SSH para revisarla o modificarla.

Con el scheduler de Laravel **defines la programación de tareas dentro de la propia aplicación** con una API fluida.
Solo necesitas una entrada de cron en el servidor, y las tareas quedan versionadas junto al código.

Lo habitual es definir la programación en `routes/console.php`.

```php theme={null}
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

<Info>
  Con el comando Artisan `schedule:list` puedes ver todas las tareas definidas y su próxima ejecución.

  ```shell theme={null}
  php artisan schedule:list
  ```
</Info>

### Flujo de ejecución del scheduler

```mermaid theme={null}
flowchart TD
    A["* * * * * artisan schedule:run<br>(cron lo llama cada minuto)"] --> B["Console Kernel"]
    B --> C["Se invoca schedule()"]
    C --> D["Se evalúan las tareas registradas"]
    D --> E{"¿La expresión cron<br>coincide con la hora actual?"}
    E -->|"No coincide"| F["Salta"]
    E -->|"Coincide"| G{"Comprobar when / skip /<br>environments"}
    G -->|"No cumple"| F
    G -->|"Cumple"| H{"¿La aplicación<br>está en mantenimiento?"}
    H -->|"Modo normal"| I["Ejecuta la tarea<br>(before → cuerpo → after)"]
    H -->|"Mantenimiento"| J{"¿Se ha declarado<br>evenInMaintenanceMode()?"}
    J -->|"Sí"| I
    J -->|"No"| F
```

## Cómo definir la programación

La programación se define en `routes/console.php`.
También puedes definirla en el método `withSchedule` de `bootstrap/app.php`.

```php theme={null}
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})
```

## Tipos de tareas programables

### Comandos Artisan

Con `command` programas un comando Artisan.
Puedes pasar el nombre o la clase del comando.

```php theme={null}
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;

// Por nombre
Schedule::command('emails:send Taylor --force')->daily();

// Por clase (con argumentos en un array)
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
```

Los comandos Artisan definidos con closure también admiten el encadenamiento.

```php theme={null}
Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
```

### Jobs de cola

Con `job` programas un [job de cola](/es/queues). Es una forma cómoda de encolarlo sin closures.

```php theme={null}
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
```

También puedes indicar la cola y la conexión.

```php theme={null}
// Cola "heartbeats" y conexión "sqs"
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
```

### Comandos de shell

Con `exec` ejecutas un comando del sistema operativo.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

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

### Closures

Con `call` programas cualquier closure PHP.

```php theme={null}
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

También aceptas objetos invocables (con `__invoke`).

```php theme={null}
Schedule::call(new DeleteRecentUsers)->daily();
```

## Configurar la frecuencia

### Métodos más habituales

| Método                     | Descripción                                 |
| -------------------------- | ------------------------------------------- |
| `->everySecond()`          | Cada segundo                                |
| `->everyMinute()`          | Cada minuto                                 |
| `->everyFiveMinutes()`     | Cada 5 minutos                              |
| `->everyFifteenMinutes()`  | Cada 15 minutos                             |
| `->everyThirtyMinutes()`   | Cada 30 minutos                             |
| `->hourly()`               | Cada hora                                   |
| `->hourlyAt(17)`           | Cada hora al minuto 17                      |
| `->daily()`                | Cada día a las 00:00                        |
| `->dailyAt('13:00')`       | Cada día a las 13:00                        |
| `->twiceDaily(1, 13)`      | Cada día a la 1 y a las 13                  |
| `->weekly()`               | Cada domingo a las 00:00                    |
| `->weeklyOn(1, '8:00')`    | Cada lunes a las 8:00                       |
| `->monthly()`              | El día 1 de cada mes a las 00:00            |
| `->monthlyOn(4, '15:00')`  | El día 4 a las 15:00                        |
| `->quarterly()`            | El primer día de cada trimestre a las 00:00 |
| `->yearly()`               | Cada 1 de enero a las 00:00                 |
| `->timezone('Asia/Tokyo')` | Establece la zona horaria                   |

### Expresiones cron directamente

Con `cron` puedes pasar la expresión directamente.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->cron('0 9 * * *'); // Cada día a las 9:00
```

### Combinar frecuencia y días

Combinando frecuencia y restricciones por día obtienes programaciones más finas.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// Cada lunes a las 13:00
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// De lunes a viernes, cada hora entre las 8 y las 17
Schedule::command('foo')
    ->weekdays()
    ->hourly()
    ->timezone('Asia/Tokyo')
    ->between('8:00', '17:00');
```

### Configurar la zona horaria

Con `timezone` indicas la zona horaria de una tarea concreta.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('Asia/Tokyo')
    ->at('9:00');
```

Para una zona horaria común a todas las tareas, define `schedule_timezone` en `config/app.php`.

```php theme={null}
// config/app.php
'schedule_timezone' => 'Europe/Madrid',
```

<Warning>
  Al usar zonas horarias con horario de verano, en el cambio de hora las tareas pueden ejecutarse dos veces o no ejecutarse.
  Se recomienda usar UTC siempre que sea posible.
</Warning>

## Restricciones condicionales

### when / skip

`when` ejecuta la tarea solo si la closure devuelve `true`.
`skip` hace lo contrario: la salta si devuelve `true`.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// Ejecuta solo si es true
Schedule::command('emails:send')->daily()->when(function () {
    return true;
});

// Salta si es true
Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});
```

### environments

Con `environments` restringes a determinados entornos.

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);
```

### Restricciones horarias

Con `between` / `unlessBetween` limitas el rango horario.

```php theme={null}
// Solo entre las 7 y las 22
Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');

// No ejecutar entre las 23 y las 4
Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');
```

### Restricciones por día

| Método                        | Descripción                                 |
| ----------------------------- | ------------------------------------------- |
| `->weekdays()`                | Solo días laborables                        |
| `->weekends()`                | Solo fines de semana                        |
| `->mondays()` … `->sundays()` | Un día concreto                             |
| `->days([0, 3])`              | Varios días (0 = domingo, 3 = miércoles...) |

```php theme={null}
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
```

## Evitar solapamientos

Por defecto, aunque la ejecución anterior no haya terminado, se inicia la siguiente.
Con `withoutOverlapping` la nueva ejecución espera a que termine la anterior.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();
```

Puedes indicar la caducidad del lock en minutos (por defecto 24 horas).

```php theme={null}
// El lock expira a los 10 minutos
Schedule::command('emails:send')->withoutOverlapping(10);
```

<Info>
  `withoutOverlapping` gestiona el lock en la caché de la aplicación.
  Si una tarea queda atascada, puedes liberarlo con `schedule:clear-cache`.

  ```shell theme={null}
  php artisan schedule:clear-cache
  ```
</Info>

## Ejecución en varios servidores

Cuando el scheduler se ejecuta en varios servidores, con `onOneServer` te aseguras de que solo uno ejecute la tarea.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();
```

<Warning>
  Para usar esta funcionalidad, la caché por defecto debe ser `database`, `memcached`, `dynamodb` o `redis`, y todos los servidores deben apuntar al mismo backend.
</Warning>

### Flujo de control distribuido con `onOneServer()`

```mermaid theme={null}
flowchart TD
    A["Varios servidores<br>ejecutan schedule:run a la vez"] --> B["Servidor 1"]
    A --> C["Servidor 2"]
    A --> D["Servidor 3"]

    B --> E["Intento de obtener un lock atómico<br>en la caché compartida"]
    C --> E
    D --> E

    E -->|"Obtenido (el primero)"| F["Ejecuta la tarea"]
    E -->|"No obtenido (resto)"| G["Salta la tarea"]

    F --> H["Al terminar libera el lock"]
```

## Agrupar tareas

Con `group` aplicas la misma configuración a varias tareas.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('Europe/Madrid')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });
```

## Ejecución en segundo plano

Por defecto, las tareas programadas al mismo tiempo se ejecutan en el orden en el que se definen.
Si una es lenta, retrasa a las siguientes. Con `runInBackground` se ejecutan en paralelo.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
```

<Warning>
  `runInBackground` solo se puede usar con `command` y `exec`.
</Warning>

## Modo mantenimiento

En modo mantenimiento no se ejecutan las tareas programadas.
Para forzar la ejecución incluso en mantenimiento, usa `evenInMaintenanceMode`.

```php theme={null}
Schedule::command('emails:send')->evenInMaintenanceMode();
```

### Flujo de decisión en modo mantenimiento

```mermaid theme={null}
flowchart TD
    A["artisan schedule:run"] --> B{"¿La aplicación<br>está en mantenimiento?<br>(artisan down)"}
    B -->|"Modo normal"| C["Ejecuta la tarea"]
    B -->|"Mantenimiento"| D{"¿La tarea define<br>evenInMaintenanceMode()?"}
    D -->|"Sí"| C
    D -->|"No"| E["Salta la tarea"]
```

## Pausar el scheduler

Puedes pausarlo sin modificar código.

```shell theme={null}
# Pausa
php artisan schedule:pause

# Reanudar
php artisan schedule:continue
```

Si quieres que ciertas tareas se sigan ejecutando durante la pausa, márcalas con `evenWhenPaused`. Útil para health checks o monitorización.

```php theme={null}
Schedule::command('emails:send')->evenWhenPaused();
```

## Manejo de la salida

### Enviar la salida a un archivo

Con `sendOutputTo` guardas la salida.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo(storage_path('logs/emails-send.log'));
```

Con `appendOutputTo` la añades al final del archivo existente.

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/emails-send.log'));
```

### Enviar la salida por correo

Con `emailOutputTo` la salida se envía por correo (necesitas la [configuración de correo](/es/mail) de Laravel).

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('admin@example.com');
```

Para enviar solo cuando falla, usa `emailOutputOnFailure`.

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('admin@example.com');
```

## Hooks de tarea

Con `before` / `after` ejecutas lógica antes y después de la tarea.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // Antes de ejecutar
    })
    ->after(function () {
        // Después de ejecutar
    });
```

Con `onSuccess` / `onFailure` reaccionas al resultado.

```php theme={null}
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // En caso de éxito
    })
    ->onFailure(function (Stringable $output) {
        // En caso de fallo
    });
```

## Despliegue en servidor

<Steps>
  <Step title="Añadir la entrada de cron">
    Con esta línea en el crontab del servidor, el scheduler se ejecutará cada minuto.

    ```shell theme={null}
    * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    ```

    Ábrelo con `crontab -e`.
  </Step>

  <Step title="Comprobar el scheduler">
    Muestra las tareas definidas y su próxima ejecución.

    ```shell theme={null}
    php artisan schedule:list
    ```
  </Step>
</Steps>

<Tip>
  Con [Laravel Cloud](https://cloud.laravel.com) puedes gestionar tareas programadas sin configurar cron.
</Tip>

### Ejecución en desarrollo local

En local, en vez de usar cron, puedes mantener el scheduler activo con `schedule:work`.

```shell theme={null}
php artisan schedule:work
```

Se ejecuta en primer plano e invoca al scheduler cada minuto. Se detiene con `Ctrl+C`.

### Tareas sub-minuto (con periodicidad inferior a 1 minuto)

Cron usa un minuto como unidad mínima; en Laravel puedes programar tareas por segundos.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
```

Si hay tareas sub-minuto, `schedule:run` sigue corriendo durante el minuto en curso para procesarlas todas.

<Tip>
  En tareas sub-minuto conviene delegar el trabajo a jobs de cola o comandos en segundo plano.
  Si la tarea es lenta, retrasa a las siguientes tareas sub-minuto.
</Tip>

Para interrumpir un `schedule:run` en curso durante un despliegue, añade esto al script:

```shell theme={null}
php artisan schedule:interrupt
```

## Comandos habituales

```shell theme={null}
# Listado de tareas
php artisan schedule:list

# Ejecución manual (lo llama cron)
php artisan schedule:run

# Modo desarrollo (siempre activo)
php artisan schedule:work

# Pausar
php artisan schedule:pause

# Reanudar
php artisan schedule:continue

# Limpiar locks de solapamiento
php artisan schedule:clear-cache

# Interrumpir tareas sub-minuto (al desplegar)
php artisan schedule:interrupt
```


## Related topics

- [Consola Artisan](/es/artisan.md)
- [Tutorial - Laravel Console Starter](/es/packages/laravel-console-starter/tutorial.md)
- [Tutorial de bots - Laravel Bluesky](/es/packages/laravel-bluesky/bot-tutorial.md)
- [Introducción a Laravel Nightwatch](/es/blog/nightwatch-introduction.md)
- [Clase Lottery](/es/advanced/lottery.md)
