vattenpharma

Cadastro CRM

Adriana, vou montar o sistema completo para você. Será um CRM funcional com banco de dados real. Vou usar:

  • HTML + CSS + JavaScript para o front-end
  • LocalStorage como banco de dados inicial (depois te mostro como migrar para um banco real)
  • Chart.js para os relatórios gráficos

Vou dividir em partes organizadas:


PARTE 1 – Estrutura HTML principal (index.html)

<!DOCTYPE html> <html lang=”pt-BR”> <head> <meta charset=”UTF-8″ /> <meta name=”viewport” content=”width=device-width, initial-scale=1.0″/> <title>CRM Médico</title> <link rel=”stylesheet” href=”style.css” /> <script src=”https://cdn.jsdelivr.net/npm/chart.js”></script> </head> <body> <!– SIDEBAR –> <div class=”sidebar”> <div class=”logo”>🏥 CRM Médico</div> <nav> <a href=”#” onclick=”showSection(‘dashboard’)”>📊 Dashboard</a> <a href=”#” onclick=”showSection(‘pacientes’)”>👤 Pacientes</a> <a href=”#” onclick=”showSection(‘medicos’)”>🩺 Médicos</a> <a href=”#” onclick=”showSection(‘representantes’)”>🤝 Representantes</a> <a href=”#” onclick=”showSection(‘produtos’)”>💊 Produtos</a> <a href=”#” onclick=”showSection(‘vendas’)”>💰 Vendas</a> <a href=”#” onclick=”showSection(‘relatorios’)”>📈 Relatórios</a> </nav> </div> <!– CONTEÚDO PRINCIPAL –> <div class=”main-content”> <!– DASHBOARD –> <div id=”dashboard” class=”section active”> <h1>Dashboard</h1> <div class=”cards”> <div class=”card” id=”card-pacientes”>👤 <span id=”total-pacientes”>0</span> Pacientes</div> <div class=”card” id=”card-medicos”>🩺 <span id=”total-medicos”>0</span> Médicos</div> <div class=”card” id=”card-representantes”>🤝 <span id=”total-representantes”>0</span> Representantes</div> <div class=”card” id=”card-vendas”>💰 R$ <span id=”total-vendas”>0,00</span> em Vendas</div> </div> <div class=”chart-container”> <canvas id=”grafico-vendas-mes”></canvas> </div> </div> <!– PACIENTES –> <div id=”pacientes” class=”section”> <h1>Cadastro de Pacientes</h1> <form id=”form-paciente” class=”form-card”> <input type=”hidden” id=”paciente-id” /> <div class=”form-row”> <div class=”form-group”> <label>Nome Completo *</label> <input type=”text” id=”paciente-nome” required /> </div> <div class=”form-group”> <label>CPF *</label> <input type=”text” id=”paciente-cpf” maxlength=”14″ placeholder=”000.000.000-00″ required /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Telefone *</label> <input type=”text” id=”paciente-telefone” placeholder=”(00) 00000-0000″ required /> </div> <div class=”form-group”> <label>Email</label> <input type=”email” id=”paciente-email” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>CEP</label> <input type=”text” id=”paciente-cep” maxlength=”9″ placeholder=”00000-000″ onblur=”buscarCEP(‘paciente’)” /> </div> <div class=”form-group”> <label>Rua</label> <input type=”text” id=”paciente-rua” /> </div> <div class=”form-group”> <label>Número</label> <input type=”text” id=”paciente-numero” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Bairro</label> <input type=”text” id=”paciente-bairro” /> </div> <div class=”form-group”> <label>Cidade</label> <input type=”text” id=”paciente-cidade” /> </div> <div class=”form-group”> <label>Estado</label> <input type=”text” id=”paciente-estado” maxlength=”2″ /> </div> </div> <div class=”form-actions”> <button type=”submit” class=”btn-primary”>💾 Salvar Paciente</button> <button type=”button” class=”btn-secondary” onclick=”limparForm(‘paciente’)”>🔄 Limpar</button> </div> </form> <div class=”search-bar”> <input type=”text” id=”busca-paciente” placeholder=”🔍 Buscar paciente por nome ou CPF…” onkeyup=”filtrarTabela(‘pacientes’)” /> </div> <div class=”table-container”> <table id=”tabela-pacientes”> <thead> <tr> <th>Nome</th> <th>CPF</th> <th>Telefone</th> <th>Email</th> <th>Cidade/UF</th> <th>Ações</th> </tr> </thead> <tbody id=”tbody-pacientes”></tbody> </table> </div> </div> <!– MÉDICOS –> <div id=”medicos” class=”section”> <h1>Cadastro de Médicos</h1> <form id=”form-medico” class=”form-card”> <input type=”hidden” id=”medico-id” /> <div class=”form-row”> <div class=”form-group”> <label>Nome Completo *</label> <input type=”text” id=”medico-nome” required /> </div> <div class=”form-group”> <label>CRM *</label> <input type=”text” id=”medico-crm” required placeholder=”CRM/UF 000000″ /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Telefone *</label> <input type=”text” id=”medico-telefone” placeholder=”(00) 00000-0000″ required /> </div> <div class=”form-group”> <label>Especialidade</label> <input type=”text” id=”medico-especialidade” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>CEP</label> <input type=”text” id=”medico-cep” maxlength=”9″ placeholder=”00000-000″ onblur=”buscarCEP(‘medico’)” /> </div> <div class=”form-group”> <label>Rua</label> <input type=”text” id=”medico-rua” /> </div> <div class=”form-group”> <label>Número</label> <input type=”text” id=”medico-numero” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Bairro</label> <input type=”text” id=”medico-bairro” /> </div> <div class=”form-group”> <label>Cidade</label> <input type=”text” id=”medico-cidade” /> </div> <div class=”form-group”> <label>Estado</label> <input type=”text” id=”medico-estado” maxlength=”2″ /> </div> </div> <div class=”form-actions”> <button type=”submit” class=”btn-primary”>💾 Salvar Médico</button> <button type=”button” class=”btn-secondary” onclick=”limparForm(‘medico’)”>🔄 Limpar</button> </div> </form> <div class=”search-bar”> <input type=”text” id=”busca-medico” placeholder=”🔍 Buscar médico por nome ou CRM…” onkeyup=”filtrarTabela(‘medicos’)” /> </div> <div class=”table-container”> <table id=”tabela-medicos”> <thead> <tr> <th>Nome</th> <th>CRM</th> <th>Especialidade</th> <th>Telefone</th> <th>Cidade/UF</th> <th>Ações</th> </tr> </thead> <tbody id=”tbody-medicos”></tbody> </table> </div> </div> <!– REPRESENTANTES –> <div id=”representantes” class=”section”> <h1>Cadastro de Representantes</h1> <form id=”form-representante” class=”form-card”> <input type=”hidden” id=”representante-id” /> <div class=”form-row”> <div class=”form-group”> <label>Nome Completo *</label> <input type=”text” id=”representante-nome” required /> </div> <div class=”form-group”> <label>Telefone *</label> <input type=”text” id=”representante-telefone” placeholder=”(00) 00000-0000″ required /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Email *</label> <input type=”email” id=”representante-email” required /> </div> <div class=”form-group”> <label>PIX</label> <input type=”text” id=”representante-pix” placeholder=”CPF, e-mail ou chave aleatória” /> </div> </div> <div class=”form-group”> <label>Médicos Vinculados</label> <select id=”representante-medicos” multiple style=”height:100px”> </select> <small>Segure Ctrl para selecionar vários</small> </div> <div class=”form-actions”> <button type=”submit” class=”btn-primary”>💾 Salvar Representante</button> <button type=”button” class=”btn-secondary” onclick=”limparForm(‘representante’)”>🔄 Limpar</button> </div> </form> <div class=”table-container”> <table id=”tabela-representantes”> <thead> <tr> <th>Nome</th> <th>Telefone</th> <th>Email</th> <th>PIX</th> <th>Médicos</th> <th>Ações</th> </tr> </thead> <tbody id=”tbody-representantes”></tbody> </table> </div> </div> <!– PRODUTOS –> <div id=”produtos” class=”section”> <h1>Cadastro de Produtos</h1> <form id=”form-produto” class=”form-card”> <input type=”hidden” id=”produto-id” /> <div class=”form-row”> <div class=”form-group”> <label>Nome do Produto *</label> <input type=”text” id=”produto-nome” required /> </div> <div class=”form-group”> <label>Categoria</label> <input type=”text” id=”produto-categoria” placeholder=”Ex: CBD, CBG, Óleo…” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Concentração</label> <input type=”text” id=”produto-concentracao” placeholder=”Ex: 10mg/ml” /> </div> <div class=”form-group”> <label>Preço Unitário (R$)</label> <input type=”number” id=”produto-preco” step=”0.01″ min=”0″ /> </div> </div> <div class=”form-group”> <label>Descrição</label> <textarea id=”produto-descricao” rows=”3″></textarea> </div> <div class=”form-actions”> <button type=”submit” class=”btn-primary”>💾 Salvar Produto</button> <button type=”button” class=”btn-secondary” onclick=”limparForm(‘produto’)”>🔄 Limpar</button> </div> </form> <div class=”table-container”> <table id=”tabela-produtos”> <thead> <tr> <th>Nome</th> <th>Categoria</th> <th>Concentração</th> <th>Preço</th> <th>Ações</th> </tr> </thead> <tbody id=”tbody-produtos”></tbody> </table> </div> </div> <!– VENDAS –> <div id=”vendas” class=”section”> <h1>Registro de Vendas</h1> <form id=”form-venda” class=”form-card”> <input type=”hidden” id=”venda-id” /> <div class=”form-row”> <div class=”form-group”> <label>Data da Venda *</label> <input type=”date” id=”venda-data” required /> </div> <div class=”form-group”> <label>Paciente *</label> <select id=”venda-paciente” required> <option value=””>Selecione…</option> </select> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Produto *</label> <select id=”venda-produto” required onchange=”preencherPreco()”> <option value=””>Selecione…</option> </select> </div> <div class=”form-group”> <label>Quantidade</label> <input type=”number” id=”venda-quantidade” min=”1″ value=”1″ onchange=”calcularTotal()” /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Valor Unitário (R$) *</label> <input type=”number” id=”venda-valor-unitario” step=”0.01″ min=”0″ onchange=”calcularTotal()” required /> </div> <div class=”form-group”> <label>Valor Total (R$)</label> <input type=”number” id=”venda-valor-total” step=”0.01″ min=”0″ readonly style=”background:#f0f0f0″ /> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Meio de Pagamento *</label> <select id=”venda-pagamento” required> <option value=””>Selecione…</option> <option value=”PIX”>PIX</option> <option value=”Cartão”>Cartão</option> <option value=”Transferência”>Transferência</option> <option value=”Boleto”>Boleto</option> </select> </div> <div class=”form-group”> <label>Médico Prescritor</label> <select id=”venda-medico” onchange=”preencherRepresentante()”> <option value=””>Selecione…</option> </select> </div> </div> <div class=”form-row”> <div class=”form-group”> <label>Representante</label> <select id=”venda-representante”> <option value=””>Selecione…</option> </select> </div> <div class=”form-group”> <label>Observações</label> <input type=”text” id=”venda-obs” /> </div> </div> <div class=”form-actions”> <button type=”submit” class=”btn-primary”>💾 Registrar Venda</button> <button type=”button” class=”btn-secondary” onclick=”limparForm(‘venda’)”>🔄 Limpar</button> </div> </form> <div class=”search-bar”> <input type=”date” id=”filtro-venda-inicio” /> <input type=”date” id=”filtro-venda-fim” /> <button class=”btn-primary” onclick=”filtrarVendas()”>🔍 Filtrar</button> <button class=”btn-secondary” onclick=”renderTabela(‘vendas’)”>Ver Todas</button> </div> <div class=”table-container”> <table id=”tabela-vendas”> <thead> <tr> <th>Data</th> <th>Paciente</th> <th>Produto</th> <th>Qtd</th> <th>Total</th> <th>Pagamento</th> <th>Médico</th> <th>Representante</th> <th>Ações</th> </tr> </thead> <tbody id=”tbody-vendas”></tbody> </table> </div> </div> <!– RELATÓRIOS –> <div id=”relatorios” class=”section”> <h1>Relatórios</h1> <div class=”relatorio-filtros form-card”> <h3>Filtros</h3> <div class=”form-row”> <div class=”form-group”> <label>Período</label> <select id=”rel-periodo” onchange=”mudarPeriodo()”> <option value=”personalizado”>Personalizado</option> <option value=”semana”>Esta Semana</option> <option value=”mes”>Este Mês</option> <option value=”ano”>Este Ano</option> </select> </div> <div class=”form-group” id=”rel-data-inicio-group”> <label>Data Início</label> <input type=”date” id=”rel-data-inicio” /> </div> <div class=”form-group” id=”rel-data-fim-group”> <label>Data Fim</label> <input type=”date” id=”rel-data-fim” /> </div> </div> <div class=”form-actions”> <button class=”btn-primary” onclick=”gerarRelatorio()”>📊 Gerar Relatório</button> <button class=”btn-secondary” onclick=”exportarCSV()”>⬇️ Exportar CSV</button> <button class=”btn-secondary” onclick=”imprimirRelatorio()”>🖨️ Imprimir</button> </div> </div> <div id=”relatorio-resultado”> <div class=”cards” id=”rel-cards”> <div class=”card”>Total Vendas: <strong id=”rel-total-vendas”>R$ 0,00</strong></div> <div class=”card”>Nº Vendas: <strong id=”rel-num-vendas”>0</strong></div> <div class=”card”>Ticket Médio: <strong id=”rel-ticket-medio”>R$ 0,00</strong></div> </div> <div class=”graficos-grid”> <div class=”chart-box”> <h3>Vendas por Período</h3> <canvas id=”grafico-periodo”></canvas> </div> <div class=”chart-box”> <h3>Vendas por Meio de Pagamento</h3> <canvas id=”grafico-pagamento”></canvas> </div> <div class=”chart-box”> <h3>Vendas por Médico</h3> <canvas id=”grafico-medico”></canvas> </div> <div class=”chart-box”> <h3>Vendas por Representante</h3> <canvas id=”grafico-representante”></canvas> </div> </div> <div class=”table-container”> <h3>Detalhamento das Vendas</h3> <table> <thead> <tr> <th>Data</th> <th>Paciente</th> <th>Produto</th> <th>Qtd</th> <th>Total</th> <th>Pagamento</th> <th>Médico</th> <th>Representante</th> </tr> </thead> <tbody id=”tbody-relatorio”></tbody> </table> </div> </div> </div> </div><!– fim main-content –> <script src=”crm.js”></script> </body> </html>


