
# Jobs

Las tareas son la unidad básica de trabajo en Queuety. Envíe una tarea con el nombre de un controlador y una carga útil; un worker la recoge y la ejecuta en segundo plano.

## Envío de tareas

```php
use Queuety\Queuety;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] );
```

`dispatch()` devuelve un generador `PendingJob`. Puede encadenar opciones antes de que se envíe automáticamente:

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High )
    ->delay( 300 )
    ->max_attempts( 5 );
```

Para obtener el ID de la tarea de inmediato, llame a `->id()`:

```php
$job_id = Queuety::dispatch( 'send_email', $payload )->id();
```

## Escritura de controladores

Un controlador puede ser cualquier clase que implemente la interfaz `Queuety\Handler`:

```php
use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
            'backoff'         => 'exponential',
        ];
    }
}
```

El método `handle()` recibe la carga útil de la tarea y ejecuta el trabajo. El método `config()` devuelve valores predeterminados opcionales:

| Clave | Tipo | Descripción |
|---|---|---|
| `queue` | `string` | Nombre predeterminado de la cola |
| `max_attempts` | `int` | Número máximo de ejecuciones, incluida la primera, antes de que se archive la tarea |
| `backoff` | `string` | Estrategia de espera entre reintentos: `exponential`, `linear` o `fixed` |
| `rate_limit` | `array` | Límite de frecuencia como `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | Grupo compartido de admisión de workers para controladores costosos |
| `concurrency_limit` | `int` | Máximo de tareas en procesamiento simultáneo dentro del grupo |
| `cost_units` | `int` | Coste de ejecución relativo que se usa en colas ponderadas, presupuestos de proveedores y flujos de trabajo, y heurísticas de admisión |

Estas claves de recursos forman parte del modelo más amplio de [Resource Management](https://queuety.inline0.dev/es/docs/features/resource-management) de Queuety, donde los metadatos de las tareas, los presupuestos de flujos de trabajo y la admisión de workers funcionan en conjunto.

Registre el controlador:

```php
Queuety::register( 'send_email', SendEmailHandler::class );
```

O use los atributos [PHP](https://queuety.inline0.dev/es/docs/features/attributes) para el registro automático.

## Prioridad

Los trabajos tienen cuatro niveles de prioridad. Los trabajadores reclaman primero los trabajos de mayor prioridad.

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'handler', $payload )->with_priority( Priority::Urgent );
```

| Nivel | Valor | Descripción |
|---|---|---|
| `Priority::Low` | `0` | Predeterminada. Trabajo en segundo plano que puede esperar. |
| `Priority::Normal` | `1` | Prioridad estándar. |
| `Priority::High` | `2` | Se procesa antes que los de prioridad baja y normal. |
| `Priority::Urgent` | `3` | Se procesa primero. Úsela con moderación. |

## Retraso

Retrase un trabajo para que esté disponible solo después del número de segundos especificado:

```php
Queuety::dispatch( 'send_reminder', $payload )->delay( 3600 ); // 1 hour
```

El trabajo se inserta de inmediato, pero los trabajadores no lo reclamarán hasta que haya transcurrido el retraso.

## Trabajos únicos

Evite los envíos duplicados para el mismo controlador y la misma carga útil. Si ya existe un trabajo pendiente o en curso con el mismo controlador y la misma carga útil, se devuelve el ID del trabajo existente en lugar de crear uno nuevo.

```php
Queuety::dispatch( 'sync_inventory', [ 'sku' => 'ABC-123' ] )->unique();
```

## Dependencias entre trabajos

Un trabajo puede depender de otro. Un trabajador no lo reclamará hasta que se haya completado la dependencia.

```php
$first_id = Queuety::dispatch( 'import_data', $payload )->id();

Queuety::dispatch( 'send_notification', [ 'import_id' => $first_id ] )
    ->after( $first_id );
```

## Envío por lotes

Envíe varios trabajos en una sola instrucción INSERT de varias filas para obtener el máximo rendimiento:

```php
$job_ids = Queuety::batch( [
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'a@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'b@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'c@example.com' ] ],
] );
```

Cada elemento puede incluir claves opcionales: `queue`, `priority`, `delay`, `max_attempts`.

## Reintentos y espera progresiva

Cuando un trabajo lanza una excepción, Queuety lo devuelve a `pending` con un `available_at` retrasado hasta que se haya ejecutado `max_attempts` veces en total (cuenta también la primera ejecución, por lo que `max_attempts( 3 )` permite hasta dos reintentos). La estrategia de espera progresiva determina el retraso antes de cada reintento, donde `attempt` es el número de la ejecución que acaba de fallar:

| Estrategia | Fórmula del retraso | Ejemplo (tras las ejecuciones 1, 2 y 3) |
|---|---|---|
| `exponential` | `2^attempt * 30` segundos, con un máximo de 3600 | 60s, 120s, 240s |
| `linear` | `attempt * 60` segundos | 60s, 120s, 180s |
| `fixed` | `60` segundos | 60s, 60s, 60s |

Establezca el valor predeterminado global en `wp-config.php`:

```php
define( 'QUEUETY_RETRY_BACKOFF', 'linear' );
```

O por controlador en `config()`:

```php
public function config(): array {
    return [ 'backoff' => 'exponential', 'max_attempts' => 5 ];
}
```

O por envío:

```php
Queuety::dispatch( 'handler', $payload )->max_attempts( 10 );
```

## Trabajos en la cola de fallidos

Tras agotar todos los intentos de reintento, un trabajo se marca como **enterrado**. Los trabajos enterrados permanecen en la base de datos para su inspección, pero no se procesan. Puede reintentarlos manualmente. Un reintento manual vuelve a poner el trabajo en `pending` sin restablecer el recuento de intentos; así, un trabajo enterrado que se reintenta se ejecuta una vez más y vuelve a quedar enterrado si esa ejecución falla:

```php
// Retry a specific buried job
Queuety::retry( $job_id );

