From 59b9b402fbedadb6e442a0dd215df80b3a5d8c5e Mon Sep 17 00:00:00 2001 From: Alexeer Date: Fri, 30 May 2025 10:59:54 -0600 Subject: [PATCH] Resumen diario --- .env | 2 +- app/controllers/login.php | 191 +++++++--- resumen_diario.php | 494 ++++++++++++++++++++++++++ views/preferencias/notificaciones.php | 4 +- 4 files changed, 642 insertions(+), 49 deletions(-) create mode 100644 resumen_diario.php diff --git a/.env b/.env index b16b6a0..e427a09 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -DB_HOST=localhost +DB_HOST=DESKTOP-22T88B6 DB_DATABASE=Importaciones_HC DB_USERNAME=sa DB_PASSWORD=Soluciones01 diff --git a/app/controllers/login.php b/app/controllers/login.php index 9cf1196..3c818b3 100644 --- a/app/controllers/login.php +++ b/app/controllers/login.php @@ -748,9 +748,7 @@ function enviarNotificacionSeguridadIntentos($emailDestino, $nombreUsuario, $dat $asunto = "🚨 Alerta de Seguridad - Intentos de Verificación Excedidos"; $mail->Subject = $asunto; - - $tipoCorreo = $esCorreoAdicional ? "(Correo Adicional)" : ""; - + $mail->Body = " @@ -761,7 +759,6 @@ function enviarNotificacionSeguridadIntentos($emailDestino, $nombreUsuario, $dat

🚨 Alerta de Seguridad

-

$tipoCorreo

@@ -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 .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + foreach ($operaciones as $op) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + $html .= ''; + $html .= '
No. FacturaValorMonedaEstadoFecha
' . htmlspecialchars($op['numero_factura']) . '$' . number_format($op['valor_factura'], 2) . '' . htmlspecialchars($op['tipo_moneda']) . '' . htmlspecialchars($op['status']) . '' . date('d/m/Y H:i', strtotime($op['fecha_factura'])) . '
'; + + // 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';
+
@@ -434,9 +435,6 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
-
- -
📧 Correo adicional configurado: