# Varredura de bugs de dinheiro no Core (auditoria exaustiva)

Data 2026-06-26. 15 controllers auditados, 177 campos de dinheiro, **94 bugs**.

## Causa-raiz

Os formatadores de moeda do front sao INCONSISTENTES (cada app tem varios `formatCurrency`): admin `lib/format.ts` divide por 100 (centavos), mas `shared/utils/moneyUnit.ts` divide por 10000 (subcentavos) e `formatCurrencyReais` por 1; banking/merchant idem, misturando ÷100 e ÷10000. O TigerBeetle e o account_entries guardam SUBCENTAVOS (BRLx10000). Quando um endpoint devolve subcentavos cru para um formatter ÷100, o valor aparece 100x maior.

## Resumo por tipo

| Tipo | Qtd | O que e |
|---|---|---|
| unit_100x | 37 | saldo do TB em subcentavos devolvido sem converter -> 100x na tela |
| empty_transactions | 27 | le a tabela `transactions` (vazia pos-migracao) p/ saldo/movimento/netting/tarifa |
| hardcoded_mock | 24 | valor de dinheiro chumbado no codigo (feature nao implementada) |
| other | 6 | diversos |

## Por app consumidor

| App | unit_100x | empty_transactions | hardcoded_mock | other |
|---|---|---|---|---|
| admin | 12 | 21 | 17 | 2 |
| banking | 7 | 2 | 1 | 4 |
| merchant | 5 | 1 | 2 | 0 |
| partner | 4 | 2 | 1 | 0 |
| unknown | 9 | 1 | 3 | 0 |

## Bugs por controller

| Controller | bugs |
|---|---|
| account_controller.ex | {'unit_100x': 6, 'hardcoded_mock': 1, 'empty_transactions': 2} |
| account_controller.ex:487-505) | {'unit_100x': 12, 'hardcoded_mock': 3, 'empty_transactions': 1} |
| accounts_controller.ex | {'unit_100x': 4, 'hardcoded_mock': 1, 'empty_transactions': 2} |
| bank_accounts_controller.ex | {'other': 1} |
| fees_controller.ex | {'empty_transactions': 4, 'hardcoded_mock': 4} |
| merchant_portal_controller.ex | {'hardcoded_mock': 2, 'empty_transactions': 1, 'unit_100x': 5} |
| merchants_controller.ex | {'unit_100x': 2, 'hardcoded_mock': 2} |
| netting_controller.ex | {'hardcoded_mock': 7, 'empty_transactions': 9, 'other': 1} |
| pix_controller.ex) | {'unit_100x': 1, 'other': 1} |
| subcentavos | {'hardcoded_mock': 4, 'empty_transactions': 1} |
| transactions_controller.ex | {'empty_transactions': 7, 'unit_100x': 1} |
| transfer_controller.ex | {'other': 3} |
| wallet_controller.ex | {'unit_100x': 6} |

## Detalhe completo (campo | fonte | unidade | bug | fix)

- **[unit_100x]** `account_controller.ex:487-505)` index — GET /api/v1/accounts campo=`balance` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: Convert before return: `balance: subcent_to_cents_signed(Map.get(tb_data, :balance, 0))` (div by 100) so admin's ÷100 formatter shows reais.
- **[unit_100x]** `account_controller.ex:487-505)` index — GET /api/v1/accounts campo=`credits_posted` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: Wrap in div(_,100): `credits_posted: div(Map.get(tb_data,:credits_posted,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` index — GET /api/v1/accounts campo=`debits_posted` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `debits_posted: div(Map.get(tb_data,:debits_posted,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` show — GET /api/v1/accounts/:id (+ GET /api/accounts/:id) campo=`balance` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `balance: subcent_to_cents_signed(Map.get(tb_data,:balance,0))`.
- **[unit_100x]** `account_controller.ex:487-505)` show — GET /api/v1/accounts/:id campo=`credits_posted` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `div(Map.get(tb_data,:credits_posted,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` show — GET /api/v1/accounts/:id campo=`debits_posted` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `div(Map.get(tb_data,:debits_posted,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` show — GET /api/v1/accounts/:id campo=`credits_pending` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `div(Map.get(tb_data,:credits_pending,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` show — GET /api/v1/accounts/:id campo=`debits_pending` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `div(Map.get(tb_data,:debits_pending,0),100)`.
- **[unit_100x]** `account_controller.ex:487-505)` close — POST /api/v1/accounts/:id/close (+ /api/accounts/:id campo=`error.balance` fonte=tigerbeetle unidade=subcentavos app=admin
  - fix: `balance: subcent_to_cents_signed(balance)` in the 422 error payload.
- **[unit_100x]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD: not routed to this module; li campo=`current_balance` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: `current_balance: subcent_to_cents_signed(bal)` (and remove dead route ambiguity — V2 controller is the live one).
- **[unit_100x]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD code) campo=`available_balance` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: `available_balance: subcent_to_cents_signed(bal)`.
- **[hardcoded_mock]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD code) campo=`blocked_amount` fonte=hardcoded unidade=unknown app=unknown
  - fix: Compute from TB pending (credits_pending/debits_pending) or holds source, in centavos; stop hardcoding 0.
