$user, 'password' => $pass]); error_log("[getApiToken] POST $url → $body"); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); error_log("[getApiToken] HTTP $http → $resp"); if ($http === 200 && ($data = json_decode($resp, true)) && !empty($data['token'])) { // 3) Guardamos token y tiempo actual $_SESSION['api_token'] = $data['token']; $_SESSION['api_token_time'] = time(); return $data['token']; } // 4) Si no se pudo obtener, devolvemos null error_log('[getApiToken] No se pudo obtener token de la API'); return null; } /** Listado de solicitudes de importación (facturas) del importador logueado **/ function lista() { if (!($_SESSION['usuario_id'] ?? false)) { header('Location: /IMPORTADORES/login'); exit; } $id_importador = $_SESSION['usuario_id']; $conn = getConnection(); // ✅ NUEVO: Obtener la agencia actual del usuario $stmt = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]); $id_agencia = null; if ($stmt && $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $id_agencia = $row['id_agencia_en_uso']; } $sql = "SELECT f.*, tr.nombre AS transportista, (c.nombre + ' ' + c.apellido) AS chofer, p.nombre AS nombre_pais_proveedor, f.foto_solicitud_url, a.patente AS patente FROM dbo.solicitud_importacion_factura f JOIN dbo.transportistas tr ON f.transportista_id = tr.id_transportista LEFT JOIN dbo.choferes c ON f.chofer_id = c.id_chofer LEFT JOIN dbo.paises p ON f.pais_proveedor = p.id_pais LEFT JOIN dbo.agentes_aduanales a ON f.patente_id = a.id_agente WHERE f.id_importador = ? AND f.id_agencia = ? AND f.status >= 1 ORDER BY f.created_at DESC "; $stmt = sqlsrv_query($conn, $sql, [$id_importador, $id_agencia]); if ($stmt === false) { die("Error en lista(): " . print_r(sqlsrv_errors(), true)); } $facturas = []; while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { if ($row['fecha_factura'] instanceof DateTime) { $row['fecha_factura'] = $row['fecha_factura']->format('Y-m-d'); } $facturas[] = $row; } include __DIR__ . '/../../views/solicitud_importacion/lista.php'; } /** Formulario de nueva factura **/ function crear() { if (!($_SESSION['usuario_id'] ?? false)) { header('Location: /IMPORTADORES/login'); exit; } $id_importador = $_SESSION['usuario_id']; $conn = getConnection(); // ✅ NUEVO: Obtener la agencia actual del usuario $stmt = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]); if ($stmt === false) { die(print_r(sqlsrv_errors(), true)); } $id_agencia = null; if ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $id_agencia = $row['id_agencia_en_uso']; } // Carga de datos para selects $patentes = []; $stmtP = sqlsrv_query($conn, "SELECT id_agente, patente, agente_aduanal FROM dbo.agentes_aduanales WHERE id_agencia = ? AND activo = 1 ORDER BY patente", [$id_agencia]); while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $patentes[] = $r; } $transportistas = []; $stmtT = sqlsrv_query($conn, "SELECT id_transportista, clave_identificador, nombre FROM dbo.transportistas WHERE id_usuario = ? AND activo = 1 ORDER BY nombre",[$id_importador]); while ($r = sqlsrv_fetch_array($stmtT, SQLSRV_FETCH_ASSOC)) { $transportistas[] = $r; } // ✅ MODIFICADO: Obtener choferes con su transportista_id para el filtro $choferes = []; $stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre, c.transportista_id FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id=t.id_transportista WHERE t.id_usuario=? AND c.status=1 ORDER BY c.nombre",[$id_importador]); while ($r = sqlsrv_fetch_array($stmtC, SQLSRV_FETCH_ASSOC)) { $choferes[] = $r; } $paises = []; $stmtP = sqlsrv_query($conn, "SELECT id_pais, nombre FROM dbo.paises ORDER BY nombre"); while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $paises[] = $r; } $aduanas = []; $stmtA = sqlsrv_query($conn, "SELECT DISTINCT RIGHT(REPLICATE('0', 3) + CAST(aduana_seccion AS VARCHAR), 3) AS aduana_seccion, nombre FROM dbo.aduanas ORDER BY aduana_seccion"); while ($r = sqlsrv_fetch_array($stmtA, SQLSRV_FETCH_ASSOC)) { $aduanas[] = $r; } $incoterms = []; $stmtI = sqlsrv_query($conn, "SELECT INCOTERM, DESCESPANOL FROM dbo.gIncoterms ORDER BY INCOTERM"); while ($r = sqlsrv_fetch_array($stmtI, SQLSRV_FETCH_ASSOC)) { $incoterms[] = $r; } $unidades_medida = []; $stmtU = sqlsrv_query($conn, "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id"); while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) { $unidades_medida[] = $r; } include __DIR__ . '/../../views/solicitud_importacion/crear.php'; } // ✅ NUEVO: Endpoint AJAX para obtener choferes por transportista function obtenerChoferesPorTransportista() { // Configurar headers para JSON desde el inicio header('Content-Type: application/json'); try { if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['error' => 'No autorizado']); exit; } $id_importador = $_SESSION['usuario_id']; $transportista_id = $_GET['transportista_id'] ?? null; if (!$transportista_id || !is_numeric($transportista_id)) { http_response_code(400); echo json_encode(['error' => 'ID de transportista requerido']); exit; } $conn = getConnection(); // Validar que el transportista pertenezca al usuario $stmtValidate = sqlsrv_query($conn, "SELECT id_transportista FROM dbo.transportistas WHERE id_transportista = ? AND id_usuario = ? AND activo = 1", [intval($transportista_id), $id_importador]); if (!$stmtValidate || !sqlsrv_fetch_array($stmtValidate, SQLSRV_FETCH_ASSOC)) { http_response_code(403); echo json_encode(['error' => 'Transportista no válido']); exit; } // Obtener choferes del transportista $choferes = []; $stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre FROM dbo.choferes c WHERE c.transportista_id = ? AND c.status = 1 ORDER BY c.nombre", [intval($transportista_id)]); if ($stmtC) { while ($r = sqlsrv_fetch_array($stmtC, SQLSRV_FETCH_ASSOC)) { $choferes[] = [ 'id_chofer' => $r['id_chofer'], 'nombre' => $r['nombre'] ]; } } echo json_encode($choferes); exit; } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => 'Error interno del servidor']); exit; } } function buscar_productos() { header('Content-Type: application/json'); // 1. Validar usuario autenticado if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['error' => 'No autorizado']); exit; } $id_importador = $_SESSION['usuario_id']; $query = trim($_GET['q'] ?? ''); // 2. Validar longitud mínima if (strlen($query) < 2) { echo json_encode([]); exit; } try { $conn = getConnection(); // Obtener conexión // 3. Búsqueda mejorada con ponderación $searchTerm = "%$query%"; $sql = "SELECT TOP 10 id_producto_frecuente, sinonimo, descripcion, preferencia, fraccion, nico, numero_parte, CAST(umc_id AS VARCHAR) AS umc_id, -- Campos para cálculo de relevancia CASE WHEN sinonimo LIKE ? THEN 100 WHEN descripcion LIKE ? THEN 50 ELSE 0 END AS relevancia FROM dbo.productos_frecuentes WHERE id_importador = ? AND status = 1 AND (sinonimo LIKE ? OR descripcion LIKE ? OR numero_parte LIKE ?) ORDER BY relevancia DESC, frecuencia_uso DESC, sinonimo"; $params = [ "$query%", // Para búsqueda al inicio del sinonimo "$query%", // Para búsqueda al inicio de descripción $id_importador, $searchTerm, $searchTerm, $searchTerm ]; $stmt = sqlsrv_query($conn, $sql, $params); if ($stmt === false) { error_log("Error en búsqueda de productos: " . print_r(sqlsrv_errors(), true)); echo json_encode([]); exit; } $productos = []; while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $productos[] = [ 'id' => $row['id_producto_frecuente'], 'sinonimo' => $row['sinonimo'], 'descripcion' => $row['descripcion'] ?? '', 'preferencia' => $row['preferencia'] ?? '', 'fraccion' => $row['fraccion'] ?? '', 'nico' => $row['nico'] ?? '', 'numero_parte' => $row['numero_parte'] ?? '', 'umc_id' => $row['umc_id'] ?? null ]; } sqlsrv_free_stmt($stmt); echo json_encode($productos); } catch (Exception $e) { error_log("Excepción en buscar_productos: " . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Error interno']); } exit; } function incrementar_frecuencia() { header('Content-Type: application/json'); // 1. Validar usuario if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['success' => false, 'error' => 'No autorizado']); exit; } $id_importador = $_SESSION['usuario_id']; $producto_id = (int)($_POST['producto_id'] ?? 0); if ($producto_id <= 0) { echo json_encode(['success' => false, 'error' => 'ID inválido']); exit; } try { $conn = getConnection(); // 2. Verificar que el producto pertenece al usuario $sqlValidate = "SELECT 1 FROM dbo.productos_frecuentes WHERE id_producto_frecuente = ? AND id_importador = ?"; $stmtValidate = sqlsrv_query($conn, $sqlValidate, [$producto_id, $id_importador]); if (!$stmtValidate || !sqlsrv_fetch_array($stmtValidate)) { http_response_code(403); echo json_encode(['success' => false, 'error' => 'Producto no válido']); exit; } // 3. Actualizar frecuencia $sqlUpdate = "UPDATE dbo.productos_frecuentes SET frecuencia_uso = frecuencia_uso + 1 WHERE id_producto_frecuente = ?"; $stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$producto_id]); if ($stmtUpdate === false) { error_log("Error actualizando frecuencia: " . print_r(sqlsrv_errors(), true)); echo json_encode(['success' => false, 'error' => 'Error en actualización']); exit; } echo json_encode(['success' => true]); } catch (Exception $e) { error_log("Excepción en incrementar_frecuencia: " . $e->getMessage()); http_response_code(500); echo json_encode(['success' => false, 'error' => 'Error interno']); } exit; } /** Procesa la creación de una nueva factura y sus partidas **/ function guardar() { if (!($_SESSION['usuario_id'] ?? false)) { die("⚠️ No autorizado."); } $id_importador = $_SESSION['usuario_id']; $conn = getConnection(); // ✅ Obtener id_agencia_en_uso del usuario $stmtAgencia = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]); $id_agencia = null; if ($stmtAgencia && $row = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC)) { $id_agencia = $row['id_agencia_en_uso']; } if (!$id_agencia) { die("❌ No se pudo determinar la agencia en uso para guardar la solicitud."); } // Obtener y validar campos del formulario $aduana_seccion = $_POST['anexo22_apendice'] ?? null; $num_factura = trim($_POST['numero_factura'] ?? ''); $fecha = $_POST['fecha_factura'] ?? null; $pedimento = trim($_POST['pedimento'] ?? ''); // ✅ NUEVO CAMPO $incoterm = $_POST['incoterm'] ?? null; $pais_proveedor = $_POST['pais_proveedor'] ?? null; $tipo_moneda = $_POST['tipo_moneda'] ?? null; $valor_factura = $_POST['valor_factura'] ?? null; $vinculacion = $_POST['vinculacion'] ?? 0; $transportista_id = $_POST['transportista_id'] ?? null; $chofer_id = $_POST['chofer_id'] ?? null; $status = isset($_POST['status']) ? 1 : 0; $proveedor_clave = trim($_POST['proveedor_id'] ?? ''); $patente_id = $_POST['patente'] ?? null; if ($patente_id) { $stmtValidatePatente = sqlsrv_query($conn, "SELECT id_agente FROM dbo.agentes_aduanales WHERE id_agente = ? AND id_agencia = ? AND activo = 1", [$patente_id, $id_agencia]); if (!$stmtValidatePatente || !sqlsrv_fetch_array($stmtValidatePatente, SQLSRV_FETCH_ASSOC)) { die("❌ La patente seleccionada no es válida para su agencia."); } sqlsrv_free_stmt($stmtValidatePatente); } if (empty($num_factura) || empty($fecha) || empty($transportista_id) || empty($chofer_id)) { die("❌ Faltan campos obligatorios."); } // Foto solicitud $fotoUrl = null; if (!empty($_FILES['foto_solicitud']['tmp_name']) && $_FILES['foto_solicitud']['error']===UPLOAD_ERR_OK) { $ext = pathinfo($_FILES['foto_solicitud']['name'], PATHINFO_EXTENSION); $dest = __DIR__.'/../../public/uploads/solicitud_'.uniqid().".$ext"; if (!is_dir(dirname($dest))) mkdir(dirname($dest),0755,true); if(move_uploaded_file($_FILES['foto_solicitud']['tmp_name'],$dest)) { $fotoUrl = "/IMPORTADORES/public/uploads/".basename($dest); } } $params = [ $id_importador, $id_agencia, $aduana_seccion, $aduana_seccion, $num_factura, $fecha, $incoterm, $pais_proveedor, $tipo_moneda, $valor_factura, $vinculacion, (int)$transportista_id, (int)$chofer_id, $fotoUrl, $status, $proveedor_clave, $patente_id ? (int)$patente_id : null, $pedimento ?: null // ✅ CORREGIDO: Campo pedimento_vinculado al final ]; $sql = "INSERT INTO dbo.solicitud_importacion_factura (id_importador, id_agencia, aduana, anexo22_apendice, numero_factura, fecha_factura, incoterm, pais_proveedor, tipo_moneda, valor_factura, vinculacion, transportista_id, chofer_id, foto_solicitud_url, status, proveedor_clave, patente_id, pedimento_vinculado) OUTPUT INSERTED.id_solicitud VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "; $stmt = sqlsrv_query($conn, $sql, $params, ['Scrollable' => SQLSRV_CURSOR_KEYSET]); if ($stmt === false) { die("❌ Error ejecutando INSERT con OUTPUT: " . print_r(sqlsrv_errors(), true)); } $new = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); if (!$new || empty($new['id_solicitud'])) { die("❌ No se pudo recuperar el ID insertado de la factura."); } $id_solicitud = (int)$new['id_solicitud']; try { // Verificar configuración de notificaciones del usuario $sqlNotif = "SELECT u.nombre, u.email, u.notificaciones, u.notificaciones_extra, COALESCE(p.nuevas_solicitudes, 0) as nuevas_solicitudes, ce.correo as correo_extra 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 WHERE u.id_usuario = ? "; $stmtNotif = sqlsrv_prepare($conn, $sqlNotif, [$id_importador]); if ($stmtNotif && sqlsrv_execute($stmtNotif)) { $notifConfig = sqlsrv_fetch_array($stmtNotif, SQLSRV_FETCH_ASSOC); sqlsrv_free_stmt($stmtNotif); // 🔐 Desencriptar datos if ($notifConfig) { // Correo $notifConfig['email'] = decrypt($notifConfig['email']); // Nombre $notifConfig['nombre'] = decrypt($notifConfig['nombre']); } // Verificar si debe enviar notificaciones if ($notifConfig && $notifConfig['notificaciones'] == 1 && $notifConfig['nuevas_solicitudes'] == 1) { // Preparar datos para la notificación $datosNotificacion = [ 'id_solicitud' => $id_solicitud, 'numero_factura' => $num_factura, 'fecha_factura' => $fecha, 'valor_factura' => $valor_factura, 'tipo_moneda' => $tipo_moneda ]; // Enviar al correo principal $resultadoPrincipal = enviarNotificacionNuevaSolicitud( $notifConfig['email'], $notifConfig['nombre'], $datosNotificacion ); // Enviar al correo adicional si está configurado if ($notifConfig['notificaciones_extra'] == 1 && !empty($notifConfig['correo_extra'])) { $resultadoExtra = enviarNotificacionNuevaSolicitud( $notifConfig['correo_extra'], $notifConfig['nombre'], $datosNotificacion, true // Indicar que es correo adicional ); } // Log de notificaciones enviadas (opcional) error_log("✅ Notificación enviada para solicitud ID: $id_solicitud"); } else { // Log informativo error_log("ℹ️ Usuario ID: $id_importador no tiene notificaciones de nuevas solicitudes habilitadas"); } } else { error_log("⚠️ No se pudo consultar configuración de notificaciones para usuario ID: $id_importador"); } } catch (Exception $e) { // No fallar el proceso por errores de notificación error_log("❌ Error en sistema de notificaciones: " . $e->getMessage()); } // Partidas if(!empty($_POST['partidas'])&&is_array($_POST['partidas'])){ $sqlP = "INSERT INTO dbo.solicitud_importacion_partidas (id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa, valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "; $partidas_insertadas = 0; // ← contador foreach ($_POST['partidas'] as $i => $p) { error_log("Partida $i: " . print_r($p, true)); $params = [ $id_solicitud, trim($p['descripcion'] ?? ''), floatval($p['cantidad_comercial'] ?? 0), floatval($p['cantidad_tarifa'] ?? 0), floatval($p['valor_factura'] ?? 0), floatval($p['peso_bruto'] ?? 0), intval($p['unidad_comercial_id'] ?? 0) ?: null, trim($p['tasa_preferencial'] ?? '') ]; if ($params[1] !== '' && $params[2] > 0) { $stmtPartida = sqlsrv_query($conn, $sqlP, $params); if ($stmtPartida === false) { die("❌ Error insertando partida $i: " . print_r(sqlsrv_errors(), true)); } else { $partidas_insertadas++; } } } if ($partidas_insertadas === 0) { ///header('Location: /IMPORTADORES/solicitud_importacion/crear?error_partidas=1'); // exit; } } header('Location: /IMPORTADORES/solicitud_importacion/lista?created=ok'); exit; } /** Función para generar la notificación **/ function enviarNotificacionNuevaSolicitud($email, $nombreUsuario, $datosSolicitud, $esCorreoExtra = 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); // Personalizar subject si es correo adicional $subjectPrefix = $esCorreoExtra ? '[COPIA] ' : ''; $mail->Subject = $subjectPrefix . '📦 Nueva Solicitud de Importación Registrada'; // Formatear datos para el email $idSolicitud = $datosSolicitud['id_solicitud']; $numeroFactura = htmlspecialchars($datosSolicitud['numero_factura']); $fechaFactura = htmlspecialchars($datosSolicitud['fecha_factura']); $valorFactura = number_format($datosSolicitud['valor_factura'], 2); $tipoMoneda = htmlspecialchars($datosSolicitud['tipo_moneda']); $tipoNotificacion = $esCorreoExtra ? '
📧 Copia enviada a correo adicional
' : ''; $mail->Body = "
h2 style='color: white; margin: 0;'>📥 Nueva Solicitud Registrada
$tipoNotificacion

Hola " . htmlspecialchars($nombreUsuario) . ",

Tu solicitud de importación ha sido registrada correctamente con los siguientes datos:

ID Solicitud: $idSolicitud
Número de Factura: $numeroFactura
Fecha: $fechaFactura
Valor: $valorFactura $tipoMoneda

Puedes consultar el estado de tu solicitud accediendo a tu panel de control.


Si no realizaste esta acción, contacta al administrador del sistema.

© " . date('Y') . " SIIH · Desarrollado por AduanaSoft
"; $envioExitoso = $mail->send(); if ($envioExitoso) { $tipoCorreo = $esCorreoExtra ? 'correo adicional' : 'correo principal'; error_log("✅ Notificación enviada exitosamente al $tipoCorreo: $email"); return true; } return false; } catch (Exception $e) { $tipoCorreo = $esCorreoExtra ? 'correo adicional' : 'correo principal'; error_log("❌ Error al enviar notificación al $tipoCorreo ($email): {$mail->ErrorInfo}"); return false; } } /** Formulario de edición de factura **/ function editar() { if (!($_SESSION['usuario_id'] ?? false)) { header('Location: /IMPORTADORES/login'); exit; } $id_solicitud = $_GET['id']??null; if(!$id_solicitud || !is_numeric($id_solicitud)) { die("❌ ID inválido."); } $id_importador = $_SESSION['usuario_id']; $conn = getConnection(); // ✅ NUEVO: Obtener la agencia actual del usuario $stmtAg = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]); if ($stmtAg === false) { die(print_r(sqlsrv_errors(), true)); } $id_agencia = null; if ($row = sqlsrv_fetch_array($stmtAg, SQLSRV_FETCH_ASSOC)) { $id_agencia = $row['id_agencia_en_uso']; } $stmt = sqlsrv_query($conn, "SELECT * FROM dbo.solicitud_importacion_factura WHERE id_solicitud=? AND id_importador=? AND id_agencia=?", [(int)$id_solicitud, $id_importador, $id_agencia]); if($stmt === false) { die(print_r(sqlsrv_errors(),true)); } $factura = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); if(!$factura) { die("❌ No autorizado."); } if($factura['fecha_factura'] instanceof DateTime) { $factura['fecha_factura'] = $factura['fecha_factura']->format('Y-m-d'); } // Solo necesitamos la clave del proveedor actual para JavaScript $proveedor_clave_actual = $factura['proveedor_clave'] ?? ''; // Carga selects (igual que crear) // Patentes $patentes = []; $stmtP = sqlsrv_query($conn, "SELECT id_agente, patente, agente_aduanal FROM dbo.agentes_aduanales WHERE id_agencia = ? AND activo = 1 ORDER BY patente", [$id_agencia]); while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $patentes[] = $r; } // Transportistas $transportistas = []; $stmtT = sqlsrv_query($conn, "SELECT id_transportista, clave_identificador, nombre FROM dbo.transportistas WHERE id_usuario = ? AND activo = 1 ORDER BY nombre",[$id_importador]); while ($r = sqlsrv_fetch_array($stmtT, SQLSRV_FETCH_ASSOC)) { $transportistas[] = $r; } // Choferes $choferes = []; $stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre, c.transportista_id FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id = t.id_transportista WHERE t.id_usuario=? AND c.status=1 ORDER BY c.nombre",[$id_importador]); while ($r = sqlsrv_fetch_array($stmtC, SQLSRV_FETCH_ASSOC)) { $choferes[] = $r; } // Paises $paises = []; $stmtP = sqlsrv_query($conn, "SELECT id_pais, nombre FROM dbo.paises ORDER BY nombre"); while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $paises[] = $r;} // Aduanas $aduanas = []; $stmtA = sqlsrv_query($conn, "SELECT DISTINCT RIGHT(REPLICATE('0', 3) + CAST(aduana_seccion AS VARCHAR), 3) AS aduana_seccion, nombre FROM dbo.aduanas ORDER BY aduana_seccion"); while ($r = sqlsrv_fetch_array($stmtA, SQLSRV_FETCH_ASSOC)) { $aduanas[] = $r; } // Incoterms $incoterms = []; $stmtI = sqlsrv_query($conn, "SELECT INCOTERM, DESCESPANOL FROM dbo.gIncoterms ORDER BY INCOTERM"); while ($r = sqlsrv_fetch_array($stmtI, SQLSRV_FETCH_ASSOC)) { $incoterms[] = $r; } // Unidades de Medida $unidades_medida = []; $stmtU = sqlsrv_query($conn, "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id"); while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) { $unidades_medida[] = $r; } // Partidas existentes $partidas = []; $stmtPar = sqlsrv_query($conn, "SELECT id_partida, descripcion, cantidad_comercial, cantidad_tarifa, valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ? ORDER BY id_partida", [(int)$id_solicitud]); while ($r = sqlsrv_fetch_array($stmtPar, SQLSRV_FETCH_ASSOC)) { $partidas[]=$r; } include __DIR__ . '/../../views/solicitud_importacion/editar.php'; } /** Procesa la actualización de una factura y sus partidas **/ function actualizar() { if (!($_SESSION['usuario_id'] ?? false)) { die("⚠️ No autorizado."); } $id_importador = $_SESSION['usuario_id']; $id_solicitud = (int)($_POST['id_solicitud'] ?? 0); if ($id_solicitud <= 0) { die("❌ ID inválido."); } // 1) Conexión $conn = getConnection(); // ✅ NUEVO: Obtener la agencia actual del usuario $stmt = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]); $id_agencia = null; if ($stmt && $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $id_agencia = $row['id_agencia_en_uso']; } // 2) Obtener URL de foto actual desde BD para conservar si no suben nueva $fotoUrl = null; $stmtFoto = sqlsrv_query($conn, "SELECT foto_solicitud_url FROM dbo.solicitud_importacion_factura WHERE id_solicitud = ? AND id_importador = ? AND id_agencia = ?", [ $id_solicitud, $_SESSION['usuario_id'], $id_agencia ] ); if ($stmtFoto !== false && ($row = sqlsrv_fetch_array($stmtFoto, SQLSRV_FETCH_ASSOC))) { $fotoUrl = $row['foto_solicitud_url']; } // 3) Procesar posible nueva foto if (!empty($_FILES['foto_solicitud']['tmp_name']) && $_FILES['foto_solicitud']['error'] === UPLOAD_ERR_OK) { $ext = pathinfo($_FILES['foto_solicitud']['name'], PATHINFO_EXTENSION); $dest = __DIR__ . '/../../public/uploads/solicitud_' . uniqid() . ".$ext"; if (!is_dir(dirname($dest))) { mkdir(dirname($dest), 0755, true); } if (move_uploaded_file($_FILES['foto_solicitud']['tmp_name'], $dest)) { $fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest); } } // 4) Extraer campos del formulario $aduana_seccion = $_POST['anexo22_apendice'] ?? null; $num_factura = trim($_POST['numero_factura'] ?? ''); $fecha = $_POST['fecha_factura'] ?? null; $incoterm = $_POST['incoterm'] ?? null; $pais_proveedor = $_POST['pais_proveedor'] ?? null; $tipo_moneda = $_POST['tipo_moneda'] ?? null; $valor_factura = $_POST['valor_factura'] ?? null; $vinculacion = $_POST['vinculacion'] ?? 0; $transportista_id = (int)($_POST['transportista_id'] ?? 0); $chofer_id = (int)($_POST['chofer_id'] ?? 0); $status = isset($_POST['status']) ?? 1; $patente_id = $_POST['patente'] ?? null; $proveedor_clave = trim($_POST['proveedor_clave'] ?? null); if ($proveedor_clave === '') { $proveedor_clave = null; } // ✅ NUEVO: Validar que la patente pertenezca a la agencia del usuario (si se seleccionó una) if ($patente_id) { $stmtValidatePatente = sqlsrv_query($conn, "SELECT id_agente FROM dbo.agentes_aduanales WHERE id_agente = ? AND id_agencia = ? AND activo = 1", [$patente_id, $id_agencia]); if (!$stmtValidatePatente || !sqlsrv_fetch_array($stmtValidatePatente, SQLSRV_FETCH_ASSOC)) { die("❌ La patente seleccionada no es válida para su agencia."); } sqlsrv_free_stmt($stmtValidatePatente); } // 5) Validar obligatorios if (empty($num_factura) || empty($fecha) || $transportista_id <= 0 || $chofer_id <= 0) { die("❌ Faltan campos obligatorios."); } // 6) UPDATE de la cabecera $paramsU = [ $aduana_seccion, $aduana_seccion, $num_factura, $fecha, $incoterm, $pais_proveedor, $tipo_moneda, $valor_factura, $vinculacion, (int)$transportista_id, (int)$chofer_id, $fotoUrl, $status, $proveedor_clave, $patente_id ? (int)$patente_id : null, $id_solicitud, $_SESSION['usuario_id'], $id_agencia ]; $sqlU = "UPDATE dbo.solicitud_importacion_factura SET aduana = ?, anexo22_apendice = ?, numero_factura = ?, fecha_factura = ?, incoterm = ?, pais_proveedor = ?, tipo_moneda = ?, valor_factura = ?, vinculacion = ?, transportista_id = ?, chofer_id = ?, foto_solicitud_url = ?, status = ?, proveedor_clave = ?, patente_id = ?, updated_at = GETDATE() WHERE id_solicitud = ? AND id_importador = ? AND id_agencia = ? "; $stmtU = sqlsrv_query($conn, $sqlU, $paramsU); if ($stmtU === false) { die("❌ Error ejecutando UPDATE: " . print_r(sqlsrv_errors(), true)); } // 7) Manejo de partidas - Versión mejorada if (!empty($_POST['partidas']) && is_array($_POST['partidas'])) { // Obtener partidas existentes de la base de datos $partidasExistentes = []; $stmtPartidas = sqlsrv_query($conn, "SELECT id_partida FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ?", [$id_solicitud]); while ($row = sqlsrv_fetch_array($stmtPartidas, SQLSRV_FETCH_ASSOC)) { $partidasExistentes[] = $row['id_partida']; } // Procesar cada partida del formulario foreach ($_POST['partidas'] as $i => $p) { $desc = trim($p['descripcion'] ?? ''); $cantCom = floatval($p['cantidad_comercial'] ?? 0); $cantTar = floatval($p['cantidad_tarifa'] ?? 0); $valPart = floatval($p['valor_factura'] ?? 0); $peso = floatval($p['peso_bruto'] ?? 0); $umId = intval($p['unidad_comercial_id'] ?? 0) ?: null; $tasaPref = trim($p['tasa_preferencial'] ?? ''); // Solo procesar si tiene descripción y cantidad válida if ($desc !== '' && $cantCom > 0) { // Verificar si es una partida existente (tiene id_partida numérico > 0) if (!empty($p['id_partida']) && intval($p['id_partida']) > 0) { // ACTUALIZAR partida existente $sql = "UPDATE dbo.solicitud_importacion_partidas SET descripcion = ?, cantidad_comercial = ?, cantidad_tarifa = ?, valor_factura = ?, peso_bruto = ?, unidad_comercial_id = ?, tasa_preferencial = ? WHERE id_partida = ? AND id_solicitud = ? "; $params = [ $desc, $cantCom, $cantTar, $valPart, $peso, $umId, $tasaPref, intval($p['id_partida']), $id_solicitud ]; // Eliminar de la lista de existentes if (($key = array_search($p['id_partida'], $partidasExistentes)) !== false) { unset($partidasExistentes[$key]); } } else { // INSERTAR nueva partida (asegurarse que no tenga id_partida o sea 0) $sql = "INSERT INTO dbo.solicitud_importacion_partidas (id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa, valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "; $params = [ $id_solicitud, $desc, $cantCom, $cantTar, $valPart, $peso, $umId, $tasaPref ]; error_log("Insertando nueva partida: " . print_r($params, true)); } $stmt = sqlsrv_query($conn, $sql, $params); if ($stmt === false) { error_log("Error en consulta SQL: " . print_r(sqlsrv_errors(), true)); die("❌ Error procesando partida #$i: " . print_r(sqlsrv_errors(), true)); } } } // Eliminar partidas que ya no están en el formulario if (!empty($partidasExistentes)) { $ids = implode(',', $partidasExistentes); $sql = "DELETE FROM dbo.solicitud_importacion_partidas WHERE id_partida IN ($ids) AND id_solicitud = ? "; $stmt = sqlsrv_query($conn, $sql, [$id_solicitud]); if ($stmt === false) { die("❌ Error eliminando partidas obsoletas: " . print_r(sqlsrv_errors(), true)); } } } // 8) Redirigir header('Location: /IMPORTADORES/solicitud_importacion/lista?updated=ok'); exit; } /** “Soft-delete” de una factura **/ function eliminar() { if (!($_SESSION['usuario_id'] ?? false)) { header('Location: /IMPORTADORES/login'); exit; } $id = (int) ($_GET['id'] ?? 0); $conn = getConnection(); sqlsrv_query($conn, "UPDATE dbo.solicitud_importacion_factura SET status = 0, updated_at = GETDATE() WHERE id_solicitud = ? AND id_importador = ?",[$id,$_SESSION['usuario_id']]); header('Location: /IMPORTADORES/solicitud_importacion/lista?deleted=ok'); exit; } function actualizar_masivo() { header('Content-Type: application/json; charset=utf-8'); if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['success' => false, 'error' => 'No autorizado']); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'error' => 'Método no permitido']); exit; } $ids = $_POST['ids'] ?? []; $status = (int) ($_POST['status'] ?? 0); if (empty($ids) || !is_array($ids)) { echo json_encode(['success' => false, 'error' => 'No se recibieron solicitudes']); exit; } $conn = getConnection(); $usuarioId = $_SESSION['usuario_id']; $token = ($status === 2) ? getApiToken() : null; $actualizadas = 0; $fallidas = []; $pedimentosGenerados = []; foreach ($ids as $id) { $id = (int)$id; // 1. Actualizar el status $sql = "UPDATE dbo.solicitud_importacion_factura SET status = ? WHERE id_solicitud = ? AND id_importador = ?"; $stmt = sqlsrv_query($conn, $sql, [$status, $id, $usuarioId]); if ($stmt === false) { $fallidas[] = $id; continue; } $actualizadas++; // 2. Si el status es 2, generar pedimento if ($status === 2 && $token) { $res = generarPedimentoDesdeSolicitud($conn, $id, $usuarioId, $token); if ($res['success']) { $pedimentosGenerados[$id] = $res['numeroPedimento'] ?? null; } else { error_log("[actualizar_masivo] Error en pedimento solicitud $id: {$res['error']}"); } } } echo json_encode([ 'success' => true, 'actualizadas' => $actualizadas, 'fallidas' => $fallidas, 'pedimentos' => $pedimentosGenerados ]); exit; } function generarPedimentoDesdeSolicitud($conn, $id, $usuarioId, $token) { try { // Consulta solicitud $sqlSel = "SELECT *, '9999' as patente FROM dbo.solicitud_importacion_factura WHERE id_solicitud = ? AND id_importador = ?"; $stmtSel = sqlsrv_query($conn, $sqlSel, [$id, $usuarioId]); $solicitud = sqlsrv_fetch_array($stmtSel, SQLSRV_FETCH_ASSOC); if (!$solicitud) return ['success' => false, 'error' => 'Solicitud no encontrada']; // Fechas $solicitud['fecha_factura'] = $solicitud['fecha_factura'] instanceof DateTime ? $solicitud['fecha_factura'] ->format('Y-m-d') : $solicitud['fecha_factura']; $solicitud['created_at'] = $solicitud['created_at'] instanceof DateTime ? $solicitud['created_at'] ->format('Y-m-d\TH:i:s') : $solicitud['created_at']; $solicitud['updated_at'] = $solicitud['updated_at'] instanceof DateTime ? $solicitud['updated_at'] ->format('Y-m-d\TH:i:s') : $solicitud['updated_at']; // Partidas $partidas = []; $stmtPart = sqlsrv_query($conn, "SELECT * FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ?", [$id]); while ($p = sqlsrv_fetch_array($stmtPart, SQLSRV_FETCH_ASSOC)) { if ($p['creado_en'] instanceof DateTime) $p['creado_en'] = $p['creado_en']->format('Y-m-d\TH:i:s'); $p['cantidad_comercial'] = (float)$p['cantidad_comercial']; $p['cantidad_tarifa'] = (float)$p['cantidad_tarifa']; $p['valor_factura'] = (float)$p['valor_factura']; $p['peso_bruto'] = (float)$p['peso_bruto']; $p['unidad_comercial_id'] = (int)$p['unidad_comercial_id']; $partidas[] = $p; } $solicitud['numero_pedimento'] = ""; $solicitud['transporte_id'] = null; $solicitud['partidas'] = $partidas; $jsonPayload = json_encode($solicitud); $apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $urlPed = $apiBase . '/pedimentos/crearPedimento'; $ch = curl_init($urlPed); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Content-Type: application/json'], CURLOPT_POSTFIELDS => $jsonPayload, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $respPed = curl_exec($ch); $httpPed = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpPed === 200 || $httpPed === 201) { $decoded = json_decode($respPed, true); if (isset($decoded['pedimento']['PEDIMENTO'])) { $numeroPed = $decoded['pedimento']['PEDIMENTO']; sqlsrv_query($conn, "UPDATE dbo.solicitud_importacion_factura SET numero_pedimento = ? WHERE id_solicitud = ?", [$numeroPed, $id]); return ['success' => true, 'numeroPedimento' => $numeroPed]; } return ['success' => false, 'error' => 'Respuesta inválida de API']; } return ['success' => false, 'error' => "HTTP $httpPed"]; } catch (Exception $e) { return ['success' => false, 'error' => $e->getMessage()]; } } /** GET /IMPORTADORES/solicitud_importacion/ajax_proveedores * Devuelve JSON para poblar el select de Proveedor **/ function ajax_proveedores() { // 1) Asegura que la respuesta sea JSON header('Content-Type: application/json; charset=utf-8'); // 2) Obtén o renueva tu token (usa tu función existente) $token = getApiToken(); if (!$token) { echo json_encode(['results' => []]); return; } // 3) Llama al endpoint de la API de proveedores $apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $url = $apiBase . '/proveedores'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); $resp = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 4) Parsear y transformar al formato { results: [ {id,text}, … ] } $out = ['results' => []]; if ($status === 200 && ($json = json_decode($resp, true)) && is_array($json)) { foreach ($json as $p) { $clave = $p['Clave'] ?? ''; $nombre = $p['Nombre'] ?? ''; $out['results'][] = [ 'id' => $clave, 'text' => "[$clave] - $nombre" ]; } } // 5) Devolver JSON echo json_encode($out); exit; } function ajax_lista() { // 1) Fijamos el header JSON header('Content-Type: application/json; charset=utf-8'); // 2) Obtener o renovar token $token = getApiToken(); if (!$token) { error_log('[ajax_lista] Sin token válido'); // Devolvemos estructura vacía echo json_encode(['data' => []]); return; } // 3) Construimos la URL de la API $apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $url = $apiBase . '/proveedores'; error_log("[ajax_lista] GET $url"); // 4) Ejecutamos cURL $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); $resp = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); error_log("[ajax_lista] HTTP $status → $resp"); // 5) Parseamos y formateamos $dataList = []; if ($status === 200 && ($json = json_decode($resp, true)) && is_array($json)) { foreach ($json as $p) { $clave = htmlspecialchars($p['Clave'] ?? '', ENT_QUOTES); // Construir dirección $direccion = trim(implode(', ', array_filter([ $p['Calles'] ?? '', 'Num. Ext: ' . ($p['NumExt'] ?? ''), 'Num. Int: ' . ($p['NumInt'] ?? ''), $p['Colonia'] ?? '', $p['Municipio'] ?? '', $p['Ciudad'] ?? '', 'C.P. ' . ($p['CodigoPostal'] ?? ''), $p['EntidadFederativa'] ?? '', $p['Pais'] ?? '' ]))); $dataList[] = [ $clave, htmlspecialchars($p['Nombre'] ?? '', ENT_QUOTES), htmlspecialchars($p['RFC'] ?? '', ENT_QUOTES), htmlspecialchars($p['Ciudad'] ?? '', ENT_QUOTES), htmlspecialchars($p['Telefono'] ?? '', ENT_QUOTES), htmlspecialchars($direccion ?? '', ENT_QUOTES), // Acciones "✏️ " ]; } } else { error_log('[ajax_lista] Respuesta inválida o status != 200'); } // 6) Devolvemos siempre HTTP 200 con data (posiblemente vacío) echo json_encode(['data' => $dataList]); } function update_status() { header('Content-Type: application/json; charset=utf-8'); if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['error' => 'No autorizado']); exit; } $id = intval($_POST['id'] ?? 0); $status = intval($_POST['status'] ?? 0); // 1) Conexión a la BD $conn = getConnection(); // 2) Actualizar el status en la tabla solicitud_importacion_factura $sql = "UPDATE dbo.solicitud_importacion_factura SET status = ? WHERE id_solicitud=? AND id_importador=?"; $params = [$status, $id, $_SESSION['usuario_id']]; $stmt = sqlsrv_query($conn, $sql, $params); if ($stmt === false) { http_response_code(500); echo json_encode(['error' => 'Error al actualizar status']); exit; } // 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) { // 4.1) Obtener o renovar el token de la API $token = getApiToken(); if (!$token) { error_log("[update_status] No se pudo obtener token para crearPedimento"); echo json_encode(['success' => true, 'warning' => 'No se generó pedimento: sin token']); exit; } // 4.2) Leer de la BD todos los campos de la cabecera de la solicitud $sqlSel = "SELECT s.id_solicitud, s.id_importador, s.aduana, s.numero_pedimento, s.anexo22_apendice, s.numero_factura, s.fecha_factura, s.incoterm, s.pais_proveedor, s.tipo_moneda, s.valor_factura, s.vinculacion, s.transportista_id, s.chofer_id, s.foto_solicitud_url, s.proveedor_clave, s.created_at, s.updated_at, '9999' as patente FROM dbo.solicitud_importacion_factura s WHERE s.id_solicitud = ? AND s.id_importador = ? "; $stmtSel = sqlsrv_query($conn, $sqlSel, [$id, $_SESSION['usuario_id']]); if ($stmtSel === false) { error_log("[update_status] Error al consultar solicitud: " . print_r(sqlsrv_errors(), true)); echo json_encode(['success' => true, 'warning' => 'Status actualizado, fallo al leer datos para pedimento']); exit; } $solicitud = sqlsrv_fetch_array($stmtSel, SQLSRV_FETCH_ASSOC); sqlsrv_free_stmt($stmtSel); if (!$solicitud) { error_log("[update_status] No se encontró la solicitud para id={$id}"); echo json_encode(['success' => true, 'warning' => 'Status actualizado, solicitud no encontrada para pedimento']); exit; } // 4.3) Formatear fechas (si vienen como DateTime) antes de construir JSON $fechaFactura = ($solicitud['fecha_factura'] instanceof DateTime) ? $solicitud['fecha_factura']->format('Y-m-d') : $solicitud['fecha_factura']; $createdAt = ($solicitud['created_at'] instanceof DateTime) ? $solicitud['created_at']->format('Y-m-d\TH:i:s') : $solicitud['created_at']; $updatedAt = ($solicitud['updated_at'] instanceof DateTime) ? $solicitud['updated_at']->format('Y-m-d\TH:i:s') : $solicitud['updated_at']; // 4.4) Obtener todas las partidas asociadas a esta solicitud $sqlPart = "SELECT p.id_partida, p.id_solicitud, p.descripcion, p.creado_en, p.cantidad_comercial, p.cantidad_tarifa, p.valor_factura, p.peso_bruto, p.unidad_comercial_id, p.tasa_preferencial FROM dbo.solicitud_importacion_partidas p WHERE p.id_solicitud = ? ORDER BY p.id_partida "; $stmtPart = sqlsrv_query($conn, $sqlPart, [$id]); if ($stmtPart === false) { error_log("[update_status] Error al consultar partidas: " . print_r(sqlsrv_errors(), true)); echo json_encode(['success' => true, 'warning' => 'Status actualizado, fallo al leer partidas para pedimento']); exit; } $partidas = []; while ($row = sqlsrv_fetch_array($stmtPart, SQLSRV_FETCH_ASSOC)) { if ($row['creado_en'] instanceof DateTime) { $row['creado_en'] = $row['creado_en']->format('Y-m-d\TH:i:s'); } $row['cantidad_comercial'] = floatval($row['cantidad_comercial']); $row['cantidad_tarifa'] = floatval($row['cantidad_tarifa']); $row['valor_factura'] = floatval($row['valor_factura']); $row['peso_bruto'] = floatval($row['peso_bruto']); $row['unidad_comercial_id'] = intval($row['unidad_comercial_id']); $partidas[] = $row; } sqlsrv_free_stmt($stmtPart); // 4.5) Construir el arreglo PHP con la misma estructura JSON que envías $payload = [ "id_solicitud" => intval($solicitud['id_solicitud']), "id_importador" => intval($solicitud['id_importador']), "aduana" => strval($solicitud['aduana']), "patente" => strval($solicitud['patente']), "anexo22_apendice" => strval($solicitud['anexo22_apendice']), "numero_factura" => strval($solicitud['numero_factura']), "fecha_factura" => $fechaFactura, "numero_pedimento" => "", // La API lo rellenará "incoterm" => strval($solicitud['incoterm']), "pais_proveedor" => strval($solicitud['pais_proveedor']), "tipo_moneda" => strval($solicitud['tipo_moneda']), "valor_factura" => floatval($solicitud['valor_factura']), "vinculacion" => intval($solicitud['vinculacion']), "transportista_id" => intval($solicitud['transportista_id']), "created_at" => $createdAt, "updated_at" => $updatedAt, "transporte_id" => null, "chofer_id" => intval($solicitud['chofer_id']), "foto_solicitud_url" => strval($solicitud['foto_solicitud_url']), "proveedor_clave" => strval($solicitud['proveedor_clave']), "partidas" => $partidas ]; $jsonPayload = json_encode($payload); // 3.6) Preparar cURL para hacer POST a /pedimentos/crearPedimento $apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $urlPed = $apiBase . '/pedimentos/crearPedimento'; $ch = curl_init($urlPed); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_HTTPHEADER => [ "Authorization: Bearer $token", 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => $jsonPayload, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $respPed = curl_exec($ch); $httpPed = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); // 4.7) Verificar respuesta del endpoint pedimentos if ($httpPed === 201 || $httpPed === 200) { $decoded = json_decode($respPed, true); // Si existe PEDIMENTO dentro de la clave 'pedimento' if (isset($decoded['pedimento']['PEDIMENTO'])) { $numeroPedimento = $decoded['pedimento']['PEDIMENTO']; // 3.8) Actualizar localmente el campo numero_pedimento $sqlUpdPed = "UPDATE dbo.solicitud_importacion_factura SET numero_pedimento = ? WHERE id_solicitud = ? AND id_importador = ?"; $stmtUpdPed = sqlsrv_query($conn, $sqlUpdPed, [$numeroPedimento, $id, $_SESSION['usuario_id']]); if ($stmtUpdPed === false) { error_log("[update_status] Error al actualizar numero_pedimento en BD: " . print_r(sqlsrv_errors(), true)); } } error_log("[update_status] Pedimento creado con éxito para solicitud $id → HTTP $httpPed → $respPed"); echo json_encode([ 'success' => true, 'pedimento' => $decoded['pedimento'], 'numeroPedimentoLocal' => $numeroPedimento ]); exit; } else { error_log("[update_status] Error al crear pedimento (HTTP $httpPed): $respPed · cURL error: $curlErr"); echo json_encode([ 'success' => true, 'warning' => "Status actualizado, pero fallo al crear pedimento (HTTP $respPed)" ]); exit; } } // 5) Si el status no es 2, devolvemos normal echo json_encode(['success' => true]); 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 aduanal', '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'] ? '
📧 Copia enviada a correo adicional
' : ''; return "

