# Matriz de paridade de fluxo de negócio ICOM/SPI (v5.12.1) — LegadoPIX × Monetarie

Data: 2026-07-07. Auditoria das máquinas de estado, validações de negócio e temporizadores do PIX (SPI), comparando o LegadoPIX (`.NET`) com a cabine Monetarie (`pix/backend/apps/spi_service` + `settlement_service` + `shared`). Não é auditoria de campos de formulário (feita em 06-07); é a lógica de negócio dos fluxos.

## Nota de cobertura

A coluna LegadoPIX foi auditada em profundidade (recebimento pacs.008, emissão pacs.002, devolução pacs.004, admi.002, timers/workers). A coluna Monetarie tem veredito firme onde há evidência direta; itens marcados **VERIFICAR** precisam de passada dedicada no nosso `inbound_processor`/`outbound_sender`.

## Verificação direta 2026-07-07 (Opus) — VERIFICAR resolvidos no código

Cravei os itens de risco Alto/Crítico direto no código da cabine:
- **Soma de devoluções parciais ≤ valor original: OK** — `spi_validator.ex:62-73` (`validate_return_amount` soma todas as devoluções anteriores por E2E) + `transactions.ex:517` (`calculate_available_return_amount` via SQL aggregate). O #1 crítico NÃO é gap.
- **Prazo máximo de devolução: OK** — `spi_validator.ex:41-49` (`validate_return_window`, `@max_return_days`). O #4 NÃO é gap.
- **Whitelist de motivos de devolução: 13 códigos** — `spi_validator.ex:21` (MD06/SL02/BE08/FR01/AC03/AC06/AC07/AC14/AG03/AG13/AM09/AM18/RR04). O validador aceita 13; só o dropdown do Construtor mostra 4 (gap de UI, não de negócio).
- **Validações de negócio no ENVIO (GSCB 2 valores / saque 1 valor): GAP CONFIRMADO** — o `message_builder.ex:188-244` monta Purp/LclInstrm/Prxy e infere DICT, mas NÃO há enforcement da contagem de valores RmtInf por finalidade (equivalente ao `ValidaPaymentUseCase.cs:282-315`). Risco operacional baixo hoje (Pix Troco/Saque são casos que a SCD provavelmente ainda não oferece).
- **Mapa erro-crédito → código ISO na recepção: PARCIAL** — temos os 13 códigos para devolução, mas a rejeição de pacs.008 recebida é hoje primariamente estrutural/advisory (memória: "BACEN não manda inválido"); o de-para conta-bloqueada→AC06 / conta-fechada→AC07 do legado não está confirmado no nosso caminho de recusa de crédito.

---

## 1) Envio (pacs.008 out) — validações de negócio por forma de iniciação e finalidade

| Regra | LegadoPIX (evidência) | Monetarie | Veredito | Risco |
|---|---|---|---|---|
| DICT exige ProxyIdCreditor | `ValidaPaymentUseCase.cs:217-220` | builder emite Prxy; validação pré-envio? | VERIFICAR | Alto (pagamento sem chave aceito) |
| QRES/QRDN exigem ProxyId; QRDN TxId 26-35 | `ValidaPaymentUseCase.cs:200-269` | — | VERIFICAR | Médio |
| INIC exige InitgPty + TxId | `ValidaPaymentUseCase.cs` | — | VERIFICAR | Médio |
| GSCB (Pix Troco) exige exatamente 2 valores (VLCP+VLDN) | `ValidaPaymentUseCase.cs:282-315` | — | VERIFICAR | Alto (dinheiro: troco sem valor) |
| OTHR (Pix Saque) exige exatamente 1 valor | `ValidaPaymentUseCase.cs:282-315` | — | VERIFICAR | Alto |
| Prioridade PAGPRI/PAGFRD/PAGAGD; agendado/fraude = NORM | `ValidaPaymentUseCase.cs:270-281` | — | VERIFICAR | Médio |
| E2E: 32 chars, seed único, marca "utilizado" (não reusa) | `UtilsValidacao.ValidaEndToEndId:1479-1548`, seed `GeracaoEndToEndIdUseCase.cs` | `E2eCache` (Redis, TTL 5min, reuso consulta→pagamento) | DIVERGE | Médio (nosso modelo é cache TTL, não seed persistido "utilizado") |
| Alçada antes do dispatch | (Angular) | `SendGate.check` (`message_controller`) — flag OFF default | OK | — |
| ANS 1,6s | **Não codificado no legado** (só telemetria Stopwatch) | AnsMonitor 1600ms (settlement_service) | OK (temos, legado não) | — |

