From 6bc29f8786c43565f507aa1c51874c1c21d42260 Mon Sep 17 00:00:00 2001 From: Alexeer Date: Wed, 2 Jul 2025 08:49:07 -0600 Subject: [PATCH] =?UTF-8?q?Cambio=20de=20contrase=C3=B1a=20desde=20dashboa?= =?UTF-8?q?rd=20de=20cualquier=20tipo=20de=20usuario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/agentes.php | 33 ++ app/controllers/bitacoras.php | 28 +- app/controllers/importadores.php | 17 + app/controllers/reset.php | 505 +++++++++++++++++ app/controllers/transportistas.php | 18 +- app/controllers/vinculaciones.php | 26 + app/helpers/session.php | 6 + views/bitacoras/bitacora_cambios.php | 80 +++ views/bitacoras/bitacora_usuarios.php | 46 +- views/bitacoras/dashboard_bitacoras.php | 21 +- views/importadores/dashboard_importador.php | 14 +- views/login/cambiar_password.php | 2 +- views/login/verificar_codigo.php | 204 ++++--- views/partials/sidebar_configuracion.php | 50 +- views/partials/sidebar_importador.php | 6 +- views/reset/change_password.php | 427 +++++++++++++++ views/reset/verificar_codigo.php | 573 ++++++++++++++++++++ views/seguridad/agregar.php | 151 ++++-- views/seguridad/dashboard_seguridad.php | 8 +- views/solicitud_importacion/crear.php | 11 + views/solicitud_importacion/editar.php | 3 +- views/solicitud_importacion/lista.php | 10 + views/transportistas/lista.php | 10 +- 23 files changed, 2013 insertions(+), 236 deletions(-) create mode 100644 app/controllers/reset.php create mode 100644 views/bitacoras/bitacora_cambios.php create mode 100644 views/reset/change_password.php create mode 100644 views/reset/verificar_codigo.php diff --git a/app/controllers/agentes.php b/app/controllers/agentes.php index 2fd4388..d9a9eb5 100644 --- a/app/controllers/agentes.php +++ b/app/controllers/agentes.php @@ -165,6 +165,39 @@ function desvincularAgente() throw new Exception('Error al desactivar la relación: ' . print_r(sqlsrv_errors(), true)); } + // NUEVO: Verificar si el importador desvinculado tenía esta agencia como activa + $sqlVerificarAgenciaActiva = " + SELECT id_agencia_en_uso + FROM usuarios_sistema + WHERE id_usuario = ? AND id_agencia_en_uso = ? + "; + $stmtVerificarActiva = sqlsrv_query($conn, $sqlVerificarAgenciaActiva, + [$relacion['id_importador'], $relacion['id_agencia']]); + + if ($stmtVerificarActiva === false) { + throw new Exception('Error al verificar agencia activa: ' . print_r(sqlsrv_errors(), true)); + } + + if ($stmtVerificarActiva && sqlsrv_fetch_array($stmtVerificarActiva, SQLSRV_FETCH_ASSOC)) { + // Si tenía esta agencia como activa, quitársela + $sqlQuitarAgenciaActiva = " + UPDATE usuarios_sistema + SET id_agencia_en_uso = NULL + WHERE id_usuario = ? + "; + $stmtQuitarActiva = sqlsrv_query($conn, $sqlQuitarAgenciaActiva, [$relacion['id_importador']]); + + if ($stmtQuitarActiva === false) { + throw new Exception('Error al actualizar la agencia activa del importador: ' . print_r(sqlsrv_errors(), true)); + } + } + + // Limpiar statements + sqlsrv_free_stmt($stmtVerificarActiva); + if (isset($stmtQuitarActiva)) { + sqlsrv_free_stmt($stmtQuitarActiva); + } + // Confirmar transacción sqlsrv_commit($conn); diff --git a/app/controllers/bitacoras.php b/app/controllers/bitacoras.php index 18d22a9..671e3c9 100644 --- a/app/controllers/bitacoras.php +++ b/app/controllers/bitacoras.php @@ -102,7 +102,7 @@ function sistemaAgencia() include __DIR__ . '/../../views/bitacoras/sistema_agencia.php'; } -function usuarios() +function cambios() { if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'super_admin') { header('Location: /IMPORTADORES/login'); @@ -122,6 +122,32 @@ function usuarios() $registros[] = $row; } + include __DIR__ . '/../../views/bitacoras/bitacora_cambios.php'; +} + +function usuarios() +{ + if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'super_admin') { + header('Location: /IMPORTADORES/login'); + exit; + } + + $conn = getConnection(); + $sql = "SELECT u.*, creador.nombre AS nombre_creador + FROM usuarios_sistema u + LEFT JOIN usuarios_sistema creador ON u.creado_por = creador.id_usuario + ORDER BY creado_en DESC"; + $stmt = sqlsrv_query($conn, $sql); + + if ($stmt === false) { + die(print_r(sqlsrv_errors(), true)); + } + + $usuarios = []; + while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + $usuarios[] = $row; + } + include __DIR__ . '/../../views/bitacoras/bitacora_usuarios.php'; } diff --git a/app/controllers/importadores.php b/app/controllers/importadores.php index 5481345..0051f3a 100644 --- a/app/controllers/importadores.php +++ b/app/controllers/importadores.php @@ -184,9 +184,26 @@ function desvincularUsuario() throw new Exception('No se pudo actualizar la relación'); } + // 3. Actualizar el campo id_agencia_en_uso del usuario si es necesario + if ($_SESSION['id_agencia_en_uso'] == $relacion['id_agencia']) { + $sqlActualizarAgencia = " + UPDATE usuarios_sistema + SET id_agencia_en_uso = NULL + WHERE id_usuario = ? + "; + $stmtActualizarAgencia = sqlsrv_query($conn, $sqlActualizarAgencia, [$_SESSION['usuario_id']]); + + if ($stmtActualizarAgencia === false) { + $errors = sqlsrv_errors(); + error_log("Error al actualizar agencia en uso: " . print_r($errors, true)); + throw new Exception('Error al actualizar la agencia en uso'); + } + } + // Limpiar statements sqlsrv_free_stmt($stmtVerificar); sqlsrv_free_stmt($stmtDesactivar); + sqlsrv_free_stmt($stmtActualizarAgencia); // Confirmar transacción sqlsrv_commit($conn); diff --git a/app/controllers/reset.php b/app/controllers/reset.php new file mode 100644 index 0000000..bf704af --- /dev/null +++ b/app/controllers/reset.php @@ -0,0 +1,505 @@ + false, 'message' => '❌ Sesión no válida. Inicia sesión nuevamente.']); + exit; + } + + $conn = getConnection(); + if (!$conn) { + echo json_encode(['success' => false, 'message' => '❌ Error de conexión con la base de datos.']); + exit; + } + + $id_usuario = $_SESSION['usuario_id']; + $email = $_SESSION['usuario_email']; + $emailEncrypted = encrypt($email); + + // Verificar que la cuenta esté activa + $sql = "SELECT activo FROM usuarios_sistema WHERE id_usuario = ?"; + $stmt = sqlsrv_query($conn, $sql, [$id_usuario]); + + if (!$stmt || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { + echo json_encode(['success' => false, 'message' => '❌ Usuario no encontrado.']); + exit; + } + + if ($row['activo'] != 1) { + echo json_encode(['success' => false, 'message' => '❌ Tu cuenta está inactiva.']); + exit; + } + + // Verificar si ya existe un código activo + $now = (new DateTime())->format('Y-m-d H:i:s'); + $sqlCheck = "SELECT COUNT(*) AS total FROM recuperacion_password + WHERE email = ? AND estatus = 0 AND expiracion > ?"; + $checkStmt = sqlsrv_query($conn, $sqlCheck, [$emailEncrypted, $now]); + $checkRow = sqlsrv_fetch_array($checkStmt, SQLSRV_FETCH_ASSOC); + + if ($checkRow['total'] > 0) { + echo json_encode(['success' => false, 'message' => '⚠️ Ya tienes un código activo. Revisa tu correo o espera 10 minutos.']); + exit; + } + + // Generar nuevo código + $codigo = strval(random_int(100000, 999999)); + $expira = (new DateTime('+10 minutes'))->format('Y-m-d H:i:s'); + $estatus = 0; + + $insert = "INSERT INTO recuperacion_password (email, codigo, expiracion, estatus) VALUES (?, ?, ?, ?)"; + $params = [$emailEncrypted, $codigo, $expira, $estatus]; + $result = sqlsrv_query($conn, $insert, $params); + + if (!$result) { + echo json_encode(['success' => false, 'message' => '❌ Error al generar el código de verificación.']); + exit; + } + + // Establecer variables de sesión para el cambio interno + $_SESSION['cambio_interno'] = true; + $_SESSION['codigo_timestamp'] = time(); + + // Agregar estos logs: + error_log("DEBUG enviarCodigoInterno - SESSION después de establecer variables: " . print_r($_SESSION, true)); + error_log("DEBUG - cambio_interno establecido: " . ($_SESSION['cambio_interno'] ? 'TRUE' : 'FALSE')); + + // Enviar código por correo + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->Host = 'secure.emailsrvr.com'; + $mail->SMTPAuth = true; + $mail->Username = 'noreply@aduanasoft.com.mx'; + $mail->Password = $_ENV['SMTP_PASS']; + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = 587; + + $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft'); + $mail->addAddress($email); + $mail->CharSet = 'UTF-8'; + $mail->isHTML(true); + $mail->Subject = 'Código para cambio de contraseña'; + + $mail->Body = " +
+
+
+

