# Dossiê de paridade PROFUNDO — Multiliquidação / lote (importação, agendamento, saque/troco, QR composto)

Auditoria read-only. Cada afirmação tem prova (arquivo:linha decompilado, tabela.coluna do SQL Server
ou arquivo:linha do nosso Elixir/Vue). Sem prova = verdict INCONCLUSIVO. pt-br sem travessão.

Legado: sistema CRK/Corner `Multiliquidacao.*` (.NET) + banco SQL Server `crk_multtiliquidacao`.
Nosso: cabine PIX Elixir (`pix/backend/apps/{spi_service,settlement_service,shared}`) + admin Vue
(`pix/frontend/admin`).

## Veredito de topo

A Multiliquidação de negócio (motor de PIX-out em lote + agendamento por janela + PIX Saque/Troco +
QR composto + bloqueio de saldo em conta + expurgo) é a MAIOR lacuna funcional da nossa cabine. Do
lado legado tudo existe e roda em produção (492.076 operações no restore). Do nosso lado as rotas
existem no router mas os handlers são **stub / 501 / proxy-para-404**: não há modelo de dados de lote,
não há parser de arquivo, não há worker de processamento por linha, não há QR composto, e o PIX
Saque/Troco não é emitido no fio (a pacs.008 não carrega o bloco de retirada). O que temos pronto e
reaproveitável: motor de PIX-out unitário (pacs.008, DICT lookup, E2E burn), reconciliação/camt.060,
recorrência (PIX Automático), e um validador de forma (`Pacs008SendValidator`) que já conhece as
regras de OTHR/GSCB como salvaguarda.

**Nuance de dados (importante, provada):** neste backup TODAS as 492.076 operações têm
`TP_FINALIDADE='IPAY'` e ZERO operações têm `JS_TIPOVALOR` preenchido — ou seja, o motor de
saque/troco está CODIFICADO no legado mas o volume histórico pago foi 100% transferência. QR composto
foi de fato gerado (2 saque + 3 troco em `TB_INICPAGTODINAMICORECEBEDOR`).

---

## 1. Evidência DB (legado vivo, `crk_multtiliquidacao`)

| Tabela | Linhas | Prova (query) |
|---|---|---|
| `TB_OPERACAO` | 492.076 | `SELECT COUNT(*) FROM TB_OPERACAO` |
| `TB_ARQUIVO_PAGTO` (arquivos de lote) | 179 | `SELECT COUNT(*) FROM TB_ARQUIVO_PAGTO` |
| `TB_ARQUIVO_PAGTO_DETALHE` (linhas de lote) | 115 | idem |
| `TB_ARQUIVO_PAGTO_DETALHE` com `TX_URL_EMV` (tipo 03) | 16 | `WHERE TX_URL_EMV IS NOT NULL AND LEN>0` |
| `TB_INICPAGTODINAMICORECEBEDOR` (QR dinâmico/composto) | 230 | idem |
| `TB_INICPAGTODINAMICORECEBEDOR` com `VL_SAQUE>0` | 2 | QR composto de Saque realmente gerado |
| `TB_INICPAGTODINAMICORECEBEDOR` com `VL_TROCO>0` | 3 | QR composto de Troco realmente gerado |
| `TB_JANELA_RETENTATIVA` | 0 | config de janela não populada NESTE backup |
| `TB_BLOQUEIO` | 0 | nenhum bloqueio MED NESTE backup |
| `TB_OPERACAO` por `TP_FINALIDADE` | IPAY=492.076 | 100% IPAY (nenhum OTHR/GSCB no acervo) |
| `TB_OPERACAO` com `JS_TIPOVALOR` preenchido | 0 | saque/troco pago = 0 no acervo |

