# Promos/Descuentos Scraping — Arquitectura (2026-03-05, actualizado 2026-03-06) ## Resumen La tarea es scrapear descuentos y promos de los supermercados (producto + bancarias/medios de pago). Hay DOS tipos de datos completamente distintos que requieren approachs diferentes: 1. **Promos de producto** (2x1, 2do al 50%, 3x2) → VTEX Teasers en product search API 2. **Promos bancarias/por día** (Lunes 25% con Visa, etc.) → **YA RESUELTO CON APIs DIRECTAS** --- ## PROMOS BANCARIAS — APIs Directas (Sin Browser) ✅ COMPLETADO ### Script principal: `scripts/fetch_bank_promos.js` - Autónomo, sin browser, llama APIs directas de cada súper - Output: `data/discounts/bank-promos-latest.json` + `super-ranking-report/bank-promos-data.js` - Ejecución: `node /home/ubuntu/.openclaw/workspace/scripts/fetch_bank_promos.js` - **Cron systemd**: corre automáticamente 2x día (09:00 y 21:00 UTC = 06:00 y 18:00 AR) - `sudo systemctl status bank-promos-fetch.timer` - Log: `data/discounts/fetch-bank-promos.log` ### Resultados por tienda (típico): - Coto: ~87 promos/semana (indexadas por cada día) - Jumbo: ~117 promos (explodidas por día) - Disco: ~117 promos (misma fuente que Jumbo) - Carrefour: ~52 promos - Changomas: ~48 promos - DIA: sin API pública → 0 (ver abajo) --- ## APIs por Tienda — Detalle Técnico ### Coto (plataforma ATG, NO VTEX) ``` GET https://www.cotodigital.com.ar/rest/model/atg/actors/cProfileActor/getPromociones?enviroment=ag ``` Respuesta: `{ result: { porDia: [{dia, arrayItem:[{titDescuento,titDescuentoDet,titDias,titFooter,imagenPath}]}], porBanco:[...], conComunidad:[...] } }` - `porDia`: array de 7 grupos (uno por día), cada uno con `arrayItem` - `porBanco`: array de bancos, cada uno con `arrayItem` - `arrayItem[i].titDescuento` = descuento (ej: "12 cuotas sin interés") - `arrayItem[i].titDias` = validez (ej: "De Lunes a Domingo") - `arrayItem[i].imagenPath` = logo del banco (ej: `logo_macro_bma2.png`) - `arrayItem[i].titFooter` = texto legal/footer - **AVISO**: Páginas web de Coto bloqueadas por WAF desde la VPS. SIEMPRE usar API ATG. ### Jumbo + Disco (VTEX Master Data — MISMA FUENTE) ``` GET https://www.jumbo.com.ar/api/dataentities/JN/documents/bankDiscount?_fields=value,id&an=jumboargentina&_size=200 ``` Respuesta: `{ value: "[{banks,days,discount,discountText,dateEnd,dateStart,legals,checkout,websites}]", id }` - El campo `value` es un JSON string → parsearlo dos veces - 177 registros totales, filtrar por `parseInt(dateEnd) > now` → ~45 activos - Filtrar por tienda con campo `websites` (array duplicado): - Jumbo: `websites.includes('jumboargentina')` - Disco: `websites.includes('discoargentina')` - Vea: `websites.includes('veaargentina')` (Cencosud también) - `days`: array de strings "1"-"7" (1=Lunes...7=Domingo) - `banks`: array de `{name, image, refId, order}` - `discount`: string float ("20.00") - `legals`: texto legal completo ### Carrefour (VTEX GraphQL persisted query) ``` GET https://www.carrefour.com.ar/_v/public/graphql/v1 ?workspace=master&maxAge=short&appsEtag=remove&domain=store&locale=es-AR &operationName=GetPromotions &variables={} &extensions={"persistedQuery":{"version":1,"sha256Hash":"cdedb2142b133164ce61b85e94287592451ebee4a2fbede815e09336d40d29ae","sender":"valtech.carrefourar-bank-promotions@0.x","provider":"vtex.store-graphql@2.x"},"variables":""} ``` - El campo `extensions.variables` (base64): `{"where":"active=true AND ((active_from < 2026-03-05T20:15:48) AND (active_to > 2026-03-05T20:15:48))","account":"carrefourar"}` - Formato datetime: ISO sin ms/Z (`new Date().toISOString().replace(/\.\d+Z$/,'')`) - Respuesta: `{data:{documents:[{fields:[{key,value}]}]}}` - Campos clave: `title`, `sub_title`, `discount_percentage`, `validText`, `legal`, `monday`..`sunday`, `ecommerce`, `hyper`, `market`, `express` ### Changomas/MasOnline (VTEX GraphQL persisted query) ``` GET https://www.masonline.com.ar/_v/public/graphql/v1 ?operationName=GetPromos &extensions={"persistedQuery":{"sha256Hash":"1a071ebc5dc407a3f65e687b0f4c0a3b8d12a0c45d8d11370075c3b2a505251c","sender":"valtech.gdn-banks-promotions@0.x",...},"variables":""} ``` - `extensions.variables` (base64): `{"where":"active=true AND (...)","account":"masonlineprod"}` - Misma estructura de respuesta que Carrefour ### DIA (sin API pública) ❌ - Account name VTEX: `diaio` - `JN/documents/bankDiscount?an=diaio` → 403 (campos privados) - Página `/medios-de-pago` solo hace GraphQL calls: `pwaData`, `getSettings`, `installedApp`, `topSearches` (no hay GetPromos) - Los datos de promos están en el CMS/contenido estático de VTEX (no en API separada) - Alternativa: Chrome Relay / Playwright headless para extraer texto de la página --- ## Promos de Producto — VTEX Teasers Endpoint: `GET /api/catalog_system/pub/products/search?_fields=productName,items&_from=0&_to=49` - **DIA** y **Carrefour**: tienen teasers activos (2do al 50%, 2x1, 3x2, etc.) - **Disco**, **Jumbo**, **Changomas**: NO usan VTEX Teasers → `teaserHits=0` - Sus promos van directo en precio (ListPrice > Price), pero no distinguible de diferencia de PVP - Script: `scripts/fetch_all_promos.js` (escanea 200 productos por tienda) --- ## Scripts disponibles | Script | Qué hace | Output | |--------|----------|--------| | `scripts/fetch_bank_promos.js` | **PRINCIPAL** — Promos bancarias directas 5 tiendas | `data/discounts/bank-promos-latest.json` + `super-ranking-report/bank-promos-data.js` | | `scripts/coto_fetch_promos.js` | Fetch promos Coto via ATG API (legacy) | `data/coto/promos-latest.json` | | `scripts/fetch_all_promos.js` | Coto ATG + scan VTEX 5 tiendas (teasers) | `data/discounts/all-promos-latest.json` + `super-ranking-report/discounts-data.js` | | `scripts/discover_discount_apis.js` | Playwright — descubre APIs via network interception | `data/discounts/api-discovery.json` | ## Página de descuentos URL: `https://miopenclaw-vnic.tail9799d2.ts.net/preview/super-ranking/descuentos.html` Archivos HTML: `super-ranking-report/descuentos.html` Datos: `super-ranking-report/bank-promos-data.js` (auto-generado por fetch_bank_promos.js) La página muestra promos bancarias por día para cada tienda, con tabs por día de la semana, filtrando automáticamente al día actual de Argentina (UTC-3). Incluye legales expandibles. --- ## Endgoal: Servicio autónomo de compra de supermercado Ignacio tiene preferencias de consumo → Pipo debe: 1. **Saber qué consume Ignacio** (historial, preferencias, lista de productos regulares) 2. **Calcular mejor día/tienda/tarjeta** usando bank-promos-latest.json por día 3. **Armar carrito** en el super ganador para esa semana 4. **Ignacio finaliza** la compra (último paso manual) Estado: paso 2 resuelto (datos de promos por día disponibles autónomamente). Pasos 1, 3, 4 pendientes de diseño/implementación. ## Notas técnicas importantes - **Exit node Tailscale**: no ayuda con WAF de Coto — VPS (Oracle Cloud) y Pixel 6a (exit node) tienen la misma IP de egreso (Telecom Argentina AS7303, 181.111.43.138) - **Playwright**: instalado en `~/.openclaw/workspace/node_modules/` — usar para descubrimiento de APIs, no para scraping regular - **Chrome CDP Relay**: `ws://127.0.0.1:18792/cdp` con `x-openclaw-relay-token`; sessionId va a top-level del mensaje JSON (no en params)