Usuário ou senha incorretos. Tente novamente.

Carregando dados...

Todas as Clínicas

0)||(_rawChk&&_rawChk.length>10);if(\!_hasReal){DATA=gerarDadosDemostracao();window._MODO_DEMO=true;}else{window._MODO_DEMO=false;} } } else { window._MODO_DEMO=false; try {const res = await fetch(SCRIPT_URL + '?action=all&t=' + Date.now());const json = await res.json();if (json.sucesso) {DATA.pipeline = json.pipeline || [];DATA.metas = json.metas || [];DATA.recebimentos = json.recebimentos || [];DATA.historico = json.historico || [];DATA.config = json.config || [];}} catch (err) {console.warn('Erro ao carregar dados:', err);if (!DATA.pipeline.length) DATA = gerarDadosDemostracao();}}(function(){var _el=document.getElementById('footer-last-update');if(_el)_el.textContent='Atualizado: '+new Date().toLocaleTimeString('pt-BR');})();renderAll();document.getElementById('loading').style.display = 'none';}function setFilter(tipo, mesVal) {FILTER.tipo = tipo;if (tipo === 'mes-custom' && mesVal) FILTER.mesCustom = mesVal;if (tipo === 'mes-custom') {const sel = document.getElementById('mes-select');FILTER.mesCustom = sel.value || null;}document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));const mapa = { hoje: 0, semana: 1, mes: 2 };const btns = document.querySelectorAll('.filter-btn');if (tipo !== 'mes-custom' && mapa[tipo] !== undefined) btns[mapa[tipo]].classList.add('active');renderAll();}function filtrarPorData(registros) {const hoje = new Date();hoje.setHours(23, 59, 59, 999);const hojeStr = hoje.toISOString().split('T')[0];return registros.filter(r => {const dataStr = r.data_registro || r.data_venda || r.data_recebimento || '';if (!dataStr) return false;const d = new Date(dataStr + 'T00:00:00');if (FILTER.tipo === 'hoje') {return dataStr === hojeStr;}if (FILTER.tipo === 'semana') {const inicio = new Date(hoje);inicio.setDate(hoje.getDate() - hoje.getDay());inicio.setHours(0, 0, 0, 0);return d >= inicio && d <= hoje;}if (FILTER.tipo === 'mes') {const mes = hoje.toISOString().substring(0, 7);return dataStr.startsWith(mes);}if (FILTER.tipo === 'mes-custom' && FILTER.mesCustom) {return dataStr.startsWith(FILTER.mesCustom);}return true;});}function calcMetrics(pipeline, unit) {const base = unit === 'all'? pipeline: pipeline.filter(r => r.unidade === unit);const filtrado = filtrarPorData(base);const vendas = filtrado.filter(r => r.status === 'Vendido');const leads = filtrado;const agendados = filtrado.filter(r =>['Agendado','Consultado','Vendido'].includes(r.status));const consultados = filtrado.filter(r =>['Consultado','Vendido'].includes(r.status));const online = consultados.filter(r => r.tipo_consulta === 'Online');const presencial = consultados.filter(r => r.tipo_consulta === 'Presencial');const faturamento = vendas.reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0);const ticket = vendas.length ? faturamento / vendas.length : 0;const conv = leads.length ? (vendas.length / leads.length * 100) : 0;const hoje = new Date().toISOString().split('T')[0];const vendasHoje = base.filter(r =>r.status === 'Vendido' &&(r.data_venda || r.data_registro || '').startsWith(hoje));const fatHoje = vendasHoje.reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0);const mesAtual = new Date().toISOString().substring(0, 7);const vendasMes = base.filter(r =>r.status === 'Vendido' &&(r.data_venda || r.data_registro || '').startsWith(mesAtual));const fatMes = vendasMes.reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0);return {leads: leads.length,agendados: agendados.length,consultados: consultados.length,vendas: vendas.length,faturamento,ticket,conv,online: online.length,presencial: presencial.length,pctOnline: consultados.length ? (online.length / consultados.length * 100) : 0,pctConvConsulta: consultados.length ? (vendas.length / consultados.length * 100) : 0,fatHoje,fatMes};}function getMeta(unit, vendedor) {const mesAtual = new Date().toISOString().substring(0, 7);const m = DATA.metas.find(r =>r.mes_ano === mesAtual &&r.unidade === (unit === 'all' ? 'GERAL' : unit) &&(r.vendedor === (vendedor || 'GERAL') || r.vendedor === 'GERAL'));return m ? {mensal: parseFloat(m.meta_mensal) || 0,semanal: parseFloat(m.meta_semanal) || 0} : { mensal: 0, semanal: 0 };}function fmtCurrency(v) {return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 }).format(v || 0);}function fmtPct(v) { return (v || 0).toFixed(1) + '%'; }function fmtNum(v) { return new Intl.NumberFormat('pt-BR').format(v || 0); } // ════════════════════════════════════════════ // BANNER MOTIVACIONAL (meta semanal faturamento) // ════════════════════════════════════════════ function renderBannerMotivacional() { try { const el = document.getElementById('banner-motivacional'); if (!el || !currentUser) { if(el) el.style.display='none'; return; } const uid = currentUser.usuario || currentUser.id; const m = muGet(uid); const metaMensal = parseFloat(m.aplica_faturamento !== false ? m.faturamento : 0) || 0; if (!metaMensal) { el.style.display='none'; return; } const metaSemanal = metaMensal / 4; const realSemanal = getUserMetrics(uid, 'semana').faturamento || 0; const pct = metaSemanal > 0 ? (realSemanal / metaSemanal) * 100 : 0; let msg, bg, color, cls = ''; if (pct >= 100) { msg = '🏆 PARABÉNS, META BATIDA. BATA RECORDES AGORA!'; bg = '#00ff41'; color = '#000'; cls = 'flash-meta'; } else if (pct > 70) { msg = '💪 FALTA POUQUÍSSIMO, FOCO TOTAL NO OBJETIVO, VOCÊ VAI CONSEGUIR!'; bg = '#1b5e20'; color = '#69f0ae'; } else if (pct > 50) { msg = '🔵 FALTA POUCO PARA QUE SUA META SEJA CUMPRIDA, SEJA RESILIENTE!'; bg = '#0d47a1'; color = '#90caf9'; } else if (pct > 30) { msg = '⚡ VOCÊ AVANÇOU E NÃO DUVIDE QUE É CAPAZ DE CUMPRIR SUA META'; bg = '#f57f17'; color = '#000'; } else { msg = '🔥 VAMOS LÁ, A SUPREMA FIO ACREDITA E CONFIA EM VOCÊ'; bg = '#b71c1c'; color = '#fff'; } el.className = 'banner-motivacional' + (cls ? ' ' + cls : ''); el.style.cssText = 'display:block;background:' + bg + ';color:' + color + ';' + 'padding:11px 24px;text-align:center;font-size:.97rem;font-weight:900;' + 'letter-spacing:.06em;text-transform:uppercase;position:sticky;top:0;z-index:200;' + 'box-shadow:0 2px 10px rgba(0,0,0,.35);'; if (cls) el.style.animation = 'flash-meta-batida .7s infinite'; else el.style.animation = ''; const subtext = '  ·  ' + fmtCurrency(realSemanal) + ' / ' + fmtCurrency(metaSemanal) + ' (' + pct.toFixed(0) + '%)'; el.innerHTML = msg + '' + subtext + ''; } catch(e) { console.warn('Banner motivacional:', e); } } // ════════════════════════════════════════════ // PROGRESSO MENSAL (geral, por clínica, por usuário) // ════════════════════════════════════════════ function renderProgressoMensal() { try { const wrap = document.getElementById('progresso-mensal-wrap'); if (!wrap) return; const mesAtual = new Date().toISOString().substring(0, 7); const pipeline = DATA.pipeline || []; function fatMes(arr) { return arr.filter(r => r.status === 'Vendido' && (r.data_venda || r.data_registro || '').startsWith(mesAtual)) .reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0); } function pmRow(label, real, meta, cor, bold) { const pct = meta > 0 ? Math.min((real / meta) * 100, 110) : 0; const barPct = Math.min(pct, 100); const c = pct >= 100 ? '#00e676' : pct >= 70 ? '#69f0ae' : pct >= 50 ? '#42a5f5' : pct >= 30 ? '#ffa726' : '#ef5350'; const color = cor || c; return '
' + '
' + esc(label) + '
' + '
' + '
' + pct.toFixed(0) + '%
' + '
' + fmtCurrency(real) + '
meta ' + fmtCurrency(meta) + '
' + '
'; } // GERAL const totalReal = fatMes(pipeline); const metaGeral = getMeta('all').mensal || 0; let html = '

Progresso de Faturamento — ' + mesAtual.replace('-','/') + '

'; html += '
Resultado Geral
'; html += pmRow('TODAS AS CLÍNICAS', totalReal, metaGeral, null, true); // POR CLÍNICA html += '
Por Clínica
'; UNITS.forEach(u => { const real = fatMes(pipeline.filter(r => r.unidade === u)); const meta = getMeta(u).mensal || 0; if (!real && !meta) return; html += pmRow((UNIT_NAMES[u] || u), real, meta, UNIT_COLORS[u], false); }); // POR USUÁRIO const usuariosAtivos = (window.USUARIOS || []).filter(u => u.ativo !== false && u.role !== 'master' && u.role !== 'admin'); if (usuariosAtivos.length) { html += '
Por Consultor/SDR
'; usuariosAtivos.forEach(u => { const uid = u.usuario || u.id; const m = muGet(uid); const metaU = parseFloat(m.aplica_faturamento !== false ? m.faturamento : 0) || 0; const realU = getUserMetrics(uid, 'mes').faturamento || 0; if (!realU && !metaU) return; html += pmRow(u.nome || uid, realU, metaU, null, false); }); } wrap.innerHTML = html; wrap.style.display = ''; } catch(e) { console.warn('Progresso mensal:', e); } } // ════════════════════════════════════════════ // AVISOS DE SISTEMA (demo / fallback local) // ════════════════════════════════════════════ function renderAvisoSistema() { try { const wrap = document.getElementById('aviso-sistema'); if (!wrap) return; const avisos = []; if (window._MODO_DEMO) { avisos.push('
' + '⚠️ DADOS DEMO ATIVOS — Configure a URL do Google Apps Script nas ' + '⚙️ Configurações para visualizar dados reais da empresa.' + '
'); } const isLocalUsers = typeof window.USUARIOS !== 'undefined' && typeof DEFAULT_USUARIOS !== 'undefined' && window.USUARIOS === DEFAULT_USUARIOS; if (isLocalUsers && typeof _supabase !== 'undefined' && currentUser) { avisos.push('
' + '⚠️ USUÁRIOS LOCAIS — Sincronização com Supabase pendente. ' + 'Dados de acesso carregados do cache local.' + '
'); } wrap.innerHTML = avisos.join(''); wrap.style.display = avisos.length ? '' : 'none'; } catch(e) { console.warn('renderAvisoSistema:', e); } } function renderAll() {renderAvisoSistema();renderBannerMotivacional();renderProgressoMensal();renderOverview();UNITS.forEach(u => renderUnit(u));renderRankings();renderMetas();renderComparativo();renderReceitas();}function renderOverview() {const el = document.getElementById('page-all');const m = calcMetrics(DATA.pipeline, 'all');const meta = getMeta('all');const hojeStr = new Date().toISOString().split('T')[0];const mesStr = new Date().toISOString().substring(0, 7);const unitsRow = UNITS.map(u => {const vendasHojeU = DATA.pipeline.filter(r =>r.unidade === u && r.status === 'Vendido' &&(r.data_venda || r.data_registro || '').startsWith(hojeStr));const fatU = vendasHojeU.reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0);return `
${UNIT_NAMES[u]}
${fmtCurrency(fatU).replace('R$','')}
Faturamento hoje
`;}).join('');const metaPct = meta.mensal ? Math.min((m.fatMes / meta.mensal) * 100, 100) : 0;const diasMes = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();const diaAtual = new Date().getDate();const diasRestantes = diasMes - diaAtual + 1;const vendasDiaParaMeta = (meta.mensal && diasRestantes > 0)? Math.max(0, (meta.mensal - m.fatMes) / diasRestantes): 0;el.innerHTML = `

Visão Geral da Rede

Todas as Clínicas
Faturamento Hoje — Rede
${fmtCurrency(m.fatHoje).replace('R$','')}
${m.vendas} vendas no período selecionado
Faturamento do Mês
${fmtCurrency(m.fatMes).replace('R$','')}
Acumulado ${mesStr}
${meta.mensal ? `
${fmtPct(metaPct)} da meta
` : ''}
Ticket Médio
${fmtCurrency(m.ticket).replace('R$','')}
${m.vendas} vendas no período
Taxa de Conversão
${fmtPct(m.conv)}
${m.vendas} vendas / ${m.leads} leads

Faturamento Hoje por Unidade

${unitsRow}

Pipeline de Vendas

Total de Leads
${fmtNum(m.leads)}
No período selecionado
Agendamentos
${fmtNum(m.agendados)}
${m.leads ? fmtPct(m.agendados/m.leads*100) : '0%'} dos leads
Consultas
${fmtNum(m.consultados)}
${m.online} online · ${m.presencial} presencial
Vendas Fechadas
${fmtNum(m.vendas)}
${fmtPct(m.pctConvConsulta)} das consultas
Faturamento por Unidade
Consulta Online vs Presencial
Evolução Diária de Faturamento (mês atual)
${meta.mensal ? `

Meta do Mês — Rede

Progresso Meta Mensal
${fmtPct(metaPct)}
${fmtCurrency(m.fatMes)} de ${fmtCurrency(meta.mensal)}
Necessário por dia para bater a meta:
${fmtCurrency(vendasDiaParaMeta)}/dia nos próximos ${diasRestantes} dias
` : ''} `; renderChartUnits('chart-all-units'); renderChartTipo('chart-all-tipo', m); renderChartDiario('chart-all-diario', 'all'); } function renderUnit(unit) { const el = document.getElementById('page-' + unit); if (!el) return; const m = calcMetrics(DATA.pipeline, unit); const meta = getMeta(unit); const color = UNIT_COLORS[unit]; const mesStr = new Date().toISOString().substring(0, 7); const metaPct = meta.mensal ? Math.min((m.fatMes / meta.mensal) * 100, 100) : 0; const diasMes = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); const diaAtual = new Date().getDate(); const diasRestantes = diasMes - diaAtual + 1; const vendasDiaParaMeta = (meta.mensal && diasRestantes > 0) ? Math.max(0, (meta.mensal - m.fatMes) / diasRestantes) : 0; const filtrado = filtrarPorData(DATA.pipeline.filter(r => r.unidade === unit && r.status === 'Vendido')); const vendedores = {}; filtrado.forEach(r => { const v = r.vendedor || r.agente || 'Desconhecido'; vendedores[v] = (vendedores[v] || 0) + (parseFloat(r.valor_venda) || 0); }); const vendRanking = Object.entries(vendedores).sort((a, b) => b[1] - a[1]); const rankRows = vendRanking.map(([nome, fat], i) => { const ticket = filtrado.filter(r => (r.vendedor||r.agente) === nome).length; const rankClass = i === 0 ? 'rank-1' : i === 1 ? 'rank-2' : i === 2 ? 'rank-3' : 'rank-n'; return ` ${i+1} ${nome} ${fmtCurrency(fat)} ${fmtNum(ticket)} ${ticket ? fmtCurrency(fat/ticket) : '—'} `; }).join('') || 'Sem dados'; const convRows = (() => { const filtradoAll = filtrarPorData(DATA.pipeline.filter(r => r.unidade === unit)); const agentes = {}; filtradoAll.forEach(r => { const a = r.agente || r.vendedor || 'Desconhecido'; if (!agentes[a]) agentes[a] = { leads: 0, vendas: 0 }; agentes[a].leads++; if (r.status === 'Vendido') agentes[a].vendas++; }); return Object.entries(agentes).sort((a, b) => b[1].vendas - a[1].vendas).map(([nome, d], i) => { const taxa = d.leads ? (d.vendas / d.leads * 100) : 0; const rankClass = i === 0 ? 'rank-1' : i === 1 ? 'rank-2' : i === 2 ? 'rank-3' : 'rank-n'; return ` ${i+1} ${nome} ${fmtNum(d.leads)} ${fmtNum(d.vendas)} ${fmtPct(taxa)} `; }).join('') || 'Sem dados'; })(); el.innerHTML = `

${UNIT_NAMES[unit]}

${unit}
Faturamento Hoje
${fmtCurrency(m.fatHoje).replace('R$','')}
Atualizado em tempo real
Faturamento do Mês
${fmtCurrency(m.fatMes).replace('R$','')}
Acumulado ${mesStr}
${meta.mensal ? `
${fmtPct(metaPct)} da meta
` : ''}
Ticket Médio
${fmtCurrency(m.ticket).replace('R$','')}
${m.vendas} vendas
Taxa de Conversão
${fmtPct(m.conv)}
${m.vendas} / ${m.leads} leads
Leads Recebidos
${fmtNum(m.leads)}
No período selecionado
Agendamentos
${fmtNum(m.agendados)}
${m.leads ? fmtPct(m.agendados/m.leads*100) : '0%'} dos leads
Consultas
${fmtNum(m.consultados)}
${m.online} online · ${m.presencial} presencial
% Consultas Online
${fmtPct(m.pctOnline)}
${fmtPct(100 - m.pctOnline)} presencial
Faturamento por Vendedor
Consultas Online vs Presencial
Evolução Diária — ${UNIT_NAMES[unit]}

Ranking de Vendedores — ${UNIT_NAMES[unit]}

${rankRows}
#VendedorFaturamentoVendasTicket Médio

Taxa de Conversão por Agente

${convRows}
#AgenteLeadsVendasConversão
${meta.mensal ? `

Metas — ${UNIT_NAMES[unit]}

Meta Mensal
${fmtPct(metaPct)}
${fmtCurrency(m.fatMes)} de ${fmtCurrency(meta.mensal)}
Necessário: ${fmtCurrency(vendasDiaParaMeta)}/dia nos próximos ${diasRestantes} dias
` : `
Nenhuma meta definida para este mês.
Use o formulário para definir metas.
`} `; renderChartVendedor('chart-' + unit + '-vend', unit, color); renderChartTipo('chart-' + unit + '-tipo', m, color); renderChartDiario('chart-' + unit + '-diario', unit, color); } function renderRankings() { const el = document.getElementById('page-ranking'); const mesStr = new Date().toISOString().substring(0, 7); const inicioSemana = new Date(); inicioSemana.setDate(inicioSemana.getDate() - inicioSemana.getDay()); function rankVendedores(filtroFn, label) { const filtrado = DATA.pipeline.filter(r => r.status === 'Vendido' && filtroFn(r)); const vend = {}; filtrado.forEach(r => { const v = r.vendedor || r.agente || 'Desconhecido'; const u = r.unidade || '?'; if (!vend[v]) vend[v] = { fat: 0, vendas: 0, unidade: u }; vend[v].fat += parseFloat(r.valor_venda) || 0; vend[v].vendas++; }); const rows = Object.entries(vend).sort((a, b) => b[1].fat - a[1].fat).slice(0, 10) .map(([nome, d], i) => { const rc = i===0?'rank-1':i===1?'rank-2':i===2?'rank-3':'rank-n'; return ` ${i+1} ${nome} ${d.unidade} ${fmtCurrency(d.fat)} ${d.vendas} ${fmtCurrency(d.fat/d.vendas)} `; }).join('') || 'Sem dados'; return `