PARTE 2 – CSS (style.css)

* { margin: 0; padding: 0; box-sizing: border-box; font-family: ‘Segoe UI’, sans-serif; } :root { –primary: #2c7a7b; –primary-dark: #1a5657; –secondary: #e6f4f1; –accent: #38b2ac; –danger: #e53e3e; –warning: #dd6b20; –text: #2d3748; –bg: #f7fafc; –sidebar-w: 220px; } body { display: flex; background: var(–bg); color: var(–text); min-height: 100vh; } /* SIDEBAR */ .sidebar { width: var(–sidebar-w); background: var(–primary-dark); color: white; position: fixed; height: 100vh; overflow-y: auto; z-index: 100; } .sidebar .logo { padding: 24px 20px; font-size: 1.2rem; font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.15); } .sidebar nav a { display: block; padding: 14px 20px; color: rgba(255,255,255,0.85); text-decoration: none; transition: background 0.2s; font-size: 0.95rem; } .sidebar nav a:hover { background: var(–primary); color: white; } /* MAIN */ .main-content { margin-left: var(–sidebar-w); padding: 30px; flex: 1; min-height: 100vh; } /* SECTIONS */ .section { display: none; } .section.active { display: block; } h1 { font-size: 1.6rem; margin-bottom: 24px; color: var(–primary-dark); border-bottom: 2px solid var(–accent); padding-bottom: 8px; } h3 { font-size: 1.1rem; margin-bottom: 14px; color: var(–primary-dark); } /* CARDS */ .cards { display: flex; gap: 16px; margin-bottom: 30px; flex-wrap: wrap; } .card { background: white; padding: 20px 28px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex: 1; min-width: 160px; font-size: 1rem; text-align: center; border-left: 4px solid var(–accent); } .card strong { display: block; font-size: 1.3rem; color: var(–primary); margin-top: 6px; } /* FORMS */ .form-card { background: white; border-radius: 10px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.07); } .form-row { display: flex; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; } .form-group { flex: 1; min-width: 180px; display: flex; flex-direction: column; gap: 6px; } .form-group label { font-size: 0.85rem; font-weight: 600; color: #4a5568; } .form-group input, .form-group select, .form-group textarea { padding: 9px 12px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 0.95rem; transition: border 0.2s; background: white; color: var(–text); } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(–accent); box-shadow: 0 0 0 3px rgba(56,178,172,0.15); } .form-actions { display: flex; gap: 12px; margin-top: 8px; } /* BUTTONS */ .btn-primary { background: var(–primary); color: white; border: none; padding: 10px 22px; border-radius: 6px; cursor: pointer; font-size: 0.95rem; font-weight: 600; transition: background 0.2s; } .btn-primary:hover { background: var(–primary-dark); } .btn-secondary { background: #e2e8f0; color: var(–text); border: none; padding: 10px 22px; border-radius: 6px; cursor: pointer; font-size: 0.95rem; transition: background 0.2s; } .btn-secondary:hover { background: #cbd5e0; } .btn-danger { background: var(–danger); color: white; border: none; padding: 6px 12px; border-radius: 5px; cursor: pointer; font-size: 0.82rem; } .btn-edit { background: var(–warning); color: white; border: none; padding: 6px 12px; border-radius: 5px; cursor: pointer; font-size: 0.82rem; } /* SEARCH */ .search-bar { display: flex; gap: 10px; margin-bottom: 16px; align-items: center; flex-wrap: wrap; } .search-bar input { padding: 9px 14px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 0.95rem; flex: 1; min-width: 200px; } /* TABLES */ .table-container { background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.07); overflow-x: auto; margin-bottom: 30px; } table { width: 100%; border-collapse: collapse; font-size: 0.9rem; } thead { background: var(–primary); color: white; } th, td { padding: 12px 14px; text-align: left; border-bottom: 1px solid #e2e8f0; } tbody tr:hover { background: var(–secondary); } /* CHARTS */ .chart-container { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.07); margin-bottom: 30px; } .graficos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; } .chart-box { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.07); } /* RESPONSIVE */ @media (max-width: 768px) { .sidebar { width: 60px; } .sidebar .logo { font-size: 0; padding: 20px 15px; } .sidebar nav a { padding: 14px; font-size: 1.2rem; } .main-content { margin-left: 60px; padding: 16px; } .graficos-grid { grid-template-columns: 1fr; } .form-row { flex-direction: column; } } /* RELATORIO PRINT */ @media print { .sidebar, .form-card, .search-bar, .form-actions, .btn-primary, .btn-secondary { display: none !important; } .main-content { margin-left: 0; } }


PARTE 3 – JavaScript completo (crm.js)

// ============================== // BANCO DE DADOS (localStorage) // ============================== const DB = { get: (key) => JSON.parse(localStorage.getItem(key) || ‘[]’), set: (key, data) => localStorage.setItem(key, JSON.stringify(data)), genId: () => ‘_’ + Math.random().toString(36).substr(2, 9) }; // Instâncias dos gráficos (para destruir antes de recriar) let chartInstances = {}; // ============================== // NAVEGAÇÃO // ============================== function showSection(id) { document.querySelectorAll(‘.section’).forEach(s => s.classList.remove(‘active’)); document.getElementById(id).classList.add(‘active’); // Atualiza selects e tabelas ao navegar if (id === ‘dashboard’) atualizarDashboard(); if (id === ‘pacientes’) renderTabela(‘pacientes’); if (id === ‘medicos’) renderTabela(‘medicos’); if (id === ‘representantes’) { popularSelectMedicos(); renderTabela(‘representantes’); } if (id === ‘produtos’) renderTabela(‘produtos’); if (id === ‘vendas’) { popularSelects(); renderTabela(‘vendas’); } if (id === ‘relatorios’) mudarPeriodo(); } // ============================== // UTILITÁRIOS // ============================== function formatMoney(v) { return ‘R$ ‘ + Number(v || 0).toFixed(2).replace(‘.’, ‘,’).replace(/\B(?=(\d{3})+(?!\d))/g, ‘.’); } function formatDate(d) { if (!d) return ”; const [y, m, day] = d.split(‘-‘); return `${day}/${m}/${y}`; } function getNomeById(lista, id) { const item = lista.find(i => i.id === id); return item ? item.nome : ‘—’; } // ============================== // BUSCA CEP (ViaCEP) // ============================== async function buscarCEP(prefixo) { const cep = document.getElementById(`${prefixo}-cep`).value.replace(/\D/g, ”); if (cep.length !== 8) return; try { const r = await fetch(`https://viacep.com.br/ws/${cep}/json/`); const d = await r.json(); if (!d.erro) { document.getElementById(`${prefixo}-rua`).value = d.logradouro || ”; document.getElementById(`${prefixo}-bairro`).value = d.bairro || ”; document.getElementById(`${prefixo}-cidade`).value = d.localidade || ”; document.getElementById(`${prefixo}-estado`).value = d.uf || ”; } } catch(e) { console.log(‘Erro CEP:’, e); } } // ============================== // DASHBOARD // ============================== function atualizarDashboard() { const pacientes = DB.get(‘pacientes’); const medicos = DB.get(‘medicos’); const representantes = DB.get(‘representantes’); const vendas = DB.get(‘vendas’); document.getElementById(‘total-pacientes’).textContent = pacientes.length; document.getElementById(‘total-medicos’).textContent = medicos.length; document.getElementById(‘total-representantes’).textContent = representantes.length; const totalVendas = vendas.reduce((s, v) => s + Number(v.valorTotal || 0), 0); document.getElementById(‘total-vendas’).textContent = totalVendas.toFixed(2).replace(‘.’, ‘,’); // Gráfico de vendas por mês no dashboard const meses = [‘Jan’,’Fev’,’Mar’,’Abr’,’Mai’,’Jun’,’Jul’,’Ago’,’Set’,’Out’,’Nov’,’Dez’]; const dadosMes = Array(12).fill(0); const anoAtual = new Date().getFullYear(); vendas.forEach(v => { if (!v.data) return; const d = new Date(v.data + ‘T00:00:00’); if (d.getFullYear() === anoAtual) { dadosMes[d.getMonth()] += Number(v.valorTotal || 0); } }); const ctx = document.getElementById(‘grafico-vendas-mes’).getContext(‘2d’); if (chartInstances[‘dashboard’]) chartInstances[‘dashboard’].destroy(); chartInstances[‘dashboard’] = new Chart(ctx, { type: ‘bar’, data: { labels: meses, datasets: [{ label: `Vendas ${anoAtual} (R$)`, data: dadosMes, backgroundColor: ‘rgba(44,122,123,0.7)’, borderColor: ‘#1a5657’, borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { position: ‘top’ } } } }); } // ============================== // PACIENTES // ============================== document.getElementById(‘form-paciente’).addEventListener(‘submit’, function(e) { e.preventDefault(); const pacientes = DB.get(‘pacientes’); const id = document.getElementById(‘paciente-id’).value; const obj = { id: id || DB.genId(), nome: document.getElementById(‘paciente-nome’).value, cpf: document.getElementById(‘paciente-cpf’).value, telefone: document.getElementById(‘paciente-telefone’).value, email: document.getElementById(‘paciente-email’).value, cep: document.getElementById(‘paciente-cep’).value, rua: document.getElementById(‘paciente-rua’).value, numero: document.getElementById(‘paciente-numero’).value, bairro: document.getElementById(‘paciente-bairro’).value, cidade: document.getElementById(‘paciente-cidade’).value, estado: document.getElementById(‘paciente-estado’).value }; if (id) { const idx = pacientes.findIndex(p => p.id === id); pacientes[idx] = obj; } else { pacientes.push(obj); } DB.set(‘pacientes’, pacientes); limparForm(‘paciente’); renderTabela(‘pacientes’); alert(‘Paciente salvo com sucesso!’); }); function renderTabela(tipo) { const configs = { pacientes: { dados: DB.get(‘pacientes’), tbody: ‘tbody-pacientes’, cols: (p) => ` <td>${p.nome}</td> <td>${p.cpf}</td> <td>${p.telefone}</td> <td>${p.email || ‘—’}</td> <td>${p.cidade || ‘—’}/${p.estado || ”}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘pacientes’,’${p.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘pacientes’,’${p.id}’)”>🗑️</button> </td>` }, medicos: { dados: DB.get(‘medicos’), tbody: ‘tbody-medicos’, cols: (m) => ` <td>${m.nome}</td> <td>${m.crm}</td> <td>${m.especialidade || ‘—’}</td> <td>${m.telefone}</td> <td>${m.cidade || ‘—’}/${m.estado || ”}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘medicos’,’${m.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘medicos’,’${m.id}’)”>🗑️</button> </td>` }, representantes: { dados: DB.get(‘representantes’), tbody: ‘tbody-representantes’, cols: (r) => { const medicos = DB.get(‘medicos’); const nomes = (r.medicos || []).map(mid => getNomeById(medicos, mid)).join(‘, ‘) || ‘—’; return ` <td>${r.nome}</td> <td>${r.telefone}</td> <td>${r.email}</td> <td>${r.pix || ‘—’}</td> <td style=”max-width:180px;white-space:normal”>${nomes}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘representantes’,’${r.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘representantes’,’${r.id}’)”>🗑️</button> </td>`; } }, produtos: { dados: DB.get(‘produtos’), tbody: ‘tbody-produtos’, cols: (p) => ` <td>${p.nome}</td> <td>${p.categoria || ‘—’}</td> <td>${p.concentracao || ‘—’}</td> <td>${formatMoney(p.preco)}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘produtos’,’${p.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘produtos’,’${p.id}’)”>🗑️</button> </td>` }, vendas: { dados: DB.get(‘vendas’), tbody: ‘tbody-vendas’, cols: (v) => { const pacientes = DB.get(‘pacientes’); const medicos = DB.get(‘medicos’); const representantes = DB.get(‘representantes’); const produtos = DB.get(‘produtos’); return ` <td>${formatDate(v.data)}</td> <td>${getNomeById(pacientes, v.pacienteId)}</td> <td>${getNomeById(produtos, v.produtoId)}</td> <td>${v.quantidade}</td> <td>${formatMoney(v.valorTotal)}</td> <td>${v.pagamento}</td> <td>${getNomeById(medicos, v.medicoId)}</td> <td>${getNomeById(representantes, v.representanteId)}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘vendas’,’${v.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘vendas’,’${v.id}’)”>🗑️</button> </td>`; } } }; const cfg = configs[tipo]; if (!cfg) return; const tbody = document.getElementById(cfg.tbody); if (!tbody) return; tbody.innerHTML = cfg.dados.length === 0 ? `<tr><td colspan=”10″ style=”text-align:center;padding:20px;color:#999″>Nenhum registro encontrado.</td></tr>` : cfg.dados.map(item => `<tr>${cfg.cols(item)}</tr>`).join(”); } function editarItem(tipo, id) { const dados = DB.get(tipo); const item = dados.find(i => i.id === id); if (!item) return; showSection(tipo); if (tipo === ‘pacientes’) { document.getElementById(‘paciente-id’).value = item.id; document.getElementById(‘paciente-nome’).value = item.nome; document.getElementById(‘paciente-cpf’).value = item.cpf; document.getElementById(‘paciente-telefone’).value = item.telefone; document.getElementById(‘paciente-email’).value = item.email || ”; document.getElementById(‘paciente-cep’).value = item.cep || ”; document.getElementById(‘paciente-rua’).value = item.rua || ”; document.getElementById(‘paciente-numero’).value = item.numero || ”; document.getElementById(‘paciente-bairro’).value = item.bairro || ”; document.getElementById(‘paciente-cidade’).value = item.cidade || ”; document.getElementById(‘paciente-estado’).value = item.estado || ”; } if (tipo === ‘medicos’) { document.getElementById(‘medico-id’).value = item.id; document.getElementById(‘medico-nome’).value = item.nome; document.getElementById(‘medico-crm’).value = item.crm; document.getElementById(‘medico-telefone’).value = item.telefone; document.getElementById(‘medico-especialidade’).value = item.especialidade || ”; document.getElementById(‘medico-cep’).value = item.cep || ”; document.getElementById(‘medico-rua’).value = item.rua || ”; document.getElementById(‘medico-numero’).value = item.numero || ”; document.getElementById(‘medico-bairro’).value = item.bairro || ”; document.getElementById(‘medico-cidade’).value = item.cidade || ”; document.getElementById(‘medico-estado’).value = item.estado || ”; } if (tipo === ‘representantes’) { popularSelectMedicos(); document.getElementById(‘representante-id’).value = item.id; document.getElementById(‘representante-nome’).value = item.nome; document.getElementById(‘representante-telefone’).value = item.telefone; document.getElementById(‘representante-email’).value = item.email; document.getElementById(‘representante-pix’).value = item.pix || ”; const sel = document.getElementById(‘representante-medicos’); Array.from(sel.options).forEach(opt => { opt.selected = (item.medicos || []).includes(opt.value); }); } if (tipo === ‘produtos’) { document.getElementById(‘produto-id’).value = item.id; document.getElementById(‘produto-nome’).value = item.nome; document.getElementById(‘produto-categoria’).value = item.categoria || ”; document.getElementById(‘produto-concentracao’).value = item.concentracao || ”; document.getElementById(‘produto-preco’).value = item.preco || ”; document.getElementById(‘produto-descricao’).value = item.descricao || ”; } if (tipo === ‘vendas’) { popularSelects(); document.getElementById(‘venda-id’).value = item.id; document.getElementById(‘venda-data’).value = item.data; document.getElementById(‘venda-paciente’).value = item.pacienteId; document.getElementById(‘venda-produto’).value = item.produtoId; document.getElementById(‘venda-quantidade’).value = item.quantidade; document.getElementById(‘venda-valor-unitario’).value = item.valorUnitario; document.getElementById(‘venda-valor-total’).value = item.valorTotal; document.getElementById(‘venda-pagamento’).value = item.pagamento; document.getElementById(‘venda-medico’).value = item.medicoId || ”; document.getElementById(‘venda-representante’).value = item.representanteId || ”; document.getElementById(‘venda-obs’).value = item.obs || ”; } window.scrollTo(0, 0); } function excluirItem(tipo, id) { if (!confirm(‘Tem certeza que deseja excluir este registro?’)) return; const dados = DB.get(tipo).filter(i => i.id !== id); DB.set(tipo, dados); renderTabela(tipo); } function limparForm(tipo) { const forms = { paciente: ‘form-paciente’, medico: ‘form-medico’, representante: ‘form-representante’, produto: ‘form-produto’, venda: ‘form-venda’ }; document.getElementById(forms[tipo]).reset(); const hiddenId = document.getElementById(`${tipo}-id`); if (hiddenId) hiddenId.value = ”; } function filtrarTabela(tipo) { const buscas = { pacientes: ‘busca-paciente’, medicos: ‘busca-medico’ }; const termo = document.getElementById(buscas[tipo]).value.toLowerCase(); const tbody = document.querySelector(`#tbody-${tipo}`); Array.from(tbody.querySelectorAll(‘tr’)).forEach(row => { row.style.display = row.textContent.toLowerCase().includes(termo) ? ” : ‘none’; }); } // ============================== // MÉDICOS // ============================== document.getElementById(‘form-medico’).addEventListener(‘submit’, function(e) { e.preventDefault(); const medicos = DB.get(‘medicos’); const id = document.getElementById(‘medico-id’).value; const obj = { id: id || DB.genId(), nome: document.getElementById(‘medico-nome’).value, crm: document.getElementById(‘medico-crm’).value, telefone: document.getElementById(‘medico-telefone’).value, especialidade: document.getElementById(‘medico-especialidade’).value, cep: document.getElementById(‘medico-cep’).value, rua: document.getElementById(‘medico-rua’).value, numero: document.getElementById(‘medico-numero’).value, bairro: document.getElementById(‘medico-bairro’).value, cidade: document.getElementById(‘medico-cidade’).value, estado: document.getElementById(‘medico-estado’).value }; if (id) { const idx = medicos.findIndex(m => m.id === id); medicos[idx] = obj; } else medicos.push(obj); DB.set(‘medicos’, medicos); limparForm(‘medico’); renderTabela(‘medicos’); alert(‘Médico salvo com sucesso!’); }); // ============================== // REPRESENTANTES // ============================== function popularSelectMedicos() { const medicos = DB.get(‘medicos’); const sel = document.getElementById(‘representante-medicos’); sel.innerHTML = medicos.map(m => `<option value=”${m.id}”>${m.nome} – ${m.crm}</option>`).join(”); } document.getElementById(‘form-representante’).addEventListener(‘submit’, function(e) { e.preventDefault(); const representantes = DB.get(‘representantes’); const id = document.getElementById(‘representante-id’).value; const sel = document.getElementById(‘representante-medicos’); const medicosSelecionados = Array.from(sel.selectedOptions).map(o => o.value); const obj = { id: id || DB.genId(), nome: document.getElementById(‘representante-nome’).value, telefone: document.getElementById(‘representante-telefone’).value, email: document.getElementById(‘representante-email’).value, pix: document.getElementById(‘representante-pix’).value, medicos: medicosSelecionados }; if (id) { const idx = representantes.findIndex(r => r.id === id); representantes[idx] = obj; } else representantes.push(obj); DB.set(‘representantes’, representantes); limparForm(‘representante’); renderTabela(‘representantes’); alert(‘Representante salvo com sucesso!’); }); // ============================== // PRODUTOS // ============================== document.getElementById(‘form-produto’).addEventListener(‘submit’, function(e) { e.preventDefault(); const produtos = DB.get(‘produtos’); const id = document.getElementById(‘produto-id’).value; const obj = { id: id || DB.genId(), nome: document.getElementById(‘produto-nome’).value, categoria: document.getElementById(‘produto-categoria’).value, concentracao: document.getElementById(‘produto-concentracao’).value, preco: parseFloat(document.getElementById(‘produto-preco’).value) || 0, descricao: document.getElementById(‘produto-descricao’).value }; if (id) { const idx = produtos.findIndex(p => p.id === id); produtos[idx] = obj; } else produtos.push(obj); DB.set(‘produtos’, produtos); limparForm(‘produto’); renderTabela(‘produtos’); alert(‘Produto salvo com sucesso!’); }); // ============================== // VENDAS // ============================== function popularSelects() { const pacientes = DB.get(‘pacientes’); const produtos = DB.get(‘produtos’); const medicos = DB.get(‘medicos’); const representantes = DB.get(‘representantes’); const selPac = document.getElementById(‘venda-paciente’); selPac.innerHTML = ‘<option value=””>Selecione…</option>’ + pacientes.map(p => `<option value=”${p.id}”>${p.nome}</option>`).join(”); const selProd = document.getElementById(‘venda-produto’); selProd.innerHTML = ‘<option value=””>Selecione…</option>’ + produtos.map(p => `<option value=”${p.id}” data-preco=”${p.preco}”>${p.nome}</option>`).join(”); const selMed = document.getElementById(‘venda-medico’); selMed.innerHTML = ‘<option value=””>Selecione…</option>’ + medicos.map(m => `<option value=”${m.id}”>${m.nome}</option>`).join(”); const selRep = document.getElementById(‘venda-representante’); selRep.innerHTML = ‘<option value=””>Selecione…</option>’ + representantes.map(r => `<option value=”${r.id}”>${r.nome}</option>`).join(”); // Data default = hoje if (!document.getElementById(‘venda-id’).value) { document.getElementById(‘venda-data’).value = new Date().toISOString().split(‘T’)[0]; } } function preencherPreco() { const sel = document.getElementById(‘venda-produto’); const opt = sel.options[sel.selectedIndex]; const preco = opt ? opt.dataset.preco : 0; document.getElementById(‘venda-valor-unitario’).value = preco || ”; calcularTotal(); } function calcularTotal() { const qtd = parseFloat(document.getElementById(‘venda-quantidade’).value) || 1; const unit = parseFloat(document.getElementById(‘venda-valor-unitario’).value) || 0; document.getElementById(‘venda-valor-total’).value = (qtd * unit).toFixed(2); } function preencherRepresentante() { const medicoId = document.getElementById(‘venda-medico’).value; const representantes = DB.get(‘representantes’); const rep = representantes.find(r => (r.medicos || []).includes(medicoId)); if (rep) { document.getElementById(‘venda-representante’).value = rep.id; } } document.getElementById(‘form-venda’).addEventListener(‘submit’, function(e) { e.preventDefault(); const vendas = DB.get(‘vendas’); const id = document.getElementById(‘venda-id’).value; const obj = { id: id || DB.genId(), data: document.getElementById(‘venda-data’).value, pacienteId: document.getElementById(‘venda-paciente’).value, produtoId: document.getElementById(‘venda-produto’).value, quantidade: parseInt(document.getElementById(‘venda-quantidade’).value) || 1, valorUnitario: parseFloat(document.getElementById(‘venda-valor-unitario’).value) || 0, valorTotal: parseFloat(document.getElementById(‘venda-valor-total’).value) || 0, pagamento: document.getElementById(‘venda-pagamento’).value, medicoId: document.getElementById(‘venda-medico’).value, representanteId: document.getElementById(‘venda-representante’).value, obs: document.getElementById(‘venda-obs’).value }; if (id) { const idx = vendas.findIndex(v => v.id === id); vendas[idx] = obj; } else vendas.push(obj); DB.set(‘vendas’, vendas); limparForm(‘venda’); renderTabela(‘vendas’); alert(‘Venda registrada com sucesso!’); }); function filtrarVendas() { const inicio = document.getElementById(‘filtro-venda-inicio’).value; const fim = document.getElementById(‘filtro-venda-fim’).value; const vendas = DB.get(‘vendas’); const tbody = document.getElementById(‘tbody-vendas’); const pacientes = DB.get(‘pacientes’); const produtos = DB.get(‘produtos’); const medicos = DB.get(‘medicos’); const representantes = DB.get(‘representantes’); const filtradas = vendas.filter(v => { if (inicio && v.data < inicio) return false; if (fim && v.data > fim) return false; return true; }); tbody.innerHTML = filtradas.length === 0 ? `<tr><td colspan=”10″ style=”text-align:center;padding:20px”>Nenhuma venda no período.</td></tr>` : filtradas.map(v => `<tr> <td>${formatDate(v.data)}</td> <td>${getNomeById(pacientes, v.pacienteId)}</td> <td>${getNomeById(produtos, v.produtoId)}</td> <td>${v.quantidade}</td> <td>${formatMoney(v.valorTotal)}</td> <td>${v.pagamento}</td> <td>${getNomeById(medicos, v.medicoId)}</td> <td>${getNomeById(representantes, v.representanteId)}</td> <td> <button class=”btn-edit” onclick=”editarItem(‘vendas’,’${v.id}’)”>✏️</button> <button class=”btn-danger” onclick=”excluirItem(‘vendas’,’${v.id}’)”>🗑️</button> </td> </tr>`).join(”); } // ============================== // RELATÓRIOS // ============================== function mudarPeriodo() { const periodo = document.getElementById(‘rel-periodo’).value; const hoje = new Date(); let inicio, fim; fim = hoje.toISOString().split(‘T’)[0]; if (periodo === ‘semana’) { const d = new Date(hoje); d.setDate(hoje.getDate() – hoje.getDay()); inicio = d.toISOString().split(‘T’)[0]; } else if (periodo === ‘mes’) { inicio = `${hoje.getFullYear()}-${String(hoje.getMonth()+1).padStart(2,’0′)}-01`; } else if (periodo === ‘ano’) { inicio = `${hoje.getFullYear()}-01-01`; } else { inicio = ”; fim = ”; } document.getElementById(‘rel-data-inicio’).value = inicio; document.getElementById(‘rel-data-fim’).value = fim; if (periodo !== ‘personalizado’) gerarRelatorio(); } function gerarRelatorio() { const inicio = document.getElementById(‘rel-data-inicio’).value; const fim = document.getElementById(‘rel-data-fim’).value; const todasVendas = DB.get(‘vendas’); const pacientes = DB.get(‘pacientes’); const produtos = DB.get(‘produtos’); const medicos = DB.get(‘medicos’); const representantes = DB.get(‘representantes’); const vendas = todasVendas.filter(v => { if (inicio && v.data < inicio) return false; if (fim && v.data > fim) return false; return true; }); // Totais const totalVal = vendas.reduce((s, v) => s + Number(v.valorTotal || 0), 0); document.getElementById(‘rel-total-vendas’).textContent = formatMoney(totalVal); document.getElementById(‘rel-num-vendas’).textContent = vendas.length; document.getElementById(‘rel-ticket-medio’).textContent = vendas.length > 0 ? formatMoney(totalVal / vendas.length) : ‘R$ 0,00’; // Tabela detalhada const tbody = document.getElementById(‘tbody-relatorio’); tbody.innerHTML = vendas.length === 0 ? `<tr><td colspan=”8″ style=”text-align:center;padding:20px”>Nenhuma venda no período.</td></tr>` : vendas.map(v => `<tr> <td>${formatDate(v.data)}</td> <td>${getNomeById(pacientes, v.pacienteId)}</td> <td>${getNomeById(produtos, v.produtoId)}</td> <td>${v.quantidade}</td> <td>${formatMoney(v.valorTotal)}</td> <td>${v.pagamento}</td> <td>${getNomeById(medicos, v.medicoId)}</td> <td>${getNomeById(representantes, v.representanteId)}</td> </tr>`).join(”); // Gráfico por período (dias ou meses) const porData = {}; vendas.forEach(v => { const chave = v.data ? v.data.substr(0, 7) : ‘S/D’; porData[chave] = (porData[chave] || 0) + Number(v.valorTotal || 0); }); renderGrafico(‘grafico-periodo’, ‘bar’, Object.keys(porData), Object.values(porData), ‘Vendas por Mês (R$)’); // Gráfico por pagamento const porPag = {}; vendas.forEach(v => { porPag[v.pagamento] = (porPag[v.pagamento] || 0) + Number(v.valorTotal || 0); }); renderGrafico(‘grafico-pagamento’, ‘doughnut’, Object.keys(porPag), Object.values(porPag), ‘Por Pagamento’); // Gráfico por médico const porMed = {}; vendas.forEach(v => { const nome = getNomeById(medicos, v.medicoId) || ‘Sem médico’; porMed[nome] = (porMed[nome] || 0) + Number(v.valorTotal || 0); }); renderGrafico(‘grafico-medico’, ‘bar’, Object.keys(porMed), Object.values(porMed), ‘Por Médico (R$)’); // Gráfico por representante const porRep = {}; vendas.forEach(v => { const nome = getNomeById(representantes, v.representanteId) || ‘Sem representante’; porRep[nome] = (porRep[nome] || 0) + Number(v.valorTotal || 0);