OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Entrenador personal con IA, en español, para LATAM. Next.js 15 (App Router) + Supabase + Tailwind v4, desplegado en Vercel.
| Ruta | Qué es |
|---|---|
/ · /precios | Landing de ventas (tema claro, marca Banana) |
/entrar | Login multiusuario: código por correo (OTP) + Google |
/bienvenida | Onboarding de 6 pasos: meta, datos, peso y fecha con ritmo sano, días, equipo, consentimiento → plan calculado |
/app | Hoy: racha 🔥, nivel/XP, rutina del día, pesaje rápido, anillos de proteína/kcal/agua/pasos/sueño |
/app/entreno | Reproductor para el gym: animación de técnica, cargas sugeridas, descanso con alarma, récords, resumen |
/app/entrenar · /nutricion · /progreso · /coach | Programa por bloques con selector de día, registro de comidas con base tica (70 alimentos), gráficas de peso y fuerza con ajuste semanal, Coach IA con Claude (streaming) |
/app/perfil | Plan de entreno (días, gym/casa, ritmo), suscripción, descargar datos y borrar cuenta (Ley 8968) |
/admin/* | Panel de staff: resumen, usuarios, pagos, contenido, comunidad y auditoría (ver sección Admin) |
src/lib/engine/ — motor portado del prototipo: calorías (Mifflin-St Jeor), doble progresión con reinicio −10 %, descargas, tendencia de peso, plan semanal anti-aburrimiento. Tests: npm test.src/middleware.ts — protege /app y /admin.supabase/migrations/0001_init.sql — esquema completo con RLS, roles y consentimiento de salud (Ley 8968).Sin variables de entorno la app corre en modo demo (datos de Andrés, sin login) para que el deploy funcione desde el día 1.
us-east-1 recomendada).supabase/migrations/ (0001_init.sql, 0002_onboarding.sql, … 0005_admin.sql).{{ .Token }} para que llegue el código de 6 dígitos.https://TU-DOMINIO/auth/callback y http://localhost:3000/auth/callback.ANTHROPIC_API_KEY en Vercel. Para que "Borrar mi cuenta" funcione agrega también SUPABASE_SERVICE_ROLE_KEY (solo servidor).NEXT_PUBLIC_SUPABASE_URL y NEXT_PUBLIC_SUPABASE_ANON_KEY a Vercel → Settings → Environment Variables y redepliega./entrar y hazte superadmin en el SQL Editor:
update public.profiles set role = 'superadmin'
where id = (select id from auth.users where email = 'TU_CORREO');
Suscripciones en USD con ONVO Pay. Flujo: /precios → POST /api/pagos/checkout crea el cliente y una suscripción pendiente en ONVO (paymentBehavior: "allow_incomplete") → /pago/checkout muestra el SDK web de ONVO, que cobra sin que la tarjeta pase por nuestro servidor → ONVO llama al webhook, que es lo único que activa el plan (subscriptions + profiles.plan). Los precios salen de src/lib/plans.ts; el navegador solo envía plan y billing.
| Ruta | Qué hace |
|---|---|
POST /api/pagos/checkout | { plan: "entreno"|"completo", billing: "mes"|"anual" } → { url } (requiere sesión) |
POST /api/pagos/webhook | Eventos de ONVO. Verifica X-Webhook-Secret, idempotente (payment_events) |
POST /api/pagos/cancelar | Corta la renovación; el acceso sigue hasta el fin del periodo pagado |
/app/suscripcion | Plan, estado, renovación, cambio de plan, cancelación con oferta de retención, historial de pagos |
/pago/checkout · /pago/exito · /pago/cancelado | Pago con el SDK, confirmación y "no se cobró" |
Configuración (modo prueba primero):
supabase/migrations/0004_payments.sql. Requiere SUPABASE_SERVICE_ROLE_KEY en Vercel.onvo_test_secret_key_…) a ONVO_SECRET_KEY y la publicable (onvo_test_publishable_key_…) a NEXT_PUBLIC_ONVO_PUBLISHABLE_KEY.plans.ts:
ONVO_PRICE_ENTRENO_MES 9.99/mes · ONVO_PRICE_ENTRENO_ANUAL 59.99/año · ONVO_PRICE_COMPLETO_MES 14.99/mes · ONVO_PRICE_COMPLETO_ANUAL 89.99/año. Copia cada ID a su variable. Si un monto no coincide, el webhook lo anota en payment_events.note.https://<dominio>/api/pagos/webhook con los eventos subscription.renewal.succeeded, subscription.renewal.failed y payment-intent.succeeded (los demás se registran y se ignoran). Copia el secreto (webhook_secret_…) a ONVO_WEBHOOK_SECRET.4242 4242 4242 4242 (aprobada), 4000 0000 0000 0002 (rechazada) o 4000 0000 0000 3220 (3DS). Revisa public.payment_events y que profiles.plan cambie.onvo_live_… y precios en modo live (los IDs de prueba no sirven en live).Sin estas variables la app compila y funciona igual: los botones de pago quedan desactivados y en modo demo se muestra un aviso.
Correo (Resend), Web Push (VAPID), avisos in-app (campanita) y un cron de recordatorios. Todo es no-op sin variables de entorno: el build y el modo demo funcionan igual.
| Pieza | Dónde |
|---|---|
| Lógica de decisión (quién recibe qué, a qué hora local, silencio, tope de 2 push/día) | src/lib/notify.ts · tests src/lib/notify.test.ts |
| Plantillas de correo (HTML + texto, marca banana, español) | src/lib/email/templates.ts |
| Envío Resend + funciones transaccionales para auth/onboarding/pagos | src/lib/email/send.ts, src/lib/email/index.ts |
| Baja con un clic (token HMAC) | GET/POST /api/email/baja?token=… · src/lib/email/token.ts |
| Web Push | public/sw.js (solo push, sin caché offline), src/lib/push.ts, POST/DELETE /api/push/subscribe |
| Cron | GET /api/cron/recordatorios (Authorization: Bearer $CRON_SECRET) · src/lib/notify-run.ts |
| UI | src/components/notifications/{settings,bell,enable-push}.tsx · server actions src/app/actions/notifications.ts |
| Esquema | supabase/migrations/0006_notifications.sql (prefs, push_subscriptions, notification_log, notifications + RLS) |
Qué se manda (hora local del usuario, zona por defecto America/Costa_Rica):
reminder_time, día de entreno y sin entreno → "Hoy te toca {rutina} 💪" (push + correo según preferencias).notification_log).supabase/migrations/0006_notifications.sql. Requiere SUPABASE_SERVICE_ROLE_KEY en Vercel (el cron y los webhooks usan service_role).bananaburn.app: Resend → Domains → Add domain bananaburn.app (región us-east-1). Copia en tu DNS los registros que te da: MX y TXT (SPF) del subdominio send, el TXT DKIM resend._domainkey y, recomendado, un TXT DMARC en _dmarc (v=DMARC1; p=none; rua=mailto:[email protected]). Espera a que salga Verified y crea una API key con permiso Sending. Variables: RESEND_API_KEY, EMAIL_FROM="Banana Burn <[email protected]>".EMAIL_UNSUB_SECRET=$(openssl rand -hex 32). Si lo cambias, los enlaces viejos dejan de funcionar.npx web-push generate-vapid-keys
Pon la pública en NEXT_PUBLIC_VAPID_PUBLIC_KEY (y opcionalmente VAPID_PUBLIC_KEY), la privada en VAPID_PRIVATE_KEY y VAPID_SUBJECT=mailto:[email protected]. No las rotes: cambiar las llaves invalida todas las suscripciones. En iPhone el push solo funciona con la app instalada en inicio (iOS 16.4+); la UI lo explica.CRON_SECRET=$(openssl rand -hex 32) en Vercel. Vercel lo manda solo como Authorization: Bearer …. vercel.json lo programa cada hora (0 * * * *), lo que requiere Vercel Pro.
"0 13 * * *" (07:00 Costa Rica) y pon NOTIFY_CRON_MODE=daily. En ese modo el recordatorio, la reactivación y el semanal de los lunes salen en esa corrida (ignorando reminder_time) y el aviso de racha de las 20:00 se omite. Pro desbloquea el horario por usuario y la racha.curl -H "Authorization: Bearer $CRON_SECRET" https://TU-DOMINIO/api/cron/recordatorios (es idempotente: repetirlo no duplica envíos).Panel en /admin (en español, pensado para escritorio y usable en móvil). Sin Supabase muestra datos de demo y permite simular rol desde la barra lateral para revisar permisos.
| Ruta | Qué hace |
|---|---|
/admin | KPIs: usuarios, nuevos 7/30 d, activos hoy/7 d, entrenos/día, conversión, MRR, churn 30 d, cohortes D1/D7/D30 |
/admin/usuarios · /[id] | Búsqueda (nombre, correo, ID) con filtros y paginación · ficha con cambio de rol, cortesías y reinicio de bienvenida |
/admin/pagos | Suscripciones, MRR por plan, renovaciones y pagos fallidos (tolera columnas faltantes en subscriptions) |
/admin/contenido | Overrides de ejercicios, alimentos (CRUD + verificado), retos y anuncios |
/admin/comunidad | Cola de moderación sobre reports (resolver, ocultar, descartar) |
/admin/auditoria | Visor de audit_log con filtros |
Permisos (src/lib/admin-perms.ts, repetidos en RLS y en funciones SECURITY DEFINER de 0005_admin.sql):
| coach | support | admin | superadmin | |
|---|---|---|---|---|
| Resumen (sin ingresos) · usuarios (cuenta) · moderación | ✅ | ✅ | ✅ | ✅ |
| Peso, meta, actividad del usuario | ✅ | — | ✅ | ✅ |
| Facturación, pagos, cortesías, reiniciar bienvenida | — | ✅ | ✅ | ✅ |
| Ejercicios, alimentos, retos | ✅ | — | ✅ | ✅ |
| Ingresos (MRR/churn), anuncios, auditoría, cambiar roles | — | — | ✅ | ✅ |
| Otorgar/quitar admin o superadmin | — | — | — | ✅ |
Toda mutación es una server action que valida rol y entrada en el servidor y escribe public.audit_log. Cortesías vencidas: programa select public.expire_comp_grants(); a diario (pg_cron o un cron con service_role).
Para la app: src/lib/admin-content.ts → getLiveAnnouncements(), getChallengeCatalog(), getExerciseOverrides(); componente <AnnouncementBanner /> en src/components/admin/announcement-banner.tsx. Comunidad: insertar en reports y filtrar con public.is_hidden(tipo, id).
Por cada versión con cambios que la persona nota, agrega una entrada arriba de src/lib/novedades.ts con los beneficios escritos para ella (qué cambió y cómo mejora su experiencia, con el enlace para probarlo). Al entrar a Hoy, quien no la vio recibe "Lo nuevo en Banana Burn" con todo lo que cambió desde su última visita, según su plan; el historial está en /app/novedades (Perfil y Más). Lo visto se guarda en profiles.news_seen (0036_novedades.sql) y el cron avisa por push y campanita a quien no abre la app en 3 días. Reglas de redacción y detalles: docs/novedades.md. Prueba: npx vitest run src/lib/novedades.test.ts.
cp .env.example .env.local # opcional
npm install
npm run dev # http://localhost:3000
npm test # motor de entrenamiento
npm run build
Fase 1 (2–22 nov): reproductor de entreno + registro de series, onboarding, coach IA (Claude), PWA. Fase 2 (23 nov–6 dic): pagos ONVO (tarjeta, SINPE Móvil, Apple Pay), admin v1 completo. Fase 3 (7–20 dic): beta cerrada CR. Fase 4 (ene 2027): lanzamiento LATAM.
/app/ejercicios lista 1.324 ejercicios con pasos en español, músculos y equipo, generados desde
hasaneyldrm/exercises-dataset (datos bajo licencia MIT,
ver src/data/EXERCISES-DATASET-LICENSE.txt). Regenerar: node scripts/build-exercise-library.mjs <exercises.json> <names_es.json>.
Medios (GIF/imágenes): son © Gym visual y la licencia MIT no los cubre; el permiso del repo es para ese autor,
no para nosotros. Por eso no están en este repo. Para mostrarlos en una app de pago hay que obtener licencia propia de
Gym visual (https://gymvisual.com/), subir los archivos a un CDN y definir EXERCISE_MEDIA_BASE_URL; mientras tanto la app
usa sus propias animaciones SVG y no muestra GIFs.