En Loreto el 88 % de las familias rurales ya tiene un celular, y el Estado ya instaló internet satelital en cientos de escuelas — pero solo 12 de cada 100 niños entienden lo que leen. El problema no es el acceso a la tecnología: es que nadie diseñó la educación para funcionar entre conexión y conexión.
Yachai es un copiloto conversacional que ayuda a un docente rural a planificar clases: genera sesiones de aprendizaje y evaluaciones alineadas al Currículo Nacional (CNEB), contextualizadas a la Amazonía, al aula multigrado y a la lengua originaria — y las entrega como PDF imprimible, para que el resultado viva fuera de línea.
Proyecto para el reto ODS 4 · Educación de Calidad — Región Loreto, Perú.
| Dato | Fuente |
|---|---|
| 6 de cada 100 estudiantes de 4.º de primaria alcanzan nivel satisfactorio en Matemática (12 en Lectura) | ENLA 2025 |
| 87 % de los 3 903 colegios públicos de Loreto son rurales | Minedu |
| Más del 70 % de las escuelas no tiene acceso básico a internet | ComexPerú |
| 88 % de los hogares rurales tiene al menos un celular | INEI |
| 1 316 localidades ya cuentan con internet satelital instalado | PRONATEL / Conecta Selva |
| 27 lenguas indígenas en la región, con pocos docentes bilingües titulados | Minedu |
Las escuelas unidocentes y multigrado son la norma: un maestro atiende todos los grados a la vez. Planificar una sesión alineada al currículo, diferenciada por grado y pertinente al contexto amazónico es un trabajo que hoy se hace a mano, sin apoyo y con conexión intermitente.
La IA se usa cuando hay conexión (UGEL, hotspot de Conecta Selva, WhatsApp desde el celular). El resultado —la sesión— vive fuera de línea: exportado a PDF imprimible. La herramienta está pensada para funcionar entre conexión y conexión.
- No inventa el currículo. Un Verificador (patrón Critic/Verifier) valida cada generación contra la base de datos: todo desempeño citado debe existir realmente en el Programa Curricular. Un código inventado invalida la sesión y fuerza a regenerar.
- Multigrado nativo. Una sola sesión produce actividades diferenciadas por grado; el Verificador rechaza la sesión si falta algún grado solicitado.
- Contextualización amazónica. Canoas y aguaje, no semáforos y manzanas.
- Lenguas originarias como dato, no como código. Kukama-kukamiria, shawi, awajún, kichwa amazónico, achuar, shipibo-konibo.
- Auditoría total. Cada generación queda registrada en PostgreSQL (
generation_audit): prompt, contexto usado, modelo, tokens. - Multicanal. Web (chat con voz) y WhatsApp — el canal que el docente rural ya tiene.
Docente ──► Chat web (texto o voz) ─┐
├─► Copiloto (tool loop) ─► Verificador ─► PDF (LaTeX)
Docente ──► WhatsApp (texto o nota) ─┘ │ │
▼ ▼
Currículo real (BD) Rechaza citas inventadas
- El docente conversa con el copiloto (escribiendo o con una nota de voz).
- El copiloto pregunta lo que falta: área, grado(s), tema, lengua y contexto local.
- Usa herramientas del servidor para consultar el currículo real en la base de datos (
buscar_curriculo,obtener_desempenos,buscar_recursos_contexto). - Propone la sesión mediante
proponer_sesion. El modelo no tiene una herramienta para guardar. - El Verificador aplica tres compuertas antes de guardar:
- Estructural — propósito, momentos inicio/desarrollo/cierre, evidencias.
- Multigrado — cada grado solicitado tiene actividades diferenciadas.
- Integridad de citas — cada
codigode desempeño existe en la tabladesempeno.
- Solo si pasa, se persiste como borrador. El docente edita y exporta a PDF.
El currículo no está en el prompt: está en la base de datos, en filas relacionales jerárquicas.
CurriculumArea → Competencia → Capacidad → Estandar (por ciclo) → Desempeno (por grado)
Contenido cargado en el MVP (primaria):
| Área | Competencias | Capacidades | Estándares | Desempeños |
|---|---|---|---|---|
| Comunicación | 3 | 13 | 9 | 115 |
| Matemática | 4 | 16 | 12 | 156 |
| Total | 7 | 29 | 21 | 271 |
Los 271 desempeños son citables: todos tienen su código oficial y fueron verificados contra el PDF fuente. Un desempeño sin código se marca needsReview = true y el Verificador no lo acepta como cita.
Fuentes oficiales
- Programa Curricular de Educación Primaria — Minedu, 2016 (desempeños por grado) → https://www.minedu.gob.pe/curriculo/pdf/programa-curricular-educacion-primaria.pdf
- Currículo Nacional de la Educación Básica (CNEB) — Minedu, 2016 (competencias, capacidades y estándares por ciclo)
La extracción se hizo con
pdfplumbersobre las tablas por grado y se validó página por página. Los datos viven enapps/api/src/database/seeds/cneb/*.jsony se cargan con un loader idempotente.
| Capa | Tecnología |
|---|---|
| Monorepo | pnpm 11 · Node ≥ 22 |
| Backend | NestJS 11 · TypeORM · PostgreSQL 16 |
| Frontend | React 19 · Vite 7 · Tailwind v4 · shadcn/ui · TanStack Query |
| Contratos | Zod compartido (@app/contracts, build dual CJS + ESM) |
| IA | OpenAI directo · AI SDK v6 (ai, @ai-sdk/openai) · gpt-4o · gpt-4o-transcribe |
| LaTeX vía Tectonic (embebido en la API) | |
| Evolution API | |
| Deploy | Docker · Coolify |
Regla de oro:
synchronize: falsesiempre. El esquema cambia solo con migraciones.
apps/
api/ NestJS
src/
agent/ Tool loop + Verificador + herramientas de currículo
copiloto/ Chat conversacional (streaming) + transcripción de voz
curriculum/ Área → Competencia → Capacidad → Estándar → Desempeño
sesiones/ Sesiones de aprendizaje (generar, leer, editar, PDF)
evaluaciones/ Exámenes alineados al currículo (+ PDF)
pdf/ Servicio Tectonic (LaTeX → PDF) y plantillas
whatsapp/ Webhook de Evolution + enrutado al copiloto
generation-audit/ Auditoría de cada generación
auth/ users/ escuelas/ materiales/ common/ config/ database/
web/ React (chat, sesiones, panel)
packages/
contracts/ Esquemas Zod compartidos
tsconfig/ Configuración TypeScript base
Requisitos: Node ≥ 22, pnpm (vía Corepack), Docker.
# 1. Dependencias
corepack enable
pnpm install
# 2. Entorno
cp .env.example .env # completa OPENAI_API_KEY y JWT_SECRET
# 3. Base de datos
pnpm db:up # PostgreSQL 16 (imagen pgvector) en Docker
pnpm migration:run # migraciones
pnpm seed # usuario administrador inicial
pnpm --filter @app/api seed:cneb # carga el currículo (idempotente)
# 4. Desarrollo
pnpm dev # API (3000) + Web (5173)Ingreso: el usuario administrador que definas en ADMIN_EMAIL / ADMIN_PASSWORD. El inicio de sesión valida formato de correo electrónico.
| Comando | Qué hace |
|---|---|
pnpm dev |
API + Web en paralelo |
pnpm build |
Compila todos los paquetes |
pnpm lint / pnpm typecheck |
Calidad estática |
pnpm test |
Tests unitarios (sin base de datos ni clave de IA) |
pnpm --filter @app/api test:db |
Tests de integración contra PostgreSQL |
pnpm db:up / pnpm db:down |
Levanta / baja PostgreSQL |
pnpm migration:run / pnpm migration:revert |
Migraciones |
pnpm --filter @app/api seed:cneb |
Carga el currículo desde los JSON |
Un único .env en la raíz (Vite lee las VITE_* desde ahí).
| Variable | Descripción |
|---|---|
DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME |
PostgreSQL |
API_PORT |
Puerto de la API (por defecto 3000) |
NODE_ENV |
development / production |
CORS_ORIGIN |
Orígenes exactos separados por coma (nunca *: se usan cookies) |
FRONTEND_URL |
URL del frontend |
| Variable | Descripción |
|---|---|
AUTH_LOCAL_ENABLED |
Habilita el ingreso con correo y contraseña |
JWT_SECRET JWT_EXPIRES_IN |
Sesión JWT en cookie httpOnly |
COOKIE_SECURE COOKIE_SAMESITE COOKIE_DOMAIN |
Cookie de sesión |
BCRYPT_ROUNDS |
Costo de hash |
ADMIN_EMAIL ADMIN_PASSWORD ADMIN_NAME |
Administrador inicial (semilla idempotente) |
GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET GOOGLE_CALLBACK_URL |
OAuth opcional (vacío = desactivado) |
| Variable | Descripción |
|---|---|
OPENAI_API_KEY |
Requerida para generar. Sin ella el chat responde 503 y el resto funciona igual |
OPENAI_MODEL |
Por defecto gpt-4o |
OPENAI_TRANSCRIBE_MODEL |
Por defecto gpt-4o-transcribe |
| Variable | Descripción |
|---|---|
EVOLUTION_URL |
URL de la instancia de Evolution API |
EVOLUTION_API_KEY |
Clave de Evolution |
EVOLUTION_INSTANCE |
Nombre de la instancia |
WA_WEBHOOK_TOKEN |
Token compartido que protege el webhook |
| Variable | Descripción |
|---|---|
VITE_API_URL |
URL absoluta de la API, sin /api al final. Se hornea en tiempo de build |
Todas las rutas usan el prefijo global /api (excepto /health). La autenticación es por cookie httpOnly; los clientes deben enviar credenciales. Las rutas de negocio requieren rol DOCENTE (el rol admin es superusuario y pasa todas las compuertas).
| Método | Ruta | Descripción |
|---|---|---|
POST |
/api/copiloto/chat |
Chat en streaming (UI Message Stream de AI SDK v6). Cuerpo: { conversacionId?, messages } |
POST |
/api/copiloto/transcribe |
Voz → texto. multipart/form-data, campo audio → { text } |
POST |
/api/copiloto/conversaciones |
Crea un hilo de conversación |
GET |
/api/copiloto/conversaciones |
Hilos del docente |
GET |
/api/copiloto/conversaciones/:id/mensajes |
Historial (para retomar) |
| Método | Ruta | Descripción |
|---|---|---|
GET |
/api/curriculo/areas |
Áreas curriculares |
GET |
/api/curriculo/competencias?area= |
Competencias y capacidades del área |
| Método | Ruta | Descripción |
|---|---|---|
POST |
/api/sesiones/generar |
Genera una sesión (pasa por el Verificador) |
GET |
/api/sesiones/:id |
Detalle con contenidoJson |
PATCH |
/api/sesiones/:id |
Edita contenido y/o estado (borrador ↔ final) |
GET |
/api/sesiones/:id/pdf |
PDF de la sesión (LaTeX → Tectonic) |
| Método | Ruta | Descripción |
|---|---|---|
POST |
/api/evaluaciones/generar |
Genera un examen alineado al currículo |
GET |
/api/evaluaciones/:id |
Detalle |
GET |
/api/evaluaciones/:id/pdf |
PDF del examen |
| Método | Ruta | Descripción |
|---|---|---|
POST |
/api/pdf/compile |
Compila LaTeX a PDF |
POST |
/api/whatsapp/webhook |
Webhook público de Evolution (protegido por token) |
GET |
/health |
Health check (sin prefijo) |
| — | /api/auth/* |
register, login, logout, me, config |
Un docente escribe o envía una nota de voz por WhatsApp y recibe la sesión, con los códigos oficiales de los desempeños citados. Es el mismo copiloto y el mismo Verificador: no hay una segunda ruta de IA.
Nota de voz ─► Evolution ─► webhook ─► transcripción ─► copiloto ─► Verificador ─► respuesta
Configuración
-
Define en la API:
EVOLUTION_URL,EVOLUTION_API_KEY,EVOLUTION_INSTANCEyWA_WEBHOOK_TOKEN. Reinicia el contenedor para que tome las variables. -
En Evolution, apunta el webhook a la API incluyendo el token en la URL:
https://TU-API/api/whatsapp/webhook?token=EL_MISMO_WA_WEBHOOK_TOKENTambién se acepta el encabezado
x-webhook-tokencon ese valor. -
Suscribe el evento
MESSAGES_UPSERT(mensajes entrantes).
Verificación
curl -i -X POST "https://TU-API/api/whatsapp/webhook?token=TOKEN" # 200 {"ok":true}
curl -i -X POST "https://TU-API/api/whatsapp/webhook" # 401Detalles
- Idempotencia: cada mensaje entrante se registra por su identificador de Evolution (
wa_inbound_log); los reintentos no se procesan dos veces. - Identidad: el número de teléfono se asocia a un usuario docente, creado en el primer mensaje. Simplificación de demostración: no hay verificación por OTP.
- Si las variables de Evolution no están definidas, el resto de Yachai arranca con normalidad y el canal queda inactivo.
Las sesiones y evaluaciones se renderizan a LaTeX con plantillas deterministas y se compilan con Tectonic, embebido en la imagen de la API.
- El
Dockerfiledescarga el binario de Tectonic y precalienta la caché de paquetes durante el build, para que la primera compilación en producción no dependa de la red. - Todo el texto proveniente del usuario o del modelo se escapa antes de entrar al documento.
- Si el binario no está disponible en tiempo de ejecución, los endpoints de PDF responden
503y la aplicación sigue funcionando.
Dos servicios a partir del mismo repositorio:
| Servicio | Dockerfile | Notas |
|---|---|---|
| API | apps/api/Dockerfile |
Ejecuta migraciones y semillas al arrancar. Incluye Tectonic |
| Web | apps/web/Dockerfile |
VITE_API_URL es un argumento de build, no de runtime |
Cookies entre subdominios. La sesión viaja por cookie httpOnly. Publica ambos servicios bajo el mismo dominio (por ejemplo app.midominio.com y api.midominio.com) y configura COOKIE_DOMAIN=.midominio.com, COOKIE_SAMESITE=lax, COOKIE_SECURE=true y CORS_ORIGIN con el origen exacto del frontend.
pgvector. El esquema incluye una columna de embeddings para búsqueda semántica. Si el PostgreSQL de destino no tiene la extensión vector, la migración lo detecta, omite la columna y continúa: el MVP funciona igual. Para habilitar la búsqueda semántica, usa una imagen pgvector/pgvector:pg16 y vuelve a ejecutar la migración.
users (roles: admin | docente | director | user)
escuela { nombre, ugel, esUnidocente, esMultigrado, lenguas[] }
curriculum_area → competencia → capacidad
→ estandar (por ciclo: III, IV, V)
→ desempeno (por grado 1-6, codigo, needsReview, embedding?)
sesion_aprendizaje { docenteId, escuelaId, grados[], areaId, competenciaIds[],
lengua, contexto, contenidoJson, estado, generationAuditId }
evaluacion { ..., contenidoJson (ítems con desempenoCodigo) }
material { sesionId, tipo, lengua, contenido }
generation_audit { prompt, contextoUsado, versionCurriculo, modelo, tokens }
conversacion / mensaje Historial del copiloto
wa_inbound_log Idempotencia de WhatsApp
Migraciones en apps/api/src/database/migrations/ (7 a la fecha).
pnpm test # unitarios: no requieren base de datos ni clave de IA
pnpm --filter @app/api test:db # integración: requiere PostgreSQLLa cobertura se concentra donde importa: integridad de citas del Verificador (un código inventado se rechaza), diferenciación multigrado, idempotencia del loader del currículo, y seguridad e idempotencia del webhook de WhatsApp.
Funcionando
- Copiloto conversacional con voz (web) y WhatsApp
- Currículo real de Comunicación y Matemática (primaria) con 271 desempeños citables
- Verificador con las tres compuertas
- Generación de sesiones y evaluaciones
- Exportación a PDF vía LaTeX
- Auditoría de generaciones
Simplificaciones de esta versión
- La identidad por WhatsApp no verifica el número (sin OTP).
- El contenido curricular cubre Comunicación y Matemática de primaria; el resto de áreas y niveles es trabajo posterior.
- La búsqueda semántica (pgvector) está preparada en el esquema pero no es parte del camino de la demostración.
- Las lenguas originarias se tratan como dato de contextualización; no hay traducción automática certificada.
El modelo es B2G / B2I — UGEL, DRE, gobiernos regionales, PRONATEL, FITEL, ONG y FORMABIAP — nunca cobro directo a las familias. Se apoya en infraestructura que el Estado ya pagó: los hotspots satelitales de Conecta Selva y el celular que el 88 % de los hogares rurales ya tiene.
Metas ODS 4 atendidas: 4.1 (educación primaria de calidad), 4.5 (equidad y grupos vulnerables) y 4.c (docentes calificados).
Construido sobre TemplateFullStack. La arquitectura de agente con herramientas auditadas y el patrón Critic/Verifier se adaptaron de MayordomoAI.
Datos curriculares: Ministerio de Educación del Perú (Minedu) — Currículo Nacional de la Educación Básica y Programa Curricular de Educación Primaria, 2016.