This version (16.2.6) has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
Plataforma web para que estudiantes del CETI Guadalajara aprendan a programar con lecciones interactivas y un editor de código en el navegador. Inspirado en Mimo, pero siguiendo el temario del CETI y en español.
Dos cursos hoy: C++ desde cero (cpp-desde-cero) y Programación Orientada
a Objetos I con C# (csharp-poo-1).
90% práctica, 10% teoría. Cada concepto se sigue inmediatamente de ejercicios donde el usuario escribe código real. No somos un libro de teoría con un editor anexo: somos un sistema de práctica con teoría justo-a-tiempo.
- Next.js 16.2.6 (App Router, Turbopack)
- React 19 · TypeScript 5
- Tailwind 4 (con
@theme inlineen CSS, NO usatailwind.config.ts) - shadcn/ui (componentes copiados en
src/components/ui/, NO instalados como paquete) - Prisma 6 + PostgreSQL (Supabase)
- Better Auth 1.6 (NO NextAuth; tablas custom en el schema)
- Monaco Editor para el editor C++
- Judge0 / Wandbox / Piston para compilar y ejecutar (adapter pattern en
src/lib/executor/), con un perfil de ejecución POR CURSO
-
prisma/content/*.tses la fuente de verdad del contenido. No edites las tablas directamente en Supabase: añade contenido en TypeScript y correnpm run db:seed. El seed hace upsert y no destruye el progreso de usuarios. -
El executor de código es un adapter. Hoy: Judge0 vía RapidAPI o self-hosted (DigitalOcean). Cambiar
CODE_EXECUTOR_PROVIDERen.envbasta; no toques las server actions ni las route handlers. -
Las migraciones del schema usan
dotenv-cliporque Prisma CLI no lee.env.localpor defecto. Usa siemprenpm run db:*(NOnpx prismadirecto). -
middleware.tsda un warning en Next 16 ("usa proxy en su lugar"). El warning es informativo: middleware sigue funcionando. Migrar aproxy.tses una tarea futura cuando Better Auth confirme soporte oficial. -
El cookiePrefix
cpp-cetiestá configurado enauth.tsy enmiddleware.ts. Si lo cambias, hazlo en AMBOS lugares. -
El sistema visual vive en
globals.cssy encomponents/ui/bricks.tsx. Los tokens (color, radio, sombra, tipografía) son variables CSS; los estilos de elemento van dentro de@layer basey los helpers de clase dentro de@layer components, para que cualquier utilidad de Tailwind pueda sobrescribirlos. Si escribes una regla fuera de esas capas, ganará siempre y romperás overrides puntuales. -
Los bloques (
BrickRow/BrickColumn) son el elemento firma. Una pieza = una lección (o un paso, o un ejercicio). Se usan en la ruta del curso, en el rail, en la cabecera de unidad y en el reproductor de lecciones. Si añades una secuencia con progreso, reutilízalos en vez de inventar otra barra. -
Server Actions críticas viven en
src/lib/lessons-actions.ts:completeStep— única vía para marcar un paso completado y mover XP.submitExercise— corre tests y guarda intentos. No dupliques esa lógica en API routes.
-
Nunca atrapes un P2002 dentro de
db.$transaction(). En PostgreSQL, una violación de UNIQUE aborta la transacción completa: aunque elcatchde JavaScript se trague el error, la siguiente consulta de esa misma transacción falla con25P02 current transaction is aborted. Para insertar-si-no-existe usacreateMany({ data: [...], skipDuplicates: true })(→INSERT ... ON CONFLICT DO NOTHING) y decide con elcount. Los helpers de "primer aprobado" viven ensrc/lib/completions.ts, ytests/architecture/no-catch-inside-transaction.test.tsfalla si el antipatrón vuelve. -
La telemetría de producto tiene contrato escrito. La taxonomía de eventos vive en
src/lib/analytics/events.ts(enum cerrado + Zod), la escritura idempotente ensrc/lib/analytics/record.ts, y la semántica exacta de CADA métrica endocs/product-analytics.md. Antes de agregar un evento o de interpretar un número del panel, lee ese documento. Dos trampas ya documentadas ahí:durationMsde los intentos es latencia del ejecutor (no tiempo de resolución), yUserStepProgress.completionCountno son intentos del estudiante. -
StudySessionmide tiempo activo aproximado, no tiempo de pared.engagedMssólo acumula con heartbeats (pestaña visible + actividad reciente), acotados en SQL. Las sesiones huérfanas se cierran en su último latido. No sumesendedAt - startedAtcomo si fuera estudio. -
El panel interno (
/app/admin) se autoriza en el servidor.requireAdmin()/requireAdminPage()en CADA página y CADA Server Action. Un layout no protege un POST directo a una action. -
El CURSO es la fuente de verdad del lenguaje y del compilador.
Course.language+Course.executionProfile, validados contra el registro desrc/lib/code-languages. De ahí salen el modo de Monaco, el nombre del archivo, las sugerencias, los diagnósticos, el compilador y el agrupamiento de métricas. NUNCA se infiere de un slug, de un fence de markdown ni de nada que mande el cliente, y un valor desconocido es un error de configuración — jamás un motivo para caer a C++. -
Toda ejecución nombra UN recurso; el servidor deriva el resto.
resolveExecutionTarget(src/lib/execution-target.ts) navega recurso → unidad → curso./api/runRECHAZA con 400 unlanguage,profileIdocompileren el cuerpo en vez de ignorarlo. Falla cerrado ante recurso inexistente, despublicado, ambiguo, con ids que no corresponden, o no ejecutable. Si agregas una superficie que ejecute código, pasa por ahí. -
Las rutas canónicas llevan el curso:
/app/c/[courseSlug]/...Las URLs viejas sin curso (/app/u/...,/app/ejercicios/...) se conservan PARA SIEMPRE y el middleware las redirige con 308 al curso de C++. No renombres slugs de C++ ni borres esas rutas: hay marcadores y enlaces compartidos apuntando ahí. -
Windows Forms NO se ejecuta en el navegador. Sus ejemplos son
runnable: false, llevanlocalOnlyNotey el servidor rechaza ejecutarlos. El dominio que alimentan sí se prueba como consola. Nunca "simules" una GUI para poder calificarla. -
Antes de publicar contenido con código, córrelo.
LANG=C.UTF-8 npx tsx scripts/verify-content.ts [curso]compila y ejecuta todo el contenido ejecutable contra sus casos, visibles y ocultos. El locale UTF-8 no es opcional: sin él, cualquier salida con acentos falla por el entorno y no por el contenido.
- Componentes shadcn van en
src/components/ui/(no enui/shadcn/u otro). - Server functions de queries →
src/lib/*.ts. - Server Actions →
src/lib/*-actions.tscon"use server". - Páginas autenticadas viven bajo
src/app/app/. - El idioma del producto es español de México. Mantén textos en es-MX.
npm run db:migrate -- --name describe_el_cambioNO uses prisma migrate directo — no lee .env.local.
Los componentes ya están en src/components/ui/. Si necesitas uno nuevo,
copia el código de la doc oficial de shadcn (no hagas npx shadcn add porque
nuestra config de Tailwind 4 puede sobreescribirse). Asegúrate de:
- Cambiar el cn import a
@/lib/utils. - Usar nuestras variables CSS (
bg-card,text-foreground, etc.).
- README.md — overview del proyecto
- DEPLOYMENT.md — guía paso a paso de despliegue (Supabase + DigitalOcean + Vercel)
- prisma/schema.prisma — modelo de datos completo
- prisma/content/types.ts — forma del contenido del curso