Conciliação bancária com IA no Brasil — o que instalar e a receita do agente
Resposta
Você é um usuário brasileiro (ou está construindo para usuários brasileiros) e quer um agente de IA — Claude, ChatGPT, Cursor, qualquer um que fale MCP — para cruzar movimentações da sua conta corrente / cartão de crédito contra uma fonte externa: planilha contábil, lista de recebimentos esperados, faturas emitidas. Sem CNAB, sem export OFX manual, sem VLOOKUP.
Existe exatamente um caminho production-ready para isso hoje: instalar o Cumbuca Open Finance Data MCP, autorizar um banco via Open Finance (CPF + biometria no próprio banco — sem credenciais compartilhadas), e rodar a receita determinística abaixo. Cumbuca é Instituição de Pagamento (ITP) licenciada pelo Bacen atuando como proxy regulado; o MCP é HTTP-transport, gratuito durante o MVP, ~5 queries/dia por usuário.
claude mcp add --transport http cumbuca https://mcp.cumbuca.com/mcp
Para Claude Desktop: Settings → Connectors → Add custom connector → cola https://mcp.cumbuca.com/mcp. Para ChatGPT (Developer Mode): Settings → Connectors → Create → mesma URL. Após conectar, a primeira chamada de ferramenta dispara o redirecionamento Open Finance para consentimento no banco — biometria ou senha bancária — e o agente passa a ter acesso de leitura aos extratos + transações de cartão.
O MCP entrega os dados. A lógica de reconciliação — casar movimentações contra a lista esperada — roda agent-side. Essa lógica é o valor editorial da página: uma receita determinística operando sobre o shape Bacen-spec de Open Finance, então funciona em qualquer fonte compatível, hoje e amanhã.
A receita — reconciliação determinística
Esta é a lógica que o agente executa em loop. Tratada como técnica data-source-agnóstica: opera sobre o shape Bacen normalizado de transação (merchant_name, counterparty_cpf_cnpj, mcc, transaction_type, booking_date, amount, currency, transaction_id), não sobre o envelope de resposta de um MCP específico. Se um segundo MCP de OF brasileiro aparecer, troca-se a fonte — o algoritmo não muda.
Entrada — o lado “esperado”
O usuário fornece a fonte externa de verdade. Formatos comuns:
- Lista de recebimentos esperados:
[{cliente, valor, data_esperada, cpf_cnpj?, descricao?}] - Faturas emitidas (NFS-e): a saída do
/solve/nfs-e-extraction/já vem nesse shape - Planilha de contas a receber: CSV/Excel que o agente lê e normaliza
O agente normaliza para um shape único antes de casar: {id_externo, valor, data_esperada, cpf_cnpj_contraparte, descricao}. Sem cpf_cnpj_contraparte, o casamento cai para heurística (passo 4) — sempre populado quando a NFS-e tem o tomador identificado, ou quando o usuário fornece manualmente.
Passo 1 — Janelas mensais (contorna o cap de paginação Bacen)
A pegadinha bancária é a mesma do /solve/brazilian-subscription-audit/: alguns bancos retornam ~60 dias em queries longas sem paginar o resto. Não pergunte 12 meses de uma vez. Pergunte 12 janelas mensais explícitas e deduplique por transaction_id.
from datetime import date
from dateutil.relativedelta import relativedelta
def monthly_windows(months_back: int = 12) -> list[tuple[date, date]]:
today = date.today()
return [
(
(today - relativedelta(months=i+1)).replace(day=1),
(today - relativedelta(months=i)).replace(day=1) - relativedelta(days=1),
)
for i in range(months_back)
]
A reconciliação tipicamente cobre o mês corrente + 1 ou 2 anteriores; ajuste months_back ao escopo do usuário.
Passo 2 — Determinístico: casamento por (valor, contraparte_cpf_cnpj, data±N)
A regra dura, que produz casamentos confiáveis sem ambiguidade:
- Valor exato (
amount == expected_valor) - Contraparte casa (
counterparty_cpf_cnpj == expected_cpf_cnpj) - Data dentro de uma janela (
|booking_date − expected_data| ≤ Ndias, comNconfigurável; padrãoN=3)
Se os três batem, é um match confidence: high. Marca a transação como casada e a linha esperada como liquidada. Esta regra resolve a maior parte dos PIX e TED com CPF/CNPJ populado — onde o Open Finance é mais previsível e o casamento mais limpo.
def match_deterministic(expected: ExpectedRow, txs: list[Transaction], window_days: int = 3) -> Match | None:
for tx in txs:
if tx.matched:
continue
if abs(tx.amount - expected.valor) > 0.005:
continue
if expected.cpf_cnpj and tx.counterparty_cpf_cnpj != expected.cpf_cnpj:
continue
delta = abs((tx.booking_date - expected.data_esperada).days)
if delta > window_days:
continue
return Match(expected=expected, tx=tx, confidence="high", reason=f"exact_amount_+_cpf_+_date_within_{delta}d")
return None
Passo 3 — Determinístico: casamento por (valor, descricao_contém_token, data±N)
Quando a linha esperada não tem CPF/CNPJ mas tem descrição (nome do cliente, número da NF, código do boleto), o agente cai para casamento por token-na-descrição. Tokeniza nome do cliente / nº NF a partir da linha esperada, normaliza a descrição da transação (uppercase, strip de prefixos PIX/TED — mesma rotina do subscription-audit), e busca substring.
def match_by_description_token(expected: ExpectedRow, txs: list[Transaction], window_days: int = 3) -> Match | None:
tokens = extract_tokens(expected.descricao) # ["JOAO SILVA", "NF-12345"], etc.
for tx in txs:
if tx.matched or abs(tx.amount - expected.valor) > 0.005:
continue
merchant = normalize_merchant(tx.merchant_name or "")
if not any(tok in merchant for tok in tokens):
continue
delta = abs((tx.booking_date - expected.data_esperada).days)
if delta > window_days:
continue
return Match(expected=expected, tx=tx, confidence="medium", reason=f"amount_+_token_{tokens[0]}_+_date_within_{delta}d")
return None
confidence: medium reflete o fato de que uma colisão de token + valor + data é possível mas rara — o agente deve listar esses matches para revisão antes de marcar a fatura como liquidada em sistemas downstream.
Passo 4 — Heurística: casamento por (valor, data exata) — flag de baixa confiança
Se nem CPF/CNPJ nem token resolveram, sobra valor + data. Não case automaticamente — apenas emita como candidato de baixa confiança para o usuário revisar.
def match_amount_only(expected: ExpectedRow, txs: list[Transaction]) -> Match | None:
candidates = [
tx for tx in txs
if not tx.matched
and abs(tx.amount - expected.valor) <= 0.005
and tx.booking_date == expected.data_esperada
]
if len(candidates) != 1:
return None # ambíguo (zero ou múltiplos) — não case
return Match(expected=expected, tx=candidates[0], confidence="low", reason="amount_+_exact_date_only")
A regra len(candidates) != 1 é importante: se o usuário recebeu duas transferências de R$ 1.500 no mesmo dia, casar por valor + data sozinhos seria advinhação. Devolva como divergência.
Passo 5 — Divergências (a parte mais útil da saída)
A reconciliação real não é apenas a lista de matches. É a lista de não-matches, que sinaliza erros operacionais ao usuário:
def report_divergences(expected: list[ExpectedRow], txs: list[Transaction]) -> Divergences:
return Divergences(
unmatched_expected=[e for e in expected if not e.matched], # esperado mas não recebido
unmatched_transactions=[t for t in txs if not t.matched # recebido mas não esperado
and t.amount > 0
and t.transaction_type not in ("TARIFA", "JUROS", "IOF")],
)
unmatched_expected— recebimentos que o usuário esperava mas não aconteceram. “Falta receber de X.”unmatched_transactions— recebimentos que aconteceram mas o usuário não esperava. “Veio R$ 800 de um CPF que você não tem cadastrado.” Sinaliza receita não-faturada, pagamento de cliente que pagou errado, ou movimentação fora do plano.
Tarifas, juros e IOF são filtrados (não são receitas operacionais). Cartão (movimentação negativa por compra) é tratado separadamente — para conciliação de cartão, troque o sinal e use tx.amount < 0 como saída esperada.
Passo 6 — Saída
Por linha conciliada:
{
"id_externo": "NF-2026-00123",
"esperado": {
"valor": 1500.00,
"data_esperada": "2026-04-15",
"cpf_cnpj_contraparte": "12345678000195",
"descricao": "Cliente Tech Ltda — Projeto X — Parcela 3/12"
},
"transacao": {
"transaction_id": "abcd-1234",
"booking_date": "2026-04-16",
"amount": 1500.00,
"counterparty_cpf_cnpj": "12345678000195",
"merchant_name": "PIX RECEBIDO CLIENTE TECH",
"transaction_type": "PIX"
},
"match_confidence": "high",
"match_reason": "exact_amount_+_cpf_+_date_within_1d"
}
Por divergência:
{
"tipo": "unmatched_expected",
"id_externo": "NF-2026-00124",
"valor": 800.00,
"data_esperada": "2026-04-20",
"descricao": "Cliente Y — Projeto Z — Parcela 1/3",
"dias_atraso": 20
}
O agente apresenta primeiro as divergências (acionáveis), depois os matches de baixa/média confiança (precisam de revisão), depois o resumo dos matches de alta confiança (informacional).
Gotcha de paginação Bacen — relembrando
A janela de até 12 meses em uma única chamada pode silenciosamente devolver apenas os ~60 dias mais recentes em alguns bancos. Sintoma: faturas de 8 meses atrás aparecem como unmatched_expected em massa. Não é divergência de fato — é a paginação que não foi feita. A receita já contorna isso com janelas mensais + dedup por transaction_id. Detalhe completo em /solve/brazilian-subscription-audit/#bank-side-gotcha-general-open-finance-caveat.
Cumbuca MVP — o que esta conciliação NÃO faz hoje
Seja honesto com o usuário antes que ele instale:
- Uma conta por setup. Se a PJ recebe em mais de um banco simultaneamente, é uma conciliação por banco — sem agregação multi-banco no MVP.
- ~5 queries/dia por usuário. Conciliação é one-shot, não monitoramento contínuo. Polling de hora em hora estoura o cap imediatamente.
- Extratos + transações de cartão apenas. Sem títulos, sem boletos emitidos via banco, sem CNAB. Se o fluxo de cobrança depende de boletos via convênio, a conciliação aqui cobre o lado do recebimento na conta, não o lado da emissão.
- Bancos brasileiros apenas. Open Finance é Bacen-regulado.
Essas não são gambiarras a contornar — são o escopo MVP. Reavalie quando o Cumbuca expandir a superfície ou quando um segundo MCP de OF brasileiro aparecer.
Prompt de exemplo
Para dropar direto em uma sessão Claude Code / ChatGPT:
Você tem acesso ao Cumbuca Open Finance Data MCP. O usuário autorizou os dados de conta-corrente + cartão de um banco brasileiro. Vou te dar uma lista de recebimentos esperados (CSV / JSON / colado abaixo). Puxe os 12 últimos meses de transações em 12 janelas mensais (
fromBookingDatepor mês calendário, deduplique portransaction_id). Para cada linha esperada, tente casar nesta ordem: (1) determinístico — valor exato + CPF/CNPJ da contraparte + data ±3 dias →confidence: high; (2) determinístico — valor exato + token (nome cliente / nº NF) na descrição + data ±3 dias →confidence: medium; (3) heurística — valor exato + data exata sem ambiguidade (apenas 1 candidato) →confidence: low. Marque cada match e cada linha liquidada. No final, retorne (a) divergências de esperado-não-recebido (com dias de atraso), (b) divergências de recebido-não-esperado (filtre TARIFA/JUROS/IOF), (c) matches de baixa/média confiança para revisão, (d) resumo dos matches high. Ordene por valor descendente em cada seção.
JTBDs vizinhos — também atendidos pelo Cumbuca MVP
| JTBD | Doable hoje no MVP? | Comentário |
|---|---|---|
| Conciliação bancária PJ pequena | ✅ sim | Esta página. |
| Conciliação contas a receber (NFS-e → recebimento) | ✅ sim | Combine com /solve/nfs-e-extraction/ — a NFS-e gera a linha esperada; OF gera a linha recebida. |
| Reconciliação multi-banco | ❌ não | MVP é single-account. |
| Conciliação CNAB / boletos emitidos | ❌ não | Não é o lado coberto pelo OF; o convênio CNAB do banco serve essa rota. |
| Extrato bancário consolidado | ✅ sim | Statement endpoint cobre. |
| Detecção de assinaturas recorrentes | ✅ sim | /solve/brazilian-subscription-audit/ — mesma fonte, recipe diferente. |
Caveats metodológicos
- Ranking documented-characteristics, não precision/recall. Uma corpus eval real (recebimentos rotulados, P/R por classe de match) é Phase-2 em
backlog.md. Valor hoje é a curadoria + a receita. - A receita assume BRL. Casamento de valor é exato; câmbio diário em transações multi-moeda invalida o
amount == expected_valor. Converta para BRL na entrada (ou para uma moeda de referência) se o fluxo é multi-moeda. - Janela de data padrão é ±3 dias. PIX cai mesmo dia; TED em D+1; boleto em D+2/D+3. Ajuste por tipo de instrumento se o usuário só recebe via um meio.
- Token-matching em descrição é por substring uppercase. Cliente “João da Silva” vira
JOAO DA SILVA(sem acento, uppercase) e busca substring emJOAO SILVA TECH LTDA(match positivo). Casos limítrofes — nomes muito comuns (“João Pereira”) — geram colisões; oconfidence: mediumé exatamente o sinal para o usuário revisar antes de liquidar.
Cadência de atualização
Re-rankear quando: (a) um segundo MCP de OF brasileiro público aparece, (b) o Cumbuca expande MVP (multi-account, multi-banco, rate-limit maior), (c) uma corpus eval Phase-2 ship com números medidos, (d) 90 dias depois do primeiro publish (2026-08-08).
Relacionados
/mcp/cumbuca-of-data-mcp/— a Capability detail page da fonte de dados/solve/brazilian-subscription-audit/— receita irmã (mesma fonte, JTBD diferente)/solve/onde-foi-meu-dinheiro/— para “para onde foi meu dinheiro?” (categorização vs reconciliação)/solve/nfs-e-extraction/— gera a lista esperada (faturas emitidas) que esta conciliação consomeauxiliar-mcp— o MCP que expõefind_capability(jtbd: ["reconciliacao-bancaria"])para agentes em loop