Proyecto personal · SaaS · App móvil + panel web · 2026 Personal project · SaaS · Mobile app + web panel · 2026

App de gimnasioGym app

Una plataforma para que los socios vuelvan: entrenamiento, reservas y gamificación en el móvil, y un panel para el gimnasio. A platform that makes members come back: training, bookings and gamification on the phone, plus a panel for the gym.

Descárgalo en elDownload on theApp Store
La app en un iPhone: pantalla de inicio del socio con el calendario del mes y los accesos a entrenar, mensajes, progreso, nutrición y puntos
El panel web del gimnasio en un portátil, el modo profesional en un iPad y la app del socio en un iPhone

↑ El panel web, el modo profesional en iPad y la app del socio. Capturas reales, con datos de un centro de pruebas. ↑ The web panel, professional mode on iPad and the member app. Real captures, with data from a test centre.

RolRole
Arquitectura, diseño y desarrollo full-stackArchitecture, design & full-stack dev
ContextoContext
Proyecto personal · cliente piloto en LeónPersonal project · pilot client in León
PlataformaPlatform
iOS (App Store) · Web · Android en preparacióniOS (App Store) · Web · Android coming
Stack
TypeScript · React Native · Next.js · Supabase
EstadoStatus
Publicada en la App Store · en usoLive on the App Store · in use

Qué esWhat it is 01

Es una plataforma SaaS multi-tenant para gimnasios: una sola base de datos y un solo código sirven a N gimnasios, cada uno con sus socios, entrenadores y configuración, aislados entre sí. Es un proyecto personal que nació con un cliente real: un centro de entrenamiento de León, que es quien la estrena. It's a multi-tenant SaaS platform for gyms: one database and one codebase serve N gyms, each with its own members, trainers and settings, isolated from one another. It's a personal project born with a real client: a training centre in León, which is the first to use it.

El problema que ataca no es "gestionar un gimnasio", sino la retención: la mayoría de la gente que se apunta deja de ir a los pocos meses. Por eso el producto une en un mismo sistema al socio y al entrenador (entrenamientos, progresión, plan semanal, citas) y le añade una capa de gamificación diseñada desde por qué vuelve la gente, no desde qué botones tiene la pantalla. The problem it tackles isn't "managing a gym" but retention: most people who sign up stop going within a few months. So the product brings member and trainer into one system (workouts, progression, weekly plan, appointments) and adds a gamification layer designed around why people come back, not around what buttons the screen has.

Tiene dos caras: una app móvil (React Native + Expo) para socios y entrenadores, y un panel web (Next.js) para el dueño y el staff. Todo apoyado en Supabase / PostgreSQL, con la lógica crítica viviendo en el servidor. It has two faces: a mobile app (React Native + Expo) for members and trainers, and a web panel (Next.js) for the owner and staff. All of it backed by Supabase / PostgreSQL, with the critical logic living on the server.

Lo que empezó para un solo centro se convirtió en producto. El modelo soporta cadenas con varias sedes, donde cada sede puede llevarle la contraseña a su central en lo que le afecta, y tiene un modelo de precios cerrado —degresivo por socio activo, con mínimo y tope por sede— pensado para que un gimnasio pequeño pueda pagarlo el primer mes. What started for a single centre became a product. The model supports chains with several branches, where each branch can override its head office on what affects it, and it has a closed pricing model — degressive per active member, with a floor and a per-branch cap — designed so a small gym can afford it from month one.

Y una parte que no se ve en las pantallas y condiciona todo lo demás: la app maneja lesiones, mediciones corporales y nutrición. Eso son datos de salud, categoría especial del artículo 9 del RGPD. No es una funcionalidad más: cambia cómo se guarda, quién puede leerlo, cuánto tiempo se conserva y qué pasa el día que alguien pide sus datos o quiere borrarse. And a part you don't see on screen that shapes everything else: the app handles injuries, body measurements and nutrition. That is health data — GDPR Article 9 special category. It isn't just another feature: it changes how it's stored, who can read it, how long it's kept and what happens the day someone asks for their data or wants to be erased.

Qué haceWhat it does 02