Colunas relevantes provadas: `TB_OPERACAO(TP_QRCODE, TP_FINALIDADE, JS_TIPOVALOR, NR_PRESTADORSAQUE)`;
`TB_INICPAGTODINAMICORECEBEDOR(VL_SAQUE, IC_PERMITEALTERACAOVALORSAQUE, NM_MODALIDADESAQUE, NR_SPBSAQUE,
VL_TROCO, IC_PERMITEALTERACAOVALORTROCO, NM_MODALIDADETROCO, NR_SPBTROCO)`;
`TB_JANELA_RETENTATIVA(NR_SPB, TP_PAGAMENTO, DT_VIGENCIA, DH_INICIO_AGENDAMENTO, DH_FIM_AGENDAMENTO,
DH_INICIO_RETENTATIVA, DH_FIM_RETENTATIVA, NR_TENTATIVAS, NR_MINUTOS_INTERVALO)`;
`TB_ARQUIVO_PAGTO_DETALHE(ID_SITUACAO, ID_SITUACAO_OPERACAO, TX_URL_EMV)`.

---

## 2. IMPORTAÇÃO de arquivo de lote (01/02/03/99)

### 2.1 Legado — pipeline e validações (prova arquivo:linha)

`Multiliquidacao.Core.Application.decompiled.cs`:
- **Assinatura ZIP** obrigatória `{80,75,3,4}` (`PK\x03\x04`): `:2628-2634` (`byte[4] { 80, 75, 3, 4 }`;
  se não bate → erro "Arquivo não possui assinatura de arquivo ZIP" `:2661`).
- **ZIP com exatamente 1 arquivo**: `DescompactaArquivo` `:2671`; `zipArchive.Entries.Count > 1` → erro
  "O arquivo compactado contém N arquivos" `:2680-2682`.
- **Sem BOM UTF-8**: `IsUTF8WithBOM` `:2696` (bytes `239,187,191`); se BOM → erro "Arquivo não pode
  possuir Byte Order Mark ('BOM'). Converter para UTF-8 comum." `:2713`.
- **Data de criação não anterior a hoje**: `arquivoPagto.DtCriacao < DateTime.Now.Date` → erro `:2641-2646`.
- **Parser `;`-delimitado** (`TextFieldParser` + `SetDelimiters(";")`): `:2718-2727`.
- **Tipos permitidos na col[0]** = `{"01","02","03","99"}`; qualquer outro → erro `:2728`.
- **`ValidaHeader` `:2769`** (registro `01`), **`ValidaDetail` `:2913`** (registros `02`/`03`),
  **`ValidaTrailler` `:3278`** (registro `99`).
- **Contagem de colunas por tipo**: `02` deve ter 22, `03` deve ter 19 `:2923`.
- **Regras de campo do detalhe** (amostra provada): conta pagador `≤9` (02) / `≤12` (03) `:3032`;
  nome pagador `≤30`/`≤40` `:3044`; descrição `≤117` `:3077`; chave DICT `≤77` `:3085`;
  ISPB recebedor `=8` `:3093`; tipo conta ∈ `CACC/SLRY/SCGS/TRAN/OTHR` `:3018,:3116-3124`;
  UrlEMV (03) obrigatória `≤79` `:3204`; ID do QR (03) `≤35` `:3216`.
- **Trailler `99` = 5 colunas** `:3288-3290`; **batimento**: qtd tipo `02` (detalhes sem UrlEMV) deve
  casar `:3308-3311`; valor total tipo `02` deve casar `:3320-3326`; idem tipo `03` (detalhes com
  UrlEMV) `:3331-3350`. Só grava totais se bate `:3352-3356`.
- **Idempotência** por (CPF/CNPJ, NrTipo, NrSequencial) via `FindArquivoPagto` (documentado no mapa 06
  §2.2; endpoint `POST /api/Entrada/ImportarArquivoPagamento`).

Execução por linha (worker `Multiliquidacao.Core.Worker.ImportacaoArquivos.decompiled.cs`):
- `WorkerImportacaoPagamento`: varre diretório, move para `Processando`, re-zipa, POSTa ao endpoint.
- `WorkerProcessamentoPagamento.Paga` chama `sendService.TratarPix` por detalhe `NaoIniciado`.
- **QR Saque/Troco barrado na importação**: `ValidaQRCode` `:493`; se o QR resolvido for saque/troco →
  erro "Não é possível realizar pagamentos QR Codes de Saque ou Troco através da rotina de importação de
  pagamentos." `:569`.

