Aprobación de Agencias

This commit is contained in:
2025-07-14 13:27:15 -06:00
parent e820f81d31
commit 0e88fea37e
36 changed files with 2807 additions and 1128 deletions

View File

@@ -129,6 +129,16 @@
</script>
<?php endif; ?>
<?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'): ?>

View File

@@ -62,110 +62,98 @@
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 ========== */
/* 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; }
/* ========== 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); }
}
/* 4. Floating labels con animación */
/* 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; }
/* 5. Efecto de pulso en hover */
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.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); }
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; }
.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); }
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; }
.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; }
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 */
.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(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 ========== */
/* 1. Estilos base para select */
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.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; }
/* 2. Animación al hacer focus */
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"); }
/* 3. Animación cuando tiene valor seleccionado */
.form-select:not([value=""]):not(:invalid) { 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"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-floating-custom .form-select:focus ~ .form-label,
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de shake en validación para select */
.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; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.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::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%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.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"); }
/* 10. Efecto de ondas para select */
.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; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.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; }
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>
@@ -200,15 +188,15 @@
<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=""></option>
<option value="">-- Tipo de Usuario --</option>
<option value="super_admin">Administrador General</option>
<option value="importador">Importador</option>
</select>
<label for="tipo" class="form-label">Tipo</label>
<label for="tipo" class="form-label">Tipo de Usuario</label>
</div>
</div>
</div>
<div class="text-end mt-4">
<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>
@@ -285,6 +273,28 @@
}, 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');
}
}
}
// Validación del formulario
document.getElementById('formAltaUsuarios').addEventListener('submit', function(e) {
@@ -297,17 +307,6 @@
isValid = false;
}
});
if (isValid) {
// Aquí puedes agregar tu lógica de envío
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: 'Agencia registrada correctamente',
showConfirmButton: false,
timer: 1500
});
}
});
});
</script>

View File

@@ -127,6 +127,30 @@
});
});
</script>
<?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'] === 'error_user'): ?>
Swal.fire({ icon: 'error', title: 'Error de creación', text: 'El usuario administrador no se creo correctamente.', 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'] === 'get_id_agencia'): ?>
Swal.fire({ icon: 'error', title: 'Error de obtención', text: 'El identificador no se pudo obtener correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_request'): ?>
Swal.fire({ icon: 'error', title: 'ID de solicitud inválido', text: 'El identificador de la solicitud 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 administrador de la agencia.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_request_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'Error al actualizar la solicitud.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'get_id_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error de obtención', text: 'Error al obtener el id de la agencia recien creada.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -61,6 +61,99 @@
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>
@@ -70,26 +163,38 @@
<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">
<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">
<input name="nombre" class="form-control" placeholder="Nombre completo" required>
<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">
<input name="email" type="email" class="form-control" placeholder="Correo electrónico" required>
<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">
<input name="password" type="password" class="form-control" placeholder="Contraseña" required>
<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">
<select name="tipo_usuario" class="form-select" required>
<option value="">Tipo</option>
<option value="agente_aduanal">Agente Aduanal</option>
</select>
<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">
<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>
@@ -147,14 +252,112 @@
}
});
});
// 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' });
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; ?>

View File

@@ -44,8 +44,8 @@
.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); }
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@@ -72,8 +72,8 @@
.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); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
@@ -94,14 +94,14 @@
.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); }
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; }
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
@@ -169,8 +169,8 @@
.form-floating-fecha .form-control:not(:placeholder-shown) ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-fecha .form-control:focus, .form-floating-fecha .form-control:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: 1px solid #ccc !important; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: 1px solid #0d6efd !important; box-shadow: none !important; }
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
@@ -290,6 +290,7 @@
const telefonoRegex = /^\d{3}\s\d{7}$/;
const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const soloNumerosRegex = /^[0-9]+$/;
const fechaRegex = /^\d{2}\/\d{2}\/\d{4}$/; // formato dd/mm/aaaa
// VALIDAR CAMPOS OBLIGATORIOS PRIMERO
if (!transportista_id) {
@@ -320,7 +321,7 @@
if (!soloNumerosRegex.test(numero_licencia)) {
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545'
});
document.getElementById('Clave').focus();
document.getElementById('numero_licencia').focus();
return;
}
// VALIDAR CAMPOS OPCIONALES (solo si tienen contenido)
@@ -336,8 +337,19 @@
}
// VALIDAR FECHA DE INGRESO (solo si se proporciona)
if (fecha_ingreso) {
const fechaIngreso = new Date(fecha_ingreso);
const fechaActual = new Date();
// Validar formato primero
if (!fechaRegex.test(fecha_ingreso)) {
Swal.fire({ icon: 'error', title: 'Formato de fecha inválido', text: 'La fecha debe tener el formato dd/mm/aaaa', confirmButtonColor: '#dc3545' });
document.getElementById('fecha_ingreso').focus();
return;
}
// Convertir fecha correctamente (dd/mm/aaaa -> mm/dd/aaaa)
const [dia, mes, año] = fecha_ingreso.split('/');
const fechaIngreso = new Date(año, mes - 1, dia); // mes - 1 porque Date usa 0-11
const fechaActual = new Date();
fechaActual.setHours(0, 0, 0, 0); // Normalizar a medianoche
if (fechaIngreso > fechaActual) {
Swal.fire({ icon: 'error', title: 'Fecha inválida', text: 'La fecha de ingreso no puede ser futura.', confirmButtonColor: '#dc3545' });
document.getElementById('fecha_ingreso').focus();
@@ -415,21 +427,6 @@
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
});
});
@@ -465,56 +462,6 @@
}
});
// Validación específica para campos de texto
document.addEventListener('DOMContentLoaded', function() {
// Validación del número de licencia (solo números y letras)
const licenciaInput = document.getElementById('numero_licencia');
if (licenciaInput) {
licenciaInput.addEventListener('input', function() {
this.value = this.value.replace(/[^A-Za-z0-9]/g, '');
});
}
// Validación del teléfono (solo números)
const telefonoInput = document.getElementById('telefono');
if (telefonoInput) {
telefonoInput.addEventListener('input', function() {
this.value = this.value.replace(/[^0-9]/g, '');
});
}
// Validación del email en tiempo real
const emailInput = document.getElementById('email');
if (emailInput) {
emailInput.addEventListener('input', function() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (this.value && !emailRegex.test(this.value)) {
this.setCustomValidity('Por favor, ingrese un email válido');
} else {
this.setCustomValidity('');
}
});
}
});
// Función para mostrar notificaciones
function showNotification(message, type = 'info') {
// Crear el elemento de notificación
const notification = document.createElement('div');
notification.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`;
notification.style.cssText = ` top: 20px; right: 20px; z-index: 9999; min-width: 300px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);`;
notification.innerHTML = `${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
document.body.appendChild(notification);
// Auto-remover después de 5 segundos
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
// Inicializar Flatpickr para la fecha
flatpickr("#fecha_ingreso", {
dateFormat: "d/m/Y",

View File

@@ -14,6 +14,12 @@
<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">
<!-- CSS de Flatpickr -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<!-- JS de Flatpickr -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- Idioma español -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/es.js"></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; }
@@ -58,6 +64,114 @@
.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%; }
/* ========== 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; }
/* ========== ESTILOS PARA CAMPO FECHA ========== */
.form-floating-fecha { position: relative; margin-bottom: 1.5rem; }
.form-floating-fecha .form-control { padding: 1rem 0.75rem; transition: all 0.3s ease; border: 2px solid #dee2e6; }
.form-floating-fecha .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); }
.form-floating-fecha .form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-floating-fecha .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%);pointer-events: none;
transition: all 0.3s ease; color: #6c757d; opacity: 0; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; }
.form-floating-fecha .form-control:focus ~ .form-label,
.form-floating-fecha .form-control:not(:placeholder-shown) ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-fecha .form-control:focus, .form-floating-fecha .form-control:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
</head>
<body>
@@ -68,100 +182,128 @@
<form action="/IMPORTADORES/choferes/actualizar" method="POST" enctype="multipart/form-data" id="formEditChoferes">
<input type="hidden" name="id_chofer" value="<?= (int)$chofer['id_chofer'] ?>">
<!-- Transportista -->
<div class="mb-3">
<label for="transportista_id" class="form-label">Transportista</label>
<select name="transportista_id" id="transportista_id" class="form-select" required>
<option value="">-- Selecciona un transportista --</option>
<?php foreach ($transportistas as $t): ?>
<option value="<?= $t['id_transportista'] ?>"
<?= $chofer['transportista_id'] == $t['id_transportista'] ? 'selected' : '' ?>>
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre'] . ' - ' . $t['ciudad_nombre'] . ' - ' . $t['domicilio']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="row mb-3">
<!-- Transportista -->
<div class="col-md-12 form-group-animated">
<label for="transportista_id" class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="transportista_id" id="transportista_id" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un transportista --</option>
<?php foreach ($transportistas as $t): ?>
<option value="<?= $t['id_transportista'] ?>"
<?= $chofer['transportista_id'] == $t['id_transportista'] ? 'selected' : '' ?>>
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre'] . ' - ' . $t['ciudad_nombre'] . ' - ' . $t['domicilio']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="transportista_id" class="form-label">Transportista</label>
</div>
</div>
<div class="row">
<!-- Nombre -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="nombre" class="form-label">Nombre</label>
<input name="nombre" id="nombre" type="text" class="form-control"
value="<?= htmlspecialchars($chofer['nombre']) ?>" required>
<div class="form-floating-custom">
<input name="nombre" id="nombre" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['nombre']) ?>" required>
<label for="nombre" class="form-label">Nombre</label>
</div>
</div>
<!-- Apellido -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="apellido" class="form-label">Apellido</label>
<input name="apellido" id="apellido" type="text" class="form-control"
value="<?= htmlspecialchars($chofer['apellido']) ?>" required>
<div class="form-floating-custom">
<input name="apellido" id="apellido" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['apellido']) ?>" required>
<label for="apellido" class="form-label">Apellido</label>
</div>
</div>
</div>
<div class="row">
<!-- Licencia -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="numero_licencia" class="form-label">Número de Licencia</label>
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control"
value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required>
<div class="form-floating-custom">
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required>
<label for="numero_licencia" class="form-label">Número de Licencia</label>
</div>
</div>
<!-- Gafete -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="numero_gafete" class="form-label">Número de Gafete</label>
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control"
value="<?= htmlspecialchars($chofer['numero_gafete']) ?>" required>
<div class="form-floating-custom">
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['numero_gafete']) ?>" required>
<label for="numero_gafete" class="form-label">Número de Gafete</label>
</div>
</div>
<!-- Teléfono -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="telefono" class="form-label">Teléfono</label>
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control"
value="<?= htmlspecialchars($chofer['telefono']) ?>">
<div class="form-floating-custom">
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['telefono']) ?>">
<label for="telefono" class="form-label">Teléfono</label>
</div>
</div>
</div>
<div class="row">
<!-- Email -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="email" class="form-label">Email</label>
<input name="email" id="email" type="email" class="form-control"
value="<?= htmlspecialchars($chofer['email']) ?>">
<div class="form-floating-custom">
<input name="email" id="email" type="email" class="form-control input-pulse input-typing"
value="<?= htmlspecialchars($chofer['email']) ?>">
<label for="email" class="form-label">Email</label>
</div>
</div>
<!-- Fecha de Ingreso -->
<div class="col-md-6 mb-3">
<div class="col-md-6 form-group-animated">
<label for="fecha_ingreso" class="form-label">Fecha de Ingreso</label>
<input name="fecha_ingreso" id="fecha_ingreso" type="date" class="form-control"
value="<?= htmlspecialchars($chofer['fecha_ingreso']) ?>">
<div class="form-floating-custom">
<input name="fecha_ingreso" id="fecha_ingreso" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['fecha_ingreso']) ?>">
<label for="fecha_ingreso" class="form-label">Fecha de Ingreso</label>
</div>
</div>
<!-- Cambiar foto -->
<div class="col-md-6 form-group-animated">
<label for="foto" class="form-label">Nueva Foto</label>
<div class="form-floating-custom">
<input name="foto" id="foto" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
<!-- Foto actual -->
<div class="mb-3 form-group-animated">
<label class="form-label">Foto Actual</label><br>
<?php if (!empty($chofer['foto_url'])): ?>
<img src="<?= htmlspecialchars($chofer['foto_url']) ?>" alt="Foto Chofer" width="100" class="mb-2">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
</div>
<!-- Activo -->
<div class="col-md-2 form-group-animated">
<div class="form-check">
<div class="form-floating-custom">
<input name="status" id="status" type="checkbox" class="form-check-input"
<?= $chofer['status'] == 1 ? 'checked' : '' ?>>
<label for="status" class="form-check-label">Activo</label>
</div>
</div>
</div>
</div>
<!-- Foto actual -->
<div class="mb-3">
<label class="form-label">Foto Actual</label><br>
<?php if (!empty($chofer['foto_url'])): ?>
<img src="<?= htmlspecialchars($chofer['foto_url']) ?>" alt="Foto Chofer" width="100" class="mb-2">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/choferes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
<!-- Cambiar foto -->
<div class="mb-3">
<label for="foto" class="form-label">Nueva Foto</label>
<input name="foto" id="foto" type="file" class="form-control" accept="image/*">
</div>
<!-- Activo -->
<div class="form-check mb-4">
<input name="status" id="status" type="checkbox" class="form-check-input"
<?= $chofer['status'] == 1 ? 'checked' : '' ?>>
<label for="status" class="form-check-label">Activo</label>
</div>
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/choferes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</form>
</div>
</div>

