Files
MVE/app/controllers/reset.php
2025-07-09 16:14:22 -06:00

624 lines
24 KiB
PHP

<?php
require_once __DIR__ . '/../helpers/session.php';
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../helpers/crypto.php';
require_once __DIR__ . '/../helpers/bitacoras.php';
require_once __DIR__ . '/../helpers/env.php';
require_once __DIR__ . '/../../vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
loadEnv();
function enviarCodigoInterno()
{
// header('Content-Type: application/json');
// Verificar que el usuario esté autenticado
if (!isset($_SESSION['usuario_id']) || !isset($_SESSION['usuario_email'])) {
echo json_encode(['success' => 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 = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
<div style='max-width: 600px; margin: auto; background: #fff; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;'>
<div style='background: linear-gradient(to right, #003366, #0055A5); padding: 20px; text-align: center;'>
<h2 style='color: white;'>Cambio de contraseña</h2>
</div>
<div style='padding: 30px; color: #333; font-size: 16px;'>
<p>Has solicitado cambiar tu contraseña desde tu cuenta.</p>
<div style='text-align: center; margin: 30px 0;'>
<p>Tu código de verificación es:</p>
<h2 style='color:#333; background: #f8f9fa; padding: 15px; border-radius: 8px; border: 2px dashed #0055A5;'>$codigo</h2>
</div>
<p>Este código expirará en 10 minutos.</p>
<p><strong>Si no solicitaste este cambio, ignora este correo.</strong></p>
</div>
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #666;'>
&copy; " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos
</div>
</div>
</div>";
$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 = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
<div style='max-width: 600px; margin: auto; background: #fff; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;'>
<div style='background: linear-gradient(to right, #003366, #0055A5); padding: 20px; text-align: center;'>
<h2 style='color: white;'>Cambio de contraseña</h2>
</div>
<div style='padding: 30px; color: #333; font-size: 16px;'>
<p>Has solicitado cambiar tu contraseña desde tu cuenta.</p>
<div style='text-align: center; margin: 30px 0;'>
<p>Tu código de verificación es:</p>
<h2 style='color:#333; background: #f8f9fa; padding: 15px; border-radius: 8px; border: 2px dashed #0055A5;'>$codigo</h2>
</div>
<p>Este código expirará en 10 minutos.</p>
<p><strong>Si no solicitaste este cambio, ignora este correo.</strong></p>
</div>
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #666;'>
&copy; " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos
</div>
</div>
</div>";
$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()
{
// Limpiar buffer de salida
if (ob_get_level()) {
ob_end_clean();
}
// Iniciar buffer limpio
ob_start();
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'])) {
ob_end_clean(); // Limpiar buffer antes de enviar JSON
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($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 = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
<div style='max-width: 600px; margin: auto; background: #fff; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;'>
<div style='background: linear-gradient(to right, #003366, #0055A5); padding: 20px; text-align: center;'>
<h2 style='color: white;'>Contraseña actualizada</h2>
</div>
<div style='padding: 30px; color: #333; font-size: 16px;'>
<p>Hola,</p>
<p>Te confirmamos que tu contraseña ha sido <strong>cambiada exitosamente</strong> desde tu cuenta.</p>
<p><strong>Fecha y hora:</strong> {$fechaHora}</p>
<p><strong>IP:</strong> {$ip}</p>
<div style='background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0;'>
<p style='margin: 0; color: #856404;'><strong>⚠️ Importante:</strong> Si no realizaste este cambio, contacta inmediatamente al área de soporte.</p>
</div>
</div>
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #666;'>
&copy; " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos
</div>
</div>
</div>";
$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']);
// Al final, antes de cada echo json_encode:
ob_end_clean();
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;
}