# Dossiê de paridade profunda — Contábil COSIF (cobertura evento por CdMsg)

Auditoria READ-ONLY, zero inferência. Confronto: Legado PIX CRK/Corner (.NET decompilado + SQL Server
vivo `CRK_SPICONTABIL` + Angular) x Nossa cabine PIX Monetarie (`pix/backend/apps/settlement_service` +
`pix/frontend/admin`) x Core (`core/backend`, COSIF autoritativo do PIX).

Convenção de unidade: legado em REAIS decimais (`numeric(18,2)`); nossa cabine em base_units/centavos
(`accounting_event.amount :integer`). Toda comparação de valor exige conversão.

---

## 1. O QUE O LEGADO FAZ (com prova)

### 1.1 Natureza — monitor batch/diário, D contra C sumarizado por evento

O legado PIX **NÃO** faz partida dobrada por operação num ledger interno. É uma camada de CONFIGURAÇÃO
(plano de contas, histórico padrão, centro de custo, eventos contábeis) + um MONITOR que agrega as
operações **LIQUIDADAS do dia** e produz linhas D/C somadas por evento e um ARQUIVO posicional de
exportação para o sistema contábil externo importar. Espelho regulatório, não ledger transacional.

### 1.2 Modelo de dados (banco vivo `CRK_SPICONTABIL`)

Tabelas (contagem viva no backup restaurado):
`EventoContabil=5`, `EventoMensagem=5`, `EventoMensagemTag=5`, `PlanoConta=16`, `HistPadrao=8`, `CentroCusto=5`.

- `EventoContabil` (colunas vivas): `IdEvento, IdInstFinanc, CdEvento, IdHist, IdContaDebito1,
  IdContaDebito2, IdContaCredito1, IdContaCredito2, DsEvento, FlAtivo, IdCentroCustoDebito1/2,
  IdCentroCustoCredito1/2` — **evento com até 2 contas de débito + 2 de crédito, cada perna com centro
  de custo, + histórico padrão**. Prova de escrita: `SPI.Core.Contabil.Application.decompiled.cs:406-420`
  (IncluiEvento monta `EventoContabil` com as 4 contas + 4 CCs + IdHist).
- `EventoMensagem` = `{IdEventoMsg, CdMsg, IdEvento}` — **qual mensagem PIX (CdMsg) dispara qual evento**.
- `EventoMensagemTag` = `{IdEventoMsgTag, IdEventoMsg, NmTag, VlTag}` — condições por TAG/campo da mensagem.

CRUD completo por REST (`SPI.Web.Contabil.API`) + UI Angular
(`.../spi/contabilizacao/cadastro/eventos-contabeis`, `.../monitor/monitor-contabil`):
`IncluiEvento/AlteraEvento/ExcluiEvento` (`Contabil.Application:365,536,620`),
`IncluiConta/AlteraConta/ExcluiConta`, `IncluiHist`, `IncluiCentroCusto`. É **data-driven/configurável**.

**Dados vivos observados (importante — o backup restaurado está com CONFIG DE TESTE):**
`EventoMensagem` mapeia só `CAMT.052` (IdEvento 18) e `PACS.008` (IdEvento 19,20,21,22). Nenhum evento
para `PACS.004`. `EventoContabil`: só `IdEvento 21` tem `FlAtivo=1` (DsEvento "teste magno"); os demais
inativos ("teste", "testes 1"). `HistPadrao`/`PlanoConta`/`CentroCusto` também com descrições de teste.
Ou seja: a CAPACIDADE do legado é rica e configurável, mas a CONFIG dessa instalação era de laboratório
(sem eventos de produção para devolução; a maioria inativa).

### 1.3 Gatilho e motor — só operação LIQUIDADA (IdStatus==9 = Efetivada)

`MonitorContabilUseCase.ObtemLancamentos` (`Contabil.Application:715-805`):