View File

@@ -64,6 +64,51 @@ if ($tipoUsuario === 'agente_aduanal') {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.hide { display: none !important; }
/* ========== 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); }
}
/* 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); } }
</style>
</head>
<body>
@@ -73,15 +118,15 @@ if ($tipoUsuario === 'agente_aduanal') {
<div class="row g-3">
<!-- Nuevo Estado -->
<div class="col-md">
<div class="col-md form-group-animated">
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto">
<form id="estadoForm">
<h4 class="mb-4 text-dark"> Nuevo Estado</h4><br>
<!-- País -->
<div class="col-md-12">
<div class="col-md-12 form-group-animated">
<label for="paisEstado" class="form-label">País *</label>
<select id="paisEstado" name="pais" class="form-select" required>
<option value="">Selecciona país</option>
<option value="">-- Selecciona un país --</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>">
<?= htmlspecialchars($p['nombre']) ?>
@@ -91,12 +136,12 @@ if ($tipoUsuario === 'agente_aduanal') {
</div><br><br><br>
<!-- Estado -->
<div class="col-md-12">
<div class="col-md-12 form-group-animated">
<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">
<div class="col-md-4 d-flex align-items-end form-group-animated">
<button type="submit" class="btn btn-success W-100 btn-animated">
<i class="fas fa-plus"></i> Registrar Estado
</button>
@@ -108,25 +153,25 @@ if ($tipoUsuario === 'agente_aduanal') {
</div>
<!-- Nueva Ciudad -->
<div class="col-md">
<div class="col-md form-group-animated">
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto">
<form id="ciudadForm" action="/IMPORTADORES/locaciones/guadarCiudad" method="POST" enctype="multipart/form-data">
<h4 class="mb-4 text-dark"> Nueva Ciudad</h4>
<!-- País -->
<div class="col-md-12">
<div class="col-md-12 form-group-animated">
<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>
<option value="">-- Selecciona un 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">
<div class="col-md-12 form-group-animated">
<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>
@@ -134,12 +179,12 @@ if ($tipoUsuario === 'agente_aduanal') {
</div><br>
<!-- Ciudad -->
<div class="col-md-12">
<div class="col-md-12 form-group-animated">
<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">
<div class="col-md-4 d-flex align-items-end form-group-animated">
<button type="submit" class="btn btn-success W-100 btn-animated">
<i class="fas fa-plus"></i> Registrar Ciudad
</button>
@@ -156,12 +201,12 @@ if ($tipoUsuario === 'agente_aduanal') {
// Función para cargar estados
function cargarEstados(paisId, selectElement) {
selectElement.innerHTML = '<option>Cargando...</option>';
selectElement.disabled = true;
selectElement.disabled = true;
fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`)
.then(response => response.json())
.then(estados => {
selectElement.innerHTML = '<option value="">Selecciona estado</option>';
selectElement.innerHTML = '<option value="">-- Selecciona un estado --</option>';
estados.forEach(estado => {
const option = new Option(estado.nombre, estado.id_estado);
selectElement.add(option);
@@ -176,14 +221,14 @@ if ($tipoUsuario === 'agente_aduanal') {
// 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 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;
estadoSelect.innerHTML = '<option value="">Primero selecciona un país...</option>';
estadoSelect.disabled = true;
}
});
@@ -192,28 +237,20 @@ if ($tipoUsuario === 'agente_aduanal') {
e.preventDefault();
// Obtener valores directamente
const paisSelect = document.getElementById('paisEstado');
const paisSelect = document.getElementById('paisEstado');
const entidadInput = document.getElementById('entidadEstado');
const paisValue = paisSelect.value;
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'
});
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'
});
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe ingresar el nombre del estado' });
return;
}
@@ -222,11 +259,11 @@ if ($tipoUsuario === 'agente_aduanal') {
formData.append('pais', paisValue);
formData.append('entidad', entidadValue);
const submitBtn = this.querySelector('button[type="submit"]');
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;
submitBtn.disabled = true;
// Debug: mostrar lo que se va a enviar
console.log('Enviando:', {
@@ -241,33 +278,19 @@ if ($tipoUsuario === 'agente_aduanal') {
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
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
});
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'
});
Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' });
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
submitBtn.disabled = false;
});
});
@@ -275,12 +298,12 @@ if ($tipoUsuario === 'agente_aduanal') {
document.getElementById('ciudadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitBtn = this.querySelector('button[type="submit"]');
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;
submitBtn.disabled = true;
fetch('/IMPORTADORES/locaciones/guardarCiudad', {
method: 'POST',
@@ -289,36 +312,60 @@ if ($tipoUsuario === 'agente_aduanal') {
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
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;
document.getElementById('estadoCiudad').disabled = true;
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: data.message
});
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'
});
Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' });
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
submitBtn.disabled = false;
});
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.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);
});
});
// Validación del formulario
document.getElementById('formAltaPatente').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>

View File

@@ -588,7 +588,7 @@ if ($tipoUsuario === 'agente_aduanal') {
.finally(() => {
// RESTAURAR BOTÓN
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
submitBtn.disabled = false;
});
});
</script>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>

View File

@@ -172,7 +172,7 @@ function obtenerUrlPermiso($permiso) {
default => '/IMPORTADORES/importadores/dashboard'
};
?>
<a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm" style="margin: 0 0 0 25px;">
<a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated" style="margin: 0 0 0 25px;">
← Panel Principal
</a>
</div>

View File

@@ -24,7 +24,7 @@
if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>

View File

@@ -89,6 +89,51 @@ if ($tipoUsuario === 'agente_aduanal') {
.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%; }
/* ========== 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); }
}
/* 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); } }
</style>
</head>
<body>
@@ -119,67 +164,67 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pestaña General -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<p style="font-weight: bold;">Datos de la Patente / Aduana</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="aduana" class="col-md-2 col-form-label">Aduana</label>
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<input name="aduana" id="aduana" type="text" maxlength="3" class="form-control" required>
</div>
<label for="aduana" class="col-md-2 col-form-label">[ Sin Sección ]</label>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="patente" class="col-md-2 col-form-label">Patente</label>
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<input name="patente" id="patente" type="text" maxlength="4" class="form-control" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label>
<div class="col-md-10">
<div class="col-md-10 form-group-animated">
<input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="rfc" class="col-md-2 col-form-label">RFC</label>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<input name="rfc" id="rfc" type="text" maxlength="13" class="form-control" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="curp" class="col-md-2 col-form-label">CURP</label>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<input name="curp" id="curp" type="text" maxlength="18" class="form-control" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="razon_social" class="col-md-2 col-form-label">Razón Social</label>
<div class="col-md-10">
<div class="col-md-10 form-group-animated">
<input name="razon_social" id="razon_social" type="text" class="form-control" required>
</div>
</div><hr>
<div class="col mb-3">
<div class="col mb-3 form-group-animated">
<P>Capturar para el llenado de la Manifestación de Valor</P>
<p style="font-weight: bold;">Datos del Agente Aduanal:</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
<div class="col-md-9">
<input name="mf_nombre" id="mf_nombre" type="text" class="form-control">
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label>
<div class="col-md-9">
<div class="col-md-9 form-group-animated">
<input name="mf_paterno" id="mf_paterno" type="text" class="form-control">
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label>
<div class="col-md-9">
<div class="col-md-9 form-group-animated">
<input name="mf_materno" id="mf_materno" type="text" class="form-control">
</div>
</div>
@@ -191,107 +236,107 @@ if ($tipoUsuario === 'agente_aduanal') {
<p style="font-weight: bold;">Números de Validación y Folios</p>
<!-- Pedimentos -->
<p>Números de Validación para Pedimentos</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vp_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div>
</div><hr>
<!-- Pago Electrónico -->
<p>Números de Validación para Pago Electrónico</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vpe_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div>
</div><hr>
<!-- Avisos Traslado / Plantas-Bodegas -->
<p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div>
</div><hr>
<!-- Validación y Folio de Cartas Cupo -->
<p>Números de Validación para Cartas Cupo</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vcc_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div>
</div><hr>
<!-- Avisos Electrónicos -->
<p>Números de Validación para Avisos Electrónicos</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vae_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control" value="000">
</div>
<div class="col-md-1"></div>
<label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div>
</div>
</div>
</div>
<div style="display: flex; justify-content: right;">
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Registrar</button>
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
@@ -421,6 +466,43 @@ if ($tipoUsuario === 'agente_aduanal') {
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
this.submit();
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.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);
});
});
// Validación del formulario
document.getElementById('formAltaPatente').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>
</body>

View File

@@ -86,6 +86,51 @@ if ($tipoUsuario === 'agente_aduanal') {
.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%; }
/* ========== 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); }
}
/* 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); } }
</style>
</head>
<body>
@@ -118,75 +163,75 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pestaña General -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<p style="font-weight: bold;">Datos de la Patente / Aduana</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="aduana" class="col-md-2 col-form-label">Aduana</label>
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<input name="aduana" id="aduana" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars($agente['aduana'] ?? '') ?>" required>
</div>
<label for="aduana" class="col-md-2 col-form-label">[Sin Sección]</label>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="patente" class="col-md-2 col-form-label">Patente</label>
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<input name="patente" id="patente" type="text" maxlength="4" class="form-control"
value="<?= htmlspecialchars($agente['patente'] ?? '') ?>" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label>
<div class="col-md-10">
<div class="col-md-10 form-group-animated">
<input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control"
value="<?= htmlspecialchars($agente['agente_aduanal'] ?? '') ?>" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="rfc" class="col-md-2 col-form-label">RFC</label>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<input name="rfc" id="rfc" type="text" maxlength="13" class="form-control"
value="<?= htmlspecialchars($agente['rfc'] ?? '') ?>" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="curp" class="col-md-2 col-form-label">CURP</label>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<input name="curp" id="curp" type="text" maxlength="18" class="form-control"
value="<?= htmlspecialchars($agente['curp'] ?? '') ?>" required>
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="razon_social" class="col-md-2 col-form-label">Razón Social</label>
<div class="col-md-10">
<div class="col-md-10 form-group-animated">
<input name="razon_social" id="razon_social" type="text" class="form-control"
value="<?= htmlspecialchars($agente['razon_social'] ?? '') ?>" required>
</div>
</div><hr>
<div class="col mb-3">
<div class="col mb-3 form-group-animated">
<P>Capturar para el llenado de la Manifestación de Valor</P>
<p style="font-weight: bold;">Datos del Agente Aduanal:</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
<div class="col-md-9">
<div class="col-md-9 form-group-animated">
<input name="mf_nombre" id="mf_nombre" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_nombre'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label>
<div class="col-md-9">
<div class="col-md-9 form-group-animated">
<input name="mf_paterno" id="mf_paterno" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_paterno'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label>
<div class="col-md-9">
<div class="col-md-9 form-group-animated">
<input name="mf_materno" id="mf_materno" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_materno'] ?? '') ?>">
</div>
@@ -199,22 +244,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<p style="font-weight: bold;">Números de Validación y Folios</p>
<!-- Pedimentos -->
<p>Números de Validación para Pedimentos</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_inicio'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vp_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_final'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_siguiente'] ?? '')) ?>">
</div>
@@ -222,22 +267,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pago Electrónico -->
<p>Números de Validación para Pago Electrónico</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_inicio'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vpe_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_final'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_siguiente'] ?? '')) ?>">
</div>
@@ -245,22 +290,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Avisos Traslado / Plantas-Bodegas -->
<p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_inicio'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_final'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_siguiente'] ?? '')) ?>">
</div>
@@ -268,22 +313,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Validación y Folio de Cartas Cupo -->
<p>Números de Validación para Cartas Cupo</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_inicio'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vcc_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_final'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_siguiente'] ?? '')) ?>">
</div>
@@ -291,22 +336,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Avisos Electrónicos -->
<p>Números de Validación para Avisos Electrónicos</p>
<div class="row mb-3">
<div class="row mb-3 form-group-animated">
<div class="col-md-1"></div>
<label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_inicio'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vae_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_final'] ?? '')) ?>">
</div>
<div class="col-md-1"></div>
<label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1">
<div class="col-md-1 form-group-animated">
<input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_siguiente'] ?? '')) ?>">
</div>
@@ -314,7 +359,7 @@ if ($tipoUsuario === 'agente_aduanal') {
</div>
</div>
<div style="display: flex; justify-content: right;">
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-success">💾 Guardar</button>
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>

View File

@@ -110,8 +110,8 @@ if ($tipoUsuario === 'agente_aduanal') {
<td><?= htmlspecialchars($aa['curp']) ?></td>
<td><?= htmlspecialchars($aa['razon_social']) ?></td>
<td>
<a href="/IMPORTADORES/patente/editar?id=<?= $aa['id_agente'] ?>" class="btn btn-sm btn-primary">✏️</a>
<button class="btn btn-sm btn-danger" onclick="confirmDeleteChofer(<?= $aa['id_agente'] ?>)">🗑️</button>
<a href="/IMPORTADORES/patente/editar?id=<?= $aa['id_agente'] ?>" class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated" onclick="confirmDeleteChofer(<?= $aa['id_agente'] ?>)">🗑️</button>
</td>
</tr>
<?php endforeach; ?>
@@ -147,13 +147,13 @@ if ($tipoUsuario === 'agente_aduanal') {
});
<?php if(isset($_GET['deleted'])): ?>
Swal.fire('¡Hecho!','Agente eliminado.','success');
Swal.fire('¡Hecho!','Patente eliminada.','success');
<?php endif; ?>
<?php if(isset($_GET['created'])): ?>
Swal.fire('¡Listo!','Agente creado.','success');
Swal.fire('¡Listo!','Patente creado.','success');
<?php endif; ?>
<?php if(isset($_GET['updated'])): ?>
Swal.fire('¡Listo!','Agente actualizado.','success');
Swal.fire('¡Listo!','Patente actualizada.','success');
<?php endif; ?>
</script>

View File

@@ -28,8 +28,11 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Choices.js CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<!-- SweetAlert2 -->
<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.1.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 -->
@@ -67,6 +70,51 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.form-label { font-weight: 500; }
/* ========== 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); }
}
/* 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); } }
</style>
</head>
<body>
@@ -87,23 +135,23 @@
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow"> Alta de Producto Frecuente</h4>
<div class="card p-4 shadow-sm bg-white card-hover position-relative h-auto">
<form id="altaProductoFrecuenteForm" action="/IMPORTADORES/productos_frecuentes/guardar" method="POST">
<div class="row g-3">
<div class="row g-3 form-group-animated">
<!-- Sinónimo, Fracción, NICO -->
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="sinonimo" class="form-label">Sinónimo *</label>
<input type="text" id="sinonimo" name="sinonimo" class="form-control" required>
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="fraccion" class="form-label">Fracción *</label>
<input type="text" id="fraccion" name="fraccion" class="form-control" required>
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="nico" class="form-label">NICO *</label>
<input type="text" id="nico" name="nico" class="form-control" required>
</div>
<!-- Número de Parte / Proveedor -->
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<label for="numero_parte" class="form-label">Número de Parte</label>
<input type="text" id="numero_parte" name="numero_parte" class="form-control">
</div>
@@ -115,7 +163,7 @@
</div>
<!-- Descripción -->
<div class="col-12">
<div class="col-12 form-group-animated">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"></textarea>
</div>
@@ -135,7 +183,7 @@
</select>
</div>
<!-- Uso de la Mercancía -->
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="uso_mercancia" class="form-label">Uso de la Mercancía</label>
<input type="text" id="uso_mercancia" name="uso_mercancia" class="form-control">
</div>
@@ -174,29 +222,28 @@
<!-- Criterio Preferencia / Tipo Mercancía -->
<div class="col-md-6">
<label for="criterio_preferencia" class="form-label">Criterio Preferencia</label>
<!-- Agrega `disabled` al select de criterio -->
<select id="criterio_preferencia" name="criterio_preferencia" class="form-select searchable" disabled>
<option value="">-- Selecciona --</option>
<?php foreach($criterios as $val => $lab): ?>
<option value="<?= $val ?>"><?= htmlspecialchars($lab) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="criterio_preferencia" class="form-label">Criterio Preferencia</label>
<!-- Agrega `disabled` al select de criterio -->
<select id="criterio_preferencia" name="criterio_preferencia" class="form-select searchable" disabled>
<option value="">-- Selecciona --</option>
<?php foreach($criterios as $val => $lab): ?>
<option value="<?= $val ?>"><?= htmlspecialchars($lab) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6 form-group-animated">
<label for="tipo_mercancia" class="form-label">Tipo de Mercancía</label>
<input type="text" id="tipo_mercancia" name="tipo_mercancia" class="form-control">
</div>
<!-- Checkboxes -->
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="certificado_origen" name="certificado_origen" value="1">
<label class="form-check-label" for="certificado_origen">Certificado de Origen</label>
</div>
</div>
<div class="col-md-3">
<div class="col-md-3 form-group-animated">
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="documento_en_original" name="documento_en_original" value="1">
<label class="form-check-label" for="documento_en_original">Documento en Original</label>
@@ -209,7 +256,7 @@
<input type="hidden" name="frecuencia_uso" value="1">
<!-- Botones -->
<div class="col-12 text-end mt-4">
<div class="col-12 text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Producto</button>
<a href="/IMPORTADORES/productos_frecuentes" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
@@ -218,10 +265,12 @@
</div>
</div>
<!-- JS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Choices.js JS -->
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<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/select2@4.1.0/dist/js/select2.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
function fetchJsonOrThrow(url) {
@@ -247,7 +296,7 @@
])
.then(([provData, umcData, paisData]) => {
// Proveedores
const provSel = document.getElementById('proveedor');
const provSel = document.getElementById('proveedor');
provSel.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
provData.results.forEach(item => {
const o = document.createElement('option');
@@ -255,7 +304,7 @@
provSel.appendChild(o);
});
// Unidades de Medida
const umcSel = document.getElementById('umc_id');
const umcSel = document.getElementById('umc_id');
umcSel.innerHTML = '<option value="">-- Selecciona UMC --</option>';
umcData.results.forEach(item => {
const o = document.createElement('option');
@@ -263,13 +312,14 @@
umcSel.appendChild(o);
});
// Países
const poSel = document.getElementById('pais_origen_destino');
const pcSel = document.getElementById('pais_comprador_vendedor');
const poSel = document.getElementById('pais_origen_destino');
const pcSel = document.getElementById('pais_comprador_vendedor');
poSel.innerHTML = pcSel.innerHTML = '<option value="">-- Selecciona País --</option>';
paisData.results.forEach(item => {
[poSel, pcSel].forEach(sel => {
const o = document.createElement('option');
o.value = item.id; o.textContent = item.text;
const o = document.createElement('option');
o.value = item.id;
o.textContent = item.text;
sel.appendChild(o);
});
});
@@ -284,25 +334,26 @@
});
});
// Después de inicializar Choices.js:
const prefEl = document.getElementById('preferencia');
const critEl = document.getElementById('criterio_preferencia');
const critChoice = critEl._choices;
// Después de inicializar Choices.js:
const prefEl = document.getElementById('preferencia');
const critEl = document.getElementById('criterio_preferencia');
const critChoice = critEl._choices;
function updateCriterio() {
if (prefEl.value === 'PROSEC') {
critEl.disabled = false;
critChoice.enable();
} else {
critChoice.removeActiveItems();
critChoice.disable();
critEl.disabled = true;
}
}
prefEl.addEventListener('change', updateCriterio);
updateCriterio();
if (critChoice) {
function updateCriterio() {
if (prefEl.value === 'PROSEC') {
critEl.disabled = false;
critChoice.enable();
} else {
critChoice.removeActiveItems();
critChoice.disable();
critEl.disabled = true;
}
}
prefEl.addEventListener('change', updateCriterio);
updateCriterio();
}
})
.catch(err => {
@@ -315,7 +366,67 @@ updateCriterio();
});
});
// 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');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>
</body>
</html>
</html>

View File

@@ -28,8 +28,8 @@
.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); }
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@@ -171,10 +171,10 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function() {
$(document).ready(function () {
$('#tablaProductosFrecuentes').DataTable({
language: {
url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});

View File

@@ -30,8 +30,8 @@
.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); }
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@@ -51,6 +51,51 @@
.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%; }
/* ========== 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); }
}
/* 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); } }
</style>
</head>
<body>
@@ -76,48 +121,48 @@
<div class="card p-4 shadow-sm card-hover position-relative h-auto">
<form action="/IMPORTADORES/proveedores/guardar" method="POST" enctype="multipart/form-data" id="formAltaProveedores">
<div class="row g-3">
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="Clave" class="form-label">Clave *</label>
<input type="text" name="Clave" id="Clave" class="form-control" maxlength="20" required>
</div>
<div class="col-md-8">
<div class="col-md-8 form-group-animated">
<label for="Nombre" class="form-label">Nombre *</label>
<input type="text" name="Nombre" id="Nombre" class="form-control" required>
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="RFC" class="form-label">RFC *</label>
<input type="text" name="RFC" id="RFC" class="form-control" maxlength="13" required>
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="Ciudad" class="form-label">Ciudad *</label>
<input type="text" name="Ciudad" id="Ciudad" class="form-control" required>
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="Telefono" class="form-label">Teléfono</label>
<input type="tel" name="Telefono" id="Telefono" maxlength="11" class="form-control">
</div>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<label for="Pais" class="form-label">País</label>
<input type="text" name="Pais" id="Pais" class="form-control">
</div>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<label for="EntidadFederativa" class="form-label">Entidad Federativa</label>
<input type="text" name="EntidadFederativa" id="EntidadFederativa" class="form-control">
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="Municipio" class="form-label">Municipio</label>
<input type="text" name="Municipio" id="Municipio" class="form-control">
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="Colonia" class="form-label">Colonia</label>
<input type="text" name="Colonia" id="Colonia" class="form-control">
</div>
<div class="col-md-4">
<div class="col-md-4 form-group-animated">
<label for="CodigoPostal" class="form-label">Código Postal</label>
<input type="text" name="CodigoPostal" id="CodigoPostal" maxlength="5" class="form-control">
</div>
</div>
<div class="mt-4 text-end">
<div class="mt-4 text-end form-group-animated">
<a href="/IMPORTADORES/proveedores" class="btn btn-secondary me-2 mt-auto w-auto btn-animated">Cancelar</a>
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Proveedor</button>
</div>
@@ -269,6 +314,44 @@
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
this.submit();
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.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);
});
});
// Validación del formulario
document.getElementById('formAltaProveedores').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>
</body>

View File

@@ -98,8 +98,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="col-md-6">
<div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Cambiar contraseña</h4>
<p>Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br><br>
<a href="/IMPORTADORES/reset/changePassword" class="btn btn-primary">Cambiar contraseña</a>
<p class="text-muted small flex-grow-1 mb-4">Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br>
<form>
<button type="button" class="btn btn-primary btn-sm mt-2 w-40 btn-animated" data-bs-toggle="modal" data-bs-target="#modalConfirmarCambio">
<i class="fas fa-key"></i>Cambiar contraseña
</button>
</form>
</div>
</div>

View File

@@ -179,8 +179,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="col-md-6">
<div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Cambiar contraseña</h4>
<p>Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br><br>
<a href="/IMPORTADORES/reset/changePassword" class="btn btn-primary">Cambiar contraseña</a>
<p class="text-muted small flex-grow-1 mb-4">Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br>
<form>
<button type="button" class="btn btn-primary btn-sm mt-2 w-40 btn-animated" data-bs-toggle="modal" data-bs-target="#modalConfirmarCambio">
<i class="fas fa-key"></i>Cambiar contraseña
</button>
</form>
</div>
</div>

View File

@@ -58,6 +58,80 @@
.btn-animated:hover::before { left: 100%; }
.hide { display: none !important; }
.alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); }
.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%; }
.form-label { font-weight: 500; }
/* ========== 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); }
}
/* 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); } }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
</head>
<body>
@@ -84,11 +158,11 @@
<!-- Sección principal -->
<div class="row">
<div class="col-md-3 mb-3">
<div class="col-md-3 mb-3 form-group-animated">
<label for="numero_factura" class="form-label">Número de Factura</label>
<input id="numero_factura" name="numero_factura" type="text" class="form-control" required>
</div>
<div class="col-md-3 mb-3">
<div class="col-md-3 mb-3 form-group-animated">
<label for="fecha_factura" class="form-label">Fecha de Factura</label>
<input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required>
</div>
@@ -156,7 +230,7 @@
<!-- Valor y vinculación -->
<div class="row">
<div class="col-md-8 mb-3">
<div class="col-md-8 mb-3 form-group-animated">
<label for="valor_factura" class="form-label">Valor Factura</label>
<input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control" required>
</div>
@@ -194,9 +268,9 @@
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 mb-3">
<div class="col-md-4 mb-3 form-group-animated">
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
@@ -221,9 +295,9 @@
</thead>
<tbody>
<tr>
<td><input name="partidas[0][descripcion]" class="form-control w-auto" required></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
<td><input name="partidas[0][descripcion]" class="form-control w-auto form-group-animated" required></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td>
<div class="mb-3">
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" required>
@@ -234,8 +308,8 @@
</select>
</div>
</td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto" min="0" required></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto form-group-animated" min="0" required></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td>
<div class="mb-3">
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" required>
@@ -248,24 +322,28 @@
</select>
</div>
</td>
<td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto"></td>
<td class="hide"><input name="partidas[0][oma_factura]" class="form-control w-auto"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
<td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto form-group-animated"></td>
<td class="hide"><input name="partidas[0][oma_factura]" class="form-control w-auto form-group-animated"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row mt-auto w-auto btn-animated">✖️</button></td>
</tr>
</tbody>
</table>
</div>
<button type="button" class="btn btn-secondary btn-sm mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
<div class="form-group-animated">
<button type="button" class="btn btn-secondary btn-sm mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
</div>
<!-- Activo -->
<div class="form-check mt-4">
<div class="form-check mt-4 form-group-animated">
<input id="status" name="status" type="checkbox" class="hide form-check-input" checked>
<label for="status" class="hide form-check-label">Activo</label>
</div>
<!-- Botones finales -->
<button type="submit" class="btn btn-success mt-3 mt-auto w-auto btn-animated">Guardar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary mt-3 ms-2 mt-auto w-auto btn-animated">Cancelar</a>
<div class="col-12 text-end mt-4 form-group-animated"></div>
<button type="submit" class="btn btn-success mt-3 mt-auto w-auto btn-animated">Guardar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary mt-3 ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form>
</div>
</div>
@@ -512,6 +590,67 @@
});
});
});
// 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');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>
</body>

View File

@@ -59,6 +59,80 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.hide { display: none !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%; }
.form-label { font-weight: 500; }
/* ========== 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); }
}
/* 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); } }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
</head>
<body>
@@ -71,12 +145,12 @@
<!-- Datos principales -->
<div class="row">
<div class="col-md-3 mb-3">
<div class="col-md-3 mb-3 form-group-animated">
<label for="numero_factura" class="form-label">Número de Factura</label>
<input id="numero_factura" name="numero_factura" type="text" class="form-control" required
value="<?= htmlspecialchars($factura['numero_factura']) ?>">
</div>
<div class="col-md-3 mb-3">
<div class="col-md-3 mb-3 form-group-animated">
<label for="fecha_factura" class="form-label">Fecha de Factura</label>
<input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required
value="<?= htmlspecialchars($factura['fecha_factura']) ?>">
@@ -150,7 +224,7 @@
<!-- Valor y Vinculación -->
<div class="row">
<div class="col-md-8 mb-3">
<div class="col-md-8 mb-3 form-group-animated">
<label for="valor_factura" class="form-label">Valor Factura</label>
<input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control valor-total" required
value="<?= htmlspecialchars($factura['valor_factura']) ?>">
@@ -193,7 +267,7 @@
</div>
<div class="col-md-4 mb-3">
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
@@ -219,9 +293,9 @@
<tbody>
<?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?>
<tr>
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
<td>
<select name="partidas[<?= $i ?>][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option>
@@ -232,8 +306,8 @@
<?php endforeach; ?>
</select>
</td>
<td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td>
<td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td>
<td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida form-group-animated" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td>
<td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td>
<td>
<select name="partidas[<?= $i ?>][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option>
@@ -242,15 +316,15 @@
<?php endforeach; ?>
</select>
</td>
<td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td>
<td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td>
<td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control form-group-animated" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td>
<td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
</tr>
<?php endforeach; else: ?>
<tr>
<td><input name="partidas[0][descripcion]" class="form-control"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control" min="0"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control" min="0"></td>
<td><input name="partidas[0][descripcion]" class="form-control form-group-animated"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td>
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option>
@@ -259,8 +333,8 @@
<?php endforeach; ?>
</select>
</td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida" min="0"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control" min="0"></td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida form-group-animated" min="0"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td>
<select name="partidas[0][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option>
@@ -276,16 +350,21 @@
</tbody>
</table>
</div>
<button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
<div class="form-group-animated">
<button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
</div>
<!-- Activo -->
<div class="form-check mb-4">
<div class="form-check mb-4 form-group-animated">
<input id="status" value=1 name="status" type="hidden" <?= $factura['status']==1?'checked':'' ?>>
<label for="status" class="hide form-check-label">Activo</label>
</div>
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
<!-- Botones finales -->
<div class="col-12 text-end mt-4 form-group-animated"></div>
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form>
</div>
</div>
@@ -637,6 +716,67 @@
}
});
});
// 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');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script>
</body>

View File

@@ -162,8 +162,8 @@
<!-- Celda de Acciones -->
<td>
<a href="/IMPORTADORES/solicitud_importacion/editar?id=<?= $f['id_solicitud'] ?>"
class="btn btn-sm btn-primary">✏️</a>
<button class="btn btn-sm btn-danger"
class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="confirmDelete(<?= $f['id_solicitud'] ?>)">🗑️</button>
</td>
</tr>
@@ -223,10 +223,10 @@
// Al cambiar el select de status
$('#tabla-solicitudes').on('change', '.status-select', function () {
const $sel = $(this);
const id = $sel.data('id');
const status = $sel.val();
const $row = $sel.closest('tr');
const $sel = $(this);
const id = $sel.data('id');
const status = $sel.val();
const $row = $sel.closest('tr');
const oldStatus = $sel.data('old-status') || $sel.find('option:first').val();
// Guardar el estado anterior para poder revertir si falla
@@ -238,8 +238,8 @@
// Mostrar toast de procesando
Swal.fire({
title: 'Actualizando...',
text: 'Procesando cambio de estado',
icon: 'info',
text: 'Procesando cambio de estado',
icon: 'info',
allowOutsideClick: false,
showConfirmButton: false,
timer: 1000,
@@ -273,8 +273,8 @@
if (res.numeroPedimentoLocal) {
Swal.fire({
title: '¡Importación solicitada!',
text: `Se generó el número de pedimento: ${res.numeroPedimentoLocal}`,
icon: 'success',
text: `Se generó el número de pedimento: ${res.numeroPedimentoLocal}`,
icon: 'success',
confirmButtonText: 'Entendido'
});
@@ -288,15 +288,15 @@
Swal.fire({
title: 'Estado actualizado',
text: res.warning,
icon: 'warning',
icon: 'warning',
confirmButtonText: 'Entendido'
});
} else {
// Éxito normal
Swal.fire({
title: '¡Actualizado!',
text: 'Estado cambiado correctamente',
icon: 'success',
text: 'Estado cambiado correctamente',
icon: 'success',
timer: 2000,
showConfirmButton: false
});
@@ -408,11 +408,11 @@
Swal.fire({
title: `¿Solicitar importación para ${selected.length} solicitudes?`,
text: 'Se actualizará el estatus a "Solicitar importación" (2)',
icon: 'warning',
text: 'Se actualizará el estatus a "Solicitar importación" (2)',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sí, proceder',
cancelButtonText: 'Cancelar'
cancelButtonText: 'Cancelar'
}).then(result => {
if (result.isConfirmed) {
$.ajax({
@@ -435,10 +435,6 @@
}
});
});
});
</script>

View File

@@ -66,115 +66,104 @@
.modal-dialog { z-index: 10000 !important; position: relative; }
/* Opcional: Mejorar la apariencia del overlay */
.modal-backdrop.show { opacity: 0.5; }
/* ========== 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; }
/* ========== 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); }
}
/* 4. Floating labels con animación */
/* 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; }
/* 5. Efecto de pulso en hover */
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.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); }
}
/* 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; }
.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); }
}
/* 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; }
.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; }
}
/* 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 */
.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(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 ========== */
/* 1. Estilos base para select */
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.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; }
/* 2. Animación al hacer focus */
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"); }
/* 3. Animación cuando tiene valor seleccionado */
.form-select:not([value=""]):not(:invalid) { 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"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-floating-custom .form-select:focus ~ .form-label,
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de shake en validación para select */
.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; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.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::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%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.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"); }
/* 10. Efecto de ondas para select */
.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; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.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; }
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; }
/* Quitar todo tipo de animación y borde para el campo foto */
.no-animation,
.no-border-style { transition: none !important; border: 1px solid #ccc !important; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
</head>
<body>
@@ -185,49 +174,51 @@
<div class="row g-3">
<div class="col-md-6 form-group-animated">
<label for="" class="form-label">Contenedor *</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center">
<input name="vehiculo" id="vehiculo" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required>
<label for="vehiculo" class="form-label">Contenedor *</label>
<label for="vehiculo" class="form-label">Contenedor</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button>
</div>
</div>
</div>
<div class="col-md-6 form-group-animated">
<label for="ident_fiscal" class="form-label">Identificación fiscal *</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center">
<input name="identificador_fiscal" id="ident_fiscal" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required>
<label for="ident_fiscal" class="form-label">Identificación fiscal *</label>
<label for="ident_fiscal" class="form-label">Identificación fiscal</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button>
</div>
</div>
</div>
<div class="col-md-6 form-group-animated">
<label class="form-label">Foto (opcional)</label>
<label for="" class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="transportista_id" id="transportista_id" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un transportista --</option>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>">
<?= htmlspecialchars($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="trasportista_id" class="form-label">Transportista</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<label for="foto" class="form-label">Foto (opcional)</label>
<div class="form-floating-custom">
<input type="file" name="foto" id="foto" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
<div class="col-md-6 form-group-animated">
<label for="" class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="id_transportista" id="transportista" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value=""></option>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>">
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
</option>
<?php endforeach; ?>
</select>
<label for="trasportista" class="form-label">Transportista</label>
</div>
</div>
</div>
<div class="text-end mt-4">
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-success ms-2 mt-auto w-auto btn-animated">Guardar</button>
<a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
@@ -270,10 +261,10 @@
document.getElementById('formTransCrear').addEventListener('submit', function(e) {
e.preventDefault();
const vehiculo = document.getElementById('vehiculo').value.trim();
const ident_fiscal = document.getElementById('ident_fiscal').value.trim();
const transportista = document.getElementById('transportista').value;
const fotoF = document.getElementById('foto').files[0];
const vehiculo = document.getElementById('vehiculo').value.trim();
const ident_fiscal = document.getElementById('ident_fiscal').value.trim();
const transportista_id = document.getElementById('transportista_id').value;
const fotoF = document.getElementById('foto').files[0];
if (!vehiculo) {
Swal.fire({ icon: 'warning', title: 'Contenedor requerido', text: 'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545' });
@@ -294,10 +285,12 @@
this.submit();
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('.form-control');
const inputs = document.querySelectorAll('input.form-control, select.form-select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
@@ -306,7 +299,8 @@
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
@@ -314,6 +308,68 @@
}, 500);
});
});
// Validación del formulario
document.getElementById('formTransCrear').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
});
</script>

