NBA Feed
팀 뉴스와 경기 정보를 제공, 점수는 직접 확인할 때까지 숨겨져 있습니다
개인 트레이너를 대신하는 몸 재구성 앱, 정확한 루틴·세트 중량·동작 애니메이션 제공, 7일 무료 체험
La app de recomposición corporal que reemplaza a tu entrenador personal: rutina exacta, el peso de cada serie y ejercicios con técnica animada. Prueba 7 días gratis con todo incluido.
스포츠·피트니스TypeScript · PLpgSQLGitHub @andresguevarag1-droid온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 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.