# ETL Autbank — Re-migração completa (backup 2026-06-26) Implementation Plan

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

**Goal:** Re-migrar a entidade Autbank no Core a partir do backup completo `monbank_backup_2026-06-26.sql`, usando `autbank_legacy_transactions` (histórico completo com saldo corrido) como fonte do movimento, reconciliando cada conta ao centavo contra `balance_after`, sem plug de abertura.

**Architecture:** Carregar o backup numa origem Postgres acessível à task `core-api` (DB temporário no cluster Aurora `monetarie-core-homolog-45` OU container Docker), apontar `AUTBANK_SOURCE_URL`. Estender o ETL (`core/backend/lib/monetarie/etl/autbank/*`, em `origin/main`) com um leitor de `autbank_legacy_transactions` (stream por conta, ordenado por `posted_at`). Cada lançamento vira: (1) linha `account_entries` (subcentavos) e (2) par `cosif_journal_entries` (centavos) pelo mapa evento→COSIF já validado, classificando por `history_code`/`description`. Reset da entidade + apply + reconciliação `account_entries` cumulativo == `balance_after` do último lançamento por conta.

**Tech Stack:** Elixir/Ecto (ETL mix task), Postgrex (origem read-only), TigerBeetle, COSIF posting, psql/pg_restore.

**Fonte de verdade do mapa COSIF:** `docs/plans/2026-06-26-etl-autbank-monbank-design.md` + plano de contas `Plano_de_Contas_Interno_SCD.md`. Disponibilidades `1.1.2.10.01.10.001`, Obrigações `4.9.8.10.01.10.002`, Tarifas `7.1.7.10.01.10.001/002`, IOF a recolher `4.9.1.10.02.10.001/002`, Juros `7.1.1.10.01.10.213`.

---

## RECONCILIAÇÃO CRÍTICA (provada 2026-06-26, fonte carregada em mon_core.autbank_src)

Três totais do MESMO backup novo:
- `account_wallets.balance` (AUTORIDADE) = **R$ 961.178,02** (73 contas ≠ 0; balance_available −R$6.491,66; blocked_amount R$ 967.669,68 — quase tudo bloqueado).
- `balance_after` reconstruído do histórico (258.959 tx, 291 contas) = **R$ 457.454,16** — INCOMPLETO.
- Core atual (migração do backup ANTIGO) = R$ 741.158,98.

**Veredito:** NÃO é inconsistência. Das 291 contas COM histórico, **288 batem exato** (wallet == balance_after, diff total R$4.142,41). As **57 contas com saldo e SEM histórico** seguram a diferença (~R$503k). Logo:
- AUTORIDADE do saldo = `account_wallets.balance` (= R$ 961.178,02), NÃO o balance_after.
- Re-migração: usar wallet.balance como saldo final/abertura + replay do histórico onde existe (288 reconciliam sozinhas; 57 sem histórico + 3 quase-batem recebem abertura pela diferença).
- O novo backup é MAIS recente/completo que o antigo (R$741k→R$961k): histórico real p/ 291 contas, +33 contas, +9 usuários.
- ⚠️ GOTCHA: as 33 contas novas precisam de carteira TigerBeetle criada (OpenAccount) — SQL puro NÃO cria wallet TB; exige o mix task do ETL (ou chamada a Wallet.create).

## INVESTIGAÇÃO DO BLOQUEIO (provada 2026-06-26) — é ARTEFATO, não judicial

- Invariante `balance = balance_available + blocked_amount` vale p/ as 1.606 carteiras.
- Só 6 carteiras têm `blocked>0`. Padrão: POOLMAK balance R$1,57 / available −R$442.245 / blocked R$442.246; LIDER balance R$0 / available −R$138.819 / blocked R$138.819 → o "blocked" apenas COMPENSA um available NEGATIVO; saldo real ≈ 0. KAY CASS é o único com balance=blocked=R$373.446,51 (dinheiro real, confirmado pelo histórico, só marcado 100% blocked).
- **Veredito:** `blocked_amount` é ruído contábil (offset de available negativo), NÃO bloqueio judicial/MED. Migrar o campo **`balance`** (autoridade, R$961.178,02). NÃO replicar o split available/blocked (geraria available negativo/lixo). Igual à migração original.

## Premissas validadas (backup 2026-06-26)

- `accounts` 1.606, `users` 1.542, `account_wallets` 1.606 (mais que a migração atual: +24 contas, +9 usuários).
- `autbank_legacy_transactions` 258.959 linhas: `type` (debit|credit, CHECK), `amount numeric(15,2)` em REAIS, `posted_at`, `description`, `history_code`, `balance_before`/`balance_after` numeric(15,2) (saldo corrido por conta), `account_id`/`account_number`.
- As 3 tabelas `autbank_*_import_items`/`autbank_migration_batches` são ferramenta de import do Autbank, NÃO entram no Core.
- Unidade: account_entries em subcentavos (reais×10000); cosif em centavos (reais×100). `amount` reais → subcent = ×10000; → centavos = ×100.