### 2.2 Nosso — FileController é stub total (prova arquivo:linha)

`pix/backend/apps/settlement_service/lib/settlement_service_web/controllers/file_controller.ex`:
- `import/2` `:10-16` → responde `{status:"accepted", message:"File import queued"}` e **não persiste,
  não parseia, não enfileira nada** (nenhuma chamada a repo/worker).
- `list_imports/2` `:18-28` → sempre `{data: [], total: 0}`.
- `show_import/2` `:30-38` → sempre `{status:"not_found"}`.
- `import_records/2` `:40-46` → sempre `{data: [], total: 0}`.
- `export/2` `:48-54` → `{status:"accepted"}` (não gera nada).
- `download/2` `:78-82` → `404 {error:"File not found"}`.

Rotas registradas (reais, alcançáveis) em `router.ex:188-197` (`scope "/files"`, pipeline
`[:gateway, :api, :gateway_authenticated]`). **Não existe** modelo de dados de lote: grep em
`apps/shared/priv/repo/migrations` + `apps/shared/lib/shared/schemas` por
`arquivo_pagamento|batch_payment|payment_batch|file_import|multiliq|operacao` = **nenhuma tabela**.

**Gap**: parser de arquivo, validações de layout, batimento de trailer, idempotência, modelo
Arquivo/Detalhe/Erro, worker de processamento por linha — TODOS ausentes.

---

## 3. Endpoint `/batch` (PIX-out em lote via API)

### 3.1 Legado

O motor unitário `PagaOperacaoDTOUseCase.TratarPix` (`Application:4541`) é a entrada única; o lote
agrega chamadas ao motor (cada linha vira uma pacs.008 `IdSystem=4` via
`ComplementarPix` → `_spiDTOUseCase.AddPagto`). Não há "uma mensagem de lote" ao BACEN; é
transação-a-transação (mapa 06 §10.1).

### 3.2 Nosso — proxy para rota SPI que foi DELETADA (prova arquivo:linha)

- `settlement_service_web/controllers/gateway/spi_proxy_controller.ex`:
  - `create_batch/2` `:641-649` → `InternalClient.call_spi(:post, "/api/v1/batch", ...)`.
  - `list_batches/2` `:656-665` → `GET /api/v1/batch`.
  - `get_batch/2` `:672-680` → `GET /api/v1/batch/:id`.
  - rotas em `router.ex:580-583`.
- **Mas o SPI não tem `/api/v1/batch`**: `apps/spi_service/lib/spi_service_web/router.ex:129-130`
  comentário: "V2-01b cleanup: Batch payment endpoints (pain.001/002) deletados — fictícias não em XSDs
  raw v5.12 SPI oficial. BatchController removido." Logo o proxy recebe 404 e devolve
  `error_response`/`service_unavailable`.

**Gap**: divergência — a rota `/api/v1/batch` do gateway é anunciada mas o backend que ela chama não
existe (404 garantido). Não há motor de lote por API.

---

## 4. PIX Saque (OTHR/VLDN) e PIX Troco (GSCB/VLDN+VLCP)

### 4.1 Legado — validação + engine + fio (prova arquivo:linha)

`Multiliquidacao.Core.Application.decompiled.cs`:
- Guarda: **Saque/Troco não agendam**: `dtoPagto.Agendamento && (tpFinalidade=="OTHR"||"GSCB")` → erro
  "PIX Saque e Troco não permite agendamento." `:4544-4546`.
- Guarda: dados de saque/troco proibidos em agendamento recorrente `:4406-4408`.
- **OTHR (Saque)**: exige exatamente 1 `TipoValor`; deve ser `VLDN` `:4631-4640`; exige
  `ModalidadeAgente` `:4646-4648` e `PrestadorSaque` `:4650-4652`.
