/* global React, Icon, BRL, NUM, Ticker, Sparkline, Drawer */ const STATUS_LABELS = { aberto: { label: 'EM ABERTO', cls: '' }, 'venc-hoje': { label: 'VENCE HOJE', cls: 'badge-amber' }, atraso: { label: 'EM ATRASO', cls: 'badge-red' }, pago: { label: 'PAGO', cls: 'badge-lime' }, }; const NF_STATUS_LABELS = { pend: { label: 'AGUARDA APROVAÇÃO', cls: 'badge-amber' }, aprov: { label: 'APROVADA · CAP CRIADO', cls: 'badge-cyan' }, pago: { label: 'PAGA', cls: 'badge-lime' }, recusado: { label: 'RECUSADA', cls: 'badge-red' }, }; const FinanceiroContext = React.createContext({ TITULOS_CAR: [], TITULOS_CAP: [], NFS_CONTRA_BTM: [], reload: () => {}, loading: true }); function MFinanceiro({ onNav }) { const [data, setData] = React.useState({ TITULOS_CAR: [], TITULOS_CAP: [], NFS_CONTRA_BTM: [] }); const [loading, setLoading] = React.useState(true); const reload = React.useCallback(async () => { try { const [car, cap, nfs] = await Promise.all([ window.dataApi.listTitulos('car'), window.dataApi.listTitulos('cap'), window.dataApi.listNfsContra(), ]); setData({ TITULOS_CAR: car, TITULOS_CAP: cap, NFS_CONTRA_BTM: nfs }); } catch { setData({ TITULOS_CAR: [], TITULOS_CAP: [], NFS_CONTRA_BTM: [] }); } finally { setLoading(false); } }, []); React.useEffect(() => { reload(); }, [reload]); const TITULOS_CAR = data.TITULOS_CAR; const TITULOS_CAP = data.TITULOS_CAP; const NFS_CONTRA_BTM = data.NFS_CONTRA_BTM; const [tab, setTab] = React.useState('overview'); const [titulo, setTitulo] = React.useState(null); const [tituloTab, setTituloTab] = React.useState('det'); const [nf, setNf] = React.useState(null); const [nfTab, setNfTab] = React.useState('resumo'); const [showImport, setShowImport] = React.useState(false); const [showLanc, setShowLanc] = React.useState(false); const [toast, setToast] = React.useState(null); const showToast = (msg, ms = 4000) => { setToast(msg); setTimeout(() => setToast(null), ms); }; return (
Gestão Financeira, Contábil e FiscalM07

Financeiro · DRE por empenho · Fluxo de caixa 90d

Contas a pagar/receber vinculadas a empenhos · Conciliação automática por PDF · NF contra a BTM com aprovação manual.

t.status !== 'pago').reduce((a, t) => a + t.valor, 0))} sub={`${TITULOS_CAR.filter(t => t.status !== 'pago').length} títulos`} cls="lime" /> t.status !== 'pago').reduce((a, t) => a + t.valor, 0))} sub={`${TITULOS_CAP.filter(t => t.status !== 'pago').length} títulos`} cls="amber" />
setTab('overview')}>Visão Geral
setTab('cap')}>Contas a Pagar {TITULOS_CAP.length}
setTab('car')}>Contas a Receber {TITULOS_CAR.length}
setTab('conc')}>Conciliação
setTab('nf')}>NF contra BTM {NFS_CONTRA_BTM.filter(n => n.status === 'pend').length}
setTab('dre')}>DRE por Empenho
{tab === 'overview' && } {(tab === 'cap' || tab === 'car') && { setTitulo(t); setTituloTab('det'); }} />} {tab === 'conc' && } {tab === 'nf' && { setNf(n); setNfTab('resumo'); }} />} {tab === 'dre' && }
{titulo && setTitulo(null)} tab={tituloTab} onTab={setTituloTab} onNav={onNav} />} {nf && setNf(null)} tab={nfTab} onTab={setNfTab} />} {showImport && setShowImport(false)} onDone={(nome, mov) => { setShowImport(false); showToast(`Extrato ${nome} importado · ${mov} movimentações detectadas · conciliação iniciada (vai pra aba Conciliação)`); setTab('conc'); }} />} {showLanc && setShowLanc(false)} onCreate={(tipo, id) => { setShowLanc(false); showToast(`${tipo === 'cap' ? 'CAP' : 'CAR'} ${id} criado · status EM ABERTO · vencimento adicionado à agenda do M07`); setTab(tipo); }} />} {toast && (
{toast}
)}
); } function useFinanceiro() { return React.useContext(FinanceiroContext); } function ImportExtratoModal({ onClose, onDone }) { const [file, setFile] = React.useState(null); const [banco, setBanco] = React.useState('BB'); const [conta, setConta] = React.useState('BB CC 12.847-3'); const [periodo, setPeriodo] = React.useState(''); const [importando, setImportando] = React.useState(false); const fileRef = React.useRef(null); const submit = () => { if (!file) return; setImportando(true); setTimeout(() => { const mov = Math.floor(Math.random() * 18) + 8; onDone(file, mov); }, 2000); }; return (
e.stopPropagation()}>
Importar extrato bancário
Conciliação automática via IA
Suporta PDF (OFX, extratos de BB/Itaú/Sicredi) e arquivos OFX/CSV. Após o upload, a IA tenta casar cada movimentação com CAPs/CARs do sistema. Divergências aparecem na aba Conciliação.
BANCO
CONTA
setConta(e.target.value)} />
PERÍODO (opcional)
setPeriodo(e.target.value)} placeholder="Ex: 01/05/2026 - 12/05/2026 (auto-detectado pelo PDF)" />
ARQUIVO DO EXTRATO *
fileRef.current?.click()}>
{file || 'Clique para escolher (PDF, OFX, CSV — até 20MB)'}
e.target.files[0] && setFile(e.target.files[0].name)} />
); } function NovoLancamentoModal({ onClose, onCreate }) { const [tipo, setTipo] = React.useState('car'); const [form, setForm] = React.useState({ id: 'CAR-2026-' + String(Math.floor(Math.random() * 900) + 100), desc: '', valor: '', vcto: '', forma: 'TED', contraparte: '', cnpj: '', empenhoOuNf: '', }); const [erro, setErro] = React.useState(null); const set = (k, v) => setForm({ ...form, [k]: v }); React.useEffect(() => { // Atualiza prefixo do ID quando muda tipo set('id', (tipo === 'car' ? 'CAR' : 'CAP') + '-2026-' + String(Math.floor(Math.random() * 900) + 100)); }, [tipo]); const submit = () => { if (!form.desc || !form.valor || !form.vcto || !form.contraparte) { setErro('Preencha Descrição, Valor, Vencimento e Cliente/Fornecedor'); return; } onCreate(tipo, form.id); }; return (
e.stopPropagation()}>
Novo lançamento financeiro
TIPO DE LANÇAMENTO
setTipo('car')}>↓ A RECEBER (CAR)
setTipo('cap')}>↑ A PAGAR (CAP)
ID DO LANÇAMENTO
set('id', e.target.value)} />
VENCIMENTO *
set('vcto', e.target.value)} />
DESCRIÇÃO *
set('desc', e.target.value)} placeholder={tipo === 'car' ? 'NF XXX · Cliente Y' : 'Boleto Z · Fornecedor W'} />
{tipo === 'car' ? 'CLIENTE' : 'FORNECEDOR'} *
set('contraparte', e.target.value)} placeholder={tipo === 'car' ? 'Prefeitura Mun. de...' : 'Suzano Distribuição'} />
CNPJ/CPF
set('cnpj', e.target.value)} placeholder="00.000.000/0000-00" />
VALOR (R$) *
set('valor', e.target.value)} placeholder="51576.60" />
FORMA DE PAGAMENTO
{tipo === 'car' ? 'EMPENHO VINCULADO (opcional)' : 'NF DO FORNECEDOR (opcional)'}
set('empenhoOuNf', e.target.value)} placeholder={tipo === 'car' ? '2026NE000XXX' : 'NF nº'} />
{erro &&
{erro}
}
); } function KpiSmall({ l, v, sub, cls }) { return (
{l}
{v} {sub}
); } /* ── OVERVIEW ──────────────────────────────────────────────── */ function Overview() { return (
Fluxo de Caixa · 90 dias
Saldos por banco
{[ { b: 'Banco do Brasil', c: 'CC 12.847-3', v: 487230.50 }, { b: 'Itaú', c: 'CC 38201-1', v: 184620.20 }, { b: 'Sicredi', c: 'CC 5210-X', v: 92140.80 }, ].map(b => (
{b.b}
{b.c}
{BRL(b.v)}
))}
TOTAL R$ 763.991,50
); } function FluxoChart() { const days = Array.from({ length: 90 }, (_, i) => i); const entradas = days.map(d => 100 + Math.sin(d * 0.15) * 40 + (d * 17 % 21) + d * 0.5); const saidas = days.map(d => 80 + Math.cos(d * 0.18) * 30 + (d * 11 % 17) + d * 0.3); const saldo = entradas.map((e, i) => e - saidas[i] + 200); const W = 800, H = 240; const max = Math.max(...entradas, ...saidas) * 1.1; const path = (arr) => arr.map((v, i) => `${i === 0 ? 'M' : 'L'} ${(i / 89) * W} ${H - (v / max) * H}`).join(' '); return ( {[0, 0.25, 0.5, 0.75, 1].map(p => )} ━ ENTRADAS ┄ SAÍDAS ━ SALDO {[30, 60, 90].map(d => D+{d})} ); } /* ── CONTAS A PAGAR / RECEBER ──────────────────────────────── */ function ContasTab({ tipo, onOpen }) { const car = tipo === 'car'; const { TITULOS_CAR, TITULOS_CAP } = useFinanceiro(); const items = car ? TITULOS_CAR : TITULOS_CAP; return (
Contas a {car ? 'Receber' : 'Pagar'}
{items.length} títulos · {BRL(items.reduce((a, t) => a + t.valor, 0))}
{items.map(r => { const st = STATUS_LABELS[r.status]; return ( onOpen(r)} style={{ cursor: 'pointer' }}> ); })}
Vcto ID Descrição {car ? 'Cliente' : 'Fornecedor'} {car ? 'Empenho' : 'NF origem'} Valor Status
{r.vcto} {r.id} {r.desc} {car ? r.cliente : r.fornecedor} {car ? r.empenho : r.nfOrigem} {BRL(r.valor)} {st.label}
); } /* ── CONCILIAÇÃO ───────────────────────────────────────────── */ function Conciliacao() { return (
Conciliação Bancária — Extrato 12/05/2026 (BB CC 12.847-3)
14 / 17 OK · 3 DIVERG.
{[ { d: '12/05', desc: 'TED REC PMS NITEROI 33804351', v: 51576.00, m: 'ok', sys: '2026NE000847 (NF 002847)' }, { d: '12/05', desc: 'TED REC SEFAZ RJ', v: 184320.00, m: 'ok', sys: '2026NE000812' }, { d: '11/05', desc: 'PIX REC TJ-RJ 287000', v: 287000.00, m: 'div', sys: 'sem correspondência' }, { d: '11/05', desc: 'DEB BOLETO SUZANO DISTR', v: -48200.00, m: 'ok', sys: 'CAP 2026-184' }, { d: '11/05', desc: 'DEB TARIFA TED', v: -12.40, m: 'div', sys: 'não lançado' }, { d: '10/05', desc: 'TED REC UFMG FINANC', v: 87420.00, m: 'ok', sys: '2026NE000756' }, { d: '10/05', desc: 'PIX DEB BIC BRASIL FORN', v: -8420.00, m: 'div', sys: 'lanç R$ 8.240 — divergência R$ 180' }, ].map((r, i) => ( ))}
DataDescrição extratoValorMatchLançamento sistemaAção
{r.d} {r.desc} 0 ? 'var(--lime)' : 'var(--red)' }}>{BRL(r.v)} {r.m === 'ok' ? ✓ MATCH : DIVERG.} {r.sys} {r.m === 'div' ? : }
); } /* ── NF CONTRA BTM ─────────────────────────────────────────── */ function NFContra({ onOpen }) { const { NFS_CONTRA_BTM } = useFinanceiro(); return (
NF Emitidas contra BTM · captura SEFAZ + aprovação manual
Integração ativa: SEFAZ-NACIONAL · {NFS_CONTRA_BTM.filter(n => n.status === 'pend').length} NFs pendentes de aprovação para criação de CAP.
{NFS_CONTRA_BTM.map(n => { const st = NF_STATUS_LABELS[n.status]; return ( onOpen(n)} style={{ cursor: 'pointer' }}> ); })}
NF Emitente Emissão Valor Vencimento Status
{n.nf}
{n.emitente}
{n.cnpj}
{n.emissao} {BRL(n.valor)} {n.venc} {st.label}
); } /* ── DRE POR EMPENHO ───────────────────────────────────────── */ function DREEmpenho({ onNav }) { const linhas = [ { id: '2026NE000847', o: 'Pref. Niterói', r: 61701.60, c: 32140.00, t: 10027.00, f: 312.40, l: 19222.20 }, { id: '2026NE000812', o: 'SEFAZ-RJ', r: 184320.00, c: 96420.00, t: 29952.00, f: 612.80, l: 57335.20 }, { id: '2026NE000798', o: 'TJ-PR', r: 32480.00, c: 18420.00, t: 5278.00, f: 287.10, l: 8494.90 }, { id: '2026NE000756', o: 'UFMG', r: 87420.00, c: 47200.00, t: 14206.00, f: 487.30, l: 25526.70 }, { id: '2026NE000721', o: 'Câmara SP', r: 18920.00, c: 11420.00, t: 3074.00, f: 184.50, l: 4241.50 }, ]; const totals = linhas.reduce((a, r) => ({ r: a.r + r.r, c: a.c + r.c, t: a.t + r.t, f: a.f + r.f, l: a.l + r.l }), { r: 0, c: 0, t: 0, f: 0, l: 0 }); return (
DRE por Empenho · lucro líquido real
clique para abrir empenho
{linhas.map(r => ( onNav('empenhos')} style={{ cursor: 'pointer' }}> ))}
Empenho Órgão Receita (-) Custos (-) Tributos (-) Frete Lucro líq. Margem
{r.id} {r.o} {BRL(r.r)} {BRL(r.c)} {BRL(r.t)} {BRL(r.f)} {BRL(r.l)} {((r.l / r.r) * 100).toFixed(1)}%
TOTAL · {linhas.length} empenhos {BRL(totals.r)} {BRL(totals.c)} {BRL(totals.t)} {BRL(totals.f)} {BRL(totals.l)} {((totals.l / totals.r) * 100).toFixed(1)}%
); } /* ── TITULO DRAWER (CAP/CAR) ───────────────────────────────── */ function TituloDrawer({ titulo, onClose, tab, onTab, onNav }) { const car = titulo.tipo === 'car'; const st = STATUS_LABELS[titulo.status]; return ( {car ? 'Conta a Receber' : 'Conta a Pagar'}M07{st.label}} title={{titulo.id} · {titulo.desc}} subtitle={`${car ? titulo.cliente : titulo.fornecedor} · ${titulo.cnpj}`} headRight={
VALOR · VENCE {titulo.vcto}
{BRL(titulo.valor)}
} tabs={[ { id: 'det', label: 'Detalhes' }, { id: 'doc', label: car ? 'Nota Fiscal' : 'Boleto / NF' }, { id: 'hist', label: 'Histórico' }, ]} activeTab={tab} onTabChange={onTab} footer={ <> {titulo.status === 'pago' ? ( ) : ( <> {car ? : } )} } > {tab === 'det' && } {tab === 'doc' && } {tab === 'hist' && }
); } function TituloDet({ titulo, onNav }) { const car = titulo.tipo === 'car'; return (
DADOS DO TÍTULO
{car ? 'CLIENTE' : 'FORNECEDOR'}
{car ? titulo.cliente : titulo.fornecedor}
{titulo.cnpj}
{car && titulo.empenho && titulo.empenho !== '—' && ( <>
EMPENHO ORIGEM
{titulo.empenho}
vinculado à NF {titulo.nf}
)} {titulo.status === 'atraso' && (
Título em atraso
Vencido em {titulo.vcto}. Recomendado abrir procedimento de cobrança formal.
)} {titulo.status === 'venc-hoje' && (
Vence hoje — confirmar {car ? 'recebimento' : 'pagamento'} antes do fim do expediente.
)} {titulo.folha && (
Lançamento de folha · {titulo.fornecedor}. Recolhimentos INSS/IRRF gerados automaticamente.
)}
); } function TituloDoc({ titulo }) { const car = titulo.tipo === 'car'; return (
BTM
BTM SERVIÇOS E COMÉRCIO LTDA
CNPJ 33.804.351/0001-04 · Rio de Janeiro/RJ
{car ? `NF ${titulo.nf}` : `BOLETO ${titulo.id}`}
Vcto: {titulo.vcto}/2026
{car ? 'NOTA FISCAL ELETRÔNICA · DUPLICATA' : 'BOLETO DE COBRANÇA'}
{car ? 'Sacado:' : 'Sacador:'} {car ? titulo.cliente : titulo.fornecedor}
{titulo.cnpj}
Descrição: {titulo.desc}
{car && titulo.empenho && titulo.empenho !== '—' && (
Empenho: {titulo.empenho}
)}
Valor a {car ? 'receber' : 'pagar'}: {BRL(titulo.valor)}
Vencimento: {titulo.vcto}/2026 · Forma: {titulo.forma} · {titulo.banco}
{!car && (
34195.18217 04802.{titulo.id.replace(/[^0-9]/g, '').slice(0, 5)}.{titulo.id.replace(/[^0-9]/g, '').slice(-5)} 1 9821{titulo.valor.toFixed(0).padStart(10, '0')}
)}
{!car && }
); } function TituloHist({ titulo }) { const eventos = [ { titulo: 'Título emitido', quando: `${titulo.emissao} · 14:08`, tone: 'cyan', descricao: `Lançamento ${titulo.id} criado automaticamente a partir de ${titulo.tipo === 'car' ? `NF ${titulo.nf}` : `NF ${titulo.nfOrigem || 'avulsa'}`}.` }, { titulo: 'Boleto/título enviado', quando: `${titulo.emissao} · 14:12`, tone: '', descricao: `Enviado via e-mail para ${titulo.tipo === 'car' ? titulo.cliente : titulo.fornecedor}.` }, ...(titulo.status === 'pago' ? [{ titulo: 'Pagamento confirmado', quando: `${titulo.pagoEm || titulo.vcto} · 09:42`, tone: 'lime', descricao: `Conciliado automaticamente com extrato bancário (${titulo.banco}).` }] : []), ...(titulo.status === 'atraso' ? [{ titulo: 'Vencimento ultrapassado', quando: `${titulo.vcto}`, tone: 'red', descricao: 'Sistema disparou alerta automático para o setor financeiro.' }] : []), ]; return (
{eventos.map((h, i) => (
{h.titulo}
{h.quando}
{h.descricao &&
{h.descricao}
}
))}
); } /* ── NF DRAWER (NF contra BTM) ─────────────────────────────── */ function NfDrawer({ nf, onClose, tab, onTab }) { const st = NF_STATUS_LABELS[nf.status]; return ( NF contra BTM · captura SEFAZM07{st.label}} title={NF {nf.nf} · {nf.emitente}} subtitle={`${nf.cnpj} · ${nf.uf} · emitida em ${nf.emissao} · vence ${nf.venc}`} headRight={
VALOR
{BRL(nf.valor)}
} tabs={[ { id: 'resumo', label: 'Resumo' }, { id: 'itens', label: 'Itens da NF' }, { id: 'aprov', label: 'Aprovação' }, { id: 'doc', label: 'DANFE / XML' }, ]} activeTab={tab} onTabChange={onTab} footer={ nf.status === 'pend' ? ( <> ) : ( <> ) } > {tab === 'resumo' && } {tab === 'itens' && } {tab === 'aprov' && } {tab === 'doc' && }
); } function NfResumo({ nf }) { return (
EMITENTE
{nf.emitente}
{nf.cnpj} · {nf.uf}
DADOS FISCAIS
OBSERVAÇÕES
{nf.observacao}
); } function NfItens({ nf }) { const itens = [ { desc: 'Papel A4 75g — Resma 500 fls', cod: 'BTM-PA-001', ncm: '4802.56.99', qtd: Math.round(nf.valor / 14.20 * 0.6), unit: 14.20, total: Math.round(nf.valor * 0.6) }, { desc: 'Caneta esferográfica azul 1.0mm', cod: 'BTM-CN-014', ncm: '9608.10.00', qtd: Math.round(nf.valor / 0.92 * 0.3), unit: 0.92, total: Math.round(nf.valor * 0.3) }, { desc: 'Diversos · agregado', cod: '—', ncm: '—', qtd: 1, unit: nf.valor * 0.1, total: Math.round(nf.valor * 0.1) }, ]; return ( {itens.map((it, i) => ( ))}
Item Descrição Cod NCM Qtd Unit Total
{String(i + 1).padStart(2, '0')} {it.desc} {it.cod} {it.ncm} {NUM(it.qtd)} {BRL(it.unit)} {BRL(it.total)}
Total: {BRL(nf.valor)}
); } function NfAprov({ nf }) { const [coment, setComent] = React.useState(''); return (
VALIDAÇÕES AUTOMÁTICAS
{[ { ok: true, t: 'CNPJ emitente cadastrado como fornecedor' }, { ok: true, t: 'XML válido (assinatura digital ok)' }, { ok: true, t: 'Valor da NF compatível com pedido de compras' }, { ok: nf.cfop === '6101', t: `CFOP ${nf.cfop} ${nf.cfop === '6101' ? 'compatível com triangulação' : 'requer verificação'}` }, { ok: true, t: 'Impostos calculados conforme alíquota esperada' }, { ok: nf.icms / nf.baseICMS < 0.1, t: `Alíquota ICMS ${((nf.icms / nf.baseICMS) * 100).toFixed(1)}% dentro do esperado` }, ].map((v, i) => (
{v.t}
))}
{nf.status === 'pend' && ( <>
COMENTÁRIO DA APROVAÇÃO (opcional)