@@ -820,85 +817,189 @@ function enviarNotificacionSeguridadIntentos($emailDestino, $nombreUsuario, $dat
}
}
-/** Función para enviar el email de notificación de cuenta bloqueado **/
-function enviarNotificacionCuentaBloqueada($email, $esCorreoAdicional = false)
+/** Función para enviar el email de notificación de cuenta bloqueada **/
+function enviarNotificacionCuentaBloqueada($email)
{
$conn = getConnection();
- if (!$conn) throw new Exception("No se pudo conectar a la base de datos");
+ if (!$conn) {
+ throw new Exception("No se pudo conectar a la base de datos");
+ }
+ // Buscar información del usuario por email (incluyendo correo extra)
$emailEncrypted = encrypt($email);
- $sqlNotif = "SELECT nombre, email FROM usuarios_sistema WHERE email = ?";
- $stmt = sqlsrv_query($conn, $sqlNotif, [$emailEncrypted]);
+ $sqlNotif = "
+ SELECT
+ u.nombre,
+ u.email,
+ u.notificaciones,
+ u.notificaciones_extra,
+ ce.correo as correo_extra
+ FROM usuarios_sistema u
+ LEFT JOIN correo_extra ce ON u.id_usuario = ce.id_usuario
+ WHERE u.email = ?";
- if (!$stmt) throw new Exception("Error al consultar información del usuario");
+ $stmtUsuario = sqlsrv_query($conn, $sqlNotif, [$emailEncrypted]);
+
+ if ($stmtUsuario === false) {
+ throw new Exception("Error al consultar información del usuario");
+ }
- $usuario = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
- sqlsrv_free_stmt($stmt);
+ $usuario = sqlsrv_fetch_array($stmtUsuario, SQLSRV_FETCH_ASSOC);
+ sqlsrv_free_stmt($stmtUsuario);
if (!$usuario) {
error_log("⚠️ Usuario no encontrado para enviar notificación de cuenta bloqueada");
return false;
}
+ // 🔐 Desencriptar datos sensibles
$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;
+ // Preparar datos para la notificación
+ $datosNotificacion = [
+ 'email' => $usuario['email'],
+ 'nombre' => $usuario['nombre'],
+ 'fecha_hora' => date('Y-m-d H:i:s'),
+ 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'Desconocida',
+ 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Desconocido'
+ ];
- // Configuración de correo
- $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
- $mail->addAddress($usuario['email']);
- $mail->CharSet = 'UTF-8';
- $mail->isHTML(true);
+ $resultadoEnvio = false;
- $asunto = "🚫 Cuenta Bloqueada por Seguridad";
- $mail->Subject = $asunto;
+ // Enviar al correo principal
+ $resultadoPrincipal = enviarEmailCuentaBloqueada(
+ $usuario['email'],
+ $usuario['nombre'],
+ $datosNotificacion
+ );
- $tipoCorreo = $esCorreoAdicional ? "(Correo Adicional)" : "";
+ if ($resultadoPrincipal) {
+ $resultadoEnvio = true;
+ error_log("✅ Notificación de cuenta bloqueada enviada a: {$usuario['email']}");
+ }
- $mail->Body = "
-
+ // Enviar al correo adicional si está configurado
+ if ($usuario['notificaciones_extra'] == 1 && !empty($usuario['correo_extra'])) {
+ $resultadoExtra = enviarEmailCuentaBloqueada(
+ $usuario['correo_extra'],
+ $usuario['nombre'],
+ $datosNotificacion,
+ true // Indicar que es correo adicional
+ );
+
+ if ($resultadoExtra) {
+ $resultadoEnvio = true;
+ error_log("✅ Notificación de cuenta bloqueada enviada a correo adicional: {$usuario['correo_extra']}");
+ }
+ }
+
+ return $resultadoEnvio;
+}
+
+/** Función auxiliar para enviar el email físico de cuenta bloqueada **/
+function enviarEmailCuentaBloqueada($emailDestino, $nombreUsuario, $datos, $esCorreoAdicional = false)
+{
+ try {
+ $mail = new PHPMailer(true);
+
+ // Configuración de servidor SMTP
+ $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 del correo
+ $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
+ $mail->addAddress($emailDestino);
+ $mail->CharSet = 'UTF-8';
+ $mail->isHTML(true);
+
+ $asunto = "🚫 Cuenta Bloqueada por Seguridad";
+ $mail->Subject = $asunto;
+
+ $mail->Body = "
+
-
Alerta de Seguridad
+
Cuenta Bloqueada
🚫 Cuenta Bloqueada
-
$tipoCorreo
-
-
+
-
Hola {$usuario['nombre']},
-
Tu cuenta ha sido bloqueada automáticamente tras 5 intentos fallidos al verificar tu código de recuperación.
-
Por seguridad, deberás contactar a tu agente aduanal para reactivar tu cuenta.
-
Fecha: " . date('Y-m-d H:i:s') . "
- IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'Desconocida') . "
+
Cuenta Bloqueada por Seguridad
+
Hola " . htmlspecialchars($nombreUsuario) . ",
+
Tu cuenta ha sido bloqueada automáticamente tras detectarse múltiples intentos fallidos de verificación del código de recuperación de contraseña.
-
-
+
+
+
📋 Detalles del Bloqueo:
+
+ - Fecha y Hora: " . htmlspecialchars($datos['fecha_hora']) . "
+ - Dirección IP: " . htmlspecialchars($datos['ip']) . "
+ - Navegador: " . htmlspecialchars(substr($datos['user_agent'], 0, 100)) . "...
+ - Motivo: 5 intentos fallidos de verificación de código
+
+
+
+
+
🔒 ¿Qué significa esto?
+
+ - Tu cuenta ha sido suspendida temporalmente por seguridad
+ - No podrás acceder al sistema hasta que sea reactivada
+ - Todos los códigos de recuperación han sido invalidados
+ - Este evento ha sido registrado en nuestros logs de seguridad
+
+
+
+
+
🔧 ¿Cómo reactivar tu cuenta?
+
Deberás contactar a tu agente aduanal para solicitar la reactivación de tu cuenta.
+
El agente aduanal podrá:
+
+ - Verificar tu identidad
+ - Reactivar tu cuenta de manera segura
+ - Ayudarte a establecer una nueva contraseña
+
+
+
+
+
⚠️ ¿No fuiste tú?
+
Si no intentaste recuperar tu contraseña, esto podría indicar que alguien está tratando de acceder a tu cuenta de manera no autorizada.
+
Te recomendamos:
+
+ - Contactar inmediatamente a tu agente aduanal
+ - Reportar esta actividad sospechosa
+ - Revisar tus dispositivos en busca de malware
+ - Cambiar las contraseñas de tus otras cuentas importantes
+
+
+
Esta es una notificación automática de seguridad.
- Si tienes preguntas, contacta a nuestro equipo de soporte.
+ Si tienes preguntas, contacta a tu agente aduanal o a nuestro equipo de soporte.
";
- $mail->send();
- error_log("✅ Notificación de cuenta bloqueada enviada a: {$usuario['email']}");
- return true;
+ // Enviar el correo
+ $mail->send();
+ return true;
+
+ } catch (Exception $e) {
+ error_log("❌ Error al enviar notificación de cuenta bloqueada: " . $e->getMessage());
+ return false;
+ }
}
function cambiarPasswordVista()
diff --git a/resumen_diario.php b/resumen_diario.php
new file mode 100644
index 0000000..3a4a4bd
--- /dev/null
+++ b/resumen_diario.php
@@ -0,0 +1,494 @@
+getMessage());
+ if (isset($conn)) {
+ sqlsrv_close($conn);
+ }
+ return false;
+ }
+}
+
+/** Función para verificar si es hora de enviar el resumen para un usuario específico **/
+function esHoraDeEnvio($usuario, $horaActual, $diaActual)
+{
+ $horaConfigurada = $usuario['resumen_diario_hora'] ?: '08:00';
+ $diasConfigurados = $usuario['resumen_diario_dias'] ?: 'L-V';
+
+ // Verificar hora (con margen de 5 minutos)
+ $horaActualMinutos = horaAMinutos($horaActual);
+ $horaConfiguradaMinutos = horaAMinutos($horaConfigurada);
+
+ if (abs($horaActualMinutos - $horaConfiguradaMinutos) > 5) {
+ return false;
+ }
+
+ // Verificar día
+ if ($diasConfigurados === 'TODOS') {
+ return true;
+ } elseif ($diasConfigurados === 'L-V') {
+ return in_array($diaActual, ['L', 'M', 'X', 'J', 'V']);
+ } else {
+ // Días personalizados
+ $diasArray = array_map('trim', explode(',', $diasConfigurados));
+ return in_array($diaActual, $diasArray);
+ }
+}
+
+/** Función auxiliar para convertir hora a minutos **/
+function horaAMinutos($hora)
+{
+ list($h, $m) = explode(':', $hora);
+ return ($h * 60) + $m;
+}
+
+/** Función auxiliar para obtener el día de la semana **/
+function obtenerDiaSemana()
+{
+ $dias = ['D', 'L', 'M', 'X', 'J', 'V', 'S'];
+ return $dias[date('w')];
+}
+
+/** Función para enviar el resumen diario a un usuario específico **/
+function enviarResumenDiarioUsuario($usuario, $fechaFiltro)
+{
+ try {
+ // Obtener datos del importador
+ $datosImportador = obtenerDatosImportador($usuario['id_usuario']);
+ if (!$datosImportador) {
+ error_log("⚠️ No se encontraron datos del importador para usuario ID: {$usuario['id_usuario']}");
+ return false;
+ }
+
+ // Obtener operaciones del día anterior
+ $operaciones = obtenerOperacionesDiarias($usuario['id_usuario'], $fechaFiltro);
+
+ // Si no hay operaciones, no enviar resumen (opcional: enviar notificación de "sin actividad")
+ if (empty($operaciones)) {
+ error_log("ℹ️ Sin operaciones para enviar resumen a: {$usuario['email']} en fecha: $fechaFiltro");
+ return false;
+ }
+
+ // Generar PDF
+ $rutaPdf = generarPdfResumenDiario($datosImportador, $operaciones, $fechaFiltro);
+ if (!$rutaPdf) {
+ error_log("❌ Error al generar PDF para usuario: {$usuario['email']}");
+ return false;
+ }
+
+ $resultadoEnvio = false;
+
+ // Enviar al correo principal
+ $resultadoPrincipal = enviarEmailResumenDiario(
+ $usuario['email'],
+ $usuario['nombre'],
+ $datosImportador,
+ $fechaFiltro,
+ count($operaciones),
+ $rutaPdf
+ );
+
+ if ($resultadoPrincipal) {
+ $resultadoEnvio = true;
+ error_log("✅ Resumen diario enviado a: {$usuario['email']}");
+ }
+
+ // Enviar al correo adicional si está configurado
+ if ($usuario['notificaciones_extra'] == 1 && !empty($usuario['correo_extra'])) {
+ $resultadoExtra = enviarEmailResumenDiario(
+ $usuario['correo_extra'],
+ $usuario['nombre'],
+ $datosImportador,
+ $fechaFiltro,
+ count($operaciones),
+ $rutaPdf,
+ true
+ );
+
+ if ($resultadoExtra) {
+ $resultadoEnvio = true;
+ error_log("✅ Resumen diario enviado a correo adicional: {$usuario['correo_extra']}");
+ }
+ }
+
+ // Limpiar archivo temporal
+ if (file_exists($rutaPdf)) {
+ unlink($rutaPdf);
+ }
+
+ return $resultadoEnvio;
+
+ } catch (Exception $e) {
+ error_log("❌ Error al procesar resumen diario para usuario {$usuario['email']}: " . $e->getMessage());
+ return false;
+ }
+}
+
+/** Función para obtener datos del importador **/
+function obtenerDatosImportador($idUsuario)
+{
+ $conn = getConnection();
+ if (!$conn) {
+ return null;
+ }
+
+ $sql = "
+ SELECT
+ ig.nombre,
+ ig.rfc,
+ CONCAT(ig.calle, ' ',
+ CASE WHEN ig.num_exterior IS NOT NULL THEN CONCAT('#', ig.num_exterior) ELSE '' END,
+ CASE WHEN ig.num_interior IS NOT NULL AND ig.num_interior != '' THEN CONCAT(' Int. ', ig.num_interior) ELSE '' END,
+ CASE WHEN ig.colonia IS NOT NULL AND ig.colonia != '' THEN CONCAT(', ', ig.colonia) ELSE '' END,
+ CASE WHEN ig.ciudad IS NOT NULL AND ig.ciudad != '' THEN CONCAT(', ', ig.ciudad) ELSE '' END,
+ CASE WHEN ig.estado IS NOT NULL AND ig.estado != '' THEN CONCAT(', ', ig.estado) ELSE '' END,
+ CASE WHEN ig.codigo_postal IS NOT NULL THEN CONCAT(' C.P. ', ig.codigo_postal) ELSE '' END
+ ) as direccion,
+ ig.correo as correo_contacto,
+ ig.telefono,
+ u.email as email_usuario
+ FROM informacion_general ig
+ INNER JOIN usuarios_sistema u ON ig.id_usuario = u.id_usuario
+ WHERE u.id_usuario = ?
+ ";
+
+ $stmt = sqlsrv_prepare($conn, $sql, [$idUsuario]);
+ if (!$stmt || !sqlsrv_execute($stmt)) {
+ sqlsrv_close($conn);
+ return null;
+ }
+
+ $datos = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
+ sqlsrv_free_stmt($stmt);
+ sqlsrv_close($conn);
+
+ if ($datos) {
+ // Desencriptar datos sensibles
+ $datos['nombre_importador'] = decrypt($datos['nombre']); // Mapear nombre a nombre_importador para compatibilidad
+ $datos['direccion'] = $datos['direccion']; // Ya concatenada en la consulta
+ $datos['correo_contacto'] = decrypt($datos['correo_contacto']);
+ $datos['email_usuario'] = decrypt($datos['email_usuario']);
+ $datos['rfc'] = $datos['rfc']; // RFC generalmente no se encripta
+ }
+
+ return $datos;
+}
+
+/** Función para obtener operaciones diarias **/
+function obtenerOperacionesDiarias($idUsuario, $fecha)
+{
+ $conn = getConnection();
+ if (!$conn) {
+ return [];
+ }
+
+ $sql = "
+ SELECT
+ sif.numero_factura,
+ sif.valor_factura,
+ sif.tipo_moneda,
+ sif.fecha_factura,
+ sif.numero_pedimento,
+ sif.pais_proveedor,
+ sif.aduana,
+ CASE
+ WHEN sif.status = 0 THEN 'Pendiente'
+ WHEN sif.status = 1 THEN 'En Proceso'
+ WHEN sif.status = 2 THEN 'Completado'
+ WHEN sif.status = 3 THEN 'Cancelado'
+ ELSE 'Sin Estado'
+ END as status
+ FROM solicitud_importacion_factura sif
+ INNER JOIN usuarios_sistema u ON sif.id_importador = u.id_importador
+ WHERE u.id_usuario = ?
+ AND CAST(sif.fecha_factura AS DATE) = ?
+ ORDER BY sif.fecha_factura DESC
+ ";
+
+ $stmt = sqlsrv_prepare($conn, $sql, [$idUsuario, $fecha]);
+ if (!$stmt || !sqlsrv_execute($stmt)) {
+ sqlsrv_close($conn);
+ return [];
+ }
+
+ $operaciones = [];
+ while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
+ // Desencriptar datos si es necesario
+ $row['numero_factura'] = decrypt($row['numero_factura']);
+ $row['numero_pedimento'] = $row['numero_pedimento'] ? decrypt($row['numero_pedimento']) : 'N/A';
+ $operaciones[] = $row;
+ }
+
+ sqlsrv_free_stmt($stmt);
+ sqlsrv_close($conn);
+
+ return $operaciones;
+}
+
+/** Función para generar el PDF del resumen **/
+function generarPdfResumenDiario($datosImportador, $operaciones, $fecha)
+{
+ require_once 'vendor/autoload.php'; // Asumiendo que usas TCPDF o similar
+
+ try {
+ $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
+
+ // Configuración del documento
+ $pdf->SetCreator('SIIH | AduanaSoft');
+ $pdf->SetAuthor('Sistema de Importaciones');
+ $pdf->SetTitle('Resumen Diario de Importaciones - ' . $fecha);
+ $pdf->SetSubject('Resumen de Operaciones');
+
+ // Configurar margenes
+ $pdf->SetMargins(15, 20, 15);
+ $pdf->SetHeaderMargin(10);
+ $pdf->SetFooterMargin(10);
+
+ // Agregar página
+ $pdf->AddPage();
+
+ // Encabezado
+ $html = '
📊 Resumen Diario de Importaciones
';
+ $html .= '
Fecha: ' . date('d/m/Y', strtotime($fecha)) . '
';
+
+ // Datos del importador
+ $html .= '
';
+ $html .= '
🏢 Datos del Importador
';
+ $html .= '
Nombre: ' . htmlspecialchars($datosImportador['nombre_importador']) . '
';
+ $html .= '
RFC: ' . htmlspecialchars($datosImportador['rfc'] ?? 'N/A') . '
';
+ $html .= '
Dirección: ' . htmlspecialchars($datosImportador['direccion']) . '
';
+ $html .= '
Correo: ' . htmlspecialchars($datosImportador['correo_contacto']) . '
';
+ $html .= '
';
+
+ // Resumen estadístico
+ $totalOperaciones = count($operaciones);
+ $valorTotal = array_sum(array_column($operaciones, 'valor_factura'));
+
+ $html .= '
';
+ $html .= '
📈 Resumen Estadístico
';
+ $html .= '
Total de Operaciones: ' . $totalOperaciones . '
';
+ $html .= '
Valor Total: $' . number_format($valorTotal, 2) . '
';
+ $html .= '
';
+
+ // Tabla de operaciones
+ $html .= '
📋 Detalle de Operaciones
';
+ $html .= '
';
+ $html .= '';
+ $html .= '';
+ $html .= '| No. Factura | ';
+ $html .= 'Valor | ';
+ $html .= 'Moneda | ';
+ $html .= 'Estado | ';
+ $html .= 'Fecha | ';
+ $html .= '
';
+ $html .= '';
+ $html .= '';
+
+ foreach ($operaciones as $op) {
+ $html .= '';
+ $html .= '| ' . htmlspecialchars($op['numero_factura']) . ' | ';
+ $html .= '$' . number_format($op['valor_factura'], 2) . ' | ';
+ $html .= '' . htmlspecialchars($op['tipo_moneda']) . ' | ';
+ $html .= '' . htmlspecialchars($op['status']) . ' | ';
+ $html .= '' . date('d/m/Y H:i', strtotime($op['fecha_factura'])) . ' | ';
+ $html .= '
';
+ }
+
+ $html .= '';
+ $html .= '
';
+
+ // Pie del documento
+ $html .= '
';
+ $html .= '
Este documento fue generado automáticamente el ' . date('d/m/Y H:i:s') . '
';
+ $html .= '
SIIH | AduanaSoft - Sistema Integral de Importaciones
';
+ $html .= '
';
+
+ $pdf->writeHTML($html, true, false, true, false, '');
+
+ // Generar nombre único para el archivo
+ $nombreArchivo = 'resumen_diario_' . $fecha . '_' . uniqid() . '.pdf';
+ $rutaCompleta = sys_get_temp_dir() . '/' . $nombreArchivo;
+
+ $pdf->Output($rutaCompleta, 'F');
+
+ return $rutaCompleta;
+
+ } catch (Exception $e) {
+ error_log("❌ Error al generar PDF: " . $e->getMessage());
+ return false;
+ }
+}
+
+/** Función para enviar el email con el resumen **/
+function enviarEmailResumenDiario($emailDestino, $nombreUsuario, $datosImportador, $fecha, $totalOperaciones, $rutaPdf, $esCorreoAdicional = false)
+{
+ try {
+ $mail = new PHPMailer(true);
+
+ // Configuración de servidor SMTP
+ $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 del correo
+ $mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
+ $mail->addAddress($emailDestino);
+ $mail->CharSet = 'UTF-8';
+ $mail->isHTML(true);
+
+ // Adjuntar PDF
+ $mail->addAttachment($rutaPdf, 'resumen_diario_' . $fecha . '.pdf');
+
+ $fechaFormateada = date('d/m/Y', strtotime($fecha));
+
+ $asunto = "📊 Resumen Diario de Importaciones - $fechaFormateada$tipoCorreo";
+ $mail->Subject = $asunto;
+
+ $mail->Body = "
+
+
+
+
Resumen Diario de Importaciones
+
+
+
+
+
📊 Resumen Diario de Importaciones
+ $fechaFormateada
+
+
+
+
Hola " . htmlspecialchars($nombreUsuario) . ",
+
Te enviamos el resumen de tus operaciones de importación correspondientes al día $fechaFormateada.
+
+
+
+
🏢 Importador: " . htmlspecialchars($datosImportador['nombre_importador']) . "
+
📧 Correo: " . htmlspecialchars($datosImportador['correo_contacto']) . "
+
📍 Dirección: " . htmlspecialchars($datosImportador['direccion']) . "
+
+
+
+
📈 Resumen del Día
+
Total de Operaciones: $totalOperaciones
+
📎 Adjunto: Reporte detallado en formato PDF con todas tus operaciones
+
+
+
+
📋 ¿Qué incluye el reporte?
+
+ - Número de factura de cada operación
+ - Valor y moneda de las facturas
+ - Estado actual de cada solicitud
+ - Fecha y hora de registro
+ - Resumen estadístico del día
+
+
+
+
+
⚙️ Configurar Notificaciones
+
Puedes modificar la frecuencia y horario de estos resúmenes desde tu panel de Preferencias de Notificaciones en el sistema.
+
+
+
+
+ Este resumen se genera automáticamente según tus preferencias de notificación.
+ SIIH | AduanaSoft - Sistema Integral de Importaciones
+ Generado el " . date('d/m/Y H:i:s') . "
+
+
+
+
+ ";
+
+ // Enviar el correo
+ $mail->send();
+ return true;
+
+ } catch (Exception $e) {
+ error_log("❌ Error al enviar resumen diario a $emailDestino: " . $e->getMessage());
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/views/preferencias/notificaciones.php b/views/preferencias/notificaciones.php
index 0d50b0c..c3afa59 100644
--- a/views/preferencias/notificaciones.php
+++ b/views/preferencias/notificaciones.php
@@ -416,6 +416,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';