Files
GenExam-IA/README.md
T
Mireya Cueto Garrido 699a0364b6 Update README with image and Tech Stack section
Added an image and updated the Tech Stack section.
2026-06-04 13:35:16 +02:00

16 KiB
Raw Blame History

GenExámenes IA

FastAPI Python React Vite PostgreSQL Docker Moodle LLM

Plataforma full-stack para diseñar exámenes con IA, gestionar materiales de contexto y exportar bancos de preguntas a Moodle XML, TXT y JSON.

certificate Overview: What is this project meant

GenExámenes IA está pensado para flujos docentes en los que hay que crear evaluaciones estructuradas, apoyarse en un LLM y exportar el resultado a Moodle.

Capacidades principales:

  • Registro e inicio de sesión (email/contraseña y Google opcional)
  • Creación de plantillas de examen (tipos de pregunta, dificultad, barajar, feedback)
  • Subida de Material IA (PDF, DOCX, TXT, MD…) con extracción de texto para contexto
  • Subida de imágenes para preguntas visuales (embebidas en Moodle XML)
  • Generación de preguntas: automática (LLM en servidor), solo prompt o importar JSON/TXT
  • Revisión, vinculación imagenpregunta y exportación Moodle XML, TXT o JSON
  • Cupo de almacenamiento por examen y controles de seguridad en producción

Note

El despliegue en Sinbad2 (UJA) sigue el patrón orcid2sword: HTTPS en Apache, contenedores en HTTP en puertos internos (8069 frontend, 8068 backend). Ver deploy/DESPLIEGUE_SINBAD2.md.

URL pública (producción): https://sinbad2.ujaen.es/generadorexamenesllm/

Guía ampliada de API, flujo y errores: GUIA_API_Y_FLUJO.md

image

certificate Tech Stack

Backend

  • FastAPI
  • SQLAlchemy
  • PostgreSQL
  • python-jose (JWT)
  • bcrypt (contraseñas)
  • httpx (cliente LLM Sinbad2IA)
  • pypdf / python-docx / Pillow / pytesseract (extracción de materiales)

Frontend

  • React 18
  • Vite 5
  • React Router 6
  • Axios
  • CSS propio (UI responsive, menú móvil)

Infrastructure

  • Docker / Docker Compose
  • Nginx (contenedor frontend: SPA + proxy /auth y /exam)
  • Apache reverse proxy (HTTPS en Sinbad2, fuera del repo)

IA

  • Sinbad2IA UJA — POST {LLM_BASE_URL}/api/chat (modelo por defecto qwen3.5:35b)

certificate Quick Start

Desde la raíz del proyecto:

cp backend/.env.example backend/.env
# Edita backend/.env: LLM_BASE_URL y LLM_API_KEY (solo en el servidor, no en Git)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

URLs por defecto con Docker Compose:

Servicio URL local
Frontend http://localhost:8069
Frontend (ruta pública) http://localhost:8069/generadorexamenesllm/
Backend http://localhost:8068
PostgreSQL localhost:5432

Desarrollo del frontend sin Docker (solo UI):

cd frontend
npm install
npm run dev

Vite escucha en http://localhost:8075 (configurable en vite.config.js).

Important

Las rutas bajo /exam requieren JWT de usuario (Authorization: Bearer <token>). El material y las imágenes son opcionales para generar; basta el campo «Tema / instrucciones para la IA» (mínimo 5 caracteres).

Important

Seguridad del LLM: la URL y la clave del servicio de IA no están en el repositorio. POST /exam/generate solo funciona si el servidor tiene LLM_BASE_URL y LLM_API_KEY en backend/.env. El backend no se publica en el host en producción (solo nginx en :8069). Quien clone el repo no puede llamar al LLM sin esas variables en el despliegue.

Tip

Si la generación automática devuelve 503 (Automatic AI generation is not available), faltan variables LLM en el servidor o el servicio no es alcanzable desde el contenedor. Usa Solo prompt → LLM externo → Pegar respuesta IA.


certificate Environment Configuration

Backend

  • Archivo principal: backend/.env
  • Referencia: backend/.env.example

Frontend

  • Build Docker / ejemplo: frontend/.env.example
  • Variables inyectadas en build: VITE_APP_BASE_PATH, VITE_API_URL, VITE_GOOGLE_CLIENT_ID

Variables backend importantes:

Variable Descripción
JWT_SECRET_KEY Secreto JWT (≥ 32 caracteres)
JWT_EXPIRE_MINUTES Duración del token
GOOGLE_CLIENT_ID OAuth Google (POST /auth/google)
DATABASE_URL PostgreSQL
ALLOWED_ORIGINS Orígenes CORS (HTTPS Sinbad2)
TRUSTED_HOSTS Hosts permitidos (TrustedHostMiddleware)
PUBLIC_BASE_URL URL pública con prefijo de despliegue
LLM_BASE_URL URL interna del LLM (solo en .env del servidor, no en el repo)
LLM_API_KEY Clave para autorizar llamadas al LLM (obligatoria para generación automática)
LLM_MODEL Modelo (p. ej. qwen3.5:35b)
LLM_TIMEOUT_SECONDS Timeout llamada LLM (180 s por defecto)
LLM_GENERATE_RATE_LIMIT_* Cuota de POST /exam/generate por usuario
MAX_STORAGE_BYTES_PER_TEMPLATE Cupo materiales + imágenes por examen

