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

# Colas y jobs

> Aprende a ejecutar tareas pesadas (envío de correos, procesamiento de imágenes...) de forma asíncrona con las colas (Queues) y los jobs (Jobs) de Laravel.

## Qué es una cola

En una aplicación web hay operaciones que tardan varios segundos: enviar correos, redimensionar imágenes, llamar a APIs externas...
Si las haces de forma síncrona durante la petición HTTP, el usuario tiene que esperar a que terminen para recibir la respuesta.

Con las colas de Laravel puedes ejecutar esas operaciones **en segundo plano de forma asíncrona**.
La petición responde inmediatamente y el trabajo real lo lleva a cabo un proceso worker aparte.

<Info>
  Las colas admiten varios backends (base de datos, Redis, Amazon SQS, etc.).
  En desarrollo puedes usar el driver `sync` para ejecutar los jobs al instante, sin cola.
</Info>

```mermaid theme={null}
flowchart LR
    A["Crear job<br>dispatch()"] --> B["Driver de la cola<br>(Redis/BD, etc.)"]
    B --> C["Worker<br>queue:work"]
    C --> D{"¿Éxito?"}
    D -->|"Sí"| E["Completado"]
    D -->|"No"| F{"¿Reintentable?"}
    F -->|"Sí"| B
    F -->|"No"| G["Registrado como fallido<br>failed_jobs"]
```

## Configuración de la cola

### config/queue.php

Toda la configuración se centraliza en `config/queue.php`.
La variable `QUEUE_CONNECTION` selecciona el driver.

```php theme={null}
// config/queue.php
'default' => env('QUEUE_CONNECTION', 'database'),
```

### Configuración en `.env`

```ini theme={null}
# Selección del driver
QUEUE_CONNECTION=database

# Ejemplo con Redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379
```

### Preparar el driver de base de datos

Si usas `database`, necesitas una tabla para almacenar los jobs.
Los proyectos nuevos con Laravel 11+ incluyen la migración; si no, créala con:

```shell theme={null}
php artisan make:queue-table
php artisan migrate
```

### Preparar el driver de Redis

Añade la configuración de Redis en `config/database.php` e instala el driver con Composer.

```shell theme={null}
composer require predis/predis
```

### Overflow storage de SQS

Amazon SQS limita el tamaño de los mensajes. Si tus jobs generan payloads grandes, puedes configurar el excedente para guardarlo en la caché y enviar solo un puntero a SQS.

```php theme={null}
'sqs' => [
    // ...
    'overflow' => [
        'enabled' => env('SQS_OVERFLOW_ENABLED', false),
        'store' => env('SQS_OVERFLOW_STORE'),
        'always' => false,
        'delete_after_processing' => true,
        'flush_on_clear' => env('SQS_OVERFLOW_FLUSH_ON_CLEAR', false),
    ],
],
```

* Con `enabled`, los payloads de **más de 1 MB** se guardan en el almacén de caché indicado.
* Con `always: true`, se guardan siempre, sea cual sea su tamaño.
* `delete_after_processing` elimina el payload al finalizar con éxito (por defecto `true`).
* `flush_on_clear: true` vacía el store de overflow al ejecutar `queue:clear`. Como no queremos borrar el resto de la caché, úsalo con un store dedicado.

## Crear la clase de job

### Comando `make:job`

Genera el esqueleto con `make:job`.

```shell theme={null}
php artisan make:job SendWelcomeEmail
```

Se crea `app/Jobs/SendWelcomeEmail.php`.

### Estructura de la clase

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

namespace App\Jobs;

use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    /**
     * Crea una nueva instancia del job
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Ejecuta el job
     */
    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
}
```

Al implementar `ShouldQueue`, indicamos a Laravel que este job debe procesarse en cola.
El trait `Queueable` aporta los métodos necesarios para operar sobre la cola.

<Tip>
  Cuando pasas un modelo Eloquent al constructor, Laravel serializa solamente el ID.
  Al ejecutarse el job se recupera el modelo actualizado de la base de datos, con lo que la payload de la cola queda ligera.
</Tip>

## Despachar un job

### `dispatch()`

Desde un controlador o servicio, envía el job a la cola con `dispatch()`.

```php theme={null}
use App\Jobs\SendWelcomeEmail;

