= '$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()]); } ?>