# Plano de implementação do ETL Autbank/monbank para o Core Monetarie

> **For Claude:** REQUIRED SUB-SKILL: usar superpowers:executing-plans para implementar tarefa a tarefa.

**Goal:** Migrar 100% dos dados reais da base `monbank_acc` (legado Autbank) para o Core Monetarie (SCD), com partida dobrada COSIF + TigerBeetle, batendo ao centavo, idempotente e re-executável.

**Architecture:** Mix task `Mix.Tasks.Monetarie.EtlAutbank` no Core, lendo a origem por conexão Postgrex somente leitura e gravando pelos contratos do próprio Core (`OpenAccount`, `Wallet`, `PostingEngine`). Casos duros vão para quarentena; uma porta de saída valida o batimento ao centavo. Detalhe e justificativa em `docs/plans/2026-06-25-etl-autbank-monbank-design.md`; base empírica em `etl/study/2026-06-25-monbank-acc-deep-study.md`.

**Tech Stack:** Elixir 1.19 / OTP 28, Ecto/Postgrex, Mix task, TigerBeetle, COSIF posting engine. Origem em `postgres:18` (container `monbank-etl-src`, `127.0.0.1:5544`).

**Convenções:** dinheiro como inteiro (centavos no COSIF, base_units=BRL×10.000 no TB), via `Monetarie.Util.MoneyUnit`. Sem PII em git (fixtures de teste são sintéticas; testes de integração leem o container vivo). Commits frequentes; pt-br humano sem travessão no relatório final.

---

## Fase 0 — Ambiente e arcabouço de teste

### Task 0.1: Subir o ambiente do Core no worktree
**Files:** nenhum (setup).
- Step 1: `cd core/backend && mix deps.get`. Esperado: deps baixadas, 0 erro.
- Step 2: `mix compile`. Esperado: compila (warnings ok), 0 erro.
- Step 3: Subir Postgres de teste do Core e TigerBeetle de teste (ver `core/backend/README`/`docker-compose` do Core ou `config/test.exs`). Criar DB: `MIX_ENV=test mix ecto.create && mix ecto.migrate`.
- Step 4: `mix test --max-failures 1` para estabelecer baseline verde do Core. Esperado: suíte passa (registrar contagem). Se algo já falha sem nossa mudança, reportar e decidir com o dono antes de seguir.
- Step 5: Confirmar origem viva: `docker exec monbank-etl-src psql -U postgres -d monbank_acc -tAc "select count(*) from accounts"` == 1582.

### Task 0.2: Conexão de leitura à origem (read-only Repo)
**Files:**
- Create: `core/backend/lib/monetarie/etl/autbank/source_repo.ex` (Ecto.Repo dedicado, read-only, lido de `AUTBANK_SOURCE_URL`).
- Create: `core/backend/test/monetarie/etl/autbank/source_repo_test.exs`
- Modify: `config/runtime.exs` (registrar o repo só quando `AUTBANK_SOURCE_URL` setado; NÃO subir no boot normal).
- Step 1 (test): teste de integração que, dado `AUTBANK_SOURCE_URL` apontando ao container, faz `SourceRepo.query!("select count(*) from accounts")` e espera 1582. Marcar `@tag :autbank_source` (só roda com a env setada).
- Step 2: rodar, falha (repo inexistente).
- Step 3: implementar o repo read-only (`read_only: true`, pool pequeno, start manual via `SourceRepo.start_link` na task, não na árvore de supervisão padrão).
- Step 4: rodar com env, passa.
- Step 5: commit.

## Fase 1 — Conversão de dinheiro (pura, fundação do centavo)

### Task 1.1: Conversor origem→destino
**Files:**
- Create: `core/backend/lib/monetarie/etl/autbank/money.ex`
- Create: `core/backend/test/monetarie/etl/autbank/money_test.exs`
- Step 1 (test): 
```elixir
test "numeric(10,2) reais -> centavos e base_units exatos" do
  assert Money.to_centavos(Decimal.new("741144.73")) == 74_114_473
  assert Money.to_base_units(Decimal.new("741144.73")) == 7_411_447_300
  assert Money.to_centavos(Decimal.new("0.01")) == 1
  assert Money.to_centavos(Decimal.new("-8.02")) == -802
  assert Money.to_centavos(Decimal.new("2999975.68")) == 299_997_568
end
```
- Step 2: rodar, falha.
- Step 3: implementar com `Decimal` (sem float): `to_centavos = Decimal.mult(d,100) |> Decimal.round(0) |> to_integer`; `to_base_units = centavos * 100`. Validar via `MoneyUnit` onde aplicável.
- Step 4: passa.
- Step 5: property test: para 1.000 valores reais amostrados da origem (`select amount from transactions limit 1000`), `to_base_units(x) == to_centavos(x)*100` e round-trip não perde. Commit.

## Fase 2 — Camada de leitura da origem (queries tipadas)

### Task 2.1: Leitura de titulares/contas/carteiras (1:1)
**Files:** `lib/monetarie/etl/autbank/source.ex` + teste `@tag :autbank_source`.
- Funções: `stream_holders/0` (join account_information + accounts + account_wallets + users + addresses por account_information_id), `wallet_balances/0`, `quarantine_wallets/0` (os 9 casos duros, por consulta determinística).
- Teste: `stream_holders |> Enum.count == 1582`; `quarantine_wallets |> Enum.count == 9` (4 resets + 2 negativas + 3 bloqueios). Implementar, passar, commit.

### Task 2.2: Leitura do razão por carteira (ordenado)
- Função `stream_ledger(wallet_id)` retornando `transactions` da carteira em ordem `created_at, id`, já com `reference_type`, `display_name`, e, quando Transfer, o join em `transfer_transactions` (history_code, details). Teste com uma carteira-fixture real (id do estudo): contagem e ordem. Commit.

## Fase 3 — Mapa COSIF (event_type) e cobertura de template

### Task 3.1: Classificador de evento (history_code/display_name → event_type)
**Files:** `lib/monetarie/etl/autbank/event_map.ex` + teste unitário (puro, sem DB).
- Step 1 (test): tabela de casos reais do estudo:
```
{"01000", :credit} -> "PIX_RECEIVE"
{"01012", :debit}  -> "PIX_SEND"
{"00732", :credit} -> "PAG_TED_RECEIVE"
{"00724", :debit}  -> "PAG_TED_SEND"
{"00708", :credit} -> "INTERNAL_TRANSFER_CREDIT"
{"00709", :debit}  -> "INTERNAL_TRANSFER_DEBIT"
{"01002", :credit} -> "PIX_RETURN"
{"00505", :credit} -> "TED_RETURN"
{:legacy, display_name} -> derivar (Tarifa->*_FEE, IOF->IOF_COLLECTION, Juros->INTEREST_*, etc.)
```
- Regra dura: entrada não mapeada retorna `{:error, :unmapped}` (NUNCA chuta) -> a conta vai para o relatório de exceções.
- Step 2-4: implementar tabela determinística, passar.
- Step 5: commit.

### Task 3.2: Templates COSIF faltantes (GATE contábil)
**Files:** `priv/repo/seeds/03x_autbank_posting_templates.exs` (novos templates).
- Novos: `INTERNAL_TRANSFER_CREDIT/DEBIT`, `TED_RETURN`, `OPENING_BALANCE`, e confirmar `INTEREST_*`, `IOF_COLLECTION`, tarifas TED/manutenção.
- BLOQUEIO: cada par débito/crédito novo precisa de confirmação contábil do dono antes do `--apply` (registrar no relatório de pendências). Test: `PostingEngine.validate_template/1` == :ok para todos os event_types exigidos pela origem.