// En una ruta o controlador
public function register(Request $request): RedirectResponse
{
    $user = User::create($request->validated());

    // Encolar el job
    SendWelcomeEmail::dispatch($user);

    return redirect('/dashboard');
}
```

### Despacho retrasado

Con `delay()` retrasas la ejecución.

```php theme={null}
// Se ejecuta 5 minutos después
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
```

### `dispatchAfterResponse()`

Ejecuta el job **inmediatamente después** de devolver la respuesta HTTP al usuario.
Funciona incluso con el driver `sync`, así que es útil para casos ligeros que no necesitan worker.

```php theme={null}
SendWelcomeEmail::dispatchAfterResponse($user);
```

### Despachar a una cola concreta

```php theme={null}
SendWelcomeEmail::dispatch($user)->onQueue('emails');
```

### Queue Routing

Para dirigir por defecto un job a una conexión o cola concreta, usa `Queue::route()` en el `boot()` de un service provider. Así centralizas la configuración en vez de repetir `onQueue()` / `onConnection()` en cada job.

```php theme={null}
use App\Concerns\RequiresVideo;
use App\Jobs\ProcessPodcast;
use App\Jobs\ProcessVideo;
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
    Queue::route(RequiresVideo::class, queue: 'video');
}
```

También puedes usar interfaces, traits o clases padre; se aplica a todos los jobs que las implementen, usen o hereden.

Para agrupar varios jobs, pasa un array.

```php theme={null}
Queue::route([
    ProcessPodcast::class => ['podcasts', 'redis'], // queue y connection
    ProcessVideo::class => 'videos',                // solo queue (usa la conexión por defecto)
]);
```

<Info>
  El propio job puede sobreescribir el Queue Routing mediante `onQueue()` u `onConnection()`.
</Info>

### Ejecución síncrona (para tests y desarrollo)

Con `dispatchSync()` el job se ejecuta al momento, sin pasar por la cola.

```php theme={null}
SendWelcomeEmail::dispatchSync($user);
```

### Despacho masivo (bulk)

Cuando necesitas despachar muchos jobs independientes, puedes usar `Bus::bulk()`. Es ideal si no necesitas seguimiento ni callbacks (como un batch).

`Bus::bulk()` agrupa los jobs por conexión y cola configuradas y los empuja de golpe a la cola, con más eficiencia.

```php theme={null}
use App\Jobs\ProcessUser;
use Illuminate\Support\Facades\Bus;

Bus::bulk(
    $users->map(fn ($user) => new ProcessUser($user))
);
```

<Info>
  `Bus::bulk()` envía los jobs **agrupados por lotes** a la cola. A diferencia del batching (`Bus::batch()`), no ofrece progreso ni callbacks al terminar. Es la opción sencilla para enviar muchos jobs independientes de una tacada.
</Info>

## Procesar los jobs

### Comando `queue:work`

Arranca un worker para procesar los jobs.

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

Puedes indicar el driver o la cola concreta.

```shell theme={null}
# Solo la cola "emails" de Redis
php artisan queue:work redis --queue=emails

