get('year', now()->year); $month = $request->get('month', now()->month); $budgets = Budget::forUser(Auth::id()) ->forPeriod($year, $month) ->active() ->with('category') ->orderBy('amount', 'desc') ->get(); // Calcular totales $totalBudget = $budgets->sum('amount'); $totalSpent = $budgets->sum('spent_amount'); $totalRemaining = $totalBudget - $totalSpent; return response()->json([ 'data' => $budgets, 'summary' => [ 'total_budget' => $totalBudget, 'total_spent' => $totalSpent, 'total_remaining' => $totalRemaining, 'usage_percentage' => $totalBudget > 0 ? round(($totalSpent / $totalBudget) * 100, 1) : 0, 'year' => $year, 'month' => $month, 'period_label' => Carbon::createFromDate($year, $month, 1)->format('F Y'), ], ]); } /** * Crear un presupuesto (con propagación automática a meses futuros) */ public function store(Request $request) { $validated = $request->validate([ 'category_id' => 'nullable|exists:categories,id', 'name' => 'nullable|string|max:255', 'amount' => 'required|numeric|min:0.01', 'currency' => 'nullable|string|size:3', 'year' => 'required|integer|min:2020|max:2100', 'month' => 'required|integer|min:1|max:12', 'period_type' => 'nullable|in:monthly,yearly', 'notes' => 'nullable|string', ]); // Verificar que no exista ya $exists = Budget::forUser(Auth::id()) ->where('category_id', $validated['category_id']) ->where('year', $validated['year']) ->where('month', $validated['month']) ->exists(); if ($exists) { return response()->json([ 'message' => 'Ya existe un presupuesto para esta categoría en este período', ], 422); } $validated['user_id'] = Auth::id(); // Crear el presupuesto del mes actual $budget = Budget::create($validated); // Propagar automáticamente a los 12 meses siguientes $currentYear = $validated['year']; $currentMonth = $validated['month']; for ($i = 1; $i <= 12; $i++) { $nextMonth = $currentMonth + $i; $nextYear = $currentYear; if ($nextMonth > 12) { $nextMonth -= 12; $nextYear++; } // Solo crear si no existe $existsNext = Budget::forUser(Auth::id()) ->where('category_id', $validated['category_id']) ->where('year', $nextYear) ->where('month', $nextMonth) ->exists(); if (!$existsNext) { Budget::create([ 'user_id' => Auth::id(), 'category_id' => $validated['category_id'], 'name' => $validated['name'] ?? null, 'amount' => $validated['amount'], 'currency' => $validated['currency'] ?? null, 'year' => $nextYear, 'month' => $nextMonth, 'period_type' => $validated['period_type'] ?? 'monthly', 'notes' => $validated['notes'] ?? null, ]); } } return response()->json([ 'message' => 'Presupuesto creado y propagado', 'data' => $budget->load('category'), ], 201); } /** * Ver un presupuesto */ public function show($id) { $budget = Budget::forUser(Auth::id()) ->with('category') ->findOrFail($id); // Obtener transacciones del período $query = Transaction::where('user_id', Auth::id()) ->where('transaction_type', 'debit') ->whereYear('effective_date', $budget->year) ->whereMonth('effective_date', $budget->month); if ($budget->category_id) { $categoryIds = [$budget->category_id]; $subcategories = Category::where('parent_id', $budget->category_id)->pluck('id')->toArray(); $categoryIds = array_merge($categoryIds, $subcategories); $query->whereIn('category_id', $categoryIds); } $transactions = $query->with('category') ->orderBy('effective_date', 'desc') ->limit(50) ->get(); return response()->json([ 'budget' => $budget, 'transactions' => $transactions, ]); } /** * Actualizar un presupuesto */ public function update(Request $request, $id) { $budget = Budget::forUser(Auth::id())->findOrFail($id); $validated = $request->validate([ 'amount' => 'sometimes|numeric|min:0.01', 'notes' => 'nullable|string', 'is_active' => 'sometimes|boolean', ]); $budget->update($validated); return response()->json([ 'message' => 'Presupuesto actualizado', 'data' => $budget->fresh('category'), ]); } /** * Eliminar un presupuesto (y de meses futuros) */ public function destroy($id) { $budget = Budget::forUser(Auth::id())->findOrFail($id); $categoryId = $budget->category_id; $year = $budget->year; $month = $budget->month; // Eliminar este y todos los futuros de la misma categoría Budget::forUser(Auth::id()) ->where('category_id', $categoryId) ->where(function($q) use ($year, $month) { $q->where('year', '>', $year) ->orWhere(function($q2) use ($year, $month) { $q2->where('year', $year) ->where('month', '>=', $month); }); }) ->delete(); return response()->json([ 'message' => 'Presupuesto eliminado (incluyendo meses futuros)', ]); } /** * Copiar presupuestos al siguiente mes */ public function copyToNextMonth(Request $request) { $validated = $request->validate([ 'year' => 'required|integer', 'month' => 'required|integer|min:1|max:12', ]); Budget::copyToNextMonth(Auth::id(), $validated['year'], $validated['month']); $nextMonth = $validated['month'] === 12 ? 1 : $validated['month'] + 1; $nextYear = $validated['month'] === 12 ? $validated['year'] + 1 : $validated['year']; return response()->json([ 'message' => 'Presupuestos copiados', 'next_period' => [ 'year' => $nextYear, 'month' => $nextMonth, ], ]); } /** * Obtener categorías disponibles para presupuesto */ public function availableCategories(Request $request) { $year = $request->get('year', now()->year); $month = $request->get('month', now()->month); // Obtener IDs de categorías ya usadas en el período $usedCategoryIds = Budget::forUser(Auth::id()) ->forPeriod($year, $month) ->pluck('category_id') ->toArray(); // Categorías padre con tipo expense o both (gastos) $categories = Category::where('user_id', Auth::id()) ->whereNull('parent_id') ->whereIn('type', ['expense', 'both']) ->whereNotIn('id', $usedCategoryIds) ->orderBy('name') ->get(); return response()->json($categories); } /** * Resumen anual de presupuestos */ public function yearSummary(Request $request) { $year = $request->get('year', now()->year); $monthlyData = []; for ($month = 1; $month <= 12; $month++) { $budgets = Budget::forUser(Auth::id()) ->forPeriod($year, $month) ->active() ->get(); $totalBudget = $budgets->sum('amount'); $totalSpent = $budgets->sum('spent_amount'); $monthlyData[] = [ 'month' => $month, 'month_name' => Carbon::createFromDate($year, $month, 1)->format('M'), 'budgeted' => round($totalBudget, 2), 'spent' => round($totalSpent, 2), 'remaining' => round($totalBudget - $totalSpent, 2), 'percentage' => $totalBudget > 0 ? round(($totalSpent / $totalBudget) * 100, 1) : 0, ]; } return response()->json($monthlyData); } }