name: immutable-ledger description: | Skill executável para o Ledger Financeiro Imutável do StreamShare. Use quando precisar: registrar movimentações financeiras, substituir cobranças canceladas, auditar o histórico de saldo, ou estruturar entradas no FinancialLedger. Triggers: "ledger", "entrada no ledger", "registrar pagamento", "substituir cobrança", "auditoria financeira", "saldo da wallet", "fluxo espelhado"
Skill: Immutable Financial Ledger
Quando invocar esta skill: Sempre que precisar escrever código que toque
FinancialLedger,Wallet,CobrancaLoteHistorico, ou qualquer operação que envolva substituição deCobrança.
📐 PROCEDIMENTO 1 — Registrar Entrada no Ledger
Contexto
Toda movimentação financeira DEVE ser registrada via LedgerService.processEntry(). Nunca escreva diretamente em FinancialLedger via Prisma.
Passos
Step 1 — Gere um correlationId único para agrupar a operação atômica:
import { randomUUID } from "crypto";
const correlationId = randomUUID();
Step 2 — Monte os payloads de entrada usando o padrão de Fluxo Espelhado:
Para pagamento de cobrança individual (4 entradas obrigatórias):
const entries = [
// 1. Aporte externo: dinheiro entra no sistema pelo participante
{
walletId: participanteWalletId,
tipoOperacao: "ENTRADA",
tipoEvento: "PAGAMENTO_RECEBIDO",
valor: new Prisma.Decimal(cobranca.valorFinal),
referenciaId: cobranca.id,
metadata: { correlationId, descricao: "Aporte PIX - Pagamento recebido" }
},
// 2. Liquidação interna: participante quita a dívida
{
walletId: participanteWalletId,
tipoOperacao: "SAIDA",
tipoEvento: "PAGAMENTO_RECEBIDO",
valor: new Prisma.Decimal(cobranca.valorFinal),
referenciaId: cobranca.id,
metadata: { correlationId, descricao: "Liquidação de fatura" }
},
// 3. Recebimento bruto na conta do organizador
{
walletId: contaWalletId,
tipoOperacao: "ENTRADA",
tipoEvento: "PAGAMENTO_RECEBIDO",
valor: new Prisma.Decimal(cobranca.valorOriginal),
referenciaId: cobranca.id,
metadata: { correlationId, descricao: "Repasse líquido ao organizador" }
},
// 4. Dedução de taxas da conta do organizador
{
walletId: contaWalletId,
tipoOperacao: "SAIDA",
tipoEvento: "TAXA_PLATAFORMA",
valor: new Prisma.Decimal(cobranca.valorTaxaPlataforma),
referenciaId: cobranca.id,
metadata: { correlationId, descricao: "Taxa da plataforma StreamShare" }
},
];
Step 3 — Execute DENTRO de prisma.$transaction:
await prisma.$transaction(async (tx) => {
// ... lógica de negócio ...
await LedgerService.processEntry(tx, entries);
});
📐 PROCEDIMENTO 2 — Substituir Cobrança Cancelada (Imutabilidade)
⚠️ NUNCA faça UPDATE em campos sensíveis. A única forma de corrigir uma cobrança é substituindo-a.
Passos
Step 1 — Valide que a substituição é permitida:
// Verificar: assinatura ativa, sem cobrança ativa no mesmo período
const cobrancaExistente = await tx.cobranca.findFirst({
where: {
assinaturaId: original.assinaturaId,
periodoInicio: original.periodoInicio,
status: { not: "cancelado" },
id: { not: original.id }
}
});
if (cobrancaExistente) throw new Error("Já existe cobrança ativa para este período");
Step 2 — Cancele o PIX antigo (se existir):
await PaymentService.cancelPixForCobranca(tx, original.id);
Step 3 — Crie a nova cobrança com substituidaPorId nulo (ela é a nova versão):
const nova = await tx.cobranca.create({
data: {
...dadosAtualizados,
status: "pendente",
metadataJson: {
...dadosAtualizados.metadataJson,
referenciaCobrancaAnteriorId: original.id,
}
}
});
Step 4 — Vincule a original à nova via substituidaPorId:
await tx.cobranca.update({
where: { id: original.id },
data: {
substituidaPorId: nova.id,
status: "cancelado",
deletedAt: new Date()
}
});
Step 5 — Registre o log de transação para auditoria:
await tx.logTransacaoGateway.create({
data: {
contaId,
tipo: "SUBSTITUICAO",
referenciaId: original.id.toString(),
metadata: { novaCobrancaId: nova.id, motivo: "..." }
}
});
📐 PROCEDIMENTO 3 — Consultar Saldo de Wallet
⚠️ NUNCA calcule saldo com
reduceem memória sobre todos os registros.
// ✅ Correto — usa agregação SQL com snapshot como âncora
const saldo = await LedgerService.getBalance(walletId);
// O serviço internamente faz:
// 1. Busca o último LedgerSnapshot para o walletId
// 2. Soma as entradas/saídas posteriores ao snapshot
// 3. Retorna Prisma.Decimal
📐 PROCEDIMENTO 4 — Audit de Rastreabilidade de Lotes
Toda adição/remoção de cobrança em lote deve ser registrada no histórico:
// Ao adicionar ao lote:
await tx.cobrancaLoteHistorico.create({
data: { cobrancaId, lotePagamentoId, adicionadoEm: new Date() }
});
// Ao remover do lote:
await tx.cobrancaLoteHistorico.updateMany({
where: { cobrancaId, lotePagamentoId, removidoEm: null },
data: { removidoEm: new Date(), motivoRemocao: "..." }
});
// NUNCA deletar registros históricos
🚫 Antipadrões a Evitar
| ❌ Proibido | ✅ Correto |
|---|---|
UPDATE cobranca SET valorFinal = x |
Padrão de substituição (Procedimento 2) |
FinancialLedger.create() direto |
LedgerService.processEntry() |
Saldo via entries.reduce() |
LedgerService.getBalance(walletId) |
valorFinal = subtotal * 1.01 |
valorFinal = subtotal / (1 - GATEWAY_FEE) |
Valores como number nativo |
new Prisma.Decimal(valor) |
Deletar CobrancaLoteHistorico |
Marcar removidoEm |
🔗 Referências
- Regras financeiras completas:
.agents/agents/financial-agent.md - Casos de teste BDD:
.agents/docs/financial-ledger-spec.md - Motor de faturamento:
.agents/docs/rules/BILLING_ENGINE.md