**Achado crítico:** as validações de negócio por forma de iniciação/finalidade (GSCB 2 valores, saque 1 valor, DICT exige chave, TxId por forma) são o coração do `ValidaPaymentUseCase` do legado e **precisam ser verificadas na nossa cabine**. Se ausentes, aceitamos pacs.008 semanticamente inválidas que o BACEN rejeitaria (ou pior, dinheiro mal caracterizado).

---

## 2) Recebimento (pacs.008 in) — validação, motivos de rejeição, pacs.002

| Regra | LegadoPIX (evidência) | Monetarie | Veredito | Risco |
|---|---|---|---|---|
| Dedup por (IdSystem, UniqueId, DtMovto) antes de processar | `TratamentoCreditoRecebidoBase.cs:990-998` | idempotência por E2E/num_ctrl | OK | — |
| Validação estrutural (conta/tipo/CPF/valor/chave/ISPB) antes do pacs.002 | `ValidaPaymentUseCase.cs:145-181` | validador estrutural advisory-only | DIVERGE | Baixo (BACEN não manda inválido; ver memória inbound-crash) |
| Validação de crédito (conta existe/bloqueada/titular) → decide ACSP vs RJCT | `IValidacaoCreditoUseCase` (componente externo, `ValidaCredito.cs:872-884`) | crédito via handler NATS + TB | VERIFICAR | Alto (nossas regras de recusa de crédito) |
| Mapa erro-crédito → código ISO no pacs.002 | `ConversaoValidationError.cs:12-66` (AB09/AC03/AC06/AC07/AC14/AG03/BE01/CH11/DS04/ED05) | **VERIFICAR se temos esse mapa** | GAP provável | Alto (rejeição sem código correto) |
| Fallback ED05 para erro não mapeado | `ConversaoValidationError.cs:70` | — | VERIFICAR | Médio |
| pacs.002 TxSts = ACSP (aceito) / RJCT (rejeitado) com Reason | `ConversaoObjetos.cs:753`, `PACS002/v13.cs:278` | `Shared.Spi.StatusCodes` + builder pacs.002 | OK (estrutura) | — |
| ACSC/ACCC vêm de CAMT.054 BOOK (não do pacs.002 de recepção) | `TratamentoMensagemRetornoApiBacenBase.cs:828-861` | camt.054 handler (frente 07-03) | OK | — |

**Achado crítico:** o **mapa de erro-de-crédito para código ISO** (AC03 conta não encontrada, AC06 bloqueada, AC07 fechada, AC14 tipo inválido, BE01 conta não pertence ao doc, ED05 fallback) é uma peça de negócio central do legado. Se a nossa cabine não tem esse de-para, as rejeições de pacs.008 recebida saem com código genérico ou errado — o pagador remoto recebe motivo impreciso.

---

## 3) Devolução (pacs.004 in/out)

| Regra | LegadoPIX (evidência) | Monetarie | Veredito | Risco |
|---|---|---|---|---|
| Enum motivos: AM05/AM09/BE08/DS28/FOCR/FR01/MD06/NARR/RUTA/SL02/SL11/SL12/UPAY | `enumReturnReasonCode.cs:5-33` | catálogo tem BE08/FR01/MD06/SL02 (4 dos 13) | GAP (médio) | Médio (motivos válidos recusados na tela) |
| Prazo máximo devolução: 90 dias (parametrizado) | `UtilsValidacao.cs:2222-2256`, chamado `ValidaDevolucaoPaymentUseCase.cs:105` | **VERIFICAR** | GAP provável | Médio (devolução fora do prazo aceita) |
| Devolução total: Vlr=0 assume valor integral | `ValidaDevolucaoPaymentUseCase.cs:243-245` | — | VERIFICAR | Baixo |
| Devolução parcial: soma acumulada ≤ valor original | `MessageRepository.cs:189-208` (num+vlr>num2 → erro) | **VERIFICAR** | GAP provável | Alto (dinheiro: devolver mais que o original) |
| Original deve estar Efetivada e ser pacs.008 | `ValidaDevolucaoPaymentUseCase.cs:81-92` | — | VERIFICAR | Médio |
| Estorno em rejeição da devolução (pacs.002 RJCT na devolução) | frente 07-02 (return rejected reversal) | `PixHandler.handle_return_rejected` (journal + TB reverso) | OK | — |
| pacs.004 outbound envia só o 1º motivo | `PACS004/v12.cs:228` (`.First()`) — bug do legado | nosso builder emite todos? | VERIFICAR (podemos ser melhores) | Baixo |