Cambio de contraseña

+
+
+

Has solicitado cambiar tu contraseña desde tu cuenta.

+
+

Tu código de verificación es:

+

$codigo

+
+

Este código expirará en 10 minutos.

+

Si no solicitaste este cambio, ignora este correo.

+
+
+ © " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos +
+
+
"; + + $mail->send(); + + // Redirigir directamente desde PHP + header("Location: /IMPORTADORES/reset/verificarCodigoInternoView"); + exit; + + } catch (Exception $e) { + error_log("Error al enviar correo de cambio interno: {$mail->ErrorInfo}"); + + // Solo en caso de error, devolver JSON + echo json_encode(['success' => false, 'message' => '❌ Error al enviar el correo. Intenta más tarde.']); + exit; + } +} + +function verificarCodigoInternoView() +{ + // Verificar si el usuario ya está autenticado + if (!isset($_SESSION['usuario_id']) || !isset($_SESSION['usuario_email']) || !isset($_SESSION['cambio_interno'])) { + header("Location: /IMPORTADORES/login"); + exit; + } + + // Verificar si el código ya fue verificado + if (isset($_SESSION['codigo_verificado_interno']) && $_SESSION['codigo_verificado_interno'] === true) { + header("Location: /IMPORTADORES/reset/cambiarPasswordInternoView"); + exit; + } + + // Mostrar la vista de verificación de código interno + include __DIR__ . '/../../views/reset/verificar_codigo.php'; +} + +function verificarCodigoInterno() +{ + header('Content-Type: application/json'); + + $conn = getConnection(); + + $email = $_SESSION['usuario_email']; + $emailEncrypted = encrypt($email); // aplica aquí también + + try { + // Verificar conexión + if (!isset($conn) || $conn === false) { + echo json_encode([ + 'success' => false, + 'message' => '❌ Error de conexión a la base de datos.', + 'intentos_restantes' => max(0, 3 - ($_SESSION['intentos_codigo_interno'] ?? 0)) + ]); + exit; + } + + $codigo = trim($_POST['codigo'] ?? ''); + + if (empty($codigo)) { + echo json_encode([ + 'success' => false, + 'message' => '❌ Código requerido.', + 'intentos_restantes' => max(0, 3 - ($_SESSION['intentos_codigo_interno'] ?? 0)) + ]); + exit; + } + + if (!preg_match('/^\d{6}$/', $codigo)) { + echo json_encode([ + 'success' => false, + 'message' => '❌ El código debe tener 6 dígitos numéricos.', + 'intentos_restantes' => max(0, 3 - ($_SESSION['intentos_codigo_interno'] ?? 0)) + ]); + exit; + } + + $intentosActuales = $_SESSION['intentos_codigo_interno'] ?? 0; + + if ($intentosActuales >= 3) { + echo json_encode([ + 'success' => false, + 'message' => '❌ Demasiados intentos fallidos. Solicita un nuevo código.', + 'blocked' => true, + 'redirect' => '/IMPORTADORES/seguridad/index', + 'intentos_restantes' => 0 + ]); + exit; + } + + if (!isset($_SESSION['usuario_id']) || !isset($_SESSION['usuario_email'])) { + echo json_encode([ + 'success' => false, + 'message' => '❌ Sesión inválida. Intenta nuevamente.', + 'blocked' => true, + 'redirect' => '/IMPORTADORES/seguridad/index' + ]); + exit; + } + + // Consulta principal para verificar el código + $sql = "SELECT id, codigo, expiracion + FROM recuperacion_password + WHERE email = ? AND estatus = 0 AND expiracion > GETDATE() + ORDER BY expiracion DESC + "; + + $stmt = sqlsrv_query($conn, $sql, [$emailEncrypted]); + + if (!$stmt) { + throw new Exception("Error en la consulta a la base de datos"); + } + + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + sqlsrv_free_stmt($stmt); + + if (!$row) { + $_SESSION['intentos_codigo_interno'] = $intentosActuales + 1; + echo json_encode([ + 'success' => false, + 'message' => '❌ Código inválido o expirado.', + 'intentos_restantes' => max(0, 3 - $_SESSION['intentos_codigo_interno']) + ]); + exit; + } + + if (!isset($row['codigo'], $row['id'], $row['expiracion'])) { + throw new Exception("Faltan campos requeridos en la respuesta de la base de datos."); + } + + if ($codigo !== $row['codigo']) { + $_SESSION['intentos_codigo_interno'] = $intentosActuales + 1; + echo json_encode([ + 'success' => false, + 'message' => '❌ Código incorrecto.', + 'intentos_restantes' => max(0, 3 - $_SESSION['intentos_codigo_interno']) + ]); + exit; + } + + $expiracion = $row['expiracion']; + $ahora = new DateTime(); + + if ($expiracion instanceof DateTime) { + $expiracionStr = $expiracion->format('Y-m-d H:i:s'); + + if ($expiracion <= $ahora) { + $_SESSION['intentos_codigo_interno'] = $intentosActuales + 1; + echo json_encode([ + 'success' => false, + 'message' => '❌ El código ha expirado.', + 'intentos_restantes' => max(0, 3 - $_SESSION['intentos_codigo_interno']) + ]); + exit; + } + } + + // Marcar el código como usado + $sqlUpdate = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?"; + $stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$row['id']]); + + if (!$stmtUpdate) { + throw new Exception("Error al actualizar el estado del código"); + } + + sqlsrv_free_stmt($stmtUpdate); + + // Marcar como verificado y limpiar intentos + $_SESSION['codigo_verificado_interno'] = true; + unset($_SESSION['intentos_codigo_interno']); + + echo json_encode([ + 'success' => true, + 'message' => '✅ Código verificado correctamente.', + 'redirect' => '/IMPORTADORES/reset/cambiarPasswordInternoView' + ]); + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'message' => '❌ Error interno del servidor. Intenta nuevamente.', + 'intentos_restantes' => max(0, 3 - ($_SESSION['intentos_codigo_interno'] ?? 0)) + ]); + exit; + } finally { + // Cerrar conexión si existe + if (isset($conn) && $conn !== false) { + sqlsrv_close($conn); + } + } +} + +function cambiarPasswordInternoView() +{ + // Verificar si el usuario ya está autenticado y el código verificado + if (!isset($_SESSION['usuario_id']) || !isset($_SESSION['usuario_email']) || + !isset($_SESSION['cambio_interno']) || !isset($_SESSION['codigo_verificado_interno'])) { + header("Location: /IMPORTADORES/login"); + exit; + } + + // Mostrar la vista de cambio de contraseña interno + include __DIR__ . '/../../views/reset/change_password.php'; +} + +function cambiarPasswordInterno() +{ + header('Content-Type: application/json'); // ⬅️ AGREGAR ESTA LÍNEA + + // Verificar autorización completa + if (!isset($_SESSION['usuario_id']) || !isset($_SESSION['usuario_email']) || + !isset($_SESSION['cambio_interno']) || !isset($_SESSION['codigo_verificado_interno'])) { + echo json_encode(['success' => false, 'message' => '❌ No tienes autorización para cambiar la contraseña.']); + exit; + } + + $conn = getConnection(); + if (!$conn) { + echo json_encode(['success' => false, 'message' => '❌ Error de conexión con la base de datos.']); + exit; + } + + $passwordActual = $_POST['password_actual'] ?? ''; + $passwordNueva = $_POST['password_nueva'] ?? ''; + $confirmarPassword = $_POST['confirmar_password'] ?? ''; + + // Validaciones + if (empty($passwordActual)) { + echo json_encode(['success' => false, 'message' => '❌ La contraseña actual es requerida.']); + exit; + } + + if (strlen($passwordNueva) < 6) { + echo json_encode(['success' => false, 'message' => '❌ La nueva contraseña debe tener al menos 6 caracteres.']); + exit; + } + + if ($passwordNueva !== $confirmarPassword) { + echo json_encode(['success' => false, 'message' => '❌ Las contraseñas nuevas no coinciden.']); + exit; + } + + if ($passwordActual === $passwordNueva) { + echo json_encode(['success' => false, 'message' => '❌ La nueva contraseña debe ser diferente a la actual.']); + exit; + } + + $id_usuario = $_SESSION['usuario_id']; + $email = $_SESSION['usuario_email']; + $emailEncrypted = encrypt($email); + + // Verificar contraseña actual + $sql = "SELECT password_hash FROM usuarios_sistema WHERE id_usuario = ?"; + $stmt = sqlsrv_query($conn, $sql, [$id_usuario]); + + if (!$stmt || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { + echo json_encode(['success' => false, 'message' => '❌ Error al verificar la contraseña actual.']); + exit; + } + + if (!password_verify($passwordActual, $row['password_hash'])) { + echo json_encode(['success' => false, 'message' => '❌ La contraseña actual es incorrecta.']); + exit; + } + + // Actualizar contraseña + $hashNueva = password_hash($passwordNueva, PASSWORD_BCRYPT); + $sqlUpdate = "UPDATE usuarios_sistema SET password_hash = ? WHERE id_usuario = ?"; + $stmtUpdate = sqlsrv_prepare($conn, $sqlUpdate, [$hashNueva, $id_usuario]); + + if (!$stmtUpdate || !sqlsrv_execute($stmtUpdate)) { + echo json_encode(['success' => false, 'message' => '❌ Error al actualizar la contraseña.']); + exit; + } + + // Registrar en bitácora + $ip = $_SERVER['REMOTE_ADDR'] ?? 'N/A'; + registrarBitacora($conn, $id_usuario, $email, $ip, 1, 'Cambio de contraseña interno'); + + // Enviar correo de confirmación + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->Host = 'secure.emailsrvr.com'; + $mail->SMTPAuth = true; + $mail->Username = 'noreply@aduanasoft.com.mx'; + $mail->Password = $_ENV['SMTP_PASS']; + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = 587; + + $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft'); + $mail->addAddress($email); + $mail->CharSet = 'UTF-8'; + $mail->isHTML(true); + $mail->Subject = 'Contraseña actualizada exitosamente'; + + $fechaHora = date('d/m/Y H:i:s'); + $mail->Body = " +
+
+
+

