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

# Reprocessamento

> Recalcule comissões com dry-run obrigatório e relatório de impacto.

O Reprocessamento reaplica retroativamente uma versão de [regra de comissão](/docs/conceitos/programas-e-regras) a um conjunto histórico de conversões e comissões, gerando um recálculo **auditável**. Mudanças de regra valem por padrão só para o futuro. Aplicar uma versão nova ao passado exige um job de reprocessamento explícito.

Toda execução é protegida por dois mecanismos: nenhum recálculo toca em dinheiro sem antes produzir um relatório de impacto (`dry_run`) que você aprova, e o estado anterior nunca é apagado: o recálculo gera eventos novos encadeados ao histórico, e comissões já pagas (imutáveis) recebem ajustes em vez de serem reescritas.

<Warning>
  Reprocessamento mexe em **dinheiro retroativamente**. Um único job pode alterar dezenas de comissões e gerar ajustes positivos ou negativos no próximo [payout](/docs/conceitos/payouts) de vários afiliados. Sempre revise o relatório de impacto (`summary` + itens) antes de chamar `execute`. Acima do limiar configurado, a plataforma exige uma dupla confirmação por token.
</Warning>

## O fluxo em duas fases

O reprocessamento é uma **dupla confirmação explícita**: você cria um dry-run, revisa o impacto, e só então executa. Não há atalho: o create nunca altera estado e o execute só aplica um dry-run aprovado.

<Steps>
  <Step title="Crie o dry-run (relatório de impacto)">
    `POST /reprocess` congela uma cópia da regra nova, recalcula cada cobrança no intervalo e cria um job em `dry_run` com o `summary` de impacto e um item por comissão afetada. **Nenhuma comissão é alterada.** A resposta inclui um `confirmationToken` (só enquanto o job está `dry_run`).
  </Step>

  <Step title="Revise o impacto">
    Leia o `summary` (delta total, afiliados impactados, maiores variações) e, se precisar, `GET /reprocess/{id}/items` para o detalhe before/after por comissão. Decida se o resultado é o esperado.
  </Step>

  <Step title="Execute ou cancele">
    `POST /reprocess/{id}/execute` aplica exatamente o relatório aprovado, de forma atômica. Se o delta exceder o limiar da política, envie o `confirmationToken`. Para descartar o dry-run, use `POST /reprocess/{id}/cancel`.
  </Step>
</Steps>

```mermaid theme={null}
stateDiagram-v2
    [*] --> dry_run: POST /reprocess<br/>recálculo sem alterar comissões
    dry_run --> executed: POST /reprocess/{id}/execute<br/>aplica o relatório aprovado
    dry_run --> canceled: POST /reprocess/{id}/cancel
    executed --> [*]
    canceled --> [*]
```

A execução é **síncrona e atômica**: ou aplica o relatório inteiro, ou nada. Não existe estado intermediário de processamento. `executed` e `canceled` são terminais: tentar executar ou cancelar um job que não está mais em `dry_run` retorna `409 conflict`.

## Dry-run obrigatório

Todo job nasce obrigatoriamente em `dry_run`. O body de `POST /reprocess` aceita apenas `dryRun: true` (é o default). Enviar `dryRun: false` resulta em `400 parameter_invalid`. A aplicação real é sempre o passo separado `POST /reprocess/{id}/execute`.

O escopo de um job é:

<ParamField body="programId" type="string (prog_)" required>
  Programa-alvo. Obrigatório.
</ParamField>

<ParamField body="from" type="string (ISO 8601)" required>
  Início do intervalo (inclusivo). Filtra pelo `occurredAt` da conversão.
</ParamField>

<ParamField body="to" type="string (ISO 8601)" required>
  Fim do intervalo (exclusivo). O intervalo é meio-aberto `[from, to)`. Exige `from < to` (senão `400 parameter_invalid`).
</ParamField>

<ParamField body="newRuleId" type="string (cmrl_)" required>
  Regra de comissão cuja versão será aplicada. Deve pertencer ao programa (senão `404 resource_not_found`).
</ParamField>

