556 lines
22 KiB
PHP
556 lines
22 KiB
PHP
<?php include __DIR__ . '/../partials/sidebar_agente.php'; ?>
|
||
|
||
<!DOCTYPE html>
|
||
<html lang="es">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Dashboard | Agente Aduanal</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>
|
||
<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; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="content">
|
||
<h4 class="mb-4">🌍 Locaciones</h4>
|
||
<a href="/IMPORTADORES/locaciones/alta" class="btn btn-success mb-3">➕ Agregar Locación</a>
|
||
<div class="card p-3 shadow-sm">
|
||
<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"
|
||
onclick="editarItem('ciudad', <?= $loc['id_ciudad'] ?>, '<?= htmlspecialchars($loc['nombre_ciudad'], ENT_QUOTES) ?>', '', '', '', <?= $loc['id_estado'] ?>)">
|
||
✏️
|
||
</button>
|
||
<button class="btn btn-sm btn-danger"
|
||
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"
|
||
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"
|
||
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"
|
||
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"
|
||
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" data-bs-dismiss="modal">Cancelar</button>
|
||
<button type="submit" class="btn btn-primary">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>
|