Supermarket receipt scanner that respects your privacy.
Scan receipts from Mercadona, Carrefour, Lidl, and more. Track spending. Keep your data local. Started in Spain, opening to more countries one chain at a time.
1.1.0 is in review at the App Store and Google Play.
- 100% on-device: ML Kit OCR runs locally. No cloud uploads.
- Any format: Document scanner, gallery, or PDF.
- Smart categories: Auto-sorts items. Learns from your corrections.
- Supermarket templates: Pre-trained templates for Spanish chains, with a contribution path for every other country.
- Multi-language: English & Spanish UI.
Capture: Document scanner (auto-crop & enhance), gallery import, PDFs. CestApp reads a capture on the spot and opens it in review.
Review: Edit the store, date, total and every line before saving. "How it was read" shows the zones over the receipt and the recognized text. If the reading is off, redraw the zones or crop the image to the receipt, and CestApp reads it again. It warns you before you save a duplicate.
Smart Parsing: Chain-specific templates detect Mercadona, Carrefour, Lidl, Eroski, Dia, Consum, Alcampo, Aldi, HiperDino by NIF/name patterns.
Smart reading (iOS): On iPhones with Apple Intelligence, an on-device model repairs receipts the OCR read badly. CestApp keeps only the items it can match to the OCR text, and checks that their prices add up to the total.
Organize: 11 built-in categories, auto-categorization, user learning, store detection.
Analyze: Monthly trends, category breakdowns, store comparisons, top items.
Privacy: Offline-first, local SQLite, no tracking, no ads. Export everything to a JSON backup. Full policy: jbgsoft.com/cestapp/privacy.
Details: Phone and tablet layouts, dark mode, each receipt kept in its own currency and amounts written the way your language writes them, animations that respect Reduce Motion.
git clone https://github.com/jaimebg/CestApp.git
cd CestApp
npm install
# Dev build required (ML Kit needs native code)
npx expo run:ios # or
npx expo run:androidExpo Go won't work. You need a development build.
| Category | Technology |
|---|---|
| Framework | React Native 0.86.2 + Expo SDK 57 |
| Navigation | Expo Router |
| Styling | NativeWind v4 |
| Database | Drizzle ORM + expo-sqlite |
| State | Zustand v5 |
| OCR | ML Kit (@infinitered) |
| Fiscal QR | ZXing (Android), Vision (iOS) |
| LLM | Apple Foundation Models (iOS) |
| Custom parser + pako | |
| i18n | i18next |
| Animations | Reanimated v4 |
| Charts | react-native-gifted-charts |
app/ # Screens (Expo Router)
src/
components/ # UI components
config/ # Countries and their chain templates, currencies
db/ # Schema, migrations & queries
services/ # OCR, fiscal QR, PDF, capture, storage, LLM
store/ # Zustand state
theme/ # Palette & type
i18n/ # Translations
scripts/
store-screenshots/ # Captures, frames and lays out the store screenshots
fixtures/ # Scrubs a recorded capture into a chain fixture
modules/
qr-decoder/ # Native QR decoding for fiscal codes
npx tsc --noEmit # Type check
npm test # Jest, including fixture-based parser tests
npm run check # ESLint + PrettierCI runs all three on every push. If you change the parser, extend the fixtures in src/services/ocr/__tests__/. Chain fixtures under src/config/countries/*/__tests__/fixtures/ test themselves; npm run fixture makes one from a recorded capture (see CONTRIBUTING.md).
Store screenshots regenerate end to end with npm run shots:all, with no manual step. It
renders the sample ticket and boots two dedicated Android emulators (a phone and a tablet, created
upright on first use so your development AVDs are never touched). It builds a release APK in
screenshot mode (EXPO_PUBLIC_SCREENSHOTS=1, wiped from android/ when the run ends so no store
build can pick it up), seeds demo data, opens each screen through cestapp:///screenshots deep
links and captures it once the screen stops moving. Finally it frames the captures for the App
Store and Google Play in English and Spanish, and lays out the panorama at the top of this README.
Releases build locally and ship through fastlane. The fastlane/ folder holds credentials and store assets, so it stays out of the repo.
- Detect Chain and Country: Identifies the supermarket and its country by tax ID, store name patterns, or fingerprints (brand names like "Hacendado" for Mercadona). Where the receipt carries a fiscal QR code readable offline (Portugal, Austria), its total, date and tax ID take precedence
- Apply Template: Uses chain-specific parsing rules (layout, item patterns, OCR corrections)
- Extract: ML Kit OCR for images, custom parser for PDFs. The parser uses the capture's zones when it has them, then the geometry of its text blocks, then its lines alone
- Categorize: User corrections → keyword matching → default
Learning is store-aware: same item can have different categories at different stores.
| Country | Chains |
|---|---|
| Spain | Mercadona, Carrefour, Lidl, Eroski, Dia, Consum, Alcampo, Aldi, HiperDino (with IGIC tax for Canarias) |
| Portugal | None yet: fiscal QR codes are read, chains are waiting for contributions |
| Austria | None yet: fiscal QR codes are read, chains are waiting for contributions |
Your country or chain is missing? Donate a ticket: a photo is enough.
The most useful contributions are receipts and chain templates from countries CestApp can't read yet: donate a ticket (no code needed), or write the template for a donated one. CONTRIBUTING.md covers both, plus adding a country or a UI language.
MIT. See LICENSE.
Made with care by JBGSoft - Jaime Barreto 🧡