1. `SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED` (:718), join `MessageFull + Payments`
   **WHERE `DtMovto==dia` && `IdInstFinanc==empresa` && `(int)msg.IdStatus == 9`** (:720-722) →
   lista `MsgPay` com `FlDebitoCredito` = D/C (`msg.DebitoCredito==1?"D":"C"`, :732), `Vlr`, tags
   (`FinalidadeTransacao, Ccy, CpfCnpj*, FormaIniciacao, ...`). `IdStatus==9` = `Efetivada` provado
   em `SPI.Core.General.decompiled.cs:7114-7135` (enum inicia em 0; posição 9 = "Efetivada").
   O filtro é **agnóstico de CdMsg** quando `CdMsg==null`: toda mensagem liquidada do dia entra
   (incluindo PACS.004 devolução — vira `MsgPay` com direção D/C própria).
2. `RetornaQueryMsg` (:807-882) monta, por evento ativo, uma query dinâmica
   `CdMsg==@0 && NmTag==@1 && NmTag==@2 ...` a partir de `EventoMensagem`+`EventoMensagemTag`,
   com `Peso = nº de condições` (:865).
3. Ordena `orderby Peso descending, IdEvento` (:756), casa as `MsgPay` ainda não consumidas
   (`.Where(item.Where, item.Valores)`), **soma D e C** (`Sum` :764-765), `Vlr = |somaC - somaD|`
   (:766), produz um `Lancamento` (2 contas D, 2 C, histórico, `Qtd, VlrDeb, VlrCred`) e
   **REMOVE as operações casadas** (:787) — cada operação cai em um único evento, o mais específico.

### 1.4 Bucket "NÃO CONTABILIZADO" (detecção de gap)

`Contabil.Application:789-803`: quando não há filtro (`!IdEvento && !ContaDeb && !ContaCre`), as
operações liquidadas que NENHUM evento casou viram um `Lancamento` `idEvento=0`,
`dsEvento="NÃO CONTABILIZADO"`, com `Qtd`, `VlrDeb`, `VlrCred`. É a **visibilidade explícita de cobertura**:
o operador vê no monitor quanto liquidou sem evento contábil. Grid no HTML do monitor mostra
cdEvento/dsEvento/Débito/Crédito com totais
(`.../monitor-contabil/monitor-contabil.component.html:125-141`).

### 1.5 Exportação (arquivo posicional por EVENTO)

`DownloadArquivo(id, idEmpresa, idFilial, dtMovimento)` (`Contabil.Application:884-908`):
`ObtemLancamentos(...).Where(d => d.idEvento != 0)` — **exclui o bucket "NÃO CONTABILIZADO"** (:889).
- `id=1` `LayoutCRK` (:910-984): header `H{yyyyMMdd}...0000001`; por lançamento, linhas
  `00090{conta:15}{D|C}{Vlr:17}{CdHist:4}{DsHist:80}{seq:7}` — 1 linha por perna (D1, C1, D2 se houver,
  C2 se houver); trailer `T{total:18}{total:18}...{seq:7}`.
- `id=2` `LayoutCRKDinamico` (:986-1040): por perna `{filial:4}{empresa:4}{ddMMyyyy}{conta:14}{D|C}
  {DsHist:200}...{Vlr:17}{CentroCusto:16}` — **inclui centro de custo por perna**.

### 1.6 Tarifa (NÃO é lançamento contábil no legado)

`ParametroTarifa` existe (`SPI.Core.Domain.decompiled.cs:817-827`: `VlrTarifa, NrQtdeMsgs,
DtInicioVigencia, Msgs=List<TarifaXMsg>`) mas é **cobrança por contagem de mensagens** (billing/APIX).
`grep 'arifa'` nos 3 arquivos Contábeis = **0**. A tarifa NÃO entra no monitor contábil nem gera evento
D/C. Portanto, no legado, tarifa PIX não vira journal COSIF dentro do PIX.

---

## 2. O QUE A NOSSA CABINE FAZ (com prova)

### 2.1 Modelo de dados (schema `monetarie_settlement`)