- **GSCB (Troco)**: exige exatamente 2 `TipoValor` (`VLDN`+`VLCP`) `:4655-4668`; exige modalidade
  `:4674-4676` e prestador `:4678-4680`.
- Domínio da modalidade: `AGPSS/AGFSS/AGTEC/AGTOT` `:4970-4972`; prestador = ISPB 8 dígitos `:4975`.
- Prazo de devolução de saque/troco 1h vs 90d de IPAY `:4781,:4786`.
- O motor emite a pacs.008 com o bloco de retirada (dados saque/troco) ao SPI (`ComplementarPix`,
  `Application:5495`; campos `formaIniciacao`/`finalidadeTransacao`/`valorTipo`/modalidade/prestador,
  mapa 06 §10.1). QR composto expõe `retirada.saque`/`retirada.troco` no payload `:870-884`.

### 4.2 Nosso — config + mock; validador de forma existe mas o fio NÃO carrega saque/troco

- **Controller = configuração + estatística MOCK**:
  `settlement_service_web/controllers/pix_saque_troco_controller.ex`:
  - `index/2` `:47-54` devolve só CONFIG (limites/horários), não operações.
  - `stats/2` `:92-102` → `get_transaction_stats` `:252-283` = dados HARDCODED ("For now, return sample
    statistics", totais 1542/3891 fixos).
  - `locations/2` `:109-118` → `get_enabled_locations` `:285-318` = "Farmacia Popular Centro" etc.
    HARDCODED ("In a real implementation, this would query the database").
  - Nenhuma emissão de PIX; nenhum acesso a repo.
- **Validador de forma existe (salvaguarda, não motor)**:
  `settlement_service/spi/pacs008_send_validator.ex` — moduledoc `:22-56` documenta GSCB (2 valores
  VLCP+VLDN) e OTHR (1 valor espécie); `validate_purpose` implementa as contagens. MAS o moduledoc diz
  explicitamente "Pix Troco/Saque provavelmente ainda não ofertados pela SCD; a regra fica como
  salvaguarda."
- **A pacs.008 NÃO carrega o bloco de retirada**:
  `shared/lib/shared/bacen/iso20022/message_builder.ex` `render_cdt_trf_tx_inf/2` `:224-247` emite
  `<Purp><Cd>` `:241` mas **nenhum bloco de retirada** (`<Wdrl>`/`<Wthdrwl>`, `AgtMdlty`, `PrvdrTp`,
  breakdown VLDN/VLCP). Grep por `Wdrl|Wthdrwl|AgtMdlty|PrvdrTp|VLDN` em `message_builder.ex` = ausente.
- **Nada popula `value_type`**: prova no NOSSO código —
  `shared/lib/shared/apix/spi_calculator.ex:162` "NOTE: no monetarie flow populates `value_type` with
  VLDN cash details yet". Schema tem as colunas (`transaction.ex:218-223`:
  `initiation_form, priority_type, transaction_purpose, value_type, agent_modality, withdrawal_provider`)
  mas todas em `@optional_fields` `:232` e nunca preenchidas no fluxo real.
- **Tela sempre vazia**: `frontend/admin/src/views/participants/PixSaquePixTrocoView.vue:510-514`
  chama `store.fetchPixSaqueOperations`; store `:297-304`
  (`fetchPixSaqueOperations`) faz `api.get('/api/v1/pix-saque-troco')` e tenta
  `asArray(response.data, ['operations','data'])`. Mas esse endpoint devolve o objeto de CONFIG
  (`pixSaque`/`pixTroco`), sem chave `operations`/`data` → `asArray` retorna `[]` → a tela nasce
  sempre vazia. Divergência real (contrato tela↔API quebrado).

**Gap**: PIX Saque e PIX Troco end-to-end ausentes. Existe só schema (colunas), validador de forma
(salvaguarda) e telas/stats mock; o motor não emite saque/troco no fio.

---

## 5. QR composto (Saque/Troco) — geração/alteração/cancelamento

