Harden LLM access: secrets only in server .env, no URL in repo.
Require LLM_BASE_URL and LLM_API_KEY for automatic generation, add per-user rate limits, stop publishing backend/LLM settings in docker-compose, and document secure deployment.
This commit is contained in:
@@ -1,185 +1,405 @@
|
||||
# GenExamenes IA
|
||||
# GenExámenes IA
|
||||
|
||||
Backend para generar exámenes con IA, procesar la salida de un LLM y exportar preguntas a Moodle XML.
|
||||
<div align="center">
|
||||
|
||||
**Guía detallada de flujo, endpoints, ejemplos y errores:** [GUIA_API_Y_FLUJO.md](GUIA_API_Y_FLUJO.md)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
El proyecto está centrado en backend. La carpeta `frontend` se mantiene vacía a nivel de aplicación, aunque existe un servicio en Docker Compose para reservar el despliegue futuro.
|
||||
</div>
|
||||
|
||||
## Stack
|
||||
<div align="center">
|
||||
<strong>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.</strong>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
##  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 imagen–pregunta 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](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](GUIA_API_Y_FLUJO.md)
|
||||
|
||||
---
|
||||
|
||||
##  Tech Stack
|
||||
|
||||
### Backend
|
||||
- FastAPI
|
||||
- PostgreSQL
|
||||
- SQLAlchemy
|
||||
- Cliente LLM para Sinbad2IA UJA (`POST /api/chat`, modelo `qwen3.5:35b`)
|
||||
- Docker Compose con servicios `backend`, `frontend` y `db`
|
||||
- PostgreSQL
|
||||
- python-jose (JWT)
|
||||
- bcrypt (contraseñas)
|
||||
- httpx (cliente LLM Sinbad2IA)
|
||||
- pypdf / python-docx / Pillow / pytesseract (extracción de materiales)
|
||||
|
||||
## Puesta en Marcha
|
||||
### Frontend
|
||||
- React 18
|
||||
- Vite 5
|
||||
- React Router 6
|
||||
- Axios
|
||||
- CSS propio (UI responsive, menú móvil)
|
||||
|
||||
Copia el ejemplo de variables dentro de la carpeta del backend:
|
||||
### 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`)
|
||||
|
||||
---
|
||||
|
||||
##  Quick Start
|
||||
|
||||
Desde la raíz del proyecto:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
Después levanta los servicios:
|
||||
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):
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
La API queda disponible en:
|
||||
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**.
|
||||
|
||||
---
|
||||
|
||||
##  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.
|
||||
|
||||
---
|
||||
|
||||
##  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 |
|
||||
|
||||
---
|
||||
|
||||
##  Request Examples
|
||||
|
||||
### Health
|
||||
```bash
|
||||
curl http://localhost:8068/health
|
||||
```
|
||||
|
||||
### Registro e inicio de sesión
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
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)
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
curl "http://localhost:8068/exam/export/xml/TEMPLATE_UUID" \
|
||||
-H "Authorization: Bearer YOUR_JWT" \
|
||||
-o examen_moodle.xml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
##  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).
|
||||
|
||||
---
|
||||
|
||||
##  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
|
||||
|
||||
---
|
||||
|
||||
##  Project Structure
|
||||
|
||||
```text
|
||||
http://sinbad2.ujaen.es:8068
|
||||
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
|
||||
```
|
||||
|
||||
## Configuración
|
||||
---
|
||||
|
||||
El archivo de entorno debe estar en `backend/.env`.
|
||||
##  Production Checklist
|
||||
|
||||
Variables principales:
|
||||
- [ ] `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/`
|
||||
|
||||
- `JWT_SECRET_KEY`: secreto para firmar tokens JWT (mínimo 32 caracteres).
|
||||
- `JWT_EXPIRE_MINUTES`: duración del token de acceso.
|
||||
- `GOOGLE_CLIENT_ID`: Client ID de OAuth 2.0 en Google Cloud Console (para `/auth/google`).
|
||||
- `DATABASE_URL`: conexión PostgreSQL usada por el backend.
|
||||
- `LLM_BASE_URL`: URL base del servidor (por defecto ``; el cliente usa `/api/chat`).
|
||||
- `LLM_MODEL`: modelo (por defecto `qwen3.5:35b`).
|
||||
- `LLM_TIMEOUT_SECONDS`: tiempo máximo de espera (por defecto 180 s).
|
||||
- `LLM_API_KEY`: opcional, solo si el servidor exige autenticación.
|
||||
- `ALLOWED_ORIGINS`: orígenes permitidos por CORS.
|
||||
- `MAX_STORAGE_BYTES_PER_TEMPLATE`: cupo total de almacenamiento por examen (materiales + imágenes).
|
||||
---
|
||||
|
||||
Todas las rutas bajo `/exam` requieren autenticación de usuario con:
|
||||
##  Authors and Team
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
Este proyecto es el resultado de la colaboración con la **Universidad de Jaén** (grupo Sinbad²).
|
||||
|
||||
Si ya tenías una base de datos creada antes de añadir usuarios, recrea el volumen:
|
||||
| Rol | Developer | GitHub |
|
||||
| :--- | :--- | :--- |
|
||||
| **Frontend** | Alexis López Moral | [@AlexisLopez-Dev](https://github.com/AlexisLopez-Dev) |
|
||||
| **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) |
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
docker compose up --build
|
||||
```
|
||||
### Direction
|
||||
* **Project Supervisor:** Luis Martínez López
|
||||
|
||||
## Flujo de Usuario
|
||||
---
|
||||
|
||||
1. Registrarse o iniciar sesión.
|
||||
2. Crear una plantilla de examen (queda asociada al usuario).
|
||||
3. Subir materiales de referencia (PDF, DOCX, TXT, PNG, JPG…) a la plantilla.
|
||||
4. Generar un prompt guiado para el LLM (incluye el texto extraído de los ficheros).
|
||||
5. Generar preguntas automáticamente con el LLM o parsear una salida externa en JSON/TXT.
|
||||
6. Guardar las preguntas validadas en PostgreSQL.
|
||||
7. Consultar el historial de exámenes creados.
|
||||
8. Exportar el examen a Moodle XML, TXT o JSON.
|
||||
|
||||
## Endpoints
|
||||
|
||||
`GET /health`
|
||||
|
||||
Comprueba que la API está levantada.
|
||||
|
||||
`POST /auth/register`
|
||||
|
||||
Registra un usuario con email y contraseña.
|
||||
|
||||
`POST /auth/login`
|
||||
|
||||
Devuelve un token JWT para usar en las rutas protegidas.
|
||||
|
||||
`POST /auth/google`
|
||||
|
||||
Recibe el `id_token` de Google (Sign in with Google en el frontend), verifica la cuenta y devuelve el mismo JWT de la API.
|
||||
|
||||
`GET /auth/me`
|
||||
|
||||
Devuelve los datos del usuario autenticado.
|
||||
|
||||
`GET /exam/history`
|
||||
|
||||
Lista el historial de exámenes del usuario (plantillas, preguntas y exportaciones).
|
||||
|
||||
`POST /exam/templates/{template_id}/materials`
|
||||
|
||||
Sube un fichero (`multipart/form-data`, campo `file`). Formatos: PDF, DOCX, TXT, MD, PNG, JPG, WEBP. Extrae texto y lo guarda como contexto.
|
||||
|
||||
`GET /exam/templates/{template_id}/materials`
|
||||
|
||||
Lista los materiales subidos a una plantilla.
|
||||
|
||||
`DELETE /exam/templates/{template_id}/materials/{material_id}`
|
||||
|
||||
Elimina un material.
|
||||
|
||||
`POST /exam/templates/{template_id}/images`
|
||||
|
||||
Sube una imagen para preguntas visuales (`file`, opcional `caption`). No se usa OCR: la imagen se muestra en el examen y se embebe en el XML de Moodle.
|
||||
|
||||
`GET /exam/templates/{template_id}/images`
|
||||
|
||||
Lista las imágenes de la plantilla.
|
||||
|
||||
`GET /exam/images/{image_id}/content`
|
||||
|
||||
Devuelve la imagen (requiere JWT). Para previsualizar en el frontend o en Moodle tras importar.
|
||||
|
||||
`DELETE /exam/templates/{template_id}/images/{image_id}`
|
||||
|
||||
Elimina una imagen.
|
||||
|
||||
`PATCH /exam/questions/{question_id}/image`
|
||||
|
||||
Vincula o desvincula una imagen a una pregunta existente (`{"image_id": "uuid"}` o `null`).
|
||||
|
||||
`POST /exam/templates`
|
||||
|
||||
Crea una plantilla con materia, nivel educativo, tipos de pregunta, puntuación, penalización y dificultad.
|
||||
|
||||
`GET /exam/templates`
|
||||
|
||||
Lista las plantillas del usuario autenticado.
|
||||
|
||||
`GET /exam/templates/{template_id}`
|
||||
|
||||
Obtiene una plantilla concreta.
|
||||
|
||||
`GET /exam/templates/{template_id}/storage`
|
||||
|
||||
Muestra cuánto espacio usa el examen (materiales + imágenes) y el límite configurado.
|
||||
|
||||
`POST /exam/prompts/{template_id}`
|
||||
|
||||
Genera un prompt estructurado para IA.
|
||||
|
||||
`POST /exam/generate`
|
||||
|
||||
Llama al LLM configurado, parsea la respuesta y guarda las preguntas.
|
||||
|
||||
`POST /exam/parse`
|
||||
|
||||
Procesa una salida externa de IA en formato `json` o `txt`.
|
||||
|
||||
`GET /exam/export/xml/{template_id}`
|
||||
|
||||
Exporta las preguntas en Moodle XML.
|
||||
|
||||
`GET /exam/export/txt/{template_id}`
|
||||
|
||||
Exporta las preguntas en texto plano.
|
||||
|
||||
`GET /exam/export/json/{template_id}`
|
||||
|
||||
Exporta las preguntas en JSON.
|
||||
|
||||
## Seguridad
|
||||
|
||||
- Registro e inicio de sesión con contraseña hasheada (bcrypt).
|
||||
- Autenticación JWT por usuario.
|
||||
- Cada examen pertenece a un único usuario; no se puede acceder al de otro.
|
||||
- Rate limiting por cliente.
|
||||
- Límite de tamaño de petición.
|
||||
- Validación de entrada con Pydantic.
|
||||
- Manejo uniforme de errores HTTP.
|
||||
- Sanitización básica de prompts y respuestas antes de persistir/exportar.
|
||||
<p align="center">
|
||||
Built with professional care and ❤️ for AI-assisted exam workflows at the University of Jaén.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user