fluxocash-pro / script.js
EraDigital's picture
corrigir projeção de fluxo de caixa
1492879 verified
// 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();
});