Runbook de manutenção de massas financeiras do FintechBank. Use quando a auditoria (run_audit_consistency_report.js) acusar discrepância/anomalia em massa (CPF), ou quando o usuário relatar erro em fatura de massa — pagamento dividido em 2+ lançamentos, encargos criados após quitação, fatura quitada herdando dias de atraso, fatura aberta zerada após pagamento, dias de atraso errados, saldo credor perdido. Siga o diagnóstico e a correção abaixo antes de alterar qualquer dado. NUNCA edite fatura FECHADA (imutável) — a quitação é derivada por cascata na leitura.
Install
npx skillscat add gilvans/fintechbankapp/manutencao-massas Install via the SkillsCat registry.
Manutenção de Massas Financeiras
Runbook de correção para massas com estado financeiro inconsistente. Este
documento é a fonte de verdade para o motor/cron/agente corrigir a massa quando
o problema acontecer — os scripts de correção pontual ficam em scripts/manutencao/
(gitignorado; consulte-os conforme o caso).
1. Diagnóstico (sempre primeiro)
Rode a auditoria oficial e localize o CPF:
cd API
node scripts/run_audit_consistency_report.js --csvO relatório aponta: discrepâncias (dias de atraso × esperado por fatura) e
anomalias estruturais (pagamento dividido, encargo pós-quitação). Inspecione
a massa em detalhe:
node -e "
require('dotenv').config();
const { Pool } = require('pg');
const p = new Pool({ connectionString: process.env.DATABASE_URL });
(async () => {
const cpf = process.argv[1] || '80535757654';
const g = await p.query(\`SELECT id, status, due_date, valor_total, valor_pago, dias_atraso, data_pagamento FROM fintech.invoices WHERE cpf='\${cpf}' ORDER BY due_date\`);
console.table(g.rows);
const t = await p.query(\`SELECT date, amount, description, invoice_id FROM fintech.transactions WHERE cpf='\${cpf}' ORDER BY date\`);
console.table(t.rows);
process.exit(0);
})().catch(e => { console.error(e.message); process.exit(1); });
\` 80535757654Verifique: quantas faturas fechadas não pagas, o total pago (soma dosINVOICE_PAYMENT), e se há 2+ transações de pagamento no mesmo instante
(pagamento dividido).
1b. Confirmar a entrega no Telegram (log persistente)
Comprovantes/avisos vão ao tópico da massa no Telegram. O histórico de envios é
durável na tabela fintech.telegram_message_log (o Telegram não expõe API
para ler o histórico de tópicos — cada envio do telegramService grava 1 linha
por destino: cpf/pagamentos/general). Para verificar se a massa recebeu a
mensagem:
cd API
# Verifica envio de PDFs (fatura fechada/aberta) no tópico e lista os últimos
# envios persistidos da massa — respostas autoritativas da Bot API (message_id)
node scripts/verify_topic_733.cjs --cpf 80535757654 --topic 1415
# Ou consulte direto a tabela
node -e "
require('dotenv').config();
const { Pool } = require('pg');
const p = new Pool({ connectionString: process.env.DATABASE_URL });
(async () => {
const { rows } = await p.query(\`SELECT category, destination, message_type, message_id, ok, error, created_at
FROM fintech.telegram_message_log WHERE cpf='\${process.argv[1]}' ORDER BY created_at DESC, id DESC LIMIT 10\`);
for (const r of rows) console.log(r.created_at, '|', r.category, '->', r.destination, '('+r.message_type+')', r.ok ? 'OK msg#'+r.message_id : 'ERRO '+(r.error||''));
process.exit(0);
})().catch(e => { console.error(e.message); process.exit(1); });
\` 80535757654No painel admin WEB (Telegram → Histórico de Envios) ou via API:GET /admin/telegram/log?cpf=&category=&destination=&limit= mostra o mesmo
histórico, com ok:true/false e o message_id quando entregue. ok:false +error = envio falhou (tópico ausente, bot sem permissão) — não é problema da
massa, é de canal.
2. Regra de negócio canônica (fonte da verdade)
- Pagamento = UMA transação
INVOICE_PAYMENTcom o valor total (igual ao
comprovante). NUNCA dividir em N transações por fatura. - Distribuição por cascata (
planDistribution, emAPI/utils/billing):
da fatura mais antiga para a mais nova, excedente = saldo credor (residual
negativo). - Encargos estopam quando a dívida das faturas FECHADAS é coberta pelo
pagamento →dias_atraso = 0e sem geração de novos encargos. - Fatura aberta NÃO zera: continua somando compras + encargos restantes.
- Fatura FECHADA é imutável — nunca escrever
data_pagamento/valor_pago
nela; a quitação é derivada na leitura. - PDF da fatura (Página 2) — regra R-PDF1 (
validate_skill_rules.js): a
Página 2 usacard.transactions(aberta) /card.closedTransactions
(fechada, snapshotitemized_transactions) — NUNCAcard.openTransactions/card._closedInvoiceSnapshot(apagados do payload).
Parcelada exibe coluna PARCELA; com juros, linha vermelha "Total financiado"
(art. 52 CDC). Pagamentos (PAYMENT) só na fatura ABERTA, valor negativo em
verde na seção "PAGAMENTOS" — a fechada nunca exibe pagamentos. Ver §23 dodocs/REGRAS-NEGOCIO-FATURA.md. - Boleto/PIX (Página 4) — padrão Febraban 598: fator 5 dígitos, DV do
código de barras na posição 20, campo livre 24; nosso número = cpf[-10:];
DVs calculados (módulo 11), nunca hardcoded. Python é a fonte primária,buildBoletoData(JS) o fallback idêntico. Datas SEM fuso (fatorVencimento
extrai YYYY-MM-DD da string ISO) — nuncanew Date(iso)+ getDate(). Ver
§24 dodocs/REGRAS-NEGOCIO-FATURA.md.
3. Correção
3a. Sincronizar dias de atraso (drift pré-existente)
cd API
node scripts/sync_dias_atraso.cjs # dry-run
node scripts/sync_dias_atraso.cjs --confirm # aplica3b. Juntar pagamento dividido em 1 transação
Se o extrato mostra 2+ INVOICE_PAYMENT no mesmo instante (o usuário pagou uma
vez), consolide em 1 transação com o valor total. O script de referência está emscripts/manutencao/ — procure por fix_massa_1tx ou reaplique o padrão:
somar os ABS(amount), manter a descrição/origem, remover as extras e ancorar oinvoice_id na fatura mais recente em aberto (a que ancora o comprovante).
3c. Remover encargos criados APÓS a quitação
O motor diário pode ter inserido billing_charges pending em fatura já paga
(se rodou com código pré-cascata). Identifique: created_at > data do último INVOICE_PAYMENT e fatura quitada pela cascata. Delete apenas essas linhas
(encargos ANTERIORES ao pagamento são legítimos — NÃO remover).
3d. Recalcular encargos de massa específica
Para recalcular charges de uma massa, use os scripts em scripts/manutencao/
(recompute_charges_*.cjs, freeze_encargos_*.cjs, cleanup_duplicate_charges.cjs).
Leia o cabeçalho do script antes de executar — todos têm modo dry-run.
4. Validação pós-correção
cd API
node scripts/run_audit_consistency_report.js --csv # 0 discrepâncias / 0 anomalias
node scripts/sync_dias_atraso.cjs --confirm
npm test -- --silent # suíte completa (662 testes)Confirme na massa corrigida: account_status = 'adimplente' (se quitada),days_overdue = 0, overdue_status = 'EM_DIA', faturas com dias_atraso = 0
e a fatura aberta com valor correto (compras + encargos restantes). Se a massa
relatou comprovante não entregue, rode verify_topic_733.cjs --cpf <cpf> e
confirme ok:true + message_id no telegram_message_log (ver §1b).
5. Referências
| Componente | Papel |
|---|---|
API/utils/billing → planDistribution |
Distribuição por cascata (fonte da verdade) |
API/index.cjs → runBillingValidation / syncInvoiceDiasAtraso |
Motor diário com cascata |
API/src/controllers/invoiceController.js → persistPaymentDistribution |
Grava 1 tx de pagamento |
API/scripts/audit_helpers.cjs |
Regras da auditoria (cascata + âncoras) |
API/scripts/sync_dias_atraso.cjs |
Sync oficial de dias_atraso (com cascata) |
API/scripts/run_audit_consistency_report.js |
Relatório diário + varredura de anomalias |
API/repositories/usersRepo.js → overdueStatusFor |
Tier overdue_status (EM_DIA / 7D / 15D / 30D) |
API/repositories/telegramMessageLogRepo.js |
Log persistente de envios (tabela telegram_message_log) |
API/scripts/verify_topic_733.cjs |
Verifica PDFs no tópico + lista envios persistidos (--cpf/--topic) |
GET /admin/telegram/log |
Rota admin do histórico (cpf/category/destination/limit) |
scripts/manutencao/ |
Scripts one-off de correção pontual (gitignorado) |
docs/REGRAS-NEGOCIO-FATURA.md §23 |
Regra da Página 2 do PDF (fonte das movimentações, PARCELA, juros art. 52, pagamentos) |
API/services/invoicePdfService.js → drawMovimentacoes |
Renderiza a Página 2 (compras + PAGAMENTOS verde) |
API/utils/boletoMath.js → buildBoletoData |
Fonte única JS do boleto 598 (linha digitável + barcode, padrão Febraban novo) |
scripts/invoice_payment_generator.py |
Gerador Python (primário) de boleto/PIX — JS fallback idêntico (§24) |