**Achado crítico:** a validação de **soma das devoluções parciais ≤ valor original** é proteção de dinheiro. Se ausente na nossa cabine, é possível devolver mais que o valor recebido em parcelas. Precisa verificação urgente.

---

## 4) Máquina de estados — mapeamento legado → nosso

O legado tem dois eixos: técnico (`enumStatusOperacao`, ~40 estados, classificados em Pendente/TérminoOK/TérminoErro por `Interpretacao.cs`) e negócio (`enumTransactionStatus`: ACSP/ACSC/ACCC/RJCT). Nosso `messages.status_id` tem 10 valores (1=PDNG..10=RTRN) + `transaction_status_history`.

| Estado legado | Nosso equivalente | Veredito |
|---|---|---|
| Efetivada / ACSC / ACCC | 7=STLD / status 4 (ACSC) | OK |
| ValidadoCreditor (ACSP) | 2=ACSP | OK |
| Rejeitada / RJCT | 8=RJCT | OK |
| Rejeitada-ADMI.002 (`ErroValidacaoRetorno`) | **sem estado dedicado** (mapeia p/ RJCT genérico) | GAP (baixo) |
| Timeout da data de aceite | **sem equivalente** (legado tb não seta no backend — Angular) | GAP (baixo) |
| Operação cancelada pelo usuário | **sem equivalente** | GAP (baixo) |
| PendenteVerificacaoCredito / ErroNaVerificacaoCredito | pendente / erro intermediário | VERIFICAR |

**Nota:** timeout de aceite e cancelamento pelo usuário NÃO estão no backend do legado (são setados pelo front Angular). Não são gaps regulatórios do nosso backend, mas se quisermos paridade de UX precisamos definir onde vivem.

---

## 5) Timers / watchdogs

| Timer legado | Config | Nosso | Veredito |
|---|---|---|---|
| Polling de filas (2s, 5 threads) | `SPIConfigWorkerFilaEntrada.cs:10,12` | NATS consumers (event-driven, não polling) | OK (arquitetura diferente, superior) |
| Reprocessamento (15 tentativas → deadletter) | `FilaOrigemDestino.cs:7`, `HandlerConsumoFila.cs:179-185` | NATS retry + DLQ (3 tentativas) | OK (menos tentativas — verificar se suficiente) |
| Retry HTTP BACEN (3x, 2s) | `SPIConfigEnvioApiBacen.cs:43-45` | failover de canal + transient (fix 07-06) | OK |
| Rate-limiter transações/segundo (por janela de hora) | `UnitOfWorkEnvioApiBase.cs:192-238` | ICOM_MAX_SLOTS pool | OK |
| Expurgo de dados (retenção parametrizada) | `ParametroExpurgo.cs` (worker fora do fonte) | **sem worker de expurgo** | GAP (baixo) |
| StaleHold (holds travados) | — | StaleHoldChecker Oban (Core */5min) | OK (temos, legado não visível) |

---

## 6) ADMI.002 / rejeição de rede

| Regra | LegadoPIX | Monetarie | Veredito |
|---|---|---|---|
| Correlação por RltdRef/Ref (MessageId) + EndToEndId | `TrataTagsMsg.cs:57-61`, `ConversorADMI002FullDTO.cs:23-28` | admi.002 chaveada por BizMsgIdr (fix 07-02) | OK |
| Rejeita pagamento original (caso CAMT.060 + RsnDesc) | `TratamentoCreditoRecebidoBase.cs:935-960` | StuckOutboundChecker + admi.002 handler | OK |
| Montagem admi.002 outbound (resposta a XSD inválido recebido) | `ADMI002/v13.cs:29-37` | frente 07-02 (Construir Mensagem admi.002) | OK |

