← Back to projects

Project

Balltics — Baseball League Management Platform

Multi-tenant SaaS to run amateur baseball leagues end to end: a real-time public site for fans (standings, leaders, season stats, full box scores) plus an admin suite for live pitch-by-pitch scoring, roster management, and recurring Stripe billing — built for the Mexican/LATAM market.

Founder & Engineer (Full Stack — Product, Backend, Infrastructure) Personal project Public summary

Personal product I own and operate end to end (frontend, Firebase backend, Cloud Functions, security rules, and CI/CD), offered commercially to baseball leagues. Implementation details are summarized at a high level.

Stack

Angular 17TypeScriptAngular SignalsRxJSTailwind CSSFirebaseFirestoreFirebase AuthCloud FunctionsFirebase HostingFirebase StorageIndexedDBStripeSendGridGitHub ActionsGCP

Impact

  • League organizers run an entire season from one platform — standings, stats, schedules (jornadas), tournaments, and billing — replacing spreadsheets and manual scorekeeping.
  • Fans follow their league in real time from any phone with no app install: live standings, batting/pitching leaders, season stats, and full game box scores.
  • Live pitch-by-pitch capture turns a phone into a professional scoring console that produces official box scores and season stats automatically, with no manual tallying.
  • Offline-first capture keeps scoring working at remote fields with no signal and syncs automatically when the connection returns — zero lost games.
  • Recurring revenue per league via Stripe subscription billing, account statements, and a self-service customer portal.
  • Multi-tenant from day one — each league gets its own branded public site and isolated data, so the same platform scales to many leagues without code changes.

What I did

  • Built a full pitch-by-pitch state machine (~1,500 lines) tracking count, bases, runners, outs, inning transitions, and substitutions — producing live batting/pitching lines in real time.
  • Engineered dual-layer offline persistence: synchronous localStorage writes plus debounced Firestore (IndexedDB) sync, restoring exact game state after reloads or connection loss.
  • Designed a signals-based in-memory cache that boots the whole app from a single Promise.all, with an optimistic write pattern that updates the UI without re-fetching.
  • Implemented a three-tier role system (super_admin / admin_liga / capturista) enforced in both Angular route guards and Firestore security rules, scoped per allowed league.
  • Integrated Stripe end to end: subscription billing, hardened webhook-driven charges, account statements, and a self-service customer portal via Cloud Functions.
  • Built a multi-tenant hosting setup — a dedicated branded public site per league alongside the admin app — on Firebase Hosting targets.
  • Created a historical-stats layer that merges official leader exports with computed data via fuzzy (Jaccard) name matching for legacy leagues.
  • Offered a second, faster inning-by-inning box-score capture path for organizers who don't need full pitch-level detail.
  • Set up CI/CD with GitHub Actions: automated production deploys on push to main via a dedicated service account.

Architecture

  • Angular 17 SPA: fully standalone components, lazy-loaded routes (loadComponent/loadChildren), router input binding, and global view transitions — no NgModules.
  • State managed with Angular signals (signal/computed/effect), no NgRx; LigaService boots by fetching all top-level collections in a single Promise.all and serves an in-memory signal cache.
  • Optimistic cache pattern: writes go to Firestore and immediately update the signal cache via matching mutators, avoiding re-fetches.
  • Firestore as primary datastore with multi-tab IndexedDB offline persistence; static TypeScript datasets back historical exports and fallback display data.
  • Season stats are consolidated in real time as games are captured; Cloud Functions handle user provisioning and Stripe billing.
  • Cloud Functions (Node.js + TypeScript): Stripe subscription billing and customer portal, user lifecycle (create/update/delete via Admin SDK), post-signup provisioning, and transactional email via SendGrid.
  • Security rules: public read on all collections; writes gated by role (super_admin full access, admin_liga scoped by an allowed-leagues map, capturista limited to game updates); /users writable only by the Admin SDK.
  • Firebase Hosting with multiple targets (admin app + per-league public sites) and immutable long-cache headers for hashed assets.
  • CI/CD via GitHub Actions: automatic production deploy on push to main using a dedicated service account.

Tags

SaaSSports TechBaseballLive ScoringMulti-tenantOffline-firstStripeFirebaseAngularLATAMFull Stack