All work

Aforro Tracker

A private web app for people in Portugal who hold savings certificates (Certificados de Aforro and do Tesouro), valued from public rates.

Role
Sole developer
Year
2026
Outcome
A holder enters their own lots and sees what each is worth today and why, and how certificate types compare for new money, without ever handing over the login for the official portal.
Built with
Vue 3, TypeScript, Vite, PWA, Dexie (IndexedDB), vue-i18n, big.js, Vitest, fast-check, Playwright, axe-core, GitHub Actions
Aforro Tracker, the portfolio screen: what is coming up, the holder filter and the start of the total, for made-up certificates.
Screenshot on made-up data.

Why I built it

Savings certificates are popular in Portugal, but the official portal shows little history, and comparing the two kinds means doing the sums by hand. Some third-party tools ask for the portal password, which the issuer warns against.

What it does

  1. Portfolio

    The total held, interest earned this year and since the start, the split between Certificados de Aforro and Certificados do Tesouro, a filter per holder, and every lot with its next event. A What's new card is worked out on the device: a new month's rate, a lot that can now be redeemed, a coupon coming up, the minimum for Tesouro reached, or a change in which product leads.

    The portfolio total: invested, net interest, the split between Certificados de Aforro and do Tesouro, and coupons received, for made-up holdings.
  2. Adding lots without a password

    It never asks for the official portal's login. Lots are typed in with the same columns as the official statement, or pasted: you copy the balance table from your own portal session, and the app reads it on the device, with a preview that marks each row ready, a duplicate or a problem before anything is saved.

    Import from AforroNet: a pasted made-up table, with the preview marking one row already recorded and two ready, and the app's unit value under the statement's.
  3. Each lot

    Current value, the unit value to five decimals, gross and net interest, tax withheld, the current loyalty-premium tier and when the next one starts, the next capitalisation or coupon, and a redemption guard: "redeeming today loses about this much interest; the next value date is in N days".

    One lot: current value, unit value to five decimals, invested and net interest, the loyalty-premium tier and the next one, and the next capitalisation. Made-up lot.
  4. Why this value?

    Every figure can be opened to show how it was worked out: the inputs, the formula, every rate with its source and the date it was read, the tax and the rounding. If a month's rate is missing, it says which one and shows no number rather than a guess.

    Why this value?: the rates used, month by month, each with its source and the date it was read.
  5. Decide

    For new money, the two products side by side over a chosen horizon, under a scenario for the interest rates not yet published, with checkpoints at 1, 2, 5, 10 and 15 years, a chart with a table beside it, and every projection labelled as an estimate. For money already invested, each lot ranked by what moving it would cost today. Strategies compares three ways of saving the same amount every month. The wording is tested so that it never says "should".

    Decide, for new money: the two products over 1 to 15 years as a chart and a table, every figure an estimate.
  6. Goals, holders and a page for the heirs

    Goals with a target, a deadline and a reserve; one label per holder, never a tax number; and a printable page for the heirs with every lot valued today and plain guidance on what they need to do.

    Illustration: a printed page for the heirs, a goal card with a progress bar, and two holder tags.

How it works

Nothing leaves the browser
Holdings live in the browser's own database. No accounts, no sync, no analytics, no outside fonts. The content security policy allows connections to its own address only, so the browser itself blocks anything else. A backup is a file you save, optionally encrypted with a passphrase.
Rates as data
The issuer has no public API. A scheduled job reads its public pages and the ECB's monthly Euribor, checks every record for changes in format, runs the engine's tests against the new data, and commits each rate with its source address, the time it was read and a hash, then rebuilds the app. Past values are never overwritten.
A pure engine
The valuation engine is plain TypeScript with no interface, no input or output and no clock: exact decimal money, calendar dates and a book of dated rates. It returns every value together with its explanation, which is what the "Why this value?" screen shows. The household finance app values certificates with the same engine.

How it is built and tested

Tests
About 150 tests. Golden tests reproduce 27 lots taken from the issuer's own simulator to the fifth decimal, and property tests check that removing any month the sum needs makes it fail and name that month. End-to-end runs in Chromium and WebKit go through every screen, with an accessibility scan that must find nothing.
The rounding
Rounding each lot to the cent reproduced none of the 27 captured lots. The rule that does (round the unit value to five decimals after every quarter, and show cents only at the end) was worked out from them.
Decided before code
The plan was settled in 38 recorded decisions and ten architecture records before any code was written. Research showed that several rules in the first brief were wrong, so every formula cites its source.

How Atta helped

  • The plan was grilled before any code: 38 decisions settled one question at a time.
  • The research became wiki pages (the rounding model, where the rates come from), so each later session starts from them instead of rediscovering them.
  • Atta generated a team for the stack: Vue, TypeScript, Vitest, Playwright and accessibility.
  • Its valuation engine was later reused unchanged by the household finance app.