{$datos['statusEmoji']} Estado Actualizado

$tipoNotificacion

Hola {$datos['nombreCompleto']},

El estado de tu solicitud de importación ha sido actualizado:

ID Solicitud: {$datos['idSolicitud']}
Número de Factura: {$datos['numeroFactura']}
Fecha de Factura: {$datos['fechaFactura']}
{$datos['statusEmoji']} Nuevo Estado: {$datos['statusDescripcion']}
Valor: {$datos['valorFactura']} {$datos['tipoMoneda']}

Puedes consultar todos los detalles y el seguimiento completo accediendo a tu panel de control.

📧 Notificación automática
Este correo se envía automáticamente cuando se actualiza el estado de tu solicitud.
Fecha de envío: {$datos['fechaActual']}

© {$datos['anioActual']} SIIH · Desarrollado por AduanaSoft
Sistema Integral para Importadores de Hidrocarburos
"; } // Función para generar el PDF function pdf() { // Verificar que se recibió el ID if (!isset($_GET['id'])) { http_response_code(400); echo "ID de solicitud requerido"; return; } $id_solicitud = (int)$_GET['id']; try { // 1. Conexión $conn = getConnection(); // 2. Obtener datos de la solicitud principal $sql = "SELECT s.*, i.nombre as importador_nombre, i.rfc as importador_rfc, i.calle, i.num_exterior, i.num_interior, i.ciudad, i.colonia, i.codigo_postal, i.estado, i.telefono, i.correo, t.nombre as transportista_nombre, c.nombre as chofer_nombre FROM solicitud_importacion_factura s LEFT JOIN informacion_general i ON s.id_importador = i.id_usuario LEFT JOIN transportistas t ON s.transportista_id = t.id_transportista LEFT JOIN choferes c ON s.chofer_id = c.id_chofer WHERE s.id_solicitud = ? "; $params = array($id_solicitud); $stmt = sqlsrv_query($conn, $sql, $params); if ($stmt === false) { throw new Exception("Error en la consulta: " . print_r(sqlsrv_errors(), true)); } $solicitud = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); sqlsrv_free_stmt($stmt); if (!$solicitud) { http_response_code(404); echo "Solicitud no encontrada"; return; } // 2.5. Obtener información del proveedor desde API $proveedor_info = null; if (!empty($solicitud['proveedor_clave'])) { $proveedor_info = obtenerProveedorPorClave($solicitud['proveedor_clave']); } // 3. Obtener partidas de la solicitud $sql = "SELECT p.*, u.descripcion as unidad_descripcion FROM solicitud_importacion_partidas p LEFT JOIN unidades_medida_apendice7 u ON p.unidad_comercial_id = u.id WHERE p.id_solicitud = ? ORDER BY p.id_partida "; $params = array($id_solicitud); $stmt = sqlsrv_query($conn, $sql, $params); if ($stmt === false) { throw new Exception("Error en la consulta de partidas: " . print_r(sqlsrv_errors(), true)); } $partidas = array(); while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $partidas[] = $row; } sqlsrv_free_stmt($stmt); // 4. Configuración del sistema $config = "SELECT * FROM configuracion_sistema"; $stmt = sqlsrv_query($conn, $config); if ($stmt === false) { throw new Exception("Error en la consulta: " . print_r(sqlsrv_errors(), true)); } $configuracion = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); // 5. Generar HTML del PDF $html = generarHTMLPDF($solicitud, $partidas, $configuracion, $proveedor_info); // 6. Generar PDF usando DomPDF con Composer require_once __DIR__ . '/../../vendor/autoload.php'; // Ajusta ruta si es necesario $options = new Options(); $options->set('defaultFont', 'Arial'); $options->set('isRemoteEnabled', true); // Permite cargar imágenes remotas si es necesario $options->set('isHtml5ParserEnabled', true); // Habilita el parser HTML5 $options->set('isPhpEnabled', true); // Habilita PHP dentro del HTML si es necesario $options->set('chroot', $_SERVER['DOCUMENT_ROOT']); // Asegura que las rutas relativas funcionen correctamente $dompdf = new Dompdf($options); $dompdf->loadHtml($html); $dompdf->setPaper('A4', 'portrait'); $dompdf->render(); // Enviar el PDF al navegador header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="solicitud_importacion_' . $id_solicitud . '.pdf"'); echo $dompdf->output(); } catch (Exception $e) { error_log("Error generando PDF: " . $e->getMessage()); http_response_code(500); echo "Error interno del servidor: " . $e->getMessage(); } } // NUEVA FUNCIÓN: Obtener información del proveedor por clave function obtenerProveedorPorClave($clave) { // Obtener token de la API $token = getApiToken(); if (!$token) { error_log('[obtenerProveedorPorClave] Sin token válido'); return null; } // Construir URL de la API $apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $url = $apiBase . '/proveedores'; // Ejecutar cURL para obtener todos los proveedores $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $resp = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($status !== 200) { error_log("[obtenerProveedorPorClave] Error HTTP $status al obtener proveedores"); return null; } $proveedores = json_decode($resp, true); if (!is_array($proveedores)) { error_log('[obtenerProveedorPorClave] Respuesta de API no es un array válido'); return null; } // Buscar el proveedor por clave foreach ($proveedores as $proveedor) { // Probar tanto 'Clave' como 'CLAVE' por si acaso $proveedor_clave = $proveedor['CLAVE'] ?? $proveedor['Clave'] ?? null; if ($proveedor_clave === $clave) { return $proveedor; } } error_log("[obtenerProveedorPorClave] Proveedor con clave '$clave' no encontrado"); return null; } function generarHTMLPDF($solicitud, $partidas, $configuracion, $proveedor_info = null) { // Formatear fecha $fecha_expedicion = $solicitud['fecha_factura']->format('d/m/Y'); $fecha_vencimiento = $solicitud['fecha_factura']->modify('+30 days')->format('d/m/Y'); // Construir dirección del importador $direccion_completa = trim( ($solicitud['calle'] ?? '') . ' ' . ($solicitud['num_exterior'] ?? '') . ' ' . ($solicitud['num_interior'] ? 'Int. ' . $solicitud['num_interior'] : '') . ', ' . ($solicitud['colonia'] ?? '') . ', ' . ($solicitud['ciudad'] ?? '') . ', ' . ($solicitud['estado'] ?? '') . ' ' . ($solicitud['codigo_postal'] ?? '') ); // Construir información del proveedor $proveedor_nombre = 'Proveedor no disponible'; $proveedor_rfc = 'RFC no disponible'; $proveedor_direccion = 'Dirección no disponible'; $proveedor_telefono = 'Teléfono no disponible'; // Construir información del proveedor if ($proveedor_info) { // Usar nombres de campos en mayúsculas según la estructura de la tabla $proveedor_nombre = $proveedor_info['Nombre'] ?? 'Nombre no disponible'; $proveedor_rfc = $proveedor_info['IdentFiscal'] ??$proveedor_info['RFC'] ?? 'RFC no disponible'; $proveedor_telefono = trim($proveedor_info['Telefono'] ?? 'Teléfono no disponible'); // Construir dirección del proveedor usando los campos correctos $direccion_partes = array_filter([ $proveedor_info['Calles'] ?? '', $proveedor_info['Colonia'] ?? '', $proveedor_info['Municipio'] ?? '', $proveedor_info['Ciudad'] ?? '', ($proveedor_info['CodigoPostal'] ?? '') ? 'C.P. ' . $proveedor_info['CodigoPostal'] : '', $proveedor_info['EntidadFederativa'] ?? '', $proveedor_info['Pais'] ?? '' ]); if (!empty($direccion_partes)) { $proveedor_direccion = implode(', ', $direccion_partes); } } // Calcular el total sumando todas las partidas $total = 0; foreach ($partidas as $partida) { $total += (float)($partida['valor_factura'] ?? 0); } // Función para convertir número a texto function numeroATexto($numero) { $unidades = ['', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve']; $decenas = ['', '', 'veinte', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa']; $especiales = ['diez', 'once', 'doce', 'trece', 'catorce', 'quince', 'dieciséis', 'diecisiete', 'dieciocho', 'diecinueve']; $centenas = ['', 'ciento', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos']; if ($numero == 0) return 'cero'; if ($numero == 100) return 'cien'; if ($numero == 1000) return 'mil'; if ($numero == 1000000) return 'un millón'; $resultado = ''; // Millones if ($numero >= 1000000) { $millones = intval($numero / 1000000); if ($millones == 1) { $resultado .= 'un millón '; } else { $resultado .= numeroATexto($millones) . ' millones '; } $numero %= 1000000; } // Miles if ($numero >= 1000) { $miles = intval($numero / 1000); if ($miles == 1) { $resultado .= 'mil '; } else { $resultado .= numeroATexto($miles) . ' mil '; } $numero %= 1000; } // Centenas if ($numero >= 100) { $resultado .= $centenas[intval($numero / 100)] . ' '; $numero %= 100; } // Decenas y unidades if ($numero >= 20) { $resultado .= $decenas[intval($numero / 10)]; if ($numero % 10 != 0) { $resultado .= ' y ' . $unidades[$numero % 10]; } } elseif ($numero >= 10) { $resultado .= $especiales[$numero - 10]; } elseif ($numero > 0) { $resultado .= $unidades[$numero]; } return trim($resultado); } // Obtener la moneda de la solicitud o usar MXN por defecto $moneda_codigo = $solicitud['tipo_moneda'] ?? 'MXN'; // Configuración de monedas $monedas_config = [ 'MXN' => ['nombre' => 'PESOS', 'sufijo' => 'M.N.', 'centavos' => 'CENTAVOS'], 'USD' => ['nombre' => 'DÓLARES', 'sufijo' => 'USD', 'centavos' => 'CENTAVOS'], 'EUR' => ['nombre' => 'EUROS', 'sufijo' => 'EUR', 'centavos' => 'CÉNTIMOS'], 'CNY' => ['nombre' => 'YUANES', 'sufijo' => 'CNY', 'centavos' => 'JIAO'], 'GBP' => ['nombre' => 'LIBRAS', 'sufijo' => 'GBP', 'centavos' => 'PENIQUES'], 'JPY' => ['nombre' => 'YENES', 'sufijo' => 'JPY', 'centavos' => 'SEN'] ]; $config_moneda = $monedas_config[$moneda_codigo] ?? $monedas_config['MXN']; // Convertir total a texto $partes = explode('.', number_format($total, 2, '.', '')); $enteros = (int)$partes[0]; $decimales = (int)$partes[1]; $total_texto = strtoupper(numeroATexto($enteros)) . ' ' . $config_moneda['nombre']; // Para JPY no se usan decimales tradicionalmente if ($moneda_codigo === 'JPY') { $total_texto .= ' ' . $config_moneda['sufijo']; } else { if ($decimales > 0) { $total_texto .= ' CON ' . str_pad($decimales, 2, '0', STR_PAD_LEFT) . '/100 ' . $config_moneda['sufijo']; } else { $total_texto .= ' 00/100 ' . $config_moneda['sufijo']; } } $html = ' Solicitud de Importación

