#!/usr/bin/env node 'use strict'; const fs = require('fs/promises'); const path = require('path'); const https = require('https'); const BASE_DIR = path.join(__dirname, '../data/jumbo'); const COOKIES_FILE = path.join(BASE_DIR, 'session-cookies.json'); const OUT_JSON = path.join(BASE_DIR, 'orders-latest.json'); const OUT_CSV = path.join(BASE_DIR, 'orders-latest.csv'); const ARCHIVE_DIR = path.join(BASE_DIR, 'archive'); const HOSTNAME = 'www.jumbo.com.ar'; const PER_PAGE = 50; const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0.0.0 Safari/537.36'; function csvEscape(v) { if (v === null || v === undefined) return ''; const s = String(v); return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; } function toCsv(orders) { const cols = ['orderId', 'creationDate', 'status', 'totalARS', 'items', 'paymentMethod']; const rows = orders.map(o => [ o.orderId ?? '', o.creationDate ?? '', o.statusDescription ?? o.status ?? '', o.totalValue != null ? Math.round(Number(o.totalValue)) / 100 : '', (o.items || []).map(i => `${i.quantity}x ${i.name}`).join(' | '), o.paymentNames ?? '', ]); return [cols.join(','), ...rows.map(r => r.map(csvEscape).join(','))].join('\n') + '\n'; } function timestampForFile() { return new Date().toISOString().replace(/[:.]/g, '-'); } function httpGet(urlPath, cookieHeader) { return new Promise((resolve, reject) => { const req = https.request({ hostname: HOSTNAME, path: urlPath, method: 'GET', headers: { accept: 'application/json', referer: `https://${HOSTNAME}/account#/orders`, cookie: cookieHeader, 'user-agent': UA, }}, (res) => { let body = ''; res.on('data', c => body += c); res.on('end', () => resolve({ status: res.statusCode, body })); }); req.on('error', reject); req.setTimeout(20000, () => { req.destroy(); reject(new Error('timeout')); }); req.end(); }); } async function loadCookies() { let raw; try { raw = await fs.readFile(COOKIES_FILE, 'utf8'); } catch (_) { console.error('ERROR: Run jumbo_export_cookies.js first.'); process.exit(2); } const cookies = JSON.parse(raw).cookies || []; if (!cookies.some(c => c.name.includes('VtexIdclientAutCookie'))) { console.error('ERROR: No auth cookie. Re-run jumbo_export_cookies.js.'); process.exit(3); } return cookies.map(c => `${c.name}=${c.value}`).join('; '); } async function fetchPage(cookieHeader, page) { const res = await httpGet(`/api/oms/user/orders?page=${page}&per_page=${PER_PAGE}&orderBy=creationDate,desc`, cookieHeader); if (res.status === 401 || res.status === 403) { console.error(`ERROR: Session expired (HTTP ${res.status}). Re-run jumbo_export_cookies.js.`); process.exit(7); } if (res.status !== 200) { console.error(`ERROR: HTTP ${res.status}`); process.exit(8); } return JSON.parse(res.body); } async function fetchDetail(cookieHeader, orderId) { const res = await httpGet(`/api/oms/user/orders/${orderId}`, cookieHeader); if (res.status !== 200) return null; try { return JSON.parse(res.body); } catch (_) { return null; } } async function main() { const cookieHeader = await loadCookies(); console.log('Fetching Jumbo order list...'); const first = await fetchPage(cookieHeader, 1); const total = first.paging?.total ?? 0; const totalPages = first.paging?.pages ?? 1; console.log(`Total orders: ${total}, pages: ${totalPages}`); let orders = [...(first.list || [])]; for (let p = 2; p <= totalPages; p++) { process.stdout.write(` page ${p}/${totalPages}...\r`); const page = await fetchPage(cookieHeader, p); orders = orders.concat(page.list || []); } console.log(`Fetching details for ${orders.length} orders...`); const enriched = []; for (let i = 0; i < orders.length; i++) { process.stdout.write(` ${i+1}/${orders.length}: ${orders[i].orderId} \r`); const detail = await fetchDetail(cookieHeader, orders[i].orderId); enriched.push(detail ? { ...orders[i], items: detail.items ?? [] } : orders[i]); if (i < orders.length - 1) await new Promise(r => setTimeout(r, 120)); } console.log(); await fs.mkdir(ARCHIVE_DIR, { recursive: true }); const archivePath = path.join(ARCHIVE_DIR, `orders-${timestampForFile()}.json`); const payload = { fetchedAt: new Date().toISOString(), mode: 'http-cookie', count: enriched.length, orders: enriched }; await Promise.all([ fs.writeFile(OUT_JSON, JSON.stringify(payload, null, 2)), fs.writeFile(archivePath, JSON.stringify(payload, null, 2)), fs.writeFile(OUT_CSV, toCsv(enriched)), ]); console.log(`OK jumbo orders=${enriched.length} json=${OUT_JSON} csv=${OUT_CSV}`); } main().catch(e => { console.error('ERROR:', e.message); process.exit(1); });