See the full portfolio

El Guarén: bank discounts in restaurants

A web app that consolidates the restaurant discounts published by 16 card issuers, verifies each one against the bank's own page and makes them searchable by day, cuisine, distance and card.

Year
2026
Status
Live since October 2026
Stack
Python, JavaScript, SQL, Cloudflare Pages, D1, MapLibre, Playwright
El Guarén: bank discounts in restaurants

The problem

Banks and retail cards publish restaurant discounts, but each one does it on its own page, in its own format, with its own rules: some only apply on certain days, some cap the cashback, some work only with one specific card. To know where it's worth eating today you have to open sixteen different sites and read the fine print on each.

The real problem isn't collecting the data, it's that it expires silently. A bank pulls a deal from its page and no record remains: the discount keeps circulating in listings and screenshots, and you find out at the table, when they don't honour it.

I built the app to solve both: consolidate the offer in one place and, above all, publish only what can be verified against the source.

What is published

540places
787live discounts
1.285branches on the map
16card issuers

Discounts are spread very unevenly across issuers, and that changes which card is worth having.

Banco de Chile182
Bci85
Santander81
Falabella72
Scotiabank70
BICE y Security69
BancoEstado68
Itaú43

Eight of the sixteen issuers. The other eight add up to 117 discounts between them.

The flow, from the bank page to the app

Each piece does one thing and can be run on its own. Once a month, after extraction, everything else is built and published with one command.

  1. 01
    ExtractionOne extractor per issuer. Some read the bank site’s public API, others need a browser because the page answers with an anti-bot challenge, and one comes from a monthly benefits PDF.
  2. 02
    Verification against the sourceFor issuers that allow it, every deal is checked on its bank’s page and anything returning 404 is dropped. Anything captured with a browser is only valid for the month it was captured, and a file from another month is not published.
  3. 03
    ConsolidationThe same restaurant arrives under different names from several issuers. It is unified, deduplicated, and manual corrections are applied, each with its reason written down.
  4. 04
    EnrichmentGeocoding of every branch (1,148 of 1,285 with an exact address), a photo from the restaurant’s own site, menu, opening hours and cuisine.
  5. 05
    Publishing with brakesThe deploy stops by itself if an issuer lost more than 40% of its discounts, if a JSON came out empty or invalid, or if anything shaped like a credential slipped into the published folder.
  6. 06
    Usage and feedbackOnce live, the app collects opinions from people who went and counts what gets looked at, to know which data is worth improving next month.

The rule that governs the whole project

On its first day live, the app showed a 40% Thursday discount at a restaurant, valid until the end of the month according to the original listing. The bank had already pulled it from its page. I went, asked for the discount, and they didn't honour it.

That produced the one rule that isn't negotiable: only publish what the bank's page shows today. It isn't enough that the expiry date hasn't passed. Every discount stores the day it was seen at the source, and the detail sheet shows it next to the percentage.

It's an uncomfortable decision, because it deletes deals that are probably still alive and leaves the app with less content than it could have. But the value of this data is that you can use it in front of a waiter, and for that a false positive costs far more than a gap.

Restaurant sheet with its description, Google rating and actions
The sheet gathers what could be verified: description, Google rating, menu and each issuer’s discounts with the date they were checked.

From a catalogue to a decision

A list of 787 discounts doesn't answer the question that actually matters: which card is worth having? That question mixes three things the person knows (how often they eat out, how much they spend each time, what they earn) with two that live in the data (how many places each card works at, and how big the discount is) and one you have to go and find separately (each card's monthly fee and minimum income).

The result is an estimated monthly saving per issuer, against its cost, and a one-line verdict: which card is worth it, how much it saves per month, how many places it works at and what it costs. The page states explicitly that this is an estimate and not financial advice, and discards cards the person won't qualify for.

Three questions and a verdict with the estimated monthly saving
Three questions, one recommendation with the estimated saving, the places it works and the card’s cost.

What the people who went contribute

There's one piece of data no bank page publishes: whether the discount was actually honoured. Only the person who went knows that, so each restaurant sheet lets you leave a rating, tags and a short comment, with no account and no sign-up.

Opening a public form to the internet means solving abuse before features. Opinions live in a SQL database at the edge, and each one passes several filters: the request has to come from the page itself, the text is validated and rejected if it contains links, emails or phone numbers, and there are per-connection and per-restaurant daily limits. Limits are stored as a fingerprint derived from the IP, never the full IP, and they expire on their own.

The average only appears from three opinions onwards, so a single one doesn't define a venue's rating.

Built for the phone

This gets used standing up, on the street, deciding where to walk in. So it was designed for the phone first, and after launch I reviewed it at twelve screen sizes, with a separate accessibility review.

The GPS location is used to sort by distance and never leaves the phone. There are no accounts and no cookies.

The home screen on a phone
The home screen on a phone: how many places have a discount today, with the sections at thumb height.

What I took from it

  • Freshness is part of data quality. A correct value that is no longer current is a wrong value. Storing the verification date next to each record, and showing it, changed the whole project.
  • Validations belong in the process, not in your head. When an extractor breaks, it usually returns fewer rows without raising an error. That's why publishing stops by itself when an issuer loses 40% of its discounts from one month to the next.
  • Deduplicating entities with no shared key is the slow work. Sixteen sources write the same restaurant in sixteen ways, and the manual corrections, each with its reason written down, ended up mattering as much as the code.
  • Published identifiers don't change. Shared links and opinions hang off them: improving the data can't break what is already out there.