// 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 */}
{[
['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('')}
{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]) => (
))}
◇ Distribuição por status
| Tempo | Tipo | Lote/Cliente | Corretor | Valor | Status |
{[
['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) => (
| {r[0]} |
{r[1]} |
{r[2]} |
{r[3]} |
{r[4] ? fmtBRL(r[4]) : '—'} |
{r[6]} |
))}
>
);
}
function Stat({lbl, v, sub, dark}) {
return (
);
}
function Atividade({icon, color, titulo, sub, tempo}) {
return (
);
}
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 => (
{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' && (
| Lote | Quadra | Área | Frente |
R$/m² | Valor |
Vista | Status | Ações |
{filtered.slice(0, 30).map(l => (
| {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 (
{[['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 (
{totalVal > 0 ? fmtBRL(totalVal) : '—'}
{ls.map(l => (
{l.valor > 0 &&
{fmtBRL(l.valor)}
}
{l.corretor}
{l.ultimoContato}
))}
);
})}
);
}
function CrmLista() {
return (
| Lead | Cidade | Interesse | Origem | Etapa | Lote | Valor | Corretor | Último |
{window.BL.LEADS.map(l => (
| {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`}/>
| Cliente | Lote | Contrato | Andamento | Status | Próx. boleto | Ações |
{window.BL.CLIENTES.map(c => {
const pct = c.parcelasPagas / c.parcelasTotal * 100;
return (
{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
| Cliente | Lote | Atraso | Valor | Última ação | Ações |
{atrasados.map(c => (
| {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]) => (
))}
);
}
// ─── Financeiro ─────────────────────────────────────────────
function BoFinanceiro() {
return (
◇ Fluxo de caixa · 12 meses
| Corretor | Vendas | VGV | Comissão |
{[
['Júlia Mendes', 8, 1850000, 74000],
['Rafael Pinto', 6, 1420000, 56800],
['Diego Aragão', 4, 980000, 39200],
].map((r,i) => (
| {r[0]} |
{r[1]} |
{fmtBRL(r[2])} |
{fmtBRL(r[3])} |
))}
◇ Despesas operacionais · mai
| Categoria | Mês | Acum. |
{[
['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) => (
| {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 (
);
}
// ─── 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
| Documento | Tipo | Cliente | Tamanho | Data | Status |
{[
['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) => (
| {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]) => (
))}
);
}
window.BackofficeApp = BackofficeApp;