Variables frontend importantes:

Variable Descripción
VITE_APP_BASE_PATH Prefijo SPA (/generadorexamenesllm/ en producción)
VITE_API_URL Vacío en producción: API en el mismo origen vía nginx
VITE_GOOGLE_CLIENT_ID Debe coincidir con GOOGLE_CLIENT_ID del backend

Warning

No subas secretos reales al repositorio. Rota JWT_SECRET_KEY y credenciales de Google antes de producción.


certificate API Endpoints

Base backend (Docker local): http://localhost:8068

Todas las rutas /exam/* requieren Bearer JWT salvo que se indique lo contrario.

Módulo Método Endpoint Auth Notas
Health GET /health Ninguna Liveness
Auth POST /auth/register Ninguna Email + contraseña
Auth POST /auth/login Ninguna Devuelve access_token
Auth POST /auth/google Ninguna Body: { "id_token": "..." }
Auth GET /auth/me Bearer Usuario actual
Plantillas POST /exam/templates Bearer Crear examen
Plantillas GET /exam/templates Bearer Listar del usuario
Plantillas GET /exam/templates/{id} Bearer Detalle
Plantillas GET /exam/templates/{id}/storage Bearer Uso de almacenamiento
Materiales POST /exam/templates/{id}/materials Bearer multipart/form-data
Materiales GET /exam/templates/{id}/materials Bearer Listado
Imágenes POST /exam/templates/{id}/images Bearer Imagen para preguntas
Imágenes GET /exam/images/{image_id}/content Bearer Contenido binario
Preguntas GET /exam/templates/{id}/questions Bearer Listado
Preguntas PATCH /exam/questions/{id}/image Bearer Vincular imagen
IA POST /exam/prompts/{template_id} Bearer Construir prompt
IA POST /exam/generate Bearer LLM + guardar preguntas
IA POST /exam/parse Bearer Importar JSON/TXT externo
Export GET /exam/export/xml/{template_id} Bearer Moodle XML
Export GET /exam/export/txt/{template_id} Bearer Texto plano
Export GET /exam/export/json/{template_id} Bearer JSON
Historial GET /exam/history Bearer Resumen de exámenes

certificate Request Examples

Health

curl http://localhost:8068/health

Registro e inicio de sesión

curl -X POST "http://localhost:8068/auth/register" \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"docente@ujaen.es\",\"password\":\"MiClaveSegura1\",\"password_confirm\":\"MiClaveSegura1\"}"

curl -X POST "http://localhost:8068/auth/login" \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"docente@ujaen.es\",\"password\":\"MiClaveSegura1\"}"

Crear plantilla de examen

curl -X POST "http://localhost:8068/exam/templates" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d "{
    \"title\": \"Examen Tema 3\",
    \"subject\": \"Sistemas Operativos\",
    \"educational_level\": \"Grado\",
    \"language\": \"es\",
    \"settings\": {
      \"question_types\": [{\"type\": \"multichoice\", \"count\": 5, \"score\": 1, \"penalty\": 0}],
      \"shuffle_questions\": true,
      \"shuffle_answers\": true,
      \"include_feedback\": true
    },
    \"difficulty_profile\": {\"easy\": 2, \"medium\": 3, \"hard\": 0, \"very_hard\": 0}
  }"

Generar preguntas (solo con tema, sin materiales)

curl -X POST "http://localhost:8068/exam/generate" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d "{
    \"template_id\": \"TEMPLATE_UUID\",
    \"topic_prompt\": \"Genera preguntas sobre aritmética y trigonometría\",
    \"material_ids\": null
  }"

Exportar Moodle XML

curl "http://localhost:8068/exam/export/xml/TEMPLATE_UUID" \
  -H "Authorization: Bearer YOUR_JWT" \
  -o examen_moodle.xml

certificate Security Controls

Controles implementados en backend:

  • CORS con lista de orígenes permitidos
  • filtrado de hosts de confianza (TrustedHostMiddleware)
  • cabeceras de seguridad (HSTS en producción)
  • límite de tamaño de petición
  • rate limiting por cliente
  • LLM solo desde el servidor: credenciales en .env, sin URL en el código público
  • POST /exam/generate: exige JWT + LLM_API_KEY configurada + límite por usuario
  • backend no expuesto en docker-compose.yml (solo red interna Docker; Apache → frontend)
  • contraseñas con bcrypt
  • JWT por usuario; cada plantilla pertenece a un único usuario
  • sanitización de prompts y texto persistido
  • contenedores sin privilegios innecesarios

Warning

En producción, fuerza HTTPS detrás de Apache y mantén ALLOWED_ORIGINS y TRUSTED_HOSTS alineados con https://sinbad2.ujaen.es. El servicio LLM de la UJA debería aceptar peticiones solo desde la red del backend y exigir LLM_API_KEY (firewall + clave en el propio Sinbad2IA).


certificate Frontend Details

SPA React con rutas protegidas y cliente API centralizado (frontend/src/api/client.js).

Rutas principales

Ruta Página
/login Inicio de sesión
/registro Alta de usuario
/ Panel «Mis exámenes»
/plantillas/nueva Asistente «Nuevo examen»
/plantillas/:templateId Detalle con pestañas: Resumen, Material IA, Imágenes, Generar, Preguntas, Exportar

Pestaña Generar (tres modos)

  • Generación automática: POST /exam/generate — llama al LLM del servidor.
  • Solo prompt: POST /exam/prompts/{id} — copiar prompt a un LLM externo.
  • Pegar respuesta IA: POST /exam/parse — importar JSON o TXT.

Cliente API (frontend/src/api/)

  • client.js — Axios, JWT en localStorage, errores normalizados (ApiError)
  • auth.js, templates.js, materials.js, images.js, generation.js, exports.js, questions.js

Nginx en Docker (frontend/nginx.conf)

  • Sirve la SPA bajo / y /generadorexamenesllm/
  • Proxy de /auth/ y /exam/ al backend (backend:8074 en red interna)
  • Redirección de raíz sin barra final al login

certificate Project Structure

03_GeneradorExamenes/
├── backend/
│   ├── app/
│   │   ├── api/
│   │   │   ├── routes/
│   │   │   │   ├── auth.py
│   │   │   │   ├── templates.py
│   │   │   │   ├── generation.py
│   │   │   │   ├── materials.py
│   │   │   │   ├── images.py
│   │   │   │   ├── questions.py
│   │   │   │   ├── exports.py
│   │   │   │   └── history.py
│   │   │   └── dependencies.py
│   │   ├── core/
│   │   │   ├── config.py
│   │   │   ├── auth.py
│   │   │   ├── errors.py
│   │   │   ├── middleware.py
│   │   │   └── security_headers.py
│   │   ├── db/
│   │   ├── models/
│   │   ├── schemas/
│   │   ├── services/
│   │   │   ├── exam_service.py
│   │   │   ├── llm.py
│   │   │   ├── prompt_builder.py
│   │   │   ├── parser.py
│   │   │   ├── material_service.py
│   │   │   ├── image_service.py
│   │   │   └── moodle_exporter.py
│   │   └── main.py
│   ├── .env.example
│   ├── Dockerfile
│   └── requirements.txt
├── frontend/
│   ├── src/
│   │   ├── api/
│   │   ├── components/
│   │   ├── context/
│   │   ├── pages/
│   │   │   ├── DashboardPage.jsx
│   │   │   ├── CreateTemplatePage.jsx
│   │   │   ├── TemplateDetailPage.jsx
│   │   │   └── template/
│   │   │       ├── GenerateTab.jsx
│   │   │       ├── MaterialsTab.jsx
│   │   │       └── ExportTab.jsx
│   │   ├── App.jsx
│   │   └── main.jsx
│   ├── nginx.conf
│   ├── .env.example
│   ├── package.json
│   └── vite.config.js
├── deploy/
│   ├── DESPLIEGUE_SINBAD2.md
│   └── apache-reverse-proxy.conf
├── docs/
│   └── Manual_de_Usuario_GenExamenes_IA.docx
├── scripts/
│   └── generar_manual_usuario.py
├── docker-compose.yml
├── GUIA_API_Y_FLUJO.md
└── README.md

certificate Production Checklist

  • ENVIRONMENT=production
  • Rotar JWT_SECRET_KEY y GOOGLE_CLIENT_ID de desarrollo
  • ALLOWED_ORIGINS con orígenes HTTPS reales
  • TRUSTED_HOSTS con sinbad2.ujaen.es
  • PUBLIC_BASE_URL=https://sinbad2.ujaen.es/generadorexamenesllm
  • Apache ProxyPass a puerto 8069 (frontend), no al backend directo
  • Reglas Apache antes de WordPress en Sinbad2
  • LLM_BASE_URL y LLM_API_KEY solo en backend/.env del servidor (no en Git)
  • Comprobar conectividad backend → LLM (red interna UJA)
  • Despliegue con docker compose up sin docker-compose.dev.yml (backend no publicado en host)
  • Volumen uploads_data y backups de PostgreSQL
  • Manual de usuario actualizado en docs/

github Authors and Team

Este proyecto es el resultado de la colaboración con la Universidad de Jaén (grupo Sinbad²).

Rol Developer GitHub
Frontend Alexis López Moral @AlexisLopez-Dev
Backend Mireya Cueto Garrido @MireyaCueto

Direction

  • Project Supervisor: Luis Martínez López

Built with professional care and ❤️ for AI-assisted exam workflows at the University of Jaén.