### 5.1 Legado (prova arquivo:linha)

`Multiliquidacao.Core.Application.decompiled.cs`:
- Interface `GerarQRCodeComposto(QRCodeCompostoIncDTO, requestToken, idUsuario)` `:3718`; implementação
  `:6051` (valida chave DICT, idTransacao único por chave `:6064-6070`, datas de expiração/primeiro
  pagamento `:6072-6076`; grava `VL_SAQUE`/`VL_TROCO`/`NR_SPBSAQUE`/`NR_SPBTROCO`/modalidades no
  `TB_INICPAGTODINAMICORECEBEDOR`, `:6488`).
- Payload do QR expõe `DadosRetirada.saque`/`.troco` com valor+modalidade+prestador `:856-884`.
- Alteração incrementa `IdRevisaoPayLoad`; ciclo `AlterarQRCodeComposto`/`CancelarQRCodeComposto`
  (mapa 06 §9.1). Pagamento consome a permissão de alteração de valor de saque/troco `:5101-5112,:5227`.
- DB confirma uso real: 2 QR de saque + 3 de troco (§1).

### 5.2 Nosso — 501 em TODAS as ações (prova arquivo:linha)

`settlement_service_web/controllers/composite_qr_code_controller.ex`:
- `action(conn, _opts), do: not_implemented(conn)` `:4` → todas as ações caem no `not_implemented`
  `:6-13` que responde **`501 {error:"not_implemented", message:"Composite QR Code endpoints are not
  implemented in this service"}`**.
- Rotas em `router.ex:178-183` (`POST /qr-codes/composite`, `GET /:id`, `GET /:id/components`).

**Gap**: QR composto (saque/troco) totalmente ausente (501). Temos QR estático e dinâmico/CobV
(`settlement_service/qr_codes.ex`), mas não o composto.

---

## 6. Agendamento por janela de retentativa + débito diferido no lote

### 6.1 Legado (prova arquivo:linha)

- Agendamento simples: `DtPagto > hoje` → `Agendamento=true`; o motor registra `AgdOpe_Concluido`(20)
  e NÃO liquida (débito diferido; mapa 06 §4.1, estado em `General.decompiled.cs:6593`).
- **Janela por ISPB**: `TB_JANELA_RETENTATIVA` (colunas provadas §1). Worker
  `Multiliquidacao.Core.Worker.Agendamento.decompiled.cs`: carrega janelas por participante
  (`ObtemJanelaRetentativaAtual(NrSpb,"A")` e `"N"`) `:164-172`; no loop, se dentro da janela de
  agendamento chama `ProcessaAgendamentoAutomatico(..., retentativa:false, ...)` `:186-189`; se dentro
  da janela de retentativa `retentativa:true` `:191-193`.
- Na abertura da janela, `EnviaOperacaoAgendada` chama `TratarPix` de novo (agora confere saldo, debita
  e envia ao SPI); sem saldo → finaliza Rejeitada (mapa 06 §4.2). Agendamento manual
  `POST /api/Entrada/EnviaOperacaoAgendada`.

### 6.2 Nosso

- Temos **recorrência (PIX Automático)**: `spi_service/recurrences.ex` (`create_recurrence` etc.,
  `:59-90`), evento `monetarie.spi.recurrence.created`. É o mandato de recorrência DICT, NÃO o
  agendamento por data/janela do lote.
- Não há `TB_JANELA_RETENTATIVA` equivalente, nem worker de janela por ISPB, nem agendamento por data
  no fluxo de lote (não há fluxo de lote). O agendamento one-off por data com débito diferido existe no
  CORE (banco) como `ScheduledPix`/`ScheduledTed` (fora desta cabine e fora do lote; ver CLAUDE.md
  raiz), não na multiliquidação.

**Gap**: agendamento por janela de retentativa por ISPB + débito diferido no lote ausente. Recorrência
(PIX Automático) coberta parcialmente e por caminho diferente.

---

## 7. Bloqueio de infração/MED (parcial → total) em conta

