BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Plataforma SaaS multi-club para clubes de baloncesto de formación. Este repo es la app web: Next.js (App Router), Tailwind y Supabase con RLS. • Reglas del proyecto y lista de comandos: CLAUDE.md. Léelo antes de tocar nada. • Qué se construye: docs/spec/club-os-primera-entrega.md. • Plan de cada fase: docs/superpowers/plans/. • Design system: design/README.md. • Node 24, la versión que usa CI. • pnpm 12. La versión exacta está en packageManager, en package.json. • Docker en marcha. El Supabase local corre en contenedores. 1. Instala las dependencias.檢視完整 README ↗
Plataforma SaaS multi-club para clubes de baloncesto de formación. Este repo es la app web: Next.js (App Router), Tailwind y Supabase con RLS.
packageManager, en package.json.Instala las dependencias.
pnpm install
Arranca Supabase en local. La primera vez descarga las imágenes y aplica las migraciones.
pnpm supabase start
Arráncalo con todos sus servicios, Storage incluido ([storage] enabled = true en supabase/config.toml, que es lo que hace supabase start a secas). La biblioteca de ejercicios guarda sus diagramas en un bucket privado. Sin el servicio de Storage no funcionan la subida de diagramas, los tests de Storage de pnpm test:int ni los e2e que suben un diagrama: el Supabase local tiene que incluir Storage (no lo excluyas con -x storage-api ni desactives [storage]).
Copia .env.example a .env.local y rellénalo con lo que imprime pnpm supabase status -o env:
En .env.local | Valor de supabase status -o env |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | API_URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | PUBLISHABLE_KEY (en CLI antiguos, ANON_KEY) |
SUPABASE_SERVICE_ROLE_KEY | SERVICE_ROLE_KEY (si no viene, SECRET_KEY) |
.env.local no se sube al repo. La clave de servicio solo la usan scripts/ y e2e/, y nunca se configura en Vercel.
Siembra los datos de ejemplo: dos clubes ficticios.
pnpm seed
Arranca la app y abre http://localhost:3000.
pnpm dev
El acceso es por invitación, con un código de 6 dígitos. No hay contraseñas ni registro. La única excepción es el acceso de demo, temporal y apagado por defecto (ver «Acceso de demo»).
/login, escribe el email de un usuario del seed.| Quién es | |
|---|---|
[email protected] | Entrenador de Alevín A |
[email protected] | Entrenadora ayudante de Alevín A |
[email protected] | Entrenadora de Benjamín A, en el mismo club |
[email protected] | Dirección, sin equipo |
[email protected] | Entrenadora del otro club |
[email protected] | Cuenta sin club |
pnpm seed se puede repetir: no duplica nada y recalcula las fechas respecto a hoy.
Si has usado Gestión en un club del seed, al volver a sembrar:
pnpm seed lo dice al acabar.Con las sesiones de entrenamiento pasa lo mismo: las del seed vuelven a lo que dice el seed (título, estado, lugar y ejercicios) y cambian de fecha con él; las que creaste en la app se quedan.
| Comando | Qué prueba | Qué necesita |
|---|---|---|
pnpm lint, pnpm typecheck | ESLint y TypeScript | Nada más |
pnpm check:guards | Reglas 2, 3 y 4 de CLAUDE.md (ni la clave de servicio ni nada de un club en src/; ni colores hex ni medidas entre corchetes con unidad en los componentes), que cada página de Gestión se exporta con adminPage y tokens al día | Nada más |
pnpm test | Unidad y componentes (Vitest) | Nada más |
pnpm test:db | RLS y aislamiento entre clubes y entre equipos, las funciones SQL y la postura de privilegios de todo public (pgTAP) | Supabase local |
pnpm test:int | El seed, generateLoginCode (el código de acceso de los e2e), Storage (scripts/media/storage.int.test.ts: el bucket club-media y sus políticas, con la sesión de cada usuario del seed) y el importador de contenido (scripts/content/import.int.test.ts) contra la base de datos | Supabase local con Storage, .env.local y el seed ya cargado (pnpm seed) |
pnpm test:e2e | La app en un móvil de 375×812 (Playwright) | Supabase local, .env.local y un puerto libre: el 3000, o el de PORT. Con BASE_URL, ver Entorno remoto |
pnpm exec playwright install chromium.pnpm build && pnpm start). Si ya hay algo en el puerto, lo usan tal cual. Con BASE_URL no arrancan nada: prueban esa URL.PORT=3100 pnpm test:e2e arranca y prueba la app en el 3100. Úsalo si el 3000 está ocupado (por ejemplo, con pnpm dev) o si pasas los e2e en dos copias del repo a la vez.mobile lee y corre en paralelo. admin son los specs que escriben (Gestión, la ficha y el editor de ejercicios, y las sesiones: crear, constructor y ejercicios en la sesión): van en serie y solo si mobile ha pasado. pnpm test:e2e --project=mobile lanza solo el primero. Un spec nuevo que escriba se añade a ADMIN_SPECS, en playwright.config.ts.club-media) y las sesiones de entrenamiento. Los partidos no se tocan. pnpm seed no borra nada; los e2e sí. Solo pasa con un Supabase local.En el build de producción la cookie de sesión es Secure: solo viaja por HTTPS o en localhost. Con pnpm start y http://<IP>:3000, el móvil no guarda la sesión y el login no se mantiene.
Para probar en la red local:
pnpm dev --hostname <IP de tu equipo> y abre http://<IP>:3000. En desarrollo la cookie no es Secure.pnpm start.El seed trae ejercicios de ejemplo, ficticios. El contenido real de un club (sus ejercicios, con su pizarra) vive en un paquete bajo content/ y se carga con su propio comando, que no toca el seed:
pnpm content:import content/arcangel/biblioteca-entrenador-2026 --club arcangel
--update sobrescribe. Cada ejercicio del paquete vuelve a lo que dice el paquete, y lo retocado en la app en esos ejercicios se pierde. Lo que el formato del paquete no lleva se conserva: los Standards enlazados, el resumen y el vídeo.ALLOW_REMOTE_IMPORT=true. Esa variable va en la shell, solo para esa orden, y nunca en .env.local, igual que ALLOW_REMOTE_SEED. Permitir sembrar no permite importar, ni al revés. Para un remoto, la URL y la clave de servicio se ponen como en Sembrar el demo.El formato del paquete está en content/README.md.
Dos avisos en local:
pnpm test:int falla con un paquete importado. El test del seed cuenta los ejercicios de cada club. Para volver a una base limpia: pnpm supabase db reset y pnpm seed.Hay un entorno desplegado para probar en un móvil real y para enseñar la app. Es de demo: nunca lleva datos reales.
eu-west-1 (Irlanda). Tiene las migraciones de supabase/migrations/ con las mismas versiones que el repo. No tiene supabase/seed.sql: es del esquema tests y no debe llegar nunca al remoto.club-os, con las funciones en dub1 (Dublín, junto a la base de datos). Lo fija vercel.json.main despliega producción, en https://club-os-phi.vercel.app. Las previews están detrás de la protección de despliegues de Vercel y piden un login de Vercel.Las migraciones nuevas van al remoto con las mismas versiones que tienen en el repo: pnpm supabase link --project-ref <ref> una vez y después pnpm supabase db push. Nunca supabase db reset --linked ni supabase db push --include-seed: lo primero borra la base de datos y lo segundo lleva seed.sql al remoto.
El Supabase remoto lo comparten producción (se despliega sola con cada push a main) y las previews de los PR. Las cuatro migraciones de la Fase 3 (20261103000100_drills, …000200_media_storage, …000300_drill_search y …000400_save_drill) tienen que estar aplicadas antes de usar la preview del PR para revisar en el móvil y antes de fusionar: sin ellas, /drills falla en cada carga y /train ya enlaza a ella. Son aditivas y seguras con la app que hay desplegada hoy: sigue funcionando con ellas puestas.
supabase db push aplica todo lo que falte en el remoto, no solo lo de la Fase 3: si alguna de las tres migraciones de la Fase 2 (The Way: 20261020000100_methodology, …000200_methodology_functions y, de la revisión de su PR, 20261021000100_methodology_integrity) tampoco está, se aplica en el mismo paso, delante de las cuatro.
Lista, en este orden:
pnpm supabase migration list contra el proyecto enlazado (pnpm supabase link --project-ref <ref> si aún no lo está). Apunta cuáles faltan: las de la Fase 3 y, quizá, alguna de las tres de la Fase 2. El resto de la lista vale igual con las de la Fase 2 pendientes que con ninguna, y db push las aplica en orden de versión.select count(*) from practice_items where drill_id is not null; tiene que dar 0. La migración añade la clave foránea practice_items (organization_id, drill_id) → drills, y fallaría con ítems que ya apuntan a un ejercicio que aún no existe.select extname, extnamespace::regnamespace from pg_extension where extname = 'unaccent'; no tiene que devolver una fila de otro esquema que extensions. La migración hace create extension if not exists unaccent with schema extensions: si ya estuviera instalada en otro esquema se saltaría, y extensions.unaccent (que usa la búsqueda) no existiría.show server_version;): la clave foránea del diagrama usa on delete set null (columna). El config.toml local fija la 17.storage.objects y escribir en storage.buckets.pnpm supabase db push, como arriba. Antes de confirmar, comprueba que la lista que enseña es la que viste en migration list: las cuatro de la Fase 3 y, si faltaba alguna, las de la Fase 2.media_assets no se borran).La Fase 4 trae tres migraciones: 20261117000100_practice_integrity, …000200_practice_write y …000300_practice_functions. Van detrás de las cuatro de la Fase 3: sus versiones son posteriores y dan por hecho algo que trae la primera de aquellas (un updated_at que avanza dentro de una transacción). Como aquellas, tienen que estar aplicadas antes de usar la preview del PR y antes de fusionar: sin ellas, abrir una sesión falla (/train/[eventId] lee una columna que aún no existe) y no se puede crear ni guardar ninguna.
supabase db push aplica todo lo que falte en el remoto, en orden de versión: si las de la Fase 3 (o las de la Fase 2) tampoco están, entran en el mismo paso, delante de estas tres. No hace falta aplicarlas por separado.
No hay nada nuevo en Storage, ni en Auth, ni en las variables de entorno: ni buckets, ni ajustes del panel, ni variables en Vercel.
Lista, en este orden:
pnpm supabase migration list contra el proyecto enlazado. Tienen que faltar, como mucho, las de las Fases 2, 3 y 4.pnpm supabase db push. Antes de confirmar, comprueba que la lista que enseña es la que viste en migration list.[email protected]): Sesiones → «Preparar sesión», crearla, añadirle un ejercicio y guardarla. Tiene que salir en «Próximas» y en Inicio si es la siguiente.[email protected]): abrir la URL de esa sesión. Tiene que ver «No encontramos esta página», sin ningún dato de la sesión. Lo mismo con [email protected], del otro club.organizations.timezone), aunque el móvil esté en otra zona horaria.Las tres migraciones no rompen la app que haya desplegada antes de fusionar: la única pantalla que leía planes era Inicio, que embebe el plan en su evento sin nombrar la clave foránea que la primera migración sustituye.
La Fase 6 trae dos migraciones: 20261215000100_development (objetivos y notas de jugador) y …000200_games_write (escritura de partidos). Van detrás de las de las Fases 4 y 5 y, como aquellas, tienen que estar aplicadas antes de usar la preview del PR y antes de fusionar: sin ellas, la ficha de un jugador falla (lee player_goals y coach_notes, que aún no existen) y no se puede crear ningún partido.
No hay nada nuevo en Storage, ni en Auth, ni en las variables de entorno.
Lista, en este orden:
pnpm supabase migration list contra el proyecto enlazado. Tienen que faltar, como mucho, las de las Fases 2 a 6.game con una clave foránea nueva y añade topes (rival de 1 a 80 caracteres, marcador entero o vacío). Falla, nombrando la restricción, si alguna fila no los cumple: no corrige nada en silencio. Hasta esta fase ningún usuario podía escribir partidos, así que en el remoto solo está el del seed, que los cumple.pnpm supabase db push, comprobando que la lista que enseña es la de migration list.[email protected]): Equipo → Hugo Serrano. Tiene que ver dos objetivos activos, el historial con uno logrado y las dos notas. Añadir un objetivo, marcarlo como logrado y escribir una nota.[email protected]): la misma ficha. Tiene que ver la nota del cuerpo técnico y no la privada de Álex. Lo mismo como dirección ([email protected]).[email protected]) y como [email protected]: la URL de esa ficha da «No encontramos esta página».Va encima de la estructura del club: primero su migración (abajo) y después esta, 20270316000100_calendar. Crea venues, team_schedules y event_changes, añade venue_id y schedule_id a events con sus triggers, y tres funciones para los horarios. No toca Storage, Auth ni variables de entorno. Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella fallan Inicio, la Agenda, las fichas de sesión y de partido y sus formularios, que ya leen las pistas y los cambios.
pnpm supabase migration list.events las pone la base, y el registro de cambios empieza vacío.pnpm supabase db push./admin/calendar da el 404 de siempre.Va encima del editor de pizarra: primero su migración (abajo) y después esta, 20270302000100_club_structure. Da a la dirección la escritura sobre people, seasons, categories, teams, team_staff y team_players (políticas y grant por columnas), añade restricciones a esas tablas y crea cuatro funciones. No añade tablas ni columnas, ni toca Storage, Auth o variables de entorno. Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella, en Gestión se ven la temporada y las personas, pero nada se guarda.
pnpm supabase migration list.pnpm supabase db push./admin/season y /admin/people dan el 404 de siempre.Va encima de la pizarra visual: primero su migración (abajo) y después esta, 20270216000100_save_drill_board. Concede update (board) sobre drills a authenticated y crea save_drill_board. No añade columnas, ni toca Storage, Auth o variables de entorno. Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella, guardar una pizarra falla (el resto de la app no la necesita).
pnpm supabase migration list.pnpm supabase db push.Va encima de «Preparar sesión»: primero sus dos migraciones (abajo) y después esta, 20270202000100_drill_board. Añade una columna a drills, board (la pizarra de un ejercicio como datos), con su check. No toca permisos, ni Storage, ni Auth, ni variables de entorno. Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella, la biblioteca, la ficha de un ejercicio, la de una sesión y el directo fallan (piden esa columna).
pnpm supabase migration list.pnpm supabase db push.pnpm seed; contra el remoto, solo con ALLOW_REMOTE_SEED=true y mientras producción siga siendo una demo).Va encima del uso diario: primero su migración (abajo) y después esta, 20270119000100_practice_templates. No crea tablas: añade un check a practice_plans (una plantilla no tiene evento), concede insert (is_template) y delete sobre ella, cambia la política de alta de un plan de equipo (rechaza el que venga marcado como plantilla), crea tres políticas y dos funciones (save_practice_as_template y create_practice_from_template). Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella, «Guardar como plantilla» y crear una sesión con una plantilla fallan. La propuesta de entrenamiento no depende de ella.
Nada en Storage, en Auth ni en las variables de entorno.
pnpm supabase migration list.check entra: ninguna fila tiene is_template con event_id (select count(*) from practice_plans where is_template and event_id is not null da 0). Con el seed lo da: no hay plantillas.is_template ni borra planes.pnpm supabase db push.Trae una migración: 20270105000100_live_state. Añade a practice_plans dos columnas (live_started_at y live_position), sustituye record_live_progress por una con dos parámetros opcionales más y crea reset_live_progress. Tiene que estar aplicada antes de usar la preview del PR y antes de fusionar: sin ella, Inicio y la ficha de una sesión fallan (piden esas columnas) y el directo no guarda.
No hay tablas nuevas, ni nada en Storage, en Auth o en las variables de entorno.
Lista, en este orden:
pnpm supabase migration list contra el proyecto enlazado.record_live_progress con cuatro argumentos: los dos nuevos tienen valor por defecto, así que sigue funcionando.pnpm supabase db push, comprobando que la lista que enseña es la de migration list.[email protected]): la barra es Inicio, Agenda, Sesiones, Biblioteca y Equipo. Agenda enseña entrenos y partidos por semanas, y «Identidad» está en Inicio y en el menú de cuenta.[email protected]): la cabecera tiene el selector de equipo. Elegir uno filtra Inicio, Agenda, Sesiones y Equipo; «Todos mis equipos» lo quita.Solo dos, las dos públicas. Van en Production y en Preview.
| Variable | Valor |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | La URL de la API del proyecto remoto |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | La clave pública (publishable) del proyecto remoto |
Las tres DEMO_LOGIN_* del acceso de demo son aparte y temporales: solo existen mientras esté encendido (ver «Acceso de demo»).
La clave de servicio no se configura en Vercel: la app usa siempre la sesión del usuario. Las variables NEXT_PUBLIC_ se leen al compilar, así que tras cambiarlas hace falta un despliegue nuevo.
El panel de Auth del remoto tiene que decir lo mismo que supabase/config.toml. supabase db push no lo sincroniza: se ajusta a mano en el panel.
| Ajuste | Valor | En config.toml |
|---|---|---|
| Registro abierto | Desactivado | [auth] enable_signup = false |
| Código de acceso | 6 dígitos | [auth.email] otp_length = 6 |
| Caducidad del código | 600 s | [auth.email] otp_expiry = 600 |
| Plantilla «Magic link» | El contenido de supabase/templates/magic_link.html | [auth.email.template.magic_link] content_path |
| Asunto de esa plantilla | «Tu código para entrar» | [auth.email.template.magic_link] subject |
| URL del sitio | La de producción: https://club-os-phi.vercel.app | [auth] site_url (en local, http://127.0.0.1:3000) |
[auth.email] enable_signup = true) con el registro abierto desactivado ([auth] enable_signup = false). El código por email es el único acceso.supabase config push: subiría al remoto el site_url local y los límites de desarrollo de abajo. Estos ajustes se hacen a mano en el panel.Lo que no se copia del config.toml local, porque es de desarrollo:
max_frequency = "1s" en [auth.email]. Deja pedir un código cada segundo. En el remoto se queda el valor por defecto del proyecto.[auth.rate_limit]. Se deciden aparte para el remoto (ver abajo)..test, que no pueden recibir correo. Para entrar como uno de ellos, genera su código desde el panel de Supabase, o deja que lo haga el helper de los e2e.Mientras el remoto no tenga SMTP, el login puede enseñar dos botones, «Probar como entrenador» y «Probar como dirección», que entran como un usuario de ejemplo sin pedir código.
Es una excepción a «acceso solo por invitación»: cualquiera que abra la URL entra como ese usuario y puede hacer todo lo que él pueda. Vale solo para un entorno con datos de demo, y hay que apagarlo antes de que entre un solo dato real.
Está apagado salvo que el servidor de la app tenga estas tres variables. Son de servidor, sin NEXT_PUBLIC_: la contraseña no llega al navegador.
| Variable | Valor |
|---|---|
DEMO_LOGIN_COACH_EMAIL | El usuario de demo entrenador, por ejemplo [email protected] |
DEMO_LOGIN_ADMIN_EMAIL | El usuario de demo de dirección, por ejemplo [email protected] |
DEMO_LOGIN_PASSWORD | La contraseña de los dos. Al menos 16 caracteres, generada y guardada en un gestor |
.test, los de los usuarios del seed. Con cualquier otro, ese botón no sale y la acción se niega: el botón no puede abrir la cuenta de una persona.Encenderlo:
Siembra el remoto si no lo está (ver «Sembrar el demo»).
Pon la contraseña a los usuarios con pnpm demo:password. Nunca crea cuentas: si un usuario no existe, falla sin cambiar nada. La contraseña se pide sin eco, como la clave de servicio.
Git Bash:
export NEXT_PUBLIC_SUPABASE_URL="https://…"
export DEMO_LOGIN_COACH_EMAIL="[email protected]" DEMO_LOGIN_ADMIN_EMAIL="[email protected]"
read -rs -p "Clave de servicio: " SUPABASE_SERVICE_ROLE_KEY && export SUPABASE_SERVICE_ROLE_KEY; echo
read -rs -p "Contraseña de demo: " DEMO_LOGIN_PASSWORD && export DEMO_LOGIN_PASSWORD; echo
pnpm demo:password
unset NEXT_PUBLIC_SUPABASE_URL SUPABASE_SERVICE_ROLE_KEY DEMO_LOGIN_COACH_EMAIL DEMO_LOGIN_ADMIN_EMAIL DEMO_LOGIN_PASSWORD
PowerShell:
$env:NEXT_PUBLIC_SUPABASE_URL = "https://…"
$env:DEMO_LOGIN_COACH_EMAIL = "[email protected]"
$env:DEMO_LOGIN_ADMIN_EMAIL = "[email protected]"
$clave = Read-Host "Clave de servicio" -AsSecureString
$env:SUPABASE_SERVICE_ROLE_KEY = [System.Net.NetworkCredential]::new("", $clave).Password
$demo = Read-Host "Contraseña de demo" -AsSecureString
$env:DEMO_LOGIN_PASSWORD = [System.Net.NetworkCredential]::new("", $demo).Password
pnpm demo:password
Remove-Item Env:NEXT_PUBLIC_SUPABASE_URL, Env:SUPABASE_SERVICE_ROLE_KEY, Env:DEMO_LOGIN_COACH_EMAIL, Env:DEMO_LOGIN_ADMIN_EMAIL, Env:DEMO_LOGIN_PASSWORD, Variable:clave, Variable:demo
Crea las tres variables en Vercel (Production) con los mismos valores y despliega de nuevo. /login se genera al compilar: sin un despliegue nuevo los botones no aparecen.
Apagarlo: borra las tres variables en Vercel y despliega de nuevo. Para que la contraseña deje de valer, repite el paso 2 con otra que no guardes.
En local funciona igual: las tres variables en .env.local y pnpm demo:password.
Los secretos (SUPABASE_SERVICE_ROLE_KEY y, si lo usas, VERCEL_AUTOMATION_BYPASS_SECRET) solo se dan en la sesión de la shell. Nunca van en un fichero, .env.local incluido. Y nunca los escribas en la línea de comandos: SUPABASE_SERVICE_ROLE_KEY=… pnpm seed deja la clave en el historial de la shell.
Pídela sin eco. Así no pasa por el historial.
Git Bash:
read -rs -p "Clave de servicio: " SUPABASE_SERVICE_ROLE_KEY && export SUPABASE_SERVICE_ROLE_KEY; echo
PowerShell (vale también en Windows PowerShell 5.1: Read-Host -MaskInput solo existe en PowerShell 7):
$clave = Read-Host "Clave de servicio" -AsSecureString
$env:SUPABASE_SERVICE_ROLE_KEY = [System.Net.NetworkCredential]::new("", $clave).Password
VERCEL_AUTOMATION_BYPASS_SECRET se pide igual, cambiando el nombre. Las variables que no son secretas (las URLs, BASE_URL) sí se pueden escribir en la línea.
Al terminar, cierra la shell o borra las variables. Cada bloque de abajo acaba con ese borrado.
El seed escribe con la clave de servicio y se niega a correr contra un Supabase remoto salvo que se lo pidas con ALLOW_REMOTE_SEED=true. Hazlo solo en un entorno de demo y nunca junto a datos reales. Esa variable tampoco va en ningún fichero, y solo debe estar puesta durante el seed.
Pon la URL del proyecto remoto, pide la clave como se explica arriba y siembra.
Git Bash:
export NEXT_PUBLIC_SUPABASE_URL="https://…"
read -rs -p "Clave de servicio: " SUPABASE_SERVICE_ROLE_KEY && export SUPABASE_SERVICE_ROLE_KEY; echo
ALLOW_REMOTE_SEED=true pnpm seed
unset NEXT_PUBLIC_SUPABASE_URL SUPABASE_SERVICE_ROLE_KEY
PowerShell:
$env:NEXT_PUBLIC_SUPABASE_URL = "https://…"
$clave = Read-Host "Clave de servicio" -AsSecureString
$env:SUPABASE_SERVICE_ROLE_KEY = [System.Net.NetworkCredential]::new("", $clave).Password
$env:ALLOW_REMOTE_SEED = "true"
pnpm seed
Remove-Item Env:NEXT_PUBLIC_SUPABASE_URL, Env:SUPABASE_SERVICE_ROLE_KEY, Env:ALLOW_REMOTE_SEED, Variable:clave
Las fechas del seed son relativas al día en que se siembra: vuelve a sembrar antes de una demo. Un nuevo seed también devuelve los ejercicios y las sesiones del seed a su versión original (ver «Despliegue de la biblioteca de ejercicios» y «Despliegue del Practice Builder»).
Con la URL desplegada y el Supabase remoto de esa app, y el entorno ya sembrado:
Git Bash:
export BASE_URL="https://…"
export NEXT_PUBLIC_SUPABASE_URL="https://…"
read -rs -p "Clave de servicio: " SUPABASE_SERVICE_ROLE_KEY && export SUPABASE_SERVICE_ROLE_KEY; echo
pnpm test:e2e e2e/tenancy.spec.ts
unset BASE_URL NEXT_PUBLIC_SUPABASE_URL SUPABASE_SERVICE_ROLE_KEY
PowerShell:
$env:BASE_URL = "https://…"
$env:NEXT_PUBLIC_SUPABASE_URL = "https://…"
$clave = Read-Host "Clave de servicio" -AsSecureString
$env:SUPABASE_SERVICE_ROLE_KEY = [System.Net.NetworkCredential]::new("", $clave).Password
pnpm test:e2e e2e/tenancy.spec.ts
Remove-Item Env:BASE_URL, Env:NEXT_PUBLIC_SUPABASE_URL, Env:SUPABASE_SERVICE_ROLE_KEY, Variable:clave
BASE_URL es la URL desplegada. Playwright no construye ni arranca nada.BASE_URL remoto y un Supabase local o ausente, los e2e fallan antes de empezar, con un solo mensaje.VERCEL_AUTOMATION_BYPASS_SECRET (el secreto de «Protection Bypass for Automation» del proyecto en Vercel). Los tests lo envían en la cabecera x-vercel-protection-bypass solo en las peticiones al origen de BASE_URL. Nunca a otros dominios, y nunca a localhost, aunque la variable esté puesta. Si el secreto es incorrecto y la protección redirige a otro dominio, esa redirección lo lleva también: si un test acaba en vercel.com, cambia el secreto.e2e/home.spec.ts) y los de la lista de sesiones (e2e/train.spec.ts) comparan con el calendario del seed y solo aciertan si el entorno se sembró hace poco. E2E_SEED_NOW=<fecha ISO> dice cuándo se sembró.Todas las peticiones a Auth salen de la IP del servidor de Vercel, así que los límites son compartidos por todos los usuarios del despliegue. Cada login de un e2e gasta uno de esos cupos. Qué hacer con ellos está en docs/superpowers/backlog.md («Límites de Auth»).
No se editan a mano. CI falla si no están al día.
src/ui/tokens.css sale de design/tokens.json con pnpm tokens. Lleva un bloque @theme de Tailwind: no es CSS plano y solo funciona importado desde src/app/globals.css. El generador es estricto: si a design/tokens.json le falta una familia, un token no tiene nombre o valor, un nombre se repite o un alias apunta a un token que no existe, pnpm tokens falla y dice dónde, en vez de escribir un CSS roto.src/lib/database.types.ts sale del esquema local con pnpm db:types. Regenéralo después de cada migración, con Supabase arrancado.