## Fase 0 — Origem acessível ao ETL

### Task 0.1: Carregar o backup numa origem Postgres
- Opção A (preferida, AWS): criar DB temporário `monbank_src` no cluster Aurora; `pg_restore` o backup; `AUTBANK_SOURCE_URL` aponta para ele (a task core-api alcança). Dropar ao fim.
- Opção B: subir Docker `postgres:18` (requer OrbStack/Docker ligado pelo dono via `! open -a OrbStack`).
- Validar: `select count(*) from autbank_legacy_transactions` == 258959.

## Fase 1 — Leitor da origem (autbank_legacy_transactions)

### Task 1.1: `Source.stream_legacy_ledger/0`
- Modify: `core/backend/lib/monetarie/etl/autbank/source.ex`
- Função que faz stream de `autbank_legacy_transactions` por `account_number`, ordenado `posted_at, id`, projetando `{account_number, type, amount, posted_at, description, history_code, balance_after}`.
- Teste `@tag :autbank_source`: contagem == 258959; ordenação por conta+data; `sum(amount) por conta` confere com `max(balance_after) - opening implícito`.

## Fase 2 — Classificador COSIF por history_code/description

### Task 2.1: `EventMap.cosif_for/1`
- Modify: `core/backend/lib/monetarie/etl/autbank/event_map.ex`
- Mapa determinístico `{type, history_code, description} -> {debit_code, credit_code}` (entrada não mapeada => `:error, :unmapped`, NUNCA chuta):
  - credit (entrada): Débito `1.1.2.10.01.10.001` / Crédito `4.9.8.10.01.10.002`.
  - debit transfer/pagamento/TED: Débito `4.9.8...002` / Crédito `1.1.2...001`.
  - debit Tarifa: Débito `4.9.8...002` / Crédito `7.1.7.10.01.10.001` (cadastro→`...002`).
  - debit IOF Adicional: Crédito `4.9.1.10.02.10.002`; IOF (op. crédito): `4.9.1.10.02.10.001`.
  - debit Juros: Crédito `7.1.1.10.01.10.213`.
- Teste unitário com a tabela de `history_code`/descrições reais (extrair distintos da origem).

## Fase 3 — Loader de movimento + dupla escrituração

### Task 3.1: `Loader.Ledger.replay_account/1`
- Create: `core/backend/lib/monetarie/etl/autbank/loader/ledger.ex`
- Para cada conta: percorre legacy_transactions em ordem; para cada um:
  - `account_entries`: amount subcentavos (= reais×10000, sinal: credit +, debit −), entry_date=posted_at::date, description, category (transfer|fee|interest), reference=legacy_transaction_id (idempotência), status confirmed.
  - `cosif_journal_entries`: amount centavos (= reais×100), debit/credit pelo `EventMap`, entry_date=posted_at::date.
- Idempotente por `reference`/`reference_id`.
- Teste de integração: 1 conta real reconcilia `cumulative account_entries == balance_after` (último), e cosif balanceado.

## Fase 4 — Clientes/contas (delta + novos)

### Task 4.1: re-usar `Loader.Customer`/`Member` com o backup novo
- Os 1.542 usuários + 1.606 contas via `OpenAccount`+`Member` (dedupe por documento). Status ENC/ATI/BLQ.

## Fase 5 — Reset + apply + reconciliação

### Task 5.1: `--reset-entity --apply` com a nova origem
- `reset.ex` apaga a entidade SCD (users/accounts/account_entries/cosif_journal_entries/TB/snapshots), `ALLOW_ENTITY_RESET=true`.
- Orquestração: reset → customers → ledger replay → cosif snapshots (mensal 2024..2026) → reconcile.

### Task 5.2: Porta de saída (reconcile)
- Por conta: `sum(account_entries)/10000 == balance_after` do último legacy (ao centavo). 0 divergência fora quarentena.
- COSIF: Obrigações `4.9.8...002` == soma dos saldos dos clientes; balancete fecha (débito==crédito) por mês; A = P + Resultado.

## Fase 6 — Validação viva + relatório

### Task 6.1: prints e API
- dashboard-stats, balancete mês a mês, extrato de uma conta com balance_after batendo, lista de clientes (1.542). Relatório pt-br humano sem travessão.

---

## Notas
- O ETL vive em `origin/main` (`75ca3376`); trabalhar a partir de lá (ou portar os módulos `etl/autbank/*`). A branch atual `fix/core-etl-visibility` é off `9e98a0aa` (sem o ETL).
- Operação no dado VIVO de homolog: reset + re-apply é destrutivo na entidade; confirmar com o dono antes do `--apply`. Reversível só por re-rodar.
- Cabine PIX (mon_pix) intacta (DB separado).
