1. pages/Simulator.jsx
import React, { useState, useMemo } from 'react';
import { motion } from 'framer-motion';
import {
TrendingUp,
TrendingDown,
Target,
RefreshCcw,
Layers,
Activity,
BarChart3,
Sun,
Moon
} from 'lucide-react';
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { calculateScenario, BASE_VALUES } from '../components/simulator/SimulatorEngine';
import LeverSlider from '../components/simulator/LeverSlider';
import WaterfallChart from '../components/simulator/WaterfallChart';
import SensitivityChart from '../components/simulator/SensitivityChart';
import PLTable from '../components/simulator/PLTable';
export default function Simulator() {
const [inputs, setInputs] = useState({
perfil_div_jur: 0,
vd_ativos: 0,
red_estoque: 0,
recup_multas: 0,
red_prc_mant: 0,
rec_precos: 0,
disp_prod: 0,
aj_manut: 0,
aj_subloc: 0,
vendas_aquisicoes: 0
});
const [darkMode, setDarkMode] = useState(true);
const scenario = useMemo(() => calculateScenario(inputs), [inputs]);
const handleInputChange = (key, value) => {
setInputs(prev => ({ ...prev, [key]: value }));
};
const resetInputs = () => {
setInputs({
perfil_div_jur: 0,
vd_ativos: 0,
red_estoque: 0,
recup_multas: 0,
red_prc_mant: 0,
rec_precos: 0,
disp_prod: 0,
aj_manut: 0,
aj_subloc: 0,
vendas_aquisicoes: 0
});
};
const formatCurrency = (val) => {
const absVal = Math.abs(val);
if (absVal >= 1000000000) {
return `R$ ${(val / 1000000000).toFixed(2)}B`;
}
if (absVal >= 1000000) {
return `R$ ${(val / 1000000).toFixed(1)}M`;
}
return `R$ ${(val / 1000).toFixed(0)}K`;
};
const levers = [
{ key: 'perfil_div_jur', label: 'Perfil de Dívida e Juros', min: -5, max: 0, step: 0.1, unit: '%', color: 'blue' },
{ key: 'vd_ativos', label: 'Venda de Ativos', min: 0, max: 200000000, step: 5000000, unit: 'R$', color: 'green' },
{ key: 'red_estoque', label: 'Redução de Estoque', min: 0, max: 50000000, step: 1000000, unit: 'R$', color: 'green' },
{ key: 'recup_multas', label: 'Recuperação de Multas', min: 0, max: 10, step: 0.5, unit: '%', color: 'purple' },
{ key: 'red_prc_mant', label: 'Redução Preço Manutenção', min: 0, max: 20, step: 0.5, unit: '%', color: 'orange' },
{ key: 'rec_precos', label: 'Recuperação de Preços', min: 0, max: 20, step: 0.5, unit: '%', color: 'purple' },
{ key: 'disp_prod', label: 'Disponibilidade/Produtividade', min: 0, max: 20, step: 0.5, unit: '%', color: 'blue' },
{ key: 'aj_manut', label: 'Ajuste de Manutenção', min: 0, max: 20, step: 0.5, unit: '%', color: 'orange' },
{ key: 'aj_subloc', label: 'Redução de Sublocação', min: 0, max: 100, step: 1, unit: '%', color: 'pink' },
{ key: 'vendas_aquisicoes', label: 'Vendas/Aquisições', min: 0, max: 30, step: 0.5, unit: '%', color: 'green' }
];
const potencialCrescimento = scenario.variacao_ebitda_pct;
return (
{/* Header */}
{/* Logo + Título */}
Simulador de Impacto
Análise de Melhorias Operacionais
{/* Potencial de Melhoria - Centralizado */}
{formatCurrency(scenario.variacao_ebitda)}
({formatCurrency(scenario.variacao_ebitda / 12)}/mês)
em Potencial de Melhoria no EBITDA
Ajuste as alavancas para simular cenários.
{/* Botões */}
{/* KPI Cards */}
{/* EBITDA Base */}
{formatCurrency(BASE_VALUES.ebitda)}
EBITDA Atual/Ano
{/* Potencial de Crescimento */}
{potencialCrescimento > 0 ? `${potencialCrescimento.toFixed(0)}%` : '0%'}
Potencial de Crescimento
{/* EBITDA Potencial */}
{formatCurrency(scenario.ebitda)}
EBITDA Potencial/Ano
{/* Tabs */}
Simulador
Sensibilidade
DRE Detalhado
{/* Alavancas */}
Ajuste os
Parâmetros
- Cada alavanca representa uma oportunidade
{levers.map((lever) => (
handleInputChange(lever.key, val)}
min={lever.min}
max={lever.max}
step={lever.step}
unit={lever.unit}
color={lever.color}
/>
))}
{/* Waterfall Chart */}
Como interpretar
A análise de sensibilidade mostra o impacto que
1% de melhoria
em cada alavanca tem no EBITDA final.
💡 Principais insights:
-
•
Recuperação de Preços
e
Disponibilidade
têm maior impacto
-
• Alavancas de custo (manutenção, sublocações) têm efeito direto
-
• Combine múltiplas alavancas para resultados expressivos
⚠️ Atenção
Os valores são aproximações baseadas na estrutura da planilha.
Ajuste conforme a realidade do negócio.
);
}
2. components/simulator/SimulatorEngine.jsx
// Motor de cálculo do simulador baseado na planilha "Simula Final"
// Estrutura de DRE com cascata de efeitos por alavanca
export const BASE_VALUES = {
// Linha 1-2: Dívida e Juros
divida: 866000000, // B1
juros_base: 0.17, // B2 = 17%
// Linhas 6-11: DRE Operacional (valores base em B)
receita: 400000000, // B6
impostos: -31000000, // B7
mdo: -52000000, // B8
manutencao: -90000000, // B9 (90M de manutenção)
sublocacoes: -16000000, // B10
ipva_fretes: -9000000, // B11
// Linha 12: Ganho (soma B6:B11) = 400-31-52-90-16-9 = 202M
ganho: 202000000, // B12
// Linhas 14-16: Despesas fixas/admin
do_locais: -17000000, // B14
mdo_admin: -10000000, // B15
outr_desp_admin: -10000000, // B16
// Linha 17: Custo financeiro = -Dívida * Taxa = -866M * 17% = -147.22M
c_fin_medio: -147220000, // B17
// Linha 18: Amortização
amortizacao_base: 130000000, // B18
// Linha 20: Geração Operacional = 202-17-10-10-147.22 = 17.78M
geracao_operacional: 17780000,
// Linha 21: EBITDA = 17.78 + 130 = 147.78M (valor calculado)
ebitda: 147780000
};
/**
* Calcula cenário aplicando as alavancas conforme estrutura da planilha
*/
export function calculateScenario(inputs) {
const {
perfil_div_jur = 0,
vd_ativos = 0,
red_estoque = 0,
recup_multas = 0,
red_prc_mant = 0,
rec_precos = 0,
disp_prod = 0,
aj_manut = 0,
aj_subloc = 0,
vendas_aquisicoes = 0
} = inputs;
// ETAPA 1: Perfil de Dívida/Juros
const nova_taxa = BASE_VALUES.juros_base + (perfil_div_jur / 100);
let c_fin_medio = (BASE_VALUES.c_fin_medio / BASE_VALUES.juros_base) * nova_taxa;
let divida = BASE_VALUES.divida;
// ETAPA 2: Venda de Ativos
divida -= vd_ativos;
let amortizacao = BASE_VALUES.amortizacao_base;
if (vd_ativos > 0) {
const reducao_juros_vd = vd_ativos * nova_taxa;
c_fin_medio += reducao_juros_vd;
amortizacao = 100000000;
}
// ETAPA 3: Redução de Estoque
divida -= red_estoque;
if (red_estoque > 0) {
const reducao_juros_est = red_estoque * nova_taxa;
c_fin_medio += reducao_juros_est;
}
// ETAPA 4: Recuperação de Multas
let receita = BASE_VALUES.receita;
let impostos = BASE_VALUES.impostos;
if (recup_multas > 0) {
const fator_multas = 1 + (recup_multas / 100);
receita = receita * fator_multas;
impostos = impostos * fator_multas;
}
// ETAPA 5: Redução Preço Manutenção
let manutencao = BASE_VALUES.manutencao;
if (red_prc_mant > 0) {
manutencao = manutencao * (1 - red_prc_mant / 100);
}
// ETAPA 6: Recuperação de Preços
if (rec_precos > 0) {
const fator_precos = 1 + (rec_precos / 100);
receita = receita * fator_precos;
impostos = impostos * fator_precos;
}
// ETAPA 7: Disponibilidade/Produtividade
let mdo = BASE_VALUES.mdo;
if (disp_prod > 0) {
const fator_disp = 1 + (disp_prod / 100);
receita = receita * fator_disp;
impostos = impostos * fator_disp;
mdo = mdo * fator_disp;
}
// ETAPA 8: Ajuste Manutenção (AUMENTA custo)
if (aj_manut > 0) {
manutencao = manutencao * (1 + aj_manut / 100);
}
// ETAPA 9: Ajuste Sublocação
let sublocacoes = BASE_VALUES.sublocacoes;
if (aj_subloc > 0) {
sublocacoes = sublocacoes * (1 - aj_subloc / 100);
}
// ETAPA 10: Vendas/Aquisições
if (vendas_aquisicoes > 0) {
const fator_vendas = 1 + (vendas_aquisicoes / 100);
receita = receita * fator_vendas;
impostos = impostos * fator_vendas;
mdo = mdo * fator_vendas;
manutencao = manutencao * fator_vendas;
sublocacoes = sublocacoes * fator_vendas;
}
// CÁLCULOS FINAIS
const ipva_fretes = BASE_VALUES.ipva_fretes;
const ganho = receita + impostos + mdo + manutencao + sublocacoes + ipva_fretes;
const do_locais = BASE_VALUES.do_locais;
const mdo_admin = BASE_VALUES.mdo_admin;
const outr_desp_admin = BASE_VALUES.outr_desp_admin;
const dividaAlterada = vd_ativos > 0 || red_estoque > 0;
const taxaAlterada = perfil_div_jur !== 0;
if (dividaAlterada || taxaAlterada) {
c_fin_medio = -(divida * nova_taxa);
} else {
c_fin_medio = BASE_VALUES.c_fin_medio;
}
const reducao_juros_ativos = (vd_ativos + red_estoque) * nova_taxa;
const geracao_operacional = ganho + do_locais + mdo_admin + outr_desp_admin + c_fin_medio;
const ebitda = geracao_operacional + amortizacao;
const todosZerados = perfil_div_jur === 0 && vd_ativos === 0 && red_estoque === 0 &&
recup_multas === 0 && red_prc_mant === 0 && rec_precos === 0 &&
disp_prod === 0 && aj_manut === 0 && aj_subloc === 0 && vendas_aquisicoes === 0;
const variacao_ebitda = ebitda - BASE_VALUES.ebitda;
const variacao_ebitda_pct = (ebitda / BASE_VALUES.ebitda - 1) * 100;
const variacao_geracao = geracao_operacional - BASE_VALUES.geracao_operacional;
const variacao_ganho = ganho - BASE_VALUES.ganho;
if (todosZerados) {
return {
divida: BASE_VALUES.divida,
nova_taxa: BASE_VALUES.juros_base,
receita: BASE_VALUES.receita,
impostos: BASE_VALUES.impostos,
mdo: BASE_VALUES.mdo,
manutencao: BASE_VALUES.manutencao,
sublocacoes: BASE_VALUES.sublocacoes,
ipva_fretes: BASE_VALUES.ipva_fretes,
ganho: BASE_VALUES.ganho,
do_locais: BASE_VALUES.do_locais,
mdo_admin: BASE_VALUES.mdo_admin,
outr_desp_admin: BASE_VALUES.outr_desp_admin,
c_fin_medio: BASE_VALUES.c_fin_medio,
reducao_juros_ativos: 0,
geracao_operacional: BASE_VALUES.geracao_operacional,
amortizacao: BASE_VALUES.amortizacao_base,
ebitda: BASE_VALUES.ebitda,
variacao_ebitda: 0,
variacao_ebitda_pct: 0,
variacao_geracao: 0,
variacao_ganho: 0
};
}
return {
divida, nova_taxa, receita, impostos, mdo, manutencao, sublocacoes, ipva_fretes, ganho,
do_locais, mdo_admin, outr_desp_admin, c_fin_medio, reducao_juros_ativos,
geracao_operacional, amortizacao, ebitda,
variacao_ebitda, variacao_ebitda_pct, variacao_geracao, variacao_ganho
};
}
export function calculateSensitivity() {
const levers = [
{ key: 'perfil_div_jur', name: 'Perfil de Dívida e Juros', unit: '%', step: -1, description: '-1pp na taxa', impacto_direto: BASE_VALUES.divida * 0.01 },
{ key: 'vd_ativos', name: 'Venda de Ativos', unit: 'R$', step: 10000000, description: 'R$10M vendidos', impacto_direto: 10000000 * BASE_VALUES.juros_base },
{ key: 'red_estoque', name: 'Redução de Estoque', unit: 'R$', step: 1000000, description: 'R$1M reduzido', impacto_direto: 1000000 * BASE_VALUES.juros_base },
{ key: 'recup_multas', name: 'Recuperação de Multas', unit: '%', step: 1, description: '+1% recuperado', impacto_direto: (BASE_VALUES.receita + BASE_VALUES.impostos) * 0.01 },
{ key: 'red_prc_mant', name: 'Redução de Preço de Manutenção', unit: '%', step: 1, description: '-1% no custo', impacto_direto: Math.abs(BASE_VALUES.manutencao) * 0.01 },
{ key: 'rec_precos', name: 'Recuperação de Preços', unit: '%', step: 1, description: '+1% nos preços', impacto_direto: (BASE_VALUES.receita + BASE_VALUES.impostos) * 0.01 },
{ key: 'disp_prod', name: 'Disponibilidade da Produção', unit: '%', step: 1, description: '+1% disponibilidade', impacto_direto: (BASE_VALUES.receita + BASE_VALUES.impostos + BASE_VALUES.mdo) * 0.01 },
{ key: 'aj_manut', name: 'Ajuste de Manutenção', unit: '%', step: 1, description: '+1% investimento', impacto_direto: -Math.abs(BASE_VALUES.manutencao) * 0.01 },
{ key: 'aj_subloc', name: 'Redução de Sublocação', unit: '%', step: 1, description: '-1% sublocação', impacto_direto: Math.abs(BASE_VALUES.sublocacoes) * 0.01 },
{ key: 'vendas_aquisicoes', name: 'Vendas/Aquisições', unit: '%', step: 1, description: '+1% crescimento', impacto_direto: BASE_VALUES.ganho * 0.01 }
];
return levers.map(lever => ({
...lever,
impacto_ebitda: lever.impacto_direto,
impacto_ebitda_pct: (lever.impacto_direto / BASE_VALUES.ebitda) * 100
}));
}
export function calculateIncrementalContributions(inputs) {
const steps = [
{ key: 'base', name: 'Base', inputs: {} },
{ key: 'perfil_div_jur', name: 'Perfil Dív/Jur', inputs: { perfil_div_jur: inputs.perfil_div_jur } },
{ key: 'vd_ativos', name: 'Venda Ativos', inputs: { ...inputs, vd_ativos: inputs.vd_ativos } },
{ key: 'red_estoque', name: 'Red. Estoque', inputs: { ...inputs, red_estoque: inputs.red_estoque } },
{ key: 'recup_multas', name: 'Recup. Multas', inputs: { ...inputs, recup_multas: inputs.recup_multas } },
{ key: 'red_prc_mant', name: 'Red. Manut.', inputs: { ...inputs, red_prc_mant: inputs.red_prc_mant } },
{ key: 'rec_precos', name: 'Rec. Preços', inputs: { ...inputs, rec_precos: inputs.rec_precos } },
{ key: 'disp_prod', name: 'Disp/Prod', inputs: { ...inputs, disp_prod: inputs.disp_prod } },
{ key: 'aj_manut', name: 'Aj. Manut.', inputs: { ...inputs, aj_manut: inputs.aj_manut } },
{ key: 'aj_subloc', name: 'Aj. Subloc.', inputs: { ...inputs, aj_subloc: inputs.aj_subloc } },
{ key: 'vendas_aquisicoes', name: 'Vendas/Aquis.', inputs: inputs }
];
let previousEbitda = BASE_VALUES.ebitda;
return steps.map((step, idx) => {
if (idx === 0) {
return { ...step, ebitda: BASE_VALUES.ebitda, contribuicao: 0 };
}
const result = calculateScenario(step.inputs);
const contribuicao = result.ebitda - previousEbitda;
previousEbitda = result.ebitda;
return { ...step, ebitda: result.ebitda, contribuicao };
});
}
3. components/simulator/WaterfallChart.jsx
import React from 'react';
import { motion } from 'framer-motion';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
import { BASE_VALUES } from './SimulatorEngine';
export default function WaterfallChart({ scenario, inputs }) {
const items = [];
let cumulative = BASE_VALUES.ebitda / 1000000;
items.push({
name: 'EBITDA Base',
value: cumulative,
start: 0,
end: cumulative,
isBase: true
});
const contributions = [];
if (inputs.perfil_div_jur && inputs.perfil_div_jur !== 0) {
const contrib = Math.abs(inputs.perfil_div_jur / 100) * BASE_VALUES.divida / 1000000;
contributions.push({ name: 'Perfil Dívida', value: contrib, isPositive: true });
}
if (inputs.vd_ativos && inputs.vd_ativos > 0) {
const contrib = inputs.vd_ativos * BASE_VALUES.juros_base / 1000000;
contributions.push({ name: 'Venda Ativos', value: contrib, isPositive: true });
}
if (inputs.red_estoque && inputs.red_estoque > 0) {
const contrib = inputs.red_estoque * BASE_VALUES.juros_base / 1000000;
contributions.push({ name: 'Red. Estoque', value: contrib, isPositive: true });
}
if (inputs.recup_multas && inputs.recup_multas > 0) {
const contrib = (inputs.recup_multas / 100) * (BASE_VALUES.receita + BASE_VALUES.impostos) / 1000000;
contributions.push({ name: 'Recup. Multas', value: contrib, isPositive: true });
}
if (inputs.red_prc_mant && inputs.red_prc_mant > 0) {
const contrib = (inputs.red_prc_mant / 100) * Math.abs(BASE_VALUES.manutencao) / 1000000;
contributions.push({ name: 'Red. Prc Manut', value: contrib, isPositive: true });
}
if (inputs.rec_precos && inputs.rec_precos > 0) {
const contrib = (inputs.rec_precos / 100) * (BASE_VALUES.receita + BASE_VALUES.impostos) / 1000000;
contributions.push({ name: 'Rec. Preços', value: contrib, isPositive: true });
}
if (inputs.disp_prod && inputs.disp_prod > 0) {
const contrib = (inputs.disp_prod / 100) * (BASE_VALUES.receita + BASE_VALUES.impostos + BASE_VALUES.mdo) / 1000000;
contributions.push({ name: 'Disp/Prod', value: contrib, isPositive: true });
}
// Ajuste Manutenção (CUSTO - valor negativo)
if (inputs.aj_manut && inputs.aj_manut > 0) {
const contrib = -(inputs.aj_manut / 100) * Math.abs(BASE_VALUES.manutencao) / 1000000;
contributions.push({ name: 'Aj. Manut', value: contrib, isPositive: false });
}
// Redução Sublocação (GANHO - valor positivo)
if (inputs.aj_subloc && inputs.aj_subloc > 0) {
const contrib = (inputs.aj_subloc / 100) * Math.abs(BASE_VALUES.sublocacoes) / 1000000;
contributions.push({ name: 'Red. Subloc', value: contrib, isPositive: true });
}
if (inputs.vendas_aquisicoes && inputs.vendas_aquisicoes > 0) {
const contrib = (inputs.vendas_aquisicoes / 100) * BASE_VALUES.ganho / 1000000;
contributions.push({ name: 'Vendas/Aquis', value: contrib, isPositive: true });
}
contributions.forEach(contrib => {
const start = cumulative;
cumulative += contrib.value;
items.push({
name: contrib.name,
value: contrib.value,
start: start,
end: cumulative,
isPositive: contrib.isPositive !== undefined ? contrib.isPositive : contrib.value >= 0
});
});
const ebitdaFinal = scenario.ebitda / 1000000;
items.push({
name: 'EBITDA Final',
value: ebitdaFinal,
start: 0,
end: ebitdaFinal,
isTotal: true
});
const CustomTooltip = ({ active, payload }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
const isContribution = !data.isBase && !data.isTotal;
return (
{data.name}
{isContribution ? (
{data.originalValue >= 0 ? '+' : ''}{data.originalValue.toFixed(2)}M
) : (
R$ {data.end.toFixed(1)}M
)}
);
}
return null;
};
const chartData = items.map(item => ({
name: item.name,
invisible: item.isBase || item.isTotal ? 0 : item.start,
value: item.isBase || item.isTotal ? item.end : Math.abs(item.value),
originalValue: item.value,
isBase: item.isBase,
isTotal: item.isTotal,
isPositive: item.isPositive,
end: item.end
}));
return (
Composição do EBITDA (Bridge)
Contribuição acumulada de cada alavanca
`${val}M`}
axisLine={false}
tickLine={false}
tick={{ fill: '#9ca3af', fontSize: 12 }}
domain={['auto', 'auto']}
/>
} />
{chartData.map((entry, index) => (
|
))}
|
);
}
4. components/simulator/LeverSlider.jsx
import React from 'react';
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
export default function LeverSlider({
label,
value,
onChange,
min,
max,
step,
unit = '%',
impact = null,
color = 'blue'
}) {
const formatValue = (val) => {
if (unit === 'R$') {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
maximumFractionDigits: 0
}).format(val);
}
return `${val.toFixed(1)}${unit}`;
};
const colorClasses = {
blue: 'bg-blue-500/10 border-blue-200 text-blue-700',
green: 'bg-emerald-500/10 border-emerald-200 text-emerald-700',
purple: 'bg-purple-500/10 border-purple-200 text-purple-700',
orange: 'bg-orange-500/10 border-orange-200 text-orange-700',
pink: 'bg-pink-500/10 border-pink-200 text-pink-700'
};
return (
{impact !== null && impact !== 0 && (
0 ? 'bg-emerald-50 text-emerald-700 border-emerald-200' : 'bg-red-50 text-red-700 border-red-200'}`}
>
{impact > 0 ? '+' : ''}{(impact / 1000000).toFixed(1)}M
)}
{formatValue(value)}
onChange(vals[0])}
min={min}
max={max}
step={step}
className="w-full"
/>
{formatValue(min)}
{formatValue(max)}
);
}
5. components/simulator/SensitivityChart.jsx
import React from 'react';
import { motion } from 'framer-motion';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell, ReferenceLine } from 'recharts';
import { calculateSensitivity } from './SimulatorEngine';
export default function SensitivityChart() {
const sensitivity = calculateSensitivity();
const chartData = sensitivity
.filter(item => item.unit === '%')
.map(item => ({
name: item.name.length > 20 ? item.name.substring(0, 18) + '...' : item.name,
fullName: item.name,
impacto: item.impacto_ebitda / 1000000,
impactoPct: item.impacto_ebitda_pct
}))
.sort((a, b) => Math.abs(b.impacto) - Math.abs(a.impacto));
const CustomTooltip = ({ active, payload }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
{data.fullName}
+1% →
= 0 ? 'text-emerald-600' : 'text-red-600'}`}>
{data.impacto >= 0 ? '+' : ''}{data.impacto.toFixed(2)}M
{data.impactoPct.toFixed(2)}% do EBITDA base
);
}
return null;
};
return (
Análise de Sensibilidade
Impacto de +1% em cada alavanca no EBITDA
`${val}M`}
axisLine={false}
tickLine={false}
tick={{ fill: '#9ca3af', fontSize: 12 }}
/>
} />
{chartData.map((entry, index) => (
|
= 0 ? '#10b981' : '#ef4444'}
fillOpacity={0.85}
/>
))}
|
);
}
6. components/simulator/PLTable.jsx
import React from 'react';
import { motion } from 'framer-motion';
import { BASE_VALUES } from './SimulatorEngine';
import { ArrowUp, ArrowDown, Minus } from 'lucide-react';
export default function PLTable({ scenario }) {
const formatCurrency = (val) => {
const absVal = Math.abs(val);
const formatted = new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 0 }).format(absVal);
if (val < 0) return `(${formatted})`;
return formatted;
};
const getVariation = (current, base) => {
const diff = current - base;
const pct = base !== 0 ? ((current - base) / Math.abs(base)) * 100 : 0;
return { diff, pct };
};
const VariationBadge = ({ current, base }) => {
const { diff, pct } = getVariation(current, base);
if (Math.abs(diff) < 1000) return null;
const isPositive = diff > 0;
const isNegative = diff < 0;
return (
{isPositive ?
:
isNegative ?
:
}
{Math.abs(pct).toFixed(1)}%
);
};
const rows = [
{ label: 'Receita', current: scenario.receita, base: BASE_VALUES.receita, bold: true },
{ label: 'Impostos', current: scenario.impostos, base: BASE_VALUES.impostos },
{ label: 'MDO', current: scenario.mdo, base: BASE_VALUES.mdo },
{ label: 'Manutenção', current: scenario.manutencao, base: BASE_VALUES.manutencao },
{ label: 'Sublocações', current: scenario.sublocacoes, base: BASE_VALUES.sublocacoes },
{ label: 'IPVA/Fretes', current: scenario.ipva_fretes, base: BASE_VALUES.ipva_fretes },
{ label: 'Ganho', current: scenario.ganho, base: BASE_VALUES.ganho, bold: true, highlight: true },
{ divider: true },
{ label: 'DO Locais', current: scenario.do_locais, base: BASE_VALUES.do_locais },
{ label: 'MDO Admin', current: scenario.mdo_admin, base: BASE_VALUES.mdo_admin },
{ label: 'Outr Desp Admin', current: scenario.outr_desp_admin, base: BASE_VALUES.outr_desp_admin },
{ label: 'C. Fin Médio', current: scenario.c_fin_medio, base: BASE_VALUES.c_fin_medio },
{ label: 'Red. Juros Ativos', current: scenario.reducao_juros_ativos, base: 0, show: scenario.reducao_juros_ativos > 0 },
{ divider: true },
{ label: 'Geração Operacional', current: scenario.geracao_operacional, base: BASE_VALUES.geracao_operacional, bold: true, highlight: true },
{ label: 'Amortização', current: scenario.amortizacao, base: BASE_VALUES.amortizacao_base },
{ divider: true },
{ label: 'EBITDA', current: scenario.ebitda, base: BASE_VALUES.ebitda, bold: true, primary: true }
];
return (
Demonstrativo de Resultado
Comparativo Base vs. Cenário Simulado
|
Linha
|
Base
|
Simulado
|
Var.
|
{rows.map((row, idx) => {
if (row.divider) {
return
|
;
}
if (row.show === false) return null;
return (
|
{row.label}
|
{formatCurrency(row.base)}
|
{formatCurrency(row.current)}
|
|
);
})}
);
}
Indústria 4.0 | Transforme seu negócio