Bloqueo de cuenta y config. resumen
This commit is contained in:
@@ -544,49 +544,50 @@ function verificarCodigo()
|
|||||||
sqlsrv_free_stmt($stmt);
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
if (!$row) {
|
if (!$row) {
|
||||||
echo json_encode(['success' => false, 'message' => '❌ Código no encontrado o ya usado.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manejo de intentos fallidos en sesión
|
|
||||||
if (!isset($_SESSION['intentos_codigo'])) {
|
|
||||||
$_SESSION['intentos_codigo'] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION['intentos_codigo']++;
|
|
||||||
|
|
||||||
// Verificar si se han excedido los intentos
|
|
||||||
if ($_SESSION['intentos_codigo'] >= 5) {
|
|
||||||
// Bloquear el código en la base de datos
|
|
||||||
$sqlUpdate = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?";
|
|
||||||
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$row['id']]);
|
|
||||||
|
|
||||||
if ($stmtUpdate) {
|
|
||||||
sqlsrv_free_stmt($stmtUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🚨 ENVIAR NOTIFICACIÓN DE SEGURIDAD
|
|
||||||
try {
|
|
||||||
enviarNotificacionIntentosExcedidos($email);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("❌ Error al enviar notificación de intentos excedidos: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpiar sesión
|
|
||||||
unset($_SESSION['intentos_codigo']);
|
|
||||||
unset($_SESSION['email_recuperacion']);
|
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => '🚫 Has excedido el número de intentos permitidos. Por seguridad, se ha bloqueado el código. Solicita uno nuevo.',
|
'message' => '🚫 Has excedido el número de intentos permitidos. Tu cuenta ha sido bloqueada por seguridad. Contacta a tu agente aduanal.',
|
||||||
'blocked' => true
|
'blocked' => true,
|
||||||
|
'redirect' => '/IMPORTADORES/login'
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manejo de intentos fallidos en sesión
|
||||||
|
if (!isset($_SESSION['intentos_codigo']) || !is_numeric($_SESSION['intentos_codigo'])) {
|
||||||
|
$_SESSION['intentos_codigo'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Compara códigos - asegurando que no haya problemas con mayúsculas o espacios
|
// Compara códigos - asegurando que no haya problemas con mayúsculas o espacios
|
||||||
if (strcasecmp($codigo, $row['codigo']) !== 0) {
|
if (strcasecmp($codigo, $row['codigo']) !== 0) {
|
||||||
$intentosRestantes = 5 - $_SESSION['intentos_codigo'];
|
$_SESSION['intentos_codigo']++; // ⬅️ Incrementa solo si es incorrecto
|
||||||
|
$intentosRestantes = max(0, 5 - $_SESSION['intentos_codigo']);
|
||||||
|
|
||||||
|
// Verificar si se han excedido los intentos
|
||||||
|
if ($_SESSION['intentos_codigo'] >= 5) {
|
||||||
|
// Bloquear el código
|
||||||
|
$sqlUpdate = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?";
|
||||||
|
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$row['id']]);
|
||||||
|
if ($stmtUpdate) sqlsrv_free_stmt($stmtUpdate);
|
||||||
|
|
||||||
|
// Bloquear la cuenta del usuario
|
||||||
|
$sqlBloquearUsuario = "UPDATE usuarios_sistema SET activo = 0 WHERE email = ?";
|
||||||
|
$stmtBloqueo = sqlsrv_query($conn, $sqlBloquearUsuario, [$emailEncrypted]);
|
||||||
|
if ($stmtBloqueo) sqlsrv_free_stmt($stmtBloqueo);
|
||||||
|
|
||||||
|
// Enviar notificación
|
||||||
|
try {
|
||||||
|
enviarNotificacionCuentaBloqueada($email);
|
||||||
|
enviarNotificacionIntentosExcedidos($email);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("❌ Error al enviar notificación de cuenta bloqueada: " . $e->getMessage());
|
||||||
|
error_log("❌ Error al enviar notificación de intentos excedidos: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($_SESSION['intentos_codigo']);
|
||||||
|
unset($_SESSION['email_recuperacion']);
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => "❌ Código incorrecto. Te quedan {$intentosRestantes} intentos.",
|
'message' => "❌ Código incorrecto. Te quedan {$intentosRestantes} intentos.",
|
||||||
@@ -819,6 +820,87 @@ function enviarNotificacionSeguridadIntentos($emailDestino, $nombreUsuario, $dat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Función para enviar el email de notificación de cuenta bloqueado **/
|
||||||
|
function enviarNotificacionCuentaBloqueada($email, $esCorreoAdicional = false)
|
||||||
|
{
|
||||||
|
$conn = getConnection();
|
||||||
|
if (!$conn) throw new Exception("No se pudo conectar a la base de datos");
|
||||||
|
|
||||||
|
$emailEncrypted = encrypt($email);
|
||||||
|
$sqlNotif = "SELECT nombre, email FROM usuarios_sistema WHERE email = ?";
|
||||||
|
$stmt = sqlsrv_query($conn, $sqlNotif, [$emailEncrypted]);
|
||||||
|
|
||||||
|
if (!$stmt) throw new Exception("Error al consultar información del usuario");
|
||||||
|
|
||||||
|
$usuario = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
|
if (!$usuario) {
|
||||||
|
error_log("⚠️ Usuario no encontrado para enviar notificación de cuenta bloqueada");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usuario['email'] = decrypt($usuario['email']);
|
||||||
|
$usuario['nombre'] = decrypt($usuario['nombre']);
|
||||||
|
|
||||||
|
$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;
|
||||||
|
|
||||||
|
// Configuración de correo
|
||||||
|
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
|
||||||
|
$mail->addAddress($usuario['email']);
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
$mail->isHTML(true);
|
||||||
|
|
||||||
|
$asunto = "🚫 Cuenta Bloqueada por Seguridad";
|
||||||
|
$mail->Subject = $asunto;
|
||||||
|
|
||||||
|
$tipoCorreo = $esCorreoAdicional ? "(Correo Adicional)" : "";
|
||||||
|
|
||||||
|
$mail->Body = "
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<title>Alerta de Seguridad</title>
|
||||||
|
</head>
|
||||||
|
<body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>
|
||||||
|
<div style='max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 10px;'>
|
||||||
|
<div style='text-align: center; margin-bottom: 30px;'>
|
||||||
|
<h1 style='color: #dc3545; margin: 0;'>🚫 Cuenta Bloqueada</h1>
|
||||||
|
<p style='color: #666; margin: 5px 0;'>$tipoCorreo</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div style='background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;'>
|
||||||
|
<p>Hola <b>{$usuario['nombre']}</b>,</p>
|
||||||
|
<p>Tu cuenta ha sido bloqueada automáticamente tras 5 intentos fallidos al verificar tu código de recuperación.</p>
|
||||||
|
<p>Por seguridad, deberás contactar a tu agente aduanal para reactivar tu cuenta.</p>
|
||||||
|
<p>Fecha: <b>" . date('Y-m-d H:i:s') . "</b><br>
|
||||||
|
IP: <b>" . ($_SERVER['REMOTE_ADDR'] ?? 'Desconocida') . "</b></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div style='text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee;'>
|
||||||
|
<p style='color: #666; font-size: 12px; margin: 0;'>
|
||||||
|
Esta es una notificación automática de seguridad.<br>
|
||||||
|
Si tienes preguntas, contacta a nuestro equipo de soporte.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
error_log("✅ Notificación de cuenta bloqueada enviada a: {$usuario['email']}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function cambiarPasswordVista()
|
function cambiarPasswordVista()
|
||||||
{
|
{
|
||||||
if (!isset($_SESSION['recuperacion_autorizada'])) {
|
if (!isset($_SESSION['recuperacion_autorizada'])) {
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ function notificaciones()
|
|||||||
} else {
|
} else {
|
||||||
$preferenciasRow = false;
|
$preferenciasRow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Después de obtener $preferenciasRow, antes de crear el array $preferencias
|
||||||
|
if ($preferenciasRow && isset($preferenciasRow['resumen_diario_hora'])) {
|
||||||
|
// Formatear la hora para el input HTML
|
||||||
|
$hora_obj = $preferenciasRow['resumen_diario_hora'];
|
||||||
|
if ($hora_obj instanceof DateTime) {
|
||||||
|
$preferenciasRow['resumen_diario_hora'] = $hora_obj->format('H:i');
|
||||||
|
} elseif (is_string($hora_obj)) {
|
||||||
|
// Si viene como string "08:00:00.0000000", extraer solo HH:MM
|
||||||
|
$preferenciasRow['resumen_diario_hora'] = substr($hora_obj, 0, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Si no existe registro, crear valores por defecto
|
// Si no existe registro, crear valores por defecto
|
||||||
if (!$preferenciasRow) {
|
if (!$preferenciasRow) {
|
||||||
@@ -360,8 +372,8 @@ function guardarPreferenciasAjax()
|
|||||||
sqlsrv_free_stmt($stmt);
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
$response_data = [
|
$response_data = [
|
||||||
'resumen_hora' => $hora,
|
'resumen_diario_hora' => $hora,
|
||||||
'resumen_dias' => $dias
|
'resumen_diario_dias' => $dias
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -424,6 +424,13 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
|||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
if (data.blocked) {
|
||||||
|
mostrarError(data.message); // <-- función personalizada con un div bonito
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = data.redirect;
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Código correcto
|
// Código correcto
|
||||||
mostrarExito(data.message);
|
mostrarExito(data.message);
|
||||||
@@ -454,6 +461,16 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mostrar error de cuenta bloqueada
|
||||||
|
function mostrarError(mensaje) {
|
||||||
|
const mensajeDiv = document.getElementById('mensaje');
|
||||||
|
mensajeDiv.innerHTML = `
|
||||||
|
<div class="text-danger fade-in">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i> ${mensaje}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// Manejar código incorrecto
|
// Manejar código incorrecto
|
||||||
function manejarCodigoIncorrecto(data) {
|
function manejarCodigoIncorrecto(data) {
|
||||||
intentosRealizados++;
|
intentosRealizados++;
|
||||||
|
|||||||
@@ -320,7 +320,6 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
|
|||||||
<div id="resumen-diario-config" class="fade-transition" style="display: none; opacity: 0;">
|
<div id="resumen-diario-config" class="fade-transition" style="display: none; opacity: 0;">
|
||||||
<div class="resumen-config">
|
<div class="resumen-config">
|
||||||
<h6 class="mb-3">
|
<h6 class="mb-3">
|
||||||
<i class="fas fa-clock me-2"></i>
|
|
||||||
⏰ Configuración del Resumen Diario
|
⏰ Configuración del Resumen Diario
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
@@ -328,7 +327,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
|
|||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="resumen_hora" class="form-label fw-bold">Hora de envío</label>
|
<label for="resumen_hora" class="form-label fw-bold">Hora de envío</label>
|
||||||
<input type="time" class="form-control time-input" id="resumen_hora"
|
<input type="time" class="form-control time-input" id="resumen_hora"
|
||||||
name="resumen_hora" value="08:00">
|
name="resumen_hora" value="<?= $preferencias['resumen_diario_hora'] ?? '08:00' ?>">
|
||||||
<div class="form-text">Hora en que se enviará el resumen diario</div>
|
<div class="form-text">Hora en que se enviará el resumen diario</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,7 +400,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" id="resumen_dias" name="resumen_dias" value="L-V">
|
<input type="hidden" id="resumen_dias" name="resumen_dias" value="<?= $preferencias['resumen_diario_dias'] ?? 'L-V' ?>">
|
||||||
<div class="form-text">Días en que se enviará el resumen diario</div>
|
<div class="form-text">Días en que se enviará el resumen diario</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -683,6 +682,36 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
|
|||||||
});
|
});
|
||||||
|
|
||||||
// === FUNCIONALIDAD DE RESUMEN DIARIO ===
|
// === FUNCIONALIDAD DE RESUMEN DIARIO ===
|
||||||
|
// Función para mostrar/ocultar configuración del resumen diario
|
||||||
|
function toggleResumenConfig() {
|
||||||
|
const resumenCheckbox = document.getElementById('resumen_diario');
|
||||||
|
const resumenConfig = document.getElementById('resumen-diario-config');
|
||||||
|
|
||||||
|
if (resumenCheckbox.checked) {
|
||||||
|
resumenConfig.style.display = 'block';
|
||||||
|
setTimeout(() => {
|
||||||
|
resumenConfig.style.opacity = '1';
|
||||||
|
}, 10);
|
||||||
|
} else {
|
||||||
|
resumenConfig.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
resumenConfig.style.display = 'none';
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listener para el checkbox de resumen diario
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const resumenCheckbox = document.getElementById('resumen_diario');
|
||||||
|
|
||||||
|
// Verificar estado inicial al cargar la página
|
||||||
|
toggleResumenConfig();
|
||||||
|
|
||||||
|
// Agregar listener para cambios en el checkbox
|
||||||
|
resumenCheckbox.addEventListener('change', function() {
|
||||||
|
toggleResumenConfig();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Referencias específicas para resumen diario
|
// Referencias específicas para resumen diario
|
||||||
const resumenDiario = document.getElementById('resumen_diario');
|
const resumenDiario = document.getElementById('resumen_diario');
|
||||||
|
|||||||
Reference in New Issue
Block a user