
# Compensación de flujos de trabajo

La compensación permite asociar un controlador de reversión a un paso completado del flujo de trabajo. Queuety guarda una instantánea del estado público cuando termina ese paso y luego ejecuta los controladores de compensación en orden inverso cuando se cancela el flujo de trabajo o, de forma opcional, cuando falla.

## El método `compensate_with()`

Llame a `compensate_with()` inmediatamente después del paso que debe deshacer:

```php
use Queuety\Queuety;

Queuety::workflow( 'provision_account' )
    ->then( CreateRemoteUserStep::class )
    ->compensate_with( DeleteRemoteUserCompensation::class )
    ->then( CreateSubscriptionStep::class )
    ->compensate_with( CancelSubscriptionCompensation::class )
    ->then( SendWelcomeEmailStep::class )
    ->dispatch( [ 'user_id' => 42 ] );
```

Si más adelante se cancela el flujo de trabajo, se ejecuta Queuety:

1. `CancelSubscriptionCompensation`
2. `DeleteRemoteUserCompensation`

## Controladores de compensación

Los controladores de compensación implementan `Contracts\Compensation`:

```php
use Queuety\Contracts\Compensation;

final class DeleteRemoteUserCompensation implements Compensation {
    public function handle( array $state ): void {
        remote_api_delete_user( $state['remote_user_id'] );
    }
}
```

El controlador recibe el estado público del flujo de trabajo tal como estaba inmediatamente después de completarse el paso compensado. No se incluyen las claves internas reservadas.

## Cancelación frente a fallo

En caso de cancelación explícita, las compensaciones de los pasos completados siempre se ejecutan antes del controlador opcional `on_cancel()`.

En caso de fallo, las compensaciones solo se ejecutan si se habilitan expresamente:

```php
Queuety::workflow( 'provision_account' )
    ->then( CreateRemoteUserStep::class )
    ->compensate_with( DeleteRemoteUserCompensation::class )
    ->then( CreateSubscriptionStep::class )
    ->compensate_with( CancelSubscriptionCompensation::class )
    ->then( ChargeCardStep::class )
    ->compensate_on_failure()
    ->dispatch( $payload );
```

Sin `compensate_on_failure()`, un flujo de trabajo fallido conserva intacto el historial de pasos completados y se puede reintentar desde el paso fallido.

Con `compensate_on_failure()`, las compensaciones de los pasos completados se ejecutan después de marcar el flujo de trabajo como fallido. Después, `retry_workflow()` genera una excepción porque el flujo de trabajo ya se ha compensado.

## Tipos de pasos admitidos

Se puede asociar la compensación a:

- pasos secuenciales de `then()`
- grupos de `parallel()`
- pasos de `for_each()`
- pasos de `run_workflow()`
- pasos de retardo de `delay()`
- pasos de señal de `wait_for_signal()`
- pasos de dependencia de flujo de trabajo de `wait_for_workflow()`

Para `run_workflow()` y `for_each()`, la instantánea de compensación se registra cuando todo el trabajo anidado o ramificado ha terminado por completo.

## `on_cancel()` frente a compensación

Use `compensate_with()` para deshacer los efectos secundarios de los pasos completados en orden inverso.

Use `on_cancel()` para un único enlace de limpieza a nivel del flujo de trabajo que deba ejecutarse cada vez que se cancele explícitamente el flujo.
