# 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":"<base64>"}
```
- 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":"<base64>"}
```
- `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)
