get('year', now()->year); // Ingresos y gastos del año $yearData = Transaction::where('user_id', $userId) ->whereYear('transaction_date', $year) ->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(); // Comparar con año anterior $lastYearData = Transaction::where('user_id', $userId) ->whereYear('transaction_date', $year - 1) ->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(); return response()->json([ 'year' => $year, 'current' => [ 'income' => $yearData->income ?? 0, 'expense' => $yearData->expense ?? 0, 'balance' => ($yearData->income ?? 0) - ($yearData->expense ?? 0), ], 'previous' => [ 'income' => $lastYearData->income ?? 0, 'expense' => $lastYearData->expense ?? 0, 'balance' => ($lastYearData->income ?? 0) - ($lastYearData->expense ?? 0), ], 'variation' => [ 'income' => $lastYearData->income > 0 ? round((($yearData->income - $lastYearData->income) / $lastYearData->income) * 100, 1) : 0, 'expense' => $lastYearData->expense > 0 ? round((($yearData->expense - $lastYearData->expense) / $lastYearData->expense) * 100, 1) : 0, ], ]); } /** * Reporte por categorías */ public function byCategory(Request $request) { $userId = Auth::id(); $startDate = $request->get('start_date', now()->startOfYear()->format('Y-m-d')); $endDate = $request->get('end_date', now()->format('Y-m-d')); $type = $request->get('type', 'debit'); // debit o credit $data = Transaction::where('user_id', $userId) ->whereBetween('transaction_date', [$startDate, $endDate]) ->where('type', $type) ->whereNotNull('category_id') ->join('categories', 'transactions.category_id', '=', 'categories.id') ->selectRaw(" COALESCE(categories.parent_id, categories.id) as category_group_id, SUM(ABS(transactions.amount)) as total ") ->groupBy('category_group_id') ->orderByDesc('total') ->get(); // Obtener nombres de categorías $categoryIds = $data->pluck('category_group_id')->unique(); $categories = Category::whereIn('id', $categoryIds)->get()->keyBy('id'); $result = $data->map(function($item) use ($categories) { $category = $categories->get($item->category_group_id); return [ 'category_id' => $item->category_group_id, 'category_name' => $category ? $category->name : 'Sin categoría', 'icon' => $category ? $category->icon : 'bi-tag', 'color' => $category ? $category->color : '#6b7280', 'total' => round($item->total, 2), ]; }); $grandTotal = $result->sum('total'); // Añadir porcentajes $result = $result->map(function($item) use ($grandTotal) { $item['percentage'] = $grandTotal > 0 ? round(($item['total'] / $grandTotal) * 100, 1) : 0; return $item; }); return response()->json([ 'data' => $result->values(), 'total' => $grandTotal, 'period' => [ 'start' => $startDate, 'end' => $endDate, ], ]); } /** * Reporte de evolución mensual */ public function monthlyEvolution(Request $request) { $userId = Auth::id(); $months = $request->get('months', 12); $data = Transaction::where('user_id', $userId) ->where('transaction_date', '>=', now()->subMonths($months)->startOfMonth()) ->selectRaw(" DATE_FORMAT(transaction_date, '%Y-%m') as month, 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 ") ->groupBy('month') ->orderBy('month') ->get(); // Calcular balance acumulado y tasa de ahorro $result = $data->map(function($item) { $balance = $item->income - $item->expense; $savingsRate = $item->income > 0 ? round(($balance / $item->income) * 100, 1) : 0; return [ 'month' => $item->month, 'month_label' => Carbon::parse($item->month . '-01')->format('M Y'), 'income' => round($item->income, 2), 'expense' => round($item->expense, 2), 'balance' => round($balance, 2), 'savings_rate' => $savingsRate, ]; }); return response()->json([ 'data' => $result, 'averages' => [ 'income' => round($result->avg('income'), 2), 'expense' => round($result->avg('expense'), 2), 'balance' => round($result->avg('balance'), 2), 'savings_rate' => round($result->avg('savings_rate'), 1), ], ]); } /** * Reporte de gastos por día de la semana */ public function byDayOfWeek(Request $request) { $userId = Auth::id(); $months = $request->get('months', 6); $data = Transaction::where('user_id', $userId) ->where('type', 'debit') ->where('transaction_date', '>=', now()->subMonths($months)) ->selectRaw(" DAYOFWEEK(transaction_date) as day_num, COUNT(*) as count, SUM(ABS(amount)) as total, AVG(ABS(amount)) as average ") ->groupBy('day_num') ->orderBy('day_num') ->get(); $days = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado']; $result = $data->map(function($item) use ($days) { return [ 'day' => $days[$item->day_num - 1], 'day_num' => $item->day_num, 'count' => $item->count, 'total' => round($item->total, 2), 'average' => round($item->average, 2), ]; }); return response()->json($result); } /** * Top gastos */ public function topExpenses(Request $request) { $userId = Auth::id(); $startDate = $request->get('start_date', now()->startOfMonth()->format('Y-m-d')); $endDate = $request->get('end_date', now()->format('Y-m-d')); $limit = $request->get('limit', 20); $transactions = Transaction::where('user_id', $userId) ->where('type', 'debit') ->whereBetween('transaction_date', [$startDate, $endDate]) ->with(['category', 'account']) ->orderByRaw('ABS(amount) DESC') ->limit($limit) ->get(); return response()->json([ 'data' => $transactions->map(function($t) { return [ 'id' => $t->id, 'description' => $t->description, 'amount' => abs($t->amount), 'date' => $t->transaction_date->format('Y-m-d'), 'category' => $t->category ? $t->category->name : null, 'account' => $t->account ? $t->account->name : null, ]; }), 'total' => $transactions->sum(fn($t) => abs($t->amount)), ]); } /** * Comparativa de períodos */ public function comparePeriods(Request $request) { $userId = Auth::id(); $period1Start = $request->get('period1_start'); $period1End = $request->get('period1_end'); $period2Start = $request->get('period2_start'); $period2End = $request->get('period2_end'); // Si no se especifican, comparar mes actual vs anterior if (!$period1Start) { $period1Start = now()->startOfMonth()->format('Y-m-d'); $period1End = now()->endOfMonth()->format('Y-m-d'); $period2Start = now()->subMonth()->startOfMonth()->format('Y-m-d'); $period2End = now()->subMonth()->endOfMonth()->format('Y-m-d'); } $getPeriodData = function($start, $end) use ($userId) { return Transaction::where('user_id', $userId) ->whereBetween('transaction_date', [$start, $end]) ->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, COUNT(*) as transactions ") ->first(); }; $period1 = $getPeriodData($period1Start, $period1End); $period2 = $getPeriodData($period2Start, $period2End); return response()->json([ 'period1' => [ 'label' => Carbon::parse($period1Start)->format('M Y'), 'start' => $period1Start, 'end' => $period1End, 'income' => $period1->income ?? 0, 'expense' => $period1->expense ?? 0, 'balance' => ($period1->income ?? 0) - ($period1->expense ?? 0), 'transactions' => $period1->transactions ?? 0, ], 'period2' => [ 'label' => Carbon::parse($period2Start)->format('M Y'), 'start' => $period2Start, 'end' => $period2End, 'income' => $period2->income ?? 0, 'expense' => $period2->expense ?? 0, 'balance' => ($period2->income ?? 0) - ($period2->expense ?? 0), 'transactions' => $period2->transactions ?? 0, ], 'variation' => [ 'income' => ($period2->income ?? 0) > 0 ? round(((($period1->income ?? 0) - ($period2->income ?? 0)) / ($period2->income ?? 1)) * 100, 1) : 0, 'expense' => ($period2->expense ?? 0) > 0 ? round(((($period1->expense ?? 0) - ($period2->expense ?? 0)) / ($period2->expense ?? 1)) * 100, 1) : 0, ], ]); } /** * Reporte de cuentas */ public function accountsReport(Request $request) { $userId = Auth::id(); $accounts = Account::where('user_id', $userId) ->withCount('transactions') ->get(); $result = $accounts->map(function($account) { // Últimas transacciones $recentActivity = Transaction::where('account_id', $account->id) ->orderBy('transaction_date', 'desc') ->limit(5) ->get(['id', 'description', 'amount', 'type', 'transaction_date']); // Movimientos del mes $monthStats = Transaction::where('account_id', $account->id) ->whereMonth('transaction_date', now()->month) ->whereYear('transaction_date', now()->year) ->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(); return [ 'id' => $account->id, 'name' => $account->name, 'type' => $account->type, 'currency' => $account->currency, 'balance' => $account->current_balance, 'transactions_count' => $account->transactions_count, 'month_income' => $monthStats->income ?? 0, 'month_expense' => $monthStats->expense ?? 0, 'recent_activity' => $recentActivity, ]; }); return response()->json([ 'accounts' => $result, 'summary' => [ 'total_accounts' => $accounts->count(), 'total_balance' => $accounts->where('include_in_total', true)->sum('current_balance'), ], ]); } /** * Proyección de gastos */ public function projection(Request $request) { $userId = Auth::id(); $months = $request->get('months', 3); // Obtener promedio de los últimos meses $historical = Transaction::where('user_id', $userId) ->where('transaction_date', '>=', now()->subMonths($months)->startOfMonth()) ->where('transaction_date', '<', now()->startOfMonth()) ->selectRaw(" AVG(CASE WHEN type = 'credit' THEN amount ELSE NULL END) as avg_income, AVG(CASE WHEN type = 'debit' THEN ABS(amount) ELSE NULL END) as avg_expense, SUM(CASE WHEN type = 'credit' THEN amount ELSE 0 END) / ? as monthly_income, SUM(CASE WHEN type = 'debit' THEN ABS(amount) ELSE 0 END) / ? as monthly_expense ", [$months, $months]) ->first(); // Gastos del mes actual $currentMonth = Transaction::where('user_id', $userId) ->whereMonth('transaction_date', now()->month) ->whereYear('transaction_date', now()->year) ->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(); // Días transcurridos y restantes $daysElapsed = now()->day; $daysInMonth = now()->daysInMonth; $daysRemaining = $daysInMonth - $daysElapsed; // Proyección del mes $projectedExpense = ($currentMonth->expense / $daysElapsed) * $daysInMonth; $projectedIncome = ($currentMonth->income / $daysElapsed) * $daysInMonth; return response()->json([ 'historical_average' => [ 'income' => round($historical->monthly_income ?? 0, 2), 'expense' => round($historical->monthly_expense ?? 0, 2), ], 'current_month' => [ 'income' => round($currentMonth->income ?? 0, 2), 'expense' => round($currentMonth->expense ?? 0, 2), 'days_elapsed' => $daysElapsed, 'days_remaining' => $daysRemaining, ], 'projection' => [ 'income' => round($projectedIncome, 2), 'expense' => round($projectedExpense, 2), 'balance' => round($projectedIncome - $projectedExpense, 2), ], 'vs_average' => [ 'income' => ($historical->monthly_income ?? 0) > 0 ? round((($projectedIncome - ($historical->monthly_income ?? 0)) / ($historical->monthly_income ?? 1)) * 100, 1) : 0, 'expense' => ($historical->monthly_expense ?? 0) > 0 ? round((($projectedExpense - ($historical->monthly_expense ?? 0)) / ($historical->monthly_expense ?? 1)) * 100, 1) : 0, ], ]); } /** * Reporte de recurrencias */ public function recurringReport(Request $request) { $userId = Auth::id(); $templates = RecurringTemplate::where('user_id', $userId) ->where('is_active', true) ->with(['category', 'account']) ->get(); $monthlyIncome = $templates->where('type', 'credit')->sum('amount'); $monthlyExpense = $templates->where('type', 'debit')->sum(fn($t) => abs($t->amount)); return response()->json([ 'templates' => $templates->map(function($t) { return [ 'id' => $t->id, 'description' => $t->description, 'amount' => abs($t->amount), 'type' => $t->type, 'frequency' => $t->frequency, 'category' => $t->category ? $t->category->name : null, 'next_date' => $t->next_occurrence_date, ]; }), 'summary' => [ 'total_recurring' => $templates->count(), 'monthly_income' => $monthlyIncome, 'monthly_expense' => $monthlyExpense, 'net_recurring' => $monthlyIncome - $monthlyExpense, ], ]); } }