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 reenviarCodigoInterno() { header('Content-Type: application/json'); // Validar sesión y datos requeridos if (!isset($_SESSION['usuario_id'], $_SESSION['usuario_email'])) { echo json_encode(['success' => false, 'message' => '⚠️ Sesión expirada. Vuelve a ingresar tu correo.']); 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']; $emailEncrypted = encrypt($_SESSION['usuario_email']); // 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.']); } exit; } function enviarCorreoRespaldo($conn, $id_usuario, $codigo) { // Obtener correo de respaldo del usuario $sql = "SELECT correo FROM correo_respaldo WHERE id_usuario = ?"; $stmt = sqlsrv_query($conn, $sql, [$id_usuario]); if (!$stmt || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { return false; } $correoRespaldo = $row['correo']; if (empty($correoRespaldo)) { return false; } // Enviar código por correo al correo de respaldo 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($correoRespaldo); $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(); return true; } catch (Exception $e) { error_log("Error al enviar correo de respaldo: {$mail->ErrorInfo}"); return false; } } 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; }