# Usa el driver database
php artisan queue:work database
```

<Warning>
  `queue:work` se mantiene en ejecución continua. Cuando cambies el código, usa `queue:restart` para reiniciarlo.
  En producción se gestiona típicamente con Supervisor u otro process manager.
</Warning>

## Opciones para monitorizar el worker

Puedes ajustar el comportamiento del worker con estas opciones.

```shell theme={null}
php artisan queue:work --tries=3 --timeout=60 --sleep=3
```

| Opción         | Descripción                                                      |
| -------------- | ---------------------------------------------------------------- |
| `--tries=N`    | Máximo de reintentos; al superarlo se marca como fallido         |
| `--timeout=N`  | Máximo de segundos por job; si se supera, se aborta el worker    |
| `--sleep=N`    | Segundos entre sondeos cuando la cola está vacía (por defecto 3) |
| `--max-jobs=N` | Termina tras procesar N jobs                                     |
| `--max-time=N` | Termina tras N segundos                                          |
| `--queue=A,B`  | Procesa colas con prioridad (A antes que B)                      |

### Configuración de reintentos en la propia clase

En muchos casos es más práctico definir la configuración en el propio job que como opciones de línea de comandos.

```php theme={null}
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(60)]
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    // ...
}
```

## Liberar un job (middleware `Release`)

Si bajo cierta condición quieres devolver el job a la cola sin ejecutarlo, utiliza el middleware `Release`.

```php theme={null}
use Illuminate\Queue\Middleware\Release;

/**
 * Devuelve los middleware por los que pasa el job
 */
public function middleware(): array
{
    return [
        // Si $condition es true, se libera y se reintenta a los 60 s
        Release::when($this->order->isPending(), releaseAfter: 60),
    ];
}
```

`Release::unless()` libera cuando la condición es `false`.

```php theme={null}
return [
    // Libera a los 60 s si el pedido no está pagado
    Release::unless($this->order->isPaid(), releaseAfter: 60),
];
```

También puedes usar una closure para condiciones más complejas.

```php theme={null}
return [
    Release::when(function (): bool {
        return ! $this->order->isPaid();
    }, releaseAfter: 60),
];
```

<Warning>
  Al liberar un job se incrementa igualmente el contador de intentos. Ajusta bien `#[Tries]` o la propiedad `$tries`.
</Warning>

## Jobs fallidos

### Preparar la tabla `failed_jobs`

Cuando se supera el número de reintentos, los jobs se guardan en `failed_jobs`.
Si no tienes la tabla, créala:

```shell theme={null}
php artisan make:queue-failed-table
php artisan migrate
```

### Limpieza al fallar

Definiendo `failed()` en el job, puedes ejecutar código cuando falle definitivamente.

```php theme={null}
use Throwable;

public function failed(?Throwable $exception): void
{
    // Notificar al administrador por Slack, por ejemplo
    // Notification::route('slack', config('app.slack_webhook'))
    //     ->notify(new JobFailedNotification($this, $exception));
}
```

### Detener reintentos por tipo de excepción

Para excepciones que no se resolverán reintentando, indícalas en el `withExceptions()` de `bootstrap/app.php` con `dontRetry`.

```php theme={null}
use App\Exceptions\InvalidPodcastSourceException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetry([
        InvalidPodcastSourceException::class,
    ]);
})
```

Para un control más fino, `dontRetryWhen` recibe una closure. Si devuelve `true`, el job se marca como fallido de inmediato.

```php theme={null}
use App\Exceptions\PodcastProcessingException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetryWhen(function (PodcastProcessingException $e) {
        return $e->reason() === 'Subscription expired';
    });
})
```

<Tip>
  Con excepciones cuyo resultado no cambia al reintentar (errores de validación, fallos de pago por suscripción caducada...), esta técnica evita reintentos inútiles.
</Tip>

### Listar jobs fallidos

```shell theme={null}
php artisan queue:failed
```

### Reintentar jobs fallidos

```shell theme={null}
# Un job concreto por UUID
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Todos los fallidos
php artisan queue:retry all
```

### Eliminar jobs fallidos

```shell theme={null}
# Uno concreto
php artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Todos
php artisan queue:flush
```

## Drivers de cola habituales

### Driver `database`

Es el más sencillo, se usa sin middleware adicional.
Los jobs se guardan en la tabla `jobs` y el worker hace polling.

* **Ventaja**: fácil de configurar, aprovecha la BD existente.
* **Desventaja**: carga la base de datos; no es ideal para volúmenes muy altos.

```ini theme={null}
QUEUE_CONNECTION=database
```

### Driver `redis`

El más habitual en producción por su velocidad.
Al trabajar en memoria supera con creces la BD en throughput.

* **Ventajas**: rapidez, escalabilidad.
* **Desventaja**: requiere un servidor Redis.

```ini theme={null}
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
```

<Tip>
  En producción con Redis considera adoptar [Laravel Horizon](https://laravel.com/docs/horizon).
  Ofrece un panel bonito y en tiempo real del estado de los jobs.
</Tip>

## Operación en producción con Supervisor

En producción es imprescindible reiniciar `queue:work` automáticamente si cae. En Linux, lo habitual es usar **Supervisor**.

```ini theme={null}
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600
```

`numprocs=2` arranca 2 workers en paralelo.
Recarga Supervisor tras editar la configuración.

```shell theme={null}
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
```

## Ejemplo práctico: enviar correos desde la cola

<Steps>
  <Step title="Crear la clase de job">
    ```shell theme={null}
    php artisan make:job SendOrderConfirmation
    ```
  </Step>

  <Step title="Implementar la lógica">
    ```php theme={null}
    <?php

    namespace App\Jobs;

    use App\Models\Order;
    use App\Mail\OrderConfirmed;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Queue\Queueable;
    use Illuminate\Queue\Attributes\Tries;
    use Illuminate\Queue\Attributes\Timeout;
    use Illuminate\Support\Facades\Mail;

    #[Tries(3)]
    #[Timeout(30)]
    class SendOrderConfirmation implements ShouldQueue
    {
        use Queueable;

        public function __construct(
            public Order $order,
        ) {}

        public function handle(): void
        {
            Mail::to($this->order->user->email)
                ->send(new OrderConfirmed($this->order));
        }
    }
    ```
  </Step>

  <Step title="Despachar desde el controlador">
    ```php theme={null}
    use App\Jobs\SendOrderConfirmation;

    public function store(Request $request): RedirectResponse
    {
        $order = Order::create($request->validated());

        SendOrderConfirmation::dispatch($order);

        return redirect()->route('orders.show', $order)
            ->with('success', 'Pedido registrado.');
    }
    ```
  </Step>

  <Step title="Arrancar el worker">
    ```shell theme={null}
    php artisan queue:work --tries=3 --timeout=30
    ```
  </Step>
</Steps>

## Resumen

<AccordionGroup>
  <Accordion title="Cuándo usar colas">
    * Envío de correos y SMS.
    * Redimensionado o conversión de imágenes y vídeos.
    * Peticiones a APIs externas.
    * Generación de informes y exportaciones CSV.
    * Envío de webhooks.
  </Accordion>

  <Accordion title="Consejos para desarrollo">
    Poniendo `QUEUE_CONNECTION=sync` en `.env`, los jobs se ejecutan al instante sin cola.
    Sin necesidad de arrancar un worker, es muy cómodo durante el desarrollo.

    ```ini theme={null}
    QUEUE_CONNECTION=sync
    ```
  </Accordion>

  <Accordion title="Comandos habituales">
    ```shell theme={null}
    # Iniciar el worker
    php artisan queue:work

    # Reiniciar el worker (tras un deploy)
    php artisan queue:restart

    # Listar jobs fallidos
    php artisan queue:failed

    # Reintentar todos
    php artisan queue:retry all

    # Eliminar todos
    php artisan queue:flush
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [Programación de tareas](/es/scheduling.md)
- [Context (contexto)](/es/context.md)
- [Técnicas prácticas de Laravel Telescope](/es/blog/telescope-introduction.md)
- [Actualización de Laravel — Abril de 2026](/es/blog/changelog/202604.md)
- [Resumen de las novedades de Laravel 13](/es/blog/laravel-13-new-features.md)
