// Brisas do Lago — Backoffice // Dashboard, Lotes, CRM, Carteira, Cobrança, Financeiro, Documentos, Relatórios const { useState, useMemo } = React; function BackofficeApp() { const [section, setSection] = useState('dashboard'); const sections = [ {id:'dashboard', lbl:'Dashboard', icon:'dashboard'}, {id:'lotes', lbl:'Lotes', icon:'layers'}, {id:'crm', lbl:'Funil & CRM', icon:'funnel'}, {id:'carteira', lbl:'Carteira', icon:'briefcase'}, {id:'cobranca', lbl:'Cobrança', icon:'invoice'}, {id:'financeiro', lbl:'Financeiro', icon:'coin'}, {id:'docs', lbl:'Documentos', icon:'file'}, {id:'relatorios', lbl:'Relatórios', icon:'sparkle'}, ]; return (
{/* Sidebar */}
{section === 'dashboard' && } {section === 'lotes' && } {section === 'crm' && } {section === 'carteira' && } {section === 'cobranca' && } {section === 'financeiro' && } {section === 'docs' && } {section === 'relatorios' && }
); } // ─── Dashboard ────────────────────────────────────────────── function BoDashboard() { const k = window.BL.kpis(); const [variation, setVariation] = useState('A'); return (
◇ Visão geral

Dashboard

21 Mai 2026 · atualizado há 2 minutos
{variation === 'A' ? : }
); } function DashboardA({k}) { return ( <> {/* Big hero KPI */}
◐ VGV total · 95 lotes
{fmtBRL(k.vgvTotal)}
{/* Second row */}
s+c.valorContrato*0.8/48, 0))} icon="invoice"/>
{/* Map + Funil */}
◇ Mapa de disponibilidade
Disp. Reserv. Negoc. Vendido
◇ Funil de vendas (5 meses)
{/* Atividade recente */}
◇ Atividade recente
◇ Top corretores · maio
{[ ['Júlia Mendes', 8, 1850000, '#C97B4A', 100], ['Rafael Pinto', 6, 1420000, '#2D4F3C', 77], ['Diego Aragão', 4, 980000, '#5B8FA8', 53], ].map(([n, vendas, vgv, c, pct]) => (
{n.split(' ').map(x=>x[0]).join('')}
{n}
{vendas} vendas
{fmtBRL(vgv)}
))}
); } function DashboardB({k}) { // Variação 2: dashboard mais denso, tabela return ( <>
{[ ['VGV total', fmtBRL(k.vgvTotal).replace(/\s/g,'')], ['Vendido', fmtBRL(k.vgvVendido).replace(/\s/g,'')], ['% vendido', (k.pctVendido*100).toFixed(0)+'%'], ['Disponível', k.disponiveis + ' lotes'], ['Carteira', fmtBRL(k.carteiraTotal).replace(/\s/g,'')], ['Inadimpl.', (k.inadimplenciaPct*100).toFixed(1)+'%'], ].map(([lbl, v]) => (
{lbl}
{v}
))}
◇ Distribuição por status
◇ Últimas atividades
{[ ['12 min', 'venda', 'Lote 4 · Henrique V.', 'Júlia M.', 214200, 'vendido'], ['35 min', 'pagamento', 'Lote 38 · Marcos P.', '—', 6317, 'pago'], ['1h', 'lead', 'Patrícia Nunes', 'Rafael P.', 0, 'lead'], ['2h', 'cobrança', 'Camila S. · Lote 9', '—', 7434, 'atraso'], ['3h', 'contrato', 'Lote 78 · Roberto M.', 'Rafael P.', 432000, 'contrato'], ['4h', 'reserva', 'Lote 88 · Ana R.', 'Júlia M.', 295880, 'reservado'], ['5h', 'simulação', 'Lote 9 · Carlos L.', 'Rafael P.', 446000, 'simulacao'], ['ontem', 'venda', 'Lote 22 · Vanessa L.', 'Diego A.', 215000, 'vendido'], ].map((r, i) => ( ))}
TempoTipoLote/ClienteCorretorValorStatus
{r[0]} {r[1]} {r[2]} {r[3]} {r[4] ? fmtBRL(r[4]) : '—'} {r[6]}
); } function Stat({lbl, v, sub, dark}) { return (
{lbl}
{v}
{sub}
); } function Atividade({icon, color, titulo, sub, tempo}) { return (
{titulo}
{sub}
{tempo}
); } function FunnelChart() { const data = [ {lbl:'Leads', v:128, c:'#9CB5A2'}, {lbl:'Visitas', v:54, c:'#7AA1AF'}, {lbl:'Simulações', v:32, c:'#5B8FA8'}, {lbl:'Propostas', v:21, c:'#C97B4A'}, {lbl:'Reservas', v:15, c:'#A8632F'}, {lbl:'Contratos', v:11, c:'#2D4F3C'}, ]; const max = data[0].v; return (
{data.map((d, i) => { const pct = d.v / max * 100; const conv = i > 0 ? (d.v / data[i-1].v * 100).toFixed(0) : null; return (
{d.lbl} {d.v} {conv && {conv}%}
); })}
); } function DonutChart({data, total}) { const radius = 60, c = 2 * Math.PI * radius; let offset = 0; return (
{data.map((d, i) => { const pct = d.v / total; const dash = c * pct; const el = ( ); offset += dash; return el; })} {total} LOTES
{data.map(d => (
{d.lbl} {d.v}
))}
); } // ─── Lotes ────────────────────────────────────────────────── function BoLotes() { const [filtroStatus, setFiltroStatus] = useState(null); const [filtroQ, setFiltroQ] = useState(null); const [view, setView] = useState('mapa'); const [hover, setHover] = useState(null); const [sel, setSel] = useState(null); const LOTES = window.BL.LOTES; const filtered = LOTES.filter(l => (!filtroStatus || l.status === filtroStatus) && (!filtroQ || l.quadra === filtroQ) ); return (
◇ Gestão

