💼 CRM Financeiro

Faça login para acessar o sistema

Dashboard

🔔

Bom dia! 👋

Aqui está o resumo do seu CRM hoje

📈 Desempenho — últimos 30 dias

🎯 Funil de Vendas

👥 Clientes

NomeCPFWhatsAppOrigemStatusEtapaPropostasAções

📋 Propostas

ClienteTipoBancoValorParcelaPrazoStatusComissãoAções

✅ Tarefas Pendentes

TítuloClienteTipoPrioridadeVencimentoStatus

🧠 Base de Conhecimento do Agente IA

Ensine o agente sobre sua empresa, produtos e como atender seus clientes

👨‍💼 Equipe de Agentes

⭐ Satisfação dos Clientes (NPS)

📋 Respostas Recentes

NotaClienteCategoriaComentárioData

📱 Leads de Redes Sociais

Plataforma Cliente Tipo Mensagem Status Etapa Data Ação

📈 Leads por dia

💰 Volume contratado por dia

🌐 Origem dos leads

📋 Propostas por produto

🏆 Performance por Agente

AgentePropostasContratadasVolumeComissões

📅 Agenda de hoje

🎯 Meta do mês

💼 Financeiro

Faturamento, contas a pagar/receber, DRE e fluxo de caixa

⚠️ Vencendo em 5 dias

🗂️ Por Categoria (mês atual)

📊 Evolução 12 meses

📈 DRE — Demonstrativo de Resultado

💰 Comissões por Produto

🤝 Comissões por Parceiro

💧 Fluxo de Caixa — 6 meses

Mês Receitas Pagas Receitas Previstas Despesas Pagas Despesas Previstas Resultado Saldo Acumulado

📦 Por Produto/Tipo de Crédito

ProdutoContratosVolumeComissãoTicket Médio

🤝 Por Parceiro

ParceiroIndicaçõesVolumeComissãoPago

👥 Por Agente

AgenteContratosVolumeComissão CRM
Saldos em tempo real baseados nos lançamentos pagos

📅 Histórico de Fechamentos

MêsReceita BrutaDespesasComissões PagasResultadoStatus

🤝 Parceiros

Gerencie correspondentes, indicadores e comissões

🔄 Régua de Relacionamento

Automatize tarefas de follow-up com sequências pré-definidas

🎂 Aniversariantes

Clique em "Hoje" ou "Esta semana" para ver os aniversariantes.

💬 Modelos de Mensagem

Textos prontos para WhatsApp, SMS e e-mail

🚫 Blacklist

CPFs e números restritos — leads falsos, fraudes, opt-out

ClienteCPFWhatsAppMotivoAdicionado porDataAções

🤝 Programa de Indicações

Rastreie indicações e gerencie bônus por cliente indicador

🏆 Ranking de Indicadores

📋 Histórico de Indicações

IndicadorIndicadoBônusStatus

🔁 Simulador de Portabilidade

Calcule a economia real da portabilidade e gere uma proposta em um clique

📊 Dados do Contrato Atual


🏦 Nova Proposta

📋 Análises Realizadas

ClienteBanco AtualBanco NovoEconomia/mêsEconomia TotalVale?Data

🏢 Empresa

📧 E-mail (SMTP)

💬 WhatsApp (Evolution API)

📱 SMS (Twilio)

🔍 Log de Auditoria

Data/HoraUsuárioPerfilAçãoTabelaIP

🧮 Calculadora de Crédito

6m36 meses96m

📊 Resultado da Simulação

📅 Tabela de Amortização

#ParcelaJurosAmortizaçãoSaldo

📂 Importar Leads via CSV

Aceita arquivos CSV separados por vírgula ou ponto-e-vírgula. Colunas reconhecidas: nome, cpf, telefone, whatsapp, email, cidade, estado, origem

📂

Clique para selecionar o arquivo CSV

Máximo 5MB · UTF-8 ou Latin-1

🏆 Ranking do Mês

🎯 Metas Individuais

💰 Comissões

AgenteClienteProdutoValor ComissãoStatusDataAções

📱 Enviar SMS / WhatsApp

📊 Log de SMS

ClienteNúmeroMensagemStatusData

📅 Compromissos

💬
Selecione uma conversa

📧 Campanhas de E-mail