Para el socioFor the member
La sesión de hoy ("hoy toca X") y el plan semanal, y la sesión en vivo: series, descansos, cardio por tramos y una cuenta atrás guiada para estiramientos que vibra al cambiar de lado. Un catálogo de más de 850 ejercicios clasificados por familias, historial, gráficas de progresión y mediciones. Nutrición con recetas, pasos de Apple Health y calorías del entreno calculadas con MET. Tablón, mensajes con el gimnasio y su pantalla de puntos, rachas y récords. Today's session ("today it's X") and the weekly plan, plus the live session: sets, rest timers, cardio by intervals and a guided countdown for stretches that vibrates when it's time to switch sides. A catalog of 850+ exercises grouped by families, history, progression charts and measurements. Nutrition with recipes, steps from Apple Health and workout calories computed with METs. Notice board, messaging with the gym and their points, streaks and records screen.
Para el entrenadorFor the trainer
Lista de clientes, creación de plantillas de entrenamiento, planificación de la semana de cada cliente, agenda de citas, plantillas por ciclos de semanas con series por % del 1RM, drop sets y jump sets, hojas de rutina imprimibles, un modo profesional para iPad para entrenar con el cliente delante, y un semáforo de lesiones: a partir del perfil y las mediciones del socio, un motor de reglas marca en rojo, ámbar o verde qué ejercicios conviene evitar, adaptar o mantener. Client list, workout-template builder, weekly planning for each client, an appointments agenda, templates by multi-week cycles with sets at a % of 1RM, drop sets and jump sets, printable workout sheets, a professional mode for iPad to train with the client in front of you, and an injury traffic light: from the member's profile and measurements, a rules engine flags in red, amber or green which exercises to avoid, adapt or keep.
Para el gimnasioFor the gym
Calendario de clases y actividades con reservas y aforo, gestión de entrenadores y socios, registro por códigos de invitación, y un panel de administración con analítica de retención. Y un sistema de módulos activables: cada gimnasio enciende o apaga funcionalidades (tablón, gamificación, citas…) sin tocar código. Cobro de bonos y sesiones con Stripe, y una identidad visual propia por centro: colores, logo y tipografía en la app y en el panel. Class and activity calendar with bookings and capacity, trainer and member management, sign-up by invitation codes, and an admin panel with retention analytics. Plus a system of switchable modules: each gym turns features on or off (board, gamification, appointments…) without touching code. Payments for session packs through Stripe, and its own visual identity per centre: colours, logo and typography in the app and the panel.
Salud y privacidadHealth & privacy
Lesiones, mediciones y diario de comidas viven detrás de consentimientos que impiden de verdad: si el socio no ha dado ese permiso, la base rechaza la escritura — no es una casilla que solo esconde una pantalla. Cada socio puede pedir todos sus datos y borrar su cuenta desde la app, y cada dato tiene un plazo de conservación declarado y una purga que lo cumple. Injuries, measurements and the food diary sit behind consents that actually block: if the member hasn't granted that permission, the database refuses the write — it isn't a checkbox that merely hides a screen. Every member can request all their data and delete their account from the app, and every datum has a declared retention period and a purge that enforces it.
Recepción y retenciónFront desk & retention
Control de acceso: el socio enseña un código de un solo uso que caduca en 60 segundos y recepción lo valida. De ahí sale el dato que alimenta lo importante: quién se está yendo. Cada persona se compara consigo misma, no con la media — quien viene una vez por semana desde siempre no es un problema; quien venía tres y ahora viene una, sí. Más informes de asistencia, horas punta y ocupación de clases. Access control: the member shows a single-use code that expires in 60 seconds and the desk validates it. That feeds what matters: who is drifting away. Each person is compared against themselves, not against the average — someone who has always come once a week isn't a problem; someone who came three times and now comes once is. Plus attendance reports, peak hours and class occupancy.
GamificaciónGamification
Puntos por sesión completada, por récord de peso o repeticiones, por racha semanal y por asistir a clases. Los valores se afinan por gimnasio desde su configuración. Los récords y las rachas los detecta el servidor, no la app, para que nadie pueda inflarse los puntos. Points for completed sessions, for weight or rep records, for weekly streaks and for attending classes. Values are tuned per gym from its settings. Records and streaks are detected by the server, not the app, so nobody can inflate their points.

Decisiones de Architecture arquitecturadecisions 03

Lo que más me interesa de este proyecto no son las pantallas, sino lo que hay debajo. Estas son las decisiones que lo sostienen: What interests me most about this project isn't the screens but what's underneath. These are the decisions holding it up:

01

Multi-tenant con Multi-tenant with RLS

Cada fila lleva su gym_id y las políticas de Row Level Security de PostgreSQL garantizan que un gimnasio nunca vea datos de otro, aunque el cliente lo intente. El aislamiento está en la base de datos, no en el código de la app. Every row carries its gym_id and PostgreSQL's Row Level Security policies guarantee one gym never sees another's data, even if the client tries. Isolation lives in the database, not in app code.