<ParamField body="affiliateIds" type="array de string (aff_)">
  Subconjunto opcional de afiliados (mínimo 1 elemento). Omitido ou `null` = todos os afiliados do programa. Cada id deve pertencer ao programa (senão `400`).
</ParamField>

<ParamField body="dryRun" type="boolean (literal true)">
  Sempre `true` (default). Enviar `false` resulta em `400`.
</ParamField>

```bash Criar dry-run theme={null}
curl -X POST https://api.userepass.com/reprocess \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "programId": "prog_01H8X...",
    "from": "2026-01-01T00:00:00Z",
    "to": "2026-04-01T00:00:00Z",
    "newRuleId": "cmrl_01H8X...",
    "affiliateIds": ["aff_01H8X..."]
  }'
```

<Note>
  Um job é resolvido por cobrança inteira (conversão × ciclo), não por linha de afiliado isolada. Se uma conversão cai dentro de `[from, to)`, **todos os seus ciclos de cobrança são recalculados**, inclusive os que ocorreram depois de `to`. O escopo é a conversão, não a data de cada cobrança individual.
</Note>

## Relatório de impacto

O `summary` é congelado no create e descreve, em centavos, o impacto que o execute aplicaria. Ele classifica cada comissão afetada em um de três tipos (`kind`):

| Tipo (`kind`) | Comissão                                                             | O que acontece no execute                                                                                |
| ------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `recalculate` | Não paga e não reservada (`status` `pending`/`approved`, sem payout) | O `amountCents` é atualizado direto para o novo valor. O memorial de cálculo original permanece intacto. |
| `adjustment`  | Paga (`status: paid`, imutável)                                      | A original **não muda**; o delta vira uma comissão nova do tipo `adjustment`.                            |
| `blocked`     | Reservada em um payout aberto                                        | **Impede o execute** até o payout ser resolvido. Não entra no delta.                                     |

Campos do `summary`:

<ResponseField name="conversionsAffected" type="integer">
  Conversões distintas com pelo menos um item de mudança.
</ResponseField>

<ResponseField name="commissionsRecalculated" type="integer">
  Itens `recalculate` (comissão não paga que mudaria de valor).
</ResponseField>

<ResponseField name="adjustmentsPlanned" type="integer">
  Itens `adjustment` (comissão paga cujo delta vira ajuste).
</ResponseField>

<ResponseField name="blockedCount" type="integer">
  Itens `blocked` (comissão reservada em payout aberto).
</ResponseField>

<ResponseField name="unchangedCount" type="integer">
  Comissões cujo recálculo deu delta 0. Não geram item nem evento.
</ResponseField>

<ResponseField name="affiliatesImpacted" type="integer">
  Afiliados distintos com algum item.
</ResponseField>

<ResponseField name="deltaTotalCents" type="integer (centavos)">
  Soma dos deltas **aplicáveis** (exclui `blocked`). É a base do limiar de confirmação.
</ResponseField>

<ResponseField name="topVariations" type="array">
  Até 5 entradas `{ affiliateId, deltaCents }`, ordenadas por `|deltaCents|` decrescente (exclui `blocked`).
</ResponseField>

```json Resposta 201 (resumida) theme={null}
{
  "id": "repr_01H8X...",
  "status": "dry_run",
  "programId": "prog_01H8X...",
  "newRuleId": "cmrl_01H8X...",
  "confirmationRequired": true,
  "confirmationToken": "rcft_01H8X...",
  "summary": {
    "conversionsAffected": 12,
    "commissionsRecalculated": 9,
    "adjustmentsPlanned": 3,
    "blockedCount": 0,
    "unchangedCount": 4,
    "affiliatesImpacted": 5,
    "deltaTotalCents": 184500,
    "topVariations": [
      { "affiliateId": "aff_01H8X...", "deltaCents": 92000 },
      { "affiliateId": "aff_01H8Y...", "deltaCents": 41500 }
    ]
  }
}
```

