# Fechamento: comprovante com dados do titular + triagem das 66 falhas da cabine + cache de login do CabinStatusLookup

Data: 2026-07-15 (noite). Sessão dedicada aos 3 avisos de `docs/handoff/2026-07-15-avisos-proxima-sessao-extrato-comprovante.md`, na ordem pedida pelo dono (3, 1, 2 do documento original: comprovante primeiro).

Branch: `feat/coreadmin-extrato-comprovante` (worktree), 4 commits locais sobre `1115eb01` (= origin/main), SEM push (aguardando autorização):

| commit | o quê |
|---|---|
| `4ac971e6` | fix(core): comprovante resolve o titular pelo cadastro e mescla metadata por campo |
| `98681e83` | perf(core): CabinStatusLookup cacheia o login por execução com backoff |
| `14275945` | fix(pix): migrations de holidays/ownership_validations sobrevivem ao residual legado do squash |
| `2df1c875` | fix(core): recebedor de transferência interna resolve pelo metadata quando a coluna aponta o id de trânsito |

## 1. Comprovante da M2 (aviso 3) — diagnóstico REAL difere da hipótese do handoff

A hipótese registrada ("party parcial com payer_ispb bloqueia o fallback") foi REFUTADA para o caso concreto: a forma real da transação da M2, copiada de PROD por rpc read-only, NÃO tem nenhuma chave `payer_*`/`recipient_*` no metadata:

```
transaction_id  PIXMANE46026562202607151443g2y2zizu2ig
merchant_id     NULL
to_account_id   20   (conta INEXISTENTE — id de trânsito do money path)
metadata        {source: pix_manual_register, spi_status: settled, spi_status_id: 4,
                 spi_settled_at, tb_transfer_id, settlement_source: spi_pacs002,
                 spi_status_origin: BACEN, pix_transaction_id: 13663}
```

Reproduzido o payload do comprovante DENTRO da task de PROD (mesmo código deployado, dado real) e a TELA no coreadmin de PROD (Playwright, read-only): o comprovante NÃO estava vazio — Descrição "PIX enviado", Chave PIX e Tipo CPF preenchidos, e o pagador saía com Agência/Conta corretas porém com **Nome = "36588881000150" (o LOGIN cru do usuário, que no acervo é o próprio CNPJ)** e sem CPF/CNPJ. O dono flagrou exatamente isso ("nome um CNPJ cara?"). Os defeitos reais, todos corrigidos com TDD sobre a forma real do dado:

1. `get_holder_name` ignorava `users.name` (existente e correto no acervo) e devolvia `login`. Agora prefere `users.name`; `login`/`tax_id` viram fallback.
2. O lookup de `Member` só rodava via `merchant_id` do chamador — NULL em todo o acervo do envio manual. Agora o vínculo canônico é `cooperative_members.user_id == accounts.user_id` (o antigo caminho fica de fallback).
3. Documento do titular cai para `users.tax_id` quando não há Member (o CPF/CNPJ aparece mascarado na tela).
4. A hipótese do handoff ERA um defeito latente real para OUTRAS formas de dado: party parcial do metadata (ex.: só `payer_ispb`) bloqueava o fallback inteiro. Agora o lado "self" da operação (pagador no débito, recebedor no crédito) mescla metadata com cadastro POR CAMPO (metadata vence). GUARDA: party que declara ISPB de OUTRA instituição nunca herda cadastro de conta Core (não mistura pessoas no mesmo bloco; teste pinado).
5. (Achado NOVO no acervo real de HML) Transferência interna NOVA saía com recebedor null: a coluna `to_account_id` carrega o id de trânsito 20 (conta inexistente) e o destino verdadeiro vive em `metadata.to_account_id` como string. Os dois adaptadores espelhados (admin `ReceiptsController` e v2 `find_transaction_or_entry`) agora tentam o metadata quando a coluna não resolve.

