
# Señales del flujo de trabajo

Los flujos de trabajo pueden pausarse y esperar una entrada externa mediante señales. Los métodos de construcción `wait_for_signal()` y `wait_for_signals()` añaden pasos que bloquean el flujo hasta que la aplicación, un operador u otro sistema envía la señal esperada mediante `Queuety::signal()`.

Para los flujos con intervención humana, Queuety también incluye:

- `wait_for_approval()` para una carga de aprobación con espacio de nombres
- `wait_for_input()` para datos estructurados introducidos por una persona
- `wait_for_decision()` para resultados explícitos de aprobación o rechazo

Para ver un ejemplo más amplio, de principio a fin, que combina ejecuciones de agentes con puntos de control humano, consulta [Orquestación de agentes](https://queuety.inline0.dev/es/docs/workflows/agent-orchestration).

## El método `->wait_for_signal()`

Añade un paso de espera de señal al flujo de trabajo:

```php
use Queuety\Queuety;

$workflow_id = Queuety::workflow( 'content_approval' )
    ->then( DraftContentHandler::class )
    ->wait_for_signal( 'approved' )
    ->then( PublishContentHandler::class )
    ->dispatch( [ 'post_id' => 99 ] );
```

Cuando el flujo de trabajo llega al paso `wait_for_signal`, cambia su estado a `waiting_for_signal` y se pausa. Mientras espera, no se ejecutan trabajos ni se consumen recursos.

De forma predeterminada, una sola carga de señal se combina con el estado de nivel superior del flujo de trabajo. Si quieres que la carga se agrupe en un espacio de nombres, pasa un `result_key`:

```php
$workflow_id = Queuety::workflow( 'content_approval' )
    ->then( DraftContentHandler::class )
    ->wait_for_signal( 'approved', 'approval' )
    ->then( PublishContentHandler::class )
    ->dispatch( [ 'post_id' => 99 ] );
```

Cuando llega la señal, `$state['approval']` contiene la matriz de cargas de señal.

## Esperar varias señales

Usa `wait_for_signals()` cuando el flujo de trabajo deba reanudarse con cualquiera de varias señales posibles o tenga que recopilar todas las señales necesarias antes de continuar.

```php
use Queuety\Enums\WaitMode;

$workflow_id = Queuety::workflow( 'moderation_gate' )
    ->then( SubmitForReviewHandler::class )
    ->wait_for_signals(
        [ 'editor_approved', 'legal_approved' ],
        WaitMode::All,
        'approvals'
    )
    ->then( PublishContentHandler::class )
    ->dispatch();
```

- `WaitMode::All` espera hasta que se hayan registrado todas las señales configuradas.
- `WaitMode::Any` reanuda el flujo en cuanto llega la primera señal configurada.

Con un `result_key`, una espera de varias señales almacena una matriz asociativa cuyas claves son los nombres de las señales:

```php
[
    'editor_approved' => [ 'by' => 'editor@example.com' ],
    'legal_approved' => [ 'by' => 'legal@example.com' ],
]
```

Sin un `result_key`, las cargas de las señales coincidentes se combinan con el estado de nivel superior en el orden en que se configuró el paso.

## Coincidencia y correlación

Las esperas de señal pueden ignorar eventos no relacionados comparando el contenido de la carga o correlacionando por una clave compartida.

```php
$workflow_id = Queuety::workflow( 'review_gate' )
    ->wait_for_signal(
        name: 'review_ready',
        result_key: 'review',
        match_payload: [ 'source' => 'editorial' ],
        correlation_key: 'task_id',
    )
    ->dispatch( [ 'task_id' => 'task-42' ] );
```

En ese ejemplo, la espera solo se reanuda cuando:

- el nombre de la señal es `review_ready`
- la carga contiene `source: editorial`
- `task_id` de la carga coincide con `$state['task_id']`

Esto resulta útil cuando varios sistemas externos o agentes publican eventos con los mismos nombres, pero solo una carga pertenece a la ejecución actual del flujo de trabajo.

## Ayudantes para aprobación y entrada humanas

`wait_for_approval()` y `wait_for_input()` son métodos auxiliares que simplifican las esperas de señal. Funcionan como `wait_for_signal()`, pero usan de forma predeterminada una clave de resultado con espacio de nombres.

```php
$workflow_id = Queuety::workflow( 'blog_review' )
    ->then( GenerateDraftHandler::class )
    ->wait_for_approval()
    ->wait_for_input()
    ->then( HandleDecisionHandler::class )
    ->dispatch();
```

Después de la señal `approval`, `$state['approval']` contiene la carga de aprobación. Después de la señal `input`, `$state['input']` contiene los datos introducidos.

Usa `wait_for_decision()` si quieres que un solo paso se reanude tanto tras la aprobación como tras el rechazo:

```php
$workflow_id = Queuety::workflow( 'legal_review' )
    ->then( DraftContractHandler::class )
    ->wait_for_decision( result_key: 'review' )
    ->then( HandleReviewDecisionHandler::class )
    ->dispatch();
```

Cuando llega la señal, `$state['review']` contiene:

```php
[
    'outcome' => 'approved', // or 'rejected'
    'signal'  => 'approved', // or 'rejected'
    'data'    => [ 'reviewer' => 'ops@example.com' ],
]
```

## Enviar una señal

Envía una señal a un flujo de trabajo mediante `Queuety::signal()`:

```php
Queuety::signal( $workflow_id, 'approved' );
```

Cuando la señal coincide con la que espera el flujo de trabajo, este se reanuda y avanza al paso siguiente.

Para los flujos con intervención humana, la fachada también ofrece métodos auxiliares semánticos:

```php
Queuety::approve_workflow( $workflow_id, [ 'reviewer' => 'editor@example.com' ] );
Queuety::reject_workflow( $workflow_id, [ 'reason' => 'needs citations' ] );
Queuety::submit_workflow_input( $workflow_id, [ 'notes' => 'Ship after copy edit' ] );
```

### Datos de la señal

Puedes pasar una matriz de datos con la señal. Los datos se combinan con el estado del flujo de trabajo, por lo que los pasos posteriores pueden acceder a ellos:

```php
Queuety::signal( $workflow_id, 'approved', [
    'approved_by' => 'admin@example.com',
    'approved_at' => time(),
] );
```

En el paso siguiente:

```php
class PublishContentHandler implements Step {
    public function handle( array $state ): array {
        // $state['approved_by'] is 'admin@example.com'
        // $state['approved_at'] is the timestamp
        // $state['post_id'] is 99
        wp_update_post( [
            'ID'          => $state['post_id'],
            'post_status' => 'publish',
        ] );
        return [ 'published' => true ];
    }

    public function config(): array {
        return [];
    }
}
```

Las claves que empiezan por `_` (guion bajo) están reservadas para uso interno y no se combinan con el estado.

## Señales existentes

Las señales se pueden enviar antes de que el flujo de trabajo llegue al paso de espera. Cuando llega una señal, se registra en la tabla `queuety_signals` independientemente del estado actual del flujo de trabajo. Más tarde, cuando el flujo llega al paso `wait_for_signal`, comprueba si hay señales previas y continúa de inmediato si encuentra alguna.

Esto significa que el orden no importa:

```php
// Dispatch workflow
$workflow_id = Queuety::workflow( 'order_fulfillment' )
    ->then( PrepareOrderHandler::class )
    ->wait_for_signal( 'payment_confirmed' )
    ->then( ShipOrderHandler::class )
    ->dispatch( [ 'order_id' => 123 ] );

// Signal arrives before the workflow reaches the wait step
Queuety::signal( $workflow_id, 'payment_confirmed', [ 'transaction_id' => 'txn_abc' ] );

// When the workflow finishes PrepareOrderHandler and reaches the wait step,
// it sees the pre-existing signal and continues immediately.
```

## Estado del flujo de trabajo mientras espera una señal

Mientras espera una señal, el estado del flujo de trabajo es `WorkflowStatus::WaitingForSignal` (`waiting_for_signal`):

```php
$state = Queuety::workflow_status( $workflow_id );
echo $state->status->value; // 'waiting_for_signal'
echo $state->wait_type;     // 'signal'
echo $state->wait_mode;     // 'all' or 'any'
print_r( $state->waiting_for ); // ['approved']
print_r( $state->wait_details ); // matched/remaining, correlation info, result key, ...
```

## Caso de uso: aprobación humana

Crea un flujo de aprobación en el que una persona revisora debe aprobar el contenido antes de que se publique:

```php
$workflow_id = Queuety::workflow( 'blog_review' )
    ->then( GenerateDraftHandler::class )
    ->then( NotifyReviewerHandler::class )
    ->wait_for_decision( result_key: 'review' )
    ->then( HandleDecisionHandler::class )
    ->dispatch( [ 'topic' => 'PHP middleware patterns' ] );
```

El `NotifyReviewerHandler` envía un correo electrónico o un mensaje de Slack con enlaces para aprobar o rechazar. Estos enlaces llaman a tu controlador, que envía la señal:

```php
// In your approval controller
Queuety::approve_workflow(
    $workflow_id,
    [
        'reviewer'    => $current_user->user_email,
        'reviewed_at' => time(),
    ],
    'approved'
);
```

El paso `HandleDecisionHandler` lee `$state['review']['outcome']` y publica el borrador o lo archiva.

## Caso de uso: devolución de llamada externa de API

Espera un webhook de un servicio externo:

```php
$workflow_id = Queuety::workflow( 'generate_video' )
    ->then( SubmitToRenderServiceHandler::class )
    ->wait_for_signal( 'render_complete' )
    ->then( DownloadVideoHandler::class )
    ->then( NotifyUserHandler::class )
    ->dispatch( [ 'template_id' => 'promo_v2', 'user_id' => 42 ] );
```

Cuando el servicio externo envía una solicitud a tu endpoint de webhook:

```php
// In your webhook handler
$payload = json_decode( file_get_contents( 'php://input' ), true );
$workflow_id = (int) $payload['metadata']['workflow_id'];

Queuety::signal( $workflow_id, 'render_complete', [
    'video_url'  => $payload['output_url'],
    'duration'   => $payload['duration_seconds'],
] );
```

## Varios puntos de espera de señales en un flujo de trabajo

Un flujo de trabajo puede esperar señales en varios puntos:

```php
Queuety::workflow( 'multi_approval' )
    ->then( CreateProposalHandler::class )
    ->wait_for_signal( 'manager_approval' )
    ->then( EscalateToDirectorHandler::class )
    ->wait_for_signal( 'director_approval' )
    ->then( ExecuteProposalHandler::class )
    ->dispatch( [ 'proposal_id' => 7 ] );
```

Cada paso de espera de señal es independiente. Puedes combinar `wait_for_signal()`, `wait_for_signals()`, `wait_for_approval()`, `wait_for_input()` y `wait_for_decision()` en el mismo flujo de trabajo.
