BPO Financeiro

Faça login para acessar

BPO Financeiro

Gestão Financeira Completa

Período Atual

Receita Total

R$ 0,00

Período atual

Despesa Total

R$ 0,00

Período atual

Saldo de Caixa

R$ 0,00

Atualizado

Transações

0

No período

Indicadores Críticos

Margem de Lucro

0%

Ticket Médio

R$ 0,00

Por atendimento

Custo Operacional

0%

Da receita

ROI Mensal

0%

Retorno

📊 Receitas por Serviço

Nenhuma receita registrada

💳 Receitas por Pagamento

Nenhuma receita registrada

const firebaseConfig = { apiKey: "AIzaSyAlnRgXm9hKhbLfjwjJQYBpYM1KtiwRN0Q", authDomain: "bpo-financeiro-befa3.firebaseapp.com", projectId: "bpo-financeiro-befa3", storageBucket: "bpo-financeiro-befa3.firebasestorage.app", messagingSenderId: "1057335178012", appId: "1:1057335178012:web:4908347362ebce0128c0d9" }; firebase.initializeApp(firebaseConfig); const auth = firebase.auth(); const db = firebase.firestore(); const transactionsCollection = db.collection('transactions'); let transactions = []; let currentTab = 'dashboard'; let deleteTargetId = null; let isLoading = false; let currentUser = null; const config = { clinic_name: 'BPO Financeiro', currency_symbol: 'R$', }; const loginScreen = document.getElementById('login-screen'); const appDiv = document.getElementById('app'); auth.onAuthStateChanged((user) => { if (user) { currentUser = user; loginScreen.classList.add('hidden'); // Esconde login appDiv.classList.remove('hidden'); // Mostra app loadTransactions(); // Carrega dados da nuvem } else { currentUser = null; loginScreen.classList.remove('hidden'); appDiv.classList.add('hidden'); // Oculta o app se não logado } }); document.getElementById('login-form').addEventListener('submit', (e) => { e.preventDefault(); const email = document.getElementById('login-email').value; const pass = document.getElementById('login-password').value; const errorMsg = document.getElementById('login-error'); auth.signInWithEmailAndPassword(email, pass) .catch((error) => { errorMsg.textContent = "Erro: Email ou senha incorretos."; errorMsg.classList.remove('hidden'); console.error(error); }); }); // --- FUNÇÕES DE BANCO DE DADOS (FIRESTORE) --- // Carregar Transações function loadTransactions() { // Pega apenas as transações do usuário logado (segurança) transactionsCollection .where("userId", "==", currentUser.uid) .orderBy("date", "desc") .onSnapshot((snapshot) => { transactions = snapshot.docs.map(doc => ({ __backendId: doc.id, ...doc.data() })); updateAllViews(); }, (error) => { console.error("Erro ao ler dados:", error); showToast("Erro ao carregar dados", "error"); }); } // Adicionar Transação async function addTransaction(e) { e.preventDefault(); if (isLoading) return; const submitBtn = document.getElementById('submit-btn'); submitBtn.textContent = 'Salvando...'; submitBtn.disabled = true; isLoading = true; const newTransaction = { userId: currentUser.uid, // Vincula ao usuário logado date: document.getElementById('trans-date').value, description: document.getElementById('trans-desc').value, type: document.getElementById('trans-type').value, category: document.getElementById('trans-category').value, subcategory: document.getElementById('trans-subcategory').value || '', value: parseFloat(document.getElementById('trans-value').value), paymentMethod: document.getElementById('trans-payment').value, installments: parseInt(document.getElementById('trans-installments')?.value || 1), createdAt: firebase.firestore.FieldValue.serverTimestamp() }; try { await transactionsCollection.add(newTransaction); document.getElementById('transaction-form').reset(); document.getElementById('trans-date').value = new Date().toISOString().split('T')[0]; showToast('Transação salva na nuvem!', 'success'); } catch (error) { console.error("Erro ao salvar:", error); showToast('Erro ao salvar. Verifique a internet.', 'error'); } submitBtn.textContent = 'Adicionar'; submitBtn.disabled = false; isLoading = false; } // Deletar Transação async function confirmDelete() { if (!deleteTargetId || isLoading) return; const confirmBtn = document.getElementById('confirm-delete-btn'); confirmBtn.textContent = 'Excluindo...'; confirmBtn.disabled = true; isLoading = true; try { await transactionsCollection.doc(deleteTargetId).delete(); showToast('Excluído com sucesso!', 'success'); } catch (error) { showToast('Erro ao excluir.', 'error'); } confirmBtn.textContent = 'Excluir'; confirmBtn.disabled = false; isLoading = false; closeDeleteModal(); } // --- FUNÇÕES DE INTERFACE (MANTIDAS DO ORIGINAL) --- // (Abaixo estão as mesmas funções de formatação e atualização de tela que você já tinha) const categories = { receita: { 'Consultas': ['Flavio', 'Vanessa', 'Luciana'], 'Vacinação': ['Vacina Influenza', 'Vacina Pneumocócica', 'Vacina Meningocócica', 'Vacina Hepatite B', 'Vacina HPV', 'Vacina Tétano', 'Vacina Febre Amarela', 'Outras Vacinas'], 'Procedimentos': ['Procedimento Simples', 'Procedimento Complexo', 'Pequena Cirurgia', 'Exame'], 'Exames': ['Laboratoriais', 'Imagem', 'Diagnóstico'], 'Outros Serviços': ['Atestados', 'Laudos', 'Segunda Via', 'Outros'] }, despesa: { 'Custos Operacionais': ['Material Médico', 'Material de Consumo', 'Medicamentos', 'EPIs', 'Vacinas'], 'Despesas com Pessoal': ['Salários', 'Encargos Trabalhistas', 'Benefícios', 'Pro-labore', 'Terceirizados', 'Férias', '13º Salário', 'Comissão'], 'Despesas Administrativas': ['Aluguel', 'Condomínio', 'IPTU', 'Energia', 'Água', 'Internet', 'Telefone', 'Refeições', 'Seguros'], 'Despesas Comerciais': ['Marketing', 'Publicidade', 'Sistema/Software', 'Manutenção Site'], 'Impostos e Taxas': ['ISS', 'IRPJ', 'CSLL', 'PIS/COFINS', 'Taxas Bancárias', 'Simples Nacional', 'Taxas Diversas', 'Taxa de Alvará Sanitário', 'Taxa de Localização e Funcionamento'], 'Outras Despesas': ['Manutenção', 'Equipamentos', 'Diversos'] } }; function formatCurrency(value, symbol = 'R$') { return `${symbol} ${value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } function formatDate(dateStr) { if(!dateStr) return ''; // Ajuste para lidar com string de data simples const parts = dateStr.split('-'); if(parts.length === 3) return `${parts[2]}/${parts[1]}/${parts[0]}`; return dateStr; } function getCurrentPeriod() { const now = new Date(); return now.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' }); } function switchTab(tab) { document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden')); document.querySelectorAll('.tab-btn').forEach(el => { el.classList.remove('tab-active'); el.classList.add('bg-slate-100', 'text-slate-600'); }); document.getElementById(`content-${tab}`).classList.remove('hidden'); const tabBtn = document.getElementById(`tab-${tab}`); tabBtn.classList.add('tab-active'); tabBtn.classList.remove('bg-slate-100', 'text-slate-600'); currentTab = tab; updateCurrentTabData(); } function updateCategories() { const type = document.getElementById('trans-type').value; const categorySelect = document.getElementById('trans-category'); const subcategorySelect = document.getElementById('trans-subcategory'); categorySelect.innerHTML = ''; subcategorySelect.innerHTML = ''; if (type && categories[type]) { Object.keys(categories[type]).forEach(cat => { const option = document.createElement('option'); option.value = cat; option.textContent = cat; categorySelect.appendChild(option); }); } } function updateSubcategories() { const type = document.getElementById('trans-type').value; const category = document.getElementById('trans-category').value; const subcategorySelect = document.getElementById('trans-subcategory'); subcategorySelect.innerHTML = ''; if (type && category && categories[type][category]) { categories[type][category].forEach(sub => { const option = document.createElement('option'); option.value = sub; option.textContent = sub; subcategorySelect.appendChild(option); }); } } function updateInstallmentsVisibility() { const paymentMethod = document.getElementById('trans-payment').value; const container = document.getElementById('installments-container'); if (paymentMethod === 'Cartão Crédito' || paymentMethod === 'Boleto') { container.classList.remove('hidden'); } else { container.classList.add('hidden'); document.getElementById('trans-installments').value = '1'; } } function showDeleteModal(id) { deleteTargetId = id; document.getElementById('delete-modal').classList.remove('hidden'); document.getElementById('delete-modal').classList.add('flex'); } function closeDeleteModal() { deleteTargetId = null; document.getElementById('delete-modal').classList.add('hidden'); document.getElementById('delete-modal').classList.remove('flex'); } function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-xl text-white font-medium shadow-lg z-50 transition-all transform translate-y-0 ${type === 'success' ? 'bg-emerald-500' : 'bg-red-500'}`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('translate-y-full', 'opacity-0'); setTimeout(() => toast.remove(), 300); }, 3000); } function filterByMonthYear(data, month = 'all', year = null) { return data.filter(t => { const transDate = new Date(t.date + 'T00:00:00'); const transYear = transDate.getFullYear(); const transMonth = String(transDate.getMonth() + 1).padStart(2, '0'); let yearMatch = true; if (year !== null) yearMatch = transYear === parseInt(year); let monthMatch = true; if (month !== 'all') monthMatch = transMonth === month; return yearMatch && monthMatch; }); } function calculateRunningBalance(data) { let balance = 0; // Ordena por data antes de calcular const sorted = [...data].sort((a, b) => new Date(a.date) - new Date(b.date)); return sorted.map(t => { balance += t.type === 'receita' ? t.value : -t.value; return { ...t, balance }; }); } // --- FUNÇÕES DE UPDATE (Copiadas e adaptadas para rodar sem o SDK) --- function updateDashboard() { const month = document.getElementById('dashboard-month')?.value || 'all'; const year = document.getElementById('dashboard-year')?.value; const filtered = filterByMonthYear(transactions, month, year); const revenues = filtered.filter(t => t.type === 'receita'); const expenses = filtered.filter(t => t.type === 'despesa'); const totalRevenue = revenues.reduce((sum, t) => sum + t.value, 0); const totalExpense = expenses.reduce((sum, t) => sum + t.value, 0); const balance = totalRevenue - totalExpense; document.getElementById('total-revenue').textContent = formatCurrency(totalRevenue); document.getElementById('total-expense').textContent = formatCurrency(totalExpense); document.getElementById('cash-balance').textContent = formatCurrency(balance); document.getElementById('total-transactions').textContent = filtered.length; // Critical indicators const profitMargin = totalRevenue > 0 ? ((totalRevenue - totalExpense) / totalRevenue * 100) : 0; const avgTicket = revenues.length > 0 ? totalRevenue / revenues.length : 0; const operationalCost = totalRevenue > 0 ? (totalExpense / totalRevenue * 100) : 0; const monthlyROI = totalExpense > 0 ? ((totalRevenue - totalExpense) / totalExpense * 100) : 0; document.getElementById('profit-margin').textContent = `${profitMargin.toFixed(1)}%`; document.getElementById('profit-margin-bar').style.width = `${Math.min(Math.max(profitMargin, 0), 100)}%`; document.getElementById('avg-ticket').textContent = formatCurrency(avgTicket); document.getElementById('operational-cost').textContent = `${operationalCost.toFixed(1)}%`; document.getElementById('monthly-roi').textContent = `${monthlyROI.toFixed(1)}%`; updateServiceChart(revenues); updatePaymentChart(revenues); } function updateServiceChart(revenues) { const byService = {}; revenues.forEach(t => { const cat = t.category || 'Outros'; byService[cat] = (byService[cat] || 0) + t.value; }); const container = document.getElementById('revenue-by-service-chart'); const noData = document.getElementById('no-revenue-service'); if (Object.keys(byService).length === 0) { container.innerHTML = ''; noData.style.display = 'block'; return; } noData.style.display = 'none'; const total = Object.values(byService).reduce((a, b) => a + b, 0); const colors = ['#059669', '#0891b2', '#7c3aed', '#db2777', '#ea580c']; container.innerHTML = Object.entries(byService) .sort((a, b) => b[1] - a[1]) .map(([key, value], i) => { const percent = (value / total * 100).toFixed(1); return `
${key}${percent}%
${formatCurrency(value)}
`; }).join(''); } function updatePaymentChart(revenues) { const byPayment = {}; revenues.forEach(t => { const method = t.paymentMethod || 'Outros'; byPayment[method] = (byPayment[method] || 0) + t.value; }); const container = document.getElementById('revenue-by-payment-chart'); const noData = document.getElementById('no-revenue-payment'); if (Object.keys(byPayment).length === 0) { container.innerHTML = ''; noData.style.display = 'block'; return; } noData.style.display = 'none'; const total = Object.values(byPayment).reduce((a, b) => a + b, 0); const colors = ['#14b8a6', '#8b5cf6', '#f59e0b', '#ef4444', '#3b82f6', '#ec4899', '#10b981']; container.innerHTML = Object.entries(byPayment) .sort((a, b) => b[1] - a[1]) .map(([key, value], i) => { const percent = (value / total * 100).toFixed(1); return `
${key}${percent}%
${formatCurrency(value)}
`; }).join(''); } function updateCashflowTable() { const filterMonth = document.getElementById('filter-month').value; let filtered = [...transactions]; if (filterMonth) { const [year, month] = filterMonth.split('-'); filtered = filtered.filter(t => { const date = new Date(t.date + 'T00:00:00'); return date.getFullYear() === parseInt(year) && date.getMonth() === parseInt(month) - 1; }); } const withBalance = calculateRunningBalance(filtered).reverse(); const tbody = document.getElementById('cashflow-table'); const noDataRow = document.getElementById('no-data-row'); document.getElementById('record-count').textContent = `${filtered.length} registros`; const existingRows = tbody.querySelectorAll('tr:not(#no-data-row)'); existingRows.forEach(row => row.remove()); if (withBalance.length === 0) { noDataRow.style.display = ''; return; } noDataRow.style.display = 'none'; withBalance.forEach(t => { const row = document.createElement('tr'); row.className = 'hover:bg-slate-50 transition-colors'; row.innerHTML = `${formatDate(t.date)}${t.description}${t.type === 'receita' ? '📈 Receita' : '📉 Despesa'}${t.category}${t.subcategory ? ' → ' + t.subcategory : ''}${t.type === 'receita' ? '+' : '-'} ${formatCurrency(t.value)}${t.paymentMethod}${t.installments || 1}x${formatCurrency(t.balance)}`; tbody.appendChild(row); }); } // (Omiti as funções updateExpenses, updateRevenues, updateDRE e updateIndicators por brevidade, // MAS ELAS DEVEM SER MANTIDAS NO CÓDIGO - apenas certifique-se de que elas usam a variável 'transactions' global // e chamam formatCurrency sem passar o config.currency_symbol se ele for padrão 'R$') // ... Certifique-se de manter as funções de renderização do DRE e Indicadores aqui ... function updateAllViews() { updateDashboard(); updateCashflowTable(); updateExpenses(); // Certifique-se que essa função existe no seu código original updateRevenues(); // Certifique-se que essa função existe no seu código original updateDRE(); // Certifique-se que essa função existe no seu código original updateIndicators(); // Certifique-se que essa função existe no seu código original } function updateCurrentTabData() { switch (currentTab) { case 'dashboard': updateDashboard(); break; case 'cashflow': updateCashflowTable(); break; case 'expenses': updateExpenses(); break; case 'revenues': updateRevenues(); break; case 'dre': updateDRE(); break; case 'indicators': updateIndicators(); break; } } // Inicialização function init() { document.getElementById('trans-date').value = new Date().toISOString().split('T')[0]; document.getElementById('current-period').textContent = getCurrentPeriod(); document.getElementById('filter-month').value = new Date().toISOString().slice(0, 7); const currentYear = new Date().getFullYear(); ['dashboard-year', 'expense-year', 'revenue-year', 'indicators-year', 'dre-year'].forEach(id => { const yearSelect = document.getElementById(id); if (yearSelect) { for (let i = currentYear - 5; i <= currentYear + 1; i++) { const option = document.createElement('option'); option.value = i; option.textContent = i; if (i === currentYear) option.selected = true; yearSelect.appendChild(option); } } }); document.getElementById('dre-month').value = 'all'; // Listeners document.getElementById('trans-type').addEventListener('change', updateCategories); document.getElementById('trans-category').addEventListener('change', updateSubcategories); document.getElementById('trans-payment').addEventListener('change', updateInstallmentsVisibility); document.getElementById('transaction-form').addEventListener('submit', addTransaction); document.getElementById('filter-month').addEventListener('change', updateCashflowTable); // Filtros ['dashboard', 'expense', 'revenue', 'indicators'].forEach(prefix => { document.getElementById(`${prefix}-month`)?.addEventListener('change', updateCurrentTabData); document.getElementById(`${prefix}-year`)?.addEventListener('change', updateCurrentTabData); }); document.getElementById('dre-month')?.addEventListener('change', updateDRE); document.getElementById('dre-year')?.addEventListener('change', updateDRE); } init();