NomeAssuntoStatusEnviadosCriado emAções
`); } // ────────────────────────────────────────────── // EQUIPE // ────────────────────────────────────────────── async function carregarEquipe() { const agentes = await api('/agentes'); const fmt2 = (v) => v ? Number(v).toLocaleString('pt-BR', {style:'currency',currency:'BRL'}) : 'R$ 0,00'; document.getElementById('lista-agentes').innerHTML = agentes.length ? agentes.map(a => `
${a.nome.charAt(0).toUpperCase()}
${a.nome} ${!a.ativo ? '(inativo)' : ''}
📧 ${a.email} ${a.whatsapp ? ` · 📱 ${a.whatsapp}` : ''} · 📋 ${a.total_propostas || 0} propostas (${a.contratadas || 0} contratadas) · 💰 ${fmt2(a.comissao_pendente)} pendente ${a.ultimo_acesso ? ` · Último acesso: ${fmtData(a.ultimo_acesso)}` : ''}
${a.perfil}
${a.ativo && a.id !== (usuario.id || 0) ? `` : ''}
`).join('') : '

Nenhum agente cadastrado.

'; } let _agenteEditId = null; function abrirModalAgente(id = null) { _agenteEditId = id; document.getElementById('ag-id').value = ''; document.getElementById('ag-nome').value = ''; document.getElementById('ag-email').value = ''; document.getElementById('ag-senha').value = ''; document.getElementById('ag-whats').value = ''; document.getElementById('ag-perfil').value = 'agente'; document.getElementById('ag-ativo').value = '1'; document.getElementById('modal-agente-titulo').textContent = '👨‍💼 Novo Agente'; document.getElementById('ag-senha-hint').style.display = 'none'; document.getElementById('modal-agente').classList.add('open'); } async function editarAgente(id) { const a = await api(`/agentes/${id}`); _agenteEditId = id; document.getElementById('ag-id').value = id; document.getElementById('ag-nome').value = a.nome; document.getElementById('ag-email').value = a.email; document.getElementById('ag-senha').value = ''; document.getElementById('ag-whats').value = a.whatsapp || ''; document.getElementById('ag-perfil').value = a.perfil; document.getElementById('ag-ativo').value = a.ativo ? '1' : '0'; document.getElementById('modal-agente-titulo').textContent = `✏️ Editar — ${a.nome}`; document.getElementById('ag-senha-hint').style.display = 'inline'; document.getElementById('modal-agente').classList.add('open'); } async function salvarAgente() { const id = document.getElementById('ag-id').value; const body = { nome: document.getElementById('ag-nome').value, email: document.getElementById('ag-email').value, senha: document.getElementById('ag-senha').value || undefined, whatsapp: document.getElementById('ag-whats').value, perfil: document.getElementById('ag-perfil').value, ativo: document.getElementById('ag-ativo').value === '1', }; if (!id) delete body.ativo; // novo agente sempre começa ativo if (!body.senha) delete body.senha; const res = id ? await api(`/agentes/${id}`, { method: 'PUT', body: JSON.stringify(body) }) : await api('/agentes', { method: 'POST', body: JSON.stringify(body) }); if (res.erro) { alert(res.erro); return; } fecharModal('modal-agente'); carregarEquipe(); } async function desativarAgente(id, nome) { if (!confirm(`Desativar o agente "${nome}"? Ele perderá acesso ao sistema.`)) return; await api(`/agentes/${id}`, { method: 'DELETE' }); carregarEquipe(); } // ────────────────────────────────────────────── // DOCUMENTOS (dentro do modal de histórico) // ────────────────────────────────────────────── let _clienteAtualId = null; const docIcone = { rg_cnh:'🪪', cpf:'📋', comprovante_residencia:'🏠', comprovante_renda:'💼', extrato_beneficio:'📜', contrato:'📝', outros:'📎' }; const docLabel = { rg_cnh:'RG/CNH', cpf:'CPF', comprovante_residencia:'Comp. Residência', comprovante_renda:'Comp. Renda', extrato_beneficio:'Extrato Benefício', contrato:'Contrato', outros:'Outros' }; function fmtTamanho(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes/1024).toFixed(1)} KB`; return `${(bytes/(1024*1024)).toFixed(1)} MB`; } async function carregarDocumentosCliente(clienteId) { const docs = await api(`/documentos/cliente/${clienteId}`); const el = document.getElementById('docs-hist-content'); el.innerHTML = docs.length ? docs.map(d => `
${docIcone[d.tipo] || '📎'}
${d.nome_arquivo}
${docLabel[d.tipo] || d.tipo} · ${fmtTamanho(d.tamanho || 0)} · ${fmtData(d.criado_em)} ${d.enviado_por ? ` · por ${d.enviado_por}` : ''} ${d.proposta_tipo ? ` · Proposta: ${d.proposta_tipo}` : ''}
`).join('') : '

Nenhum documento enviado ainda.

'; } async function uploadDocumentos(files) { if (!_clienteAtualId || !files.length) return; const tipo = document.getElementById('doc-tipo-select').value; for (const file of files) { const fd = new FormData(); fd.append('arquivo', file); fd.append('cliente_id', _clienteAtualId); fd.append('tipo', tipo); const res = await fetch('/api/documentos/upload', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: fd, }).then(r => r.json()); if (res.erro) { alert(res.erro); return; } } await carregarDocumentosCliente(_clienteAtualId); document.getElementById('file-input').value = ''; } function handleDrop(e) { e.preventDefault(); document.getElementById('upload-drop-area').classList.remove('drag'); uploadDocumentos(e.dataTransfer.files); } async function excluirDoc(id, btn) { if (!confirm('Excluir este documento?')) return; btn.closest('.doc-item').style.opacity = '0.4'; await api(`/documentos/${id}`, { method: 'DELETE' }); await carregarDocumentosCliente(_clienteAtualId); } // ────────────────────────────────────────────── // Atualiza verCliente para guardar _clienteAtualId // e carregar docs na aba correspondente // ────────────────────────────────────────────── const _verClienteOriginal = verCliente; // Inicia se já tiver token if (token) { iniciarApp(); aplicarPermissoes(); iniciarNotificacoes(); atualizarBadgeChat(); } else { // AUTO-LOGIN: faz login automático ao abrir a página setTimeout(async function() { try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'admin@crm.com', senha: 'admin123' }) }); const data = await res.json(); if (data.token) { token = data.token; usuario = data.usuario; localStorage.setItem('crm_token', token); localStorage.setItem('crm_user', JSON.stringify(usuario)); iniciarApp(); } } catch(e) { console.log('Auto-login falhou, use o formulário'); } }, 300); } // ==================== BUSCA GLOBAL (Ctrl+K) ==================== let _buscaTimer = null; async function buscaGlobal(q) { clearTimeout(_buscaTimer); const el = document.getElementById('busca-resultados'); if (!q || q.length < 2) { el.style.display = 'none'; return; } _buscaTimer = setTimeout(async () => { const d = await api('/leads/busca?q=' + encodeURIComponent(q)); if (!d.clientes?.length && !d.propostas?.length) { el.style.display = 'none'; return; } el.style.display = 'block'; el.innerHTML = [ d.clientes?.length ? `
Clientes
` + d.clientes.map(c => `
👤 ${c.nome}
${c.whatsapp||c.cpf||''} · ${c.cidade||''} · ${c.etapa||c.status||''}
`).join('') : '', d.propostas?.length ? `
Propostas
` + d.propostas.map(p => `
📋 ${p.tipo} — ${p.banco||'—'}
${p.cliente_nome} · ${fmt(p.valor_solicitado)} · ${p.status}
`).join('') : '' ].join(''); }, 300); } function verClienteBusca(id) { document.getElementById('busca-resultados').style.display = 'none'; document.getElementById('busca-global').value = ''; navegar('clientes'); setTimeout(() => verHistorico(id), 300); } document.addEventListener('keydown', e => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); document.getElementById('busca-global').focus(); } if (e.key === 'Escape') { document.getElementById('busca-resultados').style.display = 'none'; document.getElementById('busca-global').blur(); } }); // ==================== MEU PAINEL ==================== async function carregarMeuPainel() { const d = await api('/dashboard/meu'); const tipoIcon = { ligacao:'📞', visita:'🏢', reuniao:'🤝', whatsapp:'💬', outro:'📌' }; document.getElementById('kpi-meu-painel').innerHTML = `
Propostas este mês
${d.propostas.total}
${d.propostas.contratadas} contratadas
Volume contratado
${fmt(d.propostas.volume)}
Comissão: ${fmt(d.propostas.comissao)}
Leads este mês
${d.leads_mes}
Tarefas vencidas
${d.tarefas_vencidas}
Ver tarefas
`; document.getElementById('meu-agenda-hoje').innerHTML = d.agenda_hoje?.length ? d.agenda_hoje.map(a => { const hora = new Date(a.data_hora).toLocaleTimeString('pt-BR', {hour:'2-digit',minute:'2-digit'}); return `
${tipoIcon[a.tipo]||'📌'}
${hora} — ${a.titulo}
${a.cliente_nome ? `
👤 ${a.cliente_nome}
` : ''}
`; }).join('') : '

Nenhum compromisso hoje 🎉

'; const meta = d.meta; document.getElementById('meu-meta').innerHTML = meta ? `
💰 Meta de volume ${d.pct_meta}%
${fmt(d.propostas.volume)} de ${fmt(meta.valor)} atingidos
` : '

Nenhuma meta cadastrada para este mês.
Cadastrar meta

'; } // ==================== PERMISSÕES POR PERFIL ==================== function aplicarPermissoes() { const perfil = usuario?.perfil; // Admin e gerente veem auditoria e configurações if (perfil === 'admin' || perfil === 'gerente') { document.getElementById('nav-auditoria').style.display = ''; document.getElementById('nav-blacklist').style.display = ''; document.getElementById('nav-financeiro').style.display = ''; document.getElementById('nav-configuracoes').style.display = ''; } // Agente não vê equipe completa nem relatórios gerenciais if (perfil === 'agente') { document.getElementById('nav-relatorios').style.display = 'none'; document.getElementById('nav-metas').style.display = 'none'; } } // ==================== CEP AUTOMÁTICO ==================== async function buscarCEP(cep, prefixo) { const c = cep.replace(/\D/g,''); if (c.length !== 8) return; try { const r = await fetch(`https://viacep.com.br/ws/${c}/json/`); const d = await r.json(); if (d.erro) return; const get = id => document.getElementById(prefixo + id); if (get('logradouro')) get('logradouro').value = d.logradouro || ''; if (get('bairro')) get('bairro').value = d.bairro || ''; if (get('cidade')) get('cidade').value = d.localidade || ''; if (get('estado')) get('estado').value = d.uf || ''; // foca no número após preencher setTimeout(() => { try { get('numero').focus(); } catch {} }, 100); } catch {} } // ==================== CONFIGURAÇÕES ==================== async function carregarConfiguracoes() { const cfg = await api('/configuracoes'); const campos = ['empresa_nome','empresa_cidade','empresa_cor','base_url', 'smtp_host','smtp_port','smtp_user', 'evolution_url','evolution_instance','evolution_key', 'twilio_sid','twilio_from']; campos.forEach(k => { const el = document.getElementById('cfg-' + k); if (el && cfg[k] !== undefined) el.value = cfg[k]; }); } async function salvarConfiguracoes() { const campos = ['empresa_nome','empresa_cidade','empresa_cor','base_url', 'smtp_host','smtp_port','smtp_user','smtp_pass', 'evolution_url','evolution_instance','evolution_key', 'twilio_sid','twilio_token','twilio_from']; const body = {}; campos.forEach(k => { const el = document.getElementById('cfg-' + k); if (el && el.value) body[k] = el.value; }); await api('/configuracoes', { method: 'PUT', body: JSON.stringify(body) }); mostrarToast('⚙️ Configurações salvas', 'Algumas mudanças só têm efeito após reiniciar o servidor.'); } // ==================== EXPORTAR DADOS ==================== function exportarDados(tipo) { const url = `/api/exportar/${tipo}`; const a = document.createElement('a'); a.href = url; a.setAttribute('download', `${tipo}_${new Date().toISOString().slice(0,10)}.csv`); // Passa token via cookie temporário (o backend usa auth header) // Abre nova aba com token na URL não é seguro — usa fetch + blob fetch(url, { headers: { Authorization: `Bearer ${token}` } }) .then(r => r.blob()) .then(blob => { const u = URL.createObjectURL(blob); a.href = u; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(u); }); } // ==================== AUDITORIA ==================== let _auditPage = 1; async function carregarAuditoria(page) { _auditPage = page || 1; const acao = document.getElementById('audit-acao')?.value || ''; const tabela = document.getElementById('audit-tabela')?.value || ''; let qs = [`page=${_auditPage}`]; if (acao) qs.push('acao=' + encodeURIComponent(acao)); if (tabela) qs.push('tabela=' + tabela); const d = await api('/auditoria?' + qs.join('&')); document.getElementById('tabela-auditoria').innerHTML = (d.logs || []).length === 0 ? 'Nenhum registro' : d.logs.map(l => ` ${l.criado_em?.slice(0,16)||''} ${l.usuario_nome||'Sistema'} ${l.usuario_perfil||'—'} ${l.acao||''} ${l.tabela||'—'} ${l.ip||'—'} `).join(''); document.getElementById('paginacao-auditoria').innerHTML = ` Página ${d.page} de ${d.pages} (${d.total} registros) `; } // ==================== SIMULADOR DE CRÉDITO ==================== function simular() { const valor = parseFloat(document.getElementById('sim-valor').value) || 10000; const prazo = parseInt(document.getElementById('sim-prazo').value) || 36; const taxa = parseFloat(document.getElementById('sim-taxa').value) / 100 || 0.018; const comissaoPct = parseFloat(document.getElementById('sim-comissao').value) || 0; // Tabela Price const parcela = valor * (taxa * Math.pow(1 + taxa, prazo)) / (Math.pow(1 + taxa, prazo) - 1); const totalPago = parcela * prazo; const totalJuros = totalPago - valor; const comissaoVal = valor * (comissaoPct / 100); const cet = ((Math.pow(totalPago / valor, 12 / prazo) - 1) * 100); document.getElementById('sim-resultado').innerHTML = `
Parcela Mensal
${fmt(parcela)}
Total a Pagar
${fmt(totalPago)}
Juros: ${fmt(totalJuros)}
CET estimado
${cet.toFixed(2)}% a.a.
Comissão (${comissaoPct}%)
${fmt(comissaoVal)}
`; // Tabela de amortização let saldo = valor; let tbody = ''; for (let i = 1; i <= prazo; i++) { const juros = saldo * taxa; const amort = parcela - juros; saldo = Math.max(0, saldo - amort); tbody += `${i}${fmt(parcela)}${fmt(juros)}${fmt(amort)}${fmt(saldo)}`; } document.getElementById('sim-tbody').innerHTML = tbody; } function criarPropostaSimulador() { const valor = document.getElementById('sim-valor').value; const prazo = document.getElementById('sim-prazo').value; const taxa = document.getElementById('sim-taxa').value; const tipo = document.getElementById('sim-produto').value; const banco = document.getElementById('sim-banco').value; const comissao = document.getElementById('sim-comissao').value; const taxa_n = parseFloat(taxa) / 100; const valor_n = parseFloat(valor); const prazo_n = parseInt(prazo); const parcela = valor_n * (taxa_n * Math.pow(1+taxa_n,prazo_n)) / (Math.pow(1+taxa_n,prazo_n)-1); // Pré-preenche modal de proposta navegar('propostas'); setTimeout(() => { document.getElementById('p-tipo').value = tipo; document.getElementById('p-banco').value = banco; document.getElementById('p-valor').value = valor; document.getElementById('p-prazo').value = prazo; document.getElementById('p-taxa').value = taxa; document.getElementById('p-parcela').value = parcela.toFixed(2); document.getElementById('p-comissao-pct').value = comissao; document.getElementById('modal-proposta').classList.add('open'); }, 100); } // ==================== IMPORTAR CSV ==================== let _csvFile = null; async function previewCSV(input) { const file = input.files[0]; if (!file) return; _csvFile = file; const fd = new FormData(); fd.append('arquivo', file); const r = await fetch('/api/importar/preview', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: fd }); const d = await r.json(); if (!r.ok) { alert(d.erro || 'Erro ao ler CSV'); return; } document.getElementById('csv-preview-info').innerHTML = `${d.total} registros encontrados. Preview dos primeiros 5:`; const cols = ['nome','cpf','telefone','whatsapp','email','cidade','estado','origem']; const thead = `${cols.map(c=>`${c}`).join('')}`; const tbody = d.preview.map(r => `${cols.map(c=>`${r[c]||''}`).join('')}`).join(''); document.querySelector('#csv-preview-tabela thead').innerHTML = thead; document.querySelector('#csv-preview-tabela tbody').innerHTML = tbody; document.getElementById('csv-preview-box').style.display = 'block'; document.getElementById('csv-resultado').style.display = 'none'; } async function executarImportacao() { if (!_csvFile) return; const btn = document.getElementById('btn-importar'); btn.disabled = true; btn.textContent = '⏳ Importando...'; const fd = new FormData(); fd.append('arquivo', _csvFile); const r = await fetch('/api/importar/executar', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: fd }); const d = await r.json(); btn.disabled = false; btn.textContent = '✅ Importar Leads'; document.getElementById('csv-preview-box').style.display = 'none'; const res = document.getElementById('csv-resultado'); res.style.display = 'block'; res.innerHTML = `
${d.erros>0?'⚠️':'✅'} Importação concluída
✅ Importados: ${d.importados}
⚠️ Duplicados ignorados: ${d.duplicados}
❌ Erros: ${d.erros}
📊 Total no arquivo: ${d.total}
${d.detalhes?.length ? `
${d.detalhes.join('
')}
` : ''}
`; _csvFile = null; } function limparImportacao() { _csvFile = null; document.getElementById('csv-file').value = ''; document.getElementById('csv-preview-box').style.display = 'none'; document.getElementById('csv-resultado').style.display = 'none'; } // ==================== METAS ==================== async function carregarMetas() { const mes = document.getElementById('metas-mes').value || new Date().toISOString().slice(0,7); const [metas, ranking] = await Promise.all([api('/metas?mes='+mes), api('/metas/ranking?mes='+mes)]); // Ranking document.getElementById('ranking-agentes').innerHTML = ranking.map((a,i) => `
${['🥇','🥈','🥉'][i]||'🏅'}
${a.nome}
${a.leads_novos} leads · ${a.contratos} contratos
${fmt(a.volume)}
Comissão: ${fmt(a.comissao)}
`).join('') || '

Nenhum dado neste mês

'; // Metas individuais const tipoLabel = { volume:'💰 Volume', contratos:'📋 Contratos', leads:'👥 Leads' }; const tipoFmt = { volume: v => fmt(v), contratos: v => v+' contratos', leads: v => v+' leads' }; document.getElementById('lista-metas').innerHTML = metas.length === 0 ? '

Nenhuma meta cadastrada para este mês

' : metas.map(m => `
${m.agente_nome} ${tipoLabel[m.tipo]||m.tipo}
${tipoFmt[m.tipo]?tipoFmt[m.tipo](m.realizado):m.realizado} / ${tipoFmt[m.tipo]?tipoFmt[m.tipo](m.valor):m.valor} ${m.pct}%
${m.descricao ? `
${m.descricao}
` : ''}
`).join(''); } async function abrirModalMeta() { const agentes = await api('/agentes'); document.getElementById('meta-agente').innerHTML = agentes.map(a => ``).join(''); document.getElementById('meta-mes').value = new Date().toISOString().slice(0,7); document.getElementById('meta-tipo').value = 'volume'; document.getElementById('meta-valor').value = ''; document.getElementById('meta-desc').value = ''; document.getElementById('modal-meta').classList.add('open'); } async function salvarMeta() { const body = { agente_id: document.getElementById('meta-agente').value, mes: document.getElementById('meta-mes').value, tipo: document.getElementById('meta-tipo').value, valor: parseFloat(document.getElementById('meta-valor').value), descricao: document.getElementById('meta-desc').value }; if (!body.valor || body.valor <= 0) { alert('Informe um valor válido'); return; } await api('/metas', { method: 'POST', body: JSON.stringify(body) }); fecharModal('modal-meta'); carregarMetas(); } async function removerMeta(id) { if (!confirm('Remover esta meta?')) return; await api('/metas/' + id, { method: 'DELETE' }); carregarMetas(); } // ==================== COMISSÕES ==================== async function carregarComissoes() { const status = document.getElementById('comissoes-status')?.value || ''; const agenteId = document.getElementById('comissoes-agente')?.value || ''; // Popula select de agentes na primeira vez const sel = document.getElementById('comissoes-agente'); if (sel && sel.children.length <= 1) { const agentes = await api('/agentes'); agentes.forEach(a => { const o = document.createElement('option'); o.value = a.id; o.textContent = a.nome; sel.appendChild(o); }); } let qs = []; if (status) qs.push('status=' + status); if (agenteId) qs.push('agente_id=' + agenteId); const comissoes = await api('/propostas/comissoes?' + qs.join('&')); const total = comissoes.reduce((s, c) => s + (c.comissao_valor || 0), 0); const pagas = comissoes.filter(c => c.comissao_status === 'pago').reduce((s, c) => s + (c.comissao_valor || 0), 0); const pend = total - pagas; document.getElementById('kpi-comissoes').innerHTML = `
Total Comissões
${fmt(total)}
Pagas
${fmt(pagas)}
Pendentes
${fmt(pend)}
Contratos
${comissoes.length}
`; document.getElementById('tabela-comissoes').innerHTML = comissoes.length === 0 ? 'Nenhuma comissão encontrada' : comissoes.map(c => ` ${c.agente_nome||'—'} ${c.cliente_nome||'—'} ${c.tipo||'—'} ${fmt(c.comissao_valor)} ${c.comissao_status||'pendente'} ${c.criado_em?.slice(0,10)||''} ${c.comissao_status!=='pago' ? `` : ''} `).join(''); } async function pagarComissao(propostaId) { if (!confirm('Marcar comissão como paga?')) return; await api('/propostas/' + propostaId + '/pagar-comissao', { method: 'PUT' }); carregarComissoes(); } // ==================== SMS ==================== async function enviarSMSAvulso() { const numero = document.getElementById('sms-numero').value.trim(); const mensagem = document.getElementById('sms-msg').value.trim(); if (!numero || !mensagem) { alert('Preencha número e mensagem'); return; } const r = await api('/sms/enviar', { method: 'POST', body: JSON.stringify({ numero, mensagem }) }); if (r.mensagem) { mostrarToast('📱 SMS enviado', r.mensagem); document.getElementById('sms-msg').value = ''; carregarSmsLogs(); } else alert('Erro: ' + (r.erro || 'Falha ao enviar')); } async function carregarSmsLogs() { const logs = await api('/sms/logs'); document.getElementById('tabela-sms-logs').innerHTML = logs.length === 0 ? 'Nenhum SMS enviado ainda' : logs.map(l => ` ${l.cliente_nome || '—'} ${l.numero} ${l.mensagem} ${l.status} ${l.criado_em?.slice(0,16)||''} `).join(''); } // ==================== ASSINATURA DIGITAL ==================== async function solicitarAssinatura(contratoId, numeroCliente) { const r = await api('/assinatura/solicitar/' + contratoId, { method: 'POST' }); if (r.link) { // Copia link e oferece enviar por WhatsApp navigator.clipboard?.writeText(r.link).catch(() => {}); const enviar = confirm(`Link de assinatura gerado!\n\n${r.link}\n\n(Copiado para área de transferência)\n\nEnviar via WhatsApp agora?`); if (enviar && numeroCliente) { const msg = encodeURIComponent(`Olá! Segue o link para assinar seu contrato digitalmente:\n\n${r.link}\n\nO link expira em 7 dias.`); window.open(`https://wa.me/${numeroCliente.replace(/\D/g,'')}?text=${msg}`, '_blank'); } } else { alert('Erro: ' + (r.erro || 'Falha ao gerar link')); } } // ==================== NOTIFICAÇÕES ==================== let notifPainelAberto = false; function iniciarNotificacoes() { carregarNotifs(); const es = new EventSource('/api/notificacoes/stream?token=' + token); es.onmessage = (e) => { const n = JSON.parse(e.data); carregarNotifs(); mostrarToast(n.titulo, n.mensagem); }; setInterval(carregarNotifs, 60000); } async function carregarNotifs() { const notifs = await api('/notificacoes'); const naoLidas = notifs.filter(n => !n.lida).length; const badge = document.getElementById('badge-notif'); if (naoLidas > 0) { badge.style.display = 'inline'; badge.textContent = naoLidas; } else { badge.style.display = 'none'; } document.getElementById('lista-notifs').innerHTML = notifs.length === 0 ? '
Nenhuma notificação
' : notifs.map(n => `
${n.titulo}
${n.mensagem||''}
${n.criado_em}
`).join(''); } function toggleNotifPanel() { const p = document.getElementById('painel-notif'); notifPainelAberto = !notifPainelAberto; p.style.display = notifPainelAberto ? 'flex' : 'none'; p.style.flexDirection = 'column'; } async function clicarNotif(id, link) { await api(`/notificacoes/${id}/ler`, { method: 'PUT' }); document.getElementById('painel-notif').style.display = 'none'; notifPainelAberto = false; if (link) navegar(link); carregarNotifs(); } async function lerTodasNotifs() { await api('/notificacoes/ler-todas', { method: 'PUT' }); carregarNotifs(); } function mostrarToast(titulo, msg) { const t = document.createElement('div'); t.style.cssText = 'position:fixed;bottom:20px;right:20px;background:#1a1a2e;color:#fff;padding:14px 18px;border-radius:10px;max-width:300px;z-index:9999;font-size:13px;box-shadow:0 4px 20px rgba(0,0,0,0.3);animation:fadeIn 0.3s'; t.innerHTML = `
${titulo}
${msg||''}
`; document.body.appendChild(t); setTimeout(() => t.remove(), 4000); } document.addEventListener('click', (e) => { const painel = document.getElementById('painel-notif'); if (notifPainelAberto && !painel.contains(e.target) && !e.target.closest('[onclick*="toggleNotifPanel"]')) { painel.style.display = 'none'; notifPainelAberto = false; } }); // ==================== AGENDA ==================== async function carregarAgenda() { const ini = document.getElementById('agenda-data-ini')?.value || ''; const fim = document.getElementById('agenda-data-fim')?.value || ''; const status = document.getElementById('agenda-status-filtro')?.value || ''; let qs = []; if (ini) qs.push('data_inicio=' + ini); if (fim) qs.push('data_fim=' + fim); if (status) qs.push('status=' + status); const compromissos = await api('/agenda?' + qs.join('&')); const tipoIcon = { ligacao:'📞', visita:'🏢', reuniao:'🤝', whatsapp:'💬', outro:'📌' }; const statusCls = { agendado:'', concluido:'concluido', cancelado:'cancelado' }; document.getElementById('lista-agenda').innerHTML = compromissos.length === 0 ? '
Nenhum compromisso encontrado
' : compromissos.map(c => { const dt = new Date(c.data_hora); const hora = dt.toLocaleTimeString('pt-BR', {hour:'2-digit', minute:'2-digit'}); const data = dt.toLocaleDateString('pt-BR', {day:'2-digit', month:'short'}); return `
${hora}${data}
${tipoIcon[c.tipo]||'📌'}
${c.titulo}
${c.cliente_nome ? `
👤 ${c.cliente_nome}
` : ''} ${c.descricao ? `
${c.descricao}
` : ''} ${c.local ? `
📍 ${c.local}
` : ''}
👤 ${c.agente_nome||'—'} · ${c.duracao_min} min
${c.status==='agendado' ? `` : ''}
`; }).join(''); } function abrirModalAgenda(clienteId) { document.getElementById('agenda-id').value = ''; document.getElementById('ag-titulo').value = ''; document.getElementById('ag-desc').value = ''; document.getElementById('ag-local').value = ''; document.getElementById('ag-tipo').value = 'ligacao'; document.getElementById('ag-duracao').value = '30'; const now = new Date(); now.setMinutes(0); document.getElementById('ag-data-hora').value = now.toISOString().slice(0,16); document.getElementById('modal-agenda').classList.add('open'); } async function salvarAgenda() { const body = { titulo: document.getElementById('ag-titulo').value, descricao: document.getElementById('ag-desc').value, tipo: document.getElementById('ag-tipo').value, data_hora: document.getElementById('ag-data-hora').value, duracao_min: document.getElementById('ag-duracao').value, local: document.getElementById('ag-local').value }; if (!body.titulo || !body.data_hora) { alert('Título e data são obrigatórios'); return; } await api('/agenda', { method: 'POST', body: JSON.stringify(body) }); fecharModal('modal-agenda'); carregarAgenda(); } async function concluirAgenda(id) { await api('/agenda/' + id, { method: 'PUT', body: JSON.stringify({ status: 'concluido' }) }); carregarAgenda(); } async function deletarAgenda(id) { if (!confirm('Remover compromisso?')) return; await api('/agenda/' + id, { method: 'DELETE' }); carregarAgenda(); } // ==================== CHAT CENTRALIZADO ==================== let chatClienteAtivo = null; let chatCanalFiltro = ''; async function carregarConversas() { const busca = document.getElementById('chat-busca')?.value || ''; let qs = []; if (busca) qs.push('busca=' + encodeURIComponent(busca)); if (chatCanalFiltro) qs.push('canal=' + chatCanalFiltro); const conversas = await api('/chat/conversas?' + qs.join('&')); const canalIcon = { whatsapp:'💬', instagram:'📸', facebook:'👍', email:'📧', telefone:'📞', presencial:'🏢' }; document.getElementById('lista-conversas').innerHTML = conversas.length === 0 ? '
Nenhuma conversa
' : conversas.map(c => `
${canalIcon[c.canal]||'💬'} ${c.cliente_nome}
${c.nao_lidos>0 ? `${c.nao_lidos}` : ''}
${c.direcao==='saida'?'Você: ':''}${c.ultima_mensagem||''}
${c.etapa_funil||'—'}${c.ultima_atividade?.slice(0,16)||''}
`).join(''); } async function abrirConversa(clienteId, nome, whatsapp, canal) { chatClienteAtivo = clienteId; document.getElementById('chat-cliente-nome').textContent = nome; document.getElementById('chat-cliente-info').textContent = `${canal.toUpperCase()} · ${whatsapp}`; carregarConversas(); const msgs = await api('/chat/' + clienteId + '/mensagens'); document.getElementById('chat-mensagens').innerHTML = msgs.map(m => `
${m.conteudo}
${m.agente_nome ? m.agente_nome + ' · ' : ''}${m.criado_em?.slice(0,16)||''}
`).join(''); const el = document.getElementById('chat-mensagens'); el.scrollTop = el.scrollHeight; atualizarBadgeChat(); } async function enviarMensagemChat() { if (!chatClienteAtivo) { alert('Selecione uma conversa'); return; } const input = document.getElementById('chat-input'); const msg = input.value.trim(); if (!msg) return; input.value = ''; await api('/chat/' + chatClienteAtivo + '/enviar', { method: 'POST', body: JSON.stringify({ mensagem: msg, canal: 'whatsapp' }) }); abrirConversa(chatClienteAtivo, document.getElementById('chat-cliente-nome').textContent, '', 'whatsapp'); } async function atualizarBadgeChat() { try { const r = await api('/chat/nao-lidos/count'); const b = document.getElementById('badge-chat'); if (r.total > 0) { b.style.display='inline'; b.textContent=r.total; } else { b.style.display='none'; } } catch {} } function filtrarCanal(canal, btn) { chatCanalFiltro = canal; document.querySelectorAll('.chat-filtro').forEach(b => b.classList.remove('active')); btn.classList.add('active'); carregarConversas(); } setInterval(atualizarBadgeChat, 30000); // ==================== E-MAIL MARKETING ==================== async function carregarCampanhas() { const camps = await api('/email/campanhas'); const statusBadge = { rascunho:'badge-pendente', enviada:'badge-ativo', erro:'badge-recusada' }; document.getElementById('tabela-campanhas').innerHTML = camps.length === 0 ? 'Nenhuma campanha' : camps.map(c => ` ${c.nome} ${c.assunto} ${c.status} ${c.enviados||0} (${c.erros||0} erros) ${c.criado_em?.slice(0,10)||''} ${c.status==='rascunho' ? `` : ''} `).join(''); } function abrirModalCampanha() { ['camp-nome','camp-assunto','camp-corpo'].forEach(id => document.getElementById(id).value = ''); document.getElementById('camp-status').value = ''; document.getElementById('camp-origem').value = ''; document.getElementById('modal-campanha').classList.add('open'); } async function salvarCampanha() { const body = { nome: document.getElementById('camp-nome').value, assunto: document.getElementById('camp-assunto').value, corpo_html: document.getElementById('camp-corpo').value, filtro_status: document.getElementById('camp-status').value || null, filtro_origem: document.getElementById('camp-origem').value || null }; if (!body.nome || !body.assunto || !body.corpo_html) { alert('Preencha nome, assunto e corpo'); return; } await api('/email/campanhas', { method: 'POST', body: JSON.stringify(body) }); fecharModal('modal-campanha'); carregarCampanhas(); } async function dispararCampanha(id, nome) { if (!confirm(`Disparar campanha "${nome}" agora?`)) return; const r = await api('/email/campanhas/' + id + '/disparar', { method: 'POST' }); mostrarToast('📧 Campanha disparada', r.mensagem || 'Enviando em background...'); setTimeout(carregarCampanhas, 3000); } /* ===================== FINANCEIRO ===================== */ let _finPagina = 1; let _finMes = ''; function finMesAtual() { return new Date().toISOString().slice(0, 7); } function preencherMeses() { const sel = document.getElementById('fin-mes-filtro'); if (!sel || sel.options.length > 1) return; const hoje = new Date(); for (let i = 11; i >= -2; i--) { const d = new Date(hoje.getFullYear(), hoje.getMonth() - i, 1); const val = d.toISOString().slice(0, 7); const label = d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' }); const opt = document.createElement('option'); opt.value = val; opt.textContent = label; if (val === finMesAtual()) opt.selected = true; sel.appendChild(opt); } } async function carregarFinanceiro() { preencherMeses(); _finMes = document.getElementById('fin-mes-filtro')?.value || finMesAtual(); const d = await api('/financeiro/dashboard'); if (!d) return; // KPIs document.getElementById('fin-kpis').innerHTML = `
📥
Receitas Recebidas
${fmt(d.receitas?.recebido||0)}
${d.receitas?.qt_recebido||0} lançamentos
` + `
📤
A Receber
${fmt(d.receitas?.a_receber||0)}
${d.receitas?.qt_pendente||0} pendentes
` + `
💸
Despesas Pagas
${fmt(d.despesas?.pago||0)}
${d.despesas?.qt_pago||0} lançamentos
` + `
⚠️
A Pagar
${fmt(d.despesas?.a_pagar||0)}
${d.despesas?.qt_pendente||0} pendentes
` + `
${d.resultado_mes >= 0 ? '📈' : '📉'}
Resultado do Mês
${fmt(d.resultado_mes||0)}
${d.resultado_mes >= 0 ? 'Superávit' : 'Déficit'}
`; // Vencendo const vel = document.getElementById('fin-vencendo'); if (vel) { const itens = d.vencendo || []; vel.innerHTML = itens.length === 0 ? '

✅ Nenhum vencimento nos próximos 5 dias

' : itens.map(l => { const venc = new Date(l.data_vencimento + 'T12:00:00'); const hoje = new Date(); hoje.setHours(0,0,0,0); const diff = Math.round((venc - hoje) / 86400000); const cor = diff < 0 ? '#ef4444' : diff === 0 ? '#f59e0b' : '#64748b'; return `
${l.descricao}
${l.icone||''} ${l.categoria_nome||'Sem categoria'}
${l.tipo==='receita'?'+':'-'}${fmt(l.valor)}
${diff < 0 ? 'Vencido há '+Math.abs(diff)+'d' : diff === 0 ? 'Vence hoje!' : 'Em '+diff+' dia(s)'}
`; }).join(''); } // Por categoria const cel = document.getElementById('fin-por-categoria'); if (cel) { const cats = d.porCategoria || []; const maxVal = Math.max(1, ...cats.map(c => c.total)); cel.innerHTML = cats.length === 0 ? '

Sem dados no mês atual

' : cats.slice(0, 8).map(c => `
${c.icone} ${c.nome} ${fmt(c.total)}
`).join(''); } // Gráfico evolução 12 meses destruirChart('fin-evolucao'); const f12 = d.fluxo12 || []; if (f12.length > 0) { _charts['fin-evolucao'] = new Chart(document.getElementById('chart-fin-evolucao'), { type: 'bar', data: { labels: f12.map(f => f.mes), datasets: [ { label: 'Receitas', data: f12.map(f => f.receitas), backgroundColor: 'rgba(16,185,129,0.7)', borderRadius: 4 }, { label: 'Despesas', data: f12.map(f => f.despesas), backgroundColor: 'rgba(239,68,68,0.7)', borderRadius: 4 }, ] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 12 } } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 10 } } }, y: { beginAtZero: true, ticks: { font: { size: 10 }, callback: v => 'R$'+Number(v/1000).toFixed(0)+'k' } } } } }); } // Contas renderContasFin(d.contas || []); // Carrega também lançamentos e DRE await carregarLancamentos(); await carregarDRE(); await carregarFluxoCaixa(); await carregarComissoesRelatorio(); await carregarFaturamento(); } function finAba(btn, painel) { document.querySelectorAll('.fin-tab').forEach(b => b.classList.remove('active')); document.querySelectorAll('.fin-panel').forEach(p => p.classList.remove('active')); btn.classList.add('active'); document.getElementById(painel).classList.add('active'); } async function carregarLancamentos() { const tipo = document.getElementById('fin-filtro-tipo')?.value || ''; const status = document.getElementById('fin-filtro-status')?.value || ''; const mes = document.getElementById('fin-mes-filtro')?.value || finMesAtual(); const d = await api(`/financeiro/lancamentos?tipo=${tipo}&status=${status}&mes=${mes}&page=${_finPagina}&limit=20`); if (!d) return; const lista = d.lancamentos || []; const totRec = lista.filter(l => l.tipo === 'receita').reduce((a, l) => a + l.valor, 0); const totDesp = lista.filter(l => l.tipo === 'despesa').reduce((a, l) => a + l.valor, 0); const tot = document.getElementById('fin-total-filtro'); if (tot) tot.innerHTML = `+${fmt(totRec)} / -${fmt(totDesp)} · ${d.total} lançamentos`; const el = document.getElementById('fin-lista-lancamentos'); if (!el) return; el.innerHTML = lista.length === 0 ? '
📋

Nenhum lançamento encontrado

' : lista.map(l => { const venc = new Date(l.data_vencimento + 'T12:00:00'); const hoje = new Date(); hoje.setHours(0,0,0,0); const vencido = l.status === 'pendente' && venc < hoje; const statusClass = vencido ? 'fin-status-vencido' : `fin-status-${l.status}`; const statusLabel = vencido ? 'Vencido' : l.status; return `
${l.descricao}
${l.icone||''} ${l.categoria_nome||'Sem categoria'} ${l.parceiro_nome ? ` · 🤝 ${l.parceiro_nome}` : ''} ${l.cliente_nome ? ` · 👤 ${l.cliente_nome}` : ''}
${l.tipo==='receita'?'+':'-'}${fmt(l.valor)}
${venc.toLocaleDateString('pt-BR')}
${statusLabel}
${l.status !== 'pago' && l.status !== 'cancelado' ? `` : ''}
`; }).join(''); const pag = document.getElementById('fin-paginacao'); if (pag) pag.innerHTML = d.pages > 1 ? ` Página ${d.page} de ${d.pages} ` : ''; } async function carregarDRE() { const mes = document.getElementById('fin-mes-filtro')?.value || finMesAtual(); const d = await api('/financeiro/dre?mes=' + mes); if (!d) return; const t = d.totais || {}; const dEl = document.getElementById('fin-dre-content'); if (dEl) { const linhaRec = (d.receitas || []).map(r => `
${r.icone} ${r.categoria}${fmt(r.total)}
`).join(''); const linhaDesp = (d.despesas || []).map(r => `
${r.icone} ${r.categoria}${fmt(r.total)}
`).join(''); const resClass = t.resultado >= 0 ? 'resultado-pos' : 'resultado-neg'; dEl.innerHTML = `
RECEITAS
${linhaRec || '
Sem receitas no período-
'}
Total Receitas${fmt(t.receita_bruta||0)}
DESPESAS
${linhaDesp || '
Sem despesas no período-
'}
Total Despesas${fmt(t.total_despesas||0)}
${t.resultado >= 0 ? '📈 LUCRO LÍQUIDO' : '📉 PREJUÍZO'}
${fmt(Math.abs(t.resultado||0))}
Margem: ${t.margem_liquida||0}%
`; } // Por produto const pEl = document.getElementById('fin-dre-produto'); if (pEl) pEl.innerHTML = (d.comissoesProduto || []).map(p => `
${p.produto} (${p.quantidade}x) ${fmt(p.total)}
`).join('') || '

Sem dados

'; // Por parceiro const parEl = document.getElementById('fin-dre-parceiro'); if (parEl) parEl.innerHTML = (d.comissoesParceiros || []).slice(0,6).map(p => `
${p.parceiro} -${fmt(p.total)}
`).join('') || '

Sem comissões pagas

'; } async function carregarFluxoCaixa() { const fluxo = await api('/financeiro/fluxo-caixa?meses=6'); if (!fluxo || !fluxo.length) return; destruirChart('fin-fluxo'); const labels = fluxo.map(f => f.mes); const rec = fluxo.map(f => f.receitas_pagas); const desp = fluxo.map(f => f.despesas_pagas); const saldo = fluxo.map(f => f.saldo_acumulado); const ctx = document.getElementById('chart-fin-fluxo'); if (ctx) _charts['fin-fluxo'] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [ { label: 'Receitas', data: rec, backgroundColor: 'rgba(16,185,129,0.7)', borderRadius: 4, order: 2 }, { label: 'Despesas', data: desp, backgroundColor: 'rgba(239,68,68,0.7)', borderRadius: 4, order: 2 }, { label: 'Saldo Acumulado', data: saldo, type: 'line', borderColor: '#4f46e5', backgroundColor: 'rgba(79,70,229,0.08)', tension: 0.4, fill: true, pointRadius: 4, order: 1 } ] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 12 } } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 10 } } }, y: { beginAtZero: false, ticks: { font: { size: 10 }, callback: v => 'R$'+Number(v/1000).toFixed(1)+'k' } } } } }); const tbody = document.getElementById('fin-tabela-fluxo'); if (tbody) tbody.innerHTML = fluxo.map(f => ` ${f.mes} ${fmt(f.receitas_pagas)} ${fmt(f.receitas_previstas)} ${fmt(f.despesas_pagas)} ${fmt(f.despesas_previstas)} ${f.resultado>=0?'+':''}${fmt(f.resultado)} ${fmt(f.saldo_acumulado)} `).join(''); } async function carregarComissoesRelatorio() { const mes = document.getElementById('fin-mes-filtro')?.value || finMesAtual(); const d = await api('/financeiro/comissoes-relatorio?mes=' + mes); if (!d) return; const pEl = document.getElementById('fin-com-produto'); if (pEl) pEl.innerHTML = (d.porProduto || []).map(p => ` ${p.produto} ${p.contratos} ${fmt(p.volume)} ${fmt(p.comissao_total)} ${fmt(p.ticket_medio)} `).join('') || 'Sem contratos no mês'; const parEl = document.getElementById('fin-com-parceiro'); if (parEl) parEl.innerHTML = (d.porParceiro || []).map(p => ` ${p.parceiro}
${p.codigo} ${p.indicacoes} ${fmt(p.volume_gerado)} ${fmt(p.comissao_total)} ${fmt(p.comissao_paga)} `).join('') || 'Sem parceiros com atividade'; const aEl = document.getElementById('fin-com-agente'); if (aEl) aEl.innerHTML = (d.porAgente || []).map(a => ` ${a.agente} ${a.contratos} ${fmt(a.volume)} ${fmt(a.comissao)} `).join('') || 'Sem dados'; } async function carregarFaturamento() { const [fat, contas] = await Promise.all([api('/financeiro/faturamento'), api('/financeiro/contas')]); renderContasFin(contas || []); const tbody = document.getElementById('fin-tabela-faturamento'); if (tbody) tbody.innerHTML = (fat || []).map(f => ` ${f.mes} ${fmt(f.receita_bruta)} ${fmt(f.despesas_fixas)} ${fmt(f.comissoes_pagas)} ${fmt(f.resultado)} ${f.fechado ? 'Fechado' : 'Aberto'} `).join('') || 'Nenhum fechamento realizado'; } function renderContasFin(contas) { const el = document.getElementById('fin-contas-grid'); if (!el) return; const icones = { banco: '🏦', caixa: '💵', cartao: '💳', investimento: '📈' }; el.innerHTML = contas.map(c => `
${icones[c.tipo]||'🏦'}
${c.nome}
${c.banco||c.tipo}${c.agencia?' · Ag:'+c.agencia:''}${c.conta?' · Cc:'+c.conta:''}
${fmt(c.saldo_atual)}
Saldo inicial: ${fmt(c.saldo_inicial)}
`).join('') || '

Nenhuma conta cadastrada

'; } // Modal lançamento async function abrirModalLancamento(tipo) { document.getElementById('lanc-id').value = ''; document.getElementById('lanc-tipo-hidden').value = tipo; document.getElementById('modal-lanc-titulo').textContent = tipo === 'receita' ? '📥 Nova Receita' : '📤 Nova Despesa'; document.getElementById('btn-salvar-lanc').style.background = tipo === 'receita' ? '#10b981' : '#ef4444'; document.getElementById('lanc-descricao').value = ''; document.getElementById('lanc-valor').value = ''; document.getElementById('lanc-vencimento').value = new Date().toISOString().slice(0,10); document.getElementById('lanc-status').value = 'pendente'; document.getElementById('lanc-obs').value = ''; document.getElementById('fin-data-pag-wrap').style.display = 'none'; // Carrega categorias do tipo correto const cats = await api('/financeiro/categorias'); const sel = document.getElementById('lanc-categoria'); sel.innerHTML = '' + (cats||[]).filter(c => c.tipo === tipo).map(c => ``).join(''); // Carrega contas const contas = await api('/financeiro/contas'); const selC = document.getElementById('lanc-conta'); selC.innerHTML = '' + (contas||[]).map(c => ``).join(''); // Carrega parceiros const parceiros = await api('/parceiros'); const selP = document.getElementById('lanc-parceiro'); selP.innerHTML = '' + (parceiros||[]).filter(p=>p.status==='ativo').map(p => ``).join(''); document.getElementById('lanc-status').addEventListener('change', function() { document.getElementById('fin-data-pag-wrap').style.display = this.value === 'pago' ? '' : 'none'; if (this.value === 'pago') document.getElementById('lanc-data-pag').value = new Date().toISOString().slice(0,10); }); document.getElementById('modal-lancamento').classList.add('open'); } function fecharModalLancamento() { document.getElementById('modal-lancamento').classList.remove('open'); } async function salvarLancamento() { const id = document.getElementById('lanc-id').value; const tipo = document.getElementById('lanc-tipo-hidden').value; const status = document.getElementById('lanc-status').value; const body = { tipo, descricao: document.getElementById('lanc-descricao').value, valor: parseFloat(document.getElementById('lanc-valor').value), data_vencimento: document.getElementById('lanc-vencimento').value, data_pagamento: status === 'pago' ? document.getElementById('lanc-data-pag').value : null, status, categoria_id: document.getElementById('lanc-categoria').value || null, conta_id: document.getElementById('lanc-conta').value || null, parceiro_id: document.getElementById('lanc-parceiro').value || null, total_parcelas: document.getElementById('lanc-parcelas').value, observacao: document.getElementById('lanc-obs').value }; if (!body.descricao || !body.valor || !body.data_vencimento) { mostrarToast('⚠️ Preencha descrição, valor e data', ''); return; } if (id) { await api('/financeiro/lancamentos/' + id, { method: 'PUT', body: JSON.stringify(body) }); mostrarToast('✅ Lançamento atualizado!', ''); } else { const r = await api('/financeiro/lancamentos', { method: 'POST', body: JSON.stringify(body) }); mostrarToast('✅ ' + (r.mensagem || 'Lançamento criado!'), ''); } fecharModalLancamento(); carregarFinanceiro(); } async function editarLancamento(id) { const d = await api('/financeiro/lancamentos?page=1&limit=100'); const l = (d.lancamentos||[]).find(x => x.id === id); if (!l) return; await abrirModalLancamento(l.tipo); document.getElementById('lanc-id').value = l.id; document.getElementById('lanc-descricao').value = l.descricao; document.getElementById('lanc-valor').value = l.valor; document.getElementById('lanc-vencimento').value = l.data_vencimento; document.getElementById('lanc-status').value = l.status; document.getElementById('lanc-categoria').value = l.categoria_id || ''; document.getElementById('lanc-conta').value = l.conta_id || ''; document.getElementById('lanc-obs').value = l.observacao || ''; if (l.status === 'pago') { document.getElementById('fin-data-pag-wrap').style.display = ''; document.getElementById('lanc-data-pag').value = l.data_pagamento || ''; } } async function baixarLancamento(id) { const data_pagamento = prompt('Data de pagamento (AAAA-MM-DD):', new Date().toISOString().slice(0,10)); if (!data_pagamento) return; await api('/financeiro/lancamentos/' + id + '/baixar', { method: 'POST', body: JSON.stringify({ data_pagamento }) }); mostrarToast('✅ Lançamento baixado!', 'Saldo atualizado'); carregarFinanceiro(); } async function excluirLancamento(id) { if (!confirm('Excluir este lançamento?')) return; await api('/financeiro/lancamentos/' + id, { method: 'DELETE' }); mostrarToast('Lançamento excluído', ''); carregarFinanceiro(); } async function fecharMes() { const mes = document.getElementById('fin-mes-filtro')?.value || finMesAtual(); if (!confirm(`Fechar o mês ${mes}? Isso consolida o faturamento.`)) return; const r = await api('/financeiro/faturamento/fechar', { method: 'POST', body: JSON.stringify({ mes }) }); mostrarToast('🔒 Mês fechado!', `Resultado: ${fmt(r.resultado)}`); finAba(document.getElementById('ftab-contas'), 'fin-contas-painel'); carregarFaturamento(); } function abrirModalConta() { document.getElementById('modal-conta-fin').classList.add('open'); } async function salvarConta() { const body = { nome: document.getElementById('conta-nome').value, tipo: document.getElementById('conta-tipo').value, banco: document.getElementById('conta-banco').value, agencia: document.getElementById('conta-agencia').value, conta: document.getElementById('conta-numero').value, saldo_inicial: parseFloat(document.getElementById('conta-saldo').value) || 0 }; if (!body.nome) { mostrarToast('⚠️ Nome é obrigatório', ''); return; } await api('/financeiro/contas', { method: 'POST', body: JSON.stringify(body) }); mostrarToast('✅ Conta criada!', ''); document.getElementById('modal-conta-fin').classList.remove('open'); carregarFinanceiro(); } /* ===================== PARCEIROS ===================== */ let _parceirosCache = []; let _parceiroAtual = null; let _comissoesSelecionadas = new Set(); async function carregarParceiros() { _parceirosCache = await api('/parceiros') || []; renderParceiros(_parceirosCache); // KPIs do ranking const ranking = await api('/parceiros/ranking') || []; const totalVol = ranking.reduce((a,r) => a + r.volume_total, 0); const totalCom = ranking.reduce((a,r) => a + r.comissao_total, 0); const totalPago = ranking.reduce((a,r) => a + r.comissao_paga, 0); document.getElementById('kpi-parceiros').innerHTML = `
🤝
Total Parceiros
${_parceirosCache.length}
${_parceirosCache.filter(p=>p.status==='ativo').length} ativos
` + `
📋
Total Indicações
${_parceirosCache.reduce((a,p)=>a+p.total_indicacoes,0)}
` + `
💰
Volume Gerado
${fmt(totalVol)}
` + `
🏆
Comissões a Pagar
${fmt(totalCom - totalPago)}
`; renderRankingParceiros(ranking); await carregarComissoesPendentes(); } function renderParceiros(lista) { const busca = (document.getElementById('busca-parceiro')?.value || '').toLowerCase(); const tipo = document.getElementById('filtro-tipo-parceiro')?.value || ''; const filtrado = lista.filter(p => (!busca || p.nome.toLowerCase().includes(busca) || p.codigo.toLowerCase().includes(busca) || (p.cidade||'').toLowerCase().includes(busca)) && (!tipo || p.tipo === tipo) ); const grid = document.getElementById('grid-parceiros'); if (!grid) return; grid.innerHTML = filtrado.length === 0 ? `
🤝

Nenhum parceiro encontrado

Clique em "+ Novo Parceiro" para começar

` : filtrado.map(p => `
${p.nome.charAt(0).toUpperCase()}
${p.nome}
${p.codigo} ${tipoLabel(p.tipo)}
${p.cidade ? `
📍 ${p.cidade}${p.estado ? '-'+p.estado : ''}
` : ''}
${p.status}
${p.total_indicacoes||0}
Indicações
${fmt(p.comissoes_pagas||0)}
Pago
${fmt(p.comissoes_pendentes||0)}
Pendente
🏷️ ${p.comissao_percentual}% comissão
${p.whatsapp ? `💬` : ''}
`).join(''); } function tipoLabel(t) { return { pessoa_fisica:'Pessoa Física', pessoa_juridica:'Pessoa Jurídica', correspondente:'Correspondente Bancário' }[t] || t; } function filtrarParceiros() { renderParceiros(_parceirosCache); } function alternarAbaParceiros(aba) { ['lista','ranking','comissoes'].forEach(a => { document.getElementById(`painel-parceiros-${a}`).style.display = a===aba ? '' : 'none'; document.getElementById(`tab-parceiros-${a}`)?.classList.toggle('active', a===aba); }); } function renderRankingParceiros(ranking) { const cores = ['#f59e0b','#94a3b8','#cd7c2e']; document.getElementById('lista-ranking-parceiros').innerHTML = ranking.length === 0 ? '

Nenhum dado ainda

' : ranking.map((r, i) => `
${i<3?['🥇','🥈','🥉'][i]:i+1}
${r.nome}
${r.total_indicacoes} indicações · ${r.contratadas} contratadas · ${r.taxa_conversao}% conversão
${fmt(r.volume_total)}
${fmt(r.comissao_total)} comissão
`).join(''); } async function carregarComissoesPendentes() { const lista = await api('/parceiros/comissoes/pendentes') || []; _comissoesSelecionadas.clear(); const el = document.getElementById('lista-comissoes-parceiros'); if (!el) return; el.innerHTML = lista.length === 0 ? '

Nenhuma comissão pendente!

' : lista.map(c => `
${c.parceiro_nome}
Cliente: ${c.cliente_nome}${c.produto ? ' · '+c.produto : ''}${c.banco_proposta ? ' · '+c.banco_proposta : ''}
${c.pix ? '🔑 PIX: '+c.pix : c.banco ? '🏦 '+c.banco+' Ag:'+c.agencia+' Cc:'+c.conta : 'Sem dados bancários'}
${fmt(c.comissao_valor)}
${c.comissao_percentual}% do contrato
`).join(''); } function toggleComissao(id) { _comissoesSelecionadas.has(id) ? _comissoesSelecionadas.delete(id) : _comissoesSelecionadas.add(id); } async function pagarComissaoParceiro(indId, nome, valor) { if (!confirm(`Confirmar pagamento de ${fmt(valor)} para ${nome}?`)) return; const obs = prompt('Observação do pagamento (opcional):') || ''; await api(`/parceiros/indicacoes/${indId}/pagar`, { method:'POST', body: JSON.stringify({ observacao: obs }) }); mostrarToast('✅ Comissão paga!', `${fmt(valor)} registrado para ${nome}`); carregarParceiros(); } async function pagarTodasComissoes() { if (_comissoesSelecionadas.size === 0) { mostrarToast('⚠️ Selecione ao menos uma comissão', ''); return; } if (!confirm(`Pagar ${_comissoesSelecionadas.size} comissão(ões) selecionada(s)?`)) return; for (const id of _comissoesSelecionadas) { await api(`/parceiros/indicacoes/${id}/pagar`, { method:'POST', body: JSON.stringify({}) }); } mostrarToast('✅ Comissões pagas!', `${_comissoesSelecionadas.size} pagamento(s) registrado(s)`); carregarParceiros(); } async function verParceiro(id) { const d = await api('/parceiros/' + id); _parceiroAtual = d; document.getElementById('dp-avatar').textContent = d.nome.charAt(0).toUpperCase(); document.getElementById('dp-nome').textContent = d.nome; document.getElementById('dp-codigo').textContent = d.codigo; document.getElementById('dp-tipo').textContent = tipoLabel(d.tipo); const k = d.kpis || {}; document.getElementById('dp-kpis').innerHTML = `
${k.total||0}
Indicações
` + `
${fmt(k.volume||0)}
Volume
` + `
${fmt(k.comissao_paga||0)}
Comissão Paga
` + `
${fmt(k.comissao_pendente||0)}
A Receber
`; document.getElementById('dp-tabela-indicacoes').innerHTML = (d.indicacoes||[]).map(i => ` ${i.cliente_nome}
${i.cliente_wpp||''} ${i.produto||'—'} ${i.banco||'—'} ${fmt(i.valor_contrato)} ${fmt(i.comissao_valor)} ${i.comissao_status} ${i.comissao_status!=='pago'&&i.comissao_valor>0?``:''} ${!i.proposta_id?``:''} `).join('') || 'Nenhuma indicação ainda'; document.getElementById('dp-tabela-extrato').innerHTML = (d.extrato||[]).map(e => ` ${e.criado_em?new Date(e.criado_em).toLocaleDateString('pt-BR'):'-'} ${e.tipo} ${e.descricao} ${e.valor>0?'+':''}${fmt(e.valor)} ${fmt(e.saldo_apos)} `).join('') || 'Sem movimentações'; document.getElementById('dp-dados-content').innerHTML = `
${campo('WhatsApp', d.whatsapp)} ${campo('E-mail', d.email)} ${campo('Cidade', d.cidade ? d.cidade+(d.estado?'-'+d.estado:'') : null)} ${campo('CPF/CNPJ', d.cpf||d.cnpj)} ${campo('Banco', d.banco)} ${campo('Agência', d.agencia)} ${campo('Conta', d.conta ? d.conta+' ('+d.tipo_conta+')' : null)} ${campo('PIX', d.pix)} ${campo('Comissão', d.comissao_percentual+'% sobre valor contratado')} ${d.observacoes ? `
${campo('Observações', d.observacoes)}
` : ''}
${d.whatsapp?`💬 WhatsApp`:''}
`; document.getElementById('modal-detalhe-parceiro').classList.add('open'); } function campo(label, val) { if (!val) return ''; return `
${label}
${val}
`; } function abaDetalhe(btn, painel) { document.querySelectorAll('#modal-detalhe-parceiro .hist-tab').forEach(b => b.classList.remove('active')); document.querySelectorAll('#modal-detalhe-parceiro .hist-panel').forEach(p => p.classList.remove('active')); btn.classList.add('active'); document.getElementById(painel).classList.add('active'); } function fecharDetalhe() { document.getElementById('modal-detalhe-parceiro').classList.remove('open'); } function abrirModalParceiro() { document.getElementById('parceiro-id').value = ''; document.getElementById('modal-parceiro-titulo').textContent = 'Novo Parceiro'; ['p-nome','p-cpf','p-email','p-whatsapp','p-cidade','p-estado','p-banco','p-agencia','p-conta','p-pix','p-obs'].forEach(id => { const el = document.getElementById(id); if(el) el.value=''; }); document.getElementById('p-comissao').value = '1.0'; document.getElementById('p-tipo').value = 'pessoa_fisica'; document.getElementById('p-tipo-conta').value = 'corrente'; document.getElementById('modal-parceiro').classList.add('open'); } function fecharModalParceiro() { document.getElementById('modal-parceiro').classList.remove('open'); } async function editarParceiro(id) { const p = _parceirosCache.find(x=>x.id===id) || await api('/parceiros/'+id); document.getElementById('parceiro-id').value = p.id; document.getElementById('modal-parceiro-titulo').textContent = 'Editar Parceiro'; document.getElementById('p-nome').value = p.nome||''; document.getElementById('p-cpf').value = p.cpf||p.cnpj||''; document.getElementById('p-tipo').value = p.tipo||'pessoa_fisica'; document.getElementById('p-email').value = p.email||''; document.getElementById('p-whatsapp').value = p.whatsapp||''; document.getElementById('p-cidade').value = p.cidade||''; document.getElementById('p-estado').value = p.estado||''; document.getElementById('p-comissao').value = p.comissao_percentual||1; document.getElementById('p-banco').value = p.banco||''; document.getElementById('p-agencia').value = p.agencia||''; document.getElementById('p-conta').value = p.conta||''; document.getElementById('p-tipo-conta').value = p.tipo_conta||'corrente'; document.getElementById('p-pix').value = p.pix||''; document.getElementById('p-obs').value = p.observacoes||''; document.getElementById('modal-parceiro').classList.add('open'); } async function salvarParceiro() { const id = document.getElementById('parceiro-id').value; const body = { nome: document.getElementById('p-nome').value, cpf: document.getElementById('p-cpf').value, tipo: document.getElementById('p-tipo').value, email: document.getElementById('p-email').value, whatsapp: document.getElementById('p-whatsapp').value, cidade: document.getElementById('p-cidade').value, estado: document.getElementById('p-estado').value, comissao_percentual: parseFloat(document.getElementById('p-comissao').value)||1, banco: document.getElementById('p-banco').value, agencia: document.getElementById('p-agencia').value, conta: document.getElementById('p-conta').value, tipo_conta: document.getElementById('p-tipo-conta').value, pix: document.getElementById('p-pix').value, observacoes: document.getElementById('p-obs').value, status: 'ativo' }; if (!body.nome) { mostrarToast('⚠️ Nome é obrigatório',''); return; } if (id) { await api('/parceiros/'+id, { method:'PUT', body: JSON.stringify(body) }); mostrarToast('✅ Parceiro atualizado!',''); } else { const r = await api('/parceiros', { method:'POST', body: JSON.stringify(body) }); mostrarToast('✅ Parceiro criado!', `Código: ${r.codigo}`); } fecharModalParceiro(); carregarParceiros(); } function copiarLinkIndicacao(codigo) { const base = window.location.origin; const link = `${base}/indicar/${codigo}`; navigator.clipboard.writeText(link).then(() => mostrarToast('🔗 Link copiado!', link)); } async function abrirIndicarCliente() { if (!_parceiroAtual) return; const clientes = await api('/clientes?limit=300'); const lista = (clientes.clientes||[]); if (lista.length === 0) { mostrarToast('⚠️ Nenhum cliente cadastrado',''); return; } const opts = lista.map((c,i)=>`${i+1}. ${c.nome}${c.cpf?' — '+c.cpf:''}`).join('\n'); const idx = parseInt(prompt(`Selecione o cliente indicado:\n\n${opts}\n\nOu digite 0 para cadastrar novo`) || '0') - 1; if (idx === -1) { const nome = prompt('Nome do novo cliente:'); if (!nome) return; const wpp = prompt('WhatsApp (apenas números):') || ''; await api(`/parceiros/${_parceiroAtual.id}/indicar`, { method:'POST', body: JSON.stringify({ nome, whatsapp: wpp }) }); } else if (idx >= 0 && idx < lista.length) { await api(`/parceiros/${_parceiroAtual.id}/indicar`, { method:'POST', body: JSON.stringify({ cliente_id: lista[idx].id }) }); } else return; mostrarToast('✅ Indicação registrada!',''); verParceiro(_parceiroAtual.id); } async function vincularPropostaParceiro(indId) { const props = await api('/propostas'); if (!props||props.length===0) { mostrarToast('⚠️ Nenhuma proposta disponível',''); return; } const opts = props.map((p,i)=>`${i+1}. ${p.cliente_nome||'-'} — ${p.tipo} ${p.banco||''} ${fmt(p.valor_solicitado)}`).join('\n'); const idx = parseInt(prompt(`Qual proposta vincular à indicação?\n\n${opts}`) || '0') - 1; if (idx < 0 || idx >= props.length) return; const r = await api(`/parceiros/indicacoes/${indId}/vincular-proposta`, { method:'POST', body: JSON.stringify({ proposta_id: props[idx].id }) }); mostrarToast('✅ Proposta vinculada!', `Comissão calculada: ${fmt(r.comissao_calculada)}`); verParceiro(_parceiroAtual.id); } /* ===================== RÉGUA DE RELACIONAMENTO ===================== */ async function carregarRegua() { const reguas = await api('/regua'); document.getElementById('lista-reguas').innerHTML = (reguas || []).length === 0 ? '

Nenhuma régua criada. Crie a primeira para automatizar o follow-up!

' : (reguas || []).map(r => `
${r.nome} ${r.descricao ? `

${r.descricao}

` : ''}
${r.ativo ? 'Ativa' : 'Inativa'} ${r.total_passos} passo(s)
`).join(''); } async function verRegua(id, nome) { const r = await api('/regua/' + id); const passos = (r.passos || []).map((p, i) => `
${i+1}
${p.titulo}
Dia +${p.dias_apos} · ${p.tipo}
${p.mensagem ? `
${p.mensagem}
` : ''}
`).join(''); alert(`Régua: ${nome}\n\n${(r.passos||[]).map((p,i)=>`${i+1}. Dia +${p.dias_apos}: ${p.titulo}`).join('\n')}`); } async function deletarRegua(id) { if (!confirm('Excluir esta régua?')) return; await api('/regua/' + id, { method: 'DELETE' }); mostrarToast('Régua excluída', ''); carregarRegua(); } function abrirModalRegua() { const nome = prompt('Nome da régua (ex: "Pós-cadastro 7 dias"):'); if (!nome) return; const desc = prompt('Descrição (opcional):') || ''; // Passos padrão const passos = [ { dias_apos: 1, tipo: 'tarefa', titulo: 'Ligar para apresentação', mensagem: 'Apresentar o produto e verificar interesse' }, { dias_apos: 3, tipo: 'whatsapp', titulo: 'Enviar proposta por WhatsApp', mensagem: 'Enviar simulação personalizada' }, { dias_apos: 7, tipo: 'tarefa', titulo: 'Follow-up — decisão', mensagem: 'Verificar se o cliente decidiu' }, { dias_apos: 14, tipo: 'tarefa', titulo: 'Último contato', mensagem: 'Tentativa final antes de arquivar' } ]; api('/regua', { method: 'POST', body: JSON.stringify({ nome, descricao: desc, passos }) }) .then(() => { mostrarToast('✅ Régua criada com passos padrão!', 'Edite os passos conforme necessário'); carregarRegua(); }); } async function carregarAniversarios(periodo) { const lista = await api('/regua/aniversarios/' + periodo); const el = document.getElementById('lista-aniversarios'); if (!lista || lista.length === 0) { el.innerHTML = `

Nenhum aniversariante ${periodo === 'hoje' ? 'hoje' : 'esta semana'}.

`; return; } el.innerHTML = lista.map(c => `
🎂
${c.nome}
${c.data_nascimento ? new Date(c.data_nascimento+'T12:00:00').toLocaleDateString('pt-BR',{day:'2-digit',month:'long'}) : ''}
${c.whatsapp ? `💬 WhatsApp` : ''}
`).join(''); } /* ===================== MODELOS DE MENSAGEM ===================== */ let _modelosCat = ''; async function carregarModelos(categoria) { if (categoria !== undefined) _modelosCat = categoria; const url = '/modelos' + (_modelosCat ? '?categoria=' + _modelosCat : ''); const modelos = await api(url); document.getElementById('lista-modelos').innerHTML = (modelos || []).length === 0 ? '

Nenhum modelo encontrado. Crie o primeiro!

' : (modelos || []).map(m => `
${m.nome}
${m.categoria} ${m.canal}

${m.conteudo}

`).join(''); } function filtrarModelos(btn, cat) { document.querySelectorAll('.chat-filtro').forEach(b => b.classList.remove('active')); btn.classList.add('active'); carregarModelos(cat); } async function copiarModelo(id, e) { e.stopPropagation(); const r = await api('/modelos/' + id + '/renderizar', { method: 'POST', body: JSON.stringify({}) }); navigator.clipboard.writeText(r.texto || '').then(() => mostrarToast('📋 Copiado!', 'Texto copiado para a área de transferência')); } async function deletarModelo(id, e) { e.stopPropagation(); if (!confirm('Excluir este modelo?')) return; await api('/modelos/' + id, { method: 'DELETE' }); mostrarToast('Modelo excluído', ''); carregarModelos(); } function abrirModalModelo() { const nome = prompt('Nome do modelo (ex: "Aprovação de crédito"):'); if (!nome) return; const cats = ['aprovacao','cobranca','boas_vindas','aniversario','portabilidade','geral']; const cat = prompt(`Categoria:\n${cats.map((c,i)=>`${i+1}. ${c}`).join('\n')}\n\nDigite o número:`) || '6'; const categoria = cats[parseInt(cat) - 1] || 'geral'; const canal = prompt('Canal (whatsapp / sms / email):') || 'whatsapp'; const conteudo = prompt('Texto do modelo (use {{nome}}, {{primeiro_nome}}, {{cidade}}):'); if (!conteudo) return; api('/modelos', { method: 'POST', body: JSON.stringify({ nome, categoria, canal, conteudo }) }) .then(() => { mostrarToast('✅ Modelo salvo!', ''); carregarModelos(); }); } /* ===================== BLACKLIST ===================== */ async function carregarBlacklist() { const lista = await api('/blacklist'); document.getElementById('tabela-blacklist').innerHTML = (lista || []).map(b => ` ${b.cliente_nome || '—'} ${b.cpf || '—'} ${b.whatsapp || '—'} ${b.motivo} ${b.adicionado_nome || '—'} ${b.criado_em ? new Date(b.criado_em).toLocaleDateString('pt-BR') : '—'} `).join('') || 'Nenhum registro'; } async function removerBlacklist(id) { if (!confirm('Remover da blacklist?')) return; await api('/blacklist/' + id, { method: 'DELETE' }); mostrarToast('Removido da blacklist', ''); carregarBlacklist(); } function abrirModalBlacklist() { const motivo = prompt('Motivo da restrição (ex: "Fraude", "Opt-out", "Lead falso"):'); if (!motivo) return; const cpf = prompt('CPF (opcional, apenas números):') || ''; const wpp = prompt('WhatsApp (opcional, apenas números):') || ''; if (!cpf && !wpp) { alert('Informe ao menos CPF ou WhatsApp'); return; } api('/blacklist', { method: 'POST', body: JSON.stringify({ cpf, whatsapp: wpp, motivo }) }) .then(() => { mostrarToast('🚫 Adicionado à blacklist', ''); carregarBlacklist(); }); } /* ===================== INDICAÇÕES ===================== */ async function carregarIndicacoes() { const [hist, ranking] = await Promise.all([api('/indicacoes'), api('/indicacoes/ranking')]); document.getElementById('tabela-indicacoes').innerHTML = (hist || []).map(i => ` ${i.indicador_nome} ${i.indicado_nome} ${fmt(i.bonus_valor)} ${i.bonus_status} ${i.bonus_status !== 'pago' && i.bonus_valor > 0 ? `` : ''} `).join('') || 'Nenhuma indicação'; document.getElementById('ranking-indicacoes').innerHTML = (ranking || []).slice(0,10).map((r, i) => `
${i+1}
${r.nome}
${r.total_indicacoes} indicação(ões)
${fmt(r.bonus_pago)}
recebido
`).join('') || '

Nenhum dado

'; } async function pagarBonus(id) { if (!confirm('Confirmar pagamento do bônus?')) return; await api('/indicacoes/' + id + '/pagar-bonus', { method: 'PUT' }); mostrarToast('✅ Bônus pago!', ''); carregarIndicacoes(); } async function abrirModalIndicacao() { const clientes = await api('/clientes?limit=200'); const lista = (clientes.clientes || []); const opcoes = lista.map((c, i) => `${i+1}. ${c.nome}`).join('\n'); const idxInd = parseInt(prompt(`Quem INDICOU? (número)\n${opcoes}`) || '0') - 1; const idxInd2 = parseInt(prompt(`Quem FOI INDICADO? (número)\n${opcoes}`) || '0') - 1; if (idxInd < 0 || idxInd2 < 0 || idxInd === idxInd2) { alert('Seleção inválida'); return; } const bonus = parseFloat(prompt('Valor do bônus (R$): 0 = sem bônus') || '0'); await api('/indicacoes', { method: 'POST', body: JSON.stringify({ cliente_id: lista[idxInd].id, indicado_id: lista[idxInd2].id, bonus_valor: bonus }) }); mostrarToast('✅ Indicação registrada!', ''); carregarIndicacoes(); } /* ===================== PORTABILIDADE ===================== */ async function preencherSelectPortaCliente() { const d = await api('/clientes?limit=300'); const sel = document.getElementById('porta-cliente'); if (!sel) return; sel.innerHTML = '' + (d.clientes || []).map(c => ``).join(''); } async function calcularPortabilidade() { const payload = { cliente_id: document.getElementById('porta-cliente').value || null, banco_atual: document.getElementById('porta-banco-atual').value, valor_saldo_devedor: document.getElementById('porta-saldo').value, taxa_atual: document.getElementById('porta-taxa-atual').value, parcela_atual: document.getElementById('porta-parcela-atual').value, meses_restantes: document.getElementById('porta-meses').value, banco_novo: document.getElementById('porta-banco-novo').value, taxa_nova: document.getElementById('porta-taxa-nova').value, prazo_novo: document.getElementById('porta-prazo-novo').value }; if (!payload.valor_saldo_devedor || !payload.taxa_atual || !payload.taxa_nova) { mostrarToast('⚠️ Preencha Saldo Devedor, Taxa Atual e Nova Taxa', ''); return; } const r = await api('/portabilidade/calcular', { method: 'POST', body: JSON.stringify(payload) }); const el = document.getElementById('porta-resultado'); el.style.display = 'block'; const cor = r.vale_porta ? '#10b981' : '#ef4444'; document.getElementById('porta-dados').innerHTML = `
${r.vale_porta ? '✅ VALE A PENA!' : '❌ NÃO compensa'}
${r.vale_porta ? 'O cliente economizará com a portabilidade' : 'A nova taxa não gera economia'}
Parcela Atual
${fmt(r.parcela_atual)}
Nova Parcela
${fmt(r.parcela_nova)}
Economia/mês
${fmt(r.economia_mensal)}
Economia Total
${fmt(r.economia_total)}
CET atual${r.cet_atual}% a.a.CET novo${r.cet_novo}% a.a.
Total atual${fmt(r.total_atual)}Total novo${fmt(r.total_novo)}
${r.id ? `` : ''} `; carregarPortabilidade(); } async function gerarPropostaPortabilidade(id) { const r = await api('/portabilidade/' + id + '/gerar-proposta', { method: 'POST' }); mostrarToast('✅ Proposta criada!', 'Proposta de portabilidade gerada com sucesso'); navegar('propostas'); } async function carregarPortabilidade() { const lista = await api('/portabilidade'); document.getElementById('tabela-portabilidade').innerHTML = (lista || []).map(p => ` ${p.cliente_nome || '—'} ${p.banco_atual || '—'} ${p.banco_novo || '—'} ${fmt(p.economia_mensal)} ${fmt(p.economia_total)} ${p.vale_porta ? '✅ Sim' : '❌ Não'} ${p.criado_em ? new Date(p.criado_em).toLocaleDateString('pt-BR') : '—'} ${!p.proposta_id && p.vale_porta ? `` : ''} `).join('') || 'Nenhuma análise realizada'; }