### Task 3.3: Verificação de cobertura
- Função `EventMap.required_event_types(source)` (varre a origem) e teste que cruza com `cosif_posting_templates`: 0 faltando. Se faltar, lista. Commit.

## Fase 4 — Criação de cliente + conta + carteira (Fase 1 do design)

### Task 4.1: Builder de params do OpenAccount a partir do holder
**Files:** `lib/monetarie/etl/autbank/loader/customer.ex` + teste.
- De-para: person_type 1/2 -> user_type member_pf/pj; document -> tax_id; status ENC/ATI/BLQ -> Account.status (fechada/ativa/bloqueada); data.autbank.data_cadastro -> data de abertura; password Argon2id portado; numeração nova Monetarie (não carregar agência 19/DV legado); cod_cliente/legado -> referência externa/metadata.
- Teste com holder-fixture sintético: params corretos; PF exige campos PF; PJ idem. Commit.

### Task 4.2: Persistência via OpenAccount + Member (transacional)
- Usar `OpenAccount.open_account/2`; criar `Member` (KYC) no mesmo `Repo.transaction`. Dedupe por document (32 repetidos -> 1 cliente, N contas). Teste de integração (test DB do Core): 1 holder PF e 1 PJ criam User+Member+Account+wallet TB; idempotente (rodar 2x não duplica). Commit.

### Task 4.3: Carga de todos os 1.582 (dry-run primeiro)
- Orquestrar stream; `--dry-run` valida sem gravar; `--apply` grava por conta (commit por conta). Teste: dry-run conta 1582 e 0 escrita. Commit.

## Fase 5 — Saldo de abertura (Fase 2 do design)

### Task 5.1: Lançamento de abertura por carteira
**Files:** `lib/monetarie/etl/autbank/loader/opening.ex` + teste.
- `wallet.balance` (autoridade) -> evento `OPENING_BALANCE` (entry_date = abertura), preservando `available + blocked` (bloqueio como sub-saldo). Carteiras em quarentena NÃO recebem abertura.
- Teste: carteira com saldo R$X gera abertura que põe saldo TB == X (base_units); carteira em quarentena é pulada. Commit.

## Fase 6 — Replay do razão (Fase 3 do design)

### Task 6.1: Replay de um movimento (Transfer e Legacy)
**Files:** `lib/monetarie/etl/autbank/loader/ledger.ex` + teste.
- Para cada movimento: `EventMap` -> `PostingEngine.post_event(event_type, centavos, reference_id, reference_type, entry_date: created_at, tigerbeetle_transfer_id:)`. reference_id = id do movimento (idempotência). Legacy: metadados degradados, posição cronológica.
- Teste: um movimento PIX in credita a carteira; reexecução não duplica (on_conflict). Commit.

### Task 6.2: Replay completo por carteira (ordem + continuidade)
- Replay de uma carteira-fixture real inteira; ao fim, saldo TB == `wallet.balance` da origem (para carteira que reconcilia). Teste mede ao centavo. Commit.

## Fase 7 — Backfill de saldo COSIF diário (Fase 4 do design)

### Task 7.1: Geração de `cosif_account_balances` por data
- Após replay, consolidar saldos COSIF por `entry_date`. Teste: balancete por dia fecha (débito==crédito); saldo de fechamento por conta confere. Commit.

## Fase 8 — Quarentena

### Task 8.1: Detecção e isolamento dos 9 casos duros
**Files:** `lib/monetarie/etl/autbank/quarantine.ex` + teste.
- Detecta (4 resets de R$1mi, 2 negativas, 3 bloqueios sem lastro); NÃO carrega sem `--include-quarantine=<id,...>`; escreve `etl/out/quarentena-<data>.csv` (gitignored) com motivo. Teste: os 9 ids são detectados e excluídos do load. Commit.

## Fase 9 — Batimento ao centavo (porta de saída)