Contraseña actualizada

+
+
+

Hola,

+

Te confirmamos que tu contraseña ha sido cambiada exitosamente desde tu cuenta.

+

Fecha y hora: {$fechaHora}

+

IP: {$ip}

+
+

⚠️ Importante: Si no realizaste este cambio, contacta inmediatamente al área de soporte.

+
+
+
+ © " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos +
+
+
"; + + $mail->send(); + } catch (Exception $e) { + error_log("Error al enviar correo de confirmación interno: {$mail->ErrorInfo}"); + // No fallar el proceso por error de email, solo registrar el error + } + + // Limpiar variables de sesión del proceso + unset($_SESSION['cambio_interno']); + unset($_SESSION['codigo_verificado_interno']); + unset($_SESSION['codigo_timestamp']); + + echo json_encode([ + 'success' => true, + 'message' => '✅ Contraseña actualizada exitosamente.', + 'redirect' => '/IMPORTADORES/login' // Redirigir a la página de seguridad + ]); + exit; +} + +function cancelarCambioInterno() +{ + header('Content-Type: application/json'); + + if (isset($_SESSION['cambio_interno'])) { + $conn = getConnection(); + + if ($conn && isset($_SESSION['usuario_email'])) { + // Invalidar códigos activos + $emailEncrypted = encrypt($_SESSION['usuario_email']); + $sql = "UPDATE recuperacion_password SET estatus = 1 WHERE email = ? AND estatus = 0"; + $stmt = sqlsrv_query($conn, $sql, [$emailEncrypted]); + if ($stmt) sqlsrv_free_stmt($stmt); + } + + // Limpiar sesión + unset($_SESSION['cambio_interno']); + unset($_SESSION['codigo_verificado_interno']); + unset($_SESSION['intentos_codigo_interno']); + unset($_SESSION['codigo_timestamp']); + } + + echo json_encode(['success' => true, 'message' => 'Proceso cancelado.']); +} + +// Función para obtener el estado actual (para mostrar en la vista) +function obtenerEstadoDosFactores() +{ + $conn = getConnection(); + + // CORRIGIDO: Cambiar id_usuario por usuario_id + $id_usuario = $_SESSION['usuario_id'] ?? null; + if (!$id_usuario) { + return 0; // valor por defecto si no hay sesión + } + + $sql_dos_factores = "SELECT dos_factores FROM usuarios_sistema WHERE id_usuario = ?"; + $params = [$id_usuario]; + $stmt_dos_factores = sqlsrv_prepare($conn, $sql_dos_factores, $params); + + $dos_factores_estado = 0; // valor por defecto + + if ($stmt_dos_factores && sqlsrv_execute($stmt_dos_factores)) { + if ($row = sqlsrv_fetch_array($stmt_dos_factores, SQLSRV_FETCH_ASSOC)) { + $dos_factores_estado = (int)$row['dos_factores']; + } + } + + sqlsrv_free_stmt($stmt_dos_factores); + sqlsrv_close($conn); + + return $dos_factores_estado; +} \ No newline at end of file diff --git a/app/controllers/transportistas.php b/app/controllers/transportistas.php index 67ca8f6..9ef011e 100644 --- a/app/controllers/transportistas.php +++ b/app/controllers/transportistas.php @@ -439,15 +439,15 @@ function actualizar() // 3) Ejecutar UPDATE $sqlUpd = "UPDATE dbo.transportistas SET clave_identificador = ?, - nombre = ?, - rfc = ?, - curp = ?, - telefono = ?, - caat = ?, - pais = ?, - entidad_federativa = ?, - ciudad = ?, - domicilio = ? + nombre = ?, + rfc = ?, + curp = ?, + telefono = ?, + caat = ?, + pais = ?, + entidad_federativa = ?, + ciudad = ?, + domicilio = ? WHERE id_transportista = ?"; $params = [ $clave, $nombre, $rfc, $curp, $tel, diff --git a/app/controllers/vinculaciones.php b/app/controllers/vinculaciones.php index 857f1c2..de1c608 100644 --- a/app/controllers/vinculaciones.php +++ b/app/controllers/vinculaciones.php @@ -298,6 +298,32 @@ function desvincularAgencia() throw new Exception('Error al desactivar la relación de ' . $tipo); } + // NUEVO: Verificar si el usuario desvinculado tenía esta agencia como activa + if ($tipo === 'importador') { + // Verificar si el importador tenía esta agencia como activa + $sqlVerificarAgenciaActiva = " + SELECT id_agencia_en_uso + FROM usuarios_sistema + WHERE id_usuario = ? AND id_agencia_en_uso = ? + "; + $stmtVerificarActiva = sqlsrv_query($conn, $sqlVerificarAgenciaActiva, + [$relacion['id_importador'], $relacion['id_agencia']]); + + if ($stmtVerificarActiva && sqlsrv_fetch_array($stmtVerificarActiva, SQLSRV_FETCH_ASSOC)) { + // Si tenía esta agencia como activa, quitársela + $sqlQuitarAgenciaActiva = " + UPDATE usuarios_sistema + SET id_agencia_en_uso = NULL + WHERE id_usuario = ? + "; + $stmtQuitarActiva = sqlsrv_query($conn, $sqlQuitarAgenciaActiva, [$relacion['id_importador']]); + + if (!$stmtQuitarActiva) { + throw new Exception('Error al actualizar la agencia activa del importador'); + } + } + } + // Confirmar transacción sqlsrv_commit($conn); diff --git a/app/helpers/session.php b/app/helpers/session.php index b5bc658..3a676ba 100644 --- a/app/helpers/session.php +++ b/app/helpers/session.php @@ -1,4 +1,10 @@ + + + + + + 🔄 Cambios de Usuario + + + + + + + + + + +
+

