calculateSavingsCapacity($userId); $debtControl = $this->calculateDebtControl($userId); $budgetManagement = $this->calculateBudgetManagement($userId); $investments = $this->calculateInvestments($userId); $emergencyFund = $this->calculateEmergencyFund($userId); $futurePlanning = $this->calculateFuturePlanning($userId); // Puntuación general ponderada $weights = [ 'savings' => 0.25, 'debt' => 0.20, 'budget' => 0.15, 'investments' => 0.15, 'emergency' => 0.15, 'planning' => 0.10, ]; $overallScore = round( ($savingsCapacity['score'] * $weights['savings']) + ($debtControl['score'] * $weights['debt']) + ($budgetManagement['score'] * $weights['budget']) + ($investments['score'] * $weights['investments']) + ($emergencyFund['score'] * $weights['emergency']) + ($futurePlanning['score'] * $weights['planning']) ); // Determinar nivel de salud $healthLevel = $this->getHealthLevel($overallScore); // Generar insights y recomendaciones $insights = $this->generateInsights($userId, [ 'savings' => $savingsCapacity, 'debt' => $debtControl, 'budget' => $budgetManagement, 'investments' => $investments, 'emergency' => $emergencyFund, 'planning' => $futurePlanning, ]); return response()->json([ 'overall_score' => $overallScore, 'health_level' => $healthLevel, 'last_updated' => now()->format('Y-m-d'), 'metrics' => [ 'savings_capacity' => $savingsCapacity, 'debt_control' => $debtControl, 'budget_management' => $budgetManagement, 'investments' => $investments, 'emergency_fund' => $emergencyFund, 'future_planning' => $futurePlanning, ], 'insights' => $insights, ]); } /** * Calcular capacidad de ahorro */ private function calculateSavingsCapacity($userId) { // Últimos 3 meses $data = Transaction::where('user_id', $userId) ->where('transaction_date', '>=', now()->subMonths(3)->startOfMonth()) ->selectRaw(" SUM(CASE WHEN type = 'credit' THEN amount ELSE 0 END) as income, SUM(CASE WHEN type = 'debit' THEN ABS(amount) ELSE 0 END) as expense ") ->first(); $income = $data->income ?? 0; $expense = $data->expense ?? 0; $savings = $income - $expense; $savingsRate = $income > 0 ? ($savings / $income) * 100 : 0; // Puntuación basada en tasa de ahorro // 0-10% = 40, 10-20% = 60, 20-30% = 80, 30%+ = 100 $score = match(true) { $savingsRate >= 30 => 100, $savingsRate >= 20 => 80 + (($savingsRate - 20) * 2), $savingsRate >= 10 => 60 + (($savingsRate - 10) * 2), $savingsRate >= 0 => 40 + ($savingsRate * 2), default => max(0, 40 + $savingsRate), }; return [ 'score' => round(min(100, max(0, $score))), 'savings_rate' => round($savingsRate, 1), 'monthly_savings' => round($savings / 3, 2), 'status' => $savingsRate >= 20 ? 'excellent' : ($savingsRate >= 10 ? 'good' : 'needs_improvement'), 'message' => $savingsRate >= 20 ? "¡Excelente! Ahorras el {$savingsRate}% de tus ingresos" : ($savingsRate >= 10 ? "Buen trabajo. Ahorras el {$savingsRate}%" : "Intenta aumentar tu tasa de ahorro"), ]; } /** * Calcular control de deudas */ private function calculateDebtControl($userId) { // Obtener deudas activas $liabilities = LiabilityAccount::where('user_id', $userId) ->where('status', 'active') ->get(); $totalDebt = $liabilities->sum('current_balance'); $totalCreditLimit = $liabilities->sum('credit_limit'); $monthlyPayments = $liabilities->sum('monthly_payment'); // Ingresos mensuales promedio $monthlyIncome = Transaction::where('user_id', $userId) ->where('type', 'credit') ->where('transaction_date', '>=', now()->subMonths(3)->startOfMonth()) ->sum('amount') / 3; // Ratio deuda/ingresos $debtToIncomeRatio = $monthlyIncome > 0 ? ($monthlyPayments / $monthlyIncome) * 100 : 0; // Utilización de crédito $creditUtilization = $totalCreditLimit > 0 ? ($totalDebt / $totalCreditLimit) * 100 : 0; // Puntuación // DTI < 20% = excelente, 20-35% = bueno, 35-50% = moderado, >50% = alto riesgo $score = match(true) { $debtToIncomeRatio <= 20 => 100 - ($debtToIncomeRatio * 0.5), $debtToIncomeRatio <= 35 => 80 - (($debtToIncomeRatio - 20) * 1.3), $debtToIncomeRatio <= 50 => 60 - (($debtToIncomeRatio - 35) * 2), default => max(0, 30 - (($debtToIncomeRatio - 50) * 0.6)), }; return [ 'score' => round(min(100, max(0, $score))), 'total_debt' => round($totalDebt, 2), 'monthly_payments' => round($monthlyPayments, 2), 'debt_to_income_ratio' => round($debtToIncomeRatio, 1), 'credit_utilization' => round($creditUtilization, 1), 'active_debts' => $liabilities->count(), 'status' => $debtToIncomeRatio <= 20 ? 'excellent' : ($debtToIncomeRatio <= 35 ? 'acceptable' : 'needs_attention'), 'message' => $totalDebt == 0 ? "¡Sin deudas! Excelente situación" : ($debtToIncomeRatio <= 35 ? "Nivel aceptable. Crédito disponible: " . number_format($totalCreditLimit - $totalDebt, 2) . "€" : "Considera reducir tus deudas"), ]; } /** * Calcular gestión de presupuesto */ private function calculateBudgetManagement($userId) { $currentMonth = now()->month; $currentYear = now()->year; $budgets = Budget::where('user_id', $userId) ->where('year', $currentYear) ->where('month', $currentMonth) ->where('is_active', true) ->get(); if ($budgets->isEmpty()) { return [ 'score' => 50, 'has_budgets' => false, 'status' => 'not_configured', 'message' => 'No tienes presupuestos configurados', 'exceeded_count' => 0, 'total_budgets' => 0, ]; } $exceededCount = $budgets->filter(fn($b) => $b->is_exceeded)->count(); $totalBudgets = $budgets->count(); $complianceRate = (($totalBudgets - $exceededCount) / $totalBudgets) * 100; // También verificar el uso promedio $avgUsage = $budgets->avg('usage_percentage'); // Puntuación $score = match(true) { $exceededCount == 0 && $avgUsage <= 90 => 100, $exceededCount == 0 => 85, $exceededCount <= 1 => 70, $exceededCount <= 2 => 55, default => max(20, 55 - ($exceededCount * 10)), }; // Encontrar categoría más excedida $mostExceeded = $budgets->filter(fn($b) => $b->is_exceeded) ->sortByDesc('usage_percentage') ->first(); return [ 'score' => round($score), 'has_budgets' => true, 'total_budgets' => $totalBudgets, 'exceeded_count' => $exceededCount, 'compliance_rate' => round($complianceRate, 1), 'avg_usage' => round($avgUsage, 1), 'most_exceeded' => $mostExceeded ? [ 'category' => $mostExceeded->category?->name ?? $mostExceeded->name, 'usage' => $mostExceeded->usage_percentage, ] : null, 'status' => $exceededCount == 0 ? 'on_track' : ($exceededCount <= 2 ? 'needs_attention' : 'exceeded'), 'message' => $exceededCount == 0 ? "Todos los presupuestos bajo control" : "¡Atención! Excediste presupuesto en " . ($mostExceeded->category?->name ?? 'una categoría'), ]; } /** * Calcular inversiones */ private function calculateInvestments($userId) { // Cuentas de tipo inversión $investmentAccounts = Account::where('user_id', $userId) ->where('type', 'investment') ->get(); $totalInvestments = $investmentAccounts->sum('current_balance'); // Total de activos $totalAssets = Account::where('user_id', $userId) ->where('include_in_total', true) ->sum('current_balance'); $investmentRatio = $totalAssets > 0 ? ($totalInvestments / $totalAssets) * 100 : 0; // Puntuación basada en ratio de inversión $score = match(true) { $investmentRatio >= 30 => 100, $investmentRatio >= 20 => 80 + (($investmentRatio - 20) * 2), $investmentRatio >= 10 => 60 + (($investmentRatio - 10) * 2), $investmentRatio >= 5 => 40 + (($investmentRatio - 5) * 4), default => max(20, $investmentRatio * 8), }; return [ 'score' => round(min(100, $score)), 'total_investments' => round($totalInvestments, 2), 'investment_ratio' => round($investmentRatio, 1), 'accounts_count' => $investmentAccounts->count(), 'status' => $investmentRatio >= 20 ? 'good' : ($investmentRatio >= 10 ? 'moderate' : 'low'), 'message' => $investmentRatio >= 20 ? "Buena diversificación. Sigue así" : "Considera aumentar tu inversión", ]; } /** * Calcular fondo de emergencia */ private function calculateEmergencyFund($userId) { // Cuentas líquidas (checking, savings, cash) $liquidAccounts = Account::where('user_id', $userId) ->whereIn('type', ['checking', 'savings', 'cash']) ->where('include_in_total', true) ->sum('current_balance'); // Gastos mensuales promedio $monthlyExpenses = Transaction::where('user_id', $userId) ->where('type', 'debit') ->where('transaction_date', '>=', now()->subMonths(3)->startOfMonth()) ->sum(fn($t) => abs($t->amount)) / 3; // Usar consulta directa para mejor rendimiento $monthlyExpenses = abs(Transaction::where('user_id', $userId) ->where('type', 'debit') ->where('transaction_date', '>=', now()->subMonths(3)->startOfMonth()) ->sum('amount')) / 3; // Meses cubiertos $monthsCovered = $monthlyExpenses > 0 ? $liquidAccounts / $monthlyExpenses : 0; // Puntuación (ideal: 6 meses) $score = match(true) { $monthsCovered >= 6 => 100, $monthsCovered >= 3 => 60 + (($monthsCovered - 3) * 13.33), $monthsCovered >= 1 => 30 + (($monthsCovered - 1) * 15), default => max(0, $monthsCovered * 30), }; return [ 'score' => round(min(100, $score)), 'liquid_assets' => round($liquidAccounts, 2), 'monthly_expenses' => round($monthlyExpenses, 2), 'months_covered' => round($monthsCovered, 1), 'recommended_fund' => round($monthlyExpenses * 6, 2), 'status' => $monthsCovered >= 6 ? 'excellent' : ($monthsCovered >= 3 ? 'moderate' : 'insufficient'), 'message' => $monthsCovered >= 6 ? "Excelente fondo de emergencia" : "Tienes " . number_format($liquidAccounts, 2) . "€ (" . round($monthsCovered, 1) . " meses de gastos)", ]; } /** * Calcular planificación futura */ private function calculateFuturePlanning($userId) { // Metas financieras activas $goals = FinancialGoal::where('user_id', $userId) ->where('status', 'active') ->get(); // Transacciones recurrentes configuradas $recurringCount = RecurringTemplate::where('user_id', $userId) ->where('is_active', true) ->count(); // Presupuestos configurados $hasBudgets = Budget::where('user_id', $userId) ->where('is_active', true) ->exists(); $activeGoals = $goals->count(); $goalsOnTrack = $goals->filter(fn($g) => $g->is_on_track)->count(); $avgProgress = $goals->avg('progress_percentage') ?? 0; // Puntuación $baseScore = 40; if ($activeGoals > 0) $baseScore += 20; if ($activeGoals >= 3) $baseScore += 10; if ($goalsOnTrack > 0) $baseScore += 15; if ($hasBudgets) $baseScore += 10; if ($recurringCount >= 3) $baseScore += 5; // Bonus por progreso $baseScore += min(10, $avgProgress / 10); return [ 'score' => round(min(100, $baseScore)), 'active_goals' => $activeGoals, 'goals_on_track' => $goalsOnTrack, 'avg_progress' => round($avgProgress, 1), 'has_budgets' => $hasBudgets, 'recurring_configured' => $recurringCount, 'status' => $activeGoals >= 3 ? 'excellent' : ($activeGoals >= 1 ? 'good' : 'needs_setup'), 'message' => $activeGoals > 0 ? "¡Muy bien! Tienes $activeGoals metas activas en progreso" : "Crea metas financieras para mejorar tu planificación", ]; } /** * Determinar nivel de salud */ private function getHealthLevel($score) { return match(true) { $score >= 85 => ['level' => 'excellent', 'label' => 'Excelente Salud', 'color' => '#22c55e'], $score >= 70 => ['level' => 'good', 'label' => 'Buena Salud', 'color' => '#84cc16'], $score >= 55 => ['level' => 'moderate', 'label' => 'Salud Moderada', 'color' => '#f59e0b'], $score >= 40 => ['level' => 'needs_work', 'label' => 'Necesita Mejorar', 'color' => '#f97316'], default => ['level' => 'critical', 'label' => 'Atención Necesaria', 'color' => '#ef4444'], }; } /** * Generar insights y recomendaciones */ private function generateInsights($userId, $metrics) { $insights = []; // Prioridad Alta - Problemas críticos if ($metrics['debt']['score'] < 50) { $insights[] = [ 'type' => 'high_priority', 'icon' => 'bi-exclamation-triangle-fill', 'color' => '#ef4444', 'title' => 'Prioridad Alta', 'message' => "Reduce gastos en " . ($metrics['budget']['most_exceeded']['category'] ?? 'categorías con exceso') . ". Estás excediendo el presupuesto en " . ($metrics['budget']['most_exceeded']['usage'] ?? 0) . "%.", ]; } // Prioridad Media - Mejoras recomendadas if ($metrics['emergency']['months_covered'] < 6) { $needed = $metrics['emergency']['recommended_fund'] - $metrics['emergency']['liquid_assets']; $insights[] = [ 'type' => 'medium_priority', 'icon' => 'bi-shield-exclamation', 'color' => '#f59e0b', 'title' => 'Prioridad Media', 'message' => "Incrementa tu fondo de emergencia a 6 meses de gastos (" . number_format($metrics['emergency']['recommended_fund'], 0) . "€).", ]; } // Logros if ($metrics['savings']['savings_rate'] >= 20) { $insights[] = [ 'type' => 'achievement', 'icon' => 'bi-trophy-fill', 'color' => '#22c55e', 'title' => 'Logro Destacado', 'message' => "¡Excelente ahorro mensual! Mantienes una tasa del " . $metrics['savings']['savings_rate'] . "% por encima de la meta.", ]; } // Oportunidades if ($metrics['investments']['investment_ratio'] < 20 && $metrics['savings']['savings_rate'] > 15) { $insights[] = [ 'type' => 'opportunity', 'icon' => 'bi-lightbulb-fill', 'color' => '#3b82f6', 'title' => 'Oportunidad', 'message' => "Considera aumentar tu inversión con el excedente de tus ahorros.", ]; } // Metas próximas $upcomingGoals = FinancialGoal::where('user_id', $userId) ->where('status', 'active') ->whereNotNull('target_date') ->where('target_date', '<=', now()->addMonths(6)) ->orderBy('target_date') ->first(); if ($upcomingGoals) { $insights[] = [ 'type' => 'upcoming_goal', 'icon' => 'bi-calendar-check-fill', 'color' => '#8b5cf6', 'title' => 'Meta Próxima', 'message' => "Faltan " . now()->diffInMonths($upcomingGoals->target_date) . " meses para tu meta de " . $upcomingGoals->name . ". ¡Sigue así!", ]; } // Sugerencia general if ($metrics['planning']['active_goals'] < 3) { $insights[] = [ 'type' => 'suggestion', 'icon' => 'bi-gear-fill', 'color' => '#06b6d4', 'title' => 'Sugerencia', 'message' => "Activa ahorro automático del 10% cada vez que recibes tu nómina.", ]; } return $insights; } /** * Historial de puntuación */ public function history(Request $request) { // Por ahora retornamos datos simulados // En producción, guardaríamos snapshots diarios/semanales $months = $request->get('months', 6); $history = []; $baseScore = 72; for ($i = $months - 1; $i >= 0; $i--) { $date = now()->subMonths($i); $variation = rand(-5, 8); $baseScore = max(30, min(95, $baseScore + $variation)); $history[] = [ 'month' => $date->format('Y-m'), 'month_label' => $date->format('M Y'), 'score' => $baseScore, ]; } return response()->json($history); } }