Files
MVE/views/admin/alta_usuarios.php
2025-07-22 10:44:40 -06:00

377 lines
21 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>👥 Alta de Usuarios</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
table.dataTable thead th { background:#343a40; color:#fff; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h3 class="mb-4 animate__animated animate__fadeInDown title_glow">👥 Alta de Usuarios</h3>
<!-- FORMULARIO -->
<div class="card p-4 mb-4 shadow-sm bg-white card-hover position-relative h-auto fade-in-up">
<form action="/IMPORTADORES/administrador/guardar_usuario" method="POST" id="formAltaUsuarios">
<h5 class="mb-3"> Nuevo Usuario</h5>
<div class="row g-3">
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="nombre" type="text" class="form-control input-pulse input-gradient" id="nombre" placeholder=" " required>
<label for="nombre" class="form-label">Nombre *</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="email" type="email" class="form-control input-pulse input-typing" id="email" placeholder=" " required>
<label for="email" class="form-label">Correo *</label>
</div>
</div>
<div class="col-md-2 form-group-animated">
<div class="form-floating-custom">
<input name="password" type="password" class="form-control input-pulse input-ripple" id="password" placeholder=" " required>
<label for="password" class="form-label">Contraseña *</label>
</div>
</div>
<div class="col-md-2 form-group-animated">
<div class="form-floating-custom">
<select name="tipo_usuario" id="tipo" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Tipo de Usuario --</option>
<option value="super_admin">Administrador General</option>
<option value="importador">Importador</option>
</select>
<label for="tipo" class="form-label">Tipo de Usuario</label>
</div>
</div>
</div>
<div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button>
</div>
</form>
</div>
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">✅ Usuarios Activos</h4>
<div class="card p-3 shadow-sm bg-white card-hover position-relative h-auto">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-usuarios-activos">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Nombre</th>
<th>Correo</th>
<th>Tipo</th>
<th>Creado por</th>
<th>Fecha Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($usuarios as $u): ?>
<tr>
<td><?= $u['id_usuario'] ?></td>
<td><?= htmlspecialchars(decrypt($u['nombre'] ?? '')) ?></td>
<td><?= htmlspecialchars(decrypt($u['email'] ?? '')) ?></td>
<td><?= htmlspecialchars($u['tipo_usuario'] ?? '') ?></td>
<td><?= htmlspecialchars(decrypt($u['nombre_creador'] ?? '')) ?></td>
<td><?= isset($u['creado_en']) && $u['creado_en'] instanceof DateTime ? $u['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (isset($u['activo']) && $u['activo'] == 1): ?>
<a href="#" onclick="confirmSuspencion(<?= (int) ($u['id_usuario'] ?? 0) ?>)" class="btn btn-sm btn-danger mt-auto w-auto btn-animated">Suspender</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-usuarios-activos').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
const selects = document.querySelectorAll('select.form_select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Validación del formulario
document.getElementById('formAltaUsuarios').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const inputs = document.querySelectorAll('input.form-control, select.form-select');
// Validar todos los campos
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
setTimeout(() => input.classList.remove('shake'), 500);
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
this.submit(); // Esta línea faltaba en tu código original
} else {
// Mostrar mensaje de error si lo deseas
Swal.fire({ title: 'Campos incompletos', text: 'Por favor complete todos los campos requeridos', icon: 'error' });
}
});
});
function confirmSuspencion(id) {
Swal.fire({
title: '¿Estás seguro?',
text: 'Esta acción no se puede deshacer.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sí, suspender',
cancelButtonText: 'Cancelar'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = `/IMPORTADORES/administrador/suspender?id=${id}`;
}
});
}
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Usuario creado',
text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'user_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_data'): ?>
Swal.fire({ icon: 'error', title: 'Datos inválidos', text: 'Por favor, completa todos los campos requeridos correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_user_type'): ?>
Swal.fire({ icon: 'error', title: 'Tipo de usuario inválido', text: 'El tipo de usuario seleccionado no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'email_exists'): ?>
Swal.fire({ icon: 'error', title: 'Correo ya registrado', text: 'Este correo electrónico ya está registrado en el sistema.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>