Nossa cabine TEM um modelo contábil próprio (mais rico do que "mapa grosso"):
- `ChartOfAccounts` (`accounting/chart_of_accounts.ex`): `cosif_code, name, account_type
  (asset/liability/equity/revenue/expense), parent_code, level, active`.
- `JournalEntry` (`accounting/journal_entry.ex:14-28`): partida dobrada por operação com **UM
  `debit_account_id` + UM `credit_account_id`** (belongs_to ChartOfAccounts), `amount, date,
  description, reference, source_type, source_id, status (pending/posted/reversed)`.
- `AccountingEvent` (`accounting/accounting_event.ex:14-27`): `type (automatic/manual), event_type,
  description, amount, **debit_account + credit_account (strings, UM cada)**, date, user, status
  (processed/pending/error), reference`.
- `CostCenter` (`accounting/cost_center.ex:13-19`): `code, name, responsible, status, budget, spent`
  — CRUD de ORÇAMENTO standalone; **NÃO tem vínculo com journal/evento** (nenhum `cost_center_id`
  em journal_entry.ex nem accounting_event.ex).

Seed: `cosif_seeder.ex` semeia **apenas 2 contas**: `1.1.2.10.01.10.001` (asset, reserva/banco
liquidante) e `4.9.8.10.01.10.002` (liability, obrigação por prestação de serviço / conta do cliente).

### 2.2 Gatilho e mapa — por DIREÇÃO, hardcoded

Único gatilho automático: evento NATS `monetarie.spi.transaction.settled`, consumido pelo
`SettlementObligationWorker` (`filter_subject: "monetarie.spi.transaction.settled"`,
`settlement_obligation_worker.ex:48`). No `handle_settled`, após gravar a obrigação, chama
`emit_accounting(event, amount)` (:203).

`emit_accounting` (:392-421) resolve o par de contas por
`settlement_cosif(event.direction)` (:177-179):
```
def settlement_cosif("INBOUND"), do: {reserve_account(), payable_account()}
def settlement_cosif(_),          do: {payable_account(), reserve_account()}
```
→ cria um `AccountingEvent` `type=automatic`, `event_type="pix_settlement"`,
`description="Liquidacao PIX <e2e>"`, `debit_account`/`credit_account` = o par acima,
`reference="PIX-SETTLE-<e2e>"` (:395-406). **É um par reserva↔payable por SENTIDO do dinheiro,
não por CdMsg+tag.** `reserve_account`/`payable_account` são os 2 COSIF fixos (:677-691).

`create_accounting_event` → `maybe_create_journal_from_event` (`accounting.ex:161-214`): busca as 2
contas no plano; se ambas semeadas → cria `JournalEntry` (1D+1C) + marca evento `processed`; se conta
COSIF não semeada ou journal inválido → `report_accounting_failure` (:225-251): `Logger.error` +
telemetry `[:pix,:accounting,:failure]` + evento vira `status="error"` (queryável).

`emit_accounting` é **best-effort**: `{:error, reason} -> Logger.warning` (:412-416) e
`rescue e -> Logger.warning` (:418-421). Falha de contabilização não vira DLQ nem trava a liquidação
(correto pelo princípio "COSIF nunca bloqueia o transacional", mas sem retentativa).

### 2.3 Exportação e relatórios

`AccountingController` (`router.ex:675-691`, pipeline autenticado):
- `GET /export/circular-4010` (`accounting_controller.ex:87-128`): arquivo pipe-delimited
  `COSIF|DESCRICAO|SALDO_ANTERIOR|DEBITOS|CREDITOS|SALDO_ATUAL` **agregado por CONTA COSIF**
  (não por evento), `SALDO_ANTERIOR` sempre `0` (:118), sem histórico, sem centro de custo, sem seq.
- `GET /balance-sheet` (:140-155), `GET /income-statement` (:167-186) — DRE/balanço por account_type.
- `GET /events` (list_accounting_events, filtro por type/status), `POST /events` (lançamento manual),
  `GET /journal-entries`, `GET /chart-of-accounts`, CRUD `/cost-centers`.
