import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { financialHealthService } from '../services/api';
import useFormatters from '../hooks/useFormatters';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
ArcElement,
Title,
Tooltip,
Legend,
Filler,
} from 'chart.js';
import { Line, Doughnut, Bar } from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
ArcElement,
Title,
Tooltip,
Legend,
Filler
);
const FinancialHealth = () => {
const { t } = useTranslation();
const { currency: formatCurrency, percent, number } = useFormatters();
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
const [history, setHistory] = useState([]);
const [activeTab, setActiveTab] = useState('overview');
// Helper para formatear moneda usando la moneda del API
const currency = (value, currencyCode = null) => {
const code = currencyCode || data?.currency || 'EUR';
return formatCurrency(value, code);
};
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoading(true);
try {
const [healthData, historyData] = await Promise.all([
financialHealthService.get(),
financialHealthService.getHistory({ months: 6 })
]);
setData(healthData);
// Asegurar que history sea siempre un array
setHistory(Array.isArray(historyData) ? historyData : []);
} catch (error) {
console.error('Error loading financial health:', error);
} finally {
setLoading(false);
}
};
const getScoreColor = (score) => {
if (score >= 80) return '#10b981';
if (score >= 60) return '#84cc16';
if (score >= 40) return '#f59e0b';
if (score >= 20) return '#f97316';
return '#ef4444';
};
const getStatusColor = (status) => {
const colors = {
excellent: '#10b981',
good: '#84cc16',
adequate: '#22c55e',
moderate: '#f59e0b',
needs_improvement: '#f97316',
needs_attention: '#f97316',
needs_work: '#f97316',
negative: '#ef4444',
critical: '#ef4444',
insufficient: '#ef4444',
debt_free: '#10b981',
healthy: '#22c55e',
manageable: '#f59e0b',
concerning: '#ef4444',
on_track: '#10b981',
exceeded: '#ef4444',
not_configured: '#6b7280',
very_stable: '#10b981',
stable: '#22c55e',
volatile: '#ef4444',
optimized: '#10b981',
acceptable: '#f59e0b',
high_discretionary: '#f97316',
};
return colors[status] || '#6b7280';
};
const metricConfigs = {
savings_capacity: {
icon: 'bi-piggy-bank-fill',
gradient: ['#059669', '#10b981'],
},
debt_control: {
icon: 'bi-credit-card-2-front-fill',
gradient: ['#2563eb', '#3b82f6'],
},
budget_management: {
icon: 'bi-wallet2',
gradient: ['#7c3aed', '#8b5cf6'],
},
expense_efficiency: {
icon: 'bi-pie-chart-fill',
gradient: ['#0891b2', '#06b6d4'],
},
emergency_fund: {
icon: 'bi-shield-fill-check',
gradient: ['#d97706', '#f59e0b'],
},
financial_stability: {
icon: 'bi-graph-up-arrow',
gradient: ['#db2777', '#ec4899'],
},
};
const tabs = [
{ id: 'overview', icon: 'bi-speedometer2', label: t('financialHealth.tabs.overview') },
{ id: 'metrics', icon: 'bi-bar-chart-fill', label: t('financialHealth.tabs.metrics') },
{ id: 'categories', icon: 'bi-pie-chart-fill', label: t('financialHealth.tabs.categories') },
{ id: 'trends', icon: 'bi-graph-up', label: t('financialHealth.tabs.trends') },
{ id: 'insights', icon: 'bi-lightbulb-fill', label: t('financialHealth.tabs.insights') },
];
if (loading) {
return (
);
}
if (!data) {
return (
{t('financialHealth.errorLoading')}
);
}
const score = data.overall_score;
const scoreColor = getScoreColor(score);
// Asegurar arrays válidos para Chart.js
const safeHistory = Array.isArray(history) ? history : [];
const safeMonthlyData = Array.isArray(data.trends?.monthly_data) ? data.trends.monthly_data : [];
const safeTopExpenses = Array.isArray(data.category_analysis?.top_expenses) ? data.category_analysis.top_expenses : [];
const safeCategoryTrends = Array.isArray(data.category_analysis?.category_trends) ? data.category_analysis.category_trends : [];
// Chart data for history
const historyChartData = safeHistory.length > 0 ? {
labels: safeHistory.map(h => h.month_label),
datasets: [{
label: t('financialHealth.score'),
data: safeHistory.map(h => h.score),
borderColor: scoreColor,
backgroundColor: `${scoreColor}20`,
fill: true,
tension: 0.4,
pointRadius: 4,
pointBackgroundColor: scoreColor,
}],
} : null;
// Chart data for expense distribution
const distributionChartData = data.category_analysis?.expense_distribution ? {
labels: [
t('financialHealth.distribution.fixed'),
t('financialHealth.distribution.variable'),
t('financialHealth.distribution.discretionary'),
],
datasets: [{
data: [
data.category_analysis.expense_distribution.fixed.percentage,
data.category_analysis.expense_distribution.variable.percentage,
data.category_analysis.expense_distribution.discretionary.percentage,
],
backgroundColor: ['#3b82f6', '#22c55e', '#f59e0b'],
borderWidth: 0,
}],
} : null;
// Chart data for monthly comparison
const monthlyChartData = safeMonthlyData.length > 0 ? {
labels: safeMonthlyData.map(m => m.month),
datasets: [
{
label: t('financialHealth.income'),
data: safeMonthlyData.map(m => m.income),
backgroundColor: '#22c55e',
borderRadius: 4,
},
{
label: t('financialHealth.expenses'),
data: safeMonthlyData.map(m => m.expenses),
backgroundColor: '#ef4444',
borderRadius: 4,
},
],
} : null;
// Category expenses chart
const categoryChartData = safeTopExpenses.length > 0 ? {
labels: safeTopExpenses.slice(0, 8).map(c => c.name),
datasets: [{
data: safeTopExpenses.slice(0, 8).map(c => c.total),
backgroundColor: safeTopExpenses.slice(0, 8).map(c => c.color || '#6b7280'),
borderWidth: 0,
}],
} : null;
return (
{/* Header */}
{t('financialHealth.title')}
{t('financialHealth.subtitle')} • {t('financialHealth.lastUpdate')}: {new Date(data.last_updated).toLocaleDateString()}
{data.currency}
{/* Tabs */}
{tabs.map(tab => (
-
))}
{/* Overview Tab */}
{activeTab === 'overview' && (
{/* Score Circle */}
{/* Score Ring */}
{t(`financialHealth.levels.${data.health_level?.level}`)}
{t('financialHealth.scoreDescription')}
{/* Mini History Chart */}
{historyChartData && (
)}
{/* Summary Cards */}
{/* Net Worth */}
{t('financialHealth.summary.netWorth')}
= 0 ? 'text-success' : 'text-danger'}`}>
{currency(data.summary?.net_worth || 0)}
{t('financialHealth.summary.assets')}: {currency(data.summary?.total_assets || 0)}
{t('financialHealth.summary.liabilities')}: {currency(data.summary?.total_liabilities || 0)}
{/* Monthly Savings */}
{t('financialHealth.summary.monthlySavings')}
= 0 ? 'text-success' : 'text-danger'}`}>
{currency(data.summary?.monthly_savings || 0)}
{t('financialHealth.summary.savingsRate')}:
= 20 ? 'text-success' : 'text-warning'}>
{' '}{data.summary?.savings_rate || 0}%
{/* Monthly Income */}
{t('financialHealth.summary.monthlyIncome')}
{currency(data.summary?.monthly_income || 0)}
{data.trends?.monthly_comparison?.income?.change !== 0 && (
0 ? 'text-success' : 'text-danger'}>
0 ? 'up' : 'down'} me-1`}>
{Math.abs(data.trends.monthly_comparison.income.change)}% {t('financialHealth.vsLastMonth')}
)}
{/* Monthly Expenses */}
{t('financialHealth.summary.monthlyExpenses')}
{currency(data.summary?.monthly_expenses || 0)}
{data.trends?.monthly_comparison?.expenses?.change !== 0 && (
0 ? 'up' : 'down'} me-1`}>
{Math.abs(data.trends.monthly_comparison.expenses.change)}% {t('financialHealth.vsLastMonth')}
)}
{/* Projection */}
{t('financialHealth.summary.projectedSavings')}
= 0 ? 'text-success' : 'text-danger'}`}>
{currency(data.projection?.projected?.savings || 0)}
{data.projection?.days_remaining} {t('financialHealth.daysRemaining')}
{/* Accounts by Currency */}
{data.summary?.accounts_by_currency?.length > 1 && (
{t('financialHealth.summary.byCurrency')}
{data.summary.accounts_by_currency.map((curr, idx) => (
{curr.currency}
= 0 ? 'text-success' : 'text-danger'}>
{currency(curr.balance, curr.currency)}
))}
)}
)}
{/* Metrics Tab */}
{activeTab === 'metrics' && (
{Object.entries(data.metrics || {}).map(([key, metric]) => {
const config = metricConfigs[key];
if (!config) return null;
return (
{t(`financialHealth.metrics.${key}`)}
{t(`financialHealth.status.${metric.status}`)}
{metric.score}
/100
{/* Progress bar */}
{/* Metric-specific details */}
{key === 'savings_capacity' && (
<>
{t('financialHealth.details.savingsRate')}:
{metric.savings_rate}%
{t('financialHealth.details.monthlySavings')}:
{currency(metric.monthly_savings)}
>
)}
{key === 'debt_control' && (
<>
{t('financialHealth.details.totalDebt')}:
{currency(metric.total_debt)}
{/* Multi-currency breakdown */}
{metric.debt_by_currency && Object.keys(metric.debt_by_currency).length > 1 && (
{Object.entries(metric.debt_by_currency).map(([curr, amt]) => (
{curr}: {Number(amt).toLocaleString(undefined, {minimumFractionDigits: 2})}
))}
)}
{t('financialHealth.details.debtToIncome')}:
{metric.debt_to_income_ratio}%
{metric.active_debts > 0 && (
{t('financialHealth.details.activeDebts')}:
{metric.active_debts}
)}
>
)}
{key === 'budget_management' && (
<>
{metric.has_budgets ? (
<>
{t('financialHealth.details.budgetsConfigured')}:
{metric.total_budgets}
{t('financialHealth.details.compliance')}:
{metric.compliance_rate}%
{metric.exceeded_count > 0 && (
{t('financialHealth.details.exceeded')}:
{metric.exceeded_count}
)}
>
) : (
{t('financialHealth.details.noBudgets')}
)}
>
)}
{key === 'expense_efficiency' && (
<>
{t('financialHealth.distribution.fixed')}:
{metric.distribution?.fixed?.percentage}%
{t('financialHealth.distribution.variable')}:
{metric.distribution?.variable?.percentage}%
{t('financialHealth.distribution.discretionary')}:
{metric.distribution?.discretionary?.percentage}%
>
)}
{key === 'emergency_fund' && (
<>
{t('financialHealth.details.liquidAssets')}:
{currency(metric.liquid_assets)}
{/* Multi-currency breakdown */}
{metric.liquid_assets_by_currency && Object.keys(metric.liquid_assets_by_currency).length > 1 && (
{Object.entries(metric.liquid_assets_by_currency).map(([curr, amt]) => (
{curr}: {Number(amt).toLocaleString(undefined, {minimumFractionDigits: 2})}
))}
)}
{t('financialHealth.details.monthsCovered')}:
{metric.months_covered} {t('common.months')}
{metric.gap > 0 && (
{t('financialHealth.details.gap')}:
{currency(metric.gap)}
)}
>
)}
{key === 'financial_stability' && (
<>
{t('financialHealth.details.incomeVolatility')}:
{metric.income_volatility}%
{t('financialHealth.details.expenseVolatility')}:
{metric.expense_volatility}%
{t('financialHealth.details.savingsTrend')}:
{' '}{t(`financialHealth.trend.${metric.savings_trend}`)}
>
)}
);
})}
)}
{/* Categories Tab */}
{activeTab === 'categories' && (
{/* Expense Distribution */}
{t('financialHealth.categories.distribution')}
{distributionChartData && (
)}
{data.category_analysis?.expense_distribution && Object.entries(data.category_analysis.expense_distribution).map(([key, val]) => (
{t(`financialHealth.distribution.${key}`)}
{currency(val.amount)} ({val.percentage}%)
{/* Multi-currency breakdown */}
{val.by_currency && Object.keys(val.by_currency).length > 1 && (
{Object.entries(val.by_currency).map(([curr, amt]) => (
{curr}: {Number(amt).toLocaleString()}
))}
)}
))}
{/* Top Expenses */}
{t('financialHealth.categories.topExpenses')}
{data.category_analysis?.top_expenses?.slice(0, 10).map((cat, idx) => (
{cat.name}
{currency(cat.total)} ({cat.percentage}%)
{/* Multi-currency breakdown */}
{cat.by_currency && Object.keys(cat.by_currency).length > 1 && (
{Object.entries(cat.by_currency).map(([curr, amt]) => (
{curr}: {Number(amt).toLocaleString(undefined, { minimumFractionDigits: 2 })}
))}
)}
))}
{/* Category Trends */}
{data.category_analysis?.category_trends?.length > 0 && (
{t('financialHealth.categories.trends')}
{safeCategoryTrends.map((trend, idx) => (
{trend.category}
{Math.abs(trend.change_percent)}%
{currency(trend.previous)} → {currency(trend.current)}
))}
)}
)}
{/* Trends Tab */}
{activeTab === 'trends' && (
{/* Monthly Evolution Chart */}
{t('financialHealth.trends.monthlyEvolution')}
{monthlyChartData && (
)}
{/* Trend Indicators */}
{/* Income Trend */}
{t('financialHealth.trends.incomeTrend')}
{t(`financialHealth.trend.${data.trends?.income_trend?.direction}`)}
{data.trends?.income_trend?.strength}%
{/* Expense Trend */}
{t('financialHealth.trends.expenseTrend')}
{t(`financialHealth.trend.${data.trends?.expense_trend?.direction}`)}
{data.trends?.expense_trend?.strength}%
{/* Savings Trend */}
{t('financialHealth.trends.savingsTrend')}
{t(`financialHealth.trend.${data.trends?.savings_trend?.direction}`)}
{data.trends?.savings_trend?.strength}%
{/* Monthly Comparison */}
{t('financialHealth.trends.monthlyComparison')}
{t('financialHealth.income')}
0 ? 'text-success' : 'text-danger'}>
{data.trends?.monthly_comparison?.income?.change > 0 ? '+' : ''}{data.trends?.monthly_comparison?.income?.change}%
{t('financialHealth.expenses')}
{data.trends?.monthly_comparison?.expenses?.change > 0 ? '+' : ''}{data.trends?.monthly_comparison?.expenses?.change}%
{/* Score History */}
{t('financialHealth.trends.scoreHistory')}
{safeHistory.length > 0 && (
h.month_label),
datasets: [
{
label: t('financialHealth.score'),
data: safeHistory.map(h => h.score),
borderColor: '#3b82f6',
backgroundColor: '#3b82f620',
fill: true,
tension: 0.4,
},
{
label: t('financialHealth.savingsRate'),
data: safeHistory.map(h => h.savings_rate),
borderColor: '#22c55e',
backgroundColor: 'transparent',
borderDash: [5, 5],
tension: 0.4,
},
],
}}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: { color: '#94a3b8' },
},
},
scales: {
x: {
grid: { color: '#1e293b' },
ticks: { color: '#94a3b8' },
},
y: {
grid: { color: '#1e293b' },
ticks: { color: '#94a3b8' },
min: 0,
max: 100,
},
},
}}
/>
)}
)}
{/* Insights Tab */}
{activeTab === 'insights' && (
{/* Insights */}
{t('financialHealth.insightsTitle')}
{data.insights?.length > 0 ? (
{data.insights.map((insight, idx) => (
{t(insight.title_key, insight.data)}
{t(insight.message_key, insight.data)}
))}
) : (
{t('financialHealth.noInsights')}
)}
{/* Recommendations */}
{t('financialHealth.recommendationsTitle')}
{data.recommendations?.length > 0 ? (
{data.recommendations.map((rec, idx) => (
{rec.priority === 'high' ? t('financialHealth.priority.high') : t('financialHealth.priority.medium')}
{t(rec.action_key, rec)}
{rec.target_amount && (
{t('financialHealth.target')}: {currency(rec.target_amount)}
)}
{rec.monthly_suggestion && (
{t('financialHealth.monthlyTarget')}: {currency(rec.monthly_suggestion)}
)}
))}
) : (
{t('financialHealth.noRecommendations')}
)}
{/* Projection */}
{data.projection && (
{t('financialHealth.projection.title')}
{t('financialHealth.projection.currentExpenses')}
{currency(data.projection.current_month?.expenses || 0)}
{t('financialHealth.projection.projected')}
{currency(data.projection.projected?.expenses || 0)}
{data.projection.days_remaining} {t('financialHealth.daysRemaining')}
{data.projection.vs_average?.expenses !== 0 && (
0 ? 'text-danger' : 'text-success'}>
{' '}({data.projection.vs_average.expenses > 0 ? '+' : ''}{data.projection.vs_average.expenses}% {t('financialHealth.vsAverage')})
)}
)}
)}
);
};
export default FinancialHealth;