From 738c9b07dfe9d435868b3653490124c15dacda25 Mon Sep 17 00:00:00 2001 From: Alexeer Date: Mon, 26 May 2025 12:34:40 -0600 Subject: [PATCH] Correo respaldo y notificaciones --- app/controllers/login.php | 205 ++++++++++++++++++----- app/controllers/seguridad.php | 121 +++++++++++++ emails_tables.txt | 18 +- views/login/verificar_codigo.php | 17 +- views/partials/sidebar_configuracion.php | 109 ++++++++---- views/seguridad/agregar.php | 123 ++++++++++++++ views/seguridad/dashboard_seguridad.php | 22 +-- 7 files changed, 520 insertions(+), 95 deletions(-) create mode 100644 views/seguridad/agregar.php diff --git a/app/controllers/login.php b/app/controllers/login.php index adf7342..f9b2ef9 100644 --- a/app/controllers/login.php +++ b/app/controllers/login.php @@ -88,7 +88,7 @@ function validar() } $codigo = rand(100000, 999999); - $expiracion = date('Y-m-d H:i:s', strtotime('+1 minutes')); + $expiracion = date('Y-m-d H:i:s', strtotime('+5 minutes')); $usuarioId = $row['id_usuario']; $sql = "INSERT INTO verificaciones (id_usuario, codigo, expiracion) VALUES (?, ?, ?)"; @@ -170,6 +170,10 @@ function enviarConfirmacion($email, $codigo) function confirmarAcceso() { $conn = getConnection(); // Asegúrate que esta función retorne una conexión válida + if (!$conn) { + http_response_code(500); + exit('Error de conexión'); + } $codigoIngresado = trim($_POST['codigo'] ?? ''); $usuarioId = $_SESSION['usuario_id'] ?? null; @@ -225,22 +229,21 @@ function recuperar() function enviarCodigo() { header('Content-Type: application/json'); - $conn = getConnection(); + if (!$conn) { + echo json_encode(['success' => false, 'message' => '❌ Error de conexión con la base de datos.']); + exit; + } - // Obtener el correo proporcionado en el formulario $email = trim($_POST['email'] ?? ''); - // Verificar si el correo es válido if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo json_encode(['success' => false, 'message' => '❌ Correo inválido.']); exit; } - // Encriptar el correo ingresado para comparar con el de la base de datos $emailEncrypted = encrypt($email); - // Consultar si el correo encriptado existe en la base de datos $sql = "SELECT id_usuario FROM usuarios_sistema WHERE email = ?"; $stmt = sqlsrv_query($conn, $sql, [$emailEncrypted]); @@ -248,9 +251,9 @@ function enviarCodigo() echo json_encode(['success' => false, 'message' => '❌ Correo no encontrado.']); exit; } + $id_usuario = $row['id_usuario']; - // 🧠 Verificar si ya hay un código activo y no expirado - $now = date('Y-m-d H:i:s'); + $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]); @@ -261,12 +264,10 @@ function enviarCodigo() exit; } - // Generar el código de recuperación y la expiración - $codigo = strval(rand(100000, 999999)); // Convertir a string - $expira = date('Y-m-d H:i:s', strtotime('+10 minutes')); - $estatus = 0; // Estatus inicial + $codigo = strval(random_int(100000, 999999)); + $expira = (new DateTime('+10 minutes'))->format('Y-m-d H:i:s'); + $estatus = 0; - // Guardar el código y la expiración en la tabla de recuperación de contraseñas $insert = "INSERT INTO recuperacion_password (email, codigo, expiracion, estatus) VALUES (?, ?, ?, ?)"; $params = [$emailEncrypted, $codigo, $expira, $estatus]; $result = sqlsrv_query($conn, $insert, $params); @@ -276,7 +277,68 @@ function enviarCodigo() exit; } - // Enviar correo con el código utilizando PHPMailer + // ✅ AGREGAR ESTAS LÍNEAS PARA ESTABLECER LA SESIÓN + $_SESSION['email_recuperacion'] = $email; + $_SESSION['id_usuario'] = $id_usuario; + + 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 de recuperación de contraseña'; + + $mail->Body = " +
+
+
+

Recuperación de contraseña

+
+
+

Has solicitado recuperar tu contraseña.

+
+

Tu código de recuperación es:

+

$codigo

+
+

Este código expirará en 10 minutos.

+
+
+ © " . date('Y') . " SIIH · Desarrollado por AduanaSoft +
+
+
"; + + $mail->send(); + + echo json_encode(['success' => true, 'message' => '✅ Código enviado. Revisa tu correo.']); + } catch (Exception $e) { + error_log("Error al enviar correo de recuperación: {$mail->ErrorInfo}"); + echo json_encode(['success' => false, 'message' => '❌ Error al enviar el correo. Intenta más tarde.']); + } +} + +function enviarCorreoRespaldo($conn, $id_usuario, $codigo) +{ + // Obtener correo de respaldo + $sql = "SELECT TOP 1 correo FROM correo_respaldo WHERE id_usuario = ?"; + $stmt = sqlsrv_query($conn, $sql, [$id_usuario]); + + if (!$stmt || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { + error_log("❌ No se encontró correo de respaldo para el usuario ID $id_usuario."); + return false; // No hay correo de respaldo + } + + $correoRespaldo = $row['correo']; + try { $mail = new PHPMailer(true); // ConfigEuración de servidor SMTP @@ -290,7 +352,7 @@ function enviarCodigo() // Configuración del correo $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft'); - $mail->addAddress($email); + $mail->addAddress($correoRespaldo); $mail->CharSet = 'UTF-8'; $mail->isHTML(true); $mail->Subject = 'Código de recuperación de contraseña'; @@ -316,16 +378,55 @@ function enviarCodigo() // Enviar el correo $mail->send(); + } catch (Exception $e) { error_log("Error al enviar correo: {$mail->ErrorInfo}"); - echo json_encode(['success' => false, 'message' => '❌ Hubo un error al enviar el correo. Intenta más tarde.']); + return false; + } + + return true; +} + +function reenviarCodigo() +{ + header('Content-Type: application/json'); + + // Validar sesión y datos requeridos + if (!isset($_SESSION['id_usuario'], $_SESSION['email_recuperacion'])) { + echo json_encode(['success' => false, 'message' => '⚠️ Sesión expirada. Vuelve a ingresar tu correo.']); exit; } - $_SESSION['email_recuperacion'] = $email; // email sin cifrar - $_SESSION['intentos_codigo'] = 0; // Inicializa intentos + $conn = getConnection(); + if (!$conn) { + echo json_encode(['success' => false, 'message' => '❌ Error de conexión con la base de datos.']); + exit; + } + + $id_usuario = $_SESSION['id_usuario']; + $emailEncrypted = encrypt($_SESSION['email_recuperacion']); + + // Obtener el último código activo de recuperación + $now = (new DateTime())->format('Y-m-d H:i:s'); + $sql = "SELECT TOP 1 codigo FROM recuperacion_password + WHERE email = ? AND estatus = 0 AND expiracion > ? + ORDER BY expiracion DESC"; + $stmt = sqlsrv_query($conn, $sql, [$emailEncrypted, $now]); + + if (!$stmt || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { + echo json_encode(['success' => false, 'message' => '❌ No se encontró un código activo para reenviar.']); + exit; + } + + $codigo = $row['codigo']; + + // Llamar a la función para reenviar el correo al respaldo + if (enviarCorreoRespaldo($conn, $id_usuario, $codigo)) { + echo json_encode(['success' => true, 'message' => '📤 Código reenviado al correo de respaldo.']); + } else { + echo json_encode(['success' => false, 'message' => '❌ Error al reenviar el código al correo de respaldo.']); + } - echo json_encode(['success' => true, 'message' => '✅ Código enviado correctamente. Revisa tu correo.']); exit; } @@ -336,25 +437,33 @@ function verificarCodigoVista() function verificarCodigo() { + // ❌ REMOVER session_start() - ya se maneja en session.php header('Content-Type: application/json'); $conn = getConnection(); - $codigo = trim($_POST['codigo'] ?? ''); + // Validar existencia de conexión, código y email en sesión + $codigo = isset($_POST['codigo']) ? trim($_POST['codigo']) : ''; $email = $_SESSION['email_recuperacion'] ?? ''; - if (!$codigo || !$email) { + if (!$conn || empty($codigo) || empty($email)) { echo json_encode(['success' => false, 'message' => '❌ Código o correo faltante.']); exit; } - // Encriptar el correo ingresado + // Encriptar el correo (asegurar que coincida con formato en base) $emailEncrypted = encrypt($email); - // Consultar el código de recuperación y su expiración desde la base de datos - $sql = "SELECT id, codigo, expiracion FROM recuperacion_password + // Consulta el último código válido para ese email + $sql = "SELECT TOP 1 id, codigo, expiracion FROM recuperacion_password WHERE email = ? AND estatus = 0 ORDER BY id DESC"; $stmt = sqlsrv_query($conn, $sql, [$emailEncrypted]); + + if ($stmt === false) { + echo json_encode(['success' => false, 'message' => '❌ Error al consultar el código.']); + exit; + } + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); if (!$row) { @@ -362,36 +471,49 @@ function verificarCodigo() exit; } - // Manejo de intentos fallidos - $_SESSION['intentos_codigo'] = ($_SESSION['intentos_codigo'] ?? 0) + 1; + // Manejo de intentos fallidos en sesión + if (!isset($_SESSION['intentos_codigo'])) { + $_SESSION['intentos_codigo'] = 0; + } + + $_SESSION['intentos_codigo']++; + if ($_SESSION['intentos_codigo'] > 5) { - $update = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?"; - sqlsrv_query($conn, $update, [$row['id']]); + // Bloquea el código en la base y resetea intentos + sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]); unset($_SESSION['intentos_codigo']); + echo json_encode(['success' => false, 'message' => '🚫 Has excedido el número de intentos. Solicita un nuevo código.']); exit; } - if ($codigo != $row['codigo']) { - echo json_encode(['success' => false, 'message' => '❌ Código incorrecto. Intento {$_SESSION["intentos_codigo"]} de 5.']); + // Compara códigos - asegurando que no haya problemas con mayúsculas o espacios + if (strcasecmp($codigo, $row['codigo']) !== 0) { + echo json_encode(['success' => false, 'message' => "❌ Código incorrecto. Intento {$_SESSION['intentos_codigo']} de 5."]); exit; } - // Validar expiración - $expiracionTimestamp = strtotime($row['expiracion']->format('Y-m-d H:i:s')); - if ($expiracionTimestamp < time()) { - $update = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?"; - sqlsrv_query($conn, $update, [$row['id']]); + // Validar expiración - puede venir como DateTime o string + $expiracion = $row['expiracion']; + $expiracionTimestamp = null; + + if ($expiracion instanceof DateTime) { + $expiracionTimestamp = $expiracion->getTimestamp(); + } else { + $expiracionTimestamp = strtotime($expiracion); + } + + if ($expiracionTimestamp === false || $expiracionTimestamp < time()) { + // Código expirado: marcar como usado y enviar mensaje + sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]); echo json_encode(['success' => false, 'message' => '⏰ El código ha expirado.']); exit; } - // ✅ Código correcto - $update = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?"; - sqlsrv_query($conn, $update, [$row['id']]); + // Código correcto: actualizar estado y permitir cambio de contraseña + sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]); - // Habilitar cambio de contraseña - $_SESSION['recuperacion_autorizada'] = true; + $_SESSION['recuperacion_autorizada'] = true; unset($_SESSION['intentos_codigo']); echo json_encode(['success' => true, 'message' => '✅ Código verificado. Redirigiendo...']); @@ -400,9 +522,6 @@ function verificarCodigo() function cambiarPasswordVista() { - if (session_status() === PHP_SESSION_NONE) { - session_start(); - } if (!isset($_SESSION['recuperacion_autorizada'])) { header('Location: /IMPORTADORES/login'); exit; diff --git a/app/controllers/seguridad.php b/app/controllers/seguridad.php index 6bf4433..0088cf1 100644 --- a/app/controllers/seguridad.php +++ b/app/controllers/seguridad.php @@ -5,5 +5,126 @@ require_once __DIR__ . '/../helpers/crypto.php'; function index() { + // Asegúrate de tener conexión + $conn = getConnection(); // Suponiendo que tienes una función getConnection() + + // Verifica si la sesión tiene el id_usuario + if (!isset($_SESSION['usuario_id']) || ($_SESSION['pendiente_confirmacion'] ?? true)) { + die("Usuario no autenticado."); + } + + $id_usuario = $_SESSION['usuario_id']; + + // Obtener correos + $correos = obtenerCorreos($conn, $id_usuario); + + // Cargar vista y pasar datos include __DIR__ . '/../../views/seguridad/dashboard_seguridad.php'; +} + +function opciones() +{ + include __DIR__ . '/../../views/seguridad/agregar.php'; +} + +function correoExtra() +{ + if (!isset($_SESSION['usuario_id']) || ($_SESSION['pendiente_confirmacion'] ?? true)) { + echo "Error: no se ha iniciado sesión o falta el ID de usuario."; + exit; + } + + $id_usuario = $_SESSION['usuario_id']; + $conn = getConnection(); + + $sql_check = "SELECT COUNT(*) AS total FROM correo_extra WHERE id_usuario = ?"; + $stmt_check = sqlsrv_query($conn, $sql_check, [$id_usuario]); + $row = sqlsrv_fetch_array($stmt_check, SQLSRV_FETCH_ASSOC); + + if ($row['total'] > 0) { + echo "Ya tienes un correo adicional registrado."; + return; + } + + $correo = $_POST['email-extra'] ?? ''; + + if (!filter_var($correo, FILTER_VALIDATE_EMAIL)) { + echo "Correo inválido."; + return; + } + + $query = "INSERT INTO correo_extra (id_usuario, correo) VALUES (?, ?)"; + $stmt = sqlsrv_prepare($conn, $query, [$id_usuario, $correo]); + + if ($stmt && sqlsrv_execute($stmt)) { + header('Location: /IMPORTADORES/seguridad/index'); + exit; + } else { + echo "Error al registrar el correo extra."; + } +} + +function correoRespaldo() +{ + if (!isset($_SESSION['usuario_id']) || ($_SESSION['pendiente_confirmacion'] ?? true)) { + echo "Error: no se ha iniciado sesión o falta el ID de usuario."; + exit; + } + + $id_usuario = $_SESSION['usuario_id']; + $conn = getConnection(); + + $sql_check = "SELECT COUNT(*) AS total FROM correo_respaldo WHERE id_usuario = ?"; + $stmt_check = sqlsrv_query($conn, $sql_check, [$id_usuario]); + $row = sqlsrv_fetch_array($stmt_check, SQLSRV_FETCH_ASSOC); + + if ($row['total'] > 0) { + echo "Ya tienes un correo adicional registrado."; + return; + } + + $correo = $_POST['email-respaldo'] ?? ''; + + if (!filter_var($correo, FILTER_VALIDATE_EMAIL)) { + echo "Correo inválido."; + return; + } + + $query = "INSERT INTO correo_respaldo (id_usuario, correo) VALUES (?, ?)"; + $stmt = sqlsrv_prepare($conn, $query, [$id_usuario, $correo]); + + if ($stmt && sqlsrv_execute($stmt)) { + header('Location: /IMPORTADORES/seguridad/index'); + exit; + } else { + echo "Error al registrar el correo extra."; + } +} + +function obtenerCorreos($conn, $id_usuario) +{ + $correos = [ + 'correo_extra' => '', + 'correo_respaldo' => '' + ]; + + // Obtener correo_extra + $sql_extra = "SELECT correo FROM correo_extra WHERE id_usuario = ?"; + $stmt_extra = sqlsrv_prepare($conn, $sql_extra, [$id_usuario]); + if ($stmt_extra && sqlsrv_execute($stmt_extra)) { + if ($row = sqlsrv_fetch_array($stmt_extra, SQLSRV_FETCH_ASSOC)) { + $correos['correo_extra'] = $row['correo']; + } + } + + // Obtener correo_respaldo + $sql_respaldo = "SELECT correo FROM correo_respaldo WHERE id_usuario = ?"; + $stmt_respaldo = sqlsrv_prepare($conn, $sql_respaldo, [$id_usuario]); + if ($stmt_respaldo && sqlsrv_execute($stmt_respaldo)) { + if ($row = sqlsrv_fetch_array($stmt_respaldo, SQLSRV_FETCH_ASSOC)) { + $correos['correo_respaldo'] = $row['correo']; + } + } + + return $correos; } \ No newline at end of file diff --git a/emails_tables.txt b/emails_tables.txt index 87718c8..522cc39 100644 --- a/emails_tables.txt +++ b/emails_tables.txt @@ -6,4 +6,20 @@ CREATE TABLE verificaciones ( tipo VARCHAR(50) DEFAULT 'login', creado_en DATETIME DEFAULT GETDATE(), FOREIGN KEY (id_usuario) REFERENCES usuarios_sistema(id_usuario) -); \ No newline at end of file +); + +CREATE TABLE correo_extra ( + id INT IDENTITY(1,1) PRIMARY KEY, + id_usuario INT NOT NULL, + correo VARCHAR(255) NOT NULL, + fecha_registro DATETIME DEFAULT GETDATE(), + FOREIGN KEY (id_usuario) REFERENCES usuarios_sistema(id_usuario) +); + +CREATE TABLE correo_respaldo ( + id INT IDENTITY(1,1) PRIMARY KEY, + id_usuario INT NOT NULL, + correo VARCHAR(255) NOT NULL, + fecha_registro DATETIME DEFAULT GETDATE(), + FOREIGN KEY (id_usuario) REFERENCES usuarios_sistema(id_usuario) +); diff --git a/views/login/verificar_codigo.php b/views/login/verificar_codigo.php index 7c0ceb6..1631935 100644 --- a/views/login/verificar_codigo.php +++ b/views/login/verificar_codigo.php @@ -139,10 +139,15 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
- +
+ + +
+ +
@@ -215,6 +220,16 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue }); }); }); + + document.getElementById("btnReenviarRespaldo").addEventListener("click", () => { + fetch("/IMPORTADORES/login/reenviarCodigo", { + method: "POST", + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: "accion=reenviarCodigo" + }) + .then(response => response.json()) + .then(data => alert(data.message)); + }); diff --git a/views/partials/sidebar_configuracion.php b/views/partials/sidebar_configuracion.php index a38f504..7a65b24 100644 --- a/views/partials/sidebar_configuracion.php +++ b/views/partials/sidebar_configuracion.php @@ -55,6 +55,16 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
- - - - 📖 Bitácoras - 1 - - + @@ -118,6 +138,7 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador 1 + @@ -144,16 +165,41 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador - - 🦺 Seguridad - + + + + role="button" aria-expanded="true" aria-controls="submenuSeguridad" + onclick=""> + 👮 Seguridad + 1 + + + + + 👮 Seguridad + 1 + + + + href="" + + role="button" aria-expanded="true" aria-controls="submenuBitacoras" + onclick=""> 📖 Bitácoras 1 @@ -165,14 +211,7 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador - - - - 📖 Bitácoras - 1 - - + diff --git a/views/seguridad/agregar.php b/views/seguridad/agregar.php new file mode 100644 index 0000000..00a4379 --- /dev/null +++ b/views/seguridad/agregar.php @@ -0,0 +1,123 @@ + + + + + + Dashboard | Configuración + + + + + + + +
+

👮 Opciones de Seguridad

+ +
+ + +
+
+

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/dashboard_seguridad.php b/views/seguridad/dashboard_seguridad.php index 5da87e0..b9bd3d6 100644 --- a/views/seguridad/dashboard_seguridad.php +++ b/views/seguridad/dashboard_seguridad.php @@ -79,21 +79,17 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
-

🦺 Seguridad

+

👮 Seguridad

Recibe Notificaciones

-

Agrega un correo adicional para recibir notificaciones.

-
+
- - -
-
- + +
@@ -102,14 +98,10 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';

Correo de Respaldo

-

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

-
+
- - -
-
- + +