// Retry all buried jobs
$count = Queuety::retry_buried();
```

O mediante CLI:

```bash
wp queuety retry 42
wp queuety retry-buried
```

## Tiempo de espera

Queuety activa una `pcntl_alarm()` para el tiempo máximo de ejecución y está pensado para hacer fallar el intento con un `TimeoutException` cuando se supera ese límite. Actualmente, ese tiempo de espera no se aplica ([#10](https://github.com/inline0/queuety/issues/10)): el trabajador no habilita las señales asíncronas, por lo que un controlador de larga duración se ejecuta hasta el final, mientras que un controlador bloqueado en `sleep()` se interrumpe cuando suena la alarma y luego se marca como completado. Configure el tiempo de espera globalmente:

```php
define( 'QUEUETY_MAX_EXECUTION_TIME', 300 ); // 5 minutes
```

Los trabajos que permanecen en estado `processing` durante más de `QUEUETY_STALE_TIMEOUT` segundos se recuperan automáticamente:

```php
define( 'QUEUETY_STALE_TIMEOUT', 600 ); // 10 minutes
```

Recupere manualmente los trabajos obsoletos:

```bash
wp queuety recover
```

## Colas

Los trabajos se envían a colas con nombre. Cada trabajador procesa una cola o una lista ordenada de colas (consulte [Trabajadores](https://queuety.inline0.dev/es/docs/features/workers#multi-queue-priorities)):

```php
Queuety::dispatch( 'handler', $payload )->on_queue( 'emails' );
```

```bash
wp queuety work --queue=emails
```

La cola predeterminada es `default`. Puede ejecutar procesos de trabajador independientes para cada cola y así aislar las cargas de trabajo.

## Limitación de frecuencia

Limite la frecuencia con la que puede ejecutarse un controlador dentro de un intervalo de tiempo:

```php
Queuety::dispatch( 'call_api', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

Consulte [Limitación de frecuencia](https://queuety.inline0.dev/es/docs/features/rate-limiting) para obtener más detalles.

## Clases de trabajo enviables

Queuety v0.6.0 incorpora un trabajo basado en clases API. En lugar de separar el nombre del controlador, la carga útil y la clase del controlador, se define una sola clase que lo encapsula todo: la carga útil (como propiedades públicas tipadas), la lógica de ejecución y el middleware opcional.

### La interfaz `Contracts\Job`

```php
use Queuety\Contracts\Job;

interface Job {
    public function handle(): void;
}
```

Una clase `Job` almacena su carga útil en propiedades públicas y ejecuta su propia lógica en `handle()`. No se requiere un método `config()` ni registrar un controlador por separado.

### El rasgo `Dispatchable`

Añada el rasgo `Dispatchable` para obtener un método estático `dispatch()` en su clase de trabajo:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}
```

Envíelo pasando los argumentos del constructor:

```php
SendEmailJob::dispatch( 'user@example.com', 'Welcome!', 'Hello there.' )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High );
```

El rasgo `Dispatchable` llama internamente a `Queuety::dispatch()`, por lo que todas las opciones de `PendingJob` (cola, prioridad, retraso, max_attempts, unique, after) funcionan exactamente igual.

### Cómo funciona la serialización

Al enviar una instancia de `Job`, el Queuety de `JobSerializer` extrae el nombre completo de la clase como controlador y todas las propiedades públicas como carga útil. En el lado del trabajador, el serializador reconstruye la instancia haciendo coincidir las claves de la carga útil con los nombres de los parámetros del constructor. Los enumerados con valor asociado se serializan con su valor subyacente y se restauran automáticamente.

### Propiedades de las tareas

Las clases de trabajo pueden declarar propiedades públicas para controlar los reintentos y el tiempo de espera. El trabajador las lee mediante reflexión y las aplica automáticamente.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ImportProductsJob implements Job {
    use Dispatchable;

    public int $tries = 5;
    public int $timeout = 120;
    public array $backoff = [ 10, 30, 60, 120 ];
    public string $concurrency_group = 'imports';
    public int $concurrency_limit = 2;
    public int $cost_units = 4;

    public function __construct(
        public readonly int $supplier_id,
    ) {}

    public function handle(): void {
        // Import products...
    }
}
```

