| name | discount-sync |
| description | Sync discount data from Uruguayan bank and card provider websites into structured JSON for chetear.com. Trigger when asked to refresh, sync, or update discounts for any provider. |
Discount Sync
Fetch discount data from provider websites and write schema-valid JSON to
site/src/data/discounts/{provider}.json.
Providers
Read references/provider-sources.json for the full list of first-class runtime providers, their
source URLs, cadence, and notes.
Read references/provider-blueprint-spec.md for the blueprint contract.
Every first-class runtime provider must declare blueprintPath, and that blueprint is mandatory runtime policy for the source.
If a provider declares syncScriptPath in references/provider-sources.json, treat that repo-local helper as the provider's primary automation path.
Before extraction, run:
node skills/discount-sync/scripts/validate-provider-blueprints.mjs
node skills/discount-sync/scripts/preflight-discount-state.mjs
If blueprint validation fails, repair the blueprint system before touching runtime discount JSON.
If preflight fails, stop and clean up the checked-in discount outputs before running any provider helper.
Discovery-only sources
Read references/discovery-sources.md for aggregator sites that may help discover promos but must not write directly into runtime discount JSON.
Gathering order
- Read each provider's mode from
references/provider-sources.json and treat it as authoritative.
- If a provider entry declares
syncScriptPath, run that helper as the primary repo-local automation path for that provider.
- Providers marked
mode: "fetch" use node skills/discount-sync/scripts/provider-fetch.mjs <provider> as their primary acquisition path.
- Browser-first providers without
syncScriptPath use the browser for primary extraction.
- Do not use plain fetch as a fallback for browser-first extraction unless the repo policy explicitly declares a provider-specific helper.
- Normalize only after the relevant detail content is gathered in the provider's primary mode.
Environment
rg is not available in the standard runner for this workflow.
- Use exact file paths,
read, find, or grep instead of rg.
- Prefer the exact repo files named by the automation prompt over rediscovering the repo layout.
Fetch-helper workflow
- Run
node skills/discount-sync/scripts/provider-fetch.mjs <provider>.
- Treat the helper's validation markers as mandatory proof that the fetched HTML is the real source page, not a block/interstitial page.
- If the fetch helper fails or validation does not pass, keep the last known good data and report the failure.
- Do not route the fetch helper through special network overrides unless repo policy is intentionally changed later.
Browser extraction workflow
Use this workflow for browser-first providers that do not have a repo-local syncScriptPath, or when auditing/repairing a provider-specific helper.
- Open the provider source URL in the browser.
- Apply any visible filters needed to reach the relevant card family or category.
- Open each candidate benefit card via its full detail affordance (
Ver más, modal, drawer, or dedicated page).
- Extract rules from the expanded detail view, not from the teaser card alone.
- Preserve tier splits, day logic, caps, channels, and validity windows exactly as shown in the detail view.
- If the provider page is blocked (
403, captcha, geo/IP block, or similar), keep the last known good data and report the browser failure instead of routing through a special network workaround.
- If the browser still cannot reveal the detail content, keep the last known good data and report the browser failure instead of switching to an undeclared fetch path.
Extraction rules
- Preserve stable provider identity already used by the site.
- Capture tiers, networks, caps, day logic, channels, and validity windows when present.
- If a current source card/detail is still visible but its detail text shows a clearly past validity date, source visibility overrides the stale date. Keep the runtime rule active by omitting the past
validUntil unless the source provides a current/future replacement, and record the conflict in provider policy/reference notes.
- When a teaser card summarizes multiple benefits (for example
25% y 15% menos), open the detail view and split the output into distinct rules using the full context.
- Do not invent details that are not visible in the source.
- Prefer explicit ambiguity over false precision.
- If confidence is low, keep the last known good data and report why.
- Normalize merchant names only when the source wording is clearly noisy.
- When a source exposes a separate merchant directory behind a broad rule like
80+ restaurantes, keep the discount rule broad here and use merchant-directory-sync for the individual merchant list.
Merchant-name hygiene (required)
Source pages are often scraped or OCR'd and leak transcription noise. Before
writing, normalize these cases:
" l " (space-lowercase-L-space) between words should almost always be
" | " (pipe). The pipe is the site's canonical separator between a band
and a show name, an event and its venue, etc. Example:
"Alacran y Silva l El Show del Chiste" → "Alacran y Silva | El Show del Chiste".
- Trim trailing punctuation and collapse internal whitespace.
- Preserve accents exactly as written in the source when the source is
authoritative; only add accents when you are restoring an obvious loss
(e.g. an all-caps ASCII-only provider export).
- Do not dedupe rules that share a merchant name but differ in
conditions,
validUntil, or venue — those are distinct performances or promotions.
Category labels
The runtime label (e.g. "Gastronomía", "Educación") lives in
CATEGORY_LABELS in site/src/lib/schema.ts. The sync output only writes the
category enum key (e.g. "restaurante", "educacion"). Do not invent new
category keys; if the source uses a concept that is not in the enum, map it to
the closest existing key or to "otros".
Output format
One JSON file per provider matching the ProviderDiscounts schema from
site/src/lib/schema.ts:
{
"provider": "itau",
"label": "Itau",
"rules": [
{
"merchant": "Tienda Inglesa",
"category": "supermercado",
"percent": 15,
"tiers": ["gold", "platinum"],
"networks": ["visa"],
"days": ["sabado"],
"conditions": "Compras mayores a $2000",
"cap": "$5000 mensuales",
"validUntil": "2026-03-31"
}
]
}
Valid categories: supermercado, restaurante, farmacia, combustible, indumentaria,
electronica, hogar, salud, entretenimiento, viajes, educacion, otros.
Valid days: lunes, martes, miercoles, jueves, viernes, sabado, domingo.
After extraction
- Validate the JSON against the schema before writing.
- Write to the output path specified in
references/provider-sources.json.
- Run
node skills/discount-sync/scripts/validate-provider-smoke.mjs after any provider updates and treat failures as blocking.
- If live source changes make the smoke guardrails stale, repair
references/provider-smoke-assertions.json in the same run before re-validating.
- Run
node site/scripts/validate-runtime-data.mjs --discounts-only after any provider updates and treat failures as blocking. This step enforces the conditions-extraction.md hard rules — phantom card tiers, garbled ; sin tails, and notes that duplicate stackable / benefitType / validUntil / cap / excludedApps / days are all blocking.
- Commit and push to main with a message like "Update {provider} discounts".
Adding a new cardFamily
If the source legitimately introduces a new family name (e.g. a new pack
or co-brand), update PROVIDER_CARD_FAMILIES in
site/scripts/validate-runtime-data.mjs in the same change as the data
update. Names must match the source's casing. Do NOT add a tier-level name
(Platinum, Black, Infinite, Gold) as a cardFamily — those belong in tiers.
The Santander roster historically shipped a phantom "Crédito Oro" because a
tier name leaked into cardFamilies; the validator now blocks that path.
When a source breaks
Read references/repair-source.md for the repair workflow. Prefer the smallest
fix that makes future runs reliable. Fix references before changing anything
else.