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