${label}

${rows}
#VendedorUnidadeFaturamentoVendasTicket Médio
`; } function rankUnidades(filtroFn, label) { const filtrado = DATA.pipeline.filter(r => r.status === 'Vendido' && filtroFn(r)); const unid = {}; filtrado.forEach(r => { const u = r.unidade || '?'; if (!unid[u]) unid[u] = { fat: 0, vendas: 0 }; unid[u].fat += parseFloat(r.valor_venda) || 0; unid[u].vendas++; }); const rows = Object.entries(unid).sort((a, b) => b[1].fat - a[1].fat) .map(([u, d], i) => { const rc = i===0?'rank-1':i===1?'rank-2':i===2?'rank-3':'rank-n'; const nome = UNIT_NAMES[u] || u; return ` ${i+1} ${u} ${nome} ${fmtCurrency(d.fat)} ${d.vendas} ${d.vendas ? fmtCurrency(d.fat/d.vendas) : '—'} `; }).join('') || 'Sem dados'; return `

${label}

${rows}
#UnidadeFaturamentoVendasTicket Médio
`; } const semanaFn = r => { const d = new Date((r.data_venda || r.data_registro || '') + 'T00:00:00'); return d >= inicioSemana; }; const mesFn = r => (r.data_venda || r.data_registro || '').startsWith(mesStr); el.innerHTML = `

Rankings

Vendedores — Semana

${rankVendedores(semanaFn, 'Top Vendedores da Semana')}

Vendedores — Mês

${rankVendedores(mesFn, 'Top Vendedores do Mês')}

Unidades — Semana

${rankUnidades(semanaFn, 'Ranking Unidades da Semana')}

Unidades — Mês

${rankUnidades(mesFn, 'Ranking Unidades do Mês')}
`; } function renderMetas() { const el = document.getElementById('page-metas'); if (!window.METAS_USUARIOS) { window.METAS_USUARIOS = JSON.parse(localStorage.getItem('sf_metas_usuarios') || '{}'); if (typeof _supabase !== 'undefined' && _supabase) { const _mesMeta = new Date().toISOString().substring(0, 7); _supabase.from('metas_usuarios').select('*').eq('mes', _mesMeta) .then(({ data: _d, error: _e }) => { if (!_e && _d && _d.length) { _d.forEach(r => { const k = r.mes + '_' + r.usuario_id; window.METAS_USUARIOS[k] = { leads: r.leads||0, agendamentos: r.agendamentos||0, consultas: r.consultas||0, vendas: r.vendas||0, faturamento: r.faturamento||0, aplica_leads: r.aplica_leads !== false, aplica_agendamentos: r.aplica_agendamentos !== false, aplica_consultas: r.aplica_consultas !== false, aplica_vendas: r.aplica_vendas !== false, aplica_faturamento: r.aplica_faturamento || false }; }); localStorage.setItem('sf_metas_usuarios', JSON.stringify(window.METAS_USUARIOS)); renderMetas(); } }); } } const podeEditar = ROLES_GERENCIAIS.includes(currentUser?.role); const mesStr = new Date().toISOString().substring(0, 7); const hoje = new Date().toISOString().split('T')[0]; const diasMes = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); const diaAtual = new Date().getDate(); const iniSemana = new Date(); iniSemana.setDate(iniSemana.getDate() - iniSemana.getDay()); iniSemana.setHours(0,0,0,0); const iniSemStr = iniSemana.toISOString().split('T')[0]; const usuariosAtivos = USUARIOS.filter(u => u.role !== 'founder'); if (!window.META_EDIT_USER && usuariosAtivos.length) window.META_EDIT_USER = usuariosAtivos[0].usuario || usuariosAtivos[0].id; const usuarioEditando = window.META_EDIT_USER; function muGet(uid) { const key = mesStr + '_' + uid; return window.METAS_USUARIOS[key] || {}; } function getUserMetrics(uid, periodo) { const usr = USUARIOS.find(x => (x.usuario||x.id) === uid); if (!usr) return { leads:0, agendamentos:0, consultas:0, vendas:0, faturamento:0 }; const base = DATA.pipeline.filter(r => { const a = r.agente || r.vendedor || ''; return a === usr.nome || a === usr.usuario; }); const filt = base.filter(r => { const ds = r.data_registro || r.data_venda || ''; if (periodo === 'hoje') return ds === hoje; if (periodo === 'semana') return ds >= iniSemStr; if (periodo === 'mes') return ds.startsWith(mesStr); return true; }); const vend = filt.filter(r => r.status === 'Vendido'); return { leads: filt.length, agendamentos: filt.filter(r => ['Agendado','Consultado','Vendido'].includes(r.status)).length, consultas: filt.filter(r => ['Consultado','Vendido'].includes(r.status)).length, vendas: vend.length, faturamento: vend.reduce((s,r) => s+(parseFloat(r.valor_venda)||0), 0) }; } function progBar(real, meta, cor) { if (!meta) return 'sem meta'; const pct = Math.min((real/meta)*100, 100); const cl = pct < 40 ? '#ff4444' : pct < 80 ? '#ff9800' : '#00e676'; return `
${pct.toFixed(0)}%
`; } // === EDITOR (gerentes) === const editorHTML = podeEditar ? (() => { const u = USUARIOS.find(x => (x.usuario||x.id) === usuarioEditando); const m = muGet(usuarioEditando); const tabs = usuariosAtivos.map(usr => { const uid = usr.usuario||usr.id; const active = uid === usuarioEditando; return ``; }).join(''); if (!u) return ''; const campos = [ { id:'leads', label:'Leads / mês', icon:'fa-user-plus', tipo:'int' }, { id:'agendamentos', label:'Agendamentos / mês', icon:'fa-calendar-check',tipo:'int' }, { id:'consultas', label:'Consultas / mês', icon:'fa-stethoscope', tipo:'int' }, { id:'vendas', label:'Vendas / mês', icon:'fa-handshake', tipo:'int' }, { id:'faturamento', label:'Faturamento / mês', icon:'fa-dollar-sign', tipo:'float'} ]; const inp = `background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:7px 11px;color:var(--text);font-size:.88rem;width:150px`; const camposHTML = campos.map(c => { const aplica = c.id === 'faturamento' ? !!m.aplica_faturamento : m[`aplica_${c.id}`] !== false; const val = m[c.id] || 0; return `
${aplica ? `` : `Não se aplica`}
`; }).join(''); const diasInfo = `Meta diária = mensal ÷ ${diasMes} dias. Meta semanal = mensal ÷ 4.`; return `

Configurar Metas por Agente

${tabs}
${u.nome.charAt(0)}
${u.nome}
${ROLE_LABELS[u.role]||u.role} · ${u.unidade||'—'}
Auto-salvo
${diasInfo}
${camposHTML}
`; })() : ''; // === PROGRESSO POR AGENTE === const COR_METRICA = { leads:'#5c6bc0', agendamentos:'#26a69a', consultas:'#ec407a', vendas:'#00e676', faturamento:'#c9a84c' }; const LABEL_METRICA = { leads:'Leads', agendamentos:'Agendam.', consultas:'Consultas', vendas:'Vendas', faturamento:'Faturamento' }; const fmtM = (k,v) => k === 'faturamento' ? fmtCurrency(v) : fmtNum(v); const progressCards = usuariosAtivos.map(u => { const uid = u.usuario||u.id; const m = muGet(uid); const ativas = ['leads','agendamentos','consultas','vendas','faturamento'].filter(k => { const aplica = k === 'faturamento' ? !!m.aplica_faturamento : m[`aplica_${k}`] !== false; return aplica && (m[k]||0) > 0; }); if (!ativas.length) return ''; const cor = UNIT_COLORS[u.unidade] || '#c9a84c'; const hm = getUserMetrics(uid,'hoje'); const sm = getUserMetrics(uid,'semana'); const mm = getUserMetrics(uid,'mes'); const linhas = ativas.map(k => { const meta_mensal = m[k]||0; const meta_dia = diasMes > 0 ? meta_mensal/diasMes : 0; const meta_sem = meta_mensal/4; return `
${LABEL_METRICA[k]} Meta: ${fmtM(k,meta_mensal)}/mês
Hoje
${fmtM(k,hm[k])}
${progBar(hm[k],meta_dia,COR_METRICA[k])}
Semana
${fmtM(k,sm[k])}
${progBar(sm[k],meta_sem,COR_METRICA[k])}
Mês
${fmtM(k,mm[k])}
${progBar(mm[k],meta_mensal,COR_METRICA[k])}
`; }).join(''); return `
${u.nome.charAt(0)}
${u.nome}
${u.unidade||'—'} · ${ROLE_LABELS[u.role]||u.role}
${linhas}
`; }).filter(Boolean).join(''); // === CONSOLIDADO POR CLÍNICA === const clinicCards = UNITS.map(unit => { const usersUnit = usuariosAtivos.filter(u => u.unidade === unit); const totalFat = usersUnit.reduce((s,u) => s+(muGet(u.usuario||u.id).faturamento||0), 0); const totalLeads= usersUnit.reduce((s,u) => s+(muGet(u.usuario||u.id).leads||0), 0); const totalAg = usersUnit.reduce((s,u) => s+(muGet(u.usuario||u.id).agendamentos||0), 0); const totalVend = usersUnit.reduce((s,u) => s+(muGet(u.usuario||u.id).vendas||0), 0); const mm_unit = calcMetrics(DATA.pipeline, unit); const pct = totalFat ? Math.min((mm_unit.fatMes/totalFat)*100,100) : 0; const cor = UNIT_COLORS[unit]; const falta = Math.max(0,(totalFat||0)-mm_unit.fatMes); const diasRest = diasMes - diaAtual + 1; const porDia = diasRest > 0 ? falta/diasRest : 0; return `
${UNIT_NAMES[unit]}
Meta Fat./mês (∑ agentes)
${fmtCurrency(totalFat)}
${fmtPct(pct)}
${fmtCurrency(mm_unit.fatMes)} realizado
Pipeline vs Meta
${mm_unit.leads} / ${totalLeads||'—'} leads
${mm_unit.agendados} / ${totalAg||'—'} agem.
${mm_unit.vendas} / ${totalVend||'—'} vendas
${usersUnit.length} agente(s)
Necessário: ${fmtCurrency(porDia)}/dia nos próximos ${diasRest} dias
`; }).join(''); el.innerHTML = `

Metas — ${mesStr}

Consolidado por Clínica (soma das metas dos agentes)

${clinicCards}
${progressCards ? `

Progresso por Agente — Hoje · Semana · Mês

${progressCards}
` : `
Configure as metas dos agentes abaixo para ver o progresso aqui.
`} ${editorHTML} `; } function salvarMetaUsuario(usuarioId, campo, valor) { if (!window.METAS_USUARIOS) window.METAS_USUARIOS = {}; const mes = new Date().toISOString().substring(0, 7); const key = mes + '_' + usuarioId; if (!window.METAS_USUARIOS[key]) window.METAS_USUARIOS[key] = {}; if (campo.startsWith('aplica_')) { window.METAS_USUARIOS[key][campo] = (valor === true || valor === 'true'); } else if (campo === 'faturamento') { window.METAS_USUARIOS[key][campo] = parseFloat(valor) || 0; } else { window.METAS_USUARIOS[key][campo] = parseInt(valor) || 0; } localStorage.setItem('sf_metas_usuarios', JSON.stringify(window.METAS_USUARIOS)); if (typeof _supabase !== 'undefined' && _supabase) { const _m = window.METAS_USUARIOS[key]; _supabase.rpc('set_meta_usuario', { p_mes: mes, p_usuario_id: usuarioId, p_leads: _m.leads||0, p_agendamentos: _m.agendamentos||0, p_consultas: _m.consultas||0, p_vendas: _m.vendas||0, p_faturamento: _m.faturamento||0, p_aplica_leads: _m.aplica_leads !== false, p_aplica_agendamentos: _m.aplica_agendamentos !== false, p_aplica_consultas: _m.aplica_consultas !== false, p_aplica_vendas: _m.aplica_vendas !== false, p_aplica_faturamento: !!_m.aplica_faturamento }).then(({ error: _e }) => { if (_e) console.warn('[Metas] RPC:', _e.message); }); } renderMetas(); } function getMetaUsuario(usuarioId, campo) { if (!window.METAS_USUARIOS) window.METAS_USUARIOS = JSON.parse(localStorage.getItem('sf_metas_usuarios') || '{}'); const mes = new Date().toISOString().substring(0, 7); const key = mes + '_' + usuarioId; return (window.METAS_USUARIOS[key] || {})[campo] || 0; } function renderSDR() { const el = document.getElementById('page-sdr'); if (!el) return; const u = currentUser; const hoje = new Date().toISOString().split('T')[0]; const mesAtual = new Date().toISOString().substring(0, 7); const iniSemana = new Date(); iniSemana.setDate(iniSemana.getDate() - iniSemana.getDay()); iniSemana.setHours(0,0,0,0); const [dIni, dFim] = (window.SDR_PERIODO || [hoje, hoje]); function filtrarSDR(tipo) { return DATA.pipeline.filter(r => { const ds = r.data_registro || r.data_venda || ''; if (!ds) return false; const d = new Date(ds + 'T00:00:00'); if (tipo === 'hoje') return ds === hoje; if (tipo === 'semana') return d >= iniSemana; if (tipo === 'mes') return ds.startsWith(mesAtual); if (tipo === 'periodo') return ds >= dIni && ds <= dFim; return true; }); } function metricasSDR(base) { return { leads: base.length, agendamentos: base.filter(r => ['Agendado','Consultado','Vendido'].includes(r.status)).length, consultas: base.filter(r => ['Consultado','Vendido'].includes(r.status)).length, vendas: base.filter(r => r.status === 'Vendido').length, }; } const periodos = ['hoje','semana','mes','periodo']; const labels = { hoje:'Hoje', semana:'Esta Semana', mes:'Este Mês', periodo:'Período' }; const ativo = window.SDR_FILTRO || 'hoje'; const base = filtrarSDR(ativo); const m = metricasSDR(base); const metaAg = getMetaUsuario(u?.usuario||u?.id, 'agendamentos'); const metaVend = getMetaUsuario(u?.usuario||u?.id, 'vendas'); const metaLeads = getMetaUsuario(u?.usuario||u?.id, 'leads'); const pctAg = metaAg ? Math.min(m.agendamentos/metaAg*100,100) : 0; const pctVend = metaVend ? Math.min(m.vendas/metaVend*100,100) : 0; const pctLeads = metaLeads ? Math.min(m.leads/metaLeads*100,100) : 0; el.innerHTML = `

Dashboard SDR — ${u?.nome||'SDR'}

Acompanhe sua performance em tempo real.