- **NÃO há endpoint de "monitor contábil" que produza o bucket NÃO CONTABILIZADO** (grep coverage/
  não-contabilizado/settled-without-journal em `settlement_service/lib` = só comentários).

### 2.4 Frontend (pix admin)

`pix/frontend/admin/src/views/accounting/`: `AccountingMonitorView.vue` lista apenas os **últimos 10
accounting_events** com status pending/error/processed (`AccountingMonitorView.vue:55`) — NÃO é o
monitor do legado (agregado D/C por evento + bucket não-contabilizado). `AccountingEventsView.vue`
filtra eventos por status (all/processed/pending/error) — permite VER eventos em "error", mas não
liquidações-sem-evento. Há `ChartOfAccountsView`, `CostCentersView`, `StandardHistoryView`,
`AccountingDashboardView`, `FeeManagementView`.

### 2.5 Devolução (pacs.004) e tarifa — sem lançamento na cabine

- Devolução liquidada publica `monetarie.spi.return.settled` (`status_updater.ex:233-241`), que o
  `SettlementObligationWorker` NÃO consome (filtro é `transaction.settled`). O `ReturnProcessor`
  materializa a linha da devolução mas **não emite contabilidade** (o `accounting_date: today` em
  `return_processor.ex:569` é só uma COLUNA da tabela `messages`, não um journal). → devolução liquidada
  não vira `accounting_event`/`journal_entry` na cabine.
- Tarifa: nenhum hook contábil (`FeeManagementView` é gestão de tarifa, sem journal). Igual ao legado
  (tarifa não é lançamento COSIF do PIX nem lá nem cá).

---

## 3. MITIGAÇÃO — o COSIF autoritativo do PIX é o CORE

Regra do ecossistema (CLAUDE.md, Fase 1/2): a contabilidade autoritativa do PIX é o Core, não a cabine.
Confirmado em código:
- `core/backend/lib/monetarie/infra/nats/handlers/pix_handler.ex` (materializa perna PG/COSIF do PIX-in),
  `workers/cosif/journal_entry_worker.ex` (journal COSIF do PIX-out assíncrono),
  `workers/cosif/pix_in_projection_worker.ex`.