🔄 Cambios de Usuario

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
#ID UsuarioAcciónDescripciónFecha
format('Y-m-d H:i') : htmlspecialchars($r['fecha']) ?>
+
+
+
+ + + + + \ No newline at end of file diff --git a/views/bitacoras/bitacora_usuarios.php b/views/bitacoras/bitacora_usuarios.php index 2e0f83c..1162c46 100644 --- a/views/bitacoras/bitacora_usuarios.php +++ b/views/bitacoras/bitacora_usuarios.php @@ -4,7 +4,7 @@ - 🛠️ Cambios de Usuario + 👥 Registro de Usuarios @@ -14,7 +14,7 @@
-

🛠️ Cambios de Usuario

+

👥 Registro de Usuarios

- +
- - - - + + + + + + - - - - - - - + + + + + + + + + @@ -67,7 +77,7 @@ - + \ No newline at end of file diff --git a/views/bitacoras/dashboard_bitacoras.php b/views/bitacoras/dashboard_bitacoras.php index e3971b0..2a11fd9 100644 --- a/views/bitacoras/dashboard_bitacoras.php +++ b/views/bitacoras/dashboard_bitacoras.php @@ -26,6 +26,9 @@ .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; } } .card { border-radius: 12px; } + .btn-indigo { background-color: #6610f2; color: white; } + .btn-indigo:hover { background-color: #520dc2; color: white; } + .text-indigo { color: #520dc2; } @@ -47,20 +50,30 @@
-
Registro de usuarios
+
Cambios de usuarios
+

Ver cambios realizados por los usuarios.

+ + Ver cambios + +
+
+
+
+
Registro de usuarios

Ver registros de los usuarios.

+ class="btn btn-info btn-sm mt-2 "> Ver registro de usuarios
-
Registro de agencias
+
Registro de agencias

Ver registros de las agencias.

+ class="btn btn-indigo btn-sm mt-2 "> Ver registro de agencias
diff --git a/views/importadores/dashboard_importador.php b/views/importadores/dashboard_importador.php index da53d5f..2ac5af0 100644 --- a/views/importadores/dashboard_importador.php +++ b/views/importadores/dashboard_importador.php @@ -16,15 +16,15 @@ .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; } - .btn-indigo { background-color: #6610f2; color: white;} - .btn-indigo:hover { background-color: #520dc2; color: white;} - .text-indigo { color: #520dc2;} - .btn-orange { background-color: orangeRed; color: white;} - .btn-orange:hover { background-color: #ff3600; color: white;} - .text-orange { color: orangeRed;} + .btn-indigo { background-color: #6610f2; color: white; } + .btn-indigo:hover { background-color: #520dc2; color: white; } + .text-indigo { color: #520dc2; } + .btn-orange { background-color: orangeRed; color: white; } + .btn-orange:hover { background-color: #ff3600; color: white; } + .text-orange { color: orangeRed; } .btn-teal { background-color: #20c997; color: white; } .btn-teal:hover { background-color: #4dd4ac; color: black; } - .text-teal { color: #20c997; } + .text-teal { color: #20c997; } /* 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 */ diff --git a/views/login/cambiar_password.php b/views/login/cambiar_password.php index 78afd23..d93fd5a 100644 --- a/views/login/cambiar_password.php +++ b/views/login/cambiar_password.php @@ -107,7 +107,7 @@ if (!isset($_SESSION['recuperacion_autorizada'])) { + + + + + + + +
+ + + + +
+

👮 Opciones de Seguridad

+
+ + +
+
+

Autenticación de dos factores

+

Al activar esta funcionalidad, blindas el acceso a tu cuenta, recibiras un código de acceso a tu correo electrónico para confirmar identidad.

+
+
+ > + +
+ + +
+
+ + +
+
+

Cambiar contraseña

+

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.



+ Cambiar contraseña +
+
+ + +
+
+

Recibe notificaciones

+

Agrega un correo adicional para recibir notificaciones.

+ +

+ +
+
+
+ +
+
+ + +
+ + +
+
+ +
+ + + +
+
+ + +
+
+

Correo de respaldo

+

Agrega un correo de respaldo para reuperación de tu cuenta.

+ +

+ +
+
+
+ +
+ + + + +
+
+ +
+ + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/views/reset/verificar_codigo.php b/views/reset/verificar_codigo.php new file mode 100644 index 0000000..97a24c4 --- /dev/null +++ b/views/reset/verificar_codigo.php @@ -0,0 +1,573 @@ + + + + + + 👮 Opciones de Seguridad + + + + + + + + + + +
+ + + + +
+

👮 Opciones de Seguridad

+
+ + +
+
+

Autenticación de dos factores

+

Al activar esta funcionalidad, blindas el acceso a tu cuenta, recibiras un código de acceso a tu correo electrónico para confirmar identidad.

+
+
+ > + +
+ + +
+
+ + +
+
+

Cambiar contraseña

+

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.



+ Cambiar contraseña +
+
+ + +
+
+

Recibe notificaciones

+

Agrega un correo adicional para recibir notificaciones.

+ +

+ +
+
+
+ +
+
+ + +
+ + +
+
+ +
+ + + +
+
+ + +
+
+

Correo de respaldo

+

Agrega un correo de respaldo para reuperación de tu cuenta.

+ +

+ +
+
+
+ +
+ + + + +
+
+ +
+ + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/views/seguridad/agregar.php b/views/seguridad/agregar.php index 0ab623a..26123d3 100644 --- a/views/seguridad/agregar.php +++ b/views/seguridad/agregar.php @@ -33,15 +33,19 @@ $dos_factores_estado = obtenerEstadoDosFactores(); .card { border-radius: 12px; } .btn i { font-family: "Font Awesome 6 Free", sans-serif; margin-right: 0.5rem; } .btn { font-family: 'Segoe UI', sans-serif; } - .modal-header.bg-primary { background: linear-gradient(135deg, #003366, #0055A5) !important; color: white; } - .modal-header.bg-success { background: linear-gradient(135deg, #28a745, #20c997) !important; color: white; } - .codigo-input { text-align: center; font-size: 1.5rem; letter-spacing: 0.5rem; font-weight: bold; } - .intentos-restantes { color: #dc3545; font-weight: bold; } - .password-strength { height: 4px; border-radius: 2px; margin-top: 5px; transition: all 0.3s ease; } - .strength-weak { background-color: #dc3545; } - .strength-medium { background-color: #ffc107; } - .strength-strong { background-color: #28a745; } - .btn-loading { pointer-events: none; opacity: 0.6; } + /* Fix para z-index del modal */ + .modal { z-index: 9999 !important; } + .modal-backdrop { z-index: 9998 !important; } + /* Estilos adicionales para mejorar la apariencia del modal */ + .modal-content { border: none; border-radius: 15px; overflow: hidden; } + .modal-header { border-bottom: none; padding: 20px 25px 15px; } + .modal-body { padding: 25px; } + .modal-footer { border-top: none; padding: 15px 25px 25px; } + .btn { border-radius: 8px; padding: 10px 20px; font-weight: 500; } + .alert-info { background-color: #e3f2fd; border-color: #bbdefb; color: #1565c0; border-radius: 8px; } + /* Animación suave para el modal */ + .modal.fade .modal-dialog { transform: translate(0, -50px); transition: transform 0.3s ease-out; } + .modal.show .modal-dialog { transform: translate(0, 0); } @@ -68,7 +72,18 @@ $dos_factores_estado = obtenerEstadoDosFactores();
-
+ +
+
+

Cambiar contraseña

+

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.


+
+ + +
+
@@ -137,69 +152,95 @@ $dos_factores_estado = obtenerEstadoDosFactores();
+ + + + + \ No newline at end of file diff --git a/views/seguridad/dashboard_seguridad.php b/views/seguridad/dashboard_seguridad.php index 7464669..21013f6 100644 --- a/views/seguridad/dashboard_seguridad.php +++ b/views/seguridad/dashboard_seguridad.php @@ -51,7 +51,13 @@ -
+ +
+
+

Cambiar contraseña

+

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.


+
+
diff --git a/views/solicitud_importacion/crear.php b/views/solicitud_importacion/crear.php index e56c3ec..ac3ce88 100644 --- a/views/solicitud_importacion/crear.php +++ b/views/solicitud_importacion/crear.php @@ -7,6 +7,7 @@ ➕ Nueva Solicitud de Importación + @@ -26,10 +27,20 @@ } .card { border-radius: 12px; } .hide { display: none !important; } + .alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); } + +
+
⚠️ No tienes una agencia activa vinculada.
+