02

Lógica crítica Critical logic en el servidoron the server

Aforos, puntos, privacidad y permisos se resuelven en funciones security definer de PostgreSQL y triggers de negocio. La app solo pide; el servidor decide. Así el mismo comportamiento vale para móvil y panel, y no se puede saltar desde el cliente. Capacity, points, privacy and permissions are resolved in PostgreSQL security definer functions and business triggers. The app only asks; the server decides. The same behavior serves mobile and panel, and can't be bypassed from the client.

03

Módulos como Modules like mods

La configuración de cada gimnasio vive en un JSONB. Un módulo ausente cuenta como activo, así que añadir funcionalidades nuevas no altera a los gimnasios existentes. Solo el administrador puede tocar sus interruptores, y lo hace desde una función validada en servidor. Each gym's configuration lives in a JSONB. A missing module counts as active, so adding new features doesn't disturb existing gyms. Only the admin can flip the switches, through a server-validated function.

04

Ledger Immutable inmutableledger

Los puntos no se "guardan": se apuntan como eventos en un libro de cuentas al que solo se añade, y el total es la suma. Nadie inserta desde la app; solo las funciones de servidor, de forma idempotente. Auditable e infalsificable. Points aren't "stored": they're appended as events to an append-only ledger, and the total is the sum. Nothing inserts from the app; only server functions, idempotently. Auditable and tamper-proof.

05

Reservas sin Bookings without overbookingoverbooking

Dos socios reservando la última plaza a la vez es el caso clásico de concurrencia. Se resuelve con bloqueos de fila en la transacción: el aforo se comprueba y se descuenta de forma atómica. Two members booking the last spot at the same time is the classic concurrency case. It's solved with row locks inside the transaction: capacity is checked and decremented atomically.

06

Migraciones Versioned versionadasmigrations

Más de 180 migraciones SQL numeradas y unas 35.000 líneas: 78 tablas, más de 500 funciones, 148 políticas RLS y 70 triggers. Cada una lleva escrito en la cabecera por qué existe y qué rompía antes. Nada se toca a mano en producción. 180+ numbered SQL migrations and some 35,000 lines: 78 tables, 500+ functions, 148 RLS policies and 70 triggers. Each one states in its header why it exists and what used to break. Nothing is touched by hand in production.

07

Pruebas que Tests that fallan antesfail first

45 suites de regresión en SQL. La regla: una prueba tiene que fallar contra el código anterior y pasar con el nuevo, o no prueba nada. Cada negativa lleva su control positivo, y afirma el motivo del rechazo — no basta con que algo falle, tiene que fallar por lo que se dice. Han cazado tres correcciones que yo daba por buenas leyendo el código. 45 SQL regression suites. The rule: a test must fail against the previous code and pass with the new one, or it proves nothing. Every negative case carries its positive control and asserts the reason for the rejection — it isn't enough that something fails, it has to fail for the stated reason. They caught three fixes I had judged correct by reading the code.

08

Auditoría de Security seguridadaudit

Se auditó el sistema entero contra sí mismo: hallazgos numerados, puntuados por impacto × explotabilidad × exposición, y corregidos con su migración y su prueba. Salieron cosas serias — funciones ejecutables por usuarios anónimos, un permiso por defecto de PostgreSQL que reabría el agujero en cada función nueva. Se arregló, y hay un sensor que avisa si se vuelve a abrir. The whole system was audited against itself: numbered findings, scored by impact × exploitability × exposure, each fixed with its own migration and test. Serious things surfaced — functions executable by anonymous users, a PostgreSQL default privilege that reopened the hole on every new function. It was fixed, and a sensor now warns if it reopens.

09

Sedes que llevan Branches that la contrariaoverride

Una cadena decide por defecto y cada sede puede apartarse: una clave ausente se hereda, una presente manda. La central puede además fijar un módulo con candado y entonces la sede ni lo ve. La regla de herencia vive en la base, no copiada en cada pantalla — de eso ya me enseñó un interruptor que se veía apagado estando encendido. A chain decides by default and each branch can deviate: a missing key is inherited, a present one wins. Head office can also lock a module, and then the branch can't even see it. The inheritance rule lives in the database, not copied into every screen — a switch that showed off while being on already taught me that.

10

Textos legales Legal texts con versiónwith a version

