> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/quickstart/mais-exemplos-praticos/estoque.md).

# Estoque

Controle de estoque costuma ter vários processos que começam, param e continuam depois.<br>

Por exemplo:

* reservar um item enquanto o cliente decide
* esperar confirmação do fornecedor antes de considerar uma reposição
* aguardar a chegada física da mercadoria para atualizar o saldo

No modelo tradicional, cada uma dessas etapas vira:

* uma mensagem em fila
* um job agendado
* ou uma tabela de controle

\
Tudo com seu próprio ponto de falha.

<mark style="background-color:$success;">No skail, esse tipo de fluxo vira um método simples e linear.</mark>

### Reserva com expiração no checkout

Quando o cliente adiciona um produto ao carrinho, você precisa reservar a quantidade correspondente para que outro cliente não compre o mesmo item.&#x20;

Se o checkout não for concluído em 15 minutos, a reserva expira e a quantidade volta para o estoque.

```csharp
[SkailFunction]
public async SkailTask ReservarItem(Guid reservaId, Guid produtoId, int quantidade)
{
    await MarcarComoReservado(produtoId, quantidade, reservaId);

    var checkout = SkailTask.WaitForEvent<ResultadoCheckout>(
        "CHECKOUT_CONCLUIDO",
        reservaId.ToString()
    );
    var expiracao = SkailTask.DelayMinutes(15);

    var resultado = await SkailTask.WhenAny(checkout, expiracao);

    if (resultado == expiracao)
    {
        await LiberarReserva(produtoId, quantidade, reservaId);
        return;
    }

    var dados = await checkout;
    await ConfirmarBaixaDeEstoque(produtoId, quantidade, dados.PedidoId);
}
```

A função marca o produto como reservado, aguarda o que vier primeiro — a confirmação do checkout ou 15 minutos — e segue a vida.<br>

<mark style="background-color:$success;">Durante esse período, a execução fica hibernada ("pausada") sem consumir recurso:</mark>

* não há thread ocupada
* não há timer rodando
* não há polling
* não há registro em memória

<mark style="background-color:$success;">Quando o tempo passa ou o evento chega, o skail retoma a execução do ponto exato onde parou.</mark>

Do lado do checkout, quando o cliente conclui a compra, basta disparar o evento usando o mesmo `reservaId`:

```csharp
[SkailFunction]
public async SkailTask FinalizarCheckout(Guid reservaId, Guid pedidoId)
{
    await RegistrarPedido(pedidoId);

    await SkailTask.FireEvent(
        "CHECKOUT_CONCLUIDO",
        reservaId.ToString(),
        new ResultadoCheckout { PedidoId = pedidoId }
    );
}
```

Qualquer execução que estiver aguardando esse evento continua automaticamente.

{% hint style="info" %}

### Garantindo execução única

Os métodos que alteram o estoque — `MarcarComoReservado`, `LiberarReserva`, `ConfirmarBaixaDeEstoque` — são [`[SkailCommand]`](/funcionalidades/sdk/skailcommand.md).

Isso garante que cada um execute exatamente uma vez, mesmo que a função principal seja retomada várias vezes ao longo da execução.
{% endhint %}

### Reposição automática

A mesma ideia se estende para fluxos longos que duram dias.

Quando o estoque de um produto cai abaixo do mínimo, você dispara o fluxo de reposição:

```csharp
[SkailFunction]
public async SkailTask ReporEstoque(Guid produtoId, int quantidade)
{
    var ordemId = await CriarOrdemDeCompra(produtoId, quantidade);

    var confirmacao = await SkailTask.WaitForEvent<ConfirmacaoFornecedor>(
        "FORNECEDOR_CONFIRMOU",
        ordemId.ToString()
    );

    if (!confirmacao.Aceito)
    {
        await CancelarOrdem(ordemId, confirmacao.MotivoRecusa);
        return;
    }

    var entrega = await SkailTask.WaitForEvent<NotaFiscalEntrada>(
        "MERCADORIA_RECEBIDA",
        ordemId.ToString()
    );

    await AtualizarSaldo(produtoId, entrega.QuantidadeRecebida);
    await ConciliarNota(ordemId, entrega.NumeroNF);
}
```

\
Esse processo pode levar horas ou dias:

* entre criar a ordem e receber a confirmação
* entre a confirmação e a entrega física

\
Mesmo assim, o código continua simples e linear.

\
Em cada ponto, o fluxo:

* espera um evento (`WaitForEvent`)
* hiberna (desliga e não consume mais recurso) até esse evento acontecer
* e continua automaticamente depois (como se nunca tivesse parado)

### Limite de espera pelo fornecedor

Se o fornecedor não responder em 48 horas, você provavelmente quer escalar para o time de compras em vez de deixar a ordem pendurada.&#x20;

Basta combinar o `WaitForEvent` com um `DelayHours` dentro de um `WhenAny`:

```csharp
var confirmacaoTask = SkailTask.WaitForEvent<ConfirmacaoFornecedor>(
    "FORNECEDOR_CONFIRMOU",
    ordemId.ToString()
);
var limiteTask = SkailTask.DelayHours(48);

var resultado = await SkailTask.WhenAny(confirmacaoTask, limiteTask);

if (resultado == limiteTask)
{
    await EscalarParaTimeDeCompras(ordemId);
    return;
}

var confirmacao = await confirmacaoTask;
```

O fluxo está aguardando duas coisas ao mesmo tempo:

* a resposta do fornecedor (`WaitForEvent`)
* ou o prazo de 48 horas (`DelayHours`)

\
O [`WhenAny`](/funcionalidades/sdk/whenany.md) faz o sistema reagir ao que acontecer primeiro.

### Observabilidade

No monitor do portal do skail, cada reserva e cada ordem de reposição aparece como uma execução própria.<br>

Você consegue, por exemplo:<br>

* filtrar por TaskId
* ver quanto tempo cada reserva ficou aguardando
* identificar quantas expiraram e quantas viraram pedido
* acompanhar ordens de reposição que estão há dias esperando o fornecedor

\ <mark style="background-color:$success;">Tudo isso vem direto do estado das execuções — sem precisar adicionar nada além do código acima. Sem precisar adicionar logs, métricas, ferramentas adicionais ou código extra.</mark>