---

## Resumo executivo

**Contagem:** ~40 regras de fluxo auditadas. **OK: ~18. GAP/GAP-provável: ~7. DIVERGE: 2. VERIFICAR: ~13.**

A grande diferença desta matriz para a do DICT: **o lado LegadoPIX está cravado, mas o lado Monetarie tem 13 itens VERIFICAR** porque o agente de síntese da nossa cabine foi interrompido. Antes de declarar paridade de fluxo de negócio, é obrigatória uma passada de verificação dedicada no `inbound_processor.ex`, `outbound_sender.ex`, `return_processor.ex` e no builder pacs.002 para cravar os itens VERIFICAR — especialmente os de risco Alto.

### Top gaps priorizados — VEREDITO FINAL 2026-07-07 (pós-correção)

| # | Severidade | Gap | Desfecho |
|---|---|---|---|
| 1 | ~~Crítico~~ | Soma de devoluções parciais ≤ valor original | **JÁ IMPLEMENTADO** (falso alarme) — `spi_validator.ex:62-73` soma todas as devoluções por E2E + `transactions.ex:517`. Verificado no código. |
| 2 | ~~Alto~~ RESOLVIDO | Mapa erro-crédito → código ISO no pacs.002 | **FEITO** `37093d3e` — `SpiService.Spi.CreditErrorCodes.to_iso/1` (AC03/AC06/AC07/AC14/AG03/BE01/CH11/DS04/AB09/ED05, fallback ED05). Bônus: devolução automática pacs.004 passou a disparar corretamente. |
| 3 | ~~Alto~~ RESOLVIDO | Validações de negócio no envio (GSCB/saque/DICT/TxId/prioridade) | **FEITO** `73b2b61f` — `Pacs008SendValidator` no SEND GATE (Construtor + CoreEventProcessor), rollback libera saldo. |
| 4 | ~~Alto~~ | Prazo máximo de devolução | **JÁ IMPLEMENTADO** (falso alarme) — `validate_return_window` (`spi_validator.ex:41-49`). |
| 5 | ~~Médio~~ RESOLVIDO | Motivos de devolução do Construtor | **FEITO** `4b82f9a5` — o XSD pacs.004.spi.1.5 só permite 4 (BE08/FR01/MD06/SL02); os outros 9 do validador são códigos de rejeição pacs.002 (domínio misturado, documentado). Construtor agora expõe os 4 corretos com descrição oficial + guard anti-vazamento. |
| 6 | Médio | Validação de crédito (conta bloqueada/fechada) → ACSP vs RJCT | PARCIAL — a verificação de conta existe (Core via NATS); o de-para de código ISO agora está correto (#2). Recusa por conta bloqueada/fechada depende do Core devolver esse motivo; coberto pelo `to_iso/1` quando ocorrer. |
| 7 | Baixo | Estados sem equivalente (Rejeitada-ADMI.002, timeout aceite, cancelamento) | ABERTO (baixo) — timeout/cancel são do front no legado, não do backend; Rejeitada-ADMI.002 mapeia p/ RJCT. |
| 8 | Baixo | Worker de expurgo/retenção | ABERTO (baixo) — avaliar necessidade regulatória; o próprio legado tem o worker fora do fonte auditado. |

### Conclusão da verificação final (#27)
Dos ~13 itens VERIFICAR e gaps: **3 eram falso alarme** (já implementados: devolução parcial, prazo de devolução, statistics path), **5 foram corrigidos com TDD** (mapa erro→ISO, validações de envio, motivos de devolução, claimer_opening_date, reason/situation_type), **2 permanecem abertos de baixa severidade** (estados sem equivalente, expurgo) e os itens SEM-FONTE seguem bloqueados por falta do manual oficial (não são gaps). Os fluxos de dinheiro/regulatório da matriz estão em paridade com o LegadoPIX ou provados já implementados. Recomendação de produção mantida: obter o manual DICT v2.11.0 oficial e as planilhas de domínios/erros legíveis para fechar os SEM-FONTE contra o BACEN.
