Panel de administración
Panel de administración interno de Lade. React 19 + Vite + TypeScript +
React Router 7 + Zustand + Tailwind v4, siguiendo los patrones de
lade-app (rutas centralizadas, sidebar declarativo, un store por
dominio, tokens de diseño oklch + Inter/JetBrains Mono).
Desplegado desde el golden path
vite-cloudfront-template
(S3 + CloudFront + dominio propio) en admin.lade.com.mx — prod-único
a propósito (herramienta interna, sin dev/qa).
Desarrollo
Sección titulada «Desarrollo»cd appnpm installcp .env.local.example .env.local # completar con `terraform -chdir=../infra output` (ver Login)npm run dev # http://localhost:5173npm run build # tsc -b && vite buildnpm run lintmake bootstrap-remote # una sola vez: bucket S3 de statemake tf-apply # crea CloudFront + dominio + Cognitomake deploy # build + sync + invalidationEn CI: push de un tag vX.Y.Z dispara deploy.yml — un solo job (Terraform
apply → build de la SPA con los outputs de Cognito como VITE_* → sync a
S3 + invalidation), gated por el GitHub Environment prod.
Login — Cognito dedicado, solo equipo
Sección titulada «Login — Cognito dedicado, solo equipo»El panel tiene su propio user pool de Cognito (infra/cognito.tf),
separado del de lade-app: sin auto-registro (admin_create_user_config),
MFA (TOTP) obligatorio (mfa_configuration = "ON"). No hay UI de alta —
los usuarios se crean a mano:
aws cognito-idp admin-create-user \ --user-pool-id $(terraform -chdir=infra output -raw cognito_user_pool_id) \ --username persona@lade.com.mx \ --user-attributes Name=email,Value=persona@lade.com.mx Name=email_verified,Value=true \ --temporary-password '<temporal>'El primer login pide cambiar la contraseña temporal y después configurar
TOTP (se muestra el secret para cargar en Google Authenticator/Authy —
sin QR todavía, ver LoginPage.tsx). Login normal en adelante: email +
contraseña + código de 6 dígitos.
Implementado con aws-amplify (Auth) — src/lib/amplifyConfig.ts +
src/core/store/useAuthStore.ts + src/pages/Login/LoginPage.tsx +
src/router/ProtectedRoute.tsx. Los IDs del pool/client se inyectan en
build time (VITE_COGNITO_USER_POOL_ID/VITE_COGNITO_CLIENT_ID) — ver
.env.local.example para desarrollo local.
Golden Paths
Sección titulada «Golden Paths»Primera sección real del panel (/golden-paths): dispara el workflow
scaffold.yml para crear
proyectos nuevos desde los templates de
lade-templates (astro-cloudflare,
vite-cloudfront, terraform-lambda).
Token de GitHub para disparar scaffolds — MVP deliberado, separado del login
Sección titulada «Token de GitHub para disparar scaffolds — MVP deliberado, separado del login»El login de Cognito protege el panel; para efectivamente disparar un
scaffold, el usuario ADEMÁS pega en Configuración un fine-grained
PAT de GitHub scoped ÚNICAMENTE al repo lade-group/lade-factory
(permisos Actions: Read and write + Metadata: Read-only). El token
vive solo en sessionStorage del navegador — nunca en localStorage,
.env, ni en el bundle — y se pierde al cerrar la pestaña.
Esto es intencional para el MVP: el blast radius de una fuga está acotado a un solo repo interno. El reemplazo correcto (proxy backend con GitHub App, sin tokens en el browser) es la historia F7.5 en Notion (Lade HQ) — no lo implementes acá sin revisar esa decisión primero.
Estructura
Sección titulada «Estructura»infra/ Terraform: S3 + CloudFront + dominio propio + Cognito (vite-cloudfront-template)bootstrap/ Bucket S3 de state (una sola vez, make bootstrap-remote)envs/ prod.tfvars (custom_domain, cloudflare_zone_id) — prod-únicoapp/ La SPA (Vite) — todo el código de React vive acá src/ lib/amplifyConfig.ts Amplify.configure (Cognito) router/ProtectedRoute.tsx Guard: sin sesión → /login pages/Login/LoginPage.tsx Credenciales → NEW_PASSWORD → TOTP setup → TOTP challenge constants/routes.ts ROUTES (rutas centralizadas) constants/SidebarRoutes.tsx Sidebar declarativo, con secciones layouts/ AppLayout + Sidebar (secciones + logout) core/api/githubClient.ts Cliente fetch contra la API de GitHub (scoped a lade-factory) core/store/ Zustand: useAuthStore (Cognito), useGithubTokenStore (sessionStorage), useScaffoldStore pages/Dashboard/ Home/ GoldenPaths/ ScaffoldForm + RunsList (polling por run_key) Settings/Para agregar una página nueva: clave en ROUTES, entrada en el items de
la sección correspondiente de SIDEBAR_SECTIONS (o una sección nueva si
no encaja en ninguna existente), ruta en Router.tsx, página en
app/src/pages/Dashboard/<Seccion>/.