# CADOC 6209 — Reconstrução no leiaute oficial BACEN (2026-07-08)

Contexto: auditoria multi-agente contra o manual oficial (Decem v2.0.7, IN BCB
335/2022) provou que os dois caminhos "6209" existentes eram inválidos — módulo
`Cadoc6209` agregava **carteira de crédito** e o PGV remetia **CSV proprietário**
como documento 6209. Relatório: `docs/reports/2026-07-08-auditoria-cadoc-6209-vs-manual-bacen.md`.
Reconstrução completa aprovada pelo dono e validada viva no ambiente local.

## O que foi construído

### Camada de leiaute (pura, sem DB) — `core/backend/lib/monetarie/use_cases/regulatory/cadoc6209/`
- `formatter.ex` — pictures `9(n)`/`X(n)` (zeros à esquerda, espaço à direita,
  2 decimais implícitos em valores), datas AAAAMMDD/AAAAMM, conversão ISO 8859-1.
- `files/{conglome,usuremot,estatcrt,estatatm,transopa,opeintra,contatos,database}.ex`
  — os 8 arquivos posição a posição conforme seções 3.1–3.8 do manual, com
  domínios oficiais validados (Canal 01–08, Produto 01–10, Operação 01–10,
  Acesso ATM 01–04, Bandeira, Função ATM, UF) e remessa zerada (HEADER +
  contagem `00000000`).
- `package.ex` — `BACEN.ZIP` com exatamente os 8 TXT na raiz, Latin-1.
- `aggregator.ex` — agregação SQL do trimestre (grupos, nunca linhas):
  TRANSOPA por Canal×Produto com **ponta pagadora** (Pix recebido fora),
  `fee`→OPEINTRA 09, `internal` não-Pix→OPEINTRA 03 (Pix intra vai no 1201),
  USUREMOT com PF por titular e PJ por conta, empréstimos concedidos→produto 08.
  Canal de metadata `channel` (default configurável `:cadoc6209_default_channel`,
  padrão "03"). **Trava anti-sub-reporte:** se `cards`/`atm_terminals` ganharem
  linhas, a geração FALHA até implementarmos ESTATCRT/ESTATATM de verdade.

### Integração no pipeline CADOC padrão
- `use_cases/cadoc/generator_6209.ex` — mesmo idioma dos demais generators
  (find_or_create Submission → generating → gera → `Orchestrator.mark_generated`).
  `file_name` remetido = literalmente `BACEN.ZIP` (seção 4 item 1); path local
  único por trimestre em `:cadoc_output_dir`.
- `orchestrator.ex` — `"6209" => quarterly, deadline_days: 30` (aproximação do
  "último dia útil do mês subsequente"), entra no cron diário 07:00.
- `cadoc_controller.ex` — `generator_map["6209"]`; `show_obrigacao` devolve
  `files[]` (os 8 TXT extraídos do ZIP, Latin-1→UTF-8) em vez de `xml_content`;
  novo `GET /admin/cadoc/obrigacoes/:id/zip` (download). Endpoint legado
  `generate_6209` (crédito) removido.
- STA: rota já existia (`CADOC_6209` → `PCAD6209`/documento `6209`); o
  dispatcher envia o binário do ZIP em base64 pelo NATS
  `monetarie.sta.regulatory.upload` como qualquer CADOC.
- **Contatos institucionais** (CONTATOS.TXT): `system_settings` seção
  `cadoc6209`, chaves `diretor_{nome,telefone,email}`,
  `tecnico{1,2}_{nome,cargo,telefone,email}`, `email_institucional`.
  Sem eles a geração falha com mensagem acionável.

### UI (CadocReportsView)
- Botão olho do 6209 abre viewer com **abas por TXT** (monospace, `white-space:
  pre` preservando colunas fixas); botão "Baixar BACEN.ZIP" no dialog e na
  coluna Ações.

### Aposentadorias (impedem remessa errada)
- `use_cases/regulatory/cadoc6209/cadoc6209.ex` (crédito) + `cadoc6209_record.ex`
  (a tabela nem existia no banco) — REMOVIDOS.