### Task 9.1: Harness de reconciliação
**Files:** `lib/monetarie/etl/autbank/reconcile.ex` + teste.
- Valida: (1) saldo TB por conta == origem (exceto quarentena); (2) balancete COSIF fecha; (3) available+blocked==balance; (4) contagens (1582 contas, 1533 users, lançamentos esperados); (5) cobertura de template; (6) 0 órfãos.
- Retorna struct com cada batimento e diferença ao centavo; se qualquer != 0 (fora quarentena) -> `{:error, ...}` e a task aborta o `--apply`.
- Teste: cenário sintético que fecha -> :ok; cenário com 1 centavo de diferença -> :error com o valor. Commit.

## Fase 10 — Reset da entidade (D7)

### Task 10.1: `--reset-entity` protegido
**Files:** `lib/monetarie/etl/autbank/reset.ex` + teste.
- Apaga, transacional e logado, os dados da entidade SCD alvo (users/accounts/journal/TB/daily_summaries/cosif_balances). Guard: `ALLOW_ENTITY_RESET=true` + entity_id explícito; nunca toca outra entidade. Teste: reset apaga só a entidade alvo; sem a env, recusa. Commit.

## Fase 11 — Orquestração da Mix task

### Task 11.1: `Mix.Tasks.Monetarie.EtlAutbank`
**Files:** `lib/mix/tasks/monetarie.etl_autbank.ex` + teste de orquestração.
- Flags: `--dry-run|--apply`, `--only=customers|opening|ledger|backfill`, `--entity-ispb=46026562`, `--limit=N`, `--reset-entity`, `--include-quarantine=ids`, `--report=path`. Sequência: (reset?) -> customers -> opening -> ledger -> backfill -> reconcile -> report. Aborta em batimento != 0. Modelar em `SeedDictAccounts.run_release/1`. Commit.

## Fase 12 — Relatório final (pt-br humano, sem travessão)

### Task 12.1: Gerador de relatório
**Files:** `lib/monetarie/etl/autbank/report.ex` + teste.
- Markdown pt-br acentuado, escrita humana, sem travessão de IA. Seções: o que foi migrado (números), batimentos (saldo TB x origem, balancete COSIF, identidade) ao centavo, quarentena e motivos, pendências para o cliente (as 6 da Seção 11 do design), situação das contas e do seed (sintético destruído, substituído pelos reais). Teste: relatório contém as seções e os números do batimento; lint simples que rejeita o caractere travessão. Commit.

## Fase 13 — Execução de ponta a ponta + validação real

### Task 13.1: Dry-run completo contra a origem viva
- `mix monetarie.etl_autbank --dry-run --entity-ispb=46026562 --report=etl/out/dry-run.md`. Conferir: 1582 contas, 1533 users, cobertura de template 100% (ou lista de faltantes), quarentena = 9, contagem de lançamentos esperada. Sem escrita.

### Task 13.2: Apply em ambiente de teste/homolog + batimento
- Com confirmação contábil dos templates novos e do reset: `--reset-entity --apply`. Validar batimento ao centavo (saldo TB x origem; balancete COSIF). Gerar relatório final. Anexar evidências.

### Task 13.3: Atualizar memória e handoff
- Atualizar `etl/study/...`, `docs/handoff/2026-06-25-etl-autbank.md`, e a memória do projeto com o resultado.

---

## Notas de execução
- TDD estrito: teste falha -> mínimo -> passa -> commit. Sem pular o "ver falhar".
- Não commitar dump/PII (ver `etl/.gitignore`). Fixtures de teste são sintéticas; integração lê o container vivo via `@tag :autbank_source`.
- Gates que exigem o dono: templates COSIF novos (Fase 3.2), reset da entidade (Fase 10), e decisão dos casos de quarentena (Fase 8).
- Riscos: divergência TB↔PG se wallet criar e movimento falhar (mitigar com transação por conta + idempotência); volume (carteira com ~19.655 lançamentos) -> processar em stream, commit por conta.
