← Back to work

Vital

Full-stack · production system, end to end (backend, admin and PWA)
Real physical access control: a lightweight headless service reads the turnstile over Arduino (serialport), validates the active membership against the API and opens the gate — all event-driven on NestJS
Full Argentine tax compliance: AFIP electronic invoicing (SOAP), Mercado Pago payments, and a WhatsApp bot to issue invoices conversationally from a phone
Automated dunning via cron with escalation: retries → email/SMS reminders → grace period → suspension; plus a members' PWA with routines, set logging and personal records

Family gym · Production system · Argentina

My family's gym running on a single system: members walk in by swiping their credential at the turnstile, pay and get invoiced without friction, and train with their routine on their phone. Three applications, one source of truth.

Active Expiring Expired membership traffic-light — the anchor of the whole operation
Members' app home: greeting, membership status and a button to start the workout

What problem does Vital solve?

Vital is my family’s gym. Like any neighborhood gym that grows, for years it ran on a mix of a spreadsheet, a notebook at the front desk and a few WhatsApp groups. With several hundred active members, that stopped working: nobody knew for sure who was paid up, the door was opened by someone eyeballing it, invoicing was a manual month-end chore, and members had no tool of their own beyond texting their coach.

I built it for our real operation — one system covering the three faces of the business at once: front-desk operations (who’s in, who owes, who’s booked), tax compliance (real payments and invoices, wired to AFIP and Mercado Pago) and the member experience (their routine, their progress, their access). The challenge wasn’t any of those pieces on its own — it was making them live on top of a single data model without duplicating the truth.

How is Vital built?

I built it as a monorepo with three independent applications sharing one API. At the center, a NestJS backend on PostgreSQL and Prisma, designed event-driven: modules don’t call each other by hand, they emit events and others react. When the turnstile reads a credential, it kicks off a chain — verify access → record attendance → notify — without coupling the hardware to business logic.

gestion-backend NestJS · PostgreSQL · Prisma · EventEmitter
gestion-frontendAdmin panel · React + MUI
gestion-pwaMembers' app · Capacitor (iOS/Android)
landingPublic site · acquisition
Turnstile + Arduinolightweight headless service → serial port → API
Mercado PagoAFIPTwilio / WhatsAppFirebase Push

Each surface has a distinct user and purpose, but they all talk to the same API. That was the structural decision the rest rests on: a single source of truth for memberships, payments, attendance and routines.

Reception screen of the panel: check-in search, alerts for expired and expiring memberships, today's attendance and payments
Reception — the operational home of the panel: not a dashboard, a task inbox (expired, expiring, entries and today's payments).

What technical decisions underpin Vital?

The parts of Vital I care most about aren’t the screens — they’re the systems that connect those screens to the physical world and to the tax authority.

Hardware + software

A turnstile that knows who you are

Access isn't a button — it's real hardware. A lightweight headless service talks over the serial port (serialport) to an Arduino that controls the turnstile. When someone swipes their credential, the system checks the API for an active membership and only then opens the gate — with no desktop app in the middle.

  • Credential read via Arduino → NestJS event → access verification decoupled from the hardware
  • Alternative QR check-in: single-use tokens that expire after 5 minutes, generated from the member app
  • Every entry is recorded as attendance and fires notifications without blocking the gate
Tax compliance

Charging and invoicing for real, in Argentina

A gym that invoices seriously has to talk to AFIP (the tax authority) and to a payment provider. Vital integrates AFIP electronic invoicing over SOAP (with certificate handling) and Mercado Pago payments, including idempotent webhooks so the same payment is never processed twice.

  • AFIP electronic invoicing with certificate validation and its own diagnostics
  • Mercado Pago payments plus a processed-webhooks table to guarantee idempotency
  • A WhatsApp bot (via Twilio) that lets staff issue invoices by chatting from their phone, driven by a per-session state machine
Invoice list with receipt number, date, client, total and status (Generated, Shared, Paid)
Invoices — receipts with their status (generated → shared → paid) on top of AFIP electronic invoicing.
Collections on autopilot

Debt that chases itself

The money a gym loses is almost always money nobody went to collect. So dunning is its own subsystem that runs on cron and escalates by itself: it retries first, then reminds, then grants grace, and finally suspends — with no one having to remember.

  • A scheduler with three stages: process retries → send reminders (email/SMS) → suspend on grace-period expiry
  • Membership traffic-light (active / expiring / expired) as the central state across the app
  • Multichannel notifications: email (SMTP), SMS (Twilio) and push (Firebase) depending on the event
The member app

Train, not just pay

The members' PWA — packaged with Capacitor for iOS and Android — is what keeps the system from being purely administrative. Members see their assigned routine, run it block by block and log every set; the system keeps their history and detects personal records.

  • Public routine catalog + per-member or per-group assignments, with days, blocks and exercises
  • Workout session logging: sets, loads and automatic personal records
  • Class booking, health metrics, QR check-in and push notifications
Routine catalog in the panel: Full Body Beginner, Push Pull Legs, Upper/Lower and custom plans with their exercise counts
Staff build the routine catalog in the panel; the member runs it on their phone.
Routine detail in the app: main block with exercises, sets, reps and rest
The routine, block by block
Progress screen: total kilos lifted, workouts, hours, personal records and recent attendance
Progress: volume, workouts and PRs
QR check-in screen with a 5-minute expiry countdown
QR check-in, expiring after 5 min

The three surfaces

Admin panel

React 19 + MUI for the front desk. Designed as a task inbox, not a dashboard: ⌘K check-in, payments and membership status with direct actions on every row.

Members' app

Capacitor PWA: routines, progress, classes and QR access, publishable to the stores as a native app.

Core API + access

Event-driven NestJS on PostgreSQL/Prisma. One source of truth for the surfaces, the external integrations and the turnstile over serial — with no extra layers.

Closing

Vital is the project where end to end really shows what it means: it doesn’t stop at a nice screen, it ends at a turnstile that opens, an invoice that’s valid before the tax authority, and a member logging a set from their phone. Three applications, one data model, and a pile of decisions — event-driven design, payment idempotency, automated collections, hardware over serial — that exist because the system runs in my family’s gym, every single day. Even access control ended up minimal: the first version was a desktop app that turned out to be overkill, replaced by something far smaller that does exactly what’s needed.