CRUD gestión de locaciones

This commit is contained in:
2025-06-09 08:50:23 -06:00
parent 2eb3ec6061
commit 55fb7c0bf4
28 changed files with 1982 additions and 234 deletions

View File

@@ -0,0 +1,286 @@
<?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">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
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; }
@media (min-width: 768px) { .content { margin-left: 250px; } }
@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; }
.hide { display: none !important; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4">🗺️ Nuevas Locaciones</h4>
<div class="row g-3">
<!-- Nuevo Estado -->
<div class="col-md">
<div class="card p-4 bg-white shadow-sm">
<form id="estadoForm">
<h4 class="mb-4 text-dark"> Nuevo Estado</h4><br>
<!-- País -->
<div class="col-md-12">
<label for="paisEstado" class="form-label">País *</label>
<select id="paisEstado" name="pais" class="form-select" required>
<option value="">Selecciona país</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>">
<?= htmlspecialchars($p['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div><br><br><br>
<!-- Estado -->
<div class="col-md-12">
<label for="entidadEstado" class="form-label">Entidad / Provincia *</label>
<input id="entidadEstado" name="entidad" class="form-control" required>
</div><br><br>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-success w-100">
<i class="fas fa-plus"></i> Registrar Estado
</button>
<a href="/IMPORTADORES/agentes/lista" class="btn btn-secondary ms-2">Cancelar</a>
</div>
</form>
</div>
</div>
<!-- Nueva Ciudad -->
<div class="col-md">
<div class="card p-4 bg-white shadow-sm">
<form id="ciudadForm" action="/IMPORTADORES/agentes/guadarCiudad" method="POST" enctype="multipart/form-data">
<h4 class="mb-4 text-dark"> Nueva Ciudad</h4>
<!-- País -->
<div class="col-md-12">
<label for="paisCiudad" class="form-label">País *</label>
<select id="paisCiudad" name="pais" class="form-select" required>
<option value="">Selecciona país</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>">
<?= htmlspecialchars($p['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div><br>
<!-- Estado -->
<div class="col-md-12">
<label for="entidadCiudad" class="form-label">Entidad / Provincia *</label>
<select id="estadoCiudad" name="entidad" class="form-select" required disabled>
<option value="">Primero país…</option>
</select>
</div><br>
<!-- Ciudad -->
<div class="col-md-12">
<label for="nombreCiudad" class="form-label">Ciudad *</label>
<input id="nombreCiudad" name="ciudad" class="form-control" required>
</div><br>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-success w-100">
<i class="fas fa-plus"></i> Registrar Ciudad
</button>
<a href="/IMPORTADORES/agentes/lista" class="btn btn-secondary ms-2">Cancelar</a>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
// Función para cargar estados
function cargarEstados(paisId, selectElement) {
selectElement.innerHTML = '<option>Cargando...</option>';
selectElement.disabled = true;
fetch(`/IMPORTADORES/agentes/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);
selectElement.add(option);
});
selectElement.disabled = false;
})
.catch(error => {
console.error('Error:', error);
selectElement.innerHTML = '<option value="">Error al cargar</option>';
});
}
// Evento para cargar estados cuando se selecciona país en formulario de ciudad
document.getElementById('paisCiudad').addEventListener('change', function(e) {
const paisId = e.target.value;
const estadoSelect = document.getElementById('estadoCiudad');
if (paisId) {
cargarEstados(paisId, estadoSelect);
} else {
estadoSelect.innerHTML = '<option value="">Primero selecciona país</option>';
estadoSelect.disabled = true;
}
});
// Formulario de nuevo estado
document.getElementById('estadoForm').addEventListener('submit', function(e) {
e.preventDefault();
// Obtener valores directamente
const paisSelect = document.getElementById('paisEstado');
const entidadInput = document.getElementById('entidadEstado');
const paisValue = paisSelect.value;
const entidadValue = entidadInput.value.trim();
// Validación en frontend
if (!paisValue) {
Swal.fire({
icon: 'warning',
title: 'Campo requerido',
text: 'Debe seleccionar un país'
});
return;
}
if (!entidadValue) {
Swal.fire({
icon: 'warning',
title: 'Campo requerido',
text: 'Debe ingresar el nombre del estado'
});
return;
}
// Crear FormData manualmente
const formData = new FormData();
formData.append('pais', paisValue);
formData.append('entidad', entidadValue);
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
submitBtn.disabled = true;
// Debug: mostrar lo que se va a enviar
console.log('Enviando:', {
pais: paisValue,
entidad: entidadValue
});
fetch('/IMPORTADORES/agentes/guardarEstado', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
this.reset();
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al procesar la solicitud'
});
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
// Formulario de nueva ciudad
document.getElementById('ciudadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
submitBtn.disabled = true;
fetch('/IMPORTADORES/agentes/guardarCiudad', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
this.reset();
// Resetear el select de estados
document.getElementById('estadoCiudad').innerHTML = '<option value="">Primero selecciona país</option>';
document.getElementById('estadoCiudad').disabled = true;
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al procesar la solicitud'
});
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,492 @@
<?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/agentes/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/agentes/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 para abrir modal de edición
function editarItem(tipo, id, nombre, iso2 = '', iso3 = '', abreviatura = '', padreId = null) {
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/agentes/obtenerPaisPorEstado?estado=${padreId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.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: 'No se pudo cargar la información del país' + data.message
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error al cargar la información'
});
});
}
}
new bootstrap.Modal(document.getElementById('editarModal')).show();
}
// Función para eliminar item
function eliminarItem(tipo, id, nombre) {
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/agentes/eliminar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tipo: tipo,
id: id
})
})
.then(response => response.json())
.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
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al eliminar'
});
});
}
});
}
// 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/agentes/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/agentes/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/agentes/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>

View File

@@ -0,0 +1,85 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!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>
<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>
<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>
</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>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</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'
}
});
});
</script>
</body>
</html>