08 · Architecture
08 — Architecture
Sección titulada «08 — Architecture»Versión: 0.1 Última actualización: 2026-04-23 Status: 🟡 Draft
Stack + estructura técnica de Academia Agentes. Referencia las decisiones tomadas antes (Astro + Svelte + Supabase por razones discutidas en conversación de diseño).
Stack decisions + rationale
Sección titulada «Stack decisions + rationale»Frontend — Astro + Svelte islands
Sección titulada «Frontend — Astro + Svelte islands»- Astro para content-heavy rendering (docs, lessons)
- Svelte para islands interactivos (player, quiz, dashboard)
- Tailwind CSS para styling
- shadcn-svelte o componentes custom para UI primitives
Rationale (ver conversación docs-site):
- App es 80% contenido + 20% interactividad → Astro es ideal
- Islands arquitectura = ship JS solo donde hace falta
- Performance brutal (sub-1s TTI) importante para mobile
- Costo DX: aprender Astro + Svelte (~4 días curva, OK tradeoff)
Backend — Supabase
Sección titulada «Backend — Supabase»- PostgreSQL managed
- Auth con Google SSO + email (para Luis inicial, multi-tenant ready)
- Storage para small assets (pero audio en R2)
- Realtime subscriptions si hace falta para progreso tiempo real (nice-to-have)
- Row-level security (RLS) desde día 1
Rationale:
- Ya conocido por el equipo (Sistemia, Plania lo usan)
- Auth sin código
- Costos predecibles
- SQL en vez de NoSQL weirdness
Hosting — Cloudflare Pages + R2
Sección titulada «Hosting — Cloudflare Pages + R2»- CF Pages para Astro site + app
- CF R2 para audio files (cheaper than S3, sin egress fees)
- CF Workers (si necesitamos edge logic) — opcional
- CF Access para privatización selectiva
Content generation — Claude CLI
Sección titulada «Content generation — Claude CLI»- Claude Code CLI con Max OAuth (ya setup)
claude -pen print mode para automation--max-budget-usdcomo safety net- Generación de: lesson script, quiz, exercise, transcript
TTS — Gemini
Sección titulada «TTS — Gemini»- gemini-2.5-pro-preview-tts multi-speaker (primary)
- gemini-2.5-flash-tts (fallback + cheap layers)
- Same tooling que Guerra de Tokens (reuso de scripts)
Evaluator agents — Claude Skills
Sección titulada «Evaluator agents — Claude Skills»- Evaluators como skills en
apps/web/src/skills/evaluators/ - Invocados por pipeline o por web app UI
- Cada exercise/capstone tiene su evaluator prompt
Repo structure — monorepo
Sección titulada «Repo structure — monorepo»academia-agentes/├── docs/ ← Markdown source (esta carpeta)├── docs-site/ ← Astro + Starlight renderer de docs│ └── [ya existe, deployed]│├── apps/│ ├── web/ ← App principal (Astro + Svelte)│ │ ├── src/│ │ │ ├── pages/│ │ │ ├── components/ ← .svelte components│ │ │ ├── layouts/│ │ │ ├── content/ ← Astro content collections (lessons)│ │ │ ├── skills/ ← evaluator agents, helpers│ │ │ └── styles/│ │ ├── astro.config.mjs│ │ └── package.json│ ││ └── api/ ← CF Worker for webhooks, scheduled, etc.│ └── src/│├── content/ ← Lesson source markdown (canonical)│ ├── courses/│ │ ├── b0-bridge/│ │ │ ├── course.yaml ← metadata│ │ │ ├── lesson-01/│ │ │ │ ├── source.md ← content source│ │ │ │ ├── script.txt ← TTS-ready│ │ │ │ ├── audio.mp3 ← generated│ │ │ │ ├── transcript.vtt│ │ │ │ ├── slides.json│ │ │ │ ├── quiz.json│ │ │ │ ├── exercise.md│ │ │ │ └── evaluator.md│ │ │ └── ...│ │ └── b1-claude-code-pro/│ │ └── ...│ └── shared/│ ├── voices/ ← voice configs│ ├── stings/ ← audio transitions│ └── templates/ ← cert HTML templates│├── pipeline/ ← Scripts de generación│ ├── generate_lesson.sh│ ├── build_audio.py│ ├── build_slides.py│ ├── publish_lesson.py│ └── refresh_cycle.py ← monthly check vs sources│├── supabase/ ← DB migrations + seeds│ ├── migrations/│ ├── seed.sql│ └── config.toml│└── scripts/ ├── setup-local.sh └── deploy.shData model (Supabase schema)
Sección titulada «Data model (Supabase schema)»Users & profiles
Sección titulada «Users & profiles»-- Extends Supabase auth.usersusers_profile ( id UUID PRIMARY KEY REFERENCES auth.users(id), display_name TEXT, username TEXT UNIQUE, email TEXT, preferred_name TEXT, avatar_url TEXT, bio TEXT, public_profile BOOL DEFAULT false, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now())
user_placement ( user_id UUID PRIMARY KEY REFERENCES users_profile, lane_primary TEXT, -- 'builder', 'operator', 'cross' domain TEXT, -- 'legal', 'finance', etc. (for operator) experience_level TEXT, claude_code_level TEXT, time_per_day_min INT, preferred_modality TEXT, primary_goal TEXT, has_project BOOL, wants_certificates BOOL, placement_completed_at TIMESTAMPTZ, placement_version TEXT)Courses & lessons
Sección titulada «Courses & lessons»courses ( id TEXT PRIMARY KEY, -- 'b1', 'o1-g', 'o1-l', etc. title TEXT NOT NULL, lane TEXT, -- 'bridge', 'builder', 'operator', 'cross' variant TEXT, -- 'generic', 'legal', 'finance', etc. level TEXT, -- 'entry', 'intermediate', 'advanced' duration_weeks INT, lessons_count INT, description TEXT, status TEXT, -- 'planned', 'in_production', 'published', 'archived' published_at TIMESTAMPTZ, last_refreshed_at TIMESTAMPTZ, content_version TEXT)
lessons ( id TEXT PRIMARY KEY, -- 'b1-l1', 'b1-l2', etc. course_id TEXT REFERENCES courses, order_index INT, title TEXT, objective TEXT, duration_minutes INT, audio_url TEXT, -- R2 URL transcript_url TEXT, slides_url TEXT, -- JSON file source_md_path TEXT, -- repo path published_at TIMESTAMPTZ, last_updated_at TIMESTAMPTZ)
paths ( id TEXT PRIMARY KEY, -- 'b-i', 'o-i', 'c-i', etc. title TEXT, lane TEXT, level TEXT, required_courses TEXT[], -- ['b0', 'b1', 'b3', 'b4'] description TEXT, status TEXT)Progress tracking
Sección titulada «Progress tracking»user_progress ( user_id UUID REFERENCES users_profile, lesson_id TEXT REFERENCES lessons, audio_consumed BOOL DEFAULT false, audio_consumed_at TIMESTAMPTZ, quiz_passed BOOL DEFAULT false, exercise_submitted BOOL DEFAULT false, exercise_score FLOAT, lesson_complete BOOL DEFAULT false, notes TEXT, -- learner's personal notes updated_at TIMESTAMPTZ DEFAULT now(), PRIMARY KEY (user_id, lesson_id))
user_enrollment ( user_id UUID REFERENCES users_profile, course_id TEXT REFERENCES courses, enrolled_at TIMESTAMPTZ DEFAULT now(), completed_at TIMESTAMPTZ, paused BOOL DEFAULT false, PRIMARY KEY (user_id, course_id))Assessment
Sección titulada «Assessment»quiz_attempts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users_profile, lesson_id TEXT REFERENCES lessons, question_id TEXT, answer TEXT, correct BOOL, attempt_number INT, created_at TIMESTAMPTZ DEFAULT now())
exercise_submissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users_profile, lesson_id TEXT REFERENCES lessons, submission_type TEXT, -- 'file', 'commit', 'text', etc. submission_data JSONB, -- flexible score FLOAT, feedback TEXT, evaluator_version TEXT, manual_reviewed BOOL DEFAULT false, submitted_at TIMESTAMPTZ DEFAULT now(), graded_at TIMESTAMPTZ)
capstone_submissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users_profile, course_id TEXT REFERENCES courses, repo_url TEXT, writeup TEXT, self_assessment JSONB, agent_scores JSONB, -- 4-axis agent_feedback TEXT, final_score FLOAT, passed BOOL, manual_reviewed BOOL, reviewer_notes TEXT, submitted_at TIMESTAMPTZ DEFAULT now(), graded_at TIMESTAMPTZ)Certificates
Sección titulada «Certificates»-- Ya detallado en 14-credentials-system.md-- Resumen:
certificates ( id UUID PRIMARY KEY, user_id UUID REFERENCES users_profile, type TEXT, -- 'micro' | 'path' | 'mastery' course_id TEXT, -- for micro path_id TEXT, -- for path lane_id TEXT, -- for mastery issued_at TIMESTAMPTZ, scores JSONB, artifacts JSONB, verification_slug TEXT UNIQUE, pdf_url TEXT, badge_url TEXT, status TEXT DEFAULT 'active')Pipeline de generación de lesson
Sección titulada «Pipeline de generación de lesson»Flow completo (cron/automated)
Sección titulada «Flow completo (cron/automated)»┌─────────────────────────────────────────────────────────────┐│ 1. fetch_sources.py ││ Reads: curriculum.yaml (qué lesson toca hoy) ││ Pulls: sources listed for that lesson ││ Output: content/{course}/{lesson}/sources/ │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 2. generate_lesson.sh ││ Runs: claude -p with Max OAuth + --max-budget-usd 5 ││ Input: sources + principles.md + audio-formats.md ││ Output: source.md, script.txt, quiz.json, exercise.md, ││ evaluator.md, slides.json │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 3. build_audio.py ││ Input: script.txt ││ TTS: Gemini multi-speaker (Maestro + Chombi voices) ││ Output: audio.mp3 (16-bit mp3, 128kbps) ││ Post: normalize to -16 LUFS, add intro/outro stings │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 4. build_slides.py ││ Input: slides.json (timestamps + display types) ││ Generates: static SVG diagrams (mermaid), syntax-hl code ││ Output: slides/*.svg │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 5. publish_lesson.py ││ Uploads: audio → R2, slides → R2 ││ Writes: lessons row in Supabase (with URLs) ││ Triggers: re-build de Astro app ││ Notifies: learners subscribed (email + telegram) │└─────────────────────────────────────────────────────────────┘Execution schedule
Sección titulada «Execution schedule»- Daily 5 AM PDT: generate lesson del día (si curriculum tiene una)
- Weekly Sunday 6 AM: generate Weekly Synthesis
- Monthly 1st 4 AM: refresh_cycle (check lessons vs current docs)
- On-demand: Changelog Express (triggered by tracked RSS/releases)
Safety nets
Sección titulada «Safety nets»--max-budget-usd 5en cadaclaude -pcall- OAuth token vs API key (usa Max, no cobra)
- Retry logic con exponential backoff
- Failure notifications a Telegram del admin
- If fail 3x consecutive: alert + pause cron
App web (apps/web) — key pages
Sección titulada «App web (apps/web) — key pages»/ Dashboard (default post-login)/onboarding Placement flow/catalog Course catalog/courses/:course_id Course overview/courses/:course_id/lessons/:lesson_id Lesson player/courses/:course_id/exercise/:lesson_id Exercise submission/courses/:course_id/capstone Capstone submission/certificates My certificates/verify/:cert_slug Public verification (no auth)/u/:username Public portfolio (if opted-in)/profile Settings + placement re-takeKey components (Svelte islands)
Sección titulada «Key components (Svelte islands)»<AudioPlayer>— audio + synced transcript + slides<QuizPanel>— 5-question multiple choice with feedback<ExerciseForm>— submission upload<ProgressDashboard>— current course, streak, XP<Certificate>— cert viewer + share actions
Rest de pages son estáticas (Astro) — solo islands donde hay state.
Multi-tenant considerations (desde día 1)
Sección titulada «Multi-tenant considerations (desde día 1)»Aunque Luis es el primer user, arquitectura multi-user desde commit 1:
- ✅
auth.users(Supabase) en vez de hardcoded user - ✅ Todas las tablas con
user_idFK - ✅ Row-level security (RLS) policies
- ✅ User-facing URLs usan
user_idimplícito (via session) - ✅ No shortcuts (“solo Luis” hardcoded anywhere)
RLS básica
Sección titulada «RLS básica»-- Ejemplo: user_progress solo visible para el user o adminCREATE POLICY "users see own progress" ON user_progress FOR ALL USING (auth.uid() = user_id);
-- certificates: públicamente visibles (para verification URL)CREATE POLICY "certificates public read" ON certificates FOR SELECT USING (true);Deployment strategy
Sección titulada «Deployment strategy»Environments
Sección titulada «Environments»| Env | Branch | URL | Purpose |
|---|---|---|---|
| Local | any | localhost:4321 | Dev |
| Preview | PR branches | auto-generated CF Pages preview | Review |
| Prod | main | academia-agentes.xyz (future) | Live |
Deploy flow
Sección titulada «Deploy flow»git push main→ auto-deploy via GitHub integration a CF Pages (once configured)- Supabase migrations:
supabase db pushmanual para prod hasta que confirme - R2 bucket: 1 bucket per env (
academia-agentes-prod,academia-agentes-staging)
Secrets management
Sección titulada «Secrets management».env.local(gitignored) para dev- CF Pages secrets UI para prod
- Supabase has Vault for sensitive keys
Observability
Sección titulada «Observability»Logging
Sección titulada «Logging»- CF Workers Analytics para API traffic
- Supabase built-in logs para DB
- Sentry (future) para error tracking
Analytics (privacy-friendly)
Sección titulada «Analytics (privacy-friendly)»- Plausible o Umami (no Google Analytics)
- Eventos: lesson_started, lesson_completed, quiz_attempted, etc.
Costs tracking
Sección titulada «Costs tracking»- Monthly report: tokens usados, TTS usage, R2 storage, CF bandwidth
- Alert si costo mensual > umbral
Evolución técnica (roadmap)
Sección titulada «Evolución técnica (roadmap)»v0.1 (MVP — mes 1-2)
Sección titulada «v0.1 (MVP — mes 1-2)»- Docs site ✅ (live)
- Content generation pipeline working
- App web con 1 course funcional end-to-end (B1)
- Auth con 1 user (Luis)
- Capstone submission + evaluator
v0.2 (mes 3-4)
Sección titulada «v0.2 (mes 3-4)»- Placement system activo
- 3-4 courses producidos
- Certificates auto-emitted
- Progress dashboard
v0.3 (mes 5-6)
Sección titulada «v0.3 (mes 5-6)»- Multi-user invite system
- Public portfolios
- Paths activos
- Refresh cycle mensual corriendo
v1.0 (mes 7+)
Sección titulada «v1.0 (mes 7+)»- Cohort mode (si hay demand)
- Community features (discussion, peer review)
- Cross-device sync perfecto
- Mobile apps (Capacitor wrapper probably)
Open questions
Sección titulada «Open questions»- ¿Convex en vez de Supabase para real-time? — por ahora Supabase
- ¿Svelte 5 con runes stable? — sí, Svelte 5 released 2024
- ¿Content collections de Astro o simple file system? — content collections
- ¿Embed Claude Code diff/session viewer in evaluator feedback?
- ¿Backup strategy para DB? — Supabase auto-backup + weekly export a R2