Lotes

95 lotes · 12 quadras · 67.345 m² comercializáveis
{/* Filter bar */}
{['disponivel','reservado','negociacao','vendido'].map(s => ( ))}
{['A','B','C','D','E','F','G','H','I','J','K','L'].map(q => ( ))}
{filtered.length} de {LOTES.length}
{view === 'mapa' && (
{sel ? ( setSel(null)}/> ) : hover ? ( ) : (
Passe o mouse ou clique em um lote
)}
)} {view === 'lista' && (
{filtered.slice(0, 30).map(l => ( ))}
LoteQuadraÁreaFrente R$/m²Valor VistaStatusAções
{l.numero} {l.quadra} {l.area.toFixed(0)}m² {l.frente.toFixed(1)}m R$ {l.precoM2} {fmtBRL(l.valorTotal)} {l.vistaLago ? '🌊 Lago' : 'Interior'}
)} {view === 'grid' && (
{filtered.map(l => (
setSel(l.numero)} className="card" style={{padding:14, cursor:'pointer', position:'relative'}}>
Quadra {l.quadra}
Lote {l.numero}
{l.area.toFixed(0)} m² · {l.frente.toFixed(1)}m
{fmtBRL(l.valorTotal)}
))}
)} {/* Modal de detalhe */} setSel(null)} width={520}> {sel &&
setSel(null)}/>
}
); } function BoLotePreview({loteNum}) { const l = window.BL.LOTE_BY_NUM[loteNum]; return ( <>
◇ Lote {l.numero} · Quadra {l.quadra}
{fmtBRL(l.valorTotal)}
{l.area.toFixed(0)} m² · {l.frente.toFixed(1)}m de frente · R$ {l.precoM2}/m²
); } function BoLoteDetail({loteNum, onClose}) { const l = window.BL.LOTE_BY_NUM[loteNum]; return (
◇ Quadra {l.quadra}
Lote {l.numero}
{l.status === 'disponivel' && } {l.status === 'vendido' && }
); } const Th = ({children, align='left'}) => {children}; const Td = ({children, align='left', mono, muted}) => {children}; // ─── CRM / Funil ──────────────────────────────────────────── function BoCrm() { const [view, setView] = useState('kanban'); return (
◇ Vendas