- PGV inteiro removido: use case, generator CSV, schema, controller, rotas
  `/v1/regulatory/pgv/*`, whitelist `PGV` em RegulatoryFile/FileController,
  `generator_for("PGV")`, mapeamentos STA `PPGV001`/`PGV→6209`, cards nas UIs
  admin e backoffice, PgvView órfã. Tabela `pgv_records` permanece no banco
  (0 linhas, sem código).

## Validação
- **63 testes, 0 falhas** (formatter, 8 builders com exemplos literais do
  manual, package, gerador ponta a ponta com DB, controller, p-codes,
  regulatory_file). Suíte: `mix test test/monetarie/use_cases/cadoc/generator_6209_test.exs test/monetarie/use_cases/regulatory/cadoc6209/`.
- **Vivo (local, 2026Q2 com dados reais do ETL):** submission `gerado`,
  BACEN.ZIP 1.719 bytes com os 8 arquivos; TRANSOPA = 264 saques
  R$ 481.782.357,00 + 122 depósitos R$ 478.257.143,00 (canal 03);
  OPEINTRA 09 = 123 tarifas R$ 282.711,00; USUREMOT = 6 PF + 25 contas PJ
  (= 31 contas ativas do trimestre); DATABASE = 202606; CONGLOME/ESTATCRT/
  ESTATATM zerados. Valores conferidos ao centavo contra o Postgres.
- Teste local roda via `core/backend/Dockerfile.dev` + volumes
  `monetarie-test-{build,deps}`; containers locais do core precisam de
  `--security-opt seccomp=unconfined` (io_uring do TigerBeetle NIF).

## Code review (multi-agente, verificação adversarial) — 2026-07-08

17 achados confirmados; corrigidos antes do commit:
- **TEF** (type canônico de transferência interna) entrou no TRANSOPA produto
  03 e OPEINTRA operação 03 (só perna pagadora) — antes sumia em silêncio.
- **Ponta pagadora por whitelist** `direction == "outbound"` (antes `!= inbound`
  deixava passar linhas de sistema/`internal`/nulas).
- **USUREMOT** só conta operações INICIADAS (outbound, types de cliente,
  user_type member_*) — recebedor passivo e tarifas não contam mais.
- Filtro de types em SQL + `timeout: 5min` nas queries do aggregator.
- `Formatter.text/2`: NFC + sanitização de caracteres de controle (CRLF de
  cadastro corromperia o posicional); contatos validados a <= 50 chars.
- Generator: regeneração bloqueada quando status submitted/confirmed sem
  `tipo_remessa=S`; reference_date normalizada ao fim do trimestre; re-fetch
  pós-insert (corrida do on_conflict); ZIP versionado por hash (não sobrescreve
  artefato remetido); canal "atm"/desconhecido → default com warning (canal 06
  contradiria ESTATATM zerado).
- `:validation_results` incluído no plug RequirePermission :view.

Follow-ups de plataforma (padrão compartilhado com os outros CADOCs — mudar
uniforme, não só no 6209): Task.start→Oban com unique no dispatch; OutputStore
S3 durável (hoje /tmp local, efêmero no Fargate); bounds de trimestre em
horário civil de Brasília (hoje UTC em todos os agregadores); rewrite do
COUNT(DISTINCT) do USUREMOT com subquery materializada para volume de produção.

## Pendências para produção/HML
1. **ASPB009 (resposta do BCB)** — o conector STA não recebe/correlaciona o
   arquivo-resposta: (a) filtro `sistemas` do polling inbound não cobre ASPB;
   (b) parser ASPB009 inexistente; (c) evento `file.received` sem tracker_id.
   Alternativa pronta não-fiada: `Client.query_protocol` (§8.2.1). Status até
   `uploaded/processed` já flui pelos eventos existentes.
2. Prazo exato = último dia útil do mês subsequente (hoje: 30 dias corridos).
3. Cadastrar contatos REAIS em `system_settings` (os locais são placeholder).
4. USUREMOT/TRANSOPA: quando os canais gravarem `metadata.channel` (app/web),
   a separação por canal passa a valer automaticamente; hoje tudo cai no
   default 03.
5. Envio real ao STA/BCB não exercitado (sem STA no dev local).
6. Deploy HML: rebuild `core-api` + `core-admin-ui` + seed dos contatos.
