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 */ 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(); $budget = Budget::create($validated); return response()->json([ 'message' => 'Presupuesto creado', '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 */ public function destroy($id) { $budget = Budget::forUser(Auth::id())->findOrFail($id); $budget->delete(); return response()->json([ 'message' => 'Presupuesto eliminado', ]); } /** * 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 categorías de gasto (debit) del usuario $usedCategoryIds = Budget::forUser(Auth::id()) ->forPeriod($year, $month) ->pluck('category_id') ->toArray(); // Categorías padre con tipo debit $categories = Category::where('user_id', Auth::id()) ->whereNull('parent_id') ->where('type', 'debit') ->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'), 'budget' => $totalBudget, 'spent' => $totalSpent, 'remaining' => $totalBudget - $totalSpent, 'usage_percentage' => $totalBudget > 0 ? round(($totalSpent / $totalBudget) * 100, 1) : 0, ]; } return response()->json([ 'year' => $year, 'monthly' => $monthlyData, 'totals' => [ 'budget' => array_sum(array_column($monthlyData, 'budget')), 'spent' => array_sum(array_column($monthlyData, 'spent')), ], ]); } }