La política de privacidad y las condiciones viven en la base con versión y país, y la aceptación queda registrada contra la versión concreta que se firmó. Cambiar el contenido de una versión ya aceptada convertiría todos los consentimientos guardados en papel mojado, así que el sistema lo impide: si el texto cambia, se publica una versión nueva y se vuelve a aceptar. The privacy policy and terms live in the database with a version and a country, and acceptance is recorded against the exact version that was signed. Editing the content of an already-accepted version would turn every stored consent into waste paper, so the system forbids it: if the text changes, a new version is published and re-accepted.

11

Pagos sin tocar Payments without el dinerotouching the money

Los cobros van con Stripe Connect: el dinero pasa del socio a la cuenta del gimnasio sin quedarse nunca en la plataforma. Funciones en el borde crean el pago, y un webhook firmado e idempotente confirma el bono en la base: si Stripe avisa dos veces, se apunta una. Payments run on Stripe Connect: money goes from the member to the gym's account and never sits on the platform. Edge functions create the payment, and a signed, idempotent webhook confirms the pack in the database: if Stripe notifies twice, it's recorded once.

12

Actualizaciones Over-the-air por aireupdates

Con la app ya publicada, los cambios de interfaz y de lógica llegan a los móviles sin pasar otra vez por la tienda, con Expo Updates. Solo lo nativo (permisos, librerías con código de sistema) exige una build nueva, y cada tanda se publica pequeña para poder volver atrás si falla. With the app already live, UI and logic changes reach phones without going through the store again, via Expo Updates. Only native changes (permissions, libraries with system code) need a new build, and each batch ships small so it can be rolled back if it fails.

GaleríaGallery 04

↑ Capturas reales del panel, del modo profesional en iPad y de la app, con datos de un centro de pruebas. ↑ Real captures of the panel, the professional mode on iPad and the app, with data from a test centre.

Stack 05

LenguajesLanguages
TypeScriptSQL / plpgsqlJSX
App móvilMobile app
React NativeExpo SDK 57EAS BuildExpo UpdatesHealthKitNotificaciones pushPush notificationsPanResponder / Animated
Panel webWeb panel
Next.jsReactTailwind CSSExportación estáticaCloudflare Pages
Backend
SupabasePostgreSQLRow Level Securitysecurity definerTriggersJSONBplpgsqlStorage privadoEdge FunctionsStripe Connect
HerramientasTools
MonorepoGit / GitHubVS CodeNode.js / npmSuites de regresión SQLEspejo local de la baseSentryApp Store Connect
Seguridad y RGPDSecurity & GDPR
CSP / HSTSConsentimientosConsentsPlazos de conservaciónRetention periodsDerecho de acceso y borradoAccess & erasure rightsTextos legales versionadosVersioned legal texts
CopilotoCopilot
Desarrollo asistido con Claude, con revisión y pruebas propias de cada rebanada antes de darla por buena. Development assisted by Claude, with my own review and testing of every slice before calling it done.

Estado actualCurrent status 06

Publicada en la App Store desde septiembre de 2026, después de pasar la revisión de Apple: el primer envío lo rechazaron por cómo se explicaba el uso de Apple Health, se respondió con la descripción y un vídeo, y la siguiente se aprobó. El panel web está en producción, los cobros con Stripe funcionan con dinero real y la app se sigue actualizando por aire. Live on the App Store since September 2026, after passing Apple's review: the first submission was rejected over how the use of Apple Health was explained, it was answered with the description and a video, and the next one was approved. The web panel is in production, Stripe payments run with real money and the app keeps being updated over the air.

El centro piloto de León ya la usa. Lo siguiente es Android (Google Play) y, con el piloto cerrado, ofrecerla a más centros. The pilot centre in León is already using it. Next comes Android (Google Play) and, once the pilot is closed, offering it to more centres.

Lo que más me ha enseñado este proyecto no es una tecnología: es que una corrección no está hecha hasta que una prueba la contradice y falla. Tres veces di por buena una corrección leyendo el código y tres veces la encontró mal la prueba automática. Eso cambió cómo trabajo. What this project taught me most isn't a technology: it's that a fix isn't done until a test contradicts it and fails. Three times I judged a fix correct by reading the code, and three times the automated test proved me wrong. That changed how I work.

Base de datosDatabase
180+ migraciones · 148 políticas RLS180+ migrations · 148 RLS policies
PruebasTests
45 suites de regresión45 regression suites
App móvilMobile app
41 pantallas · en la App Store41 screens · on the App Store
Panel webWeb panel
Publicado en producciónPublished in production
LanzamientoLaunch
iOS publicado · Android en preparacióniOS live · Android coming