View File

@@ -60,6 +60,104 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.preview{ width: 40%; }
/* ========== 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; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style>
</head>
<body>
@@ -69,54 +167,72 @@
<form id="formTransEdit" action="/IMPORTADORES/transportes/actualizar" method="POST" enctype="multipart/form-data" class="card p-4 shadow-sm card-hover position-relative h-auto">
<input type="hidden" name="id_transporte" value="<?= $t['id_transporte'] ?>">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Contenedor *</label>
<div class="d-flex align-items-center">
<input name="vehiculo" id="vehiculo" class="form-control" maxlength="20"
value="<?= htmlspecialchars($t['vehiculo']) ?>" required>
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button>
<div class="col-md-6 form-group-animated">
<label class="form-label">Contenedor</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center">
<input name="vehiculo" id="vehiculo" class="form-control input-pulse input-gradient" maxlength="20"
value="<?= htmlspecialchars($t['vehiculo']) ?>" required>
<label for="vehiculo" class="form-label">Contenedor</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Identificación fiscal *</label>
<div class="d-flex align-items-center">
<input name="identificador_fiscal" id="identificador_fiscal" class="form-control" maxlength="20"
value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required>
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button>
<div class="col-md-6 form-group-animated">
<label class="form-label">Identificación fiscal</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center">
<input name="identificador_fiscal" id="identificador_fiscal" class="form-control input-pulse input-gradient" maxlength="20"
value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required>
<label for="identificador_fiscal" class="form-label">Identificación fiscal</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<label class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="id_transportista" id="transportista" class="form-select" required>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>"
<?= $tr['id_transportista']==$t['id_transportista']?'selected':'' ?>>
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
</option>
<?php endforeach; ?>
</select>
<label for="transportista" class="form-label">Transportista</label>
</div>
</div>
<div class="col-md-6"></div>
<div class="col-md-6 form-group-animated">
<label class="form-label">Foto actual</label><br>
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" class="preview">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
<div class="form-floating-custom">
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" class="preview">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
</div>
</div>
<div class="col-md-6">
<div class="col-md-6 form-group-animated">
<label class="form-label">Reemplazar foto (opcional)</label>
<input type="file" name="foto" id="foto" class="form-control" accept="image/*">
</div>
<div class="col-md-6">
<label class="form-label">Transportista *</label>
<select name="id_transportista" id="transportista" class="form-select" required>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>"
<?= $tr['id_transportista']==$t['id_transportista']?'selected':'' ?>>
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-floating-custom">
<input type="file" name="foto" id="foto" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
</div>
<div class="text-end mt-4">
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form>
</div>
<!-- Modales movidos FUERA del formulario -->
<!-- Modal de ayuda - Contenedor -->
<div class="modal fade" id="infoContenedor" tabindex="-1" aria-labelledby="infoContenedorLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
@@ -154,10 +270,10 @@
e.preventDefault(); // Prevenir envío por defecto
// Campos que validamos:
const veh = document.getElementById('vehiculo').value.trim();
const fisc = document.getElementById('identificador_fiscal').value.trim();
const trans = document.getElementById('transportista').value;
const fotoF = document.getElementById('foto').files[0];
const veh = document.getElementById('vehiculo').value.trim();
const fisc = document.getElementById('identificador_fiscal').value.trim();
const trans = document.getElementById('transportista').value;
const fotoF = document.getElementById('foto').files[0];
// Validaciones de campos obligatorios
if (!veh) {
@@ -176,11 +292,98 @@
return;
}
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.' confirmButtonColor: '#dc3545' });
return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.', confirmButtonColor: '#dc3545' });
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, 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);
});
});
// Validación del formulario
document.getElementById('formTransEdit').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
});
</script>
</body>

View File

@@ -67,40 +67,44 @@
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚛 Mis Transportes</h4>
<a href="/IMPORTADORES/transportes/crear" class="btn btn-success mb-3"> Nuevo Transporte</a>
<a href="/IMPORTADORES/transportes/crear" class="btn btn-success mb-3 mt-auto w-auto btn-animated"> Nuevo Transporte</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-transportes">
<thead>
<tr>
<th>#</th><th>Contenedor</th><th>Ident. Fiscal</th>
<th>Foto</th><th>Transportista</th>
<th>Alta</th><th>Acciones</th>
<th>#</th>
<th>Contenedor</th>
<th>Identifiación Fiscal</th>
<th>Foto</th>
<th>Transportista</th>
<th>Alta</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach($transportes as $t): ?>
<tr>
<td><?= $t['id_transporte'] ?></td>
<td><?= htmlspecialchars($t['vehiculo']) ?></td>
<td><?= htmlspecialchars($t['identificador_fiscal']) ?></td>
<td>
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" width="50">
<?php endif; ?>
</td>
<td><?= htmlspecialchars($t['transportista']) ?></td>
<td><?= $t['creado_en']->format('Y-m-d') ?></td>
<td>
<a href="/IMPORTADORES/transportes/editar?id=<?= $t['id_transporte'] ?>"
class="btn btn-sm btn-primary">✏️</a>
<button class="btn btn-sm btn-danger"
onclick="confirmDelete(<?= $t['id_transporte'] ?>)">
🗑️
</button>
</td>
</tr>
<?php endforeach; ?>
<?php foreach($transportes as $t): ?>
<tr>
<td><?= $t['id_transporte'] ?></td>
<td><?= htmlspecialchars($t['vehiculo']) ?></td>
<td><?= htmlspecialchars($t['identificador_fiscal']) ?></td>
<td>
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" width="50">
<?php endif; ?>
</td>
<td><?= htmlspecialchars($t['transportista']) ?></td>
<td><?= $t['creado_en']->format('Y-m-d') ?></td>
<td>
<a href="/IMPORTADORES/transportes/editar?id=<?= $t['id_transporte'] ?>"
class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="confirmDelete(<?= $t['id_transporte'] ?>)">
🗑️
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
@@ -132,8 +136,14 @@
});
});
<?php if(isset($_GET['deleted'])): ?>
Swal.fire('¡Hecho!','Transporte eliminado.','success');
<?php if (isset($_GET['created']) && $_GET['created']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transporte creado', text: 'El transporte se creo correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transporte eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['updated']) && $_GET['updated'] === 'ok'): ?>
Swal.fire({ icon: 'success', title: 'Transporte actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>

View File

@@ -51,110 +51,98 @@
.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%; }
/* ========== 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; }
/* ========== 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); }
}
/* 4. Floating labels con animación */
/* 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; }
/* 5. Efecto de pulso en hover */
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.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); }
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; }
.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); }
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; }
.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; }
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 */
.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(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 ========== */
/* 1. Estilos base para select */
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.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; }
/* 2. Animación al hacer focus */
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"); }
/* 3. Animación cuando tiene valor seleccionado */
.form-select:not([value=""]):not(:invalid) { 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"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-floating-custom .form-select:focus ~ .form-label,
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de shake en validación para select */
.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; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.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::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%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.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"); }
/* 10. Efecto de ondas para select */
.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; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.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; }
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>
@@ -212,7 +200,7 @@
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="caat" id="caat" type="text" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required>
<label for="caat" class="form-label">Código caat *</label>
@@ -222,7 +210,7 @@
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<select id="pais" name="pais" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value=""></option>
<option value="">-- Selecciona un País --</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"><?= htmlspecialchars($p['nombre']) ?></option>
<?php endforeach; ?>
@@ -265,168 +253,14 @@
</div>
<script>
document.getElementById('formAltaTransportista').addEventListener('submit', function(e) {
e.preventDefault();
const rfcInput = document.getElementById('rfc');
const curpInput = document.getElementById('curp');
const telefonoInput = document.getElementById('telefono');
const rfc = rfcInput.value.trim().toUpperCase();
const curp = curpInput.value.trim();
const telefono = telefonoInput.value.trim();
const clave = document.getElementById('clave').value.trim();
const nombre = document.getElementById('nombre').value.trim();
const caat = document.getElementById('caat').value.trim();
const pais = document.getElementById('pais').value;
const entidad = document.getElementById('entidad').value;
const ciudad = document.getElementById('ciudad').value;
const domicilio = document.getElementById('domicilio').value.trim();
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
const telefonoRegex = /^\d{3}\s\d{7}$/;
if (!clave) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La clave es obligatoria.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();
return;
}
if (clave.length>8) {
Swal.fire({ icon: 'error', title: 'Clave inválida', text: 'La clave no puede tener más de 8 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();
return;
}
if (!nombre) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('nombre').focus();
return;
}
if (!rfc) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('rfc').focus();
return;
}
if (!rfcRegex.test(rfc)) {
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').focus();
return;
}
if (curp && !curpRegex.test(curp)) {
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'});
document.getElementById('curp').focus();
return;
}
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).', onfirmButtonColor:'#dc3545'});
document.getElementById('telefono').focus();
return;
}
if (telefono.length>11) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'El teléfono no puede tener más de 11 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('telefono').focus();
return;
}
if (!caat) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('caat').focus();
return;
}
if (!pais) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país.', confirmButtonColor: '#dc3545'});
document.getElementById('pais').focus();
return;
}
if (!entidad) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un estado.', confirmButtonColor: '#dc3545'});
document.getElementById('entidad').focus();
return;
}
if (!ciudad) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar una ciudad.', confirmButtonColor: '#dc3545'});
document.getElementById('ciudad').focus();
return;
}
if (!domicilio) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El domicilio es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('domicilio').focus();
return;
}
document.getElementById('rfc').value = rfc;
document.getElementById('curp').value = curp;
this.submit();
});
document.getElementById('pais').addEventListener('change', e => {
const pid = e.target.value;
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
estadoSelect.innerHTML = '<option value="">Cargando…</option>';
estadoSelect.disabled = true;
ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
ciudadSelect.disabled = true;
if (!pid) { estadoSelect.innerHTML = '<option value="">Primero país…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar estados'); return r.json(); })
.then(list => {
estadoSelect.innerHTML = '<option value=""></option>';
list.forEach(st => estadoSelect.add(new Option(st.nombre, st.id_estado)));
estadoSelect.disabled = false;
})
.catch(() => {
estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error',title:'Error',text:'No se pudieron cargar los estados',confirmButtonColor:'#dc3545'});
});
});
document.getElementById('entidad').addEventListener('change', e => {
const eid = e.target.value;
const ciudadSelect = document.getElementById('ciudad');
ciudadSelect.innerHTML = '<option value="">Cargando…</option>';
ciudadSelect.disabled = true;
if (!eid) { ciudadSelect.innerHTML = '<option value="">Primero estado…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar ciudades'); return r.json(); })
.then(list => {
ciudadSelect.innerHTML = '<option value=""></option>';
list.forEach(ct => ciudadSelect.add(new Option(ct.nombre, ct.id_ciudad)));
ciudadSelect.disabled = false;
})
.catch(() => {
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error',title:'Error',text:'No se pudieron cargar las ciudades',confirmButtonColor:'#dc3545'});
});
});
// Switch Persona Moral
document.getElementById('personaMoralSwitch').addEventListener('change', function() {
const checked = this.checked;
const label = document.getElementById('labelNombre');
const curp = document.getElementById('curp');
const hidden = document.getElementById('es_persona_moral');
if (checked) {
label.textContent = 'Razón Social *';
curp.value = '';
curp.disabled = true;
curp.removeAttribute('required');
hidden.value = '1';
} else {
label.textContent = 'Nombre *';
curp.disabled = false;
curp.setAttribute('required', 'required');
hidden.value = '0';
}
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
// Referencias a elementos del DOM
const form = document.getElementById('formAltaTransportista');
const inputs = document.querySelectorAll('input.form-control, select.form-select');
const selects = document.querySelectorAll('.form-select');
// Configurar validación en tiempo real para inputs
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
@@ -436,7 +270,6 @@
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
@@ -444,32 +277,211 @@
}, 500);
});
});
// Validación del formulario
document.getElementById('formAltaAgencias').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (isValid) {
// Aquí puedes agregar tu lógica de envío
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: 'Agencia registrada correctamente',
showConfirmButton: false,
timer: 1500
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');
}
}
}
// Event listener para el formulario
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
const rfc = document.getElementById('rfc').value.trim().toUpperCase();
const curp = document.getElementById('curp').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const clave = document.getElementById('clave').value.trim();
const nombre = document.getElementById('nombre').value.trim();
const caat = document.getElementById('caat').value.trim();
const pais = document.getElementById('pais').value;
const entidad = document.getElementById('entidad').value;
const ciudad = document.getElementById('ciudad').value;
const domicilio = document.getElementById('domicilio').value.trim();
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
const telefonoRegex = /^\d{3}\s\d{7}$/;
if (!clave) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La clave es obligatoria.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();
return;
}
if (clave.length>8) {
Swal.fire({ icon: 'error', title: 'Clave inválida', text: 'La clave no puede tener más de 8 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();
return;
}
if (!nombre) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('nombre').focus();
return;
}
if (!rfc) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('rfc').focus();
return;
}
if (!rfcRegex.test(rfc)) {
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').focus();
return;
}
if (curp && !curpRegex.test(curp)) {
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'});
document.getElementById('curp').focus();
return;
}
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;
}
if (telefono.length>11) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'El teléfono no puede tener más de 11 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('telefono').focus();
return;
}
if (!caat) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('caat').focus();
return;
}
if (!pais) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país.', confirmButtonColor: '#dc3545'});
document.getElementById('pais').focus();
return;
}
if (!entidad) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un estado.', confirmButtonColor: '#dc3545'});
document.getElementById('entidad').focus();
return;
}
if (!ciudad) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar una ciudad.', confirmButtonColor: '#dc3545'});
document.getElementById('ciudad').focus();
return;
}
if (!domicilio) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El domicilio es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('domicilio').focus();
return;
}
// Actualizar valores procesados
const rfcElement = document.getElementById('rfc');
const curpElement = document.getElementById('curp');
if (rfcElement) rfcElement.value = rfc;
if (curpElement) curpElement.value = curp;
// Si todas las validaciones son correctas: Enviar el formulario
this.submit();
// Validar campos
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
})
}
document.getElementById('pais').addEventListener('change', e => {
const pid = e.target.value;
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
estadoSelect.innerHTML = '<option value="">Cargando…</option>';
estadoSelect.disabled = true;
ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
ciudadSelect.disabled = true;
if (!pid) { estadoSelect.innerHTML = '<option value="">Primero país…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar estados'); return r.json(); })
.then(list => {
estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
list.forEach(st => estadoSelect.add(new Option(st.nombre, st.id_estado)));
estadoSelect.disabled = false;
})
.catch(() => {
estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error', title:'Error', text:'No se pudieron cargar los estados', confirmButtonColor:'#dc3545'});
});
});
document.getElementById('entidad').addEventListener('change', e => {
const eid = e.target.value;
const ciudadSelect = document.getElementById('ciudad');
ciudadSelect.innerHTML = '<option value="">Cargando…</option>';
ciudadSelect.disabled = true;
if (!eid) { ciudadSelect.innerHTML = '<option value="">Primero estado…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar ciudades'); return r.json(); })
.then(list => {
ciudadSelect.innerHTML = '<option value="">-- Seleciona una ciudad --</option>';
list.forEach(ct => ciudadSelect.add(new Option(ct.nombre, ct.id_ciudad)));
ciudadSelect.disabled = false;
})
.catch(() => {
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error', title:'Error', text:'No se pudieron cargar las ciudades', confirmButtonColor:'#dc3545'});
});
});
// Switch Persona Moral
document.getElementById('personaMoralSwitch')?.addEventListener('change', function() {
const checked = this.checked;
const label = document.getElementById('labelNombre');
const curp = document.getElementById('curp');
const hidden = document.getElementById('es_persona_moral');
if (label) {
label.textContent = checked ? 'Razón Social *' : 'Nombre *';
}
if (curp && hidden) {
if (checked) {
curp.value = '';
curp.disabled = true;
curp.removeAttribute('required');
hidden.value = '1';
} else {
curp.disabled = false;
curp.setAttribute('required', 'required');
hidden.value = '0';
}
}
});
});
</script>
</body>
</html>
</html>