O recálculo reaplica a regra nova sobre a **mesma base congelada de cada cobrança** (`calculation.baseAmountCents`), com a aritmética original: taxa efetiva, arredondamento half-up em basis points e split multi-touch por peso (com a sobra de centavos indo ao winner de maior peso). Para regras `tiered`, o contexto de volume é o congelado no cálculo original: recálculo determinístico, não recontagem ao vivo. Veja [Comissões](/docs/conceitos/comissoes) e [Atribuição](/docs/conceitos/atribuicao) para a mecânica de cálculo e split.

<Info>
  A versão da regra é congelada no momento do create. O execute aplica essa versão congelada, não a regra "ao vivo", então alterar a regra entre o create e o execute não muda o resultado aprovado.
</Info>

## Sem mudança retroativa de pagamento

Comissões **pagas são imutáveis**. O reprocessamento nunca reescreve uma comissão `paid`: o delta vira uma comissão nova do tipo `adjustment`, status `approved`, sem payout, que entra no netting do próximo payout do afiliado, positivo ou negativo, pelo mesmo mecanismo do [clawback](/docs/guias/refund-e-clawback).

A comissão de ajuste referencia o job que a originou:

```json calculation.reprocess theme={null}
{
  "reprocess": {
    "jobId": "repr_01H8X...",
    "originalAmountCents": 20000,
    "recalculatedAmountCents": 30000
  }
}
```

Um ajuste de `+10000` (R\$ 100,00) acima entra como crédito no próximo payout; um delta negativo entra como débito. Saldos negativos não geram cobrança ao afiliado: rolam para payouts futuros, como qualquer clawback. Comissões `voided` são ignoradas pelo job, e comissões dos tipos `clawback`/`bonus` ficam de fora do escopo (são fatos derivados, não recalculáveis pela regra).

## Dupla confirmação por token

O execute exige `confirmationToken` quando, e somente quando, `|deltaTotalCents|` excede o `confirmationThresholdCents` da política (default R\$ 1.000,00 / `100000` centavos). O campo `confirmationRequired` é calculado no create e congelado no job.

Token ausente ou errado acima do limiar retorna `409 conflict` (não `403`: um admin sem token continua admin). Abaixo do limiar, o execute funciona sem token.

<CodeGroup>
  ```bash Execute com confirmação theme={null}
  curl -X POST https://api.userepass.com/reprocess/repr_01H8X.../execute \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{ "confirmationToken": "rcft_01H8X..." }'
  ```

  ```bash Execute abaixo do limiar theme={null}
  curl -X POST https://api.userepass.com/reprocess/repr_01H8X.../execute \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{}'
  ```
</CodeGroup>

O `confirmationToken` é exposto na API **somente enquanto o job está `dry_run`** e **nunca** aparece em payload de evento. Após o execute ou cancel, ele deixa de circular.

A política é configurável por organização via `PUT /settings/reprocess-policy`:

<ParamField body="confirmationThresholdCents" type="integer (centavos)">
  Limite acima do qual o execute exige token. Default `100000`. Mínimo `0` (zero = qualquer delta diferente de 0 exige confirmação).
</ParamField>

<ParamField body="notificationMode" type="enum">
  Transparência ao afiliado impactado: `notify_always` (default), `notify_negative_only` ou `silent`. A escolha é registrada no evento `reprocess.completed`.
</ParamField>

## Encadeamento de eventos

O reprocessamento **não reescreve o histórico**: ele gera eventos novos encadeados, junto com a mudança de estado. O estado anterior permanece reconstruível pelo [event store](/docs/conceitos/event-store). A ordem no execute é determinística:

```mermaid theme={null}
sequenceDiagram
    participant Job as reprocess_job
    participant Comm as commission
    Note over Job: reprocess.started
    Comm->>Comm: reprocess.commission_recalculated (1 por item, com before/after)
    Comm->>Comm: commission.created (1 por adjustment)
    Note over Job: reprocess.completed
```

