Spaces:
Running
Running
| // FluxoCash Pro - Financial Analysis Script | |
| // Financial data structure | |
| const financialData = { | |
| months: ['jan/25', 'fev/25', 'mar/25', 'abr/25', 'mai/25', 'jun/25', 'jul/25', 'ago/25', 'set/25', 'out/25', 'nov/25', 'dez/25'], | |
| openingBalance: [57765, 52114, 50371, 46703, 42816, 42529, 44457, 36478, 25195, 27204, 27102, 17611], | |
| totalRevenue: [32757, 36279, 37684, 34902, 34148, 42104, 33888, 44910, 46361, 49334, 51009, 47416], | |
| revenueBreakdown: { | |
| traffic: [22815, 23843, 25170, 19145, 23370, 32961, 27074, 34795, 30949, 39539, 37828, 38857], | |
| webDesign: [7476, 11217, 11324, 14952, 9151, 8596, 5985, 8236, 8135, 7985, 9165, 6165], | |
| services: [0, 497, 497, 0, 1216, 547, 829, 714, 5490, 1547, 2627, 2394], | |
| hosting: [0, 250, 250, 0, 0, 0, 0, 0, 0, 0, 222, 0], | |
| financial: [2466, 473, 0, 805, 412, 0, 0, 0, 1787, 263, 0, 0], | |
| other: [0, 0, 444, 0, 0, 0, 0, 1166, 0, 0, 1168, 0] | |
| }, | |
| totalExpenses: [38408, 38022, 41353, 38789, 38435, 46177, 43866, 44193, 50352, 51320, 52617, 50153], | |
| variableCosts: [5444, 8391, 9961, 9016, 9808, 13338, 10839, 11495, 13648, 13959, 11391, 10939], | |
| fixedCosts: [10559, 7062, 11989, 10367, 9224, 11385, 11627, 11298, 15675, 14320, 18201, 16247], | |
| plr: [19396, 19396, 19396, 19396, 19396, 21396, 21396, 21396, 20964, 22964, 22964, 22964], | |
| operationalProfit: [14289, 20353, 15290, 14714, 14704, 17381, 11422, 20951, 15252, 20792, 20249, 20231], | |
| netCashFlow: [-5651, -1743, -3668, -3887, -4286, -4073, -9978, 717, -3991, -1985, -1608, -2737], | |
| closingBalance: [52114, 50371, 46703, 42816, 42529, 44457, 36478, 25195, 27204, 27102, 27611, 14874] | |
| }; | |
| // Utility functions | |
| function formatCurrency(value) { | |
| return new Intl.NumberFormat('pt-BR', { | |
| style: 'currency', | |
| currency: 'BRL' | |
| }).format(value); | |
| } | |
| function formatPercentage(value) { | |
| return `${(value * 100).toFixed(1)}%`; | |
| } | |
| function getCurrentMonthIndex() { | |
| return 10; // November (nov/25) | |
| } | |
| // Calculate KPIs | |
| function calculateKPIs() { | |
| const currentMonth = getCurrentMonthIndex(); | |
| // Total Revenue | |
| const totalRevenue = financialData.totalRevenue[currentMonth]; | |
| document.getElementById('total-revenue').textContent = formatCurrency(totalRevenue); | |
| // Total Expenses | |
| const totalExpenses = financialData.totalExpenses[currentMonth]; | |
| document.getElementById('total-expenses').textContent = formatCurrency(totalExpenses); | |
| // Net Profit | |
| const netProfit = financialData.operationalProfit[currentMonth] - financialData.plr[currentMonth]; | |
| document.getElementById('net-profit').textContent = formatCurrency(netProfit); | |
| // Contribution Margin | |
| const contributionMargin = (financialData.totalRevenue[currentMonth] - financialData.variableCosts[currentMonth]) / financialData.totalRevenue[currentMonth]; | |
| document.getElementById('contribution-margin').textContent = formatPercentage(contributionMargin); | |
| // PLR | |
| const totalPLR = financialData.plr.reduce((sum, value) => sum + value, 0); | |
| document.getElementById('total-plr').textContent = formatCurrency(totalPLR); | |
| const plrPercentage = financialData.plr[currentMonth] / financialData.operationalProfit[currentMonth]; | |
| document.getElementById('plr-percentage').textContent = formatPercentage(plrPercentage); | |
| } | |
| // Create charts | |
| function createCashFlowChart() { | |
| const ctx = document.getElementById('cashFlowChart').getContext('2d'); | |
| new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: financialData.months, | |
| datasets: [{ | |
| label: 'Saldo Final', | |
| data: financialData.closingBalance, | |
| borderColor: 'rgb(59, 130, 246)', | |
| backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
| tension: 0.4, | |
| fill: true | |
| }, { | |
| label: 'Fluxo de Caixa', | |
| data: financialData.netCashFlow, | |
| borderColor: 'rgb(16, 185, 129)', | |
| backgroundColor: 'rgba(16, 185, 129, 0.1)', | |
| tension: 0.4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'top', | |
| }, | |
| title: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: false, | |
| ticks: { | |
| callback: function(value) { | |
| return formatCurrency(value); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| function createRevenueCompositionChart() { | |
| const ctx = document.getElementById('revenueCompositionChart').getContext('2d'); | |
| const currentMonth = getCurrentMonthIndex(); | |
| const data = { | |
| labels: ['Tráfego', 'Web Design', 'Serviços', 'Hospedagem', 'Financeiras', 'Outras'], | |
| datasets: [{ | |
| data: [ | |
| financialData.revenueBreakdown.traffic[currentMonth], | |
| financialData.revenueBreakdown.webDesign[currentMonth], | |
| financialData.revenueBreakdown.services[currentMonth], | |
| financialData.revenueBreakdown.hosting[currentMonth], | |
| financialData.revenueBreakdown.financial[currentMonth], | |
| financialData.revenueBreakdown.other[currentMonth] | |
| ], | |
| backgroundColor: [ | |
| 'rgba(59, 130, 246, 0.8)', | |
| 'rgba(16, 185, 129, 0.8)', | |
| 'rgba(245, 158, 11, 0.8)', | |
| 'rgba(139, 92, 246, 0.8)', | |
| 'rgba(236, 72, 153, 0.8)', | |
| 'rgba(107, 114, 128, 0.8)' | |
| ] | |
| }] | |
| }; | |
| new Chart(ctx, { | |
| type: 'doughnut', | |
| data: data, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'bottom', | |
| }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| const label = context.label || ''; | |
| const value = context.parsed || 0; | |
| const total = context.dataset.data.reduce((a, b) => a + b, 0); | |
| const percentage = ((value / total) * 100).toFixed(1); | |
| return `${label}: ${formatCurrency(value)} (${percentage}%)`; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Populate tables and breakdowns | |
| function populateRevenueDetails() { | |
| const currentMonth = getCurrentMonthIndex(); | |
| const tbody = document.getElementById('revenue-details'); | |
| const totalRevenue = financialData.totalRevenue[currentMonth]; | |
| const revenueItems = [ | |
| { name: 'Receita de Tráfego', value: financialData.revenueBreakdown.traffic[currentMonth] }, | |
| { name: 'Receita de Web Design', value: financialData.revenueBreakdown.webDesign[currentMonth] }, | |
| { name: 'Receita de Serviços', value: financialData.revenueBreakdown.services[currentMonth] }, | |
| { name: 'Receita de Hospedagem', value: financialData.revenueBreakdown.hosting[currentMonth] }, | |
| { name: 'Receitas Financeiras', value: financialData.revenueBreakdown.financial[currentMonth] }, | |
| { name: 'Outras Receitas', value: financialData.revenueBreakdown.other[currentMonth] } | |
| ]; | |
| tbody.innerHTML = revenueItems.map(item => ` | |
| <tr class="table-hover"> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100"> | |
| ${item.name} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> | |
| ${formatCurrency(item.value)} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"> | |
| ${formatPercentage(item.value / totalRevenue)} | |
| </td> | |
| </tr> | |
| `).join(''); | |
| } | |
| function populateExpenseBreakdown() { | |
| const currentMonth = getCurrentMonthIndex(); | |
| const container = document.getElementById('expense-breakdown'); | |
| const expenseCategories = [ | |
| { name: 'Impostos', value: -4813, icon: 'file-text', color: 'red' }, | |
| { name: 'Custos Variáveis', value: -11391, icon: 'package', color: 'orange' }, | |
| { name: 'Salários e Encargos', value: -11294, icon: 'users', color: 'blue' }, | |
| { name: 'Despesas Administrativas', value: -5827, icon: 'settings', color: 'gray' }, | |
| { name: 'Despesas Comerciais', value: -893, icon: 'shopping-cart', color: 'purple' }, | |
| { name: 'PLR', value: -22964, icon: 'gift', color: 'pink' } | |
| ]; | |
| container.innerHTML = expenseCategories.map(category => ` | |
| <div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg"> | |
| <div class="flex items-center space-x-3"> | |
| <div class="p-2 bg-${category.color}-100 dark:bg-${category.color}-900 rounded-lg"> | |
| <i data-feather="${category.icon}" class="text-${category.color}-600 w-5 h-5"></i> | |
| </div> | |
| <span class="font-medium text-gray-700 dark:text-gray-300">${category.name}</span> | |
| </div> | |
| <span class="font-bold text-gray-900 dark:text-gray-100">${formatCurrency(category.value)}</span> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| } | |
| function calculateProjections() { | |
| const currentMonth = getCurrentMonthIndex(); | |
| const lastThreeMonths = financialData.netCashFlow.slice(currentMonth - 2, currentMonth + 1); | |
| const averageCashFlow = lastThreeMonths.reduce((sum, value) => sum + value, 0) / 3; | |
| // Base projection: closing balance + average cash flow | |
| const baseProjection = financialData.closingBalance[currentMonth] + averageCashFlow; | |
| // Optimistic scenario: +15% revenue, -5% expenses | |
| const optimistic = baseProjection * 1.15; | |
| document.getElementById('optimistic-scenario').textContent = formatCurrency(optimistic); | |
| // Realistic scenario: base projection | |
| document.getElementById('realistic-scenario').textContent = formatCurrency(baseProjection); | |
| // Conservative scenario: -10% revenue, +5% expenses | |
| const conservative = baseProjection * 0.85; | |
| document.getElementById('conservative-scenario').textContent = formatCurrency(conservative); | |
| } | |
| function generateAlertsAndRecommendations() { | |
| const currentMonth = getCurrentMonthIndex(); | |
| const container = document.getElementById('alerts-recommendations'); | |
| const alerts = []; | |
| // Check cash flow trend | |
| const lastThreeMonthsFlow = financialData.netCashFlow.slice(currentMonth - 2, currentMonth + 1); | |
| const negativeFlowCount = lastThreeMonthsFlow.filter(flow => flow < 0).length; | |
| if (negativeFlowCount >= 2) { | |
| alerts.push({ | |
| type: 'danger', | |
| icon: 'alert-alert-triangle', | |
| title: 'Atenção: Fluxo de Caixa Negativo', | |
| message: 'Nos últimos 3 meses, tivemos fluxo negativo em 2 períodos. Recomenda-se revisar despesas e acelerar recebíveis.' | |
| }); | |
| } | |
| // Check PLR percentage | |
| const plrPercentage = financialData.plr[currentMonth] / financialData.operationalProfit[currentMonth]; | |
| if (plrPercentage > 0.8) { | |
| alerts.push({ | |
| type: 'warning', | |
| icon: 'percent', | |
| title: 'PLR Elevada', | |
| message: 'A PLR está consumindo mais de 80% do lucro operacional. Considere ajustar a política de distribuição.' | |
| }); | |
| } | |
| // Check cash balance | |
| const currentBalance = financialData.closingBalance[currentMonth]; | |
| if (currentBalance < 20000) { | |
| alerts.push({ | |
| type: 'warning', | |
| icon: 'dollar-sign', | |
| title: 'Saldo de Caixa Baixo', | |
| message: 'O saldo atual está abaixo de R$ 20.000. Mantenha uma reserva de segurança equivalente a 2 meses de despesas.' | |
| }); | |
| } | |
| // Positive alerts | |
| if (financialData.totalRevenue[currentMonth] > financialData.totalRevenue[currentMonth - 1]) { | |
| alerts.push({ | |
| type: 'success', | |
| icon: 'trending-up', | |
| title: 'Crescimento de Receita', | |
| message: 'As receitas cresceram em relação ao mês anterior. Continue focando nas fontes principais de receita.' | |
| }); | |
| } | |
| container.innerHTML = alerts.map(alert => ` | |
| <div class="alert-card border-${alert.type === 'success' ? 'green' : alert.type === 'warning' ? 'yellow' : 'red'}-500"> | |
| <div class="flex items-start space-x-3"> | |
| <i data-feather="${alert.icon}" class="text-${alert.type === 'success' ? 'green' : alert.type === 'warning' ? 'yellow' : 'red'}-500 w-5 h-5 mt-0.5"></i> | |
| <div> | |
| <h3 class="font-semibold text-gray-800 dark:text-gray-200">${alert.title}</h3> | |
| <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${alert.message}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| } | |
| // Initialize dashboard | |
| function initializeDashboard() { | |
| calculateKPIs(); | |
| createCashFlowChart(); | |
| createRevenueCompositionChart(); | |
| populateRevenueDetails(); | |
| populateExpenseBreakdown(); | |
| calculateProjections(); | |
| generateAlertsAndRecommendations(); | |
| } | |
| // Dark mode toggle | |
| function setupDarkMode() { | |
| const darkModeToggle = document.createElement('button'); | |
| darkModeToggle.innerHTML = '<i data-feather="moon"></i>'; | |
| darkModeToggle.className = 'fixed top-4 right-4 p-2 bg-gray-200 dark:bg-gray-700 rounded-lg shadow-lg'; | |
| darkModeToggle.addEventListener('click', () => { | |
| document.documentElement.classList.toggle('dark'); | |
| darkModeToggle.innerHTML = document.documentElement.classList.contains('dark') ? | |
| '<i data-feather="sun"></i>' : '<i data-feather="moon"></i>'; | |
| feather.replace(); | |
| }); | |
| document.body.appendChild(darkModeToggle); | |
| } | |
| // Document ready | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initializeDashboard(); | |
| setupDarkMode(); | |
| }); |