false, 'mensaje' => 'Sesión expirada o no autenticado']); exit(); } $usuario_id = (int)($_SESSION['id'] ?? 0); $usuario_nombre = $_SESSION['nombre'] ?? ($_SESSION['usuario'] ?? 'Coordinador'); $usuario_login = $_SESSION['usuario'] ?? ''; $usuario_rol = strtolower($_SESSION['rol'] ?? ''); $usuario_ger = strtoupper($_SESSION['gerencia'] ?? ''); $action = $_GET['action'] ?? ($_POST['action'] ?? ''); try { switch ($action) { // ───────────────────────────────────────────────────────────── // 1. BUSCAR VEHÍCULO (AUTOCOMPLETE POR ALIAS O PLACA) // ───────────────────────────────────────────────────────────── case 'buscar_vehiculo': { $term = trim($_GET['term'] ?? ''); if (strlen($term) < 1) { echo json_encode([]); exit(); } $like = '%' . strtoupper($term) . '%'; $sql = "SELECT * FROM ( SELECT DISTINCT DESCRIPCIONVEHICULO, FABRICANTEVEHICULO, MODELOVEHICULO, COLORVEHICULO, PLACA, CUADRILLAID, DESCRIPCIONCUADRILLA FROM VW_VEHICULOS_ACTUALES WHERE UPPER(DESCRIPCIONVEHICULO) LIKE :b1 OR UPPER(PLACA) LIKE :b1 OR UPPER(FABRICANTEVEHICULO) LIKE :b1 ORDER BY DESCRIPCIONVEHICULO ASC ) WHERE ROWNUM <= 25"; $rows = db_select_to_array($sql, [':b1' => $like]); $resultado = []; $vistos = []; foreach ($rows as $r) { $r = array_change_key_case($r, CASE_LOWER); $alias = trim($r['descripcionvehiculo'] ?? ''); $marca = trim($r['fabricantevehiculo'] ?? ''); $modelo = trim($r['modelovehiculo'] ?? ''); $placa = trim($r['placa'] ?? ''); $color = trim($r['colorvehiculo'] ?? ''); $cuad = trim($r['descripcioncuadrilla'] ?? ''); if ($alias === '' || isset($vistos[$alias])) continue; $vistos[$alias] = true; $sub = array_filter([$marca, $modelo, $placa ? "Placa: $placa" : '']); $resultado[] = [ 'label' => $alias . ($sub ? ' — ' . implode(' | ', $sub) : ''), 'value' => $alias, 'alias' => $alias, 'marca' => $marca, 'modelo' => $modelo, 'placa' => $placa, 'color' => $color, 'cuadrilla' => $cuad, 'propietario'=> (stripos($alias, 'PROP') !== false || stripos($alias, 'NETGO') !== false) ? 'Propio' : 'Rentado', ]; } echo json_encode($resultado, JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 2. BUSCAR CONDUCTOR / TÉCNICO // ───────────────────────────────────────────────────────────── case 'buscar_conductor': { $term = trim($_GET['term'] ?? ''); if (strlen($term) < 2) { echo json_encode([]); exit(); } $like = '%' . strtoupper($term) . '%'; $sql = "SELECT * FROM ( SELECT DISTINCT vc.NOMBRETECNICO, vc.IDENTIDAD, vc.NOMBRECUADRILLA, vc.NOMBREPROYECTO, vc.CELLPHONE, ct.GERENCIA, ct.COORDINADOR FROM VW_CUADRILLASACTUALES vc LEFT JOIN CONSOLIDADO_TECNICOS ct ON UPPER(TRIM(ct.NOMBRE_TECNICO)) = UPPER(TRIM(vc.NOMBRETECNICO)) WHERE UPPER(vc.NOMBRETECNICO) LIKE :b1 OR vc.IDENTIDAD LIKE :b1 ORDER BY vc.NOMBRETECNICO ASC ) WHERE ROWNUM <= 25"; $rows = db_select_to_array($sql, [':b1' => $like]); $resultado = []; $vistos = []; foreach ($rows as $r) { $r = array_change_key_case($r, CASE_LOWER); $nom = trim($r['nombretecnico'] ?? ''); $dni = trim($r['identidad'] ?? ''); $cuad = trim($r['nombrecuadrilla'] ?? ''); $proy = trim($r['nombreproyecto'] ?? ''); $ger = trim($r['gerencia'] ?? ''); $coord = trim($r['coordinador'] ?? ''); if ($nom === '' || isset($vistos[$nom])) continue; $vistos[$nom] = true; $resultado[] = [ 'label' => $nom . ($dni ? " (DNI: $dni)" : '') . ($proy ? " — $proy" : ''), 'value' => $nom, 'nombre' => $nom, 'licencia' => $dni, 'cuadrilla' => $cuad, 'proyecto' => $proy, 'gerencia' => $ger, 'coordinador'=> $coord, ]; } echo json_encode($resultado, JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 3. OBTENER SEMANA DEL COORDINADOR // ───────────────────────────────────────────────────────────── case 'obtener_semana_coordinador': { $semana = trim($_GET['semana'] ?? ''); if ($semana === '') { $semana = 'W' . date('W') . '-' . date('Y'); } $sql = "SELECT ID, SEMANA, ANIO_SEMANA, NUMERO_SEMANA, MES_PERIODO, TO_CHAR(FECHA_ASIGNACION, 'YYYY-MM-DD\"T\"HH24:MI') AS FECHA_ASIGNACION, ALIAS, DIAS_LABORADOS, MARCA, ANIO_VEHICULO, MODELO, PLACA, PROPIETARIO_RENTADO, RENTADORA, ZONA, PROYECTO, CODIGO_PROYECTO, GERENCIA, RESPONSABLE_JEFATURA, CONDUCTOR, NUMERO_LICENCIA, FECHA_VIGENCIA, STATUS_LICENCIA, OBSERVACIONES, COORDINADOR_ID, COORDINADOR_NOMBRE, COORDINADOR_USUARIO FROM FLOTA_CONTROL_SEMANAL WHERE SEMANA = :semana AND (COORDINADOR_ID = :coord_id OR COORDINADOR_USUARIO = :coord_user) ORDER BY ALIAS ASC"; $rows = db_select_to_array($sql, [ ':semana' => $semana, ':coord_id' => $usuario_id, ':coord_user' => $usuario_login ]); // Normalizar a minúsculas $items = array_map(function($r) { return array_change_key_case($r, CASE_LOWER); }, $rows); echo json_encode([ 'exito' => true, 'semana' => $semana, 'items' => $items ], JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 4. CLONAR SEMANA ANTERIOR // ───────────────────────────────────────────────────────────── case 'clonar_semana_anterior': { $semana_origen = trim($_POST['semana_origen'] ?? ''); if ($semana_origen === '') { // Calcular semana anterior por defecto $numSem = (int)date('W'); $anio = (int)date('Y'); if ($numSem <= 1) { $numSem = 52; $anio--; } else { $numSem--; } $semana_origen = 'W' . str_pad($numSem, 2, '0', STR_PAD_LEFT) . '-' . $anio; } $sql = "SELECT ALIAS, DIAS_LABORADOS, MARCA, ANIO_VEHICULO, MODELO, PLACA, PROPIETARIO_RENTADO, RENTADORA, ZONA, PROYECTO, CODIGO_PROYECTO, GERENCIA, RESPONSABLE_JEFATURA, CONDUCTOR, NUMERO_LICENCIA, FECHA_VIGENCIA, STATUS_LICENCIA, OBSERVACIONES FROM FLOTA_CONTROL_SEMANAL WHERE SEMANA = :semana AND (COORDINADOR_ID = :coord_id OR COORDINADOR_USUARIO = :coord_user) ORDER BY ALIAS ASC"; $rows = db_select_to_array($sql, [ ':semana' => $semana_origen, ':coord_id' => $usuario_id, ':coord_user' => $usuario_login ]); if (empty($rows)) { echo json_encode([ 'exito' => false, 'mensaje' => "No se encontraron registros en la semana anterior ($semana_origen) para tu usuario." ], JSON_UNESCAPED_UNICODE); exit(); } $items = array_map(function($r) { return array_change_key_case($r, CASE_LOWER); }, $rows); echo json_encode([ 'exito' => true, 'semana_origen' => $semana_origen, 'items' => $items, 'mensaje' => "Se cargaron " . count($items) . " vehículos de la semana $semana_origen." ], JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 5. GUARDAR SEMANA (COORDINADOR) // ───────────────────────────────────────────────────────────── case 'guardar_semana': { $input = json_decode(file_get_contents('php://input'), true); if (!$input) { $input = $_POST; } $semana = trim($input['semana'] ?? ''); $anio_semana = (int)($input['anio_semana'] ?? date('Y')); $numero_semana = (int)($input['numero_semana'] ?? date('W')); $mes_periodo = trim($input['mes_periodo'] ?? date('Y-m')); $filas = $input['filas'] ?? []; if ($semana === '' || empty($filas)) { echo json_encode([ 'exito' => false, 'mensaje' => 'Debe ingresar al menos un vehículo y especificar la semana.' ], JSON_UNESCAPED_UNICODE); exit(); } // Validar previamente todas las filas antes de tocar la base de datos foreach ($filas as $f) { $alias = strtoupper(trim($f['alias'] ?? '')); if ($alias === '') continue; $placa = strtoupper(trim($f['placa'] ?? '')); $diasLaborados = (int)($f['dias_laborados'] ?? 7); $obs = trim($f['observaciones'] ?? ''); if ($diasLaborados < 7 && strlen($obs) < 4) { echo json_encode([ 'exito' => false, 'mensaje' => "El vehículo '$alias' (placa $placa) tiene registrados $diasLaborados días (menos de 7 días). Es estrictamente obligatorio ingresar una observación justificando por qué no laboró los 7 días completos de la semana para proceder al pago." ], JSON_UNESCAPED_UNICODE); exit(); } } // 1. Eliminar filas previas del coordinador para esa semana $sqlDel = "DELETE FROM FLOTA_CONTROL_SEMANAL WHERE SEMANA = :semana AND (COORDINADOR_ID = :coord_id OR COORDINADOR_USUARIO = :coord_user)"; db_execute_query($sqlDel, [ ':semana' => $semana, ':coord_id' => $usuario_id, ':coord_user' => $usuario_login ]); // 2. Insertar las nuevas filas $mapaCodigos = obtenerMapaProyectoCodigo(); $guardados = 0; foreach ($filas as $f) { $alias = strtoupper(trim($f['alias'] ?? '')); if ($alias === '') continue; $diasLaborados = (int)($f['dias_laborados'] ?? 7); if ($diasLaborados < 1) $diasLaborados = 1; if ($diasLaborados > 7) $diasLaborados = 7; $marca = trim($f['marca'] ?? ''); $anioVehiculo = trim($f['anio_vehiculo'] ?? ''); $modelo = trim($f['modelo'] ?? ''); $placa = strtoupper(trim($f['placa'] ?? '')); $propRent = trim($f['propietario_rentado'] ?? 'Rentado'); $rentadora = trim($f['rentadora'] ?? 'Avis'); $zona = trim($f['zona'] ?? 'SAN PEDRO SULA'); $proyecto = trim($f['proyecto'] ?? ''); $codProyecto = trim($f['codigo_proyecto'] ?? ''); if ($codProyecto === '' && isset($mapaCodigos[$proyecto])) { $codProyecto = $mapaCodigos[$proyecto]; } $gerencia = trim($f['gerencia'] ?? ($usuario_ger ?: 'OPERACIONES TECNICAS')); $jefatura = trim($f['responsable_jefatura'] ?? $usuario_nombre); $conductor = trim($f['conductor'] ?? ''); $licencia = trim($f['numero_licencia'] ?? ''); $vigencia = trim($f['fecha_vigencia'] ?? ''); $statusLic = trim($f['status_licencia'] ?? 'VIGENTE'); $obs = trim($f['observaciones'] ?? ''); $fechaAsign = trim($f['fecha_asignacion'] ?? date('Y-m-d H:i')); $sqlIns = "INSERT INTO FLOTA_CONTROL_SEMANAL ( ID, SEMANA, ANIO_SEMANA, NUMERO_SEMANA, MES_PERIODO, FECHA_ASIGNACION, ALIAS, DIAS_LABORADOS, MARCA, ANIO_VEHICULO, MODELO, PLACA, PROPIETARIO_RENTADO, RENTADORA, ZONA, PROYECTO, CODIGO_PROYECTO, GERENCIA, RESPONSABLE_JEFATURA, CONDUCTOR, NUMERO_LICENCIA, FECHA_VIGENCIA, STATUS_LICENCIA, OBSERVACIONES, COORDINADOR_ID, COORDINADOR_NOMBRE, COORDINADOR_USUARIO, FECHA_REGISTRO, FECHA_MODIFICACION ) VALUES ( FLOTA_CONTROL_SEMANAL_SEQ.NEXTVAL, :semana, :anio_sem, :num_sem, :mes_per, TO_DATE(:fecha_asig, 'YYYY-MM-DD HH24:MI'), :alias, :dias, :marca, :anio_v, :modelo, :placa, :prop_rent, :rentadora, :zona, :proyecto, :cod_proy, :gerencia, :jefatura, :conductor, :licencia, :vigencia, :status_lic, :obs, :coord_id, :coord_nom, :coord_user, SYSDATE, SYSDATE )"; $ok = db_execute_query($sqlIns, [ ':semana' => $semana, ':anio_sem' => $anio_semana, ':num_sem' => $numero_semana, ':mes_per' => $mes_periodo, ':fecha_asig' => str_replace('T', ' ', $fechaAsign), ':alias' => $alias, ':dias' => $diasLaborados, ':marca' => $marca, ':anio_v' => $anioVehiculo, ':modelo' => $modelo, ':placa' => $placa, ':prop_rent' => $propRent, ':rentadora' => $rentadora, ':zona' => $zona, ':proyecto' => $proyecto, ':cod_proy' => $codProyecto, ':gerencia' => $gerencia, ':jefatura' => $jefatura, ':conductor' => $conductor, ':licencia' => $licencia, ':vigencia' => $vigencia, ':status_lic' => $statusLic, ':obs' => $obs, ':coord_id' => $usuario_id, ':coord_nom' => $usuario_nombre, ':coord_user' => $usuario_login ]); if ($ok) $guardados++; } echo json_encode([ 'exito' => true, 'guardados' => $guardados, 'mensaje' => "✅ Se guardaron exitosamente $guardados vehículos para la semana $semana." ], JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 6. OBTENER REPORTE CONSOLIDADO PARA ADMINISTRACIÓN // ───────────────────────────────────────────────────────────── case 'obtener_reporte_admin': { $modo = trim($_GET['modo'] ?? 'semana'); // 'semana' o 'mes' $semana_sel = trim($_GET['semana'] ?? ''); $mes_sel = trim($_GET['mes'] ?? date('Y-m')); $tipo_prop = trim($_GET['tipo_propietario'] ?? 'Todos'); // 'Todos', 'Rentado', 'Propio' $rentadora = trim($_GET['rentadora'] ?? 'Todas'); $coord_user = trim($_GET['coordinador'] ?? 'Todos'); $whereParts = ["1=1"]; $binds = []; if ($modo === 'semana') { if ($semana_sel !== '' && $semana_sel !== 'Todas') { $whereParts[] = "SEMANA = :semana"; $binds[':semana'] = $semana_sel; } } else { if ($mes_sel !== '' && $mes_sel !== 'Todos') { $whereParts[] = "MES_PERIODO = :mes"; $binds[':mes'] = $mes_sel; } } if ($tipo_prop !== 'Todos') { $whereParts[] = "PROPIETARIO_RENTADO = :prop"; $binds[':prop'] = $tipo_prop; } if ($rentadora !== 'Todas') { $whereParts[] = "RENTADORA = :rent"; $binds[':rent'] = $rentadora; } if ($coord_user !== 'Todos') { $whereParts[] = "COORDINADOR_USUARIO = :coord"; $binds[':coord'] = $coord_user; } $filtro_dias = trim($_GET['filtro_dias'] ?? 'todos'); if ($filtro_dias === 'incompletos') { $whereParts[] = "DIAS_LABORADOS < 7"; } elseif ($filtro_dias === 'completos') { $whereParts[] = "DIAS_LABORADOS = 7"; } $whereSQL = implode(' AND ', $whereParts); // 1. Obtener todas las filas ordenadas por ALIAS natural $sql = "SELECT ID, SEMANA, ANIO_SEMANA, NUMERO_SEMANA, MES_PERIODO, TO_CHAR(FECHA_ASIGNACION, 'DD/MM/YYYY HH24:MI') AS FECHA_ASIGNACION, ALIAS, DIAS_LABORADOS, MARCA, ANIO_VEHICULO, MODELO, PLACA, PROPIETARIO_RENTADO, RENTADORA, ZONA, PROYECTO, CODIGO_PROYECTO, GERENCIA, RESPONSABLE_JEFATURA, CONDUCTOR, NUMERO_LICENCIA, FECHA_VIGENCIA, STATUS_LICENCIA, OBSERVACIONES, COORDINADOR_ID, COORDINADOR_NOMBRE, COORDINADOR_USUARIO, TO_CHAR(FECHA_REGISTRO, 'DD/MM/YYYY HH24:MI') AS FECHA_REGISTRO FROM FLOTA_CONTROL_SEMANAL WHERE $whereSQL ORDER BY LENGTH(ALIAS) ASC, ALIAS ASC"; $rows = db_select_to_array($sql, $binds); $items = array_map(function($r) { return array_change_key_case($r, CASE_LOWER); }, $rows); // 2. DETECCIÓN DE DUPLICADOS EN LA SEMANA (Alerta Roja) // Agrupar por Semana + Alias para encontrar repeticiones $duplicadosPorSemana = []; $conteos = []; foreach ($items as $it) { $sem = $it['semana']; $al = $it['alias']; $key = "$sem|$al"; if (!isset($conteos[$key])) { $conteos[$key] = []; } $conteos[$key][] = [ 'id' => $it['id'], 'coordinador' => $it['coordinador_nombre'] ?: $it['coordinador_usuario'], 'conductor' => $it['conductor'], 'proyecto' => $it['proyecto'], ]; } $duplicadosInfo = []; foreach ($conteos as $key => $reportes) { if (count($reportes) > 1) { list($sem, $al) = explode('|', $key); $coordinadores = array_unique(array_column($reportes, 'coordinador')); $duplicadosInfo[$key] = [ 'semana' => $sem, 'alias' => $al, 'cantidad' => count($reportes), 'coordinadores' => implode(', ', $coordinadores), ]; } } // Marcar cada fila con flag de duplicado si aplica foreach ($items as &$it) { $key = $it['semana'] . '|' . $it['alias']; if (isset($duplicadosInfo[$key])) { $it['es_duplicado'] = true; $it['duplicado_info'] = $duplicadosInfo[$key]; } else { $it['es_duplicado'] = false; } } unset($it); // 3. Resumen acumulado por vehículo (Suma de días por vehículo en el período) $vehiculosMap = []; foreach ($items as $it) { $al = strtoupper(trim($it['alias'] ?? '')); if ($al === '') continue; if (!isset($vehiculosMap[$al])) { $vehiculosMap[$al] = [ 'alias' => $it['alias'], 'placa' => $it['placa'], 'marca' => $it['marca'], 'modelo' => $it['modelo'], 'anio_vehiculo' => $it['anio_vehiculo'], 'propietario_rentado' => $it['propietario_rentado'], 'rentadora' => $it['rentadora'], 'zona' => $it['zona'], 'proyecto' => $it['proyecto'], 'codigo_proyecto' => $it['codigo_proyecto'], 'gerencia' => $it['gerencia'], 'responsable_jefatura' => $it['responsable_jefatura'], 'conductor' => $it['conductor'], 'numero_licencia' => $it['numero_licencia'], 'fecha_vigencia' => $it['fecha_vigencia'], 'status_licencia' => $it['status_licencia'], 'total_dias' => 0, 'total_semanas' => 0, 'semanas_lista' => [], 'incompletos_count' => 0, 'justificaciones' => [], ]; } $d = (int)($it['dias_laborados'] ?? 7); $sem = $it['semana'] ?? ''; $vehiculosMap[$al]['total_dias'] += $d; $vehiculosMap[$al]['total_semanas']++; $vehiculosMap[$al]['semanas_lista'][] = $sem; if ($d < 7) { $vehiculosMap[$al]['incompletos_count']++; $obs = trim($it['observaciones'] ?? ''); $vehiculosMap[$al]['justificaciones'][] = ($sem ? "[$sem: {$d}d] " : '') . ($obs ?: 'Sin justificación registrada'); } } uksort($vehiculosMap, function($a, $b) { if (strlen($a) != strlen($b)) { return strlen($a) - strlen($b); } return strcasecmp($a, $b); }); $resumenVehiculos = array_values($vehiculosMap); // 4. Totales estadísticos $totalVehiculos = count($items); $totalRentados = 0; $totalPropios = 0; $totalDiasLab = 0; $totalIncompletos = 0; foreach ($items as $it) { if (strcasecmp($it['propietario_rentado'], 'Rentado') === 0) { $totalRentados++; } elseif (strcasecmp($it['propietario_rentado'], 'Propio') === 0) { $totalPropios++; } $d = (int)($it['dias_laborados'] ?? 0); $totalDiasLab += $d; if ($d > 0 && $d < 7) { $totalIncompletos++; } } // 5. Semanas y Meses disponibles para los selectores $semanasDisponibles = db_select_to_array("SELECT DISTINCT SEMANA FROM FLOTA_CONTROL_SEMANAL ORDER BY SEMANA DESC"); $mesesDisponibles = db_select_to_array("SELECT DISTINCT MES_PERIODO FROM FLOTA_CONTROL_SEMANAL WHERE MES_PERIODO IS NOT NULL ORDER BY MES_PERIODO DESC"); $coordsDisponibles = db_select_to_array("SELECT DISTINCT COORDINADOR_USUARIO, COORDINADOR_NOMBRE FROM FLOTA_CONTROL_SEMANAL WHERE COORDINADOR_USUARIO IS NOT NULL ORDER BY COORDINADOR_NOMBRE ASC"); echo json_encode([ 'exito' => true, 'items' => $items, 'resumen_vehiculos' => $resumenVehiculos, 'duplicados' => array_values($duplicadosInfo), 'totales' => [ 'vehiculos' => $totalVehiculos, 'vehiculos_unicos'=> count($resumenVehiculos), 'rentados' => $totalRentados, 'propios' => $totalPropios, 'dias_lab' => $totalDiasLab, 'alertas' => count($duplicadosInfo), 'incompletos' => $totalIncompletos, ], 'filtros_disponibles' => [ 'semanas' => array_column($semanasDisponibles, 'SEMANA'), 'meses' => array_column($mesesDisponibles, 'MES_PERIODO'), 'coordinadores'=> $coordsDisponibles, ] ], JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 7. VERIFICAR NOTIFICACIONES INDIVIDUALES DE CAMBIO DE VEHÍCULOS // ───────────────────────────────────────────────────────────── case 'check_notificaciones_cambio': { $userKey = flota_obtener_clave_usuario($usuario_id, $usuario_login); $estadoNotif = flota_contar_solicitudes_cambio_nuevas($userKey); $ultimas = flota_obtener_ultimas_solicitudes_cambio(5); echo json_encode([ 'exito' => true, 'user_key' => $userKey, 'tiene_nuevas' => $estadoNotif['tiene_nuevas'], 'nuevas_count' => $estadoNotif['nuevas_count'], 'total_pendientes' => $estadoNotif['total_pendientes'], 'max_id' => $estadoNotif['max_id'], 'ultimo_id_visto' => $estadoNotif['ultimo_id_visto'], 'items' => $ultimas ], JSON_UNESCAPED_UNICODE); exit(); } // ───────────────────────────────────────────────────────────── // 8. MARCAR SOLICITUDES DE CAMBIO COMO VISTAS (INDIVIDUAL POR USUARIO) // ───────────────────────────────────────────────────────────── case 'marcar_cambios_vistos': { $userKey = flota_obtener_clave_usuario($usuario_id, $usuario_login); $maxId = flota_marcar_cambios_vistos($userKey); echo json_encode([ 'exito' => true, 'user_key' => $userKey, 'ultimo_id' => $maxId, 'mensaje' => 'Solicitudes marcadas como vistas correctamente para este usuario.' ], JSON_UNESCAPED_UNICODE); exit(); } default: echo json_encode(['exito' => false, 'mensaje' => 'Acción no reconocida']); exit(); } } catch (Throwable $e) { echo json_encode([ 'exito' => false, 'mensaje' => 'Excepción del servidor: ' . $e->getMessage() ], JSON_UNESCAPED_UNICODE); exit(); }