Files
MVE/views/admin/alta_agencias.php

351 lines
18 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_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🏪 Alta de Agencias Aduanales</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>
<!-- 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 thead th { background: #343a40; color: #fff; }
/* ========== ANIMACIONES PARA INPUTS ========== */
/* 1. Animación al hacer focus */
.form-control { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control: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); }
/* 2. Animación al escribir */
.form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 3. Efecto de shake en validación */
.form-control.shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* 4. Floating labels con animación */
.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; }
/* 5. Efecto de pulso en hover */
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
/* 6. Gradiente animado en el borde */
.input-gradient { position: relative; overflow: hidden; }
.input-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; }
.input-gradient:focus::before { left: 100%; }
/* 7. Efecto de brillo en validación exitosa */
.form-control.valid { 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); }
}
/* 8. Typing effect simulation */
.input-typing { position: relative; }
.input-typing::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* 9. Efecto de ondas */
.input-ripple { position: relative; overflow: hidden; }
.input-ripple::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:focus::before { width: 300px; height: 300px; }
/* 10. Slide-up animation para los campos */
.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; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🏪 Alta de Agencias Aduanales</h4>
<!-- FORMULARIO -->
<div class="card p-4 mb-5 shadow-sm card-hover position-relative h-auto">
<form action="/IMPORTADORES/administrador/guardar_agencia" method="POST" id="formAltaAgencias">
<h5 class="mb-3"> Nueva Agencia Aduanal</h5>
<div class="row g-3">
<!-- Usando floating labels animados -->
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="text" class="form-control input-pulse input-gradient" name="nombre_agencia" id="nombre_agencia" placeholder=" " required>
<label for="nombre_agencia" class="form-label">Nombre de la agencia *</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="text" class="form-control input-pulse input-ripple" name="rfc_agencia" maxlength="13" id="rfc_agencia" placeholder=" " required>
<label for="rfc_agencia" class="form-label">RFC *</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="email" class="form-control input-pulse input-typing" name="correo" id="correo" placeholder=" " required>
<label for="correo" class="form-label">Correo electrónico *</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="text" class="form-control input-pulse input-gradient" name="telefono" maxlength="11" id="telefono" placeholder=" " required>
<label for="telefono" class="form-label">Teléfono *</label>
</div>
</div>
<div class="col-md-12 form-group-animated">
<div class="form-floating-custom">
<input type="text" class="form-control input-pulse input-ripple" name="direccion" id="direccion" placeholder=" " required>
<label for="direccion" class="form-label">Dirección *</label>
</div>
</div>
<div class="col-12 form-group-animated">
<hr>
<p><strong>Administrador de la Agencia</strong></p>
</div>
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="text" class="form-control input-pulse input-gradient" name="nombre_admin" id="nombre_admin" placeholder=" " required>
<label for="nombre_admin" class="form-label">Nombre *</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<div class="form-floating-custom">
<input type="email" class="form-control input-pulse input-typing" name="correo_admin" id="correo_admin" placeholder=" " required>
<label for="correo_admin" class="form-label">Correo Administrador *</label>
</div>
</div>
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button>
</div>
</div>
</form>
</div>
</div>
<script>
// Registro de Agencia
document.getElementById('formAltaAgencias').addEventListener('submit', function(e) {
e.preventDefault();
const nombre = document.getElementById('nombre_agencia').value.trim();
const rfc_agencia = document.getElementById('rfc_agencia').value.trim().toUpperCase();
const correo = document.getElementById('correo').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const direccion = document.getElementById('direccion').value.trim();
const nombre_admin = document.getElementById('nombre_admin').value.trim();
const correo_admin = document.getElementById('correo_admin').value.trim();
// Expresiones regulares
const correoRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
const telefonoRegex = /^\d{3}\s\d{7}$/;
// Validación de nombre
if (!nombre) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre_agencia').focus();
return;
}
// Validación de correo
if (!correo) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('correo').focus();
return;
}
if (!correoRegex.test(correo)) {
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
document.getElementById('correo').focus();
return;
}
// Validación de RFC
if (!rfc_agencia) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc_agencia').focus();
return;
}
if (!rfcRegex.test(rfc_agencia)) {
Swal.fire({ icon: 'error', title: 'RFC inválido', text: 'El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).', confirmButtonColor: '#dc3545' });
document.getElementById('rfc_agencia').focus();
return;
}
// Validación de teléfono
if (!telefono) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
return;
}
if (!telefonoRegex.test(telefono)) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos (ej. 123 4567890).', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
return;
}
// Validación de dirección
if (!direccion) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La dirección es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('direccion').focus();
return;
}
// Validación de administrador
if (!nombre_admin) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre del administrador es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre_admin').focus();
return;
}
// Validación de correo de administrador
if (!correo_admin) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo de administrador es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('correo_admin').focus();
return;
}
if (!correoRegex.test(correo_admin)) {
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
document.getElementById('correo_admin').focus();
return;
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();
// Validación del formulario
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('.form-control');
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);
});
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Agencia registrada',
text: 'La agencia aduanal se ha registrada correctamente y se ha enviado un correo con las credenciales de acceso al administrador.',
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_agencia'): ?>
Swal.fire({ icon: 'error', title: 'ID de agencia inválido', text: 'El identificador la agencia no es válido.', 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'] === 'save_agencia_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar agencia', text: 'No se pudo guardar la agencia. Inténtalo nuevamente.', 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'] === '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>