Files
MVE/views/locaciones/gestion_locaciones.php
2025-07-04 12:57:56 -06:00

597 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
$tipoUsuario = $_SESSION['tipo_usuario'] ?? null;
if ($tipoUsuario === 'agente_aduanal') {
include __DIR__ . '/../partials/sidebar_agente.php';
} elseif ($tipoUsuario === 'admin_agencia') {
include __DIR__ . '/../partials/sidebar_agencia.php';
} else {
// Sidebar por defecto o error
echo "<!-- ⚠️ No se encontró sidebar para el tipo de usuario -->";
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🌍 Locaciones</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
table.dataTable thead th { background:#343a40; color:#fff; }
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🌍 Locaciones</h4>
<a href="/IMPORTADORES/locaciones/alta" class="btn btn-success mb-3 mt-auto w-auto btn-animated"> Agregar Locación</a>
<div class="card p-3 shadow-sm card-hover position-relative h-auto">
<div class="table-responsive">
<table class="table table-striped" id="tabla-locaciones">
<thead class="table-dark">
<tr>
<th>ID País</th>
<th>País</th>
<th>ISO3</th>
<th>ID Estado</th>
<th>Estado</th>
<th>ID Ciudad</th>
<th>Ciudad</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($locaciones as $loc): ?>
<tr>
<td><?= htmlspecialchars($loc['id_pais']) ?></td>
<td><?= htmlspecialchars($loc['nombre_pais']) ?></td>
<td><?= htmlspecialchars($loc['iso3']) ?></td>
<td><?= htmlspecialchars($loc['id_estado']) ?></td>
<td><?= htmlspecialchars($loc['nombre_estado']) ?></td>
<td><?= htmlspecialchars($loc['id_ciudad']) ?></td>
<td><?= htmlspecialchars($loc['nombre_ciudad']) ?></td>
<td>
<!-- Botones de acción (editar/eliminar) -->
<?php if (!empty($loc['id_ciudad'])): ?>
<!-- Es una ciudad -->
<button class="btn btn-sm btn-primary mt-auto w-auto btn-animated"
onclick="editarItem('ciudad', <?= $loc['id_ciudad'] ?>, '<?= htmlspecialchars($loc['nombre_ciudad'], ENT_QUOTES) ?>', '', '', '', <?= $loc['id_estado'] ?>)">
✏️
</button>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="eliminarItem('ciudad', <?= $loc['id_ciudad'] ?>, '<?= htmlspecialchars($loc['nombre_ciudad'], ENT_QUOTES) ?>')">
🗑️
</button>
<?php elseif (!empty($loc['id_estado'])): ?>
<!-- Es un estado - CORREGIDO: Obtener abreviatura correctamente -->
<button class="btn btn-sm btn-primary mt-auto w-auto btn-animated"
onclick="editarItem('estado', <?= $loc['id_estado'] ?>, '<?= htmlspecialchars($loc['nombre_estado'], ENT_QUOTES) ?>', '', '', '<?= htmlspecialchars($loc['abreviatura'] ?? '', ENT_QUOTES) ?>', <?= $loc['id_pais'] ?>)">
✏️
</button>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="eliminarItem('estado', <?= $loc['id_estado'] ?>, '<?= htmlspecialchars($loc['nombre_estado'], ENT_QUOTES) ?>')">
🗑️
</button>
<?php else: ?>
<!-- Es un país -->
<button class="btn btn-sm btn-primary mt-auto w-auto btn-animated"
onclick="editarItem('pais', <?= $loc['id_pais'] ?>, '<?= htmlspecialchars($loc['nombre_pais'], ENT_QUOTES) ?>', '<?= htmlspecialchars($loc['iso2'] ?? '', ENT_QUOTES) ?>', '<?= htmlspecialchars($loc['iso3'] ?? '', ENT_QUOTES) ?>')">
✏️
</button>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="eliminarItem('pais', <?= $loc['id_pais'] ?>, '<?= htmlspecialchars($loc['nombre_pais'], ENT_QUOTES) ?>')">
🗑️
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal para Editar -->
<div class="modal fade" id="editarModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">✏️ Editar <span id="tipoItem"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editarForm">
<div class="modal-body">
<input name="editTipo" type="hidden" id="editTipo">
<input name="editId" type="hidden" id="editId">
<!-- Campos para País -->
<div id="camposPais" style="display: none;">
<div class="mb-3">
<label for="editNombrePais" class="form-label">Nombre del País *</label>
<input name="editNombrePais" type="text" class="form-control" id="editNombrePais">
</div>
<div class="row">
<div class="col-md-6">
<label for="editIso2" class="form-label">ISO2</label>
<input name="editIso2" type="text" class="form-control" id="editIso2" maxlength="2">
</div>
<div class="col-md-6">
<label for="editIso3" class="form-label">ISO3</label>
<input name="editIso3" type="text" class="form-control" id="editIso3" maxlength="3">
</div>
</div>
</div>
<!-- Campos para Estado -->
<div id="camposEstado" style="display: none;">
<div class="mb-3">
<label for="editPaisEstado" class="form-label">País *</label>
<select name="editPaisEstado" class="form-select" id="editPaisEstado">
<option value="">Selecciona país</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"><?= htmlspecialchars($p['nombre']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="editNombreEstado" class="form-label">Nombre del Estado *</label>
<input name="editNombreEstado" type="text" class="form-control" id="editNombreEstado">
</div>
<div class="mb-3">
<label for="editAbreviatura" class="form-label">Abreviatura</label>
<input name="editAbreviatura" type="text" class="form-control" id="editAbreviatura" maxlength="10">
</div>
</div>
<!-- Campos para Ciudad -->
<div id="camposCiudad" style="display: none;">
<div class="mb-3">
<label for="editPaisCiudad" class="form-label">País *</label>
<select name="editPaisCiudad" class="form-select" id="editPaisCiudad">
<option value="">Selecciona país</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"><?= htmlspecialchars($p['nombre']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="editEstadoCiudad" class="form-label">Estado *</label>
<select name="editEstadoCiudad" class="form-select" id="editEstadoCiudad">
<option value="">Primero selecciona país</option>
</select>
</div>
<div class="mb-3">
<label for="editNombreCiudad" class="form-label">Nombre de la Ciudad *</label>
<input name="editNombreCiudad" type="text" class="form-control" id="editNombreCiudad">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary mt-auto w-auto btn-animated" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Cambios</button>
</div>
</form>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-locaciones').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
// Función para cargar estados
function cargarEstados(paisId, selectElement, estadoSeleccionado = null) {
selectElement.innerHTML = '<option>Cargando...</option>';
selectElement.disabled = true;
fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`)
.then(response => response.json())
.then(estados => {
selectElement.innerHTML = '<option value="">Selecciona estado</option>';
estados.forEach(estado => {
const option = new Option(estado.nombre, estado.id_estado);
if (estadoSeleccionado && estado.id_estado == estadoSeleccionado) {
option.selected = true;
}
selectElement.add(option);
});
selectElement.disabled = false;
})
.catch(error => {
console.error('Error:', error);
selectElement.innerHTML = '<option value="">Error al cargar</option>';
});
}
// Evento para cargar estados en modal de edición
document.getElementById('editPaisCiudad').addEventListener('change', function(e) {
const paisId = e.target.value;
const estadoSelect = document.getElementById('editEstadoCiudad');
if (paisId) {
cargarEstados(paisId, estadoSelect);
} else {
estadoSelect.innerHTML = '<option value="">Primero selecciona país</option>';
estadoSelect.disabled = true;
}
});
// Función editarItem mejorada para manejar caracteres especiales
function editarItem(tipo, id, nombre, iso2 = '', iso3 = '', abreviatura = '', padreId = null) {
try {
// Sanitizar parámetros para evitar problemas con caracteres especiales
tipo = String(tipo).trim();
id = parseInt(id);
nombre = String(nombre).trim();
iso2 = String(iso2).trim();
iso3 = String(iso3).trim();
abreviatura = String(abreviatura).trim();
padreId = padreId ? parseInt(padreId) : null;
// Validaciones básicas
if (!tipo || !id || !nombre) {
throw new Error('Parámetros requeridos faltantes');
}
document.getElementById('editTipo').value = tipo;
document.getElementById('editId').value = id;
document.getElementById('tipoItem').textContent = tipo.charAt(0).toUpperCase() + tipo.slice(1);
// Ocultar todos los campos
document.getElementById('camposPais').style.display = 'none';
document.getElementById('camposEstado').style.display = 'none';
document.getElementById('camposCiudad').style.display = 'none';
if (tipo === 'pais') {
document.getElementById('camposPais').style.display = 'block';
document.getElementById('editNombrePais').value = nombre;
document.getElementById('editIso2').value = iso2;
document.getElementById('editIso3').value = iso3;
} else if (tipo === 'estado') {
document.getElementById('camposEstado').style.display = 'block';
document.getElementById('editNombreEstado').value = nombre;
document.getElementById('editAbreviatura').value = abreviatura;
document.getElementById('editPaisEstado').value = padreId || '';
} else if (tipo === 'ciudad') {
document.getElementById('camposCiudad').style.display = 'block';
document.getElementById('editNombreCiudad').value = nombre;
// Necesitamos obtener el país del estado para cargar los estados
if (padreId) {
fetch(`/IMPORTADORES/locaciones/obtenerPaisPorEstado?estado=${padreId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text(); // Primero como texto para verificar
})
.then(text => {
try {
return JSON.parse(text);
} catch (jsonError) {
console.error('Response no es JSON válido:', text);
throw new Error('Respuesta del servidor no es JSON válido');
}
})
.then(data => {
if (data.success) {
document.getElementById('editPaisCiudad').value = data.pais_id;
cargarEstados(data.pais_id, document.getElementById('editEstadoCiudad'), padreId);
} else {
console.error('Error al obtener país:', data.message);
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message || 'No se pudo cargar la información del país'
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error al cargar la información: ' + error.message
});
});
}
}
new bootstrap.Modal(document.getElementById('editarModal')).show();
} catch (error) {
console.error('Error en editarItem:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error al abrir el formulario de edición: ' + error.message
});
}
}
// Función eliminarItem mejorada
function eliminarItem(tipo, id, nombre) {
try {
// Sanitizar parámetros
tipo = String(tipo).trim();
id = parseInt(id);
nombre = String(nombre).trim();
if (!tipo || !id || !nombre) {
throw new Error('Parámetros requeridos faltantes');
}
Swal.fire({
title: '¿Estás seguro?',
text: `¿Deseas eliminar el ${tipo} "${nombre}"?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar'
}).then((result) => {
if (result.isConfirmed) {
fetch('/IMPORTADORES/locaciones/eliminar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tipo: tipo,
id: id
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text(); // Primero como texto
})
.then(text => {
try {
return JSON.parse(text);
} catch (jsonError) {
console.error('Response no es JSON válido:', text);
throw new Error('Respuesta del servidor no es JSON válido');
}
})
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Eliminado!',
text: data.message,
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message || 'Error desconocido al eliminar'
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al eliminar: ' + error.message
});
});
}
});
} catch (error) {
console.error('Error en eliminarItem:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error al procesar eliminación: ' + error.message
});
}
}
// REEMPLAZA tu función de submit del formulario con esta versión corregida
document.getElementById('editarForm').addEventListener('submit', function(e) {
e.preventDefault();
const tipo = document.getElementById('editTipo').value;
const id = document.getElementById('editId').value;
// VALIDACIÓN BÁSICA
if (!tipo || !id) {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Faltan datos requeridos'
});
return;
}
let formData = new FormData();
formData.append('tipo', tipo);
formData.append('id', id);
// Determinar endpoint y datos según el tipo
let endpoint = '';
let isValid = true;
if (tipo === 'pais') {
const nombre = document.getElementById('editNombrePais').value.trim();
if (!nombre) {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'El nombre del país es obligatorio'
});
return;
}
endpoint = '/IMPORTADORES/locaciones/actualizarPais';
formData.append('nombre', nombre);
formData.append('iso2', document.getElementById('editIso2').value.trim());
formData.append('iso3', document.getElementById('editIso3').value.trim());
} else if (tipo === 'estado') {
const nombre = document.getElementById('editNombreEstado').value.trim();
const paisId = document.getElementById('editPaisEstado').value;
if (!nombre || !paisId) {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'El nombre del estado y el país son obligatorios'
});
return;
}
endpoint = '/IMPORTADORES/locaciones/actualizarEstado';
formData.append('nombre', nombre);
formData.append('abreviatura', document.getElementById('editAbreviatura').value.trim());
formData.append('pais_id', paisId);
} else if (tipo === 'ciudad') {
const nombre = document.getElementById('editNombreCiudad').value.trim();
const estadoId = document.getElementById('editEstadoCiudad').value;
if (!nombre || !estadoId) {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'El nombre de la ciudad y el estado son obligatorios'
});
return;
}
endpoint = '/IMPORTADORES/locaciones/actualizarCiudad';
formData.append('nombre', nombre);
formData.append('estado_id', estadoId);
}
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
// INDICADOR DE CARGA
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
submitBtn.disabled = true;
// ENVÍO AJAX al endpoint específico
fetch(endpoint, {
method: 'POST',
body: formData
})
.then(response => {
// VERIFICAR QUE LA RESPUESTA SEA OK
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
}).then(() => {
// CERRAR MODAL Y RECARGAR
const modal = bootstrap.Modal.getInstance(document.getElementById('editarModal'));
if (modal) {
modal.hide();
}
location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message || 'Error desconocido'
});
}
})
.catch(error => {
console.error('Error completo:', error);
Swal.fire({
icon: 'error',
title: 'Error de conexión',
text: 'No se pudo procesar la solicitud. Verifique su conexión.'
});
})
.finally(() => {
// RESTAURAR BOTÓN
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
</script>
</body>
</html>