View File

@@ -58,6 +58,99 @@
.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%; }
/* ========== 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>
@@ -67,80 +160,108 @@
<form action="/IMPORTADORES/transportistas/actualizar" method="POST" class="card p-4 shadow-sm card-hover position-relative h-auto" id="formEditTransportista">
<input type="hidden" name="id_transportista" value="<?= $t['id_transportista'] ?>">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Clave</label>
<input name="clave" value="<?= htmlspecialchars($t['clave_identificador']) ?>"
id="clave" class="form-control" maxlength="8" required>
</div>
<div class="col-md-4">
<label class="form-label">Nombre</label>
<input name="nombre" value="<?= htmlspecialchars($t['nombre']) ?>"
id="nombre" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">RFC</label>
<input name="rfc" value="<?= htmlspecialchars($t['rfc']) ?>"
id="rfc" class="form-control" maxlength="13" required>
</div>
<div class="col-md-4">
<label class="form-label">CURP</label>
<input name="curp" value="<?= htmlspecialchars($t['curp']) ?>"
id="curp" class="form-control" maxlength="18">
</div>
<div class="col-md-4">
<label class="form-label">Teléfono</label>
<input name="telefono" value="<?= htmlspecialchars($t['telefono']) ?>"
id="telefono" class="form-control" maxlength="11" required>
</div>
<div class="col-md-4">
<label class="form-label">Código caat</label>
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
id="caat" class="form-control" maxlength="20" required>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="clave" value="<?= htmlspecialchars($t['clave_identificador']) ?>"
id="clave" class="form-control input-pulse input-gradient" maxlength="8" required>
<label for="clave" class="form-label">Clave</label>
</div>
</div>
<div class="col-md-4">
<label class="form-label">País</label>
<select id="pais" name="pais" class="form-select" required>
<option value="">Selecciona país</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"
<?= $p['id_pais']==$t['pais']?'selected':'' ?>>
<?= htmlspecialchars($p['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Estado</label>
<select id="entidad" name="entidad" class="form-select" required>
<option value="">Selecciona estado</option>
<?php foreach($estados as $e): ?>
<option value="<?= $e['id_estado'] ?>"
<?= $e['id_estado']==$t['entidad_federativa']?'selected':'' ?>>
<?= htmlspecialchars($e['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Ciudad</label>
<select id="ciudad" name="ciudad" class="form-select" required>
<option value="">Selecciona ciudad</option>
<?php foreach($ciudades as $c): ?>
<option value="<?= $c['id_ciudad'] ?>"
<?= $c['id_ciudad']==$t['ciudad']?'selected':'' ?>>
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="nombre" value="<?= htmlspecialchars($t['nombre']) ?>"
id="nombre" class="form-control input-pulse input-gradient" required>
<label for="nombre" class="form-label">Nombre</label>
</div>
</div>
<div class="col-md-12">
<label class="form-label">Domicilio Completo</label>
<textarea name="domicilio" id="domicilio" class="form-control" rows="3" required><?= htmlspecialchars($t['domicilio']) ?></textarea>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="rfc" value="<?= htmlspecialchars($t['rfc']) ?>"
id="rfc" class="form-control input-pulse input-gradient" maxlength="13" required>
<label for="rfc" class="form-label">RFC</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="curp" value="<?= htmlspecialchars($t['curp']) ?>"
id="curp" class="form-control input-pulse input-gradient" maxlength="18">
<label for="curp" class="form-label">CURP</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="telefono" value="<?= htmlspecialchars($t['telefono']) ?>"
id="telefono" class="form-control input-pulse input-gradient" maxlength="11" required>
<label for="telefono" class="form-label">Teléfono</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
id="caat" class="form-control input-pulse input-gradient" maxlength="20" required>
<label for="caat" class="form-label">Código caat</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<select id="pais" name="pais" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un País --</option>
<?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"
<?= $p['id_pais']==$t['pais']?'selected':'' ?>>
<?= htmlspecialchars($p['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="pais" class="form-label">País</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<select id="entidad" name="entidad" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un Estado --</option>
<?php foreach($estados as $e): ?>
<option value="<?= $e['id_estado'] ?>"
<?= $e['id_estado']==$t['entidad_federativa']?'selected':'' ?>>
<?= htmlspecialchars($e['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="entidad" class="form-label">Entidad</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<select id="ciudad" name="ciudad" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona una Ciudad --</option>
<?php foreach($ciudades as $c): ?>
<option value="<?= $c['id_ciudad'] ?>"
<?= $c['id_ciudad']==$t['ciudad']?'selected':'' ?>>
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="ciudad" class="form-label">Ciudad</label>
</div>
</div>
<div class="col-md-12 form-group-animated">
<div class="form-floating-custom">
<textarea name="domicilio" id="domicilio" class="form-control input-pulse input-gradient" rows="1" required><?= htmlspecialchars($t['domicilio']) ?></textarea>
<label for="domicilio" class="form-label">Domicilio Completo</label>
</div>
</div>
</div>
<div class="text-end mt-4">
<div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">💾 Guardar Cambios</button>
<a href="/IMPORTADORES/transportistas/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
@@ -150,14 +271,10 @@
<script>
document.getElementById('formEditTransportista').addEventListener('submit', function(e) {
e.preventDefault(); // Prevenir envío por defecto
const rfcInput = document.getElementById('rfc');
const curpInput = document.getElementById('curp');
const telefonoInput = document.getElementById('telefono');
const rfc = rfcInput.value.trim().toUpperCase();
const curp = curpInput.value.trim();
const telefono = telefonoInput.value.trim();
const rfc = document.getElementById('rfc').value.trim().toUpperCase();
const curp = document.getElementById('curp').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const clave = document.getElementById('clave').value.trim();
const nombre = document.getElementById('nombre').value.trim();
const caat = document.getElementById('caat').value.trim();
@@ -245,25 +362,33 @@
}
// Si llegamos aquí, todas las validaciones pasaron
// Actualizar los valores en los campos antes de enviar
document.getElementById('rfc').value = rfc;
document.getElementById('rfc').value = rfc;
document.getElementById('curp').value = curp;
// Enviar el formulario
// Si todas las validaciones son correctas: Enviar el formulario
this.submit();
// Validar campos
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
// Funcionalidad de selects en cascada
// 1) Cuando elige país → cargo estados
document.getElementById('pais').addEventListener('change', e => {
const pid = e.target.value;
const pid = e.target.value;
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
// Resetear selects dependientes
estadoSelect.innerHTML = '<option value="">Cargando…</option>';
estadoSelect.disabled = true;
estadoSelect.disabled = true;
ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
ciudadSelect.disabled = true;
ciudadSelect.disabled = true;
if (!pid) {
estadoSelect.innerHTML = '<option value="">Primero país…</option>';
@@ -277,7 +402,7 @@
return r.json();
})
.then(list => {
estadoSelect.innerHTML = '<option value="">Selecciona estado</option>';
estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
list.forEach(st => {
const option = new Option(st.nombre, st.id_estado);
estadoSelect.add(option);
@@ -287,22 +412,17 @@
.catch(error => {
console.error('Error:', error);
estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({
icon: 'error',
title: 'Error',
text: 'No se pudieron cargar los estados',
confirmButtonColor: '#dc3545'
});
Swal.fire({ icon: 'error', title: 'Error', text: 'No se pudieron cargar los estados', confirmButtonColor: '#dc3545' });
});
});
// 2) Cuando elige estado → cargo ciudades
document.getElementById('entidad').addEventListener('change', e => {
const eid = e.target.value;
const eid = e.target.value;
const ciudadSelect = document.getElementById('ciudad');
ciudadSelect.innerHTML = '<option value="">Cargando…</option>';
ciudadSelect.disabled = true;
ciudadSelect.disabled = true;
if (!eid) {
ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
@@ -316,7 +436,7 @@
return r.json();
})
.then(list => {
ciudadSelect.innerHTML = '<option value="">Selecciona ciudad</option>';
ciudadSelect.innerHTML = '<option value="">-- Selecciona una Ciudad --</option>';
list.forEach(ct => {
const option = new Option(ct.nombre, ct.id_ciudad);
ciudadSelect.add(option);
@@ -326,14 +446,187 @@
.catch(error => {
console.error('Error:', error);
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({
icon: 'error',
title: 'Error',
text: 'No se pudieron cargar las ciudades',
confirmButtonColor: '#dc3545'
});
Swal.fire({ icon: 'error', title: 'Error', text: 'No se pudieron cargar las ciudades', confirmButtonColor: '#dc3545' });
});
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, 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);
});
});
// Validación del formulario
document.getElementById('formEditTransportista').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
});
document.addEventListener('DOMContentLoaded', function () {
const paisSelect = document.getElementById('pais');
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
if (!paisSelect || !estadoSelect || !ciudadSelect) return;
// ✅ Remueve clases de animación/validación
function cleanVisualState(select) {
select.classList.remove('valid', 'invalid', 'shake', 'loading');
}
// ✅ Reset de select con mensaje personalizado
function resetSelect(select, message, disabled = true) {
if (!select) return;
select.innerHTML = `<option value="">${message}</option>`;
select.disabled = disabled;
cleanVisualState(select);
updateSelectState(select);
}
// ✅ Actualiza el estado visual según el valor
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value) {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
}
}
// ✅ Listener para cambiar país
paisSelect.addEventListener('change', function () {
const pid = this.value;
resetSelect(estadoSelect, pid ? 'Cargando estados…' : 'Primero selecciona un país…');
resetSelect(ciudadSelect, 'Primero selecciona un estado…');
if (!pid) return;
// Lógica real de carga
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => r.ok ? r.json() : Promise.reject('Error al cargar'))
.then(data => {
estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
data.forEach(e => {
const o = new Option(e.nombre, e.id_estado);
estadoSelect.add(o);
});
estadoSelect.disabled = false;
updateSelectState(estadoSelect);
})
.catch(err => {
console.error(err);
resetSelect(estadoSelect, 'Error al cargar estados');
});
});
// ✅ Listener para cambiar estado
estadoSelect.addEventListener('change', function () {
const eid = this.value;
resetSelect(ciudadSelect, eid ? 'Cargando ciudades…' : 'Primero selecciona un estado…');
if (!eid) return;
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => r.ok ? r.json() : Promise.reject('Error al cargar'))
.then(data => {
ciudadSelect.innerHTML = '<option value="">-- Selecciona una Ciudad --</option>';
data.forEach(c => {
const o = new Option(c.nombre, c.id_ciudad);
ciudadSelect.add(o);
});
ciudadSelect.disabled = false;
updateSelectState(ciudadSelect);
})
.catch(err => {
console.error(err);
resetSelect(ciudadSelect, 'Error al cargar ciudades');
});
});
// ✅ Actualizar ciudad visual
ciudadSelect.addEventListener('change', function () {
updateSelectState(this);
});
// ✅ Aplicar estado visual inicial
[paisSelect, estadoSelect, ciudadSelect].forEach(select => {
updateSelectState(select);
});
});
</script>
</body>

View File

@@ -64,8 +64,8 @@
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚚 Mis Transportistas</h4>
<a href="/IMPORTADORES/transportistas/alta" class="btn btn-success mb-3 card-hover position-relative h-auto mt-auto w-auto btn-animated"> Nuevo Transportista</a>
<div class="card p-3 shadow-sm">
<a href="/IMPORTADORES/transportistas/alta" class="btn btn-success mb-3 mt-auto w-auto btn-animated"> Nuevo Transportista</a>
<div class="card p-3 shadow-sm card-hover position-relative h-auto">
<div class="table-responsive">
<table id="transportistas-table" class="display nowrap" style="width:100%">
<thead>
@@ -145,6 +145,9 @@
});
}
<?php if (isset($_GET['created']) && $_GET['created']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transportista creado', text: 'El transportista se creo correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
// Mostrar alert de “borrado” al volver de la acción
<?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transportista eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' });