- `core/backend/lib/monetarie/use_cases/cosif/pix_reconciliation.ex` = **o análogo do "NÃO
  CONTABILIZADO"**: reconcilia por E2E `transactions × journal_entries` e classifica
  `:missing_journal` ("PIX materializado sem lançamento COSIF E sem job de espelho pendente = falha
  real", `pix_reconciliation.ex:20-24`), `:journal_pending` (async lag esperado, :24-27),
  `:amount_mismatch` (:27), `:orphan_journal`, `:duplicate_e2e`, `:missing_statement`
  (`reconcile/3` :91-139; buckets no summary :317-320).

Portanto o gap da cabine NÃO implica PIX sem lastro contábil no ecossistema: implica que a trilha
contábil **DA CABINE** é incompleta e a detecção de cobertura vive no Core. Ressalva: a reconciliação do
Core cobre transações materializadas no Core por E2E; não é o mesmo escopo por-CdMsg do monitor legado.

---

## 4. GAPS (legado x nosso)

| id | título | tipo | risco |
|----|--------|------|-------|
| g1 | Mapa contábil por DIREÇÃO, não por CdMsg+tag (sem motor evento×mensagem×tag) | divergencia | alto |
| g2 | Devolução (pacs.004) liquidada não gera lançamento na cabine | ausente | alto |
| g3 | Sem detecção de "NÃO CONTABILIZADO" na cabine (liquidação sem evento) | ausente | medio |
| g4 | Evento single-leg (1D+1C) vs legado 2D+2C | divergencia | medio |
| g5 | Centro de custo não vinculado às pernas contábeis | divergencia | medio |
| g6 | Sem modelo/CRUD configurável de evento contábil e plano de contas na cabine | parcial | medio |
| g7 | Layout de exportação por conta (circular-4010) vs por evento (LayoutCRK) | divergencia | baixo |
| g8 | Sem catálogo de histórico padrão (HistPadrao/CdHist) por evento | divergencia | baixo |
| g9 | Unidade REAIS (legado) vs base_units/centavos (nossa) — porting | info | info |
| c1 | Só contabiliza operação LIQUIDADA (mesmo gate) | coberto | info |
| c2 | Falha de materialização de evento é detectada (status=error + alerta crítico) | coberto | info |
| c3 | Tarifa não gera journal COSIF no PIX (equivalência) | coberto | info |
| c4 | Existe plano de contas + centro de custo + export posicional (estrutura análoga) | coberto | info |
| c5 | Detecção de cobertura existe no Core (pix_reconciliation :missing_journal) | coberto | info |

### g1 — Mapa por direção, não por CdMsg+tag (DIVERGÊNCIA, ALTO)
- Legado: cada operação liquidada casa a um `EventoContabil` via `EventoMensagem(CdMsg)` +
  `EventoMensagemTag(NmTag/VlTag)` com peso/especificidade e desempate pela mais específica —
  `Contabil.Application:755-787,845-880`. Distingue por CdMsg, finalidade, moeda, forma de iniciação etc.
- Nosso: `settlement_cosif/1` = 2 casos hardcoded por `event.direction` (INBOUND/OUTBOUND) —
  `settlement_obligation_worker.ex:177-179,392-406`. Todo CdMsg com a mesma direção cai no MESMO par
  de contas; impossível ter contas distintas por CdMsg/tag.

### g2 — Devolução (pacs.004) sem lançamento na cabine (AUSENTE, ALTO)
- Legado: qualquer mensagem liquidada (incl. PACS.004) entra no monitor (`IdStatus==9`, filtro
  agnóstico de CdMsg — `Contabil.Application:720-722`) e cai em evento configurado ou no bucket
  "NÃO CONTABILIZADO".
- Nosso: `return.settled` não é consumido pelo worker de contabilidade (`filter_subject:
  "monetarie.spi.transaction.settled"`, `settlement_obligation_worker.ex:48`); `status_updater.ex:233-241`
  publica `return.settled`; `ReturnProcessor` não chama contabilidade (`accounting_date` é coluna,
  `return_processor.ex:569`). Devolução liquidada não aparece em `accounting_events`/`journal_entries`.
  Mitigado no Core.

### g3 — Sem detecção de "NÃO CONTABILIZADO" na cabine (AUSENTE, MEDIO)
- Legado: bucket `idEvento=0 "NÃO CONTABILIZADO"` com Qtd/VlrDeb/VlrCred no monitor —
  `Contabil.Application:789-803`.
- Nosso: só detecta EVENTO CRIADO que não materializou journal (COSIF não semeado) → `status="error"`
  + telemetry `[:pix,:accounting,:failure]` + alerta crítico (`accounting.ex:193-251`,
  `alert_engine.ex:507-536`). NÃO detecta liquidação que nunca gerou evento (devolução, tarifa, ou
  `emit_accounting` que caiu no rescue). Sem sweep de cobertura na cabine. Mitigado no Core
  (`pix_reconciliation.ex:20 :missing_journal`).

### g4 — Evento single-leg vs 2D+2C (DIVERGÊNCIA, MEDIO)
- Legado: `EventoContabil` com `IdContaDebito1/2` + `IdContaCredito1/2` (até 4 pernas) —
  colunas vivas + `Contabil.Application:406-420`; `LayoutCRK` emite D2/C2 quando presentes (:946-971).
- Nosso: `AccountingEvent.debit_account`/`credit_account` (1+1, `accounting_event.ex:19-20`) e
  `JournalEntry.debit_account_id`/`credit_account_id` (1+1, `journal_entry.ex:24-25`). Não representa
  lançamento multi-perna (ex. principal + tarifa/tributo na mesma operação).

### g5 — Centro de custo não vinculado (DIVERGÊNCIA, MEDIO)
- Legado: centro de custo por perna (`IdCentroCustoDebito1/2, Credito1/2`), exportado no
  `LayoutCRKDinamico` (`Contabil.Application:1000,1010,1022,1034`).
- Nosso: `cost_center` é CRUD de orçamento (`code/budget/spent`, `cost_center.ex:13-19`) sem
  `cost_center_id` em journal/evento; `export/circular-4010` não tem coluna de centro de custo
  (`accounting_controller.ex:105-127`).

### g6 — Sem modelo/CRUD configurável (PARCIAL, MEDIO)
- Legado: CRUD completo de evento/plano/histórico/CC (`Contabil.Application:365,536,620` + demais)
  via `SPI.Web.Contabil.API` + UI `eventos-contabeis`. Contabilidade é data-driven por instalação.
- Nosso: mapa hardcoded (2 linhas), plano de contas SEED-only (router só `GET /chart-of-accounts`,
  `router.ex:678`; `cosif_seeder.ex` com 2 contas), sem cadastro de evento/tag. `POST /events` só cria
  lançamento manual avulso, não configura regra CdMsg→evento.

### g7 — Layout de exportação divergente (DIVERGÊNCIA, BAIXO)
- Legado: posicional por EVENTO com histórico + D/C por perna + seq + header/trailer, excluindo o
  não-contabilizado (`Contabil.Application:889,910-1040`).
- Nosso: `circular-4010` pipe-delimited por CONTA COSIF, `SALDO_ANTERIOR` sempre 0, sem histórico/CC/seq
  (`accounting_controller.ex:87-128`). Ambos exportam arquivo p/ contábil externo, formatos distintos.

### g8 — Sem catálogo de histórico padrão (DIVERGÊNCIA, BAIXO)
- Legado: `HistPadrao(CdHist, DsHist)` por evento, impresso no lançamento e no arquivo
  (`Contabil.Application:838-839,925,992`).
- Nosso: `journal.description` é texto livre ("Liquidacao PIX <e2e>", `emit_accounting:398`), sem
  código/catálogo de histórico padrão.

### g9 — Unidade (INFO)
- Legado em REAIS decimais (`Payment.Vlr` numeric(18,2)); nossa cabine em base_units/centavos
  (`accounting_event.amount :integer`). Porting exige conversão explícita.

### COBERTOS
- c1: ambos só contabilizam LIQUIDADO — legado `IdStatus==9` (`Contabil.Application:722`;
  enum `General:7135`), nosso `transaction.settled` (`settlement_obligation_worker.ex:48,203`).
- c2: falha de materialização de evento é VISÍVEL na cabine (status=error + alerta crítico) —
  `accounting.ex:193-251`, `alert_engine.ex:507-536`. O legado não tinha esse alerta granular.
- c3: tarifa não vira journal COSIF do PIX em nenhum dos dois (legado `ParametroTarifa` billing-only,
  `Domain:817-827`, 0 refs no Contábil; nosso sem hook de fee accounting) — equivalência, não regressão.
- c4: estrutura análoga existe — plano de contas, centro de custo, export posicional (embora mais
  simples): `chart_of_accounts.ex`, `cost_center.ex`, `accounting_controller.ex:87-128`.
- c5: a detecção de cobertura ("não contabilizado") existe no ecossistema, no Core autoritativo —
  `core/.../cosif/pix_reconciliation.ex:20-27,91-139` (`:missing_journal`/`:journal_pending`).

---

## 5. RESSALVA DE HONESTIDADE

O backup restaurado do legado tinha a contabilidade CONFIGURADA APENAS COM DADOS DE TESTE
(`EventoMensagem` só CAMT.052/PACS.008, um único evento ativo "teste magno", plano/histórico/CC com
descrições de laboratório, nenhum evento de devolução). Logo, a superioridade do legado aqui é de
CAPACIDADE (modelo configurável rico, por CdMsg+tag, 2D+2C, CC por perna, bucket não-contabilizado),
não necessariamente de operação observada nesta instalação. Nossa cabine tem modelo mais simples e
hardcoded, com a contabilidade autoritativa deslocada para o Core.