' . htmlspecialchars($solicitud['importador_nombre']) . '
' . htmlspecialchars($direccion_completa ?: 'Dirección no disponible') . '
RFC: ' . htmlspecialchars($solicitud['importador_rfc'] ?? 'No disponible') . '
Tel: ' . htmlspecialchars($solicitud['telefono'] ?? 'No disponible') . '
Email: ' . htmlspecialchars($solicitud['correo'] ?? 'No disponible') . '
' . htmlspecialchars($configuracion['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos') . '

Solicitud de Importación
No. ' . htmlspecialchars($solicitud['id_solicitud']) . '
RAZÓN SOCIAL: ' . htmlspecialchars($proveedor_nombre) . '
DIRECCIÓN: ' . htmlspecialchars($proveedor_direccion) . '
RFC: ' . htmlspecialchars($proveedor_rfc) . '
FECHA DE EXPEDICIÓN
' . $fecha_expedicion . '
FECHA DE VENCIMIENTO
' . $fecha_vencimiento . '
CLAVE: ' . htmlspecialchars($solicitud['proveedor_clave'] ?? 'No disponible') . ' TELÉFONO: ' . htmlspecialchars($proveedor_telefono) . '
'; // Agregar partidas foreach ($partidas as $partida) { $precio_unitario = (float)($partida['precio_unitario'] ?? 0); $cantidad = (float)($partida['cantidad_comercial'] ?? 0); $valor_partida = (float)($partida['valor_factura'] ?? 0); $html .= ' '; } $html .= '
Producto Unidad de Medida Precio Unitario Cantidad Total
' . htmlspecialchars($partida['descripcion']) . ' ' . htmlspecialchars($partida['unidad_descripcion'] ?? 'Unidad de servicio (E48)') . ' ' . $moneda_codigo . ' ' . number_format($precio_unitario > 0 ? $precio_unitario : $valor_partida, 2) . ' ' . number_format($cantidad > 0 ? $cantidad : 1, 0) . ' ' . $moneda_codigo . ' ' . number_format($valor_partida, 2) . '
Total: ' . $moneda_codigo . ' ' . number_format($total, 2) . '
'; return $html; } // NUEVO ENDPOINT: Pedimentos de catálogo activos del importador para panel de referencia function ajax_pedimentos_catalogo() { header('Content-Type: application/json; charset=utf-8'); if (!($_SESSION['usuario_id'] ?? false)) { http_response_code(401); echo json_encode(['data' => []]); exit; } $id_usuario = $_SESSION['usuario_id']; $limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10; $conn = getConnection(); // Obtener RFC del importador $sqlImportador = "SELECT rfc FROM informacion_general WHERE id_usuario = ?"; $stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]); if ($stmtImportador === false) { echo json_encode(['data' => []]); exit; } $importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC); if (!$importador) { echo json_encode(['data' => []]); exit; } // Obtener pedimentos activos del catálogo $sql = "SELECT TOP {$limit} IdPrevio, Pedimento, ClienteNombre, ClavePed, Timestamp FROM PREVIOS_COMPARTIDOS_WS WHERE ClienteRFC = ? AND Status = 1 ORDER BY Timestamp DESC"; $stmt = sqlsrv_query($conn, $sql, [$importador['rfc']]); $pedimentos = []; if ($stmt !== false) { while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { $fecha = ''; if ($row['Timestamp'] instanceof DateTime) { $fecha = $row['Timestamp']->format('d/m/Y'); } $pedimentos[] = [ 'numero' => $row['Pedimento'], 'cliente' => $row['ClienteNombre'], 'clave' => $row['ClavePed'], 'fecha' => $fecha ]; } } echo json_encode(['data' => $pedimentos]); exit; } function ajax_facturas_por_pedimento() { header('Content-Type: application/json'); $id_previo = isset($_GET['id_pedimento']) ? trim($_GET['id_pedimento']) : ''; if (!$id_previo) { echo json_encode(['success' => false, 'error' => 'ID de pedimento no válido']); return; } $conn = getConnection(); // 1. Buscar el número de pedimento real en PREVIOS_COMPARTIDOS_WS $stmt = sqlsrv_query($conn, "SELECT Pedimento FROM PREVIOS_COMPARTIDOS_WS WHERE IdPrevio = ?", [$id_previo]); if ($stmt === false || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) { echo json_encode(['success' => false, 'error' => 'No se encontró el pedimento en PREVIOS_COMPARTIDOS_WS']); return; } $numero_pedimento = $row['Pedimento']; // 2. Buscar las facturas en solicitud_importacion_factura usando el número de pedimento // ✅ CORREGIDO: Incluir id_solicitud como id_factura $stmt2 = sqlsrv_query($conn, "SELECT id_solicitud, numero_factura, fecha_factura, valor_factura FROM solicitud_importacion_factura WHERE numero_pedimento = ?", [$numero_pedimento]); if ($stmt2 === false) { echo json_encode(['success' => false, 'error' => 'Error en la consulta de facturas']); return; } $facturas = []; while ($row2 = sqlsrv_fetch_array($stmt2, SQLSRV_FETCH_ASSOC)) { $facturas[] = [ 'id_factura' => $row2['id_solicitud'], // ✅ AGREGADO: Usar id_solicitud como id_factura 'numero_factura' => $row2['numero_factura'], 'fecha' => ($row2['fecha_factura'] instanceof DateTime) ? $row2['fecha_factura']->format('Y-m-d') : $row2['fecha_factura'], 'monto' => $row2['valor_factura'] ]; } echo json_encode(['success' => true, 'facturas' => $facturas]); }