| Propiedad | Tipo | Descripción |
|---|---|---|
| `$tries` | `int` | Número máximo de intentos antes de que el trabajo quede enterrado. Sobrescribe el valor `max_attempts` definido al enviar. |
| `$timeout` | `int` | Tiempo máximo de ejecución en segundos. Sobrescribe la constante global `QUEUETY_MAX_EXECUTION_TIME`. Requiere `pcntl` y aún no se aplica (consulte [Tiempo de espera](https://queuety.inline0.dev/es/docs/jobs#timeout)). |
| `$backoff` | `array` | Matriz de valores de retraso en segundos para cada intento de reintento. El último valor se reutiliza en los intentos posteriores. |
| `$concurrency_group` | `string` | Grupo compartido de admisión de trabajadores para trabajos costosos. |
| `$concurrency_limit` | `int` | Número máximo de trabajos `processing` simultáneos en ese grupo. Se ignora si no se configura `$concurrency_group`. |
| `$cost_units` | `int` | Coste de ejecución relativo que usan los presupuestos del flujo de trabajo y la admisión de trabajadores. |

La matriz `$backoff` proporciona retrasos de reintento progresivos. Por ejemplo, `[10, 30, 60, 120]` significa que el primer reintento espera 10 segundos, el segundo 30 segundos, el tercero 60 segundos y los siguientes, 120 segundos.

Las propiedades relacionadas con los recursos son útiles cuando las clases de trabajo representan distintos perfiles de coste. Una actualización rápida de la caché y una llamada extensa a un proveedor pueden ser «un trabajo» cada una, pero no deberían contabilizarse necesariamente igual cuando un presupuesto de flujo de trabajo o una decisión de admisión de trabajadores intenta mantener el proceso dentro de unos límites seguros.

Consulte [Gestión de recursos](https://queuety.inline0.dev/es/docs/features/resource-management) para conocer el modelo completo, incluidos los presupuestos de flujo de trabajo y la admisión en el lado del trabajador.

### Hook de fallo

Cuando un trabajo queda enterrado tras agotar todos los intentos de reintento, Queuety llama a un método `failed()` en la instancia del trabajo, si está definido. Úselo para tareas de limpieza, alertas o registro.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ProcessPaymentJob implements Job {
    use Dispatchable;

    public int $tries = 3;

    public function __construct(
        public readonly int $order_id,
    ) {}

    public function handle(): void {
        // Process the payment...
    }

    public function failed( \Throwable $exception ): void {
        // Notify the team that payment processing failed permanently.
        error_log( "Payment failed for order {$this->order_id}: {$exception->getMessage()}" );
    }
}
```

El método `failed()` recibe la excepción que causó el fallo definitivo. La instancia del trabajo se deserializa con su carga útil original, por lo que están disponibles todas las propiedades públicas.

### Envío condicional

El rasgo `Dispatchable` proporciona `dispatch_if()` y `dispatch_unless()` para realizar envíos condicionales. Ambos aceptan una condición booleana como primer argumento, seguida de los argumentos del constructor del trabajo.

```php
// Only dispatch if the user has opted in to notifications.
SendEmailJob::dispatch_if( $user->wants_notifications, $user->email, 'Update', $body );

// Dispatch unless the feature flag is disabled.
SyncInventoryJob::dispatch_unless( $maintenance_mode, $sku );
```

`dispatch_if()` realiza el envío cuando la condición es `true` y devuelve `PendingJob`. Devuelve `null` cuando la condición es `false`.

`dispatch_unless()` realiza el envío cuando la condición es `false` y devuelve `PendingJob`. Devuelve `null` cuando la condición es `true`.

Ambos métodos admiten opciones `PendingJob` encadenadas cuando realizan el envío:

```php
SendEmailJob::dispatch_if( $should_send, $to, $subject, $body )
    ?->on_queue( 'emails' )
    ?->with_priority( Priority::High );
```

### Envío síncrono

Use `Queuety::dispatch_sync()` para ejecutar un trabajo de inmediato en el proceso actual, sin añadirlo a la cola. Es útil para hacer pruebas y para trabajos que deben ejecutarse en línea.

```php
use Queuety\Queuety;

// With a Job instance
Queuety::dispatch_sync( new SendEmailJob( to: 'user@example.com', subject: 'Hello', body: 'Hi!' ) );

// With a handler name and payload
Queuety::dispatch_sync( 'send_email', [ 'to' => 'user@example.com' ] );
```

Se llama directamente al método `handle()` del trabajo. No se crea ninguna fila en la base de datos, no interviene ningún worker ni se aplica middleware.

### Encadenamiento de trabajos

El encadenamiento de trabajos ejecuta una serie de trabajos uno tras otro. Cada trabajo de la cadena depende de que el anterior se complete correctamente. Si algún trabajo falla, los siguientes de la cadena quedan pendientes.

#### Uso de `Queuety::chain()`

```php
use Queuety\Queuety;

$first_job_id = Queuety::chain( [
    new FetchDataJob( $url ),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->catch( ChainFailedHandler::class )
    ->dispatch();
```

`Queuety::chain()` devuelve un `ChainBuilder` con métodos encadenables para establecer la cola y un controlador de fallos. El método `dispatch()` crea todos los trabajos con relaciones `depends_on` secuenciales y devuelve el ID del primer trabajo.

#### Uso de `with_chain()`

El rasgo `Dispatchable` proporciona un método `with_chain()`. Actualmente, equivale a `Queuety::chain( $jobs )`: la clase desde la que se llama no se añade a la cadena ([#13](https://github.com/inline0/queuety/issues/13)), así que incluya también el primer trabajo en la matriz:

```php
FetchDataJob::with_chain( [
    new FetchDataJob(),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->dispatch();
```

#### Gestión de fallos en la cadena

Registre una clase controladora de fallos mediante `catch()`. La clase controladora debe tener un método `handle()`. Se llama cuando cualquier trabajo de la cadena queda enterrado de forma permanente. El trabajador admite `handle()`, `handle( Job $job )` y `handle( Job $job, \Throwable $exception )`.

```php
class ChainFailedHandler {
    public function handle(): void {
        // Alert, clean up, etc.
    }
}
```

### Añadir middleware

Las clases de trabajo pueden definir un método `middleware()` que devuelva middleware para envolver la ejecución:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;
use Queuety\Middleware\RateLimited;
use Queuety\Middleware\Timeout;

class CallOpenAIJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $prompt,
    ) {}

    public function handle(): void {
        // Call the API...
    }

    public function middleware(): array {
        return [
            new RateLimited( max: 60, window: 60 ),
            new Timeout( seconds: 120 ),
        ];
    }
}
```

Consulte [Middleware](https://queuety.inline0.dev/es/docs/features/middleware) para obtener más información sobre el middleware integrado y cómo escribir middleware personalizado.

### Comparación con trabajos basados en controladores

| | Basado en controladores (v0.1+) | Basado en clases (v0.6+) |
|---|---|---|
| Envío | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| Controlador | Clase aparte que implementa `Handler` | La misma clase implementa `Contracts\Job` |
| Carga útil | `array $payload` sin tipar | Propiedades públicas tipadas |
| Registro | `Queuety::register()` o `#[QueuetyHandler]` | No es necesario (usa FQCN) |
| Middleware | No disponible | Método `middleware()` opcional |
| Configuración | Método `config()` | Propiedades del trabajo + cadena `PendingJob` |

Ambos enfoques son totalmente compatibles. Los trabajos basados en controladores existentes siguen funcionando sin cambios. Puede combinar ambos estilos en el mismo proyecto.

## Ciclo de vida del trabajo

```
dispatch() -> pending -> processing -> completed
                  |           |
                  |           +-> exception -> pending (retry after backoff)
                  |                       |
                  |                       +-> buried (max attempts reached)
                  |
                  +-> delayed (available_at in the future)
```

| Estado | Descripción |
|---|---|
| `pending` | A la espera de que un trabajador lo reclame |
| `processing` | En ejecución |
| `completed` | Finalizado correctamente |
| `failed` | Reservado; el trabajador no lo establece actualmente. Un intento fallido que se reintentará vuelve a `pending` |
| `buried` | Se agotaron todos los intentos de reintento |