O que continua SEM recebedor por decisão/falta de fonte: o acervo histórico do envio manual (PIXMAN) não tem o recebedor em lugar nenhum do Core (a fonte é o banco da cabine; dono decidiu não reparar; credencial Core→cabine de PROD nunca provisionada). A seção "Dados do recebedor" fica oculta para esses — comportamento honesto, sem fabricar dado.

Validação:

- Suíte COMPLETA do core no worktree: **7674 testes, 0 falhas** (25 doctests, 55 properties; partição `wt`).
- Teste de ouro da paridade admin==v2 preservado; 288 testes admin + 60 do bloco receipts/v2 verdes.
- HML com acervo REAL (lição da fixture registrada no aviso): túnel SSM + API admin. ANTES (`core-api:146`): `TEF-ae000d1767f21147` da conta 10024270 → `receiver: null`. DEPOIS (`core-api` com o fix): ver seção 4.
- Screenshots do estado de PROD antes do fix em `docs/reports/screenshots/` (sessão): comprovante da M2 com Nome = CNPJ cru.

### Imagens do dono (extrato e lista Comprovante com Recebedor "-")

Validadas: a exibição está correta; o lançamento e a transação da M2 não têm nome do recebedor em NENHUMA fonte do Core (só `creditor_ispb` no lançamento). É ausência de dado do acervo histórico, esperada. Follow-up opcional registrado: coluna Recebedor da lista poderia mostrar a instituição resolvida do ISPB quando só ele existir.

## 2. Triagem das 66 falhas pré-existentes da cabine PIX (aviso 1) — ambiente, ZERO quebra de código

Causa única, provada empiricamente (agente de triagem + contraprovas):

- O squash `20260101000000` (`mon_pix_schema_clean.sql`, dump de PROD de 2026-03-07) JÁ cria `monetarie_settlement.holidays` e `monetarie_dict.ownership_validations` na forma LEGADA (`holiday_date/...`, `validation_id/...`).
- As migrations de julho (`20260702130000`, `20260704235000`) fazem `create table` puro → 42P07 em QUALQUER bootstrap do zero (provado em partição virgem).
- O workaround documentado no CLAUDE.md ("marcar as duas migrations como aplicadas") deixa o schema LEGADO no banco → as 66 falhas eram todas `42703 undefined_column` nessas 2 tabelas. Os mesmos 10 arquivos passam 100% em banco com schema correto.
- NÃO é seed de feriado, NÃO é drift de fixture, NÃO é quebra na main.

Correções:

1. Partição `wt` local CURADA (DROP das 2 residuais + re-migrate): os 10 arquivos que falhavam somam **118 testes / 0 falhas**.
2. As 2 migrations agora são DEFENSIVAS (commit `14275945`): descartam a residual na forma legada (detectada por coluna-assinatura `holiday_date`/`validation_id`) antes do create; a forma atual nunca é tocada. Bootstrap do zero provado em partição descartável (`ecto.create`+`ecto.migrate` completos, formas novas conferidas por `\d`).
3. Gotcha do CLAUDE.md corrigido (a receita antiga plantava o defeito).

**AVISO AO TIME (Bruno/Eduardo/Micael):** se o `mix test` da cabine falhar com `42703` em `holidays`/`ownership_validations`, o banco de teste local de vocês tem o residual legado. Receita: `DROP TABLE monetarie_settlement.holidays CASCADE; DROP TABLE monetarie_dict.ownership_validations CASCADE; DELETE FROM schema_migrations WHERE version IN (20260702130000, 20260704235000);` e `mix ecto.migrate`. Com o commit `14275945` basta o DELETE das 2 versões + migrate. NÃO usar mais "marcar como aplicada". Item residual: "dict inbound poll" (4 falhas reportadas) não reproduziu isolado — observar na próxima suíte completa.

## 3. CabinStatusLookup: cache de login por execução (aviso 2)