### 7.1 Legado (prova arquivo:linha)

`Multiliquidacao.Core.Application.decompiled.cs` — `BloqueioUseCase`:
- `BloquearInfracao(BloqueioInfracaoDTO)` `:8962`: consulta saldo no provedor de CC
  `sendCC.ConsultaSaldo` `:8994`, bloqueia via `sendCC.Bloquear`; se saldo ≥ valor → Total, senão →
  Parcial; guarda contra duplicidade por `idInfracaoBacen` `:9012-9014`.
- `BloquearParciais()` (worker `Multiliquidacao.Core.Worker.Bloqueio`): reprocessa bloqueios ainda
  Parciais até virar Total conforme o saldo chega.
- `DesbloquearInfracao` acionado no `TratarPix` quando `idInfracaoBACEN` presente (antes de liquidar a
  devolução) `:4872-4876,:5164-5168`. `enumSituacaoBloqueio`: Total/Parcial/Desbloqueado.
- É bloqueio de SALDO na conta do recebedor (via sistema de conta), não a infração DICT.

### 7.2 Nosso

- Temos infração/MED DICT: `spi_service/lib/spi_service/med.ex` (MED 2.0, marcador de fraude, recovery)
  e schemas DICT. É o registro de infração no DICT, não o bloqueio de saldo em conta com convergência
  parcial→total.
- Não há `BloquearParciais` nem bloqueio de saldo em conta que reserva o remanescente conforme o saldo
  chega. No nosso desenho o débito/hold é no Core/ledger (TB), não em provedor de CC externo.

**Gap**: parcial. A infração DICT está coberta; o bloqueio cautelar de saldo em conta com convergência
parcial→total (worker) está ausente.

---

## 8. Reconciliação de pendentes ("em aberto")

### 8.1 Legado

`Worker.EmAberto` → `ProcessarPagtoEmAberto` (`Application:4142`): lista pendentes
(`ComplemPIX_ChamouSPI`(60) sem confirmação), consulta status no SPI, gera CAMT.060 se < 1h sem
protocolo, rejeita PAGPRI > 1h, confirma idempotentemente (mapa 06 §6).

### 8.2 Nosso — COBERTO (caminho equivalente)

Temos reconciliação e camt.060: `spi_service/workers/pix_in_orphan_reconciliation.ex`,
`spi_service/daily_reconciliation.ex`, `operation_query.ex` (consulta por E2E/RtrId), camt.060 no
`message_builder`. É a mesma classe de problema resolvida por caminho próprio (NATS/Oban + camt.060 +
PixInOrphanRecon). **Coberto** funcionalmente para o PIX-out unitário; o gancho de "lote em aberto"
seria só a agregação por arquivo (que não existe porque não há lote).

---

## 9. Expurgo / retenção

### 9.1 Legado

`Worker.Expurgo` + `ExpurgoUseCase` (`SPI.Core.Worker.Expurgo.Application.decompiled.cs:30`,
`VerificaExecucao :54` lê parâmetro `EXPURGO` com `Ativa`/`HoraInicio`/`HoraFim` `:62-82`). Move
operações antigas para `crk_multiliquidacao_hist`; corte por `QtdDiasManterDados` (default 90d, mapa 06
§11); limpa logs.

### 9.2 Nosso

Temos retenção de auditoria (BACEN logger, XML archiver, retenção 10a ICOM / 2a DICT — CLAUDE.md pix).
Não há expurgo de "operações de multiliquidação" porque não existe a entidade `Operacao`/lote. **Gap
baixo/info** (só relevante depois de existir o modelo de lote).

---

## 10. Peças "não portar" e substitutos por desenho

- **RetornoMQ (IBM MQ CAMPS/C&M)**: `Multiliquidacao.RetornoMQ.*` consome retorno do sistema de conta
  legado via IBM MQ. No nosso desenho o débito/crédito é no Core/ledger (TB) e a confirmação vem por
  evento NATS do Core (`monetarie.core.pix.*`). Substituição por desenho, não é gap a portar.
