[ Selected work ] / 2026
Notas
>>> Summary
Academic grade tracker: complex weightings, the grade you need to pass, an AI-assisted timetable, reminders and read-only share links.
- Role
- Diseño y desarrollo
- Stack
- Next.js, React, TypeScript, Tailwind CSS, Supabase, PostgreSQL, Vercel
- Year
- 2026
>>> Details
A web app to keep every grade of a degree and always know how much you need to pass. It is multi-user (Google sign-in), installable as a PWA and in Spanish only.
- +Optional degree → year → term → subject → assessments hierarchy, with several degrees and a global switcher.
- +Weighting by category, individual weights, sub-parts and periodic series (Practical 1…N), with configurable scale, decimals and rounding.
- +Assessment rules: minimum per part, drop the worst grade, resits, a cap and bonus points.
- +Needed grade, a "what if" simulator, best and worst case, and term and degree averages weighted by ECTS.
- +A timetable per term, with an AI assistant that proposes it and lets you review it before saving.
- +A calendar, push and email reminders, and optional Google Calendar sync.
- +Revocable read-only links with an optional expiry, export and import (JSON, CSV, PDF) and subject templates.
- +Per-user isolation with RLS on every table and 758 tests, including property-based tests on the grading engine.
>>> Case study
- 2
- Days of development
- 80
- Commits
- 758
- Tests
- 37
- Migrations
* Days and commits come from the git history (20 and 21 Oct 2026); tests come from `npm test` (Vitest, 60 files, all passing); migrations come from `supabase/migrations`.
[ 01 ] Challenge
Course guides mix category weights, minimum marks, resits and "drop the worst one", and a spreadsheet does not answer the only thing that matters mid-course: how much do I need to pass. I wanted my own multi-user tool that worked on a phone, where each student only sees their own data and the maths is reliable enough to trust.
[ 02 ] Solution
A pure TypeScript grading engine with decimal arithmetic that handles weightings, rules, needed grade, the simulator and ECTS averages. On top, a Next.js app with Supabase: Google sign-in, RLS on every table, attachments in a private bucket and push and email reminders from a database cron. It adds a per-term timetable with an AI assistant that shows a preview before saving, optional Google Calendar sync, revocable read-only links and an installable PWA that can be read offline.
[ 03 ] Result
An app live at notas.broslunas.com, with 758 tests (including property-based tests of the engine), 19 SQL files that check RLS and 37 migrations, built in two days going by the git history.
[ Technical decisions ]
01The grading engine is a pure library with decimal arithmetic
- Alternative considered
Calculating in components or in the database with floating-point numbers.
- Why
src/lib/gradingdepends on neither the UI nor the database and usesdecimal.js, so rounding does not carry floating-point errors. It is tested with examples and property-based tests (fast-check), and the interface only renders what it returns, including the "how is this calculated?" trace.
02RLS on every table, checked with SQL
- Alternative considered
Filtering by user on the server and trusting no query forgets it.
- Why
Each table filters by
user_id = auth.uid()in the database itself. There are 19 SQL files insupabase/testsrun withnpm run test:rlsagainst the linked database, so an isolation failure is caught before it reaches production.
03Document and ask before coding
- Alternative considered
Starting with the interface and discovering requirements as I go.
- Why
docs/holds requirements, domain model, architecture and phases with acceptance criteria, and each big module (performance, calendar, timetable) had its own decision questionnaire. Changing my mind meant editing a document, not redoing code.
04The AI proposes, the user confirms
- Alternative considered
Letting the assistant write the timetable straight into the database.
- Why
The assistant builds the timetable and also edits it in natural language, but it always goes through an editable preview; if several classes are candidates, it asks. Only what the user accepts is saved, so a model mistake never reaches the data.
05Read-only when offline
- Alternative considered
Offline editing with sync and conflict resolution.
- Why
The PWA caches what has been loaded so grades can be read without a network, but editing needs a connection. With several devices per user, avoiding merge conflicts mattered more than being able to jot a grade down on the metro.