Files
MVE/views/agencias/alta_agentes.php
2025-07-14 13:27:15 -06:00

390 lines
22 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 include __DIR__ . '/../partials/sidebar_agencia.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>👨‍✈️ Administración de Agentes</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>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<!-- 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>
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: normal; 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%; }
table.dataTable thead th { background: #343a40; color: #fff; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content px-4">
<h3 class="mb-4 animate__animated animate__fadeInDown title_glow">👨‍✈️ Administración de Agentes</h3>
<!-- FORMULARIO -->
<div class="card p-4 mb-4 shadow-sm bg-white card-hover position-relative h-auto">
<form action="/IMPORTADORES/agencias/guardarAgente" method="POST" id="formAltaAgentes">
<h5 class="mb-3"> Nuevo Agente</h5>
<div class="row g-3">
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="nombre" id="nombre" type="text" class="form-control input-pulse input-gradient" placeholder=" " required>
<label for="nombre" class="form-label">Nombre *</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="email" type="email" id="email" class="form-control input-pulse input-typing" placeholder=" " required>
<label for="email" class="form-label">Correo *</label>
</div>
</div>
<div class="col-md-2 form-group-animated">
<div class="form-floating-custom">
<input name="password" type="password" id="password" class="form-control input-pulse input-ripple" placeholder=" " required>
<label for="password" class="form-label">Contraseña *</label>
</div>
</div>
<div class="col-md-2 form-group-animated">
<div class="form-floating-custom">
<select name="tipo_usuario" id="tipo" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Tipo de Usuario --</option>
<option value="agente_aduanal">Agente Aduanal</option>
</select>
<label for="tipo" class="form-label">Tipo de Usuario</label>
</div>
</div>
</div>
<div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button>
</div>
</form>
</div>
<h4 class="mb-4">✅ Agentes Activos</h4>
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-agentes-activos">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Nombre</th>
<th>Correo</th>
<th>Tipo</th>
<th>Creado por</th>
<th>Fecha Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($agentes as $a): ?>
<tr>
<td><?= $a['id_usuario'] ?></td>
<td><?= htmlspecialchars(decrypt($a['nombre'] ?? '')) ?></td>
<td><?= htmlspecialchars(decrypt($a['email'] ?? '')) ?></td>
<td><?= htmlspecialchars($a['tipo_usuario_sistema'] ?? '') ?></td>
<td><?= htmlspecialchars(decrypt($a['nombre_creador'] ?? '')) ?></td>
<td><?= isset($a['creado_en']) && $a['creado_en'] instanceof DateTime ? $a['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (in_array($a['tipo_usuario_sistema'], ['agente_aduanal'])): ?>
<a href="/IMPORTADORES/vinculaciones/desvincularAgencia?id=<?= $a['id_relacion'] ?>&tipo=<?= $a['tipo_vinculacion'] ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('¿Está seguro de que desea desvincular este usuario?')">
Desvincular
</a>
<?php else: ?>
<span class="text-muted small">N/A</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-agentes-activos').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form-select');
const selects = document.querySelectorAll('select.form-select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// UN SOLO EVENT LISTENER PARA EL FORMULARIO
document.getElementById('formAltaAgentes').addEventListener('submit', function(e) {
console.log('Formulario enviándose...');
console.log('Datos del formulario:');
const formData = new FormData(this);
for (let [key, value] of formData.entries()) {
console.log(key + ': ' + value);
}
// Verificar que todos los campos requeridos estén llenos
const nombre = document.getElementById('nombre').value;
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const tipo = document.getElementById('tipo').value;
if (!nombre || !email || !password || !tipo) {
console.error('Faltan campos requeridos');
console.log('nombre:', nombre);
console.log('email:', email);
console.log('password:', password ? 'NO VACÍO' : 'VACÍO');
console.log('tipo:', tipo);
// Mostrar animación de error en campos vacíos
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
}
});
e.preventDefault(); // Solo prevenir si hay errores
return false;
}
if (tipo !== 'agente_aduanal') {
console.error('Tipo de usuario inválido:', tipo);
e.preventDefault(); // Solo prevenir si hay errores
return false;
}
console.log('Validación del formulario pasada');
// Deshabilitar botón de envío para evitar envíos múltiples
const submitButton = this.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
submitButton.innerHTML = 'Enviando...';
}
// NO HACER e.preventDefault() aquí - dejar que el formulario se envíe normalmente
// El formulario se enviará automáticamente a la acción especificada
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Usuario creado', text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.', confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'user_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_data'): ?>
Swal.fire({ icon: 'error', title: 'Datos inválidos', text: 'Por favor, completa todos los campos requeridos correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_user_type'): ?>
Swal.fire({ icon: 'error', title: 'Tipo de usuario inválido', text: 'El tipo de usuario seleccionado no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'email_exists'): ?>
Swal.fire({ icon: 'error', title: 'Correo ya registrado', text: 'Este correo electrónico ya está registrado en el sistema.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>