| Evento                              | Quando                                 | Payload relevante                                                                                        |
| ----------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `reprocess.created`                 | No create do dry-run                   | `programId`, `newRuleId`, `ruleVersion`, `from`, `to`, `affiliateIds`, `summary`, `confirmationRequired` |
| `reprocess.started`                 | No início do execute                   | `programId`, `newRuleId`, `ruleVersion`                                                                  |
| `reprocess.commission_recalculated` | Um por item (recalculate e adjustment) | `jobId`, `before.amountCents`, `after.amountCents`, `deltaCents`, `adjustmentCommissionId` (em ajustes)  |
| `commission.created`                | Um por adjustment criado               | `commission` (objeto completo), `jobId`                                                                  |
| `reprocess.completed`               | Ao final do execute                    | `summary`, `notificationMode`, `impactedAffiliates`                                                      |
| `reprocess.canceled`                | No cancel                              | `programId`                                                                                              |

Consulte o [catálogo de eventos](/docs/webhooks/catalogo-de-eventos) para os schemas completos e [Webhooks](/docs/webhooks/visao-geral) para entregá-los aos seus sistemas.

## Garantias de consistência

O execute aplica exatamente o relatório aprovado, ou nada.

* **Um job por programa.** No máximo **um** job aberto (`dry_run`) por programa. Criar um segundo enquanto há um aberto retorna `409 conflict`. Jobs em programas diferentes podem coexistir. `executed` e `canceled` liberam a vaga.
* **Proteção contra dados desatualizados, tudo ou nada.** Cada comissão não paga só é atualizada se ainda casa exatamente com o relatório (`amountCents` + `status` do dry-run + sem payout); cada comissão paga só vira ajuste se continua `paid` com o mesmo `amountCents`. Qualquer divergência (refund, aprovação, reserva, pagamento entre o dry-run e o execute) **cancela o execute inteiro** com `409 conflict`: nenhum item parcial é aplicado.
* **Bloqueio por payout aberto.** Se o relatório contém qualquer item `blocked`, o execute é recusado com `409 conflict` antes de qualquer escrita. Liquide ou cancele o payout e crie um novo dry-run.

<Warning>
  Um dry-run é uma **fotografia**. Se o estado mudou desde que você gerou o relatório, o execute falha por segurança em vez de aplicar números desatualizados. Gere um dry-run novo e revise antes de tentar de novo.
</Warning>

## Erros

| HTTP  | `code`               | Quando                                                                                                                                                   |
| ----- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `parameter_invalid`  | `dryRun: false`; `from >= to`; afiliado fora do programa; corpo inválido                                                                                 |
| `401` | `unauthorized`       | Credencial ausente ou inválida                                                                                                                           |
| `403` | `not_allowed`        | Membro sem papel `owner`/`admin` em rota de escrita                                                                                                      |
| `404` | `resource_not_found` | Programa, regra ou job inexistente                                                                                                                       |
| `409` | `conflict`           | Job já aberto no programa; execute/cancel em job não-`dry_run`; token ausente/errado acima do limiar; itens `blocked`; comissão alterada desde o dry-run |

Todas as escritas (`POST /reprocess`, `/execute`, `/cancel`, `PUT /settings/reprocess-policy`) exigem papel `owner` ou `admin`. As leituras (listar jobs, ver summary, listar itens, ler a política) estão abertas a qualquer membro. As rotas POST aceitam `Idempotency-Key`. Veja a Referência da API para os schemas detalhados de cada endpoint.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Reprocessar mudança de regra" icon="rotate" href="/docs/guias/reprocessar-mudanca-de-regra">
    Guia passo a passo para aplicar uma nova versão de regra ao histórico.
  </Card>

  <Card title="Comissões" icon="coins" href="/docs/conceitos/comissoes">
    A aritmética de cálculo, ajustes e o ciclo de vida das comissões.
  </Card>

  <Card title="Refund e clawback" icon="arrow-rotate-left" href="/docs/guias/refund-e-clawback">
    O mesmo mecanismo de netting que os ajustes de reprocessamento usam.
  </Card>

  <Card title="Event store" icon="layer-group" href="/docs/conceitos/event-store">
    Como os eventos encadeados preservam o histórico auditável.
  </Card>
</CardGroup>