Funil & CRM

{[['kanban','Kanban','grid'],['lista','Lista','list']].map(([k, lbl, ic]) => ( ))}
{view === 'kanban' ? : }
); } function CrmKanban() { const etapas = window.BL.ETAPAS_FUNIL; const leads = window.BL.LEADS; return (
{etapas.map(e => { const ls = leads.filter(l => l.etapa === e.id); const totalVal = ls.reduce((s,l)=>s+l.valor, 0); return (
{e.nome}
{ls.length}
{totalVal > 0 ? fmtBRL(totalVal) : '—'}
{ls.map(l => (
{l.nome}
{l.cidade}
{l.lote &&
L{l.lote}
}
{l.valor > 0 &&
{fmtBRL(l.valor)}
}
{l.corretor} {l.ultimoContato}
))}
); })}
); } function CrmLista() { return (
{window.BL.LEADS.map(l => ( ))}
LeadCidadeInteresseOrigemEtapaLoteValorCorretorÚltimo
{l.nome} {l.cidade} {l.interesse} {l.origem} {window.BL.ETAPAS_FUNIL.find(e => e.id === l.etapa)?.nome} {l.lote || '—'} {l.valor > 0 ? fmtBRL(l.valor) : '—'} {l.corretor} {l.ultimoContato}
); } // ─── Carteira ─────────────────────────────────────────────── function BoCarteira() { return (
s+c.valorContrato,0))} em carteira`}/>
{window.BL.CLIENTES.map(c => { const pct = c.parcelasPagas / c.parcelasTotal * 100; return ( ); })}
ClienteLoteContratoAndamentoStatusPróx. boletoAções
{c.nome.split(' ').map(x=>x[0]).slice(0,2).join('')}
{c.nome}
L{c.lote} · Q.{window.BL.LOTE_BY_NUM[c.lote]?.quadra} {fmtBRL(c.valorContrato)}
{c.parcelasPagas}/{c.parcelasTotal}
{c.proxBoleto} {c.atraso > 0 && · {c.atraso}d atraso}
); } // ─── Cobrança ─────────────────────────────────────────────── function BoCobranca() { const atrasados = window.BL.CLIENTES.filter(c => c.atraso > 0).sort((a,b)=>b.atraso-a.atraso); return (
s+c.valorContrato*0.8/48, 0))} a receber`} icon="warn" color="var(--terra)"/> c.atraso)) + ' dias'} sub="Sílvia Maia · Lote 56" icon="clock" color="var(--danger)"/>
◇ Clientes em atraso
{atrasados.map(c => ( ))}
ClienteLoteAtrasoValorÚltima açãoAções
{c.nome} L{c.lote} 30 ? '#F4CFC9' : 'var(--terra-soft)', color: c.atraso > 30 ? '#8B2A1F' : 'var(--terra-deep)'}}>{c.atraso} dias {fmtBRL(c.valorContrato * 0.8 / 48)} WhatsApp · há 2d
◇ Régua automática
{[ ['D-3', 'Lembrete amigável', 'WhatsApp', 'var(--green)'], ['D-1', 'Lembrete urgente', 'E-mail + WhatsApp', 'var(--lago)'], ['D+1', 'Primeira cobrança', 'E-mail', 'var(--terra)'], ['D+7', 'Cobrança formal', 'E-mail + WhatsApp', 'var(--terra-deep)'], ['D+15', 'Notificação extrajudicial', 'Carta + e-mail', 'var(--danger)'], ['D+30', 'Protesto + escritório', 'Manual', '#8B2A1F'], ].map(([d, t, ch, c]) => (
{d}
{t}
{ch}
))}
); } // ─── Financeiro ───────────────────────────────────────────── function BoFinanceiro() { return (
◇ Fluxo de caixa · 12 meses
◇ Comissões a pagar
{[ ['Júlia Mendes', 8, 1850000, 74000], ['Rafael Pinto', 6, 1420000, 56800], ['Diego Aragão', 4, 980000, 39200], ].map((r,i) => ( ))}
CorretorVendasVGVComissão
{r[0]} {r[1]} {fmtBRL(r[2])} {fmtBRL(r[3])}
◇ Despesas operacionais · mai
{[ ['Obra · pavimentação', 184000, 920000], ['Obra · rede elétrica', 68000, 312000], ['Marketing · ads', 12000, 64000], ['Comissões corretores', 62400, 280000], ['Folha + impostos', 38000, 190000], ['Manutenção portaria', 8400, 42000], ].map((r,i) => ( ))}
CategoriaMêsAcum.
{r[0]} {fmtBRL(r[1])} {fmtBRL(r[2])}
); } function CashflowChart() { const data = [ {m:'Jun/25', in:280000, out:240000}, {m:'Jul/25', in:320000, out:260000}, {m:'Ago/25', in:380000, out:280000}, {m:'Set/25', in:410000, out:300000}, {m:'Out/25', in:445000, out:310000}, {m:'Nov/25', in:420000, out:320000}, {m:'Dez/25', in:380000, out:330000}, {m:'Jan/26', in:435000, out:280000}, {m:'Fev/26', in:460000, out:300000}, {m:'Mar/26', in:475000, out:340000}, {m:'Abr/26', in:432000, out:340000}, {m:'Mai/26', in:485600, out:372400}, ]; const max = Math.max(...data.flatMap(d=>[d.in, d.out])); return (
{data.map(d => (
{d.m}
))}
); } // ─── Documentos ───────────────────────────────────────────── function BoDocs() { return (
{[ ['Contratos assinados', 47, 'file', 'var(--green)'], ['Aguardando assinatura', 6, 'edit', 'var(--terra)'], ['Plantas técnicas', 12, 'layers', 'var(--lago)'], ['Convenção condomínio', 1, 'file', 'var(--green-deep)'], ['Memoriais descritivos', 4, 'file', 'var(--ink-soft)'], ['Licenças e alvarás', 8, 'check', 'var(--success)'], ].map(([t, n, ic, c]) => (
{t}
{n} {n === 1 ? 'documento' : 'documentos'}
))}
◇ Atividade recente
{[ ['contrato-lote-04-vasconcelos.pdf', 'Contrato', 'Henrique Vasconcelos', '2.1 MB', 'hoje 11:42', 'assinado'], ['boleto-parcela-30-pinto.pdf', 'Boleto', 'Rodrigo Pinto', '180 KB', 'hoje 09:15', 'pago'], ['rg-rocha-frente.jpg', 'Documento pessoal', 'Ana Carolina Rocha', '850 KB', 'ontem 17:20', 'recebido'], ['planta-lote-93.pdf', 'Planta', 'João Pedro Sá', '720 KB', '20/mai 14:30', 'enviado'], ['memorial-empreendimento-v3.pdf', 'Memorial', '—', '4.2 MB', '18/mai', 'publicado'], ].map((r, i) => ( ))}
DocumentoTipoClienteTamanhoDataStatus
{r[0]} {r[1]} {r[2]} {r[3]} {r[4]} {r[5]}
); } // ─── Relatórios ───────────────────────────────────────────── function BoRelatorios() { return (
{[ ['Vendas do mês', 'Detalhes de cada venda, comissão, ticket médio', 'mai/2026', 'PDF'], ['VGV por quadra', 'Distribuição de valor de venda por bloco', 'tempo real', 'XLSX'], ['Funil de conversão', 'Lead → visita → simulação → contrato', '90 dias', 'PDF'], ['Inadimplência detalhada', 'Por cliente, idade da dívida, ações da régua', 'mai/2026', 'PDF'], ['Andamento da obra', 'Cronograma físico-financeiro', 'tempo real', 'PDF'], ['Comissões pagas', 'Por corretor e período', 'mai/2026', 'XLSX'], ['Vendas por origem', 'Eficiência de canais de marketing', '90 dias', 'PDF'], ['Demonstrativo de caixa', 'Entradas e saídas mensais', 'maio', 'PDF'], ].map(([t, sub, periodo, fmt]) => (
{t}
{fmt}
{sub}
{periodo}
))}
); } window.BackofficeApp = BackofficeApp;