Notificaciones Cambio de Estado de Solicitud
This commit is contained in:
@@ -8,7 +8,6 @@ require_once __DIR__ . '/../../vendor/autoload.php';
|
|||||||
require_once __DIR__ . '/../helpers/env.php';
|
require_once __DIR__ . '/../helpers/env.php';
|
||||||
loadEnv();
|
loadEnv();
|
||||||
|
|
||||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
||||||
require_once __DIR__ . '/../helpers/crypto.php';
|
require_once __DIR__ . '/../helpers/crypto.php';
|
||||||
|
|
||||||
/** Obtiene (y cachea en sesión) el JWT de la API usando las credenciales de $_ENV **/
|
/** Obtiene (y cachea en sesión) el JWT de la API usando las credenciales de $_ENV **/
|
||||||
@@ -814,9 +813,130 @@ function update_status() {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Si el status actualizado es 2, hacer POST a /pedimentos/crearPedimento
|
// 3) Obtener datos del usuario y solicitud para notificación
|
||||||
|
try {
|
||||||
|
$sqlNotif = "
|
||||||
|
SELECT
|
||||||
|
u.nombre,
|
||||||
|
u.email,
|
||||||
|
u.notificaciones,
|
||||||
|
u.notificaciones_extra,
|
||||||
|
COALESCE(p.cambio_estado, 0) as cambio_estado,
|
||||||
|
ce.correo as correo_extra,
|
||||||
|
s.numero_factura,
|
||||||
|
s.fecha_factura,
|
||||||
|
s.valor_factura,
|
||||||
|
s.tipo_moneda
|
||||||
|
FROM usuarios_sistema u
|
||||||
|
LEFT JOIN preferencias_notificaciones_usuario p ON u.id_usuario = p.id_usuario
|
||||||
|
LEFT JOIN correo_extra ce ON u.id_usuario = ce.id_usuario
|
||||||
|
INNER JOIN solicitud_importacion_factura s ON s.id_importador = u.id_usuario
|
||||||
|
WHERE u.id_usuario = ? AND s.id_solicitud = ?";
|
||||||
|
|
||||||
|
$stmtNotif = sqlsrv_prepare($conn, $sqlNotif, [$_SESSION['usuario_id'], $id]);
|
||||||
|
|
||||||
|
if ($stmtNotif && sqlsrv_execute($stmtNotif)) {
|
||||||
|
$notifConfig = sqlsrv_fetch_array($stmtNotif, SQLSRV_FETCH_ASSOC);
|
||||||
|
sqlsrv_free_stmt($stmtNotif);
|
||||||
|
|
||||||
|
// 🔐 Desencriptar datos
|
||||||
|
if ($notifConfig) {
|
||||||
|
$notifConfig['email'] = decrypt($notifConfig['email']);
|
||||||
|
$notifConfig['nombre'] = decrypt($notifConfig['nombre']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si debe enviar notificaciones de cambio de status
|
||||||
|
if ($notifConfig &&
|
||||||
|
$notifConfig['notificaciones'] == 1 &&
|
||||||
|
$notifConfig['cambio_estado'] == 1) {
|
||||||
|
|
||||||
|
// Formatear fecha si es DateTime
|
||||||
|
$fechaFactura = ($notifConfig['fecha_factura'] instanceof DateTime)
|
||||||
|
? $notifConfig['fecha_factura']->format('Y-m-d')
|
||||||
|
: $notifConfig['fecha_factura'];
|
||||||
|
|
||||||
|
// Preparar datos para la notificación
|
||||||
|
$datosSolicitud = [
|
||||||
|
'id_solicitud' => $id,
|
||||||
|
'numero_factura' => $notifConfig['numero_factura'],
|
||||||
|
'fecha_factura' => $fechaFactura,
|
||||||
|
'valor_factura' => $notifConfig['valor_factura'],
|
||||||
|
'tipo_moneda' => $notifConfig['tipo_moneda'],
|
||||||
|
'nuevo_status' => $status
|
||||||
|
];
|
||||||
|
|
||||||
|
$notificacionesEnviadas = 0;
|
||||||
|
$erroresNotificacion = [];
|
||||||
|
|
||||||
|
// Enviar al correo principal
|
||||||
|
if (!empty($notifConfig['email']) && filter_var($notifConfig['email'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$resultadoPrincipal = enviarNotificacionCambioStatus(
|
||||||
|
$notifConfig['email'],
|
||||||
|
$notifConfig['nombre'],
|
||||||
|
$datosSolicitud,
|
||||||
|
false // No es correo adicional
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($resultadoPrincipal) {
|
||||||
|
$notificacionesEnviadas++;
|
||||||
|
} else {
|
||||||
|
$erroresNotificacion[] = 'correo principal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enviar al correo adicional si está configurado
|
||||||
|
if ($notifConfig['notificaciones_extra'] == 1 &&
|
||||||
|
!empty($notifConfig['correo_extra']) &&
|
||||||
|
filter_var($notifConfig['correo_extra'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
|
||||||
|
$resultadoExtra = enviarNotificacionCambioStatus(
|
||||||
|
$notifConfig['correo_extra'],
|
||||||
|
$notifConfig['nombre'],
|
||||||
|
$datosSolicitud,
|
||||||
|
true // Es correo adicional
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($resultadoExtra) {
|
||||||
|
$notificacionesEnviadas++;
|
||||||
|
} else {
|
||||||
|
$erroresNotificacion[] = 'correo adicional';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log consolidado del resultado
|
||||||
|
if ($notificacionesEnviadas > 0) {
|
||||||
|
error_log("✅ Notificaciones de cambio de status enviadas ($notificacionesEnviadas) para solicitud ID: $id → Status: $status");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($erroresNotificacion)) {
|
||||||
|
error_log("⚠️ Errores al enviar notificaciones (" . implode(', ', $erroresNotificacion) . ") para solicitud ID: $id");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Log informativo cuando las notificaciones están deshabilitadas
|
||||||
|
$razon = [];
|
||||||
|
if (!$notifConfig) {
|
||||||
|
$razon[] = 'configuración no encontrada';
|
||||||
|
} else {
|
||||||
|
if ($notifConfig['notificaciones'] != 1) $razon[] = 'notificaciones generales deshabilitadas';
|
||||||
|
if ($notifConfig['cambio_estado'] != 1) $razon[] = 'notificaciones de cambio de estado deshabilitadas';
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("ℹ️ Usuario ID: {$_SESSION['usuario_id']} no recibirá notificación de cambio de status. Razón: " . implode(', ', $razon));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error_log("⚠️ No se pudo consultar configuración de notificaciones para usuario ID: {$_SESSION['usuario_id']} - Error SQL: " . print_r(sqlsrv_errors(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// No fallar el proceso principal por errores de notificación
|
||||||
|
error_log("❌ Error en sistema de notificaciones de cambio de status para solicitud ID: $id - " . $e->getMessage());
|
||||||
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Si el status actualizado es 2, hacer POST a /pedimentos/crearPedimento
|
||||||
if ($status === 2) {
|
if ($status === 2) {
|
||||||
// 3.1) Obtener o renovar el token de la API
|
// 4.1) Obtener o renovar el token de la API
|
||||||
$token = getApiToken();
|
$token = getApiToken();
|
||||||
if (!$token) {
|
if (!$token) {
|
||||||
error_log("[update_status] No se pudo obtener token para crearPedimento");
|
error_log("[update_status] No se pudo obtener token para crearPedimento");
|
||||||
@@ -824,7 +944,7 @@ function update_status() {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.2) Leer de la BD todos los campos de la cabecera de la solicitud
|
// 4.2) Leer de la BD todos los campos de la cabecera de la solicitud
|
||||||
$sqlSel = "
|
$sqlSel = "
|
||||||
SELECT
|
SELECT
|
||||||
s.id_solicitud,
|
s.id_solicitud,
|
||||||
@@ -864,7 +984,7 @@ function update_status() {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.3) Formatear fechas (si vienen como DateTime) antes de construir JSON
|
// 4.3) Formatear fechas (si vienen como DateTime) antes de construir JSON
|
||||||
$fechaFactura = ($solicitud['fecha_factura'] instanceof DateTime)
|
$fechaFactura = ($solicitud['fecha_factura'] instanceof DateTime)
|
||||||
? $solicitud['fecha_factura']->format('Y-m-d')
|
? $solicitud['fecha_factura']->format('Y-m-d')
|
||||||
: $solicitud['fecha_factura'];
|
: $solicitud['fecha_factura'];
|
||||||
@@ -877,7 +997,7 @@ function update_status() {
|
|||||||
? $solicitud['updated_at']->format('Y-m-d\TH:i:s')
|
? $solicitud['updated_at']->format('Y-m-d\TH:i:s')
|
||||||
: $solicitud['updated_at'];
|
: $solicitud['updated_at'];
|
||||||
|
|
||||||
// 3.4) Obtener todas las partidas asociadas a esta solicitud
|
// 4.4) Obtener todas las partidas asociadas a esta solicitud
|
||||||
$sqlPart = "
|
$sqlPart = "
|
||||||
SELECT
|
SELECT
|
||||||
p.id_partida,
|
p.id_partida,
|
||||||
@@ -914,7 +1034,7 @@ function update_status() {
|
|||||||
}
|
}
|
||||||
sqlsrv_free_stmt($stmtPart);
|
sqlsrv_free_stmt($stmtPart);
|
||||||
|
|
||||||
// 3.5) Construir el arreglo PHP con la misma estructura JSON que envías
|
// 4.5) Construir el arreglo PHP con la misma estructura JSON que envías
|
||||||
$payload = [
|
$payload = [
|
||||||
"id_solicitud" => intval($solicitud['id_solicitud']),
|
"id_solicitud" => intval($solicitud['id_solicitud']),
|
||||||
"id_importador" => intval($solicitud['id_importador']),
|
"id_importador" => intval($solicitud['id_importador']),
|
||||||
@@ -961,7 +1081,7 @@ function update_status() {
|
|||||||
$curlErr = curl_error($ch);
|
$curlErr = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
// 3.7) Verificar respuesta del endpoint pedimentos
|
// 4.7) Verificar respuesta del endpoint pedimentos
|
||||||
if ($httpPed === 201 || $httpPed === 200) {
|
if ($httpPed === 201 || $httpPed === 200) {
|
||||||
$decoded = json_decode($respPed, true);
|
$decoded = json_decode($respPed, true);
|
||||||
// Si existe PEDIMENTO dentro de la clave 'pedimento'
|
// Si existe PEDIMENTO dentro de la clave 'pedimento'
|
||||||
@@ -1002,11 +1122,199 @@ function update_status() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Si el status no es 2, devolvemos normal
|
// 5) Si el status no es 2, devolvemos normal
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Envía notificación de cambio de estado de solicitud de importación **/
|
||||||
|
function enviarNotificacionCambioStatus($email, $nombreCompleto, $datosSolicitud, $esCorreoExtra = false) {
|
||||||
|
// Validar datos de entrada
|
||||||
|
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
error_log("❌ Email inválido para notificación: $email");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($datosSolicitud) || !isset($datosSolicitud['nuevo_status'])) {
|
||||||
|
error_log("❌ Datos de solicitud incompletos para notificación");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Configuración 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;
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
|
||||||
|
// Configuración del mensaje
|
||||||
|
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
|
||||||
|
$mail->addAddress($email);
|
||||||
|
$mail->isHTML(true);
|
||||||
|
|
||||||
|
// Obtener descripción y emoji del status
|
||||||
|
$statusInfo = obtenerInfoStatus($datosSolicitud['nuevo_status']);
|
||||||
|
|
||||||
|
// Personalizar subject si es correo adicional
|
||||||
|
$subjectPrefix = $esCorreoExtra ? '[COPIA] ' : '';
|
||||||
|
$mail->Subject = $subjectPrefix . '📊 Cambio de Estado en Solicitud de Importación';
|
||||||
|
|
||||||
|
// Preparar datos para el template
|
||||||
|
$datosTemplate = prepararDatosTemplate($datosSolicitud, $nombreCompleto, $statusInfo, $esCorreoExtra);
|
||||||
|
|
||||||
|
// Generar el HTML del email
|
||||||
|
$mail->Body = generarHtmlNotificacion($datosTemplate);
|
||||||
|
|
||||||
|
// Enviar el email
|
||||||
|
$envioExitoso = $mail->send();
|
||||||
|
|
||||||
|
if ($envioExitoso) {
|
||||||
|
$tipoCorreo = $esCorreoExtra ? 'correo adicional' : 'correo principal';
|
||||||
|
error_log("✅ Notificación de cambio de status enviada al $tipoCorreo: $email para solicitud ID: {$datosSolicitud['id_solicitud']}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$tipoCorreo = $esCorreoExtra ? 'correo adicional' : 'correo principal';
|
||||||
|
error_log("❌ Error al enviar notificación de cambio de status al $tipoCorreo ($email): {$mail->ErrorInfo} | Exception: {$e->getMessage()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Obtiene información del status (descripción y emoji) **/
|
||||||
|
function obtenerInfoStatus($status) {
|
||||||
|
$statusMap = [
|
||||||
|
1 => ['emoji' => '🔄', 'descripcion' => 'En proceso', 'color' => '#17a2b8'],
|
||||||
|
2 => ['emoji' => '📋', 'descripcion' => 'Solicitar importación', 'color' => '#ffc107'],
|
||||||
|
3 => ['emoji' => '🏢', 'descripcion' => 'Con agencia aduana', 'color' => '#6f42c1'],
|
||||||
|
4 => ['emoji' => '💳', 'descripcion' => 'En proceso de pago', 'color' => '#fd7e14'],
|
||||||
|
5 => ['emoji' => '✅', 'descripcion' => 'Pedimento generado', 'color' => '#28a745'],
|
||||||
|
6 => ['emoji' => '📦', 'descripcion' => 'En tránsito', 'color' => '#007bff'],
|
||||||
|
7 => ['emoji' => '🏁', 'descripcion' => 'Entregado', 'color' => '#28a745'],
|
||||||
|
8 => ['emoji' => '❌', 'descripcion' => 'Cancelado', 'color' => '#dc3545'],
|
||||||
|
9 => ['emoji' => '⏸️', 'descripcion' => 'Suspendido', 'color' => '#6c757d']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $statusMap[$status] ?? [
|
||||||
|
'emoji' => '📝',
|
||||||
|
'descripcion' => 'Estado actualizado',
|
||||||
|
'color' => '#6c757d'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepara los datos para el template del email **/
|
||||||
|
function prepararDatosTemplate($datosSolicitud, $nombreCompleto, $statusInfo, $esCorreoExtra) {
|
||||||
|
return [
|
||||||
|
'nombreCompleto' => htmlspecialchars($nombreCompleto ?? 'Usuario'),
|
||||||
|
'idSolicitud' => intval($datosSolicitud['id_solicitud'] ?? 0),
|
||||||
|
'numeroFactura' => htmlspecialchars($datosSolicitud['numero_factura'] ?? 'N/A'),
|
||||||
|
'fechaFactura' => htmlspecialchars($datosSolicitud['fecha_factura'] ?? 'N/A'),
|
||||||
|
'valorFactura' => number_format(floatval($datosSolicitud['valor_factura'] ?? 0), 2),
|
||||||
|
'tipoMoneda' => htmlspecialchars($datosSolicitud['tipo_moneda'] ?? 'USD'),
|
||||||
|
'statusEmoji' => $statusInfo['emoji'],
|
||||||
|
'statusDescripcion' => $statusInfo['descripcion'],
|
||||||
|
'statusColor' => $statusInfo['color'],
|
||||||
|
'esCorreoExtra' => $esCorreoExtra,
|
||||||
|
'fechaActual' => date('Y-m-d H:i:s'),
|
||||||
|
'anioActual' => date('Y')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Genera el HTML para la notificación **/
|
||||||
|
function generarHtmlNotificacion($datos) {
|
||||||
|
$tipoNotificacion = $datos['esCorreoExtra'] ?
|
||||||
|
'<div style="background: #fff3cd; padding: 10px; border-radius: 5px; margin-bottom: 15px; border-left: 4px solid #ffc107;">
|
||||||
|
<small><strong>📧 Copia enviada a correo adicional</strong></small>
|
||||||
|
</div>' : '';
|
||||||
|
|
||||||
|
return "
|
||||||
|
<div style='font-family: \"Segoe UI\", Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f6f9; padding: 30px; margin: 0;'>
|
||||||
|
<div style='max-width: 600px; margin: auto; background: #ffffff; border: 1px solid #dee2e6; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);'>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div style='background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 25px; text-align: center;'>
|
||||||
|
<h1 style='color: white; margin: 0; font-size: 24px; font-weight: 600;'>
|
||||||
|
{$datos['statusEmoji']} Estado Actualizado
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div style='padding: 30px 25px;'>
|
||||||
|
$tipoNotificacion
|
||||||
|
|
||||||
|
<p style='font-size: 16px; line-height: 1.5; margin-bottom: 20px;'>
|
||||||
|
Hola <strong>{$datos['nombreCompleto']}</strong>,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style='font-size: 16px; line-height: 1.5; margin-bottom: 25px;'>
|
||||||
|
El estado de tu solicitud de importación ha sido actualizado:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Status Card -->
|
||||||
|
<div style='background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 10px; padding: 20px; margin: 20px 0;'>
|
||||||
|
<table style='width: 100%; border-collapse: collapse; font-size: 14px;'>
|
||||||
|
<tr>
|
||||||
|
<td style='padding: 8px 0; font-weight: 600; color: #495057; width: 40%;'>ID Solicitud:</td>
|
||||||
|
<td style='padding: 8px 0; color: #212529;'>{$datos['idSolicitud']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style='padding: 8px 0; font-weight: 600; color: #495057;'>Número de Factura:</td>
|
||||||
|
<td style='padding: 8px 0; color: #212529;'>{$datos['numeroFactura']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style='padding: 8px 0; font-weight: 600; color: #495057;'>Fecha de Factura:</td>
|
||||||
|
<td style='padding: 8px 0; color: #212529;'>{$datos['fechaFactura']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr style='background: rgba(40, 167, 69, 0.1);'>
|
||||||
|
<td style='padding: 12px 8px; font-weight: 700; color: #495057;'>
|
||||||
|
{$datos['statusEmoji']} Nuevo Estado:
|
||||||
|
</td>
|
||||||
|
<td style='padding: 12px 8px; font-weight: 700; color: {$datos['statusColor']};'>
|
||||||
|
{$datos['statusDescripcion']}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style='padding: 8px 0; font-weight: 600; color: #495057;'>Valor:</td>
|
||||||
|
<td style='padding: 8px 0; color: #212529; font-weight: 600;'>
|
||||||
|
{$datos['valorFactura']} {$datos['tipoMoneda']}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style='font-size: 16px; line-height: 1.5; margin-top: 25px;'>
|
||||||
|
Puedes consultar todos los detalles y el seguimiento completo accediendo a tu panel de control.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Info Footer -->
|
||||||
|
<div style='margin-top: 30px; padding-top: 20px; border-top: 1px solid #e9ecef;'>
|
||||||
|
<p style='color: #6c757d; font-size: 13px; line-height: 1.4; margin: 0;'>
|
||||||
|
<strong>📧 Notificación automática</strong><br>
|
||||||
|
Este correo se envía automáticamente cuando se actualiza el estado de tu solicitud.
|
||||||
|
<br><small>Fecha de envío: {$datos['fechaActual']}</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #6c757d;'>
|
||||||
|
<strong>© {$datos['anioActual']} SIIH</strong> · Desarrollado por
|
||||||
|
<span style='color: #007bff; font-weight: 600;'>AduanaSoft</span>
|
||||||
|
<br>
|
||||||
|
<small style='color: #adb5bd;'>Sistema Integral de Importación y Herramientas</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
|
||||||
// Función para generar el PDF
|
// Función para generar el PDF
|
||||||
use Dompdf\Dompdf;
|
use Dompdf\Dompdf;
|
||||||
use Dompdf\Options;
|
use Dompdf\Options;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: right;">
|
<div style="display: flex; justify-content: right;">
|
||||||
|
<a href="/IMPORTADORES/configuracion/index" class="btn btn-secondary ms-2" style="margin-right: 10px;">Cancelar</a>
|
||||||
<button type="submit" class="btn btn-primary">💾 Guardar Cambios</button>
|
<button type="submit" class="btn btn-primary">💾 Guardar Cambios</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -147,51 +147,183 @@
|
|||||||
function updateRowStatus($row, status) {
|
function updateRowStatus($row, status) {
|
||||||
const disable = status === '2'; // 2 = Solicitar importación
|
const disable = status === '2'; // 2 = Solicitar importación
|
||||||
$row.find('a.btn, button.btn').prop('disabled', disable);
|
$row.find('a.btn, button.btn').prop('disabled', disable);
|
||||||
// Si quieres bloquear también el propio select:
|
|
||||||
// $row.find('.status-select').prop('disabled', disable);
|
// Opcional: cambiar estilo visual para indicar estado bloqueado
|
||||||
|
if (disable) {
|
||||||
|
$row.addClass('table-warning').find('.status-select').addClass('border-warning');
|
||||||
|
} else {
|
||||||
|
$row.removeClass('table-warning').find('.status-select').removeClass('border-warning');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Al cambiar el select de status
|
// Función para mostrar loading en el select
|
||||||
$('#tabla-solicitudes').on('change', '.status-select', function () {
|
function setSelectLoading($select, loading) {
|
||||||
const $sel = $(this);
|
if (loading) {
|
||||||
const id = $sel.data('id');
|
$select.prop('disabled', true).addClass('border-primary');
|
||||||
const status = $sel.val();
|
$select.closest('tr').find('td').addClass('opacity-75');
|
||||||
const $row = $sel.closest('tr');
|
} else {
|
||||||
// Bloquear botones localmente
|
$select.prop('disabled', false).removeClass('border-primary');
|
||||||
updateRowStatus($row, status);
|
$select.closest('tr').find('td').removeClass('opacity-75');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Llamada AJAX
|
// Al cambiar el select de status
|
||||||
$.post(
|
$('#tabla-solicitudes').on('change', '.status-select', function () {
|
||||||
'/IMPORTADORES/solicitud_importacion/update_status',
|
const $sel = $(this);
|
||||||
{ id: id, status: status },
|
const id = $sel.data('id');
|
||||||
null,
|
const status = $sel.val();
|
||||||
'json'
|
const $row = $sel.closest('tr');
|
||||||
)
|
const oldStatus = $sel.data('old-status') || $sel.find('option:first').val();
|
||||||
.done(function(res) {
|
|
||||||
if (!res.success) {
|
// Guardar el estado anterior para poder revertir si falla
|
||||||
Swal.fire('Error', 'No se pudo actualizar status', 'error');
|
$sel.data('old-status', oldStatus);
|
||||||
} else if (res.numeroPedimentoLocal) {
|
|
||||||
// Si vino numeroPedimentoLocal, mostramos alerta específica
|
// Mostrar loading
|
||||||
Swal.fire(
|
setSelectLoading($sel, true);
|
||||||
'Importación solicitada',
|
|
||||||
'Se generó el número de pedimento: ' + res.numeroPedimentoLocal,
|
// Mostrar toast de procesando
|
||||||
'success'
|
Swal.fire({
|
||||||
);
|
title: 'Actualizando...',
|
||||||
// Además podrías actualizar en la tabla la celda de "Pedimento" si quieres:
|
text: 'Procesando cambio de estado',
|
||||||
$row.find('td:nth-child(4)').text(res.numeroPedimentoLocal);
|
icon: 'info',
|
||||||
}
|
allowOutsideClick: false,
|
||||||
// Si success pero no viene numeroPedimentoLocal, no hacemos nada especial.
|
showConfirmButton: false,
|
||||||
})
|
timer: 1000,
|
||||||
.fail(function() {
|
timerProgressBar: true
|
||||||
Swal.fire('Error', 'No se pudo conectar al servidor', 'error');
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Llamada AJAX con configuración mejorada
|
||||||
|
$.ajax({
|
||||||
|
url: '/IMPORTADORES/solicitud_importacion/update_status',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
status: status
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
timeout: 30000, // 30 segundos timeout
|
||||||
|
success: function(res) {
|
||||||
|
console.log('Respuesta del servidor:', res);
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
// Error del servidor pero respuesta válida
|
||||||
|
handleUpdateError('Error del servidor: ' + (res.error || 'Error desconocido'), $sel, oldStatus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Éxito - actualizar UI
|
||||||
|
updateRowStatus($row, status);
|
||||||
|
$sel.data('old-status', status); // Actualizar estado guardado
|
||||||
|
|
||||||
|
// Mostrar mensaje específico según el resultado
|
||||||
|
if (res.numeroPedimentoLocal) {
|
||||||
|
Swal.fire({
|
||||||
|
title: '¡Importación solicitada!',
|
||||||
|
text: `Se generó el número de pedimento: ${res.numeroPedimentoLocal}`,
|
||||||
|
icon: 'success',
|
||||||
|
confirmButtonText: 'Entendido'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actualizar la celda del pedimento si existe
|
||||||
|
const $pedimentoCell = $row.find('td[data-pedimento]');
|
||||||
|
if ($pedimentoCell.length) {
|
||||||
|
$pedimentoCell.text(res.numeroPedimentoLocal).addClass('text-success fw-bold');
|
||||||
|
}
|
||||||
|
} else if (res.warning) {
|
||||||
|
// Mostrar advertencia si hay alguna
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Estado actualizado',
|
||||||
|
text: res.warning,
|
||||||
|
icon: 'warning',
|
||||||
|
confirmButtonText: 'Entendido'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Éxito normal
|
||||||
|
Swal.fire({
|
||||||
|
title: '¡Actualizado!',
|
||||||
|
text: 'Estado cambiado correctamente',
|
||||||
|
icon: 'success',
|
||||||
|
timer: 2000,
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Error AJAX:', { xhr, status, error });
|
||||||
|
|
||||||
|
let errorMessage = 'No se pudo conectar al servidor';
|
||||||
|
|
||||||
|
// Mejorar el mensaje de error según el tipo
|
||||||
|
if (status === 'timeout') {
|
||||||
|
errorMessage = 'La operación tardó demasiado tiempo. Inténtalo de nuevo.';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage = 'Error al procesar la respuesta del servidor';
|
||||||
|
} else if (xhr.status) {
|
||||||
|
switch(xhr.status) {
|
||||||
|
case 401:
|
||||||
|
errorMessage = 'Tu sesión ha expirado. Recarga la página e inicia sesión nuevamente.';
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
errorMessage = 'No tienes permisos para realizar esta acción';
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
errorMessage = 'El servicio no está disponible temporalmente';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
errorMessage = 'Error interno del servidor. Contacta al administrador.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = `Error del servidor (${xhr.status}): ${xhr.statusText}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateError(errorMessage, $sel, oldStatus);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
// Quitar loading siempre
|
||||||
|
setSelectLoading($sel, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Al cargar, aplica bloqueo si ya está en “Solicitar importación”
|
// Función para manejar errores de actualización
|
||||||
|
function handleUpdateError(message, $select, oldStatus) {
|
||||||
|
// Revertir el select al estado anterior
|
||||||
|
$select.val(oldStatus);
|
||||||
|
|
||||||
|
// Mostrar error
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Error',
|
||||||
|
text: message,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: 'Entendido'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log para debugging
|
||||||
|
console.error('Error al actualizar estado:', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Al cargar, aplica bloqueo si ya está en "Solicitar importación"
|
||||||
$('#tabla-solicitudes tbody tr').each(function () {
|
$('#tabla-solicitudes tbody tr').each(function () {
|
||||||
const $sel = $(this).find('.status-select');
|
const $sel = $(this).find('.status-select');
|
||||||
if ($sel.length) updateRowStatus($(this), $sel.val());
|
if ($sel.length) {
|
||||||
|
updateRowStatus($(this), $sel.val());
|
||||||
|
// Guardar estado inicial
|
||||||
|
$sel.data('old-status', $sel.val());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Interceptar errores globales de AJAX para debugging
|
||||||
|
$(document).ajaxError(function(event, xhr, settings, thrownError) {
|
||||||
|
if (settings.url.includes('update_status')) {
|
||||||
|
console.error('AJAX Error Details:', {
|
||||||
|
url: settings.url,
|
||||||
|
status: xhr.status,
|
||||||
|
statusText: xhr.statusText,
|
||||||
|
responseText: xhr.responseText,
|
||||||
|
thrownError: thrownError
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mensajes SweetAlert tras acciones
|
// Mensajes SweetAlert tras acciones
|
||||||
|
|||||||
Reference in New Issue
Block a user