Proyecto
Balltics — La plataforma que opera una liga real de béisbol
SaaS multi-tenant para administrar ligas de béisbol amateur de extremo a extremo: un sitio público en tiempo real para aficionados (posiciones, líderes, estadísticas de temporada, box scores completos) más una suite de administración con anotación pitcheo por pitcheo en vivo, gestión de rosters y cobros recurrentes con Stripe — pensado para el mercado mexicano/LATAM. En producción — opera la temporada completa de una liga real (Liga Arturo Peimbert Camacho).
En producción — opera una liga real Producto propio — en producción Fundador e Ingeniero (Full Stack — Producto, Backend, Infraestructura) Resumen público
Producto propio que poseo y opero de extremo a extremo (frontend, backend en Firebase, Cloud Functions, reglas de seguridad y CI/CD), ofrecido comercialmente a ligas de béisbol. Los detalles de implementación se resumen a alto nivel.
Stack
Angular 17TypeScriptAngular SignalsRxJSTailwind CSSFirebaseFirestoreFirebase AuthCloud FunctionsFirebase HostingFirebase StorageIndexedDBStripeResendGitHub ActionsGCP
Impacto
- Los organizadores administran una temporada completa desde una sola plataforma — posiciones, estadísticas, jornadas, torneos y facturación — reemplazando hojas de cálculo y anotación manual.
- Los aficionados siguen su liga en tiempo real desde cualquier teléfono sin instalar apps: posiciones en vivo, líderes de bateo/pitcheo, estadísticas de temporada y box scores completos.
- Las notificaciones push llegan al teléfono del organizador cuando se inscribe un equipo, cuando se confirma su pago o cuando entra una solicitud de liga — los momentos donde tardar en contestar cuesta una inscripción.
- La captura pitcheo por pitcheo en vivo convierte un teléfono en una consola de anotación profesional que genera box scores oficiales y estadísticas de temporada automáticamente, sin conteo manual.
- La captura offline-first sigue funcionando en campos remotos sin señal y sincroniza automáticamente al recuperar la conexión — cero juegos perdidos.
- Ingreso recurrente por liga vía suscripciones de Stripe, estados de cuenta y portal de cliente autogestionado.
- Multi-tenant desde el día uno — cada liga obtiene su propio sitio público con marca y datos aislados, así la misma plataforma escala a muchas ligas sin cambiar código.
Lo que hice
- Construí una máquina de estados pitcheo por pitcheo completa (~1,500 líneas) que controla cuenta, bases, corredores, outs, transiciones de entrada y sustituciones — generando líneas de bateo/pitcheo en vivo en tiempo real.
- Diseñé persistencia offline de doble capa: escrituras síncronas en localStorage más sincronización debounced a Firestore (IndexedDB), restaurando el estado exacto del juego tras recargas o pérdida de conexión.
- Diseñé un caché en memoria basado en signals que arranca toda la app desde un solo Promise.all, con un patrón de escritura optimista que actualiza la UI sin re-fetch.
- Implementé un sistema de tres roles (super_admin / admin_liga / capturista) aplicado tanto en guards de ruta de Angular como en reglas de seguridad de Firestore, acotado por liga permitida.
- Integré Stripe de extremo a extremo: cobros por suscripción, cargos blindados vía webhooks, estados de cuenta y portal de cliente autogestionado mediante Cloud Functions.
- Construí un esquema de hosting multi-tenant — un sitio público con marca dedicado por liga junto a la app de administración — sobre targets de Firebase Hosting.
- Creé una capa de estadísticas históricas que combina exportaciones oficiales de líderes con datos calculados mediante coincidencia difusa de nombres (Jaccard) para ligas heredadas.
- Ofrecí una segunda ruta de captura más rápida por box score entrada por entrada para organizadores que no necesitan detalle pitcheo por pitcheo.
- Configuré CI/CD con GitHub Actions: deploys automáticos a producción en cada push a main vía una cuenta de servicio dedicada.
- Una sola marca generada desde un único vector alimenta íconos, correos e imágenes para redes, así un cambio de marca actualiza todos los archivos juntos en vez de dejar cuatro versiones desincronizándose.
Arquitectura
- SPA en Angular 17: componentes totalmente standalone, rutas lazy-loaded (loadComponent/loadChildren), router input binding y view transitions globales — sin NgModules.
- Estado con Angular signals (signal/computed/effect), sin NgRx; LigaService arranca obteniendo todas las colecciones de primer nivel en un solo Promise.all y sirve un caché en memoria basado en signals.
- Patrón de caché optimista: las escrituras van a Firestore y actualizan de inmediato el caché de signals vía mutadores correspondientes, evitando re-fetches.
- Firestore como almacén principal con persistencia offline multi-pestaña en IndexedDB; datasets estáticos en TypeScript respaldan exportaciones históricas y datos de respaldo.
- Las estadísticas de temporada se consolidan en tiempo real conforme se capturan los juegos; las Cloud Functions manejan aprovisionamiento de usuarios y facturación con Stripe.
- Cloud Functions (Node.js + TypeScript): cobro por suscripción y portal de cliente de Stripe, ciclo de vida de usuarios (crear/actualizar/eliminar vía Admin SDK), aprovisionamiento post-registro y correos transaccionales vía SendGrid.
- Reglas de seguridad: lectura pública en todas las colecciones; escrituras acotadas por rol (super_admin acceso total, admin_liga acotado por un mapa de ligas permitidas, capturista limitado a actualizar juegos); /users solo escribible por el Admin SDK.
- Firebase Hosting con múltiples targets (app de administración + sitios públicos por liga) y headers de caché largo inmutable para assets con hash.
- CI/CD con GitHub Actions: deploy automático a producción en cada push a main usando una cuenta de servicio dedicada.
Etiquetas
SaaSSports TechBaseballLive ScoringMulti-tenantOffline-firstStripeFirebaseAngularLATAMFull Stack
