Creada nueva pagina "Reportes"
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\Sale;
|
||||||
|
use App\Models\Expense;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ReportController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
// 1. Totales Generales
|
||||||
|
$totalIncome = Sale::sum('total');
|
||||||
|
$totalOutcome = Expense::where('status', 'Pagado')->sum('total_amount');
|
||||||
|
$balance = $totalIncome - $totalOutcome;
|
||||||
|
|
||||||
|
// 2. Gastos por Categoría (Para Pie Chart)
|
||||||
|
$expensesByCategory = Expense::where('status', 'Pagado')
|
||||||
|
->select('expense_category', DB::raw('SUM(total_amount) as total'))
|
||||||
|
->groupBy('expense_category')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// 3. Ingresos y Egresos por Mes (Últimos 6 meses)
|
||||||
|
$months = [];
|
||||||
|
$monthlyIncomeData = [];
|
||||||
|
$monthlyOutcomeData = [];
|
||||||
|
|
||||||
|
for ($i = 5; $i >= 0; $i--) {
|
||||||
|
$date = now()->subMonths($i);
|
||||||
|
$monthName = $date->translatedFormat('M Y'); // ej: may 2026
|
||||||
|
|
||||||
|
$months[] = ucfirst($monthName);
|
||||||
|
|
||||||
|
// Ingresos del mes
|
||||||
|
$inc = Sale::whereYear('created_at', $date->year)
|
||||||
|
->whereMonth('created_at', $date->month)
|
||||||
|
->sum('total');
|
||||||
|
$monthlyIncomeData[] = $inc;
|
||||||
|
|
||||||
|
// Egresos del mes
|
||||||
|
$out = Expense::where('status', 'Pagado')
|
||||||
|
->whereYear('expense_date', $date->year)
|
||||||
|
->whereMonth('expense_date', $date->month)
|
||||||
|
->sum('total_amount');
|
||||||
|
$monthlyOutcomeData[] = $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('reports.index', compact(
|
||||||
|
'totalIncome',
|
||||||
|
'totalOutcome',
|
||||||
|
'balance',
|
||||||
|
'expensesByCategory',
|
||||||
|
'months',
|
||||||
|
'monthlyIncomeData',
|
||||||
|
'monthlyOutcomeData'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,13 +44,20 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</x-ui.card>
|
</x-ui.card>
|
||||||
|
|
||||||
<!-- Tarjeta Compras -->
|
<!-- Tarjeta Gastos -->
|
||||||
<x-ui.card href="{{ route('expenses.index') }}" title="Gastos" description="Registro de compras y pedidos a proovedores." linkText="Ver Gastos">
|
<x-ui.card href="{{ route('expenses.index') }}" title="Gastos" description="Registro de compras y pedidos a proovedores." linkText="Ver Gastos">
|
||||||
<svg class="w-12 h-12 text-lime-500 dark:text-neon-lime" fill="none" viewBox="0 0 24 24">
|
<svg class="w-12 h-12 text-lime-500 dark:text-neon-lime" fill="none" viewBox="0 0 24 24">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 4h3a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3m0 3h6m-3 5h3m-6 0h.01M12 16h3m-6 0h.01M10 3v4h4V3h-4Z"/>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 4h3a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3m0 3h6m-3 5h3m-6 0h.01M12 16h3m-6 0h.01M10 3v4h4V3h-4Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</x-ui.card>
|
</x-ui.card>
|
||||||
|
|
||||||
|
<!-- Tarjeta Reportes -->
|
||||||
|
<x-ui.card href="{{ route('reports.index') }}" title="Reportes" description="Estadísticas de ingresos y egresos." linkText="Ver Reportes">
|
||||||
|
<svg class="w-12 h-12 text-lime-500 dark:text-neon-lime" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16v-6M8 16V6m4 10v-3m4 3v-8m4 8v-5M4 20h16"/>
|
||||||
|
</svg>
|
||||||
|
</x-ui.card>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Botón de Logout -->
|
<!-- Botón de Logout -->
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<x-layout title="Lauck - Reportes y Estadísticas">
|
||||||
|
<x-section-header subtitle="Gestión Financiera" title="Reportes y " highlight="Estadísticas" />
|
||||||
|
|
||||||
|
<div class="w-full mx-auto pb-10">
|
||||||
|
|
||||||
|
<!-- Tarjetas de Resumen -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||||
|
<!-- Ingresos -->
|
||||||
|
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-500 uppercase tracking-wider font-bold mb-1">Total Ingresos</p>
|
||||||
|
<h3 class="text-3xl font-black text-green-600 dark:text-neon-lime">${{ number_format($totalIncome, 2, ',', '.') }}</h3>
|
||||||
|
<p class="text-xs text-gray-500 mt-2">Ventas realizadas</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-green-200 dark:bg-green-900/50 rounded-lg">
|
||||||
|
<svg class="w-6 h-6 text-green-700 dark:text-neon-lime" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Egresos -->
|
||||||
|
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-500 uppercase tracking-wider font-bold mb-1">Total Egresos</p>
|
||||||
|
<h3 class="text-3xl font-black text-red-600 dark:text-red-500">${{ number_format($totalOutcome, 2, ',', '.') }}</h3>
|
||||||
|
<p class="text-xs text-gray-500 mt-2">Gastos pagados</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-red-200 dark:bg-red-900/50 rounded-lg">
|
||||||
|
<svg class="w-6 h-6 text-red-700 dark:text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"></path></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Balance -->
|
||||||
|
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-500 uppercase tracking-wider font-bold mb-1">Balance Neto</p>
|
||||||
|
<h3 class="text-3xl font-black {{ $balance >= 0 ? 'text-green-600 dark:text-neon-lime' : 'text-red-600 dark:text-red-500' }}">
|
||||||
|
${{ number_format($balance, 2, ',', '.') }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-gray-500 mt-2">Ingresos - Egresos</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-gray-300 dark:bg-neutral-800 rounded-lg">
|
||||||
|
<svg class="w-6 h-6 text-gray-700 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3"></path></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gráficos -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<!-- Gráfico de Líneas/Barras -->
|
||||||
|
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
|
||||||
|
<h3 class="text-lg font-bold text-neutral-900 dark:text-white mb-4">Evolución de Ingresos y Egresos (Últimos 6 meses)</h3>
|
||||||
|
<div class="relative h-72 w-full">
|
||||||
|
<canvas id="balanceChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gráfico de Torta -->
|
||||||
|
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
|
||||||
|
<h3 class="text-lg font-bold text-neutral-900 dark:text-white mb-4">Distribución de Gastos por Categoría</h3>
|
||||||
|
<div class="relative h-72 w-full flex justify-center">
|
||||||
|
<canvas id="expensesChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cargar Chart.js desde CDN -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Colores basados en el tema actual (usaremos Tailwind colors aprox)
|
||||||
|
const isDark = document.documentElement.classList.contains('dark');
|
||||||
|
const textColor = isDark ? '#e5e7eb' : '#111827';
|
||||||
|
const gridColor = isDark ? '#374151' : '#d1d5db';
|
||||||
|
|
||||||
|
// --- GRÁFICO DE BALANCE (Barras) ---
|
||||||
|
const ctxBalance = document.getElementById('balanceChart').getContext('2d');
|
||||||
|
const months = @json($months);
|
||||||
|
const incomeData = @json($monthlyIncomeData);
|
||||||
|
const outcomeData = @json($monthlyOutcomeData);
|
||||||
|
|
||||||
|
new Chart(ctxBalance, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: months,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Ingresos',
|
||||||
|
data: incomeData,
|
||||||
|
backgroundColor: 'rgba(132, 204, 22, 0.7)', // neon-lime ish
|
||||||
|
borderColor: 'rgb(101, 163, 13)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Egresos',
|
||||||
|
data: outcomeData,
|
||||||
|
backgroundColor: 'rgba(239, 68, 68, 0.7)', // red-500
|
||||||
|
borderColor: 'rgb(220, 38, 38)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { labels: { color: textColor } }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: { color: textColor },
|
||||||
|
grid: { color: gridColor, drawBorder: false }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
ticks: { color: textColor },
|
||||||
|
grid: { color: gridColor, drawBorder: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- GRÁFICO DE GASTOS POR CATEGORÍA (Torta) ---
|
||||||
|
const ctxExpenses = document.getElementById('expensesChart').getContext('2d');
|
||||||
|
|
||||||
|
const rawCategories = @json($expensesByCategory);
|
||||||
|
const catLabels = rawCategories.map(item => item.expense_category);
|
||||||
|
const catData = rawCategories.map(item => item.total);
|
||||||
|
|
||||||
|
// Paleta de colores para las categorías
|
||||||
|
const pieColors = [
|
||||||
|
'#3b82f6', // blue-500
|
||||||
|
'#f59e0b', // amber-500
|
||||||
|
'#10b981', // emerald-500
|
||||||
|
'#8b5cf6', // violet-500
|
||||||
|
'#ec4899', // pink-500
|
||||||
|
];
|
||||||
|
|
||||||
|
new Chart(ctxExpenses, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: catLabels,
|
||||||
|
datasets: [{
|
||||||
|
data: catData,
|
||||||
|
backgroundColor: pieColors,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: isDark ? '#1f2937' : '#ffffff' // panel-bg color aprox
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right',
|
||||||
|
labels: { color: textColor, padding: 20 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cutout: '60%'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</x-layout>
|
||||||
@@ -60,6 +60,7 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::post('suppliers/{supplier}/order', [SupplierController::class, 'storeOrder'])->name('suppliers.storeOrder');
|
Route::post('suppliers/{supplier}/order', [SupplierController::class, 'storeOrder'])->name('suppliers.storeOrder');
|
||||||
Route::resource('suppliers', SupplierController::class);
|
Route::resource('suppliers', SupplierController::class);
|
||||||
Route::resource('expenses', \App\Http\Controllers\ExpenseController::class);
|
Route::resource('expenses', \App\Http\Controllers\ExpenseController::class);
|
||||||
|
Route::get('reports', [\App\Http\Controllers\ReportController::class, 'index'])->name('reports.index');
|
||||||
Route::resource('productos', ProductosController::class)->parameters([
|
Route::resource('productos', ProductosController::class)->parameters([
|
||||||
'productos' => 'product'
|
'productos' => 'product'
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user