#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const puppeteer = require('puppeteer'); const SHALOM_SECRET = '.Ov3rsku112024l4r43l.'; const SHALOM_ENCRYPTION_KEY_B64 = 'uQn/bQ94PXBEfId70zjN+VE1hSU7kh9VBXTOUd68Ssc='; const SHALOM_TRACKING_HOME = 'https://shalom.com.pe/rastrea'; const SHALOM_SEARCH_URL = 'https://serviceswebapi.shalomcontrol.com/api/v1/web/rastrea/buscar'; const SHALOM_STATUS_URL = 'https://serviceswebapi.shalomcontrol.com/api/v1/web/rastrea/estados'; function detectChromeExecutable() { const candidates = []; const cacheRoot = '/home/ubuntu/.cache/puppeteer/chrome'; try { if (fs.existsSync(cacheRoot)) { const versions = fs.readdirSync(cacheRoot).sort().reverse(); for (const version of versions) { candidates.push(path.join(cacheRoot, version, 'chrome-linux64', 'chrome')); } } } catch (error) { // Ignoramos y seguimos con rutas fijas. } if (process.env.PUPPETEER_EXECUTABLE_PATH) { candidates.unshift(process.env.PUPPETEER_EXECUTABLE_PATH); } candidates.push( '/usr/bin/google-chrome-stable', '/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser' ); return candidates.find((candidate) => candidate && fs.existsSync(candidate)) || null; } function uuidv4() { const bytes = crypto.randomBytes(16); bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; const hex = bytes.toString('hex'); return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; } function buildAuthorizationHeader() { const token = `web-${uuidv4()}`; const expiresAt = Math.floor(Date.now() / 1000) + 300; const payload = `${token}@${expiresAt}`; const signature = crypto.createHmac('sha256', SHALOM_SECRET).update(payload).digest('hex'); return `Bearer ${payload}@${signature}`; } function decryptShalomPayload(payload) { if (!payload || payload.encrypted !== true || !payload.data) { return payload; } const key = Buffer.from(SHALOM_ENCRYPTION_KEY_B64, 'base64'); if (key.length !== 32) { throw new Error('No se pudo preparar la clave de descifrado.'); } const cipherBuffer = Buffer.from(String(payload.data), 'base64'); if (cipherBuffer.length <= 16) { throw new Error('La respuesta cifrada de Shalom es inválida.'); } const iv = cipherBuffer.subarray(0, 16); const ciphertext = cipherBuffer.subarray(16); const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); const plainText = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8'); try { return JSON.parse(plainText); } catch (error) { return plainText; } } async function obtainRecaptchaToken(page) { try { await page.goto(SHALOM_TRACKING_HOME, { waitUntil: 'domcontentloaded', timeout: 60000, }); } catch (error) { // La página puede reintentar/cargar de nuevo por el limpiador de service workers. } await page.waitForFunction(() => window.RECAPTCHA_SITE_KEY && window.grecaptcha, { timeout: 60000, }); return page.evaluate(async () => { await new Promise((resolve) => window.grecaptcha.ready(resolve)); return window.grecaptcha.execute(window.RECAPTCHA_SITE_KEY, { action: 'rastrea_buscar' }); }); } async function postShalomApi(url, fields) { const form = new FormData(); for (const [key, value] of Object.entries(fields)) { if (value === undefined || value === null) continue; form.append(key, String(value)); } const response = await fetch(url, { method: 'POST', body: form, headers: { Accept: 'application/json', Authorization: buildAuthorizationHeader(), Origin: 'https://shalom.com.pe', Referer: 'https://shalom.com.pe/', 'X-Requested-With': 'XMLHttpRequest', }, }); const rawText = await response.text(); let wrappedPayload = null; try { wrappedPayload = JSON.parse(rawText); } catch (error) { return { success: false, error: `Respuesta no JSON de Shalom (${response.status}).`, details: rawText.trim() ? rawText.slice(0, 1200) : 'Sin contenido útil.', }; } let plainPayload = wrappedPayload; try { plainPayload = decryptShalomPayload(wrappedPayload); } catch (error) { return { success: false, error: `No se pudo descifrar la respuesta de Shalom (${response.status}).`, details: error.message, }; } if (response.status >= 400) { const message = plainPayload && typeof plainPayload === 'object' ? String(plainPayload.message || plainPayload.error || '').trim() : ''; return { success: false, error: message || `Shalom respondió HTTP ${response.status}.`, details: plainPayload, }; } if (!plainPayload || typeof plainPayload !== 'object') { return { success: false, error: 'Shalom devolvió una respuesta vacía o inválida.', details: plainPayload, }; } if (plainPayload.success === false) { return { success: false, error: String(plainPayload.message || plainPayload.error || 'Shalom rechazó la solicitud.'), details: plainPayload, }; } return { success: true, data: plainPayload, }; } function printJson(payload) { console.log(JSON.stringify(payload)); } async function main() { const orderNumber = String(process.argv[2] || '').trim(); const orderCode = String(process.argv[3] || '').trim(); if (!orderNumber || !orderCode) { printJson({ success: false, error: 'Número de orden y código de orden son requeridos.', }); process.exit(1); } let browser = null; try { const executablePath = detectChromeExecutable(); if (!executablePath) { printJson({ success: false, error: 'No se encontró un navegador Chromium compatible para consultar Shalom.', }); process.exit(2); } browser = await puppeteer.launch({ headless: 'new', executablePath, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-features=SSLCommonNameMismatchHandling', ], }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 900 }); await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'); const recaptchaToken = await obtainRecaptchaToken(page); const searchResult = await postShalomApi(SHALOM_SEARCH_URL, { numero: orderNumber, codigo: orderCode, ose_id: '', recaptcha_token: recaptchaToken, }); if (!searchResult.success) { printJson(searchResult); process.exit(3); } const searchPayload = searchResult.data; const oseId = searchPayload && searchPayload.data ? searchPayload.data.ose_id : null; if (!oseId) { printJson({ success: false, error: 'La búsqueda de Shalom no devolvió un N° interno válido.', details: searchPayload, }); process.exit(4); } const statusResult = await postShalomApi(SHALOM_STATUS_URL, { ose_id: String(oseId), }); if (!statusResult.success) { printJson(statusResult); process.exit(5); } const statusPayload = statusResult.data; const trackingUrl = `${SHALOM_TRACKING_HOME}/${encodeURIComponent(String(oseId))}/${encodeURIComponent(orderCode)}`; printJson({ success: true, source: 'shalom_search_api', search: searchPayload, status: statusPayload, resolved: { ose_id: oseId, tracking_url: trackingUrl, }, }); } catch (error) { printJson({ success: false, error: error && error.message ? error.message : 'Error al consultar Shalom.', details: error && error.stack ? error.stack.split('\n').slice(0, 4).join(' | ') : undefined, }); process.exit(6); } finally { if (browser) { try { await browser.close(); } catch (error) { // Ignoramos errores al cerrar el navegador. } } } } main();