- **[hardcoded_mock]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD code) campo=`overdraft_limit` fonte=hardcoded unidade=unknown app=unknown
  - fix: Source from overdraft facility (centavos) instead of literal 0.
- **[hardcoded_mock]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD code) campo=`overdraft_used` fonte=hardcoded unidade=unknown app=unknown
  - fix: Source from overdraft facility usage (centavos) instead of 0.
- **[unit_100x]** `account_controller.ex:487-505)` balance — def balance/2 (DEAD code) campo=`projected_balance` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: `projected_balance: subcent_to_cents_signed(bal)` plus real pending/overdraft projection.
- **[empty_transactions]** `account_controller.ex:487-505)` statement — def statement/2 (DEAD: live /:id/statement → Acc campo=`amount` fonte=transactions unidade=subcentavos app=unknown
  - fix: Read account_entries (amount +credit/-debit, subcentavos) for this account instead of the transactions table, and convert each amount via subcent_to_cents_signed before return. (Note this in-file stat
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id (and GET /api/wallets/:wallet campo=`balance` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: balance: subcent_to_cents_signed(account.credits_posted - account.debits_posted) before serialize (i.e. div by 100).
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id (and GET /api/wallets/:wallet campo=`credits_posted` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: credits_posted: div(account.credits_posted, 100) (subcent_to_cents) to emit centavos.
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id (and GET /api/wallets/:wallet campo=`debits_posted` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: debits_posted: div(account.debits_posted, 100) to emit centavos.
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id (and GET /api/wallets/:wallet campo=`credits_pending` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: credits_pending: div(account.credits_pending, 100) to emit centavos.
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id (and GET /api/wallets/:wallet campo=`debits_pending` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: debits_pending: div(account.debits_pending, 100) to emit centavos.
- **[unit_100x]** `wallet_controller.ex` GET /api/v1/wallets/:wallet_id/balance (and GET /api/wallets campo=`balance` fonte=tigerbeetle unidade=subcentavos app=unknown
  - fix: balance: subcent_to_cents_signed(balance) (div by 100) before returning, to honor the documented centavos contract.
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions (index) campo=`amount` fonte=transactions unidade=subcentavos app=admin
  - fix: This is the admin movement/statement list reading the near-empty transactions table (215 test rows; real movement = account_entries 244,984 rows) — should source from account_entries. SECONDARY/latent
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions (index) campo=`fee` fonte=transactions unidade=subcentavos app=admin
  - fix: Source fees from account_entries/real movement; and div(pt.fee_amount, 100) before return so admin's /100 formatter shows centavos, not 100x.
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions (index) campo=`netAmount` fonte=transactions unidade=subcentavos app=admin
  - fix: Source from account_entries; convert div(pt.net_amount, 100) before return (admin /100 -> else 100x).
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions/:id (show) campo=`amount` fonte=transactions unidade=subcentavos app=admin
  - fix: Detail of a transactions-table row (near-empty post-Autbank). Convert div(amount,100) before return; long-term back the detail with account_entries movement. Same applies to this endpoint's fee + netA
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions/:id (show) campo=`fee` fonte=transactions unidade=subcentavos app=admin
  - fix: div(pt.fee_amount,100) before return; source from real movement (account_entries).
- **[empty_transactions]** `transactions_controller.ex` GET /api/admin/transactions/:id (show) campo=`netAmount` fonte=transactions unidade=subcentavos app=admin
  - fix: div(pt.net_amount,100) before return; source from account_entries.
- **[unit_100x]** `transactions_controller.ex` POST /api/admin/transactions/:id/refund (refund) campo=`amount` fonte=transactions unidade=subcentavos app=admin
  - fix: Return `amount: div(original.amount, 100)` (subcentavos->centavos) so the admin /100 formatter renders the true value; keep the internally-stored refund row amount in subcentavos.
- **[empty_transactions]** `transactions_controller.ex` pix_transactions (action defined at :60-64, UNROUTED — not p campo=`amount/fee/netAmount` fonte=transactions unidade=subcentavos app=admin
  - fix: If wired up, inherits the index fixes: source from account_entries and div(_,100) before return. Otherwise remove the dead action.
- **[other]** `bank_accounts_controller.ex` (DEAD CODE - no caller, not in any JSON response) tx_entry_v campo=`amount (would-be)` fonte=transactions unidade=centavos app=admin
  - fix: Delete the dead tx_* helpers (lines 433-475) and the unused `Transaction` alias (line 16) to remove confusion; they ship nothing today.
- **[unit_100x]** `merchants_controller.ex` GET /admin/merchants (index/update via serialize_account/2); campo=`blocked` fonte=other unidade=subcentavos app=admin
  - fix: `blocked: div(blocked_amount(account.id), 100)` to mirror fetch_tigerbeetle_balance (subcentavos->centavos)
- **[hardcoded_mock]** `merchants_controller.ex` GET /admin/merchants/:id/limits (limits) -> serialize_accoun campo=`pixOut.dailyUsed / pixOut.monthlyUsed / pixIn.dailyUsed / pixIn.monthlyUsed` fonte=hardcoded unidade=subcentavos app=admin
  - fix: populate from real usage — pix_in/limit_check.ex:57-61 `usage/1` already returns {daily_used, monthly_used} (subcentavos) from account_daily_summaries; wire it instead of literal 0
- **[hardcoded_mock]** `merchants_controller.ex` GET /admin/merchants/:id/limits (limits) campo=`ted.{transactionLimit,dailyLimit,monthlyLimit}` fonte=hardcoded unidade=subcentavos app=admin
  - fix: source real TED limits (account columns / config) or drop the TED block until implemented
- **[unit_100x]** `merchants_controller.ex` GET /admin/merchants/:id/config (config); also PUT .../confi campo=`maxTransactionAmount, dailyLimit, monthlyLimit` fonte=other unidade=subcentavos app=admin
  - fix: convert subcentavos->centavos before return, e.g. `maxTransactionAmount: div(config.max_transaction_amount || 0, 100)` (and dailyLimit/monthlyLimit), aligning with the admin ÷100 formatter; keep consi
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts (index) + GET /a campo=`balance` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return the TB balance raw (it is already subcentavos): `balance: Map.get(tb_data, :balance, 0)` — drop MoneySerializer.to_base.
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:id (show) campo=`balance` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return raw TB balance: `Map.get(tb_data, :balance, 0)` without to_base.
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:id (show) campo=`creditsPosted` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return raw `Map.get(tb_data, :credits_posted, 0)` (already subcentavos).
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:id (show) campo=`debitsPosted` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return raw `Map.get(tb_data, :debits_posted, 0)`.
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:account_id/bala campo=`balance` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return raw subcentavos balance, drop to_base.
- **[unit_100x]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:account_id/bala campo=`available` fonte=tigerbeetle unidade=unknown app=banking
  - fix: Return raw TB balance for available; drop to_base.
- **[hardcoded_mock]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:account_id/bala campo=`blocked` fonte=hardcoded unidade=unknown app=banking
  - fix: Compute blocked funds (sum of pending holds / StaleHold) instead of hardcoded 0, or document as unsupported.
- **[empty_transactions]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:account_id/tran campo=`amount (internal ledger source)` fonte=transactions unidade=subcentavos app=banking
  - fix: Movement/statement should be sourced from account_entries (244,984 rows), not the empty transactions table; keep entries branch as the authoritative source.
- **[empty_transactions]** `account_controller.ex` GET /api/v2/merchants/:merchant_id/accounts/:account_id/tran campo=`amount (PIX/TED payment source)` fonte=transactions unidade=subcentavos app=banking
  - fix: Rely on account_entries for historical movement; transactions table holds only live in-flight rows.
- **[hardcoded_mock]** `merchant_portal_controller.ex` GET /api/balances (action balances) campo=`pending` fonte=hardcoded unidade=unknown app=merchant
  - fix: Derive pending from real held/pending entries (account_entries holds / TB pending balances) instead of hardcoding 0, or document as unsupported.
- **[hardcoded_mock]** `merchant_portal_controller.ex` GET /api/balances (action balances) campo=`blocked` fonte=hardcoded unidade=unknown app=merchant
  - fix: Derive blocked from real blocked/judicial-hold source (e.g. account_entries / holds) instead of hardcoding 0.
- **[empty_transactions]** `merchant_portal_controller.ex` GET /api/documents/entries|out|returns|transfers + GET /api/ campo=`amount` fonte=transactions unidade=centavos app=merchant
  - fix: Source document/statement movement from account_entries (amount is subcentavos -> convert via subcent_to_cents_signed/from_base) instead of the near-empty transactions table.
- **[unit_100x]** `merchant_portal_controller.ex` GET /api/fee-splits/received (received_fee_splits) — summary campo=`totalAmount` fonte=other unidade=subcentavos app=merchant
  - fix: Return `MoneySerializer.from_base(total_amount)` (subcentavos -> centavos) in the summary.
- **[unit_100x]** `merchant_portal_controller.ex` GET /api/fee-splits/received (received_fee_splits) — summary campo=`avgAmount` fonte=other unidade=subcentavos app=merchant
  - fix: Return `MoneySerializer.from_base(avg_amount)` (convert subcentavos -> centavos).
- **[unit_100x]** `merchant_portal_controller.ex` GET /api/fee-splits/received (received_fee_splits) — rows vi campo=`amount` fonte=other unidade=subcentavos app=merchant
  - fix: Return `MoneySerializer.from_base(row.amount)` (subcentavos -> centavos).
- **[unit_100x]** `merchant_portal_controller.ex` GET /api/fee-splits/received (received_fee_splits) — rows vi campo=`totalFee` fonte=other unidade=subcentavos app=merchant
  - fix: Return `MoneySerializer.from_base(row.total_fee || row.amount)`.
- **[unit_100x]** `merchant_portal_controller.ex` GET /api/fee-splits/received (received_fee_splits) — rows vi campo=`calcValue` fonte=other unidade=subcentavos app=merchant
  - fix: When calc_type == "fixed", return `MoneySerializer.from_base(row.calc_value)` (centavos); leave percent rates unscaled.
- **[other]** `transfer_controller.ex` POST /api/v2/merchants/:merchant_id/transfers (action :creat campo=`amount` fonte=other unidade=subcentavos app=banking
  - fix: validate_amount/1 must convert the centavos input with Monetarie.Util.MoneyUnit.from_cents (x100) like create_pix_account, and return the resulting base units raw (drop the extra to_base on the respon
- **[other]** `transfer_controller.ex` POST /api/v2/transfers/pix (action :create_pix; also reachab campo=`amount` fonte=other unidade=subcentavos app=banking
  - fix: Use MoneyUnit.from_cents on the centavos input (like create_pix_account) and return raw base units; do not from_base then to_base. Aligns internal + NATS + response units.
- **[other]** `transfer_controller.ex` POST /api/v2/transfers/ted (action :create_ted) campo=`amount` fonte=other unidade=subcentavos app=banking
  - fix: Convert the centavos input with MoneyUnit.from_cents and return raw base units, matching create_pix_account; remove the from_base/to_base inversion.
- **[unit_100x]** `pix_controller.ex)` GET /api/v2/merchants/:merchant_id/transactions/:transaction campo=`amount` fonte=transactions unidade=subcentavos app=banking
  - fix: Convert at the boundary: `amount: Monetarie.Util.MoneyUnit.to_cents(tx.amount)` (subcentavos->centavos via div(_,100)); and normalize writes so return_pix also stores subcentavos.
- **[other]** `pix_controller.ex)` POST /api/v2/merchants/:merchant_id/pix/qrcode (action gener campo=`brcode (EMV tag 54 embedded transaction amount)` fonte=hardcoded unidade=centavos app=banking
  - fix: Replace the stub with a real BR Code builder: EMV tag 54 = amount in reais 2dp (e.g. `:erlang.float_to_binary(amount/100, decimals: 2)`), correct 2-digit length prefixes per field, and a computed CRC1
- **[unit_100x]** `accounts_controller.ex` GET /accounts/:id/balance (balance/2) campo=`balance` fonte=tigerbeetle unidade=unknown app=partner
  - fix: Remove the to_base/1 call — the TB value is already base_units/subcentavos; return `Map.get(tb, :balance, 0)` directly (or div by 100 to centavos if the partner FE divides by 100, matching the fixed a
- **[unit_100x]** `accounts_controller.ex` GET /accounts/:id/balance (balance/2) campo=`available` fonte=tigerbeetle unidade=unknown app=partner
  - fix: Remove to_base/1 (value already base_units); and compute available = balance - blocked instead of aliasing balance.
- **[hardcoded_mock]** `accounts_controller.ex` GET /accounts/:id/balance (balance/2) campo=`blocked` fonte=hardcoded unidade=unknown app=partner
  - fix: Derive blocked from real held/pending funds (e.g. TigerBeetle pending balance or active holds) instead of hardcoding 0; or remove the field if unsupported.
- **[empty_transactions]** `accounts_controller.ex` GET /accounts/:id/statement (statement/2 → render_transactio campo=`amount` fonte=transactions unidade=unknown app=partner
  - fix: Do not source statement movements from the near-empty transactions table; rely on the account_entries branch (already merged at line 240-241). If kept, drop to_base/1 since tx.amount is already base_u
- **[unit_100x]** `accounts_controller.ex` GET /accounts/:id/statement (statement/2 → render_account_en campo=`amount` fonte=account_entries unidade=unknown app=partner
  - fix: Remove to_base/1 — abs(e.amount) is already base_units/subcentavos; return it directly (or div by 100 to centavos to match the fixed admin controllers' contract).
- **[empty_transactions]** `accounts_controller.ex` GET /accounts/:id/statement/:entry_id/receipt (receipt/2 → r campo=`amount` fonte=transactions unidade=unknown app=partner
  - fix: Resolve receipts for movements from account_entries (the real source), not the near-empty transactions table; and drop to_base/1 since tx.amount is already base_units.
- **[unit_100x]** `accounts_controller.ex` GET /accounts/:id/statement/:entry_id/receipt (receipt/2 → r campo=`amount` fonte=account_entries unidade=unknown app=partner
  - fix: Remove to_base/1 — abs(e.amount) is already base_units/subcentavos; return directly (or div by 100 to centavos).
- **[hardcoded_mock]** `subcentavos` GET /api/admin/dashboard/stats campo=`top_kpis.capital_social.delta_month` fonte=hardcoded unidade=unknown app=admin
  - fix: Compute month-over-month COSIF group-6 delta (current ref vs prior month balance) instead of hardcoding 0.
- **[empty_transactions]** `subcentavos` GET /api/admin/dashboard/stats campo=`sections.operacoes_dia.transactions_today.value` fonte=transactions unidade=centavos app=admin
  - fix: Source today's count/volume from account_entries (e.g. sum of |amount| or credit legs where inserted_at::date=today, /100 to centavos); transactions table is near-empty after the Autbank migration.
- **[hardcoded_mock]** `subcentavos` GET /api/admin/dashboard/stats campo=`sections.operacoes_dia.pix_in.value` fonte=hardcoded unidade=unknown app=admin
  - fix: Populate from account_entries/transactions filtered by PIX-in kind for today, or omit until kind mapping is finalized (comment l.545-547 acknowledges it).
- **[hardcoded_mock]** `subcentavos` GET /api/admin/dashboard/stats campo=`sections.operacoes_dia.pix_out.value` fonte=hardcoded unidade=unknown app=admin
  - fix: Populate from real PIX-out movement for today or omit until kind mapping ready.
- **[hardcoded_mock]** `subcentavos` GET /api/admin/dashboard/stats campo=`sections.operacoes_dia.boletos.value` fonte=hardcoded unidade=unknown app=admin
  - fix: Populate from boleto movement for today or omit until kind mapping ready.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles (action :index) campo=`data[].total_credit` fonte=hardcoded unidade=centavos app=admin
  - fix: Replace generate_cycles() random data with a real query of settled SPI/STR/NPC cycle totals (e.g. account_entries or cosif_journal_entries aggregated per rail/day) instead of :crypto.strong_rand_bytes
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles (action :index) campo=`data[].total_debit` fonte=hardcoded unidade=centavos app=admin
  - fix: Source real per-rail debit totals from a ledger table; remove the strong_rand_bytes mock.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles (action :index) campo=`data[].net_position` fonte=hardcoded unidade=centavos app=admin
  - fix: Compute net_position from real credit/debit totals once those are sourced from the ledger.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles/:id (action :show) campo=`data.total_credit` fonte=hardcoded unidade=centavos app=admin
  - fix: Same as index: back generate_cycles() with real settled-cycle ledger data.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles/:id (action :show) campo=`data.total_debit` fonte=hardcoded unidade=centavos app=admin
  - fix: Same as index.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles/:id (action :show) campo=`data.net_position` fonte=hardcoded unidade=centavos app=admin
  - fix: Same as index.
- **[hardcoded_mock]** `netting_controller.ex` GET /api/v1/netting/cycles/:id (action :show) campo=`data.transactions[].amount` fonte=hardcoded unidade=centavos app=admin
  - fix: Derive these settlement-leg amounts from real cycle data; they currently echo a fabricated net_position.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.pix_credit` fonte=transactions unidade=subcentavos app=admin
  - fix: Aggregate per-rail credit from account_entries (the real extrato source) with subcent_to_cents_signed(div by 100) before return; do not read the near-empty transactions table.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.pix_debit` fonte=transactions unidade=subcentavos app=admin
  - fix: Source from account_entries debits and convert subcentavos->centavos before return.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.pix_net` fonte=transactions unidade=subcentavos app=admin
  - fix: Recompute from account_entries-derived credit/debit; convert to centavos.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.ted_credit` fonte=transactions unidade=subcentavos app=admin
  - fix: Source TED credit from account_entries; convert subcentavos->centavos.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.ted_debit` fonte=transactions unidade=subcentavos app=admin
  - fix: Source from account_entries; convert before return.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.ted_net` fonte=transactions unidade=subcentavos app=admin
  - fix: Recompute from account_entries-derived values; convert to centavos.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.boleto_credit` fonte=transactions unidade=subcentavos app=admin
  - fix: Source from account_entries boleto credits; convert subcentavos->centavos.
- **[other]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.boleto_debit` fonte=hardcoded unidade=centavos app=admin
  - fix: Leave 0 only if outbound boleto is intentionally unmodeled; otherwise source real boleto debit from account_entries.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.boleto_net` fonte=transactions unidade=subcentavos app=admin
  - fix: Recompute from account_entries-derived boleto credit minus any real debit; convert to centavos.
- **[empty_transactions]** `netting_controller.ex` GET /api/v1/netting/position (action :position) campo=`data.net_total` fonte=transactions unidade=subcentavos app=admin
  - fix: Compute net_total from account_entries per-rail aggregates and apply subcent_to_cents_signed (div by 100) before return; replace query_position_from_db transactions source with account_entries.
- **[empty_transactions]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, transactions path via qu campo=`pix_fees` fonte=transactions unidade=subcentavos app=admin
  - fix: Compute PIX fee revenue from account_entries (the real movement source) instead of the near-empty transactions table, and convert subcentavos->centavos via div(sum,100)/subcent_to_cents_signed before 
- **[empty_transactions]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, transactions path via qu campo=`ted_fees` fonte=transactions unidade=subcentavos app=admin
  - fix: Source TED fee revenue from account_entries and convert subcentavos->centavos (div by 100) before returning.
- **[empty_transactions]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, transactions path via qu campo=`boleto_fees` fonte=transactions unidade=subcentavos app=admin
  - fix: Source boleto fee revenue from account_entries and convert subcentavos->centavos (div by 100) before returning.
- **[empty_transactions]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, transactions path via qu campo=`total_revenue` fonte=transactions unidade=subcentavos app=admin
  - fix: Aggregate revenue from account_entries-derived fee figures and convert subcentavos->centavos before returning.
- **[hardcoded_mock]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, sample_data fallback pat campo=`pix_fees` fonte=hardcoded unidade=centavos app=admin
  - fix: Remove the synthetic sample/random fee revenue fallback; on query failure return an explicit error or zeroed report rather than fabricated centavos figures.
- **[hardcoded_mock]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, sample_data fallback pat campo=`ted_fees` fonte=hardcoded unidade=centavos app=admin
  - fix: Remove synthetic TED fee sample fallback; do not return fabricated values.
- **[hardcoded_mock]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, sample_data fallback pat campo=`boleto_fees` fonte=hardcoded unidade=centavos app=admin
  - fix: Remove synthetic boleto fee sample fallback; do not return fabricated values.
- **[hardcoded_mock]** `fees_controller.ex` GET /api/v1/fees/reports (:reports, sample_data fallback pat campo=`total_revenue` fonte=hardcoded unidade=centavos app=admin
  - fix: Remove the sample fallback entirely; do not synthesize revenue totals.