- **Provedores de CC externos** (Agibank/Crefisa/Sinqia/Topazio/Cobis/BNP): o legado debita conta de
  terceiro via API externa (`ParticipanteControlado.TpSistemaConta`); no nosso desenho a conta é do
  cliente Monetarie no ledger próprio. Decisão de escopo a validar com o dono (o legado suporta conta
  de terceiro controlado; nós não).
- **Sanction Screening export / APIX001**: APIX001 temos (`settlement_service/apix.ex`,
  `shared/apix/spi_calculator.ex`); o export de Sanction Screening (AML) do legado
  (`ExportarSanctionScreening`, `Application:302`) não temos. Parcial.

---

## 11. Itens COBERTOS (para dar confiança)

- **Motor de PIX-out unitário** (pacs.008 build + DICT lookup + E2E burn): existe e é reaproveitável —
  `message_builder.ex:224-247`, `spi_service` outbound, `E2ECache`. É a peça mais cara e ela está pronta.
- **Validador de forma OTHR/GSCB** (salvaguarda): `pacs008_send_validator.ex` já conhece as contagens
  de VLDN/VLCP; falta o motor e a emissão no fio.
- **Schema pronto para saque/troco**: `transaction.ex:218-223` já tem
  `value_type/agent_modality/withdrawal_provider/initiation_form/priority_type/transaction_purpose`.
- **Recorrência (PIX Automático)**: `spi_service/recurrences.ex` coberto (caminho próprio).
- **Reconciliação/camt.060**: coberto (§8).
- **QR estático e dinâmico/CobV**: `settlement_service/qr_codes.ex` + simulador CobV — só o composto
  falta.

---

## 12. Esforço por sub-parte (relativo à cabine atual)

| Sub-parte | Estado nosso | Esforço |
|---|---|---|
| Parser arquivo 01/02/03/99 (ZIP, sem BOM, `;`, batimento trailer, idempotência) | Ausente (FileController stub) | Médio |
| Modelo de dados de lote (Arquivo/Detalhe/Erro + Operacao) | Ausente | Médio |
| Worker de processamento por linha (reusa motor PIX-out) | Ausente; motor existe | Baixo-Médio |
| Endpoint `/batch` funcional | Divergência (proxy → 404) | Baixo (religar ao motor) |
| PIX Saque (OTHR/VLDN) end-to-end + bloco de retirada na pacs.008 | Parcial (só schema+validador+mock) | Médio |
| PIX Troco (GSCB/VLDN+VLCP) end-to-end | Parcial | Médio |
| QR composto (saque/troco) geração/alteração/cancelamento | Ausente (501) | Médio |
| Agendamento por janela de retentativa por ISPB + débito diferido no lote | Ausente | Médio-Alto |
| Bloqueio de saldo em conta parcial→total (worker) | Parcial (infração DICT existe) | Médio |
| Reconciliação em aberto (camt.060) | Coberto | Baixo |
| Expurgo de operações de lote | Ausente | Baixo |
| Tela PixSaquePixTroco com dados reais | Divergência (fetch de endpoint de config) | Baixo |
| Sanction Screening export | Ausente | Baixo-Médio |
| RetornoMQ CAMPS | Não portar (substituto NATS) | N/A |

**MVP funcional** (importar lote + processar por linha reusando o motor PIX-out + status por
arquivo/linha + `/batch` religado + reconciliação): **médio**. **Completo** (agendamento por janela +
débito diferido + saque/troco no fio + QR composto + bloqueio de saldo em conta parcial→total):
**médio-alto**.

**Decisões-chave a validar com o dono antes de implementar:** (1) conta pagador sempre da Monetarie ou
também de terceiro controlado; (2) débito diferido no agendamento (dinheiro livre até a janela) vs hold
no registro; (3) precisa de arquivo de retorno posicional (o legado não tem; retorno é por API/status);
(4) saque/troco e QR composto entram no MVP ou fase 2.