Para poder gestionar tus solicitudes de importación, primero debes vincularte a una agencia.

+ Ir a Vinculaciones +
+ + +

➕ Nueva Solicitud de Importación

diff --git a/views/solicitud_importacion/editar.php b/views/solicitud_importacion/editar.php index 720f8f0..e2aa7a8 100644 --- a/views/solicitud_importacion/editar.php +++ b/views/solicitud_importacion/editar.php @@ -9,8 +9,7 @@ - - + diff --git a/views/solicitud_importacion/lista.php b/views/solicitud_importacion/lista.php index bcfaf9f..2d4c369 100644 --- a/views/solicitud_importacion/lista.php +++ b/views/solicitud_importacion/lista.php @@ -34,10 +34,20 @@ } .card { border-radius: 12px; } .status-select:disabled { background-color: #e9ecef; color: #6c757d;} + .alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); } + +
+
⚠️ No tienes una agencia activa vinculada.
+

Para poder gestionar tus solicitudes de importación, primero debes vincularte a una agencia.

+ Ir a Vinculaciones +
+ + +

📄 Solicitudes de Importación

➕ Nueva Solicitud diff --git a/views/transportistas/lista.php b/views/transportistas/lista.php index d2ed633..f04294f 100644 --- a/views/transportistas/lista.php +++ b/views/transportistas/lista.php @@ -117,12 +117,10 @@ // Mostrar alert de “borrado” al volver de la acción - Swal.fire({ - icon: 'success', - title: 'Transportista eliminado', - text: 'Ya no aparecerá en tu lista.', - confirmButtonColor: '#198754' - }); + Swal.fire({ icon: 'success', title: 'Transportista eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' }); + + + Swal.fire({ icon: 'success', title: 'Transportista actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
#ID UsuarioAcciónDescripciónIDNombreCorreoTipo FechaEstatusAprobado por
format('Y-m-d H:i') : htmlspecialchars($r['fecha']) ?>
format('Y-m-d H:i:s') : '') ?> + + Activo + + Inactivo + +