1206 lines
52 KiB
PHP
1206 lines
52 KiB
PHP
<?php
|
|
require_once 'conexion.php';
|
|
session_start();
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
$tipo_reporte = $_GET['tipo_reporte'] ?? 'financiero';
|
|
$tipo = $_GET['tipo'] ?? '';
|
|
$fecha = $_GET['fecha'] ?? date('Y-m-d');
|
|
$semana = $_GET['semana'] ?? '';
|
|
$mes = $_GET['mes'] ?? '';
|
|
$anio = $_GET['anio'] ?? '';
|
|
$tipo_cancha = $_GET['tipo_cancha'] ?? '';
|
|
$id_usuario_logueado = $_SESSION['id_usuario'] ?? null;
|
|
|
|
// Función para obtener gastos fijos de un período
|
|
function obtenerGastosFijos($conexion, $fecha_inicio, $fecha_fin) {
|
|
$sql = "
|
|
SELECT * FROM gastos
|
|
WHERE Periodo_Desde <= '$fecha_fin'
|
|
AND (Periodo_Hasta IS NULL OR Periodo_Hasta >= '$fecha_inicio')
|
|
ORDER BY Periodo_Desde DESC
|
|
LIMIT 1
|
|
";
|
|
|
|
$result = $conexion->query($sql);
|
|
$total_gastos = 0;
|
|
$gastos_detalles = [];
|
|
|
|
if ($result && $result->num_rows > 0) {
|
|
$row = $result->fetch_assoc();
|
|
|
|
// Sumar todos los valores numéricos excepto ID_Gasto, Periodo_Desde, Periodo_Hasta
|
|
foreach ($row as $columna => $valor) {
|
|
if (!in_array($columna, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta']) && is_numeric($valor) && floatval($valor) > 0) {
|
|
$total_gastos += floatval($valor);
|
|
$gastos_detalles[] = [
|
|
'descripcion' => ucfirst($columna),
|
|
'monto' => floatval($valor)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return ['total' => $total_gastos, 'detalles' => $gastos_detalles];
|
|
}
|
|
|
|
function obtenerHorasDisponiblesPorPeriodo($conexion, $fecha_inicio, $fecha_fin) {
|
|
$total_horas = 0;
|
|
$fecha = new DateTime($fecha_inicio);
|
|
$fecha_fin_dt = new DateTime($fecha_fin);
|
|
|
|
while ($fecha <= $fecha_fin_dt) {
|
|
$diaSemana = intval($fecha->format('N'));
|
|
$diaTexto = $diaSemana >= 1 && $diaSemana <= 5 ? 'Lunes a Viernes' : ($diaSemana === 6 ? 'Sábado' : 'Domingo');
|
|
|
|
$sqlHorario = "SELECT Hora_Apertura, Hora_Cierre FROM horarios WHERE Dia = '$diaTexto' LIMIT 1";
|
|
$resultHorario = $conexion->query($sqlHorario);
|
|
if ($resultHorario && $rowHorario = $resultHorario->fetch_assoc()) {
|
|
$horaApertura = $rowHorario['Hora_Apertura'];
|
|
$horaCierre = $rowHorario['Hora_Cierre'];
|
|
if ($horaApertura && $horaCierre) {
|
|
$horasDia = (strtotime($horaCierre) - strtotime($horaApertura)) / 3600;
|
|
if ($horasDia > 0) {
|
|
$total_horas += $horasDia;
|
|
}
|
|
}
|
|
}
|
|
$fecha->modify('+1 day');
|
|
}
|
|
|
|
return $total_horas;
|
|
}
|
|
|
|
try {
|
|
// ===== REPORTE FINANCIERO =====
|
|
if ($tipo_reporte === 'financiero') {
|
|
|
|
// ===== REPORTE DIARIO =====
|
|
if ($tipo === 'diario') {
|
|
// Filtro opcional: si hay usuario logueado, mostrar solo sus operaciones
|
|
$filtro_venta = $id_usuario_logueado ? "AND v.ID_Usuario = $id_usuario_logueado" : "";
|
|
$filtro_reserva = $id_usuario_logueado ? "AND r.ID_Usuario = $id_usuario_logueado" : "";
|
|
$filtro_orden = $id_usuario_logueado ? "AND oc.ID_Usuario = $id_usuario_logueado" : "";
|
|
|
|
$sql = "
|
|
SELECT
|
|
DATE(v.Fecha) as fecha,
|
|
TIME(v.Fecha) as hora,
|
|
'Venta Cantina' as tipo_operacion,
|
|
GROUP_CONCAT(CONCAT(vp.Cantidad, 'x ', p.Descripcion) SEPARATOR ', ') as descripcion,
|
|
COALESCE(SUM(vp.Cantidad * p.Precio_Venta), 0) as monto,
|
|
COALESCE(CONCAT(u.Nombre, ' ', u.Apellido), 'Sistema') as usuario_registro
|
|
FROM ventas v
|
|
LEFT JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
|
LEFT JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
|
LEFT JOIN usuarios u ON v.ID_Usuario = u.ID_Usuario
|
|
WHERE DATE(v.Fecha) = '$fecha' $filtro_venta
|
|
GROUP BY v.ID_Venta, v.Fecha, v.ID_Usuario
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
DATE(r.Fecha_Hora) as fecha,
|
|
TIME(r.Fecha_Hora) as hora,
|
|
CONCAT('Reserva ', CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END) as tipo_operacion,
|
|
CONCAT(cl.Nombre, ' - Cancha ', r.Numero, ' - ', TIME(r.Fecha_Hora), ' hs') as descripcion,
|
|
r.Monto as monto,
|
|
COALESCE(CONCAT(u.Nombre, ' ', u.Apellido), 'Sistema') as usuario_registro
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
JOIN clientes cl ON r.ID_Cliente = cl.ID_Cliente
|
|
LEFT JOIN usuarios u ON r.ID_Usuario = u.ID_Usuario
|
|
WHERE DATE(r.Fecha_Hora) = '$fecha' $filtro_reserva
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
DATE(oc.Fecha) as fecha,
|
|
TIME(oc.Fecha) as hora,
|
|
'Orden de Compra' as tipo_operacion,
|
|
CONCAT('Proveedor: ', pr.Nombre) as descripcion,
|
|
-COALESCE(SUM(op.Cantidad * p.Precio_Compra), 0) as monto,
|
|
COALESCE(CONCAT(u.Nombre, ' ', u.Apellido), 'Sistema') as usuario_registro
|
|
FROM ordenes_compra oc
|
|
JOIN ordenes_productos op ON oc.ID_Orden = op.ID_Orden
|
|
JOIN productos p ON op.ID_Producto = p.ID_Producto
|
|
JOIN proveedores pr ON oc.ID_Proveedor = pr.ID_Proveedor
|
|
LEFT JOIN usuarios u ON oc.ID_Usuario = u.ID_Usuario
|
|
WHERE oc.Estado = 1 AND DATE(oc.Fecha) = '$fecha' $filtro_orden
|
|
GROUP BY oc.ID_Orden, oc.Fecha, oc.ID_Usuario
|
|
|
|
ORDER BY hora ASC
|
|
";
|
|
|
|
$result = $conexion->query($sql);
|
|
if (!$result) {
|
|
throw new Exception("Error: " . $conexion->error);
|
|
}
|
|
|
|
$transacciones = [];
|
|
$ingresos_reservas = 0;
|
|
$ingresos_cantina = 0;
|
|
$egresos = 0;
|
|
|
|
while ($row = $result->fetch_assoc()) {
|
|
$transacciones[] = $row;
|
|
$tipo_op = $row['tipo_operacion'];
|
|
|
|
if (strpos($tipo_op, 'Reserva') !== false) {
|
|
$ingresos_reservas += floatval($row['monto']);
|
|
} elseif ($tipo_op === 'Venta Cantina') {
|
|
$ingresos_cantina += floatval($row['monto']);
|
|
} else {
|
|
$egresos += abs(floatval($row['monto']));
|
|
}
|
|
}
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'diario',
|
|
'periodo' => $fecha,
|
|
'transacciones' => $transacciones,
|
|
'resumen' => [
|
|
'ingresos_reservas' => $ingresos_reservas,
|
|
'ingresos_cantina' => $ingresos_cantina,
|
|
'total_ingresos' => $ingresos_reservas + $ingresos_cantina,
|
|
'egresos' => $egresos,
|
|
'saldo_neto' => ($ingresos_reservas + $ingresos_cantina) - $egresos
|
|
]
|
|
]);
|
|
}
|
|
|
|
// ===== REPORTE SEMANAL =====
|
|
elseif ($tipo === 'semanal') {
|
|
// Obtener el rango de fechas de la semana
|
|
if (!$semana) {
|
|
$semana = date('Y-W');
|
|
}
|
|
|
|
// Parsear la semana ISO (formato YYYY-Www)
|
|
$semanaPartes = explode('-W', $semana);
|
|
if (count($semanaPartes) !== 2) {
|
|
throw new Exception("Formato de semana inválido");
|
|
}
|
|
|
|
$anio_s = intval($semanaPartes[0]);
|
|
$semana_s = intval($semanaPartes[1]);
|
|
|
|
// Calcular el lunes de la semana ISO especificada
|
|
// Crear una fecha para el jueves de la primera semana del año (para cálculo ISO correcto)
|
|
$fecha_base = new DateTime($anio_s . '-01-04'); // 4 de enero siempre está en semana 1
|
|
|
|
// Retroceder al lunes de esa semana
|
|
$dia_semana = $fecha_base->format('N'); // 1=lunes, 7=domingo
|
|
$fecha_base->modify('-' . ($dia_semana - 1) . ' days');
|
|
|
|
// Sumar las semanas necesarias
|
|
$fecha_base->modify('+' . ($semana_s - 1) . ' weeks');
|
|
|
|
$fecha_inicio = $fecha_base->format('Y-m-d');
|
|
$fecha_fin = $fecha_base->modify('+6 days')->format('Y-m-d');
|
|
|
|
// Resetear fecha_base para los cálculos posteriores
|
|
$fecha_base = new DateTime($fecha_inicio);
|
|
|
|
$sql = "
|
|
SELECT
|
|
DATE(r.Fecha_Hora) as dia,
|
|
DAYNAME(r.Fecha_Hora) as nombre_dia,
|
|
CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END as tipo_cancha,
|
|
COUNT(r.ID_Reserva) as cantidad_reservas,
|
|
COALESCE(SUM(r.Monto), 0) as ingreso_reservas
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
GROUP BY DATE(r.Fecha_Hora), c.Tipo
|
|
ORDER BY DATE(r.Fecha_Hora), tipo_cancha
|
|
";
|
|
|
|
$result = $conexion->query($sql);
|
|
$reservas_por_dia = [];
|
|
|
|
$dias_map = [
|
|
'Monday' => 'Lunes',
|
|
'Tuesday' => 'Martes',
|
|
'Wednesday' => 'Miércoles',
|
|
'Thursday' => 'Jueves',
|
|
'Friday' => 'Viernes',
|
|
'Saturday' => 'Sábado',
|
|
'Sunday' => 'Domingo'
|
|
];
|
|
|
|
while ($row = $result->fetch_assoc()) {
|
|
$dia = $row['dia'];
|
|
if (!isset($reservas_por_dia[$dia])) {
|
|
$reservas_por_dia[$dia] = [
|
|
'dia' => $dia,
|
|
'nombre_dia' => $row['nombre_dia'],
|
|
'ingresos_reservas' => 0,
|
|
'detalles_cancha' => []
|
|
];
|
|
}
|
|
$reservas_por_dia[$dia]['ingresos_reservas'] += floatval($row['ingreso_reservas']);
|
|
$reservas_por_dia[$dia]['detalles_cancha'][] = [
|
|
'tipo' => $row['tipo_cancha'],
|
|
'cantidad' => $row['cantidad_reservas'],
|
|
'ingreso' => $row['ingreso_reservas']
|
|
];
|
|
}
|
|
|
|
// Ingresos Cantina por día
|
|
$sql_cantina = "
|
|
SELECT
|
|
DATE(v.Fecha) as dia,
|
|
COALESCE(SUM(vp.Cantidad * p.Precio_Venta), 0) as ingreso_cantina,
|
|
COALESCE(SUM((vp.Cantidad * p.Precio_Venta) - (vp.Cantidad * p.Precio_Compra)), 0) as ganancia
|
|
FROM ventas v
|
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
|
WHERE DATE(v.Fecha) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
GROUP BY DATE(v.Fecha)
|
|
";
|
|
|
|
$result_cantina = $conexion->query($sql_cantina);
|
|
$cantina_por_dia = [];
|
|
|
|
while ($row = $result_cantina->fetch_assoc()) {
|
|
$cantina_por_dia[$row['dia']] = floatval($row['ingreso_cantina']);
|
|
}
|
|
|
|
// Costo de Mercadería por día
|
|
$sql_compras = "
|
|
SELECT
|
|
DATE(oc.Fecha) as dia,
|
|
COALESCE(SUM(op.Cantidad * p.Precio_Compra), 0) as costo_mercaderia
|
|
FROM ordenes_compra oc
|
|
JOIN ordenes_productos op ON oc.ID_Orden = op.ID_Orden
|
|
JOIN productos p ON op.ID_Producto = p.ID_Producto
|
|
WHERE oc.Estado = 1
|
|
AND DATE(oc.Fecha) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
GROUP BY DATE(oc.Fecha)
|
|
";
|
|
|
|
$result_compras = $conexion->query($sql_compras);
|
|
$compras_por_dia = [];
|
|
|
|
while ($row = $result_compras->fetch_assoc()) {
|
|
$compras_por_dia[$row['dia']] = floatval($row['costo_mercaderia']);
|
|
}
|
|
|
|
// Gastos fijos para el período
|
|
$gastos_fijos_data = obtenerGastosFijos($conexion, $fecha_inicio, $fecha_fin);
|
|
$gastos_totales = $gastos_fijos_data['total'];
|
|
|
|
// Ocupación
|
|
$sql_ocupacion = "
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN r.Estado != 2 THEN c.Duracion ELSE 0 END), 0) as minutos_reservados
|
|
, COALESCE(SUM(CASE WHEN r.Estado != 2 THEN 1 ELSE 0 END), 0) as turnos_ocupados
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
$sql_disponibilidad = "
|
|
SELECT
|
|
COUNT(*) as cantidad_canchas
|
|
FROM canchas
|
|
";
|
|
|
|
$result_ocu = $conexion->query($sql_ocupacion);
|
|
$result_disp = $conexion->query($sql_disponibilidad);
|
|
|
|
$minutos_reservados = 0;
|
|
$turnos_disponibles = 1; // para evitar división por cero
|
|
$horas_disponibles_por_cancha = obtenerHorasDisponiblesPorPeriodo($conexion, $fecha_inicio, $fecha_fin);
|
|
|
|
if ($result_ocu) {
|
|
$row = $result_ocu->fetch_assoc();
|
|
$minutos_reservados = intval($row['minutos_reservados'] ?? 0);
|
|
$turnos_ocupados = intval($row['turnos_ocupados'] ?? 0);
|
|
}
|
|
|
|
if ($result_disp) {
|
|
$row = $result_disp->fetch_assoc();
|
|
$cantidad_canchas = intval($row['cantidad_canchas'] ?? 1);
|
|
$turnos_disponibles = $cantidad_canchas * $horas_disponibles_por_cancha;
|
|
}
|
|
|
|
$horas_reservadas = $minutos_reservados / 60;
|
|
$ocupacion = ($turnos_disponibles > 0) ? round(($horas_reservadas / $turnos_disponibles) * 100, 2) : 0;
|
|
|
|
// Consolidar datos por día
|
|
$dias_map_semana = [
|
|
'Monday' => 'Lunes',
|
|
'Tuesday' => 'Martes',
|
|
'Wednesday' => 'Miércoles',
|
|
'Thursday' => 'Jueves',
|
|
'Friday' => 'Viernes',
|
|
'Saturday' => 'Sábado',
|
|
'Sunday' => 'Domingo'
|
|
];
|
|
|
|
$semana_datos = [];
|
|
for ($i = 0; $i < 7; $i++) {
|
|
$dia_actual = date('Y-m-d', strtotime($fecha_inicio . ' +' . $i . ' days'));
|
|
|
|
$ingresos_res = isset($reservas_por_dia[$dia_actual]) ? floatval($reservas_por_dia[$dia_actual]['ingresos_reservas']) : 0;
|
|
$ingresos_cant = isset($cantina_por_dia[$dia_actual]) ? floatval($cantina_por_dia[$dia_actual]) : 0;
|
|
$costo_merc = isset($compras_por_dia[$dia_actual]) ? floatval($compras_por_dia[$dia_actual]) : 0;
|
|
|
|
$total_ingresos = $ingresos_res + $ingresos_cant;
|
|
$saldo_neto = $total_ingresos - $costo_merc;
|
|
|
|
$nombre_dia_ingles = date('l', strtotime($dia_actual));
|
|
$nombre_dia_es = isset($dias_map_semana[$nombre_dia_ingles]) ? $dias_map_semana[$nombre_dia_ingles] : $nombre_dia_ingles;
|
|
|
|
$semana_datos[] = [
|
|
'dia' => $dia_actual,
|
|
'nombre_dia' => $nombre_dia_es,
|
|
'ingresos_reservas' => $ingresos_res,
|
|
'ingresos_cantina' => $ingresos_cant,
|
|
'costo_mercaderia' => $costo_merc,
|
|
'total_ingresos' => $total_ingresos,
|
|
'saldo_neto' => $saldo_neto
|
|
];
|
|
}
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'semanal',
|
|
'periodo' => $semana,
|
|
'fecha_inicio' => $fecha_inicio,
|
|
'fecha_fin' => $fecha_fin,
|
|
'dias' => $semana_datos,
|
|
'resumen' => [
|
|
'ingresos_reservas_totales' => array_sum(array_column($semana_datos, 'ingresos_reservas')),
|
|
'ingresos_cantina_totales' => array_sum(array_column($semana_datos, 'ingresos_cantina')),
|
|
'costo_mercaderia_total' => array_sum(array_column($semana_datos, 'costo_mercaderia')),
|
|
'total_ingresos' => array_sum(array_column($semana_datos, 'total_ingresos')),
|
|
'saldo_neto_semana' => array_sum(array_column($semana_datos, 'saldo_neto')),
|
|
'ocupacion_promedio' => $ocupacion,
|
|
'turnos_ocupados' => $turnos_ocupados,
|
|
'turnos_disponibles' => $turnos_disponibles,
|
|
'gastos_fijos_estimados' => $gastos_totales / 4 // Aproximado a una semana
|
|
]
|
|
]);
|
|
}
|
|
|
|
// ===== REPORTE MENSUAL =====
|
|
elseif ($tipo === 'mensual') {
|
|
if (!$mes) {
|
|
$mes = date('Y-m');
|
|
}
|
|
|
|
list($anio_m, $mes_m) = explode('-', $mes);
|
|
$fecha_inicio = $anio_m . '-' . $mes_m . '-01';
|
|
$fecha_fin = date('Y-m-d', strtotime($fecha_inicio . ' +1 month -1 day'));
|
|
|
|
// Ingresos por categoría
|
|
$categorias = ['Fútbol', 'Pádel', 'Quincho', 'Cantina'];
|
|
$datos_categorias = [];
|
|
|
|
// Reservas (Fútbol, Pádel, Quincho)
|
|
$sql_futbol = "
|
|
SELECT
|
|
'Fútbol' as categoria,
|
|
COUNT(r.ID_Reserva) as operaciones,
|
|
COALESCE(SUM(r.Monto), 0) as monto
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE c.Tipo = 'F' AND DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
$sql_padel = "
|
|
SELECT
|
|
'Pádel' as categoria,
|
|
COUNT(r.ID_Reserva) as operaciones,
|
|
COALESCE(SUM(r.Monto), 0) as monto
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE c.Tipo = 'P' AND DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
$sql_quincho = "
|
|
SELECT
|
|
'Quincho' as categoria,
|
|
COUNT(r.ID_Reserva) as operaciones,
|
|
COALESCE(SUM(r.Monto), 0) as monto
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE c.Tipo = 'Q' AND DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
$sql_cantina = "
|
|
SELECT
|
|
'Cantina' as categoria,
|
|
COUNT(DISTINCT v.ID_Venta) as operaciones,
|
|
COALESCE(SUM(vp.Cantidad * p.Precio_Venta), 0) as monto
|
|
FROM ventas v
|
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
|
WHERE DATE(v.Fecha) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
foreach ([$sql_futbol, $sql_padel, $sql_quincho, $sql_cantina] as $sql) {
|
|
$result = $conexion->query($sql);
|
|
if ($result && $result->num_rows > 0) {
|
|
$row = $result->fetch_assoc();
|
|
$datos_categorias[] = [
|
|
'categoria' => $row['categoria'],
|
|
'operaciones' => intval($row['operaciones']),
|
|
'monto' => floatval($row['monto'])
|
|
];
|
|
} else {
|
|
$datos_categorias[] = [
|
|
'categoria' => $row['categoria'] ?? '',
|
|
'operaciones' => 0,
|
|
'monto' => 0
|
|
];
|
|
}
|
|
}
|
|
|
|
// Costo de Mercadería
|
|
$sql_costo = "
|
|
SELECT
|
|
'Costo de Mercadería' as concepto,
|
|
COALESCE(SUM(op.Cantidad * p.Precio_Compra), 0) as monto
|
|
FROM ordenes_compra oc
|
|
JOIN ordenes_productos op ON oc.ID_Orden = op.ID_Orden
|
|
JOIN productos p ON op.ID_Producto = p.ID_Producto
|
|
WHERE oc.Estado = 1
|
|
AND DATE(oc.Fecha) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
";
|
|
|
|
$result_costo = $conexion->query($sql_costo);
|
|
$costo_mercaderia = 0;
|
|
if ($result_costo && $result_costo->num_rows > 0) {
|
|
$row = $result_costo->fetch_assoc();
|
|
$costo_mercaderia = floatval($row['monto']);
|
|
}
|
|
|
|
// Gastos fijos
|
|
$gastos_fijos_data = obtenerGastosFijos($conexion, $fecha_inicio, $fecha_fin);
|
|
$gastos_fijos = [];
|
|
foreach ($gastos_fijos_data['detalles'] as $gasto) {
|
|
$gastos_fijos[] = [
|
|
'descripcion' => $gasto['descripcion'],
|
|
'monto' => $gasto['monto']
|
|
];
|
|
}
|
|
$total_gastos_fijos = $gastos_fijos_data['total'];
|
|
|
|
// Calcular ganancias
|
|
$ingresos_totales = array_sum(array_column($datos_categorias, 'monto'));
|
|
$ganancia_bruta = $ingresos_totales - $costo_mercaderia;
|
|
$ganancia_neta = $ganancia_bruta - $total_gastos_fijos;
|
|
|
|
// Obtener el nombre del mes en español
|
|
$meses_nombres = [
|
|
'01' => 'enero', '02' => 'febrero', '03' => 'marzo', '04' => 'abril',
|
|
'05' => 'mayo', '06' => 'junio', '07' => 'julio', '08' => 'agosto',
|
|
'09' => 'septiembre', '10' => 'octubre', '11' => 'noviembre', '12' => 'diciembre'
|
|
];
|
|
$nombre_mes = $meses_nombres[$mes_m];
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'mensual',
|
|
'periodo' => $mes,
|
|
'fecha_inicio' => $fecha_inicio,
|
|
'fecha_fin' => $fecha_fin,
|
|
'nombre_mes' => $nombre_mes,
|
|
'categorias' => $datos_categorias,
|
|
'gastos_fijos' => $gastos_fijos,
|
|
'resumen' => [
|
|
'ingresos_totales' => $ingresos_totales,
|
|
'costo_mercaderia' => $costo_mercaderia,
|
|
'gastos_fijos_total' => $total_gastos_fijos,
|
|
'ganancia_bruta' => $ganancia_bruta,
|
|
'ganancia_neta' => $ganancia_neta
|
|
]
|
|
]);
|
|
}
|
|
|
|
// ===== REPORTE ANUAL =====
|
|
elseif ($tipo === 'anual') {
|
|
if (!$anio) {
|
|
$anio = date('Y');
|
|
}
|
|
|
|
$fecha_inicio = $anio . '-01-01';
|
|
$fecha_fin = $anio . '-12-31';
|
|
|
|
// Determinar si es el año actual
|
|
$ano_actual = intval(date('Y'));
|
|
$es_ano_actual = ($anio == $ano_actual);
|
|
$mes_actual = intval(date('m'));
|
|
$mes_limite = $es_ano_actual ? $mes_actual : 12;
|
|
|
|
$meses = [
|
|
'01' => 'Enero', '02' => 'Febrero', '03' => 'Marzo', '04' => 'Abril',
|
|
'05' => 'Mayo', '06' => 'Junio', '07' => 'Julio', '08' => 'Agosto',
|
|
'09' => 'Septiembre', '10' => 'Octubre', '11' => 'Noviembre', '12' => 'Diciembre'
|
|
];
|
|
|
|
$datos_anuales = [];
|
|
|
|
for ($mes_num = 1; $mes_num <= $mes_limite; $mes_num++) {
|
|
$mes_str = str_pad($mes_num, 2, '0', STR_PAD_LEFT);
|
|
$mes_inicio = $anio . '-' . $mes_str . '-01';
|
|
$mes_fin = date('Y-m-d', strtotime($mes_inicio . ' +1 month -1 day'));
|
|
|
|
// Si es el mes actual, solo contar hasta hoy
|
|
if ($es_ano_actual && $mes_num == $mes_actual) {
|
|
$mes_fin = date('Y-m-d');
|
|
}
|
|
|
|
// Ingreso Operativo
|
|
$sql_ingreso = "
|
|
SELECT
|
|
COALESCE(SUM(r.Monto), 0) as reservas,
|
|
(SELECT COALESCE(SUM(vp.Cantidad * p.Precio_Venta), 0)
|
|
FROM ventas v
|
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
|
WHERE DATE(v.Fecha) BETWEEN '$mes_inicio' AND '$mes_fin') as ventas
|
|
FROM reservas r
|
|
WHERE DATE(r.Fecha_Hora) BETWEEN '$mes_inicio' AND '$mes_fin'
|
|
";
|
|
|
|
$result_ingreso = $conexion->query($sql_ingreso);
|
|
$ingreso_operativo = 0;
|
|
|
|
if ($result_ingreso) {
|
|
$row = $result_ingreso->fetch_assoc();
|
|
$ingreso_operativo = floatval($row['reservas']) + floatval($row['ventas']);
|
|
}
|
|
|
|
// Costo Bruto (Mercadería)
|
|
$sql_costo_bruto = "
|
|
SELECT COALESCE(SUM(op.Cantidad * p.Precio_Compra), 0) as mercaderia
|
|
FROM ordenes_compra oc
|
|
JOIN ordenes_productos op ON oc.ID_Orden = op.ID_Orden
|
|
JOIN productos p ON op.ID_Producto = p.ID_Producto
|
|
WHERE oc.Estado = 1
|
|
AND DATE(oc.Fecha) BETWEEN '$mes_inicio' AND '$mes_fin'
|
|
";
|
|
|
|
$result_costo_bruto = $conexion->query($sql_costo_bruto);
|
|
$costo_bruto = 0;
|
|
|
|
if ($result_costo_bruto) {
|
|
$row = $result_costo_bruto->fetch_assoc();
|
|
$costo_bruto = floatval($row['mercaderia']);
|
|
}
|
|
|
|
// Obtener gastos fijos dinámicamente
|
|
$gastos_fijos_data = obtenerGastosFijos($conexion, $mes_inicio, $mes_fin);
|
|
$costo_gastos_fijos = $gastos_fijos_data['total'];
|
|
|
|
$costo_operativo_total = $costo_bruto + $costo_gastos_fijos;
|
|
$ganancia_bruta = $ingreso_operativo - $costo_bruto;
|
|
$ganancia_neta = $ingreso_operativo - $costo_operativo_total;
|
|
|
|
$datos_anuales[] = [
|
|
'mes' => $meses[$mes_str],
|
|
'ingreso_operativo' => $ingreso_operativo,
|
|
'costo_bruto' => $costo_bruto,
|
|
'costo_operativo' => $costo_operativo_total,
|
|
'ganancia_bruta' => $ganancia_bruta,
|
|
'ganancia_neta' => $ganancia_neta
|
|
];
|
|
}
|
|
|
|
// Calcular totales anuales
|
|
$ingreso_operativo_total = array_sum(array_column($datos_anuales, 'ingreso_operativo'));
|
|
$costo_bruto_total = array_sum(array_column($datos_anuales, 'costo_bruto'));
|
|
$costo_operativo_total = array_sum(array_column($datos_anuales, 'costo_operativo'));
|
|
$ganancia_bruta_total = array_sum(array_column($datos_anuales, 'ganancia_bruta'));
|
|
$ganancia_neta_total = array_sum(array_column($datos_anuales, 'ganancia_neta'));
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'anual',
|
|
'periodo' => $anio,
|
|
'es_ano_actual' => $es_ano_actual,
|
|
'mes_actual' => $mes_actual,
|
|
'meses' => $datos_anuales,
|
|
'totales' => [
|
|
'ingreso_operativo_total' => $ingreso_operativo_total,
|
|
'costo_bruto_total' => $costo_bruto_total,
|
|
'costo_operativo_total' => $costo_operativo_total,
|
|
'ganancia_bruta_total' => $ganancia_bruta_total,
|
|
'ganancia_neta_total' => $ganancia_neta_total
|
|
]
|
|
]);
|
|
}
|
|
|
|
} // FIN REPORTE FINANCIERO
|
|
|
|
// ===== REPORTE VENTAS CANTINA =====
|
|
elseif ($tipo_reporte === 'ventas_cantina') {
|
|
$fecha_inicio = '';
|
|
$fecha_fin = '';
|
|
|
|
if ($tipo === 'diario') {
|
|
$fecha_inicio = $fecha;
|
|
$fecha_fin = $fecha;
|
|
} elseif ($tipo === 'semanal') {
|
|
if (!$semana) $semana = date('Y-W');
|
|
$semanaPartes = explode('-W', $semana);
|
|
$anio_s = intval($semanaPartes[0]);
|
|
$semana_s = intval($semanaPartes[1]);
|
|
$fecha_base = new DateTime($anio_s . '-01-04');
|
|
$dia_semana = $fecha_base->format('N');
|
|
$fecha_base->modify('-' . ($dia_semana - 1) . ' days');
|
|
$fecha_base->modify('+' . ($semana_s - 1) . ' weeks');
|
|
$fecha_inicio = $fecha_base->format('Y-m-d');
|
|
$fecha_fin = $fecha_base->modify('+6 days')->format('Y-m-d');
|
|
} elseif ($tipo === 'mensual') {
|
|
if (!$mes) $mes = date('Y-m');
|
|
list($anio_m, $mes_m) = explode('-', $mes);
|
|
$fecha_inicio = $anio_m . '-' . $mes_m . '-01';
|
|
$fecha_fin = date('Y-m-d', strtotime($fecha_inicio . ' +1 month -1 day'));
|
|
} elseif ($tipo === 'anual') {
|
|
if (!$anio) $anio = date('Y');
|
|
$fecha_inicio = $anio . '-01-01';
|
|
$fecha_fin = $anio . '-12-31';
|
|
}
|
|
|
|
// Obtener productos de cantina con ventas
|
|
$sql_productos = "
|
|
SELECT
|
|
p.ID_Producto,
|
|
p.Descripcion as producto,
|
|
'Cantina' as categoria,
|
|
SUM(vp.Cantidad) as cantidad_vendida,
|
|
SUM(vp.Cantidad * p.Precio_Venta) as ingreso_generado,
|
|
COALESCE(SUM(vp.Cantidad * (p.Precio_Venta - p.Precio_Compra)), 0) as ganancia
|
|
FROM productos p
|
|
LEFT JOIN ventas_productos vp ON p.ID_Producto = vp.ID_Producto
|
|
LEFT JOIN ventas v ON vp.ID_Venta = v.ID_Venta
|
|
WHERE DATE(v.Fecha) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
GROUP BY p.ID_Producto, p.Descripcion
|
|
ORDER BY cantidad_vendida DESC
|
|
";
|
|
|
|
$result_productos = $conexion->query($sql_productos);
|
|
if (!$result_productos) {
|
|
throw new Exception("Error en query productos: " . $conexion->error);
|
|
}
|
|
|
|
$productos = [];
|
|
$total_ingreso = 0;
|
|
$total_cantidad = 0;
|
|
|
|
while ($row = $result_productos->fetch_assoc()) {
|
|
$ingreso = floatval($row['ingreso_generado']) ?? 0;
|
|
$productos[] = [
|
|
'producto' => $row['producto'],
|
|
'categoria' => $row['categoria'],
|
|
'cantidad_vendida' => intval($row['cantidad_vendida']) ?? 0,
|
|
'ingreso_generado' => $ingreso,
|
|
'ganancia' => floatval($row['ganancia']) ?? 0
|
|
];
|
|
$total_ingreso += $ingreso;
|
|
$total_cantidad += intval($row['cantidad_vendida']) ?? 0;
|
|
}
|
|
|
|
// Calcular porcentajes sobre el total de unidades vendidas
|
|
foreach ($productos as &$producto) {
|
|
$producto['porcentaje_total'] = $total_cantidad > 0 ? round(($producto['cantidad_vendida'] / $total_cantidad) * 100, 2) : 0;
|
|
}
|
|
unset($producto);
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'ventas_cantina',
|
|
'periodo' => $tipo,
|
|
'fecha_inicio' => $fecha_inicio,
|
|
'fecha_fin' => $fecha_fin,
|
|
'productos' => $productos,
|
|
'resumen' => [
|
|
'total_productos_vendidos' => count($productos),
|
|
'total_cantidad' => $total_cantidad,
|
|
'total_ingreso' => $total_ingreso,
|
|
'ganancia_total' => array_sum(array_column($productos, 'ganancia'))
|
|
]
|
|
]);
|
|
}
|
|
|
|
// ===== REPORTE OCUPACIÓN CANCHAS =====
|
|
elseif ($tipo_reporte === 'ocupacion_canchas') {
|
|
$fecha_inicio = '';
|
|
$fecha_fin = '';
|
|
|
|
if ($tipo === 'diario') {
|
|
$fecha_inicio = $fecha;
|
|
$fecha_fin = $fecha;
|
|
} elseif ($tipo === 'semanal') {
|
|
if (!$semana) $semana = date('Y-W');
|
|
$semanaPartes = explode('-W', $semana);
|
|
$anio_s = intval($semanaPartes[0]);
|
|
$semana_s = intval($semanaPartes[1]);
|
|
$fecha_base = new DateTime($anio_s . '-01-04');
|
|
$dia_semana = $fecha_base->format('N');
|
|
$fecha_base->modify('-' . ($dia_semana - 1) . ' days');
|
|
$fecha_base->modify('+' . ($semana_s - 1) . ' weeks');
|
|
$fecha_inicio = $fecha_base->format('Y-m-d');
|
|
$fecha_fin = $fecha_base->modify('+6 days')->format('Y-m-d');
|
|
} elseif ($tipo === 'mensual') {
|
|
if (!$mes) $mes = date('Y-m');
|
|
list($anio_m, $mes_m) = explode('-', $mes);
|
|
$fecha_inicio = $anio_m . '-' . $mes_m . '-01';
|
|
$fecha_fin = date('Y-m-d', strtotime($fecha_inicio . ' +1 month -1 day'));
|
|
} elseif ($tipo === 'anual') {
|
|
if (!$anio) $anio = date('Y');
|
|
$fecha_inicio = $anio . '-01-01';
|
|
$fecha_fin = $anio . '-12-31';
|
|
}
|
|
$total_dias_periodo = (strtotime($fecha_fin) - strtotime($fecha_inicio)) / (60 * 60 * 24) + 1;
|
|
|
|
// Filtro por tipo de cancha si está especificado
|
|
$tipo_cancha_filtro = $tipo_cancha ?? '';
|
|
$filtro_cancha = '';
|
|
if ($tipo_cancha_filtro) {
|
|
$filtro_cancha = "AND c.Tipo = '$tipo_cancha_filtro'";
|
|
}
|
|
|
|
// 1. Reservas por día
|
|
$sql_por_dia = "
|
|
SELECT
|
|
DATE(r.Fecha_Hora) as dia,
|
|
DAYNAME(r.Fecha_Hora) as nombre_dia,
|
|
COUNT(r.ID_Reserva) as cantidad_reservas,
|
|
COUNT(CASE WHEN r.Estado = 2 THEN 1 END) as canceladas
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
$filtro_cancha
|
|
GROUP BY DATE(r.Fecha_Hora)
|
|
ORDER BY dia ASC
|
|
";
|
|
|
|
$result_dia = $conexion->query($sql_por_dia);
|
|
if (!$result_dia) {
|
|
throw new Exception("Error en query por día: " . $conexion->error);
|
|
}
|
|
|
|
$reservas_por_dia = [];
|
|
$total_reservas = 0;
|
|
$total_canceladas = 0;
|
|
|
|
$dias_map = [
|
|
'Monday' => 'Lunes',
|
|
'Tuesday' => 'Martes',
|
|
'Wednesday' => 'Miércoles',
|
|
'Thursday' => 'Jueves',
|
|
'Friday' => 'Viernes',
|
|
'Saturday' => 'Sábado',
|
|
'Sunday' => 'Domingo'
|
|
];
|
|
|
|
while ($row = $result_dia->fetch_assoc()) {
|
|
$nombre_dia_original = $row['nombre_dia'];
|
|
$nombre_dia_es = isset($dias_map[$nombre_dia_original]) ? $dias_map[$nombre_dia_original] : $nombre_dia_original;
|
|
$reservas_por_dia[] = [
|
|
'dia' => $row['dia'],
|
|
'nombre_dia' => $nombre_dia_es,
|
|
'cantidad_reservas' => intval($row['cantidad_reservas']),
|
|
'canceladas' => intval($row['canceladas'])
|
|
];
|
|
$total_reservas += intval($row['cantidad_reservas']);
|
|
$total_canceladas += intval($row['canceladas']);
|
|
}
|
|
|
|
// 2. Reservas por horario - Considerar duración de cada tipo de cancha
|
|
// Primero obtener todos los tipos de canchas con su duración
|
|
$sql_tipos_canchas = "
|
|
SELECT DISTINCT
|
|
CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END as tipo_nombre,
|
|
c.Tipo as tipo_codigo,
|
|
c.Duracion as duracion_minutos
|
|
FROM canchas c
|
|
WHERE 1=1 $filtro_cancha
|
|
ORDER BY c.Tipo
|
|
";
|
|
|
|
$result_tipos = $conexion->query($sql_tipos_canchas);
|
|
if (!$result_tipos) {
|
|
throw new Exception("Error al obtener tipos de canchas: " . $conexion->error);
|
|
}
|
|
|
|
$tipos_canchas = [];
|
|
while ($row = $result_tipos->fetch_assoc()) {
|
|
$tipos_canchas[] = [
|
|
'tipo_nombre' => $row['tipo_nombre'],
|
|
'tipo_codigo' => $row['tipo_codigo'],
|
|
'duracion_minutos' => intval($row['duracion_minutos'])
|
|
];
|
|
}
|
|
|
|
// Si no hay tipos de canchas, retornar estructura vacía
|
|
if (empty($tipos_canchas)) {
|
|
$tipos_canchas[] = [
|
|
'tipo_nombre' => 'Fútbol',
|
|
'tipo_codigo' => 'F',
|
|
'duracion_minutos' => 60
|
|
];
|
|
}
|
|
|
|
// Generar horarios disponibles por tipo de cancha
|
|
$horarios_por_tipo = [];
|
|
|
|
foreach ($tipos_canchas as $tipo_cancha_info) {
|
|
$tipo_codigo = $tipo_cancha_info['tipo_codigo'];
|
|
$duracion_min = $tipo_cancha_info['duracion_minutos'];
|
|
$horarios_unicos_tipo = [];
|
|
|
|
if ($tipo_codigo === 'Q') {
|
|
$horarios_unicos_tipo['reserva_quincho'] = [
|
|
'key' => 'reserva_quincho',
|
|
'horario' => 'Reserva del Quincho',
|
|
'inicio' => '',
|
|
'fin' => '',
|
|
'es_quincho' => true
|
|
];
|
|
} else {
|
|
$fecha_temp = new DateTime($fecha_inicio);
|
|
$fecha_fin_dt = new DateTime($fecha_fin);
|
|
|
|
while ($fecha_temp <= $fecha_fin_dt) {
|
|
$diaSemana = intval($fecha_temp->format('N'));
|
|
$diaTexto = $diaSemana >= 1 && $diaSemana <= 5 ? 'Lunes a Viernes' : ($diaSemana === 6 ? 'Sábado' : 'Domingo');
|
|
|
|
$sqlHorario = "SELECT Hora_Apertura, Hora_Cierre FROM horarios WHERE Dia = '$diaTexto' LIMIT 1";
|
|
$resultHorario = $conexion->query($sqlHorario);
|
|
if ($resultHorario && $rowHorario = $resultHorario->fetch_assoc()) {
|
|
$horaApertura = $rowHorario['Hora_Apertura'];
|
|
$horaCierre = $rowHorario['Hora_Cierre'];
|
|
if ($horaApertura && $horaCierre) {
|
|
$start_hour = intval(substr($horaApertura, 0, 2));
|
|
$start_min = intval(substr($horaApertura, 3, 2));
|
|
$end_hour = intval(substr($horaCierre, 0, 2));
|
|
$end_min = intval(substr($horaCierre, 3, 2));
|
|
|
|
// Convertir a minutos desde medianoche
|
|
$apertura_minutos = $start_hour * 60 + $start_min;
|
|
$cierre_minutos = $end_hour * 60 + $end_min;
|
|
|
|
// Generar horarios considerando la duración real de la cancha
|
|
$tiempo_actual = $apertura_minutos;
|
|
while ($tiempo_actual + $duracion_min <= $cierre_minutos) {
|
|
$hora_inicio = intval($tiempo_actual / 60);
|
|
$minuto_inicio = $tiempo_actual % 60;
|
|
$hora_fin = $tiempo_actual + $duracion_min;
|
|
$hora_fin_h = intval($hora_fin / 60);
|
|
$minuto_fin = $hora_fin % 60;
|
|
$inicio_texto = sprintf('%02d:%02d', $hora_inicio, $minuto_inicio);
|
|
$fin_texto = sprintf('%02d:%02d', $hora_fin_h, $minuto_fin);
|
|
$horario_formato = $inicio_texto . ' - ' . $fin_texto;
|
|
$horarios_unicos_tipo[$inicio_texto] = [
|
|
'key' => $inicio_texto,
|
|
'horario' => $horario_formato,
|
|
'inicio' => $inicio_texto,
|
|
'fin' => $fin_texto,
|
|
'es_quincho' => false
|
|
];
|
|
$tiempo_actual += $duracion_min;
|
|
}
|
|
}
|
|
}
|
|
$fecha_temp->modify('+1 day');
|
|
}
|
|
}
|
|
|
|
// Guardar horarios por tipo
|
|
ksort($horarios_unicos_tipo);
|
|
$horarios_por_tipo[$tipo_cancha_info['tipo_nombre']] = array_values($horarios_unicos_tipo);
|
|
}
|
|
|
|
// Obtener reservas por horario y tipo de cancha
|
|
$sql_por_horario = "
|
|
SELECT
|
|
CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END as tipo_nombre,
|
|
CASE WHEN c.Tipo = 'Q' THEN 'reserva_quincho' ELSE TIME_FORMAT(r.Fecha_Hora, '%H:%i') END as horario_key,
|
|
CASE WHEN c.Tipo = 'Q' THEN 'Reserva del Quincho' ELSE TIME_FORMAT(r.Fecha_Hora, '%H:%i') END as horario,
|
|
COUNT(r.ID_Reserva) as cantidad_reservas,
|
|
COUNT(CASE WHEN r.Estado = 2 THEN 1 END) as canceladas
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
$filtro_cancha
|
|
GROUP BY CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END,
|
|
CASE WHEN c.Tipo = 'Q' THEN 'reserva_quincho' ELSE TIME_FORMAT(r.Fecha_Hora, '%H:%i') END
|
|
ORDER BY tipo_nombre, horario ASC
|
|
";
|
|
|
|
$result_horario = $conexion->query($sql_por_horario);
|
|
if (!$result_horario) {
|
|
throw new Exception("Error en query por horario: " . $conexion->error);
|
|
}
|
|
|
|
// Obtener conteo de reservas de Quincho por horario (agregado para mostrar en tablas de Fútbol/Pádel)
|
|
$sql_quincho_por_horario = "
|
|
SELECT
|
|
TIME_FORMAT(r.Fecha_Hora, '%H:%i') as horario_key,
|
|
COUNT(r.ID_Reserva) as cantidad
|
|
FROM reservas r
|
|
JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
|
WHERE c.Tipo = 'Q' AND DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
GROUP BY horario_key
|
|
";
|
|
$result_quincho_hor = $conexion->query($sql_quincho_por_horario);
|
|
$quincho_por_horario = [];
|
|
if ($result_quincho_hor) {
|
|
while ($q = $result_quincho_hor->fetch_assoc()) {
|
|
$quincho_por_horario[$q['horario_key']] = intval($q['cantidad']);
|
|
}
|
|
}
|
|
|
|
// Obtener conteo de reservas de quincho asociadas a cada tipo de cancha (emparejando por Fecha_Hora e ID_Cliente)
|
|
$sql_quincho_asociada = "
|
|
SELECT
|
|
TIME_FORMAT(q.Fecha_Hora, '%H:%i') as horario_key,
|
|
CASE WHEN c2.Tipo = 'F' THEN 'Fútbol' WHEN c2.Tipo = 'P' THEN 'Pádel' ELSE 'Otro' END as asociado_tipo,
|
|
COUNT(q.ID_Reserva) as cantidad
|
|
FROM reservas q
|
|
JOIN canchas cq ON q.ID_Cancha = cq.ID_Cancha AND cq.Tipo = 'Q'
|
|
JOIN reservas r ON q.Fecha_Hora = r.Fecha_Hora AND q.ID_Cliente = r.ID_Cliente
|
|
JOIN canchas c2 ON r.ID_Cancha = c2.ID_Cancha
|
|
WHERE DATE(q.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin' AND c2.Tipo IN ('F','P')
|
|
GROUP BY horario_key, asociado_tipo
|
|
";
|
|
$result_quincho_asoc = $conexion->query($sql_quincho_asociada);
|
|
$quincho_asociada = [];
|
|
if ($result_quincho_asoc) {
|
|
while ($qa = $result_quincho_asoc->fetch_assoc()) {
|
|
$tipo_asoc = $qa['asociado_tipo'];
|
|
$hk = $qa['horario_key'];
|
|
if (!isset($quincho_asociada[$tipo_asoc])) $quincho_asociada[$tipo_asoc] = [];
|
|
$quincho_asociada[$tipo_asoc][$hk] = intval($qa['cantidad']);
|
|
}
|
|
}
|
|
|
|
$reservas_por_horario_temp = [];
|
|
while ($row = $result_horario->fetch_assoc()) {
|
|
$tipo = $row['tipo_nombre'];
|
|
$horario_key = $row['horario_key'];
|
|
if (!isset($reservas_por_horario_temp[$tipo])) {
|
|
$reservas_por_horario_temp[$tipo] = [];
|
|
}
|
|
$reservas_por_horario_temp[$tipo][$horario_key] = [
|
|
'horario' => $row['horario'],
|
|
'inicio' => $row['horario_key'],
|
|
'fin' => '',
|
|
'cantidad_reservas' => intval($row['cantidad_reservas']),
|
|
'canceladas' => intval($row['canceladas'])
|
|
];
|
|
// Añadir conteo de reservas de quincho vinculadas por horario.
|
|
// Si existe asociación por tipo (Fútbol/Pádel), usarla; si no, usar conteo general de quincho por horario.
|
|
$reservas_por_horario_temp[$tipo][$horario_key]['reservas_quincho'] = 0;
|
|
if (isset($quincho_asociada[$tipo][$horario_key])) {
|
|
$reservas_por_horario_temp[$tipo][$horario_key]['reservas_quincho'] = intval($quincho_asociada[$tipo][$horario_key]);
|
|
} else {
|
|
$reservas_por_horario_temp[$tipo][$horario_key]['reservas_quincho'] = intval($quincho_por_horario[$horario_key] ?? 0);
|
|
}
|
|
}
|
|
|
|
// Combinar todos los horarios disponibles con las reservas por tipo
|
|
$reservas_por_horario = [];
|
|
foreach ($horarios_por_tipo as $tipo_cancha => $horarios_disponibles) {
|
|
$reservas_por_horario[$tipo_cancha] = [];
|
|
foreach ($horarios_disponibles as $horario) {
|
|
$key = $horario['key'];
|
|
if (isset($reservas_por_horario_temp[$tipo_cancha][$key])) {
|
|
$item = $reservas_por_horario_temp[$tipo_cancha][$key];
|
|
$item['horario'] = $horario['horario'];
|
|
$item['inicio'] = $horario['inicio'];
|
|
$item['fin'] = $horario['fin'];
|
|
$item['es_quincho'] = $horario['es_quincho'] ?? false;
|
|
$reservas_por_horario[$tipo_cancha][] = $item;
|
|
} else {
|
|
// Horario sin reservas
|
|
$reservas_por_horario[$tipo_cancha][] = [
|
|
'horario' => $horario['horario'],
|
|
'inicio' => $horario['inicio'],
|
|
'fin' => $horario['fin'],
|
|
'cantidad_reservas' => 0,
|
|
'canceladas' => 0,
|
|
'es_quincho' => $horario['es_quincho'] ?? false,
|
|
'reservas_quincho' => intval(isset($quincho_asociada[$tipo_cancha][$key]) ? $quincho_asociada[$tipo_cancha][$key] : ($quincho_por_horario[$horario['inicio']] ?? 0))
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Ocupación por cancha
|
|
$sql_ocupacion_cancha = "
|
|
SELECT
|
|
c.ID_Cancha,
|
|
CASE c.Tipo WHEN 'F' THEN 'Fútbol' WHEN 'P' THEN 'Pádel' WHEN 'Q' THEN 'Quincho' ELSE 'Otro' END as tipo,
|
|
c.Duracion as duracion_minutos,
|
|
COUNT(r.ID_Reserva) as cantidad_reservas,
|
|
COUNT(CASE WHEN r.Estado != 2 THEN 1 END) as activas,
|
|
COUNT(CASE WHEN r.Estado = 2 THEN 1 END) as canceladas
|
|
FROM canchas c
|
|
LEFT JOIN reservas r ON c.ID_Cancha = r.ID_Cancha
|
|
AND DATE(r.Fecha_Hora) BETWEEN '$fecha_inicio' AND '$fecha_fin'
|
|
WHERE 1=1 $filtro_cancha
|
|
GROUP BY c.ID_Cancha, c.Tipo
|
|
ORDER BY c.Tipo, c.ID_Cancha ASC
|
|
";
|
|
|
|
$result_cancha = $conexion->query($sql_ocupacion_cancha);
|
|
if (!$result_cancha) {
|
|
throw new Exception("Error en query ocupación cancha: " . $conexion->error);
|
|
}
|
|
|
|
$ocupacion_cancha = [];
|
|
$horas_disponibles_en_periodo = obtenerHorasDisponiblesPorPeriodo($conexion, $fecha_inicio, $fecha_fin);
|
|
$minutos_disponibles_por_cancha = $horas_disponibles_en_periodo * 60;
|
|
|
|
while ($row = $result_cancha->fetch_assoc()) {
|
|
$minutos_reservados = intval($row['activas']) * intval($row['duracion_minutos']);
|
|
$ocupacion = 0;
|
|
if ($minutos_disponibles_por_cancha > 0) {
|
|
$ocupacion = round(($minutos_reservados / $minutos_disponibles_por_cancha) * 100, 2);
|
|
}
|
|
|
|
$ocupacion_cancha[] = [
|
|
'numero' => intval($row['ID_Cancha']),
|
|
'tipo' => $row['tipo'],
|
|
'duracion_minutos' => intval($row['duracion_minutos']),
|
|
'cantidad_reservas' => intval($row['cantidad_reservas']),
|
|
'activas' => intval($row['activas']),
|
|
'canceladas' => intval($row['canceladas']),
|
|
'porcentaje_ocupacion' => $ocupacion
|
|
];
|
|
}
|
|
|
|
// Para el período diario, no mostrar % de ocupación para el Quincho
|
|
if ($tipo === 'diario') {
|
|
foreach ($ocupacion_cancha as &$cancha_tmp) {
|
|
if (isset($cancha_tmp['tipo']) && $cancha_tmp['tipo'] === 'Quincho') {
|
|
$cancha_tmp['porcentaje_ocupacion'] = null;
|
|
}
|
|
}
|
|
unset($cancha_tmp);
|
|
}
|
|
|
|
// Calcular horarios pico y bajos por tipo de cancha (considerando horarios sin reservas)
|
|
$horarios_pico_por_tipo = [];
|
|
$horarios_bajos_por_tipo = [];
|
|
$horarios_sin_reservas_por_tipo = [];
|
|
|
|
foreach ($reservas_por_horario as $tipo_cancha => $horarios) {
|
|
$horarios_pico_por_tipo[$tipo_cancha] = [];
|
|
$horarios_bajos_por_tipo[$tipo_cancha] = [];
|
|
$horarios_sin_reservas_por_tipo[$tipo_cancha] = [];
|
|
|
|
if (!empty($horarios)) {
|
|
// Separar horarios con reservas de los sin reservas
|
|
$horarios_con_reservas = array_filter($horarios, function($h) {
|
|
return $h['cantidad_reservas'] > 0;
|
|
});
|
|
|
|
$horarios_sin_reservas_temp = array_filter($horarios, function($h) {
|
|
return $h['cantidad_reservas'] == 0;
|
|
});
|
|
|
|
// Extraer solo los horarios sin reservas
|
|
$horarios_sin_reservas_por_tipo[$tipo_cancha] = array_values(array_map(function($h) { return $h['horario']; }, $horarios_sin_reservas_temp));
|
|
|
|
if ($tipo === 'diario') {
|
|
// Para diario: Horarios Con Reservas = todos los que tienen reservas
|
|
$horarios_pico_por_tipo[$tipo_cancha] = array_values(array_map(function($h) { return $h['horario']; }, $horarios_con_reservas));
|
|
// Horarios sin reservas ya está definido arriba
|
|
} else {
|
|
// Para otros períodos: calcular promedio y separar en mayor y menor demanda
|
|
if (!empty($horarios_con_reservas)) {
|
|
$promedio_reservas = array_sum(array_column($horarios_con_reservas, 'cantidad_reservas')) / count($horarios_con_reservas);
|
|
|
|
foreach ($horarios_con_reservas as $horario) {
|
|
if ($horario['cantidad_reservas'] >= $promedio_reservas) {
|
|
$horarios_pico_por_tipo[$tipo_cancha][] = $horario['horario'];
|
|
} else {
|
|
$horarios_bajos_por_tipo[$tipo_cancha][] = $horario['horario'];
|
|
}
|
|
}
|
|
|
|
// Incluir horarios sin reservas en "bajos"
|
|
$horarios_bajos_por_tipo[$tipo_cancha] = array_merge($horarios_bajos_por_tipo[$tipo_cancha], $horarios_sin_reservas_por_tipo[$tipo_cancha]);
|
|
sort($horarios_bajos_por_tipo[$tipo_cancha]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Para compatibilidad, crear arrays planos con todos los horarios (solo si hay datos)
|
|
$horarios_pico = [];
|
|
$horarios_bajos = [];
|
|
$horarios_sin_reservas = [];
|
|
|
|
if (!empty($horarios_pico_por_tipo)) {
|
|
foreach ($horarios_pico_por_tipo as $horarios_tipo) {
|
|
$horarios_pico = array_merge($horarios_pico, $horarios_tipo);
|
|
}
|
|
}
|
|
if (!empty($horarios_bajos_por_tipo)) {
|
|
foreach ($horarios_bajos_por_tipo as $horarios_tipo) {
|
|
$horarios_bajos = array_merge($horarios_bajos, $horarios_tipo);
|
|
}
|
|
}
|
|
if (!empty($horarios_sin_reservas_por_tipo)) {
|
|
foreach ($horarios_sin_reservas_por_tipo as $horarios_tipo) {
|
|
$horarios_sin_reservas = array_merge($horarios_sin_reservas, $horarios_tipo);
|
|
}
|
|
}
|
|
|
|
// Ocupación general del período (reservado horas / horas disponibles)
|
|
$ocupacion_general = 0;
|
|
$total_cancha_horas_disponibles = count($ocupacion_cancha) * $horas_disponibles_en_periodo;
|
|
$minutos_reservados_total = 0;
|
|
foreach ($ocupacion_cancha as $cancha) {
|
|
$minutos_reservados_total += intval($cancha['activas']) * intval($cancha['duracion_minutos']);
|
|
}
|
|
if ($total_cancha_horas_disponibles > 0) {
|
|
$ocupacion_general = round(($minutos_reservados_total / ($total_cancha_horas_disponibles * 60)) * 100, 2);
|
|
}
|
|
|
|
echo json_encode([
|
|
'tipo_reporte' => 'ocupacion_canchas',
|
|
'periodo' => $tipo,
|
|
'fecha_inicio' => $fecha_inicio,
|
|
'fecha_fin' => $fecha_fin,
|
|
'tipo_cancha_filtro' => $tipo_cancha_filtro ?: 'Todas',
|
|
'reservas_por_dia' => $reservas_por_dia,
|
|
'reservas_por_horario' => $reservas_por_horario,
|
|
'reservas_por_horario_por_tipo' => [
|
|
'horarios_pico_por_tipo' => $horarios_pico_por_tipo,
|
|
'horarios_bajos_por_tipo' => $horarios_bajos_por_tipo,
|
|
'horarios_sin_reservas_por_tipo' => $horarios_sin_reservas_por_tipo
|
|
],
|
|
'ocupacion_cancha' => $ocupacion_cancha,
|
|
'horarios_pico' => $horarios_pico,
|
|
'horarios_bajos' => $horarios_bajos,
|
|
'horarios_sin_reservas' => $horarios_sin_reservas,
|
|
'resumen' => [
|
|
'total_reservas' => $total_reservas,
|
|
'total_canceladas' => $total_canceladas,
|
|
'reservas_activas' => $total_reservas - $total_canceladas,
|
|
'ocupacion_promedio' => $ocupacion_general,
|
|
'dias_en_periodo' => intval($total_dias_periodo),
|
|
'total_horarios' => count($reservas_por_horario),
|
|
'total_canchas' => count($ocupacion_cancha)
|
|
]
|
|
]);
|
|
}
|
|
|
|
else {
|
|
echo json_encode(['error' => 'Tipo de reporte no válido']);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode(['error' => $e->getMessage()]);
|
|
}
|
|
?>
|