${periodos.map(p => ``).join('')} ${ativo==='periodo'?`
até
`:''}
Leads Recebidos
${m.leads}
${metaLeads ? `
Meta: ${metaLeads} | ${pctLeads.toFixed(0)}%
` : ''}
Agendamentos
${m.agendamentos}
${metaAg ? `
Meta: ${metaAg} | ${pctAg.toFixed(0)}%
` : ''}
Consultas Realizadas
${m.consultas}
Vendas Realizadas
${m.vendas}
${metaVend ? `
Meta: ${metaVend} | ${pctVend.toFixed(0)}%
` : ''}
${(metaAg || metaVend || metaLeads) ? `
Progresso Mensal das Metas
${metaLeads ? `
Leads
${m.leads} / ${metaLeads}
` : ''} ${metaAg ? `
Agendamentos
${m.agendamentos} / ${metaAg}
` : ''} ${metaVend ? `
Vendas
${m.vendas} / ${metaVend}
` : ''}
` : '
Metas individuais não cadastradas. Um administrador pode configurar na aba Metas.
'}
`; } function renderComparativo() { const el = document.getElementById('page-comparativo'); const mesAtual = new Date().toISOString().substring(0, 7); const mesComp = el.dataset.mescomp || MESES_HISTORICO[0].valor; const mesCompLabel = MESES_HISTORICO.find(m => m.valor === mesComp)?.label || mesComp; const fatAtual = {}; UNITS.forEach(u => { const vends = DATA.pipeline.filter(r => r.unidade === u && r.status === 'Vendido' && (r.data_venda || r.data_registro || '').startsWith(mesAtual)); fatAtual[u] = vends.reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0); }); const fatComp = {}; UNITS.forEach(u => { const h = DATA.historico.find(r => r.mes_ano === mesComp && r.unidade === u); fatComp[u] = h ? parseFloat(h.total_faturamento) || 0 : 0; }); const selectOpts = MESES_HISTORICO.map(m => `` ).join(''); const cards = UNITS.map(u => { const atual = fatAtual[u] || 0; const comp = fatComp[u] || 0; const delta = comp ? ((atual - comp) / comp * 100) : 0; const isUp = delta >= 0; const color = UNIT_COLORS[u]; return `
${UNIT_NAMES[u]}
Período atual (${mesAtual})
${fmtCurrency(atual)}
${mesCompLabel}: ${fmtCurrency(comp)}
${comp ? `
${isUp ? '+' : ''}${fmtPct(Math.abs(delta))} ${isUp ? 'crescimento':'queda'}
` : '
Sem dado histórico
'}
`; }).join(''); const totalAtual = Object.values(fatAtual).reduce((s, v) => s + v, 0); const totalComp = Object.values(fatComp).reduce((s, v) => s + v, 0); const deltaGeral = totalComp ? ((totalAtual - totalComp) / totalComp * 100) : 0; const isUpGeral = deltaGeral >= 0; el.innerHTML = `

Comparativo de Crescimento

Comparar com:
Rede Geral — ${mesAtual} vs ${mesCompLabel}
${fmtCurrency(totalAtual).replace('R$','')}
${mesCompLabel}: ${fmtCurrency(totalComp)}
${totalComp ? `
${isUpGeral?'+':''}${fmtPct(Math.abs(deltaGeral))} em relação a ${mesCompLabel}
` : ''}
${cards}
${mesAtual} vs ${mesCompLabel} por Unidade
`; setTimeout(() => { const ctx = document.getElementById('chart-comparativo'); if (!ctx) return; if (chartInstances['comparativo']) chartInstances['comparativo'].destroy(); chartInstances['comparativo'] = new Chart(ctx, { type: 'bar', data: { labels: UNITS.map(u => UNIT_NAMES[u]), datasets: [ { label: mesAtual, data: UNITS.map(u => fatAtual[u] || 0), backgroundColor: UNITS.map(u => UNIT_COLORS[u] + 'cc'), borderColor: UNITS.map(u => UNIT_COLORS[u]), borderWidth: 2, borderRadius: 6 }, { label: mesCompLabel, data: UNITS.map(u => fatComp[u] || 0), backgroundColor: 'rgba(120,120,160,.3)', borderColor: 'rgba(180,180,220,.6)', borderWidth: 2, borderRadius: 6 } ] }, options: chartOptions({ currency: true }) }); }, 50); } function renderReceitas() { const el = document.getElementById('page-receitas'); const mesStr = new Date().toISOString().substring(0, 7); const filtrado = DATA.recebimentos.filter(r => (r.data_recebimento || '').startsWith(mesStr)); const totalRec = filtrado.reduce((s, r) => s + (parseFloat(r.valor_recebido) || 0), 0); const porUnidade = {}; filtrado.forEach(r => { const u = r.unidade || '?'; porUnidade[u] = (porUnidade[u] || 0) + (parseFloat(r.valor_recebido) || 0); }); const recRows = filtrado.slice().reverse().slice(0, 50).map(r => ` ${r.data_recebimento || '—'} ${r.unidade} ${r.vendedor || '—'} ${fmtCurrency(parseFloat(r.valor_recebido))} ${r.referencia || '—'} `).join('') || 'Sem registros'; const unidCards = UNITS.map(u => `
${UNIT_NAMES[u]}
${fmtCurrency(porUnidade[u]||0).replace('R$','')}
Recebido no mês
`).join(''); el.innerHTML = `

Receitas Recebidas

Total Recebido no Mês
${fmtCurrency(totalRec).replace('R$','')}
${filtrado.length} registros em ${mesStr}
${unidCards}

Últimos Recebimentos

${recRows}
DataUnidadeVendedorValorReferência
`; } function chartOptions(opts = {}) { return { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#8888aa', font: { size: 11 }, padding: 16 } }, tooltip: { callbacks: opts.currency ? { label: ctx => ' ' + fmtCurrency(ctx.raw) } : undefined } }, scales: opts.noAxes ? {} : { x: { ticks: { color: '#8888aa', font: { size: 10 } }, grid: { color: '#1a1a38' } }, y: { ticks: { color: '#8888aa', font: { size: 10 }, callback: opts.currency ? v => 'R$' + new Intl.NumberFormat('pt-BR',{notation:'compact'}).format(v) : undefined }, grid: { color: '#1a1a38' } } } }; } function renderChartUnits(canvasId) { setTimeout(() => { const ctx = document.getElementById(canvasId); if (!ctx) return; if (chartInstances[canvasId]) chartInstances[canvasId].destroy(); const filtrado = filtrarPorData(DATA.pipeline.filter(r => r.status === 'Vendido')); const data = UNITS.map(u => filtrado.filter(r => r.unidade === u) .reduce((s, r) => s + (parseFloat(r.valor_venda) || 0), 0)); chartInstances[canvasId] = new Chart(ctx, { type: 'bar', data: { labels: UNITS.map(u => UNIT_NAMES[u].split(' ')[0]), datasets: [{ label: 'Faturamento', data, backgroundColor: UNITS.map(u => UNIT_COLORS[u] + 'bb'), borderColor: UNITS.map(u => UNIT_COLORS[u]), borderWidth: 2, borderRadius: 8 }] }, options: chartOptions({ currency: true }) }); }, 50); } function renderChartTipo(canvasId, m, color = '#c9a84c') { setTimeout(() => { const ctx = document.getElementById(canvasId); if (!ctx) return; if (chartInstances[canvasId]) chartInstances[canvasId].destroy(); chartInstances[canvasId] = new Chart(ctx, { type: 'doughnut', data: { labels: ['Online', 'Presencial'], datasets: [{ data: [m.online || 0, m.presencial || 0], backgroundColor: [color + 'cc', 'rgba(120,120,200,.4)'], borderColor: [color, 'rgba(180,180,240,.5)'], borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#8888aa', font: { size: 11 } } } }, cutout: '65%' } }); }, 50); } function renderChartVendedor(canvasId, unit, color = '#c9a84c') { setTimeout(() => { const ctx = document.getElementById(canvasId); if (!ctx) return; if (chartInstances[canvasId]) chartInstances[canvasId].destroy(); const filtrado = filtrarPorData(DATA.pipeline.filter(r => r.unidade === unit && r.status === 'Vendido')); const vend = {}; filtrado.forEach(r => { const v = r.vendedor || r.agente || 'Desconhecido'; vend[v] = (vend[v] || 0) + (parseFloat(r.valor_venda) || 0); }); const sorted = Object.entries(vend).sort((a, b) => b[1] - a[1]).slice(0, 8); chartInstances[canvasId] = new Chart(ctx, { type: 'bar', data: { labels: sorted.map(([n]) => n), datasets: [{ label: 'Faturamento', data: sorted.map(([, v]) => v), backgroundColor: color + 'aa', borderColor: color, borderWidth: 2, borderRadius: 6 }] }, options: chartOptions({ currency: true }) }); }, 50); } function renderChartDiario(canvasId, unit, color = '#c9a84c') { setTimeout(() => { const ctx = document.getElementById(canvasId); if (!ctx) return; if (chartInstances[canvasId]) chartInstances[canvasId].destroy(); const mesStr = new Date().toISOString().substring(0, 7); const diasMes = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(); const labels = Array.from({ length: diasMes }, (_, i) => `${i + 1}`); const base = unit === 'all' ? DATA.pipeline : DATA.pipeline.filter(r => r.unidade === unit); const vendasMes = base.filter(r => r.status === 'Vendido' && (r.data_venda || r.data_registro || '').startsWith(mesStr)); const daily = Array(diasMes).fill(0); vendasMes.forEach(r => { const d = parseInt((r.data_venda || r.data_registro || '').split('-')[2]) - 1; if (d >= 0 && d < diasMes) daily[d] += parseFloat(r.valor_venda) || 0; }); const acumulado = []; let sum = 0; daily.forEach(v => { sum += v; acumulado.push(sum); }); chartInstances[canvasId] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [ { type: 'bar', label: 'Fat. do Dia', data: daily, backgroundColor: color + '55', borderColor: color + '88', borderWidth: 1, borderRadius: 4, yAxisID: 'y' }, { type: 'line', label: 'Acumulado', data: acumulado, borderColor: color, backgroundColor: 'transparent', borderWidth: 2.5, pointRadius: 0, tension: 0.4, yAxisID: 'y2' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#8888aa', font: { size: 11 } } }, tooltip: { callbacks: { label: ctx => ' ' + fmtCurrency(ctx.raw) } } }, scales: { x: { ticks: { color: '#8888aa', font: { size: 9 } }, grid: { color: '#1a1a38' } }, y: { position: 'left', ticks: { color: '#8888aa', font: { size: 10 }, callback: v => 'R$' + new Intl.NumberFormat('pt-BR', { notation: 'compact' }).format(v) }, grid: { color: '#1a1a38' } }, y2: { position: 'right', ticks: { color: color, font: { size: 10 }, callback: v => 'R$' + new Intl.NumberFormat('pt-BR', { notation: 'compact' }).format(v) }, grid: { drawOnChartArea: false } } } } }); }, 50); } function showPage(page) { if (currentUser && !podeFVerPagina(page) && currentUser.role === 'unidade') { return; // silencioso — o usuário não deveria ver este botão } activePage = page; document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); document.querySelectorAll('.sb-item').forEach(i => i.classList.remove('active')); const pageEl = document.getElementById('page-' + page); if (pageEl) pageEl.classList.add('active'); event.currentTarget?.classList.add('active'); const titles = { all: 'Todas as Clínicas', SCS: 'São Caetano do Sul', GRU: 'Guarulhos', MGA: 'Maringá', RVD: 'Rio Verde', ranking: 'Rankings', metas: 'Metas', comparativo: 'Comparativo', receitas: 'Receitas', 'minha-conta': 'Minha Conta', usuarios: 'Gestão de Usuários', 'entrada-diaria': 'Entrada Diária de Leads', leads: 'CRM de Leads', sdr: 'Dashboard SDR' }; document.getElementById('topbar-title').textContent = titles[page] || page; if (page === 'minha-conta') renderPaginaMinhaConta(); if (page === 'usuarios') renderPaginaUsuarios(); if (page === 'sdr') renderSDR(); if (page === 'entrada-diaria') renderPaginaEntradaDiaria(); if (page === 'leads') renderPaginaLeads(); if (window.innerWidth < 900) document.getElementById('sidebar').classList.remove('open'); } function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); } document.addEventListener('click', e => { const sidebar = document.getElementById('sidebar'); const toggle = document.getElementById('menu-toggle'); if (window.innerWidth < 900 && !sidebar.contains(e.target) && !toggle.contains(e.target)) { sidebar.classList.remove('open'); } }); function gerarDadosDemostracao() { const hoje = new Date(); const mesStr = hoje.toISOString().substring(0, 7); const hojeStr = hoje.toISOString().split('T')[0]; const origens = ['Instagram', 'Facebook', 'Google', 'Indicação', 'WhatsApp']; const statuses = ['Lead Novo', 'Agendado', 'Consultado', 'Vendido', 'Vendido', 'Perdido']; const tipos = ['Online', 'Presencial', 'Presencial']; const pipeline = []; const vendedores = { SCS: ['Ana Silva', 'Carlos Mendes'], GRU: ['Beatriz Costa', 'Diego Alves'], MGA: ['Fernanda Lima', 'Gabriel Souza'], RVD: ['Helena Rocha', 'Igor Santos'] }; let id = 1; UNITS.forEach(u => { for (let d = 1; d <= hoje.getDate(); d++) { const qtd = Math.floor(Math.random() * 5) + 2; for (let j = 0; j < qtd; j++) { const dia = String(d).padStart(2, '0'); const dataStr = `${mesStr}-${dia}`; const status = statuses[Math.floor(Math.random() * statuses.length)]; const vend = vendedores[u][Math.floor(Math.random() * vendedores[u].length)]; const valor = status === 'Vendido' ? Math.floor(Math.random() * 15000 + 8000) : 0; pipeline.push({ id: 'SF-' + String(id++).padStart(4, '0'), data_registro: dataStr, unidade: u, agente: vend, vendedor: vend, nome_cliente: '', origem: origens[Math.floor(Math.random() * origens.length)], status, tipo_consulta: tipos[Math.floor(Math.random() * tipos.length)], data_agendamento: status !== 'Lead Novo' ? dataStr : '', data_consulta: ['Consultado', 'Vendido'].includes(status) ? dataStr : '', data_venda: status === 'Vendido' ? dataStr : '', valor_venda: valor }); } } }); const metas = UNITS.map(u => ({ mes_ano: mesStr, unidade: u, vendedor: 'GERAL', meta_mensal: Math.floor(Math.random() * 100000 + 150000), meta_semanal: Math.floor(Math.random() * 25000 + 37500) })); const historico = MESES_HISTORICO.map(m => UNITS.map(u => ({ mes_ano: m.valor, unidade: u, total_faturamento: Math.floor(Math.random() * 120000 + 100000), total_vendas: Math.floor(Math.random() * 15 + 10), total_leads: Math.floor(Math.random() * 80 + 60), total_agendamentos: Math.floor(Math.random() * 40 + 30), total_consultas: Math.floor(Math.random() * 20 + 15) }))).flat(); return { pipeline, metas, recebimentos: [], historico, config: [] }; } function abrirEsqueciSenha() { document.getElementById('reset-email').value = ''; const msg = document.getElementById('reset-msg'); msg.style.display = 'none'; msg.textContent = ''; document.getElementById('modal-esqueci').style.display = 'flex'; setTimeout(() => document.getElementById('reset-email').focus(), 100); } function enviarResetSenha() { const email = document.getElementById('reset-email').value.trim().toLowerCase(); const msg = document.getElementById('reset-msg'); USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; const existe = USUARIOS.find(x => x.email.toLowerCase() === email); msg.style.display = 'block'; if (existe) { msg.style.background = 'rgba(0,230,118,.08)'; msg.style.border = '1px solid rgba(0,230,118,.25)'; msg.style.color = 'var(--green)'; msg.innerHTML = 'E-mail encontrado. Solicite ao administrador do sistema que redefina sua senha em Usuários → Editar.'; } else { msg.style.background = 'rgba(255,68,68,.08)'; msg.style.border = '1px solid rgba(255,68,68,.25)'; msg.style.color = 'var(--red)'; msg.innerHTML = 'E-mail não encontrado. Verifique o endereço ou contate o administrador.'; } } function fecharModal(id) { document.getElementById(id).style.display = 'none'; } function renderPaginaMinhaConta() { const el = document.getElementById('page-minha-conta'); if (!currentUser) return; el.innerHTML = `

Minha Conta

Gerencie suas informações e senha de acesso.

${currentUser.nome.charAt(0).toUpperCase()}
${currentUser.nome}
${currentUser.email}
${ROLE_LABELS[currentUser.role]}

Alterar Senha

`; } async function alterarMinhaSenha() { const atual = document.getElementById('conta-atual').value; const nova = document.getElementById('conta-nova').value; const conf = document.getElementById('conta-confirma').value; const msg = document.getElementById('conta-msg'); const mostrar = (txt, ok) => { msg.style.display = 'block'; msg.style.background = ok ? 'rgba(0,230,118,.08)' : 'rgba(255,68,68,.08)'; msg.style.border = ok ? '1px solid rgba(0,230,118,.25)' : '1px solid rgba(255,68,68,.25)'; msg.style.color = ok ? 'var(--green)' : 'var(--red)'; msg.innerHTML = txt; }; // Quando Supabase está configurado, troca de senha deve ser feita pelo painel do Supabase if (_supabase) { mostrar('Com Supabase ativo, altere sua senha pelo painel de autenticação do Supabase.', false); return; } const userRecord = (INTEGRITY ? (INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS) : DEFAULT_USUARIOS).find(x => x.id === currentUser.id); if (!userRecord || !userRecord.senha || atual !== userRecord.senha) { mostrar('Senha atual incorreta.', false); return; } if (nova.length < 6) { mostrar('A nova senha deve ter pelo menos 6 caracteres.', false); return; } if (nova !== conf) { mostrar('As senhas não coincidem.', false); return; } USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; const idx = USUARIOS.findIndex(x => x.id === currentUser.id); if (idx < 0) { mostrar('Usuário não encontrado.', false); return; } USUARIOS[idx].senha = await hashSenha(nova); currentUser.senha = nova; salvarUsuarios(); sessionStorage.setItem('sf_session', JSON.stringify(currentUser)); document.getElementById('conta-atual').value = ''; document.getElementById('conta-nova').value = ''; document.getElementById('conta-confirma').value = ''; mostrar('Senha alterada com sucesso!', true); } function renderPaginaUsuarios() { if (!currentUser || !ROLES_GERENCIAIS.includes(currentUser.role)) return; const el = document.getElementById('page-usuarios'); USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; const rows = USUARIOS.map((u, i) => ` ${u.nome} ${u.usuario || u.email} ${ROLE_LABELS[u.role]} ${u.unidade || '—'}
${u.role !== 'founder' || currentUser.role === 'founder' ? `` : ''} ${(u.role !== 'founder') ? `` : '🔒 Fundador'}
`).join(''); el.innerHTML = `

Gestão de Usuários

Crie, edite e gerencie os acessos ao sistema.

${rows}
NomeUsuárioPerfilUnidadeAções
`; } function atualizarUnidadeField() { const role = document.getElementById('edit-role').value; document.getElementById('edit-unidade-wrap').style.display = role === 'founder' ? 'none' : ''; } function abrirNovoUsuario() { document.getElementById('modal-user-title').textContent = 'Novo usuário'; document.getElementById('edit-user-idx').value = '-1'; document.getElementById('edit-nome').value = ''; document.getElementById('edit-usuario').value = ''; document.getElementById('edit-email').value = ''; document.getElementById('edit-senha').value = SENHA_PADRAO; document.getElementById('edit-role').value = 'unidade'; document.getElementById('edit-unidade').value = 'SCS'; document.getElementById('modal-user-err').style.display = 'none'; atualizarUnidadeField(); document.getElementById('modal-usuario').style.display = 'flex'; setTimeout(() => document.getElementById('edit-nome').focus(), 100); } function abrirEditarUsuario(idx) { USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; const u = USUARIOS[idx]; document.getElementById('modal-user-title').textContent = 'Editar usuário'; document.getElementById('edit-user-idx').value = idx; document.getElementById('edit-nome').value = u.nome; document.getElementById('edit-usuario').value = u.usuario || ''; document.getElementById('edit-email').value = u.email || ''; document.getElementById('edit-senha').value = ''; // Não preencher senha por segurança document.getElementById('edit-role').value = u.role; document.getElementById('edit-unidade').value = u.unidade || 'SCS'; document.getElementById('modal-user-err').style.display = 'none'; atualizarUnidadeField(); document.getElementById('modal-usuario').style.display = 'flex'; } async function salvarUsuarioForm() { const idx = parseInt(document.getElementById('edit-user-idx').value); const nome = document.getElementById('edit-nome').value.trim(); const usuario = document.getElementById('edit-usuario').value.trim().toLowerCase(); const email = document.getElementById('edit-email').value.trim().toLowerCase(); const senha = document.getElementById('edit-senha').value; const role = document.getElementById('edit-role').value; const unidade = (role !== 'founder') ? document.getElementById('edit-unidade').value : null; const err = document.getElementById('modal-user-err'); if (!nome || !usuario) { err.style.display='block'; err.textContent='Preencha nome e usuário.'; return; } if (!_supabase) { // Modo offline: senha obrigatória if (!senha) { err.style.display='block'; err.textContent='Preencha a senha.'; return; } if (senha.length < 4) { err.style.display='block'; err.textContent='Senha deve ter pelo menos 4 caracteres.'; return; } } USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; USUARIOS.forEach(u => { if (!u.usuario) u.usuario = u.email; }); const dupUser = USUARIOS.findIndex((x, i) => (x.usuario||'').toLowerCase() === usuario && i !== idx); if (dupUser >= 0) { err.style.display='block'; err.textContent='Este usuário já está cadastrado.'; return; } if (idx === -1) { const newId = 'u' + Date.now(); // Novo usuário sempre começa com senha padrão se não informada const senhaFinal = senha || SENHA_PADRAO; const senhaFinalHash = await hashSenha(senhaFinal); const novoUser = { id: newId, usuario, email, role, nome, unidade, senha: senhaFinalHash, primeiro_acesso: true }; USUARIOS.push(novoUser); } else { const senhaExistente = USUARIOS[idx].senha; USUARIOS[idx] = { ...USUARIOS[idx], usuario, email, role, nome, unidade }; // Só atualizar senha em modo offline; se campo preenchido, atualiza; senão mantém if (!_supabase) { USUARIOS[idx].senha = senha || senhaExistente; } else { delete USUARIOS[idx].senha; } // Remover senha ao salvar quando Supabase ativo } salvarUsuarios(); // Persistir no Supabase se disponível if (_supabase) { const u = idx === -1 ? USUARIOS[USUARIOS.length - 1] : USUARIOS[idx]; const _sHash = senha ? await hashSenha(senha) : null; DB.salvarUsuario({ ...u, senha: _sHash }).catch(e => console.warn('salvarUsuario Supabase:', e)); } fecharModal('modal-usuario'); renderPaginaUsuarios(); } function excluirUsuario(idx) { USUARIOS = INTEGRITY.loadWithVerification('sf_users') || DEFAULT_USUARIOS; const u = USUARIOS[idx]; if (!u) return; if (u.role === 'founder') { alert('O usuário Fundador não pode ser excluído.'); return; } if (!confirm(`Excluir usuário "${u.nome}" (${u.email})? Esta ação não pode ser desfeita.`)) return; USUARIOS.splice(idx, 1); salvarUsuarios(); renderPaginaUsuarios(); } // ============================================================ // CRM DE LEADS + ENTRADA DIÁRIA // ============================================================ const ETAPAS_FUNIL = ['conexão','qualificação','agendamento','consulta realizada','formalização','venda feita']; const ETAPA_CLASSES = { 'conexão':'funil-conexao','qualificação':'funil-qualificacao','agendamento':'funil-agendamento', 'consulta realizada':'funil-consulta','formalização':'funil-formalizacao','venda feita':'funil-venda' }; // ——— Domínios CRM ——— const PROCEDIMENTOS = [ 'Transplante Capilar', 'Transplante Capilar + 3 Meso', 'Transplante Capilar + 6 Meso', 'Transplante Capilar + 12 Meso', '1 Sessão de Meso', '3 Sessões de Meso', '6 Sessões de Meso', '12 Sessões de Meso' ]; const SESSOES_POR_PROCEDIMENTO = { 'Transplante Capilar': 0, 'Transplante Capilar + 3 Meso': 3, 'Transplante Capilar + 6 Meso': 6, 'Transplante Capilar + 12 Meso': 12, '1 Sessão de Meso': 1, '3 Sessões de Meso': 3, '6 Sessões de Meso': 6, '12 Sessões de Meso': 12 }; const ADICIONAIS = ['Sem Bodyhair', 'Com Bodyhair']; const ORIGENS_LEAD = ['Indicação', 'Instagram', 'Google', 'Outros']; const STATUS_LEAD = ['Novo', 'Em Contato', 'Agendado', 'Convertido', 'Perdido']; const FORMAS_PAGAMENTO = ['À Vista', 'Cartão de Crédito', 'Cartão de Débito', 'Financiamento', 'Boleto', 'Pix']; const STATUS_PAGAMENTO = ['Pendente', 'Pago', 'Parcelado', 'Cancelado']; const STATUS_LEAD_CLASSES = { 'Novo':'funil-conexao','Em Contato':'funil-qualificacao','Agendado':'funil-agendamento', 'Convertido':'funil-venda','Perdido':'funil-formalizacao' }; /* ── DADOS REAIS — inicialização síncrona ── */ const _FALLBACK_LEADS = [{"id":"sh_93573243134_2026_05_04","_from_sheets":true,"_sheets_key":"93573243134|2026-05-04","nome":"Eduardo do Prado Lobo","cpf":"935.732.431-34","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-04","data_cirurgia":"2026-09-14","data_cadastro":"2026-05-04T00:00:00","data_registro":"2026-05-04","procedimento":"Transplante Capilar","valor_procedimento":17500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Eliene","forma_pagamento":"","origem":"Indicação Vinicius","eh_mesoterapia":false,"entrada":3000.0,"saldo":14500.0},{"id":"sh_68666314591_2026_05_05","_from_sheets":true,"_sheets_key":"68666314591|2026-05-05","nome":"Marizaldo Santos Silva","cpf":"686.663.145-91","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-05","data_cirurgia":"2026-07-04","data_cadastro":"2026-05-05T00:00:00","data_registro":"2026-05-05","procedimento":"Transplante Capilar","valor_procedimento":17400.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Micaelly","forma_pagamento":"","origem":"Rede Social","eh_mesoterapia":false,"entrada":3000.0,"saldo":14400.0},{"id":"sh_84297808153_2026_05_05","_from_sheets":true,"_sheets_key":"84297808153|2026-05-05","nome":"Alan Martins Queiroz","cpf":"842.978.081-53","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-05","data_cirurgia":"2026-08-23","data_cadastro":"2026-05-05T00:00:00","data_registro":"2026-05-05","procedimento":"Transplante Capilar","valor_procedimento":16500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Priscila","forma_pagamento":"","origem":"Lead do dia 19/01","eh_mesoterapia":false,"entrada":3000.0,"saldo":13500.0},{"id":"sh_64292355134_2026_05_08","_from_sheets":true,"_sheets_key":"64292355134|2026-05-08","nome":"Marcio Luiz Braz","cpf":"642.923.551-34","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-08","data_cirurgia":"2026-06-14","data_cadastro":"2026-05-08T00:00:00","data_registro":"2026-05-08","procedimento":"Transplante Capilar","valor_procedimento":17500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Micaelly","forma_pagamento":"","origem":"Lead do dia 05/03","eh_mesoterapia":false,"entrada":3000.0,"saldo":14500.0},{"id":"sh_04153838110_2026_05_15","_from_sheets":true,"_sheets_key":"04153838110|2026-05-15","nome":"Jean Carlos Eliazar Diniz","cpf":"041.538.381-10","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-15","data_cirurgia":"2026-07-04","data_cadastro":"2026-05-15T00:00:00","data_registro":"2026-05-15","procedimento":"Transplante Capilar","valor_procedimento":16500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Priscila","forma_pagamento":"","origem":"Lead do dia 05/03","eh_mesoterapia":false,"entrada":3000.0,"saldo":13500.0},{"id":"sh_02287348190_2026_05_18","_from_sheets":true,"_sheets_key":"02287348190|2026-05-18","nome":"Ridlle Moreira de Souza Silva","cpf":"022.873.481-90","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-18","data_cirurgia":"","data_cadastro":"2026-05-18T00:00:00","data_registro":"2026-05-18","procedimento":"Transplante Capilar","valor_procedimento":16500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Priscila","forma_pagamento":"","origem":"Lead do dia 23/01","eh_mesoterapia":false,"entrada":3000.0,"saldo":13500.0},{"id":"sh_45052956134_2026_05_18","_from_sheets":true,"_sheets_key":"45052956134|2026-05-18","nome":"Geraldo Cesar Martins da Silva","cpf":"450.529.561-34","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-18","data_cirurgia":"","data_cadastro":"2026-05-18T00:00:00","data_registro":"2026-05-18","procedimento":"Transplante Capilar","valor_procedimento":16500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Priscila","forma_pagamento":"","origem":"Indicação Ridlle","eh_mesoterapia":false,"entrada":3000.0,"saldo":13500.0},{"id":"sh_00596516100_2026_05_20","_from_sheets":true,"_sheets_key":"00596516100|2026-05-20","nome":"Fagner Costa Santos","cpf":"005.965.161-00","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-20","data_cirurgia":"2026-07-05","data_cadastro":"2026-05-20T00:00:00","data_registro":"2026-05-20","procedimento":"Transplante Capilar","valor_procedimento":17500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Micaelly","forma_pagamento":"","origem":"Paciente antigo","eh_mesoterapia":false,"entrada":3000.0,"saldo":14500.0},{"id":"sh_40306470144_2026_05_27","_from_sheets":true,"_sheets_key":"40306470144|2026-05-27","nome":"Arnoldo Kubelker","cpf":"403.064.701.44","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-27","data_cirurgia":"2026-09-07","data_cadastro":"2026-05-27T00:00:00","data_registro":"2026-05-27","procedimento":"Transplante Capilar","valor_procedimento":17500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Micaelly","forma_pagamento":"","origem":"Indicação Keila","eh_mesoterapia":false,"entrada":3000.0,"saldo":14500.0},{"id":"sh_89591534191_2026_05_29","_from_sheets":true,"_sheets_key":"89591534191|2026-05-29","nome":"Leandro Vieira da Silva","cpf":"895.915.341-91","unidade":"RVD","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-29","data_cirurgia":"2026-11-05","data_cadastro":"2026-05-29T00:00:00","data_registro":"2026-05-29","procedimento":"Transplante Capilar","valor_procedimento":16000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Priscila","forma_pagamento":"","origem":"Lead do dia 20/04","eh_mesoterapia":false,"entrada":0.0,"saldo":9000.0},{"id":"sh_30301761850_2026_04_01","_from_sheets":true,"_sheets_key":"30301761850|2026-04-01","nome":"Anderson Carvalho da Silva","cpf":"303.017.618-50","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.0},{"id":"sh_42054459895_2026_04_01","_from_sheets":true,"_sheets_key":"42054459895|2026-04-01","nome":"Luiz Fernando Roque da Silva","cpf":"420.544.598-95","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-07-02","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":11000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Lead","eh_mesoterapia":false,"entrada":1000.0,"saldo":10000.0},{"id":"sh_29043340847_2026_04_01","_from_sheets":true,"_sheets_key":"29043340847|2026-04-01","nome":"Sidney Oliveira de França","cpf":"290.433.408-47","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-05-26","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Lead","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_04041128544_2026_04_01","_from_sheets":true,"_sheets_key":"04041128544|2026-04-01","nome":"Mariza da Silva Cardoso","cpf":"040.411.285-44","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-05-05","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":6500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Debito","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":6500.0,"saldo":6500.0},{"id":"sh_27775405864_2026_04_01","_from_sheets":true,"_sheets_key":"27775405864|2026-04-01","nome":"Adriano da Silva CavalcantI","cpf":"277.754.058-64","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-06-02","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_33619361878_2026_04_01","_from_sheets":true,"_sheets_key":"33619361878|2026-04-01","nome":"Flavio Luiz Marcondes de Campos","cpf":"336.193.618-78","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-06-24","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Paciente Bse","eh_mesoterapia":false,"entrada":6000.0,"saldo":6000.0},{"id":"sh_11607743825_2026_04_01","_from_sheets":true,"_sheets_key":"11607743825|2026-04-01","nome":"Jefferson Alexandre Martinez","cpf":"116.077.438-25","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-06-24","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":6000.0,"saldo":6000.0},{"id":"sh_29086614892_2026_04_01","_from_sheets":true,"_sheets_key":"29086614892|2026-04-01","nome":"Deonircio Garcia Junior","cpf":"290.866.148-92","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-01","data_cirurgia":"2026-08-12","data_cadastro":"2026-04-01T00:00:00","data_registro":"2026-04-01","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Lead","eh_mesoterapia":false,"entrada":2000.0,"saldo":11000.0},{"id":"sh_29313963892_2026_04_03","_from_sheets":true,"_sheets_key":"29313963892|2026-04-03","nome":"André Sandro da Silva Paiva","cpf":"293.139.638-92","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-03","data_cirurgia":"2026-06-26","data_cadastro":"2026-04-03T00:00:00","data_registro":"2026-04-03","procedimento":"Transplante Capilar","valor_procedimento":17999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":6800.0,"saldo":11199.0},{"id":"sh_29264028803_2026_04_09","_from_sheets":true,"_sheets_key":"29264028803|2026-04-09","nome":"Nilson Paulo da Silva","cpf":"292.640.288-03","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-09","data_cirurgia":"2026-06-01","data_cadastro":"2026-04-09T00:00:00","data_registro":"2026-04-09","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_28270187801_2026_04_09","_from_sheets":true,"_sheets_key":"28270187801|2026-04-09","nome":"Henrique Nunes Carvalho","cpf":"282.701.878-01","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-09","data_cirurgia":"2026-08-12","data_cadastro":"2026-04-09T00:00:00","data_registro":"2026-04-09","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Lead","eh_mesoterapia":false,"entrada":2000.0,"saldo":11000.0},{"id":"sh_30051162806_2026_04_09","_from_sheets":true,"_sheets_key":"30051162806|2026-04-09","nome":"Mohamad Adel Kassab","cpf":"300.511.628-06","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-09","data_cirurgia":"2026-05-05","data_cadastro":"2026-04-09T00:00:00","data_registro":"2026-04-09","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Paciente Base","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.0},{"id":"sh_34594662862_2026_04_09","_from_sheets":true,"_sheets_key":"34594662862|2026-04-09","nome":"Leandro da Silva Carvalho","cpf":"345.946.628-62","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-09","data_cirurgia":"2026-08-06","data_cadastro":"2026-04-09T00:00:00","data_registro":"2026-04-09","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Paciente Base","eh_mesoterapia":false,"entrada":2000.0,"saldo":11000.0},{"id":"sh_31455957844_2026_04_09","_from_sheets":true,"_sheets_key":"31455957844|2026-04-09","nome":"Mauricio César da Silva Santos","cpf":"314.559.578-44","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-09","data_cirurgia":"2026-06-26","data_cadastro":"2026-04-09T00:00:00","data_registro":"2026-04-09","procedimento":"Transplante Capilar","valor_procedimento":14249.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":14249.0,"saldo":0.0},{"id":"sh_00279326564_2026_04_15","_from_sheets":true,"_sheets_key":"00279326564|2026-04-15","nome":"Ednilson Oliveira Caetano","cpf":"002.793.265-64","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-15","data_cirurgia":"2026-07-20","data_cadastro":"2026-04-15T00:00:00","data_registro":"2026-04-15","procedimento":"Transplante Capilar","valor_procedimento":12500.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":5000.0,"saldo":7500.0},{"id":"sh_46710371816_2026_04_18","_from_sheets":true,"_sheets_key":"46710371816|2026-04-18","nome":"Daniel dos Santos Vargas","cpf":"467.103.718-16","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-18","data_cirurgia":"2026-07-17","data_cadastro":"2026-04-18T00:00:00","data_registro":"2026-04-18","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Lead","eh_mesoterapia":false,"entrada":2000.0,"saldo":10000.0},{"id":"sh_32331922829_2026_04_20","_from_sheets":true,"_sheets_key":"32331922829|2026-04-20","nome":"Hildeslandio Barbosa de Medeiros","cpf":"323.319.228-29","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-20","data_cirurgia":"","data_cadastro":"2026-04-20T00:00:00","data_registro":"2026-04-20","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.0},{"id":"sh_16579878843_2026_04_23","_from_sheets":true,"_sheets_key":"16579878843|2026-04-23","nome":"Fabio Luiz Santos Carvalho","cpf":"165.798.788-43","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-23","data_cirurgia":"2026-08-05","data_cadastro":"2026-04-23T00:00:00","data_registro":"2026-04-23","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_35853895893_2026_04_23","_from_sheets":true,"_sheets_key":"35853895893|2026-04-23","nome":"Erick Vilela Mariano","cpf":"358.538.958-93","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-23","data_cirurgia":"2026-05-23","data_cadastro":"2026-04-23T00:00:00","data_registro":"2026-04-23","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Lead","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_38419308838_2026_04_23","_from_sheets":true,"_sheets_key":"38419308838|2026-04-23","nome":"Reginaldo Carvalho de Matos","cpf":"384.193.088-38","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-23","data_cirurgia":"2026-08-20","data_cadastro":"2026-04-23T00:00:00","data_registro":"2026-04-23","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.0},{"id":"sh_16579878843_2026_04_23","_from_sheets":true,"_sheets_key":"16579878843|2026-04-23","nome":"Marcelo Rocha Farias","cpf":"165.798.788-43","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-23","data_cirurgia":"2026-07-20","data_cadastro":"2026-04-23T00:00:00","data_registro":"2026-04-23","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"Cartão Cred 12 X","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_01207586528_2026_04_24","_from_sheets":true,"_sheets_key":"01207586528|2026-04-24","nome":"Leandro Aragão Dos Santos","cpf":"012.075.865-28","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-24","data_cirurgia":"","data_cadastro":"2026-04-24T00:00:00","data_registro":"2026-04-24","procedimento":"Transplante Capilar","valor_procedimento":10000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"Cartão Cred 12 X","origem":"IIndicação","eh_mesoterapia":false,"entrada":10000.0,"saldo":0.0},{"id":"sh_18312961879_2026_04_30","_from_sheets":true,"_sheets_key":"18312961879|2026-04-30","nome":"Odair Dias de Oliveira","cpf":"183.129.618-79","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-30","data_cirurgia":"2026-08-13","data_cadastro":"2026-04-30T00:00:00","data_registro":"2026-04-30","procedimento":"Transplante Barba","valor_procedimento":11000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Luciana","forma_pagamento":"PIX","origem":"Indicação","eh_mesoterapia":false,"entrada":1000.0,"saldo":10000.0},{"id":"sh_50696518899_2026_04_30","_from_sheets":true,"_sheets_key":"50696518899|2026-04-30","nome":"Lucas Junior Souza Quintino","cpf":"506.965.188-99","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-30","data_cirurgia":"2026-05-29","data_cadastro":"2026-04-30T00:00:00","data_registro":"2026-04-30","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Wagner","forma_pagamento":"PIX","origem":"Indicação Pcte","eh_mesoterapia":false,"entrada":10000.0,"saldo":3000.0},{"id":"sh_01448805805_2026_04_03","_from_sheets":true,"_sheets_key":"01448805805|2026-04-03","nome":"Joao Duarte Rito","cpf":"014.488.058-05","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-03","data_cirurgia":"","data_cadastro":"2026-04-03T00:00:00","data_registro":"2026-04-03","procedimento":"MESOTERAPIA","valor_procedimento":1800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Wagner","forma_pagamento":"Cartão Cred 3 X","origem":"Paciente Base","eh_mesoterapia":true,"entrada":1800.0,"saldo":0},{"id":"sh_25095284839_2026_04_08","_from_sheets":true,"_sheets_key":"25095284839|2026-04-08","nome":"Davi Pereira De Souza","cpf":"250.952.848-39","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-08","data_cirurgia":"","data_cadastro":"2026-04-08T00:00:00","data_registro":"2026-04-08","procedimento":"MESOTERAPIA","valor_procedimento":3000.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Wagner","forma_pagamento":"Cartão Cred 4 X","origem":"Paciente Base","eh_mesoterapia":true,"entrada":3000.0,"saldo":0},{"id":"sh_16915965823_2026_04_11","_from_sheets":true,"_sheets_key":"16915965823|2026-04-11","nome":"Mateus Dos Santos","cpf":"169.159.658-23","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-11","data_cirurgia":"","data_cadastro":"2026-04-11T00:00:00","data_registro":"2026-04-11","procedimento":"MESOTERAPIA","valor_procedimento":3800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Wagner","forma_pagamento":"Cartão Cred 3 X","origem":"Paciente Base","eh_mesoterapia":true,"entrada":3800.0,"saldo":0},{"id":"sh_24800064848_2026_04_14","_from_sheets":true,"_sheets_key":"24800064848|2026-04-14","nome":"Claudio Cesar Amici","cpf":"248.000.648-48","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-14","data_cirurgia":"","data_cadastro":"2026-04-14T00:00:00","data_registro":"2026-04-14","procedimento":"MESOTERAPIA","valor_procedimento":1800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Wagner","forma_pagamento":"Cartão Cred 4 X","origem":"Paciente Base","eh_mesoterapia":true,"entrada":1800.0,"saldo":0},{"id":"sh_01435133897_2026_04_22","_from_sheets":true,"_sheets_key":"01435133897|2026-04-22","nome":"Selma Irineia de Lima","cpf":"014.351.338-97","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-22","data_cirurgia":"","data_cadastro":"2026-04-22T00:00:00","data_registro":"2026-04-22","procedimento":"MESOTERAPIA","valor_procedimento":1800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Luciana","forma_pagamento":"Cartão Cred 3 X","origem":"Paciente Indicação","eh_mesoterapia":true,"entrada":1800.0,"saldo":0},{"id":"sh_34641742804_2026_04_22","_from_sheets":true,"_sheets_key":"34641742804|2026-04-22","nome":"Luiz Carlos Granata","cpf":"346.417.428-04","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-22","data_cirurgia":"","data_cadastro":"2026-04-22T00:00:00","data_registro":"2026-04-22","procedimento":"MESOTERAPIA","valor_procedimento":1800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Luciana","forma_pagamento":"Cartão Cred 5 X","origem":"Paciente Indicação","eh_mesoterapia":true,"entrada":1800.0,"saldo":0},{"id":"sh_00782711855_2026_04_22","_from_sheets":true,"_sheets_key":"00782711855|2026-04-22","nome":"Elvira Solange de Lima","cpf":"007.827.118-55","unidade":"GRU","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-04-22","data_cirurgia":"","data_cadastro":"2026-04-22T00:00:00","data_registro":"2026-04-22","procedimento":"MESOTERAPIA","valor_procedimento":1800.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"Luciana","forma_pagamento":"Cartão Cred 5 X","origem":"Paciente Indicação","eh_mesoterapia":true,"entrada":1800.0,"saldo":0},{"id":"sh_19523448838_2026_05_04","_from_sheets":true,"_sheets_key":"19523448838|2026-05-04","nome":"EMERSON ESTEVAM DA SILVA","cpf":"195.234.488-38","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-04","data_cirurgia":"2026-08-14","data_cadastro":"2026-05-04T00:00:00","data_registro":"2026-05-04","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":13999.99,"saldo":0.0},{"id":"sh_29871161875_2026_05_05","_from_sheets":true,"_sheets_key":"29871161875|2026-05-05","nome":"MARCILIO LUIS RODRIGUES","cpf":"298.711.618-75","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-05","data_cirurgia":"","data_cadastro":"2026-05-05T00:00:00","data_registro":"2026-05-05","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2100.0,"saldo":11899.99},{"id":"sh_22481476867_2026_05_05","_from_sheets":true,"_sheets_key":"22481476867|2026-05-05","nome":"RODRIGO ANDRÉ MARTINS CAMARA","cpf":"224.814.768-67","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-05","data_cirurgia":"2026-10-14","data_cadastro":"2026-05-05T00:00:00","data_registro":"2026-05-05","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1080.0,"saldo":10920.0},{"id":"sh_37513824886_2026_05_06","_from_sheets":true,"_sheets_key":"37513824886|2026-05-06","nome":"CIVALDO RODRIGUES SILVA","cpf":"375.138.248-86","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-06","data_cirurgia":"","data_cadastro":"2026-05-06T00:00:00","data_registro":"2026-05-06","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":11000.0},{"id":"sh_21619946890_2026_05_06","_from_sheets":true,"_sheets_key":"21619946890|2026-05-06","nome":"RICHARD AUGUSTO RODRIGUES","cpf":"216.199.468-90","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-06","data_cirurgia":"2026-08-07","data_cadastro":"2026-05-06T00:00:00","data_registro":"2026-05-06","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.99},{"id":"sh_31635409888_2026_05_06","_from_sheets":true,"_sheets_key":"31635409888|2026-05-06","nome":"WENDELL GARCIA RODRIGUES","cpf":"316.354.098-88","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-06","data_cirurgia":"","data_cadastro":"2026-05-06T00:00:00","data_registro":"2026-05-06","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":7000.0,"saldo":7000.0},{"id":"sh_03076228609_2026_06_07","_from_sheets":true,"_sheets_key":"03076228609|2026-06-07","nome":"HERNANE SANTOS MÓDENO","cpf":"030.762.286-09","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-06-07","data_cirurgia":"2026-06-09","data_cadastro":"2026-06-07T00:00:00","data_registro":"2026-06-07","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2000.0,"saldo":11000.0},{"id":"sh_33088236830_2026_06_07","_from_sheets":true,"_sheets_key":"33088236830|2026-06-07","nome":"FERNANDO BONFIM BRAIT","cpf":"330.882.368-30","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-06-07","data_cirurgia":"2026-06-25","data_cadastro":"2026-06-07T00:00:00","data_registro":"2026-06-07","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":500.0,"saldo":12500.0},{"id":"sh_22996340884_2026_05_08","_from_sheets":true,"_sheets_key":"22996340884|2026-05-08","nome":"ROBERTO WAGNER DE SOUZA","cpf":"229.963.408-84","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-08","data_cirurgia":"2026-07-06","data_cadastro":"2026-05-08T00:00:00","data_registro":"2026-05-08","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"BASE","eh_mesoterapia":false,"entrada":3000.0,"saldo":10000.0},{"id":"sh_34528413833_2026_05_11","_from_sheets":true,"_sheets_key":"34528413833|2026-05-11","nome":"RAFAEL DA SILVA GOMES","cpf":"345.284.138-33","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-11","data_cirurgia":"2026-05-20","data_cadastro":"2026-05-11T00:00:00","data_registro":"2026-05-11","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"PAGO","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":13999.0,"saldo":0.0},{"id":"sh_05130244842_2026_05_11","_from_sheets":true,"_sheets_key":"05130244842|2026-05-11","nome":"JOSE NIVALDO DA CUNHA","cpf":"051.302.448-42","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-11","data_cirurgia":"2026-07-27","data_cadastro":"2026-05-11T00:00:00","data_registro":"2026-05-11","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"PAGO","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":13999.99,"saldo":0.0},{"id":"sh_21683057813_2026_05_14","_from_sheets":true,"_sheets_key":"21683057813|2026-05-14","nome":"FABIO FERREIRA GOMES Y GOMES","cpf":"216.830.578-13","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-14","data_cirurgia":"","data_cadastro":"2026-05-14T00:00:00","data_registro":"2026-05-14","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"","origem":"LEAD","eh_mesoterapia":false,"entrada":1000.0,"saldo":13000.0},{"id":"sh_41496315812_2026_05_15","_from_sheets":true,"_sheets_key":"41496315812|2026-05-15","nome":"WILLIAM PESSOA RAMALHO","cpf":"414.963.158-12","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-15","data_cirurgia":"2026-06-06","data_cadastro":"2026-05-15T00:00:00","data_registro":"2026-05-15","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2000.0,"saldo":11999.99},{"id":"sh_32157760814_2026_05_18","_from_sheets":true,"_sheets_key":"32157760814|2026-05-18","nome":"FELIPE MORGAN PEREIRA","cpf":"32157760814","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-18","data_cirurgia":"2026-08-17","data_cadastro":"2026-05-18T00:00:00","data_registro":"2026-05-18","procedimento":"Transplante Capilar","valor_procedimento":11999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":10999.99},{"id":"sh_19433572858_2026_05_19","_from_sheets":true,"_sheets_key":"19433572858|2026-05-19","nome":"SERGIO LUIZ DOS SANTOS","cpf":"194.335.728-58","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-19","data_cirurgia":"2026-05-30","data_cadastro":"2026-05-19T00:00:00","data_registro":"2026-05-19","procedimento":"Transplante Capilar","valor_procedimento":11000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":5000.0,"saldo":6000.0},{"id":"sh_11064687830_2026_05_19","_from_sheets":true,"_sheets_key":"11064687830|2026-05-19","nome":"MAURICIO FERREIRA DE MACEDO","cpf":"110.646.878-30","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-19","data_cirurgia":"2026-08-14","data_cadastro":"2026-05-19T00:00:00","data_registro":"2026-05-19","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":3000.0,"saldo":9000.0},{"id":"sh_27459851833_2026_05_19","_from_sheets":true,"_sheets_key":"27459851833|2026-05-19","nome":"EDMAR PEREIRA DOS SANTOS","cpf":"274.598.518-33","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-19","data_cirurgia":"","data_cadastro":"2026-05-19T00:00:00","data_registro":"2026-05-19","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":3000.0,"saldo":12999.99},{"id":"sh_52438968896_2026_05_19","_from_sheets":true,"_sheets_key":"52438968896|2026-05-19","nome":"VITOR MARTINS CRUZ","cpf":"524.389.688-96","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-19","data_cirurgia":"2026-08-03","data_cadastro":"2026-05-19T00:00:00","data_registro":"2026-05-19","procedimento":"Transplante Capilar","valor_procedimento":13999.99,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":12999.99},{"id":"sh_33781665844_2026_05_19","_from_sheets":true,"_sheets_key":"33781665844|2026-05-19","nome":"JOSÉ ROBERTO MORENO DE PAULA","cpf":"337.816.658-44","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-19","data_cirurgia":"2026-06-22","data_cadastro":"2026-05-19T00:00:00","data_registro":"2026-05-19","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":3000.0,"saldo":11000.0},{"id":"sh_21634869871_2026_05_21","_from_sheets":true,"_sheets_key":"21634869871|2026-05-21","nome":"ERICK BENTO DIAS FERREIRA","cpf":"216.348.698-71","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-21","data_cirurgia":"2026-07-18","data_cadastro":"2026-05-21T00:00:00","data_registro":"2026-05-21","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":11000.0,"saldo":2000.0},{"id":"sh_42157945873_2026_05_21","_from_sheets":true,"_sheets_key":"42157945873|2026-05-21","nome":"YGOR AILLON ALVES NUNES","cpf":"421.579.458-73","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-21","data_cirurgia":"","data_cadastro":"2026-05-21T00:00:00","data_registro":"2026-05-21","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2000.0,"saldo":10000.0},{"id":"sh_36697440880_2026_05_22","_from_sheets":true,"_sheets_key":"36697440880|2026-05-22","nome":"NEILSON PEREIRA GAMA","cpf":"366.974.408-80","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-22","data_cirurgia":"2026-06-18","data_cadastro":"2026-05-22T00:00:00","data_registro":"2026-05-22","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":5000.0,"saldo":8000.0},{"id":"sh_43717937898_2026_05_23","_from_sheets":true,"_sheets_key":"43717937898|2026-05-23","nome":"WESLLEY ALVES PEREIRA","cpf":"437.179.378-98","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-23","data_cirurgia":"","data_cadastro":"2026-05-23T00:00:00","data_registro":"2026-05-23","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":3000.0,"saldo":11999.0},{"id":"sh_00000000000_2026_05_25","_from_sheets":true,"_sheets_key":"0|2026-05-25","nome":"HENRIQUE BOSCO","cpf":"","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-25","data_cirurgia":"2026-06-01","data_cadastro":"2026-05-25T00:00:00","data_registro":"2026-05-25","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"LEAD","eh_mesoterapia":false,"entrada":1000.0,"saldo":12999.0},{"id":"sh_26026499881_2026_05_25","_from_sheets":true,"_sheets_key":"26026499881|2026-05-25","nome":"MARCELO RICARDO TELES BARBOSA","cpf":"260.264.998-81","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-25","data_cirurgia":"2026-08-04","data_cadastro":"2026-05-25T00:00:00","data_registro":"2026-05-25","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":12999.0},{"id":"sh_07442725643_2026_05_26","_from_sheets":true,"_sheets_key":"07442725643|2026-05-26","nome":"BERNARDO RODRIGUES DO NASCIMENTO","cpf":"074.427.256-43","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-26","data_cirurgia":"","data_cadastro":"2026-05-26T00:00:00","data_registro":"2026-05-26","procedimento":"Transplante Capilar","valor_procedimento":12999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":11000.0},{"id":"sh_06963769540_2026_05_27","_from_sheets":true,"_sheets_key":"06963769540|2026-05-27","nome":"VAGNER SANTOS DOS ANJOS","cpf":"069.637.695-40","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-27","data_cirurgia":"","data_cadastro":"2026-05-27T00:00:00","data_registro":"2026-05-27","procedimento":"Transplante Capilar","valor_procedimento":12000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"GOOGLE","eh_mesoterapia":false,"entrada":2000.0,"saldo":10000.0},{"id":"sh_26282382840_2026_05_29","_from_sheets":true,"_sheets_key":"26282382840|2026-05-29","nome":"EDUARDO ALVADJAN","cpf":"262.823.828-40","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-29","data_cirurgia":"","data_cadastro":"2026-05-29T00:00:00","data_registro":"2026-05-29","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":3000.0,"saldo":10999.99},{"id":"sh_25320774842_2026_05_29","_from_sheets":true,"_sheets_key":"25320774842|2026-05-29","nome":"MARCO ANTONIO MARIQUE RANZAN","cpf":"253.207.748-42","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-29","data_cirurgia":"2026-06-27","data_cadastro":"2026-05-29T00:00:00","data_registro":"2026-05-29","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":13999.99,"saldo":0.0},{"id":"sh_07734045898_2026_05_29","_from_sheets":true,"_sheets_key":"07734045898|2026-05-29","nome":"ERNANDIS FARIA DA NOBREGA","cpf":"077.340.458-98","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-29","data_cirurgia":"2026-08-12","data_cadastro":"2026-05-29T00:00:00","data_registro":"2026-05-29","procedimento":"Transplante Capilar","valor_procedimento":14000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"ANA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":2000.0,"saldo":12000.0},{"id":"sh_25731837856_2026_05_30","_from_sheets":true,"_sheets_key":"25731837856|2026-05-30","nome":"WAGNER MIRANDA DE OLIVEIRA","cpf":"257.318.378-56","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-30","data_cirurgia":"","data_cadastro":"2026-05-30T00:00:00","data_registro":"2026-05-30","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"VANESSA","forma_pagamento":"ATÉ 5 DIAS","origem":"INDICAÇÃO","eh_mesoterapia":false,"entrada":1000.0,"saldo":12000.0},{"id":"sh_11650169825_2026_05_07","_from_sheets":true,"_sheets_key":"11650169825|2026-05-07","nome":"MARCIO RIBEIRO","cpf":"116.501.698-25","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-07","data_cirurgia":"","data_cadastro":"2026-05-07T00:00:00","data_registro":"2026-05-07","procedimento":"PRP","valor_procedimento":2000.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"VANESSA","forma_pagamento":"","origem":"","eh_mesoterapia":true,"entrada":2000.0,"saldo":0},{"id":"sh_14793988881_2026_05_12","_from_sheets":true,"_sheets_key":"14793988881|2026-05-12","nome":"CESAR CARNEIRO DE MOURA","cpf":"147.939.888-81","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-12","data_cirurgia":"","data_cadastro":"2026-05-12T00:00:00","data_registro":"2026-05-12","procedimento":"MESOTERAPIA","valor_procedimento":350.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"VANESSA","forma_pagamento":"","origem":"","eh_mesoterapia":true,"entrada":350.0,"saldo":0},{"id":"sh_50381535851_2026_05_13","_from_sheets":true,"_sheets_key":"50381535851|2026-05-13","nome":"SABRINA MIRANDA DE OLIVEIRA MOURA","cpf":"503.815.358-51","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-13","data_cirurgia":"","data_cadastro":"2026-05-13T00:00:00","data_registro":"2026-05-13","procedimento":"MESOTERAPIA","valor_procedimento":350.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"VANESSA","forma_pagamento":"","origem":"","eh_mesoterapia":true,"entrada":350.0,"saldo":0},{"id":"sh_00000000000_2026_05_15","_from_sheets":true,"_sheets_key":"0|2026-05-15","nome":"STEFANIE MARTINEZ CHEHAB","cpf":"","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-15","data_cirurgia":"","data_cadastro":"2026-05-15T00:00:00","data_registro":"2026-05-15","procedimento":"PRP","valor_procedimento":400.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"VANESSA","forma_pagamento":"","origem":"","eh_mesoterapia":true,"entrada":400.0,"saldo":0},{"id":"sh_91880807300_2026_05_21","_from_sheets":true,"_sheets_key":"91880807300|2026-05-21","nome":"SERGIO CARLOS JUSTINO","cpf":"918.808.073-00","unidade":"SCS","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-21","data_cirurgia":"","data_cadastro":"2026-05-21T00:00:00","data_registro":"2026-05-21","procedimento":"MESOTERAPIA/PRP","valor_procedimento":2100.0,"valor_adicional":0,"sessoes_contratadas":0,"consultor":"VANESSA","forma_pagamento":"","origem":"","eh_mesoterapia":true,"entrada":2100.0,"saldo":0},{"id":"sh_08033586877_2026_05_04","_from_sheets":true,"_sheets_key":"08033586877|2026-05-04","nome":"Cezar Gonzaga","cpf":"080.335.868-77","unidade":"MGA","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-04","data_cirurgia":"","data_cadastro":"2026-05-04T00:00:00","data_registro":"2026-05-04","procedimento":"Transplante Capilar","valor_procedimento":14999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Márcia","forma_pagamento":"Pix","origem":"","eh_mesoterapia":false,"entrada":3000.0,"saldo":11999.0},{"id":"sh_13234800804_2026_05_04","_from_sheets":true,"_sheets_key":"13234800804|2026-05-04","nome":"William Donizete Cunha","cpf":"132.348.008-04","unidade":"MGA","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-04","data_cirurgia":"","data_cadastro":"2026-05-04T00:00:00","data_registro":"2026-05-04","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Márcia","forma_pagamento":"Cartão","origem":"","eh_mesoterapia":false,"entrada":12000.0,"saldo":1900.0},{"id":"sh_07758831907_2026_05_12","_from_sheets":true,"_sheets_key":"07758831907|2026-05-12","nome":"Wellington William","cpf":"077.588.319-07","unidade":"MGA","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-12","data_cirurgia":"","data_cadastro":"2026-05-12T00:00:00","data_registro":"2026-05-12","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Márcia","forma_pagamento":"pix","origem":"","eh_mesoterapia":false,"entrada":3000.0,"saldo":3000.0},{"id":"sh_60539349330_2026_05_26","_from_sheets":true,"_sheets_key":"60539349330|2026-05-26","nome":"Manoel Jorge da Silva Filho","cpf":"605.393.493-30","unidade":"MGA","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-26","data_cirurgia":"","data_cadastro":"2026-05-26T00:00:00","data_registro":"2026-05-26","procedimento":"Transplante Capilar","valor_procedimento":13000.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Marcia","forma_pagamento":"pix e cartão","origem":"","eh_mesoterapia":false,"entrada":3349.0,"saldo":3349.0},{"id":"sh_93027800906_2026_05_01","_from_sheets":true,"_sheets_key":"93027800906|2026-05-01","nome":"Reginaldo Tochio","cpf":"930.278.009-06","unidade":"MGA","etapa":"venda feita","status_lead":"Vendido","data_consulta":"2026-05-01","data_cirurgia":"","data_cadastro":"2026-05-01T00:00:00","data_registro":"2026-05-01","procedimento":"Transplante Capilar","valor_procedimento":13999.0,"valor_adicional":0,"sessoes_contratadas":18,"consultor":"Marcia","forma_pagamento":"pix","origem":"","eh_mesoterapia":false,"entrada":7000.0,"saldo":7000.0}]; let SF_LEADS = (function() { try { var raw = localStorage.getItem('sf_leads'); if (raw) { var parsed = JSON.parse(raw); if (Array.isArray(parsed) && parsed.length > 0) return parsed; } } catch(e) {} // Sem dados no localStorage — inicializa com dados reais e salva try { localStorage.setItem('sf_leads', JSON.stringify(_FALLBACK_LEADS)); } catch(e) {} return _FALLBACK_LEADS.slice(); })(); function salvarLeads() { if (typeof INTEGRITY !== 'undefined') INTEGRITY.saveWithChecksum('sf_leads', SF_LEADS); else localStorage.setItem('sf_leads', JSON.stringify(SF_LEADS)); if (typeof _registrarAtualizacao === 'function') _registrarAtualizacao(); } let SF_DAILY = JSON.parse(localStorage.getItem('sf_daily') || '{}'); function salvarDaily() { if (typeof INTEGRITY !== 'undefined') INTEGRITY.saveWithChecksum('sf_daily', SF_DAILY); else localStorage.setItem('sf_daily', JSON.stringify(SF_DAILY)); // ── Supabase: persistir entrada_diaria ── if (_supabase) { Object.values(SF_DAILY).flat().forEach(entry => { DB.saveEntrada(entry).catch(e => console.warn('[SupremaHub] saveEntrada error:', e)); }); } if (typeof _registrarAtualizacao === 'function') _registrarAtualizacao(); } // ---------- Normalização para busca ---------- function normalizarTexto(s) { return (s||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().trim(); } function normalizarTelefone(s) { return (s||'').replace(/\D/g,''); } function normalizarCpf(s) { return (s||'').replace(/\D/g,''); } // ---------- Busca de CEP ---------- function buscarCep(val) { const clean = val.replace(/\D/g,''); if (clean.length !== 8) return; fetch('https://viacep.com.br/ws/' + clean + '/json/') .then(r => r.json()) .then(d => { if (!d.erro) { const setv = (id, v) => { const el = document.getElementById(id); if (el) el.value = v || ''; }; setv('lead-rua', d.logradouro); setv('lead-bairro', d.bairro); setv('lead-cidade', d.localidade); setv('lead-estado', d.uf); } }).catch(() => {}); } // ---------- Modal de Lead ---------- // ——— Helpers UI do modal de lead ——— function calcularValorTotal() { const vp = parseFloat(document.getElementById('lead-valor-procedimento').value) || 0; const va = parseFloat(document.getElementById('lead-valor-adicional').value) || 0; const total = vp + va; const wrap = document.getElementById('lead-valor-total-wrap'); const disp = document.getElementById('lead-valor-total-display'); if (wrap) wrap.style.display = (vp > 0 || va > 0) ? '' : 'none'; if (disp) disp.textContent = fmtCurrency(total); } function atualizarSessoesContratadas() { const proc = (document.getElementById('lead-procedimento') || {}).value || ''; const sessoes = SESSOES_POR_PROCEDIMENTO[proc] || 0; const el = document.getElementById('lead-sessoes-contratadas'); if (el) el.value = sessoes > 0 ? sessoes : ''; } function _limparModalLead() { ['lead-nome','lead-telefone','lead-cpf','lead-email','lead-cep','lead-rua','lead-numero', 'lead-bairro','lead-cidade','lead-estado','lead-data-consulta','lead-data-cirurgia', 'lead-obs','lead-consultor','lead-sessoes-realizadas','lead-sessoes-contratadas', 'lead-valor-procedimento','lead-valor-adicional'] .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); ['lead-etapa','lead-compareceu','lead-reagendou','lead-procedimento', 'lead-adicional','lead-status-lead','lead-origem','lead-forma-pagamento','lead-status-pagamento'] .forEach(id => { const el = document.getElementById(id); if (!el) return; if (id === 'lead-etapa') el.value = 'conexão'; else if (id === 'lead-adicional') el.value = 'Sem Bodyhair'; else if (id === 'lead-status-lead') el.value = 'Novo'; else el.value = ''; }); const wrap = document.getElementById('lead-valor-total-wrap'); if (wrap) wrap.style.display = 'none'; } function abrirNovoLead() { document.getElementById('modal-lead-title').textContent = 'Novo Lead'; document.getElementById('lead-idx').value = '-1'; _limparModalLead(); // Unidade: se unidade → oculta e preenche automaticamente const uw = document.getElementById('lead-unidade-wrap'); if (uw) uw.style.display = (currentUser && currentUser.role === 'unidade') ? 'none' : ''; if (currentUser && currentUser.role === 'unidade') { const lu = document.getElementById('lead-unidade'); if (lu) lu.value = currentUser.unidade; } // Preenche consultor com usuário logado se não for admin/gerente if (currentUser && currentUser.role === 'unidade') { const c = document.getElementById('lead-consultor'); if (c && !c.value) c.value = currentUser.nome; } document.getElementById('modal-lead-err').style.display = 'none'; atualizarCamposEtapa(); document.getElementById('modal-lead').style.display = 'flex'; setTimeout(() => document.getElementById('lead-nome').focus(), 100); } function abrirEditarLead(idx) { SF_LEADS = INTEGRITY.loadWithVerification('sf_leads', []); const l = SF_LEADS[idx]; if (!l) return; document.getElementById('modal-lead-title').textContent = 'Editar Lead'; document.getElementById('lead-idx').value = idx; const setv = (id, v) => { const el = document.getElementById(id); if (el) el.value = (v !== undefined && v !== null) ? v : ''; }; setv('lead-nome', l.nome); setv('lead-telefone', l.telefone); setv('lead-cpf', l.cpf); setv('lead-email', l.email); setv('lead-cep', l.cep); setv('lead-rua', l.rua); setv('lead-numero', l.numero); setv('lead-bairro', l.bairro); setv('lead-cidade', l.cidade); setv('lead-estado', l.estado); setv('lead-data-consulta', l.data_consulta); setv('lead-data-cirurgia', l.data_cirurgia); setv('lead-obs', l.obs); setv('lead-consultor', l.consultor); setv('lead-valor-procedimento', l.valor_procedimento || ''); setv('lead-valor-adicional', l.valor_adicional || ''); setv('lead-sessoes-realizadas', l.sessoes_realizadas || ''); setv('lead-sessoes-contratadas', l.sessoes_contratadas || ''); document.getElementById('lead-etapa').value = l.etapa || 'conexão'; document.getElementById('lead-compareceu').value = l.compareceu || ''; document.getElementById('lead-reagendou').value = l.reagendou || ''; document.getElementById('lead-procedimento').value = l.procedimento || ''; document.getElementById('lead-adicional').value = l.adicional || 'Sem Bodyhair'; document.getElementById('lead-status-lead').value = l.status_lead || 'Novo'; document.getElementById('lead-origem').value = l.origem || ''; document.getElementById('lead-forma-pagamento').value = l.forma_pagamento || ''; document.getElementById('lead-status-pagamento').value = l.status_pagamento || ''; const uw = document.getElementById('lead-unidade-wrap'); if (uw) uw.style.display = (currentUser && currentUser.role === 'unidade') ? 'none' : ''; const lu = document.getElementById('lead-unidade'); if (lu) lu.value = l.unidade || (currentUser && currentUser.unidade) || 'SCS'; document.getElementById('modal-lead-err').style.display = 'none'; calcularValorTotal(); atualizarCamposEtapa(); document.getElementById('modal-lead').style.display = 'flex'; } function atualizarCamposEtapa() { const etapa = (document.getElementById('lead-etapa') || {}).value; const isVenda = etapa === 'venda feita'; const notice = document.getElementById('venda-required-notice'); if (notice) notice.style.display = isVenda ? 'block' : 'none'; const reqIds = ['lead-cpf','lead-email','lead-cep','lead-rua','lead-bairro','lead-cidade','lead-data-consulta']; reqIds.forEach(id => { const el = document.getElementById(id); if (el) el.style.borderColor = isVenda ? 'rgba(201,168,76,.55)' : ''; }); ['lbl-consulta-req','lbl-cep-req','lbl-rua-req','lbl-bairro-req','lbl-cidade-req'].forEach(id => { const el = document.getElementById(id); if (el) el.innerHTML = isVenda ? '*' : ''; }); } function salvarLeadForm() { SF_LEADS = INTEGRITY.loadWithVerification('sf_leads', []); const idx = parseInt(document.getElementById('lead-idx').value); const nome = (document.getElementById('lead-nome').value || '').trim(); const telefone = (document.getElementById('lead-telefone').value || '').trim(); const etapa = document.getElementById('lead-etapa').value; const err = document.getElementById('modal-lead-err'); const showErr = (msg) => { err.style.display='block'; err.textContent=msg; err.scrollIntoView({behavior:'smooth',block:'nearest'}); }; // Validações obrigatórias if (!nome) { showErr('Nome é obrigatório.'); return; } if (!telefone) { showErr('Telefone é obrigatório.'); return; } const origem = document.getElementById('lead-origem').value; if (!origem) { showErr('Origem do lead é obrigatória.'); return; } // Validações adicionais para "Venda Feita" if (etapa === 'venda feita') { const obrig = [ ['lead-cpf','CPF'],['lead-email','E-mail'],['lead-cep','CEP'], ['lead-rua','Rua'],['lead-bairro','Bairro'],['lead-cidade','Cidade'], ['lead-data-consulta','Data da Consulta'] ]; for (const [id, label] of obrig) { const el = document.getElementById(id); if (!el || !el.value.trim()) { showErr('Para "Venda Feita" o campo "' + label + '" é obrigatório.'); return; } } if (!document.getElementById('lead-compareceu').value) { showErr('Informe se o lead compareceu à consulta.'); return; } const vp = parseFloat(document.getElementById('lead-valor-procedimento').value) || 0; if (vp <= 0) { showErr('Para "Venda Feita" informe o Valor do Procedimento.'); return; } if (!document.getElementById('lead-procedimento').value) { showErr('Para "Venda Feita" selecione o Procedimento.'); return; } } const getv = (id) => { const el = document.getElementById(id); return el ? el.value.trim() : ''; }; const getf = (id) => { const v = getv(id); return v ? (parseFloat(v) || 0) : 0; }; const unidade = (currentUser && currentUser.role === 'unidade') ? currentUser.unidade : getv('lead-unidade'); const procedimento = getv('lead-procedimento'); const sessoesContratadas = SESSOES_POR_PROCEDIMENTO[procedimento] || 0; const valorProcedimento = getf('lead-valor-procedimento'); const valorAdicional = getf('lead-valor-adicional'); const lead = { nome, telefone, cpf: getv('lead-cpf'), email: getv('lead-email'), cep: getv('lead-cep'), rua: getv('lead-rua'), numero: getv('lead-numero'), bairro: getv('lead-bairro'), cidade: getv('lead-cidade'), estado: getv('lead-estado'), data_consulta: getv('lead-data-consulta'), data_cirurgia: getv('lead-data-cirurgia'), // Campos financeiros procedimento, adicional: getv('lead-adicional') || 'Sem Bodyhair', valor_procedimento: valorProcedimento, valor_adicional: valorAdicional, valor_venda: valorProcedimento + valorAdicional, // campo legado para compatibilidade sessoes_contratadas: sessoesContratadas, sessoes_realizadas: parseInt(getv('lead-sessoes-realizadas')) || 0, // Campos comerciais status_lead: getv('lead-status-lead') || 'Novo', consultor: getv('lead-consultor'), origem: origem, forma_pagamento: getv('lead-forma-pagamento'), status_pagamento: getv('lead-status-pagamento'), // Campos operacionais compareceu: getv('lead-compareceu'), reagendou: getv('lead-reagendou'), etapa, unidade, obs: getv('lead-obs') }; // Integridade: sessões realizadas não pode exceder contratadas if (lead.sessoes_contratadas > 0 && lead.sessoes_realizadas > lead.sessoes_contratadas) { showErr('Sessões realizadas (' + lead.sessoes_realizadas + ') não podem exceder as contratadas (' + lead.sessoes_contratadas + ').'); return; } if (idx === -1) { lead.id = 'l' + Date.now(); lead.data_cadastro = new Date().toISOString(); lead.cadastrado_por = currentUser ? currentUser.usuario : ''; SF_LEADS.push(lead); } else { const existing = SF_LEADS[idx] || {}; SF_LEADS[idx] = Object.assign({}, existing, lead); } salvarLeads(); // ── Supabase: persistir lead ── if (_supabase) { DB.saveLead(lead).catch(e => console.warn('[SupremaHub] saveLead error:', e)); } fecharModal('modal-lead'); _leadSearch = ''; renderPaginaLeads(); sincronizarLeadsComDashboard(); } function excluirLead(idx) { SF_LEADS = INTEGRITY.loadWithVerification('sf_leads', []); const l = SF_LEADS[idx]; if (!l) return; if (!confirm('Excluir lead "' + l.nome + '"? Esta ação não pode ser desfeita.')) return; const _delLead = SF_LEADS[idx]; SF_LEADS.splice(idx, 1); if (_supabase && _delLead && (_delLead.supabase_id || _delLead.id)) DB.deleteLead(_delLead.supabase_id || _delLead.id).catch(console.error); salvarLeads(); renderPaginaLeads(); sincronizarLeadsComDashboard(); } function etapaParaStatus(etapa) { const map = { 'conexão':'Lead Novo','qualificação':'Lead Novo','agendamento':'Agendado', 'consulta realizada':'Consultado','formalização':'Consultado','venda feita':'Vendido' }; return map[etapa] || 'Lead Novo'; } function sincronizarLeadsComDashboard() { // Carrega dados com fallback robusto: INTEGRITY → raw localStorage → _FALLBACK_LEADS try { var _fromInteg = INTEGRITY.loadWithVerification('sf_leads', null); if (_fromInteg && _fromInteg.length > 0) { SF_LEADS = _fromInteg; } else { var _raw = localStorage.getItem('sf_leads'); SF_LEADS = (_raw ? JSON.parse(_raw) : null) || _FALLBACK_LEADS.slice(); INTEGRITY.saveWithChecksum('sf_leads', SF_LEADS); // corrige checksum } } catch(e) { if (\!SF_LEADS || SF_LEADS.length === 0) SF_LEADS = _FALLBACK_LEADS.slice(); } const mapped = SF_LEADS.map(l => ({ id: l.id, nome: l.nome, unidade: l.unidade || '', data_registro: l.data_cadastro ? l.data_cadastro.split('T')[0] : new Date().toISOString().split('T')[0], data_venda: (l.etapa === 'venda feita' && l.data_consulta) ? l.data_consulta : null, data_cirurgia: l.data_cirurgia || null, status: etapaParaStatus(l.etapa), status_lead: l.status_lead || 'Novo', // Financeiro procedimento: l.procedimento || '', adicional: l.adicional || 'Sem Bodyhair', valor_procedimento: parseFloat(l.valor_procedimento) || 0, valor_adicional: parseFloat(l.valor_adicional) || 0, valor_venda: (parseFloat(l.valor_procedimento) || 0) + (parseFloat(l.valor_adicional) || 0), sessoes_contratadas: l.sessoes_contratadas || 0, sessoes_realizadas: l.sessoes_realizadas || 0, // Comercial consultor: l.consultor || l.cadastrado_por || '', vendedor: l.consultor || l.cadastrado_por || '', origem: l.origem || '', forma_pagamento: l.forma_pagamento || '', status_pagamento: l.status_pagamento || '', tipo_consulta: 'Presencial', _from_crm: true })); DATA.pipeline = mapped; // Substitui inteiramente — impede que dados demo se misturem renderAll(); } // ---------- Busca de Leads ---------- let _leadSearch = ''; let _leadFiltroUnidade = ''; let _leadFiltroStatus = ''; let _leadFiltroOrigem = ''; let _leadFiltroProc = ''; function pesquisarLeads() { _leadSearch = (document.getElementById('crm-search') || {}).value || ''; renderPaginaLeads(); } function filtrarLeadUnidade(v) { _leadFiltroUnidade = v; renderPaginaLeads(); } function filtrarLeadStatus(v) { _leadFiltroStatus = v; renderPaginaLeads(); } function filtrarLeadOrigem(v) { _leadFiltroOrigem = v; renderPaginaLeads(); } function filtrarLeadProc(v) { _leadFiltroProc = v; renderPaginaLeads(); } function _buscarLeadGlobal(q) { // Busca rápida pelo nome, telefone ou CPF — navega para CRM se não estiver lá _leadSearch = q; _leadFiltroUnidade = ''; _leadFiltroStatus = ''; _leadFiltroOrigem = ''; _leadFiltroProc = ''; showPage('leads'); } function renderPaginaLeads() { const el = document.getElementById('page-leads'); if (!el) return; SF_LEADS = INTEGRITY.loadWithVerification('sf_leads', []); let leads = SF_LEADS; const isUnidade = currentUser && currentUser.role === 'unidade' && currentUser.unidade; if (isUnidade) leads = leads.filter(l => l.unidade === currentUser.unidade); // Filtros dropdown if (_leadFiltroUnidade) leads = leads.filter(l => l.unidade === _leadFiltroUnidade); if (_leadFiltroStatus) leads = leads.filter(l => (l.status_lead || 'Novo') === _leadFiltroStatus); if (_leadFiltroOrigem) leads = leads.filter(l => l.origem === _leadFiltroOrigem); if (_leadFiltroProc) leads = leads.filter(l => l.procedimento === _leadFiltroProc); // Busca textual (nome, telefone, CPF, consultor) if (_leadSearch.trim()) { const q = normalizarTexto(_leadSearch); const qTel = normalizarTelefone(_leadSearch); const qCpf = normalizarCpf(_leadSearch); leads = leads.filter(l => { if (normalizarTexto(l.nome).includes(q)) return true; if (normalizarTexto(l.consultor || '').includes(q)) return true; if (qTel.length >= 4 && normalizarTelefone(l.telefone).includes(qTel)) return true; if (qCpf.length >= 4 && normalizarCpf(l.cpf || '').includes(qCpf)) return true; return false; }); } // ——— KPIs financeiros da base filtrada ——— const baseFin = isUnidade ? SF_LEADS.filter(l => l.unidade === currentUser.unidade) : SF_LEADS; const convertidos = baseFin.filter(l => (l.status_lead === 'Convertido') || l.etapa === 'venda feita'); const fatTotal = convertidos.reduce((s, l) => s + ((parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0)), 0); const fatMesAtual = (() => { const mesStr = new Date().toISOString().substring(0,7); return convertidos.filter(l => (l.data_consulta || l.data_cadastro || '').substring(0,7) === mesStr) .reduce((s, l) => s + ((parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0)), 0); })(); const taxaConv = baseFin.length ? (convertidos.length / baseFin.length * 100) : 0; const mesoPendentes = baseFin.filter(l => { const cont = l.sessoes_contratadas || 0; const real = l.sessoes_realizadas || 0; return cont > 0 && real < cont; }).length; // KPIs por unidade const kpiUnidHtml = UNITS.map(u => { const uLeads = baseFin.filter(l => l.unidade === u); const uConv = uLeads.filter(l => (l.status_lead === 'Convertido') || l.etapa === 'venda feita'); const uFat = uConv.reduce((s, l) => s + ((parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0)), 0); return '
' + '
' + UNIT_NAMES[u] + '
' + '
' + fmtCurrency(uFat).replace('R$','') + '
' + '
' + uConv.length + ' conv. / ' + uLeads.length + ' leads
' + '
'; }).join(''); // Funil por status_lead const statusCount = {}; STATUS_LEAD.forEach(s => { statusCount[s] = baseFin.filter(l => (l.status_lead || 'Novo') === s).length; }); const pillsHtml = STATUS_LEAD.map(s => '
' + '
' + statusCount[s] + '
' + '
' + s + '
' ).join(''); // Etapas funil clássico (compacto) const etapaCount = {}; ETAPAS_FUNIL.forEach(e => { etapaCount[e] = baseFin.filter(l => l.etapa === e).length; }); // Tabela de leads const rows = leads.length ? leads.map(l => { const realIdx = SF_LEADS.indexOf(l); const dt = l.data_cadastro ? new Date(l.data_cadastro).toLocaleDateString('pt-BR') : '—'; const valTotal = (parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0); const sessoesInfo = (l.sessoes_contratadas > 0) ? '
' + (l.sessoes_realizadas||0) + '/' + l.sessoes_contratadas + ' sessões' : ''; return '' + '' + (l.nome||'') + '
' + (l.telefone||'') + '' + (l.cpf ? '
' + l.cpf + '' : '') + '' + '' + (l.status_lead||'Novo') + '' + '
' + (l.etapa||'—') + '' + '' + (l.procedimento ? '' + l.procedimento + '' : '') + (l.adicional && l.adicional !== 'Sem Bodyhair' ? '
' + l.adicional + '' : '') + sessoesInfo + '' + '' + (valTotal>0 ? fmtCurrency(valTotal) : '—') + '' + '' + (l.origem||'—') + '' + '
' + (l.consultor||'—') + '' + '' + (l.unidade||'—') + '' + '
' + dt + '' + '
' + '' + '' + '
'; }).join('') : 'Nenhum lead encontrado para os filtros aplicados'; // Filtros rápidos (selects) const filtroUnidOpts = '' + UNITS.map(u => '').join(''); const filtroStatusOpts = '' + STATUS_LEAD.map(s => '').join(''); const filtroOrigemOpts = '' + ORIGENS_LEAD.map(o => '').join(''); const filtroProcOpts = '' + PROCEDIMENTOS.map(p => '').join(''); el.innerHTML = '
' + '

CRM de Leads

' + '
' + '
' + // KPIs financeiros '
' + '
' + '' + '
Faturamento Total
' + '
' + fmtCurrency(fatTotal).replace('R$','') + '
' + '
' + convertidos.length + ' clientes convertidos
' + '
' + '
' + '' + '
Faturamento do Mês
' + '
' + fmtCurrency(fatMesAtual).replace('R$','') + '
' + '
' + new Date().toLocaleDateString('pt-BR',{month:'long',year:'numeric'}) + '
' + '
' + '
' + '' + '
Taxa de Conversão
' + '
' + taxaConv.toFixed(1) + '%
' + '
' + baseFin.length + ' leads no total
' + '
' + '
' + '' + '
Sessões Meso Pendentes
' + '
' + mesoPendentes + '
' + '
pacotes com sessões em aberto
' + '
' + '
' + // KPIs por unidade (!isUnidade ? '
' + kpiUnidHtml + '
' : '') + // Funil status '
' + pillsHtml + '
' + (_leadFiltroStatus ? '
Filtrando por: ' + _leadFiltroStatus + 'limpar
' : '') + // Barra de busca + filtros '
' + '
' + '
' + '' + '' + '
' + '' + '
' + '
' + (!isUnidade ? '' : '') + '' + '' + '' + '
' + '
' + // Tabela '
' + '' + '' + '' + rows + '' + '
Nome / Telefone / CPFStatus / EtapaProcedimentoValor TotalOrigem / ConsultorUnidade / DataAções
' + '
' + leads.length + ' lead(s) exibido(s) de ' + SF_LEADS.length + ' total
' + '
' + // ——— Análise Financeira Detalhada ——— _renderAnaliseFinanceiraCRM(baseFin); } function _renderAnaliseFinanceiraCRM(leads) { // Faturamento por procedimento const procMap = {}; leads.forEach(l => { if (!l.procedimento) return; if (!procMap[l.procedimento]) procMap[l.procedimento] = { count: 0, fat: 0 }; procMap[l.procedimento].count++; procMap[l.procedimento].fat += (parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0); }); const procRows = Object.entries(procMap).sort((a,b) => b[1].fat - a[1].fat).map(([p, d]) => '' + p + '' + d.count + '' + '' + fmtCurrency(d.fat) + '' ).join('') || 'Nenhum procedimento registrado'; // Faturamento por origem const origMap = {}; leads.forEach(l => { const o = l.origem || 'Não informado'; if (!origMap[o]) origMap[o] = { count: 0, fat: 0 }; origMap[o].count++; origMap[o].fat += (parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0); }); const origRows = Object.entries(origMap).sort((a,b) => b[1].fat - a[1].fat).map(([o, d]) => '' + o + '' + d.count + '' + '' + fmtCurrency(d.fat) + '' ).join(''); // Faturamento por consultor const consMap = {}; leads.forEach(l => { const c = l.consultor || l.cadastrado_por || 'Não informado'; if (!consMap[c]) consMap[c] = { count: 0, fat: 0, conv: 0 }; consMap[c].count++; consMap[c].fat += (parseFloat(l.valor_procedimento)||0) + (parseFloat(l.valor_adicional)||0); if ((l.status_lead === 'Convertido') || l.etapa === 'venda feita') consMap[c].conv++; }); const consRows = Object.entries(consMap).sort((a,b) => b[1].fat - a[1].fat).map(([c, d]) => { const taxa = d.count ? (d.conv / d.count * 100).toFixed(1) : '0.0'; return '' + c + '' + d.count + '' + '' + d.conv + ' (' + taxa + '%)' + '' + fmtCurrency(d.fat) + ''; }).join('') || 'Nenhum consultor registrado'; // Pacotes de meso em aberto const mesoPend = leads.filter(l => (l.sessoes_contratadas||0) > 0 && (l.sessoes_realizadas||0) < (l.sessoes_contratadas||0)); const mesoRows = mesoPend.length ? mesoPend.map(l => { const pct = Math.round((l.sessoes_realizadas || 0) / l.sessoes_contratadas * 100); return '' + '' + (l.nome||'') + '
' + (l.telefone||'') + '' + '' + (l.procedimento||'—') + '' + '' + (l.sessoes_realizadas||0) + ' / ' + l.sessoes_contratadas + '' + '' + '
' + '
' + '
' + '' + '' + (l.unidade||'—') + '' + ''; }).join('') : 'Nenhum pacote com sessões pendentes'; const cardStyle = 'background:var(--card);border:1px solid var(--border);border-radius:12px;padding:18px;margin-bottom:12px'; const thStyle = 'text-align:left;padding:8px 12px;color:var(--muted);font-size:.67rem;text-transform:uppercase;letter-spacing:.4px;border-bottom:1px solid var(--border)'; const tdStyle = 'padding:9px 12px;border-bottom:1px solid rgba(37,37,69,.3);font-size:.8rem'; return '
' + '

Análise Financeira Detalhada

' + '
' + // Por procedimento '
' + '
Por Procedimento
' + '' + '' + procRows.replace(/padding:9px/g, 'padding:8px') + '
ProcedimentoQtdFaturamento
' + '
' + // Por origem '
' + '
Por Origem do Lead
' + '' + '' + origRows + '
OrigemLeadsFaturamento
' + '
' + '
' + // Por consultor (linha inteira) '
' + '
Desempenho por Consultor
' + '' + '' + consRows + '
ConsultorLeadsConversõesFaturamento
' + '
' + // Sessões meso pendentes '
' + '
Pacotes de Meso — Sessões Pendentes
' + '' + '' + mesoRows + '
PacienteProcedimentoSessõesProgressoUnidade
' + '
' + '
'; } // ---------- Entrada Diária ---------- function renderPaginaEntradaDiaria() { const el = document.getElementById('page-entrada-diaria'); if (!el) return; SF_DAILY = INTEGRITY.loadWithVerification('sf_daily', {}); const isUnidade = currentUser && currentUser.role === 'unidade'; const unidade = isUnidade ? currentUser.unidade : null; const hoje = new Date().toISOString().split('T')[0]; const chave = (unidade || 'GERAL') + '_' + hoje; const saved = SF_DAILY[chave] || {}; const v = field => saved[field] !== undefined ? saved[field] : ''; const unitSelectHtml = isUnidade ? '' : '
'; el.innerHTML = '
' + '
' + '

Entrada Diária de Leads

' + '
' + '' + hoje + '' + '
' + '
' + '
Unidade e Data
' + '
' + unitSelectHtml + '
' + '
' + '
Contadores do Dia
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '
Histórico Recente
' + '
' + renderHistoricoEntradas(unidade) + '
' + '
' + '
'; } function recarregarEntradaUnidade() { SF_DAILY = INTEGRITY.loadWithVerification('sf_daily', {}); const unid = (document.getElementById('ed-unidade') || {}).value || ''; const data = (document.getElementById('ed-data') || {}).value || ''; const chave = (unid || 'GERAL') + '_' + data; const saved = SF_DAILY[chave] || {}; const setv = (id, val) => { const el = document.getElementById(id); if (el) el.value = val !== undefined ? val : ''; }; setv('ed-recebidos', saved.recebidos); setv('ed-agendados', saved.agendados); setv('ed-compareceram', saved.compareceram); setv('ed-faltaram', saved.faltaram); setv('ed-reagendaram', saved.reagendaram); } function renderHistoricoEntradas(filtroUnidade) { SF_DAILY = INTEGRITY.loadWithVerification('sf_daily', {}); const entries = Object.entries(SF_DAILY) .filter(([k]) => !filtroUnidade || k.startsWith(filtroUnidade + '_')) .map(([k, v]) => v) .sort((a, b) => (b.data || '').localeCompare(a.data || '')) .slice(0, 14); if (!entries.length) return '

Nenhum registro ainda.

'; const rows = entries.map(e => '' + '' + (e.data || '—') + '' + '' + (e.unidade || '—') + '' + '' + (e.recebidos || 0) + '' + '' + (e.agendados || 0) + '' + '' + (e.compareceram || 0) + '' + '' + (e.faltaram || 0) + '' + '' + (e.reagendaram || 0) + '' + '' ).join(''); return '' + '' + '' + rows + '' + '
DataUnidadeRecebidosAgendadosCompareceramFaltaramReagendaram
'; } function salvarEntradaDiaria() { const unidade = (document.getElementById('ed-unidade') || {}).value || ''; const data = (document.getElementById('ed-data') || {}).value || new Date().toISOString().split('T')[0]; const chave = (unidade || 'GERAL') + '_' + data; const msg = document.getElementById('ed-msg'); SF_DAILY[chave] = { unidade, data, recebidos: parseInt((document.getElementById('ed-recebidos') || {}).value) || 0, agendados: parseInt((document.getElementById('ed-agendados') || {}).value) || 0, compareceram: parseInt((document.getElementById('ed-compareceram') || {}).value) || 0, faltaram: parseInt((document.getElementById('ed-faltaram') || {}).value) || 0, reagendaram: parseInt((document.getElementById('ed-reagendaram') || {}).value) || 0, salvo_em: new Date().toISOString(), salvo_por: currentUser ? currentUser.usuario : '' }; salvarDaily(); const hist = document.getElementById('ed-historico'); if (hist) hist.innerHTML = renderHistoricoEntradas(currentUser && currentUser.role === 'unidade' ? currentUser.unidade : null); if (msg) { msg.style.display = 'block'; msg.style.background = 'rgba(0,230,118,.08)'; msg.style.border = '1px solid rgba(0,230,118,.25)'; msg.style.color = 'var(--green)'; msg.innerHTML = 'Dados salvos com sucesso! Dashboard atualizado em tempo real.'; setTimeout(() => { if (msg) msg.style.display = 'none'; }, 4000); } sincronizarLeadsComDashboard(); } // ================================================================ // MÓDULO DE SEGURANÇA, INTEGRIDADE E BACKUP — SupremaHub v3.0 // ================================================================ const SYSTEM_VERSION = '3.0.0'; const SYSTEM_BUILD = '2026-05-28'; // ── Integridade de dados ───────────────────────────────────────── const INTEGRITY = { checksum(str) { let a = 1, b = 0; for (let i = 0; i < str.length; i++) { a = (a + str.charCodeAt(i)) % 65521; b = (b + a) % 65521; } return ((b << 16) | a) >>> 0; }, saveWithChecksum(key, data) { try { const json = JSON.stringify(data); localStorage.setItem(key, json); localStorage.setItem(key + '_cs', String(this.checksum(json))); } catch(e) { console.error('[INTEGRITY] save error', key, e); } }, loadWithVerification(key, fallback) { if (fallback === undefined) fallback = null; const json = localStorage.getItem(key); if (!json) return fallback; const stored_cs = localStorage.getItem(key + '_cs'); if (stored_cs) { const actual_cs = this.checksum(json); if (String(actual_cs) !== stored_cs) { console.warn('[INTEGRITY] Checksum mismatch for', key, '— tentando recuperação de backup'); if (typeof AUDIT !== 'undefined') AUDIT.log('INTEGRITY', 'CHECKSUM_FAIL', { key }); const bk = typeof BACKUP !== 'undefined' ? BACKUP.getMostRecent() : null; if (bk && bk.data) { const recovered = key === 'sf_leads' ? bk.data.leads : key === 'sf_daily' ? bk.data.daily : key === 'sf_users' ? bk.data.users : null; if (recovered !== null) return recovered; } return fallback; } } try { return JSON.parse(json); } catch { return fallback; } }, validateLead(l) { return !!(l && typeof l === 'object' && l.nome && typeof l.nome === 'string' && l.nome.trim() && l.etapa); }, validateAll() { return SF_LEADS.map((l, i) => (!this.validateLead(l) ? { index: i, id: l.id } : null)).filter(Boolean); } }; // ── Log de auditoria ───────────────────────────────────────────── const AUDIT = { _key: 'sf_audit_log', MAX: 500, log(module, event, data) { try { const entries = this.get(); entries.push({ ts: new Date().toISOString(), user: typeof currentUser !== 'undefined' && currentUser ? currentUser.usuario : 'system', module, event, data: data || {} }); if (entries.length > this.MAX) entries.splice(0, entries.length - this.MAX); localStorage.setItem(this._key, JSON.stringify(entries)); } catch(e) {} }, get() { try { return JSON.parse(localStorage.getItem(this._key) || '[]'); } catch { return []; } }, clear() { localStorage.removeItem(this._key); } }; // ── Segurança / Sessão ─────────────────────────────────────────── const SEC = { MAX_ATTEMPTS: 5, LOCKOUT_MS: 15 * 60 * 1000, SESSION_MS: 60 * 60 * 1000, _timer: null, _key: 'sf_login_sec', _state() { try { return JSON.parse(localStorage.getItem(this._key) || '{}'); } catch { return {}; } }, _save(s) { localStorage.setItem(this._key, JSON.stringify(s)); }, isLocked() { const s = this._state(); if (!s.lockedUntil || Date.now() >= s.lockedUntil) { if (s.lockedUntil) this._save({ count: 0, lockedUntil: 0 }); return false; } return true; }, getLockRemaining() { const s = this._state(); return Math.max(0, Math.ceil(((s.lockedUntil||0) - Date.now()) / 60000)); }, recordFailure() { const s = this._state(); const count = (s.count || 0) + 1; this._save({ count, lockedUntil: count >= this.MAX_ATTEMPTS ? Date.now() + this.LOCKOUT_MS : (s.lockedUntil || 0) }); return count; }, resetFailures() { this._save({ count: 0, lockedUntil: 0 }); }, startSessionTimer() { clearTimeout(this._timer); this._timer = setTimeout(() => { if (typeof currentUser !== 'undefined' && currentUser) { AUDIT.log('SEC', 'SESSION_TIMEOUT', { user: currentUser.usuario }); alert('Sessão expirada por inatividade (60 min). Faça login novamente.'); fazerLogout(); } }, this.SESSION_MS); }, resetSession() { if (typeof currentUser !== 'undefined' && currentUser) this.startSessionTimer(); }, sanitize(str) { if (typeof str !== 'string') return str; return str.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); }, stripTags(str) { return typeof str === 'string' ? str.replace(/<[^>]*>/g,'').trim() : str; } }; // ── Sistema de Backup ──────────────────────────────────────────── const BACKUP = { PFX: 'sf_backup_', KEY_LAST: 'sf_backup_last', KEY_SHEETS: 'sf_backup_sheets_url', MAX_LOCAL: 10, INTERVAL_MS: 60 * 60 * 1000, _timer: null, start() { clearInterval(this._timer); this._timer = setInterval(() => this.perform('auto'), this.INTERVAL_MS); const last = this.getLastInfo(); const age = last ? Date.now() - new Date(last.timestamp).getTime() : Infinity; if (age > 2 * 60 * 60 * 1000) setTimeout(() => this.perform('init'), 6000); }, async perform(trigger) { trigger = trigger || 'manual'; const ts = new Date().toISOString(); const data = { leads: JSON.parse(JSON.stringify(SF_LEADS)), daily: JSON.parse(JSON.stringify(SF_DAILY)), users: JSON.parse(JSON.stringify(USUARIOS)) }; const cs = INTEGRITY.checksum(JSON.stringify(data.leads)); const obj = { timestamp: ts, trigger, version: SYSTEM_VERSION, counts: { leads: SF_LEADS.length, daily: Object.keys(SF_DAILY).length, users: USUARIOS.length }, checksum: cs, data }; const key = this.PFX + Date.now(); try { localStorage.setItem(key, JSON.stringify(obj)); } catch(e) { this._prune(true); try { localStorage.setItem(key, JSON.stringify(obj)); } catch {} } localStorage.setItem(this.KEY_LAST, JSON.stringify({ timestamp: ts, key, trigger, counts: obj.counts, checksum: cs })); this._prune(); AUDIT.log('BACKUP', 'PERFORMED', { trigger, counts: obj.counts }); this._updateFooter(ts); const url = localStorage.getItem(this.KEY_SHEETS); if (url && url.startsWith('https://script.google.com/')) { try { await fetch(url, { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timestamp: ts, version: SYSTEM_VERSION, trigger, leads_count: SF_LEADS.length, checksum: cs, leads: JSON.stringify(data.leads) }) }); AUDIT.log('BACKUP', 'SHEETS_SENT', { ts }); } catch(e) { AUDIT.log('BACKUP', 'SHEETS_ERROR', { error: e.message }); } } return obj; }, _prune(aggressive) { const keys = this._keys().sort(); const max = aggressive ? Math.floor(this.MAX_LOCAL / 2) : this.MAX_LOCAL; while (keys.length > max) localStorage.removeItem(keys.shift()); }, _keys() { const k=[]; for(let i=0;i { try { const b=JSON.parse(localStorage.getItem(k)||'{}'); return {key:k, timestamp:b.timestamp, trigger:b.trigger, counts:b.counts, version:b.version}; } catch { return null; } }).filter(Boolean); }, restore(key) { try { const b = JSON.parse(localStorage.getItem(key)||'null'); if (!b||!b.data) return false; if (INTEGRITY.checksum(JSON.stringify(b.data.leads)) !== b.checksum) if (!confirm('Aviso: checksum diverge. Restaurar mesmo assim?')) return false; SF_LEADS = b.data.leads || []; SF_DAILY = b.data.daily || {}; INTEGRITY.saveWithChecksum('sf_leads', SF_LEADS); INTEGRITY.saveWithChecksum('sf_daily', SF_DAILY); AUDIT.log('BACKUP', 'RESTORED', { key, counts: b.counts }); return true; } catch(e) { AUDIT.log('BACKUP', 'RESTORE_ERROR', { key, error: e.message }); return false; } }, downloadJSON() { const blob = new Blob([JSON.stringify({ export_timestamp: new Date().toISOString(), version: SYSTEM_VERSION, leads: SF_LEADS, daily: SF_DAILY, users: USUARIOS.map(u=>({...u, senha:'[REDACTED]'})) }, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'supremahub_backup_' + new Date().toISOString().replace(/[:.]/g,'-') + '.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); AUDIT.log('BACKUP', 'DOWNLOADED', { leads: SF_LEADS.length }); }, importJSON(file) { return new Promise((ok, fail) => { const r = new FileReader(); r.onload = e => { try { const d = JSON.parse(e.target.result); if (!d.leads||!Array.isArray(d.leads)) { fail('Campo leads ausente'); return; } ok(d); } catch(err) { fail('JSON inválido: '+err.message); } }; r.readAsText(file); }); }, setSheetsUrl(url) { if (url && url.startsWith('https://script.google.com/')) { localStorage.setItem(this.KEY_SHEETS, url); return true; } localStorage.removeItem(this.KEY_SHEETS); return false; }, getSheetsUrl() { return localStorage.getItem(this.KEY_SHEETS) || ''; }, _updateFooter(ts) { const el = document.getElementById('footer-last-backup'); if (el) { const d = new Date(ts); el.textContent = 'Backup: ' + d.toLocaleDateString('pt-BR') + ' ' + d.toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}); } } }; // ── Registro de atualização e rodapé ──────────────────────────── function _registrarAtualizacao() { const ts = new Date().toISOString(); localStorage.setItem('sf_last_update', ts); const el = document.getElementById('footer-last-update'); if (el) { const d = new Date(ts); el.textContent = 'Atualizado: ' + d.toLocaleDateString('pt-BR') + ' ' + d.toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}); } } function _initSistema() { const vEl = document.getElementById('footer-version'); if (vEl) vEl.textContent = 'SupremaHub v' + SYSTEM_VERSION + ' | ' + SYSTEM_BUILD; const lu = localStorage.getItem('sf_last_update'); if (lu) { const el = document.getElementById('footer-last-update'); if (el) { const d = new Date(lu); el.textContent = 'Atualizado: ' + d.toLocaleDateString('pt-BR') + ' ' + d.toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}); } } const lb = BACKUP.getLastInfo(); if (lb) BACKUP._updateFooter(lb.timestamp); } // ── Painel de Segurança & Backup ───────────────────────────────── function showBackupPanel() { if (!currentUser || currentUser.role !== 'admin') return; const list = BACKUP.getList(); const sheetsUrl = BACKUP.getSheetsUrl(); const issues = INTEGRITY.validateAll(); const auditLog = AUDIT.get().slice(-50).reverse(); const listHTML = list.length ? list.map(b => ` ${new Date(b.timestamp).toLocaleString('pt-BR')} ${b.trigger} ${b.counts ? b.counts.leads + ' leads' : '—'} v${b.version} `).join('') : 'Nenhum backup local.'; const auditHTML = auditLog.length ? auditLog.map(e => ` ${new Date(e.ts).toLocaleString('pt-BR')} ${e.user} ${e.module} ${e.event} `).join('') : 'Log vazio.'; const intHtml = issues.length ? ` ${issues.length} problema(s) detectado(s)` : ` Todos os ${SF_LEADS.length} leads e ${Object.keys(SF_DAILY).length} entradas diárias passaram na verificação`; const html = `

Segurança, Integridade & Backup

${[['Leads',SF_LEADS.length,'var(--gold)'],['Backups Locais',list.length,'var(--green)'],['Log Auditoria',AUDIT.get().length,'var(--accent)'],['Versão',SYSTEM_VERSION,'var(--text)']].map(([l,v,c])=>`
${v}
${l}
`).join('')}
Verificação de Integridade

${intHtml}
Backup Automático no Google Sheets

Cole a URL do Google Apps Script Web App para envio automático a cada hora.

${sheetsUrl ? '✓ Backup automático para Google Sheets ativo' : 'Sem URL configurada'}
🔄 Sincronização Automática de Vendas (Sheets → Dashboard)

Cole a URL do Google Apps Script de leitura (supremahub_sheets_sync.gs). O sistema importará vendas automaticamente a cada 60 segundos, sem duplicação. Leads, metas e entradas diárias permanecem manuais.

${SHEETS_SYNC.getUrl() ? '✓ Sincronização ativa — próxima verificação em até 60s' : 'Sem URL configurada — sem sincronização automática'}
Backups Locais (${list.length}/${BACKUP.MAX_LOCAL} máx)
${listHTML}
Data/HoraTipoDadosVersãoAção
Log de Auditoria (últimas 50 de ${AUDIT.get().length})
${auditHTML}
Data/Hora UsuárioMóduloEvento
Como configurar Google Sheets (passo a passo)
1. Acesse sheet.new → crie uma planilha.
2. Clique em Extensões → Apps Script.
3. Cole o código abaixo → Implantar → Novo Implantação → App da Web.
4. Em "Quem tem acesso" selecione "Qualquer pessoa" → copie a URL.
5. Cole a URL no campo acima e clique em Salvar.

function doPost(e) {
  try {
    var d = JSON.parse(e.postData.contents);
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var log = ss.getSheetByName('BackupLog') || ss.insertSheet('BackupLog');
    if (log.getLastRow() === 0) log.appendRow(['Timestamp','Trigger','Leads','Versao','Checksum']);
    log.appendRow([d.timestamp, d.trigger, d.leads_count, d.version, d.checksum]);
    var ls = ss.getSheetByName('Leads') || ss.insertSheet('Leads');
    var leads = JSON.parse(d.leads || '[]');
    ls.clearContents();
    if (leads.length > 0) {
      ls.appendRow(Object.keys(leads[0]));
      leads.forEach(function(l) { ls.appendRow(Object.values(l)); });
    }
    return ContentService.createTextOutput(JSON.stringify({ok:true})).setMimeType(ContentService.MimeType.JSON);
  } catch(err) {
    return ContentService.createTextOutput(JSON.stringify({ok:false,error:err.message})).setMimeType(ContentService.MimeType.JSON);
  }
}
SupremaHub v${SYSTEM_VERSION} · Build ${SYSTEM_BUILD} · Sessão expira após 60 min de inatividade · Bloqueio após ${SEC.MAX_ATTEMPTS} tentativas incorretas · Backup automático a cada hora
`; const old = document.getElementById('_bk_panel'); if (old) old.remove(); document.body.insertAdjacentHTML('beforeend', html); } function _salvarSheetsUrl() { const url = (document.getElementById('_sh_url')||{}).value||''; const ok = BACKUP.setSheetsUrl(url.trim()); const el = document.getElementById('_sh_status'); if (el) el.textContent = ok ? '✓ URL salva — backup automático ativo' : (url ? '✗ URL inválida (deve ser do Google Apps Script)' : 'URL removida'); AUDIT.log('CONFIG', 'SHEETS_URL', { hasUrl: !!ok }); } function _importarBackup(file) { if (!file) return; BACKUP.importJSON(file).then(data => { if (!confirm('Importar backup de ' + new Date(data.export_timestamp||Date.now()).toLocaleString('pt-BR') + '?\n' + data.leads.length + ' leads serão carregados.')) return; SF_LEADS = data.leads; if (data.daily) SF_DAILY = data.daily; INTEGRITY.saveWithChecksum('sf_leads', SF_LEADS); INTEGRITY.saveWithChecksum('sf_daily', SF_DAILY); _registrarAtualizacao(); sincronizarLeadsComDashboard(); typeof renderizarLeads === 'function' && renderizarLeads(); AUDIT.log('BACKUP', 'IMPORTED', { leads: SF_LEADS.length, file: file.name }); alert('Importado: ' + SF_LEADS.length + ' leads.'); const p = document.getElementById('_bk_panel'); if (p) p.remove(); }).catch(err => alert('Erro ao importar: ' + err)); } // ══════════════════════════════════════════════════════════════════════ // MÓDULO: SHEETS_SYNC — Sincronização automática com Google Sheets // Lê as 4 planilhas a cada 60s e importa vendas sem duplicar dados // Leads, metas e entrada diária permanecem exclusivamente manuais // ══════════════════════════════════════════════════════════════════════ var SHEETS_SYNC = { KEY: 'sf_sheets_sync_url', INTERVAL_MS: 60000, _timer: null, _running: false, getUrl: function() { return localStorage.getItem(this.KEY) || ''; }, setUrl: function(url) { if (url && url.indexOf('https://script.google.com/') === 0) { localStorage.setItem(this.KEY, url); return true; } localStorage.removeItem(this.KEY); return false; }, _badge: function(msg, cor) { var el = document.getElementById('_sync_status_badge'); if (el) { el.textContent = msg; el.style.color = cor || 'var(--muted)'; } }, // ── Busca dados do GAS e processa ────────────────────────────── sync: async function() { if (this._running) return; var url = this.getUrl(); if (!url) { this._badge('⊙ Sheets Sync (sem URL)', 'var(--muted)'); return; } this._running = true; this._badge('⟳ Sincronizando Sheets...', '#ffd600'); try { var sep = url.indexOf('?') >= 0 ? '&' : '?'; var resp = await fetch(url + sep + '_t=' + Date.now(), { redirect: 'follow', cache: 'no-cache' }); if (!resp.ok) throw new Error('HTTP ' + resp.status); var data = await resp.json(); if (!Array.isArray(data.vendas)) throw new Error('Resposta inválida do GAS'); var added = this._processar(data.vendas); var agora = new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); var msg = added > 0 ? ('✓ Sheets ' + agora + ' · +' + added + ' nova(s)') : ('✓ Sheets ' + agora + ' · ok'); this._badge(msg, 'var(--green)'); if (added > 0) { salvarLeads(); sincronizarLeadsComDashboard(); AUDIT.log('SYNC', 'SHEETS_IMPORT', { added: added, total: SF_LEADS.length }); console.info('[SheetSync] ' + added + ' venda(s) importada(s). Total leads: ' + SF_LEADS.length); } } catch (err) { console.warn('[SheetSync] Erro:', err.message); this._badge('⚠ Sheets: ' + err.message.substring(0, 40), '#f44336'); AUDIT.log('SYNC', 'SHEETS_ERROR', { error: err.message }); } finally { this._running = false; } }, // ── Deduplica e insere novas vendas em SF_LEADS ───────────────── _processar: function(vendas) { // Recarrega o array mais recente da memória (não do storage — evita race conditions) // Constrói mapa de chaves existentes para dedup rápido O(n) var chaves = {}; for (var i = 0; i < SF_LEADS.length; i++) { var l = SF_LEADS[i]; // Chave de registros já importados via Sheets if (l._sheets_key) chaves[l._sheets_key] = true; // Chave construída de registros manuais (para não duplicar com Sheets) var cpfD = (l.cpf || '').replace(/\D/g, ''); var dVend = l.data_consulta || (l.data_cadastro ? l.data_cadastro.split('T')[0] : '') || l.data_registro || ''; if (cpfD.length >= 9 && dVend) chaves[cpfD + '|' + dVend] = true; } var adicionados = 0; for (var j = 0; j < vendas.length; j++) { var v = vendas[j]; if (!v._key) continue; if (chaves[v._key]) continue; // Já existe — pula var cpfDigits = (v.cpf || '').replace(/\D/g, ''); if (cpfDigits.length < 9) continue; // CPF inválido if (!v.data_venda) continue; // Sem data — pula // Determina tipo e sessões padrão var ehMeso = !!v.eh_mesoterapia; var proc = v.procedimento || (ehMeso ? 'MESOTERAPIA' : 'Transplante Capilar'); var sessoes = ehMeso ? 0 : 18; var lead = { id: 'sh_' + v._key.replace(/[^a-zA-Z0-9]/g, '_'), _from_sheets: true, _sheets_key: v._key, // Identificação nome: v.paciente || '', cpf: v.cpf || '', telefone: '', email: '', // Unidade e funil unidade: v.unidade || 'GRU', etapa: 'venda feita', status_lead: 'Convertido', // Datas data_cadastro: v.data_venda + 'T12:00:00.000Z', data_consulta: v.data_venda, // usado por sincronizarLeadsComDashboard → data_venda data_cirurgia: v.data_cirurgia || '', // Financeiro procedimento: proc, adicional: 'Sem Bodyhair', valor_procedimento: v.valor_contrato || 0, valor_adicional: 0, valor_venda: v.valor_contrato || 0, sessoes_contratadas: sessoes, sessoes_realizadas: 0, // Comercial consultor: v.consultor || '', vendedor: v.consultor || '', origem: 'Planilha ' + (v.planilha || v.unidade || ''), forma_pagamento: v.forma_pagamento || '', status_pagamento:'', // Operacional compareceu: 'Sim', reagendou: 'Não', obs: 'Importado automaticamente — ' + (v.planilha || ''), cadastrado_por: '_sheets_sync' }; SF_LEADS.push(lead); chaves[v._key] = true; chaves[cpfDigits + '|' + v.data_venda] = true; // evita cross-dup com manuais adicionados++; } return adicionados; }, // ── Inicia o ciclo de sync ────────────────────────────────────── start: function() { var self = this; var url = this.getUrl(); if (!url) { this._badge('⊙ Sheets Sync (não configurado)', 'var(--muted)'); console.info('[SheetSync] URL não configurada. Configure em Painel de Segurança > Sincronização.'); return; } // Sync imediato ao carregar this.sync(); // Depois a cada 60 segundos this._timer = setInterval(function() { self.sync(); }, this.INTERVAL_MS); console.info('[SheetSync] Iniciado — intervalo: ' + (this.INTERVAL_MS / 1000) + 's'); }, stop: function() { if (this._timer) { clearInterval(this._timer); this._timer = null; } } }; function _salvarSyncUrl() { var input = document.getElementById('_sync_url_input'); var url = input ? input.value.trim() : ''; var ok = SHEETS_SYNC.setUrl(url); var el = document.getElementById('_sync_cfg_status'); if (el) { el.style.color = ok ? 'var(--green)' : (url ? '#f44336' : 'var(--muted)'); el.textContent = ok ? '✓ URL salva — sync iniciará em instantes' : (url ? '✗ URL inválida (deve começar com https://script.google.com/)' : 'URL removida — sync desativado'); } if (ok) { SHEETS_SYNC.stop(); SHEETS_SYNC.start(); } AUDIT.log('CONFIG', 'SHEETS_SYNC_URL', { hasUrl: !!ok }); } // ── ANTIBUG: protege funções de render após todas estarem definidas ── (function() { const _wrap = (name, fn, cid) => ANTIBUG.safe(name, fn, cid); if (typeof renderRankings === 'function') window.renderRankings = _wrap('renderRankings', renderRankings, 'page-rankings'); if (typeof renderMetas === 'function') window.renderMetas = _wrap('renderMetas', renderMetas, 'page-metas'); if (typeof renderComparativo=== 'function') window.renderComparativo= _wrap('renderComparativo',renderComparativo,'page-comparativo'); if (typeof showPage === 'function') { const _origShowPage = showPage; window.showPage = function(page) { try { return _origShowPage(page); } catch(err) { ANTIBUG.toast('Erro ao navegar para "' + page + '"', 'erro'); console.error('[ANTIBUG] showPage', page, err); } }; } if (typeof _initSistema === 'function') { const _origInit = _initSistema; window._initSistema = function() { try { return _origInit(); } catch(err) { console.error('[ANTIBUG] _initSistema', err); ANTIBUG.toast('Erro na inicialização. Recarregue a página.', 'erro'); } }; } })(); // ── Inicialização segura após DOMContentLoaded ─────────────────── document.addEventListener('DOMContentLoaded', function() { // Reset de sessão em qualquer interação ['click','keydown','mousemove','touchstart'].forEach(function(ev) { document.addEventListener(ev, function() { SEC.resetSession(); }, { passive: true }); }); // Init rodapé e backup após sessão ser verificada setTimeout(_initSistema, 900); // Inicia backup automático BACKUP.start(); // Inicia sincronização automática com Google Sheets (após 4s para não atrasar o render inicial) setTimeout(function() { SHEETS_SYNC.start(); }, 4000); // Log de inicialização setTimeout(function() { AUDIT.log('SYS', 'INIT', { version: SYSTEM_VERSION }); }, 2000); });