Implementado (commit `98681e83`, TDD com stubs Req.Test): o resultado do login (sucesso OU falha) é cacheado por processo chamador — sucesso reusa a sessão (TTL 5 min), falha entra em backoff (60 s; 401/429/erro de transporte não retentam por linha). Fetch 401/403 com sessão cacheada invalida o cache (a próxima chamada reloga). Bearer token não passa por login. TTLs ajustáveis por `:pix_cabin_auth_ttl_ms` / `:pix_cabin_auth_backoff_ms`. Decisão do dono preservada: em PROD a ação é provisionar a credencial, não mexer em código.

## 4. Validação em HML (acervo real) e estado de deploy

- HML `core-api:147` deployado e estável, tag `homolog-2df1c875-comprovante-20260715` (rollback `:146`). UI (`core-admin-ui:28`) NÃO mudou — os fixes são todos backend.
- Prova ANTES→DEPOIS na API admin de HML (túnel SSM, conta 10024270, `TEF-ae000d1767f21147`): `receiver` null (`:146`) → `{"name": "Firmino da Souza Bastos", "document": "76156577505", "account": "119372-4", "agency": "0001", ...}` (`:147`), destino resolvido por `metadata.to_account_id` (a coluna carrega o id de trânsito 20); pagador Camille intacto. JSONs before/after arquivados na sessão. Pagador já vinha correto em HML (Member existente via merchant_id); em PROD o caso M2 é o RED do teste unitário com a forma copiada.
- Captura VISUAL em HML não foi possível pelo túnel (ALB roteia por Host header; navegador sem host-resolver-rules) — a prova de HML é API-level sobre acervo real; a renderização da UI para payload preenchido foi provada hoje em PROD (screenshot) e na validação local da sessão anterior.
- PROD **NÃO** foi deployado nesta sessão (dono operando money-path ao vivo hoje; deploy de pix/spb proibido nessas condições e o deploy de core fica para janela combinada). Os commits estão prontos.

## 5. Pendências que ficam

1. FEITO (mesma sessão, com autorização do dono): push `1115eb01..444b1f3c` na main + branch `feat/coreadmin-extrato-comprovante` publicada; **PROD `core-api:48`** (tag `prod-2df1c875-comprovante-20260715`, MESMO digest `sha256:2411607e...` do homolog; rollback `:47`), rollout COMPLETED 1/1, health 200. Comprovante da M2 REVALIDADO VIVO em PROD: Nome "M2 Serviços Médicos em Clinica Geral Ltda" + CPF/CNPJ mascarado 36.***.***/****-50 + Agência/Conta; recebedor segue oculto por falta de fonte (esperado). Screenshots antes/depois em `docs/reports/screenshots/2026-07-15-comprovante-cadastro/`. Sem migrations novas (core code-only; as 2 migrations pix já constam aplicadas em HML/PROD, a blindagem só afeta bootstrap novo).
2. Follow-up opcional: instituição por ISPB na coluna Recebedor das listas quando só o ISPB existir.
3. Investigar na frente de money-path a ORIGEM do id de trânsito 20 em `transactions.to_account_id` (as leituras agora toleram, mas a escrita continua gravando conta inexistente).
4. Follow-ups menores herdados do handoff anterior (ReceiptView compartilhado, unique index parcial do dedup, FeeCalculator institution_id nil, outbox settled, TEFs `_RCV` históricas, drifts conhecidos).
5. "dict inbound poll": sem reprodução isolada; re-observar.

## 6. Como reproduzir as provas

- Payload real de PROD (read-only): receita do rpc em `docs/reports/2026-07-15-extrato-contraparte-fase0-prod.md` §8; scripts desta sessão consultaram `transactions`/`accounts`/`cooperative_members` da M2 e reproduziram `Payload.build` na task de PROD.
- HML API por túnel SSM (sem VPN de HML): `aws ssm start-session --document-name AWS-StartPortForwardingSessionToRemoteHost --target i-02ce3a3b6b3ad0d37 --parameters host=coreadmin-h.monetarie.internal,portNumber=80,localPortNumber=8080`; login `POST /api/admin/auth/login` (campo `email`); `GET /api/admin/bank-accounts/10024270/receipts/TEF-ae000d1767f21147` com `--resolve coreadmin-h.monetarie.internal:8080:127.0.0.1`.
- Testes: `cd core/backend && MIX_TEST_PARTITION=wt mix test test/monetarie_web/controllers/admin/receipts_controller_test.exs test/monetarie/services/pix_providers/in_house/cabin_status_lookup_test.exs`; cabine: `cd pix/backend && MIX_TEST_PARTITION=wt mix test <10 arquivos da triagem>`.

## 7. SEGUNDA RODADA (mesma noite): flags do dono derrubaram duas classificações e viraram reparo + fix

O dono reprovou o fechamento com prints novos de PROD e estava CERTO nos dois pontos:

1. **Recebedor "-" nas listas NÃO era "ausência esperada"**: a classificação herdada ("não reparar PIXMAN") estava errada como resposta ao problema — a cabine SEMPRE teve o recebedor. REPARO EXECUTADO EM PROD (rpc, mesma semântica do `StatementBackfill.fill_outbound_counterparty`: só preenche chave ausente, description ganha " - NOME", idempotente, marcador `counterparty_repair=cabin_payments_20260715`): **10 transações PIX outbound** (todos os PIXMAN sem recebedor desde 06/2026, M2 incluída) + **9 lançamentos** curados com nome/documento/ISPB lidos do banco da cabine por E2E (read-only). Censo final: 0 restantes. Validado vivo: extrato da M2 = "Recebedor ELI SIMAO GOULART 022.232.260-86", lista Comprovante idem, e o comprovante ganhou o bloco "Dados do recebedor".
2. **Pagador "-" nas devoluções pacs.004 (conta 000000982, Gabriel)**: acervo escrito pelo código pré-fix, que montava a description com o nome ("Devolução recebida - LUIZ MARCELO...") mas não gravava `counterparty_name` no metadata que a coluna Pagador lê (gap já apontado na Fase 0 §9.4; o `record_inbound_settled` ATUAL já grava). REPARO EXECUTADO EM PROD: **3 devoluções** (payer = LUIZ MARCELO GONCALVES DA COSTA PENHA, CPF, ISPB, fonte = creditor do E2E original na cabine) + **3 PIX-in** sem pagador (MURILO DE MELLO BAYER, UNIDAS LOCADORA S.A., RHR GESTAO DE NEGOCIOS LTDA, fonte = pacs.008 na cabine), entries + transactions. Censo final: 0 restantes nessas fontes. Validado vivo no extrato do Gabriel.
3. **Comprovante ilegível no painel escuro**: o componente usava tokens de tema (--p-*) que flipam no dark e os valores sumiam. Decisão do dono: comprovante é DOCUMENTO, papel claro SEMPRE. `ReceiptDocument.vue` com as 21 cores explícitas (commit `611bde1d`), vitest 20/20; **HML `core-admin-ui:29`** e **PROD `core-admin-ui:19`** (tag `prod-611bde1d-comprovantedoc-20260715`, rollback `:18`). Validado vivo em PROD nos DOIS temas (estilo computado: papel rgb(255,255,255), tinta rgb(15,23,42) com data-theme=dark; screenshots arquivados).

Pendência residual desta rodada: o crédito de remediação de R$ 4,54 (conta 2677, incidente pacs.008 de 09/07) segue sem payer_name — o nome do pagador só existe no XML original do incidente (DLQ, proibido reprocessar; leitura do XML fica como follow-up). Nenhum dado foi inventado.

Lição registrada: "sem fonte no Core" NÃO encerra o assunto quando a fonte existe na cabine — a resposta certa é reparo com fonte provada, não reclassificação como esperado.
