✨ Características: - 45 ejercicios universitarios (Basic → Advanced) - Renderizado LaTeX profesional - IA generativa (Z.ai/DashScope) - Docker 9 servicios - Tests 123/123 pasando - Seguridad enterprise (JWT, XSS, Rate limiting) 🐳 Infraestructura: - Next.js 14 + Node.js 20 - PostgreSQL 15 + Redis 7 - Docker Compose completo - Nginx + SSL ready 📚 Documentación: - 5 informes técnicos completos - README profesional - Scripts de deployment automatizados Estado: Producción lista ✅
778 lines
14 KiB
Markdown
778 lines
14 KiB
Markdown
# API Documentation
|
|
|
|
## Base URL
|
|
|
|
```
|
|
Development: http://localhost:3001/api
|
|
Production: https://api.mathplatform.com/api
|
|
```
|
|
|
|
## Autenticación
|
|
|
|
Todas las rutas (excepto auth) requieren header:
|
|
```
|
|
Authorization: Bearer {jwt_token}
|
|
```
|
|
|
|
### Flujo de Autenticación
|
|
|
|
1. **Registro/Login**: Obtiene access token (15 min) + refresh token (7 días)
|
|
2. **API Calls**: Usa access token en header
|
|
3. **Refresh**: Cuando expira, usa refresh token para obtener nuevo access token
|
|
4. **Logout**: Invalida refresh token (agregado a blacklist en Redis)
|
|
|
|
## Endpoints
|
|
|
|
### Auth
|
|
|
|
#### POST /auth/register
|
|
|
|
Registra nuevo usuario.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePass123!",
|
|
"firstName": "John",
|
|
"lastName": "Doe"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"role": "USER",
|
|
"createdAt": "2024-03-30T12:00:00.000Z"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /auth/login
|
|
|
|
Autentica usuario existente.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePass123!"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
|
|
"user": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"role": "USER"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /auth/refresh
|
|
|
|
Renueva access token usando refresh token.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /auth/logout
|
|
|
|
Invalida tokens (agrega refresh token a blacklist).
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Logout successful"
|
|
}
|
|
```
|
|
|
|
#### GET /auth/me
|
|
|
|
Obtiene perfil del usuario autenticado.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"role": "USER",
|
|
"createdAt": "2024-03-30T12:00:00.000Z",
|
|
"lastLoginAt": "2024-03-30T12:00:00.000Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Modules
|
|
|
|
#### GET /modules
|
|
|
|
Lista todos los módulos pedagógicos.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": "mod-1",
|
|
"name": "Fundamentos",
|
|
"description": "Conceptos básicos de álgebra lineal",
|
|
"type": "FUNDAMENTOS",
|
|
"order": 1,
|
|
"topicCount": 5,
|
|
"exerciseCount": 25
|
|
},
|
|
{
|
|
"id": "mod-2",
|
|
"name": "Sistemas y Espacios",
|
|
"description": "Sistemas de ecuaciones y espacios vectoriales",
|
|
"type": "SISTEMAS_ESPACIOS",
|
|
"order": 2,
|
|
"topicCount": 4,
|
|
"exerciseCount": 30
|
|
},
|
|
{
|
|
"id": "mod-3",
|
|
"name": "Aplicaciones",
|
|
"description": "Aplicaciones prácticas de álgebra lineal",
|
|
"type": "APLICACIONES",
|
|
"order": 3,
|
|
"topicCount": 3,
|
|
"exerciseCount": 20
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### GET /modules/:id
|
|
|
|
Obtiene detalle de un módulo específico.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "mod-1",
|
|
"name": "Fundamentos",
|
|
"description": "Conceptos básicos de álgebra lineal",
|
|
"type": "FUNDAMENTOS",
|
|
"order": 1,
|
|
"topics": [
|
|
{
|
|
"id": "topic-1",
|
|
"name": "Vectores",
|
|
"description": "Operaciones con vectores",
|
|
"order": 1
|
|
}
|
|
],
|
|
"progress": {
|
|
"completedExercises": 10,
|
|
"totalExercises": 25,
|
|
"percentage": 40
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Topics
|
|
|
|
#### GET /topics
|
|
|
|
Lista todos los temas.
|
|
|
|
**Query Parameters:**
|
|
- `moduleId` (optional): Filtrar por módulo
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": "topic-1",
|
|
"name": "Vectores",
|
|
"description": "Operaciones con vectores",
|
|
"moduleId": "mod-1",
|
|
"type": "VECTORES",
|
|
"order": 1,
|
|
"exerciseCount": 8
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### GET /topics/:id/theory
|
|
|
|
Obtiene contenido teórico de un tema.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "topic-1",
|
|
"name": "Vectores",
|
|
"content": {
|
|
"introduction": "Los vectores son...",
|
|
"formulas": [
|
|
{
|
|
"name": "Magnitud",
|
|
"latex": "\\|\\vec{v}\\| = \\sqrt{x^2 + y^2}"
|
|
}
|
|
],
|
|
"examples": [
|
|
{
|
|
"problem": "Calcular la magnitud del vector...",
|
|
"solution": "Usando la fórmula...",
|
|
"latex": "\\vec{v} = (3, 4)"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Exercises
|
|
|
|
#### GET /exercises
|
|
|
|
Lista ejercicios con filtros.
|
|
|
|
**Query Parameters:**
|
|
- `moduleId` (optional): Filtrar por módulo
|
|
- `topicId` (optional): Filtrar por tema
|
|
- `difficulty` (optional): BASIC | INTERMEDIATE | ADVANCED | EXPERT
|
|
- `type` (optional): MULTIPLE_CHOICE | OPEN_RESPONSE | CALCULATION | PROOF | TRUE_FALSE
|
|
- `limit` (optional): Número de resultados (default: 20)
|
|
- `offset` (optional): Paginación (default: 0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"exercises": [
|
|
{
|
|
"id": "ex-1",
|
|
"title": "Suma de vectores",
|
|
"description": "Calcular la suma de dos vectores",
|
|
"type": "CALCULATION",
|
|
"difficulty": "BASIC",
|
|
"topicId": "topic-1",
|
|
"latex": "\\vec{a} = (1, 2), \\vec{b} = (3, 4)",
|
|
"points": 10,
|
|
"hints": ["Recuerda sumar componente a componente"],
|
|
"createdAt": "2024-03-30T12:00:00.000Z"
|
|
}
|
|
],
|
|
"pagination": {
|
|
"total": 100,
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"hasMore": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### GET /exercises/:id
|
|
|
|
Obtiene detalle de un ejercicio.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "ex-1",
|
|
"title": "Suma de vectores",
|
|
"description": "Calcular la suma de dos vectores",
|
|
"type": "CALCULATION",
|
|
"difficulty": "BASIC",
|
|
"topic": {
|
|
"id": "topic-1",
|
|
"name": "Vectores"
|
|
},
|
|
"module": {
|
|
"id": "mod-1",
|
|
"name": "Fundamentos"
|
|
},
|
|
"latex": "\\vec{a} = (1, 2), \\vec{b} = (3, 4)",
|
|
"questions": [
|
|
{
|
|
"id": "q-1",
|
|
"text": "¿Cuál es la suma de los vectores?",
|
|
"options": [
|
|
"(4, 6)",
|
|
"(3, 4)",
|
|
"(1, 2)",
|
|
"(5, 8)"
|
|
]
|
|
}
|
|
],
|
|
"points": 10,
|
|
"hints": ["Suma las componentes x", "Suma las componentes y"],
|
|
"timeEstimate": 300
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /exercises/:id/attempt
|
|
|
|
Envía respuesta a ejercicio.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"answer": "(4, 6)",
|
|
"timeSpent": 120,
|
|
"showHints": true
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"attemptId": "att-123",
|
|
"isCorrect": true,
|
|
"score": 10,
|
|
"feedback": "¡Correcto! La suma de vectores se realiza componente a componente.",
|
|
"solution": {
|
|
"steps": [
|
|
"\\vec{a} + \\vec{b} = (1+3, 2+4)",
|
|
"= (4, 6)"
|
|
],
|
|
"latex": "\\vec{a} + \\vec{b} = (4, 6)"
|
|
},
|
|
"progress": {
|
|
"moduleCompleted": false,
|
|
"newAchievements": ["first-step"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Progress
|
|
|
|
#### GET /progress
|
|
|
|
Obtiene progreso general del usuario.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"overall": {
|
|
"totalExercises": 75,
|
|
"completedExercises": 25,
|
|
"percentage": 33.3,
|
|
"totalScore": 250,
|
|
"averageScore": 10
|
|
},
|
|
"modules": [
|
|
{
|
|
"moduleId": "mod-1",
|
|
"moduleName": "Fundamentos",
|
|
"completedExercises": 15,
|
|
"totalExercises": 25,
|
|
"percentage": 60,
|
|
"score": 150
|
|
}
|
|
],
|
|
"streak": {
|
|
"current": 5,
|
|
"longest": 12,
|
|
"lastActivity": "2024-03-30T10:00:00.000Z"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### GET /progress/module/:moduleId
|
|
|
|
Obtiene progreso de un módulo específico.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"moduleId": "mod-1",
|
|
"moduleName": "Fundamentos",
|
|
"completedExercises": 15,
|
|
"totalExercises": 25,
|
|
"percentage": 60,
|
|
"score": 150,
|
|
"topics": [
|
|
{
|
|
"topicId": "topic-1",
|
|
"topicName": "Vectores",
|
|
"completedExercises": 5,
|
|
"totalExercises": 8,
|
|
"percentage": 62.5
|
|
}
|
|
],
|
|
"recentAttempts": [
|
|
{
|
|
"exerciseId": "ex-1",
|
|
"exerciseTitle": "Suma de vectores",
|
|
"isCorrect": true,
|
|
"score": 10,
|
|
"attemptedAt": "2024-03-30T10:00:00.000Z"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Ranking
|
|
|
|
#### GET /ranking/global
|
|
|
|
Obtiene ranking global.
|
|
|
|
**Query Parameters:**
|
|
- `limit` (optional): Resultados por página (default: 50)
|
|
- `offset` (optional): Paginación (default: 0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"rankings": [
|
|
{
|
|
"position": 1,
|
|
"user": {
|
|
"id": "user-1",
|
|
"firstName": "Alice",
|
|
"lastName": "Smith"
|
|
},
|
|
"score": 1250,
|
|
"completedExercises": 75,
|
|
"accuracy": 92.5
|
|
}
|
|
],
|
|
"pagination": {
|
|
"total": 150,
|
|
"limit": 50,
|
|
"offset": 0,
|
|
"hasMore": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### GET /ranking/my-position
|
|
|
|
Obtiene posición del usuario actual.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"globalPosition": 25,
|
|
"score": 450,
|
|
"completedExercises": 35,
|
|
"accuracy": 85.2,
|
|
"modulePositions": [
|
|
{
|
|
"moduleId": "mod-1",
|
|
"moduleName": "Fundamentos",
|
|
"position": 15,
|
|
"score": 200
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Achievements
|
|
|
|
#### GET /achievements
|
|
|
|
Lista todos los logros disponibles.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"achievements": [
|
|
{
|
|
"id": "ach-1",
|
|
"name": "Primer Paso",
|
|
"description": "Completa tu primer ejercicio",
|
|
"category": "EXERCISES",
|
|
"rarity": "COMMON",
|
|
"icon": "🎯",
|
|
"requirement": {
|
|
"type": "EXERCISE_COUNT",
|
|
"value": 1
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### GET /achievements/my
|
|
|
|
Obtiene logros desbloqueados del usuario.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"unlocked": [
|
|
{
|
|
"id": "ach-1",
|
|
"name": "Primer Paso",
|
|
"description": "Completa tu primer ejercicio",
|
|
"category": "EXERCISES",
|
|
"rarity": "COMMON",
|
|
"icon": "🎯",
|
|
"unlockedAt": "2024-03-30T12:00:00.000Z"
|
|
}
|
|
],
|
|
"progress": [
|
|
{
|
|
"achievementId": "ach-2",
|
|
"name": "En Marcha",
|
|
"progress": 5,
|
|
"required": 10,
|
|
"percentage": 50
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### AI Generation
|
|
|
|
#### POST /ai/generate-exercise
|
|
|
|
Genera ejercicio usando AI.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"topicId": "topic-1",
|
|
"difficulty": "INTERMEDIATE",
|
|
"type": "CALCULATION",
|
|
"context": "Vectores en 3D"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"exercise": {
|
|
"title": "Producto cruz en 3D",
|
|
"description": "Calcular el producto cruz de dos vectores en 3D",
|
|
"latex": "\\vec{a} = (1, 2, 3), \\vec{b} = (4, 5, 6)",
|
|
"solution": {
|
|
"steps": [
|
|
"\\vec{a} \\times \\vec{b} = (2\\cdot6 - 3\\cdot5, 3\\cdot4 - 1\\cdot6, 1\\cdot5 - 2\\cdot4)",
|
|
"= (12-15, 12-6, 5-8)",
|
|
"= (-3, 6, -3)"
|
|
],
|
|
"latex": "\\vec{a} \\times \\vec{b} = (-3, 6, -3)"
|
|
},
|
|
"difficulty": "INTERMEDIATE",
|
|
"points": 15
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /ai/validate-answer
|
|
|
|
Valida respuesta usando AI.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"exerciseId": "ex-1",
|
|
"userAnswer": "(4, 6)",
|
|
"context": "Suma de vectores 2D"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"isCorrect": true,
|
|
"confidence": 0.95,
|
|
"feedback": "Respuesta correcta. La suma de vectores (1,2) + (3,4) = (4,6)",
|
|
"explanation": "Se sumaron correctamente las componentes x (1+3=4) y y (2+4=6)"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Códigos de Error
|
|
|
|
| Código | HTTP | Descripción |
|
|
|--------|------|-------------|
|
|
| `BAD_REQUEST` | 400 | Datos inválidos o faltantes |
|
|
| `UNAUTHORIZED` | 401 | Token inválido o expirado |
|
|
| `FORBIDDEN` | 403 | Sin permisos para el recurso |
|
|
| `NOT_FOUND` | 404 | Recurso no existe |
|
|
| `VALIDATION_ERROR` | 422 | Error de validación de datos |
|
|
| `RATE_LIMIT` | 429 | Rate limit excedido |
|
|
| `INTERNAL_ERROR` | 500 | Error interno del servidor |
|
|
| `SERVICE_UNAVAILABLE` | 503 | Servicio temporalmente no disponible |
|
|
|
|
### Formato de Error
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": "VALIDATION_ERROR",
|
|
"message": "Datos de entrada inválidos",
|
|
"details": {
|
|
"field": "email",
|
|
"issue": "Email inválido"
|
|
}
|
|
},
|
|
"meta": {
|
|
"timestamp": "2024-03-30T12:00:00.000Z",
|
|
"requestId": "req_abc123"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
- **Auth endpoints**: 5 requests / 15 min / IP
|
|
- **API general**: 100 requests / 15 min / user
|
|
- **AI generation**: 10 requests / hour / user
|
|
- **Exercise attempts**: Sin limitación
|
|
|
|
Headers de respuesta:
|
|
```
|
|
X-RateLimit-Limit: 100
|
|
X-RateLimit-Remaining: 95
|
|
X-RateLimit-Reset: 1711802400
|
|
```
|
|
|
|
## Versionado
|
|
|
|
La API usa versionado en URL:
|
|
- `/api/v1/...` - Versión actual
|
|
- `/api/...` - Alias a v1 (default)
|
|
|
|
## Webhooks (Admin)
|
|
|
|
### POST /webhooks/telegram
|
|
|
|
Endpoint para recibir actualizaciones de Telegram.
|
|
|
|
### POST /webhooks/pdf-processed
|
|
|
|
Notificación cuando un PDF es procesado.
|
|
|
|
## Health Check
|
|
|
|
### GET /health
|
|
|
|
Verifica estado del servicio.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"timestamp": "2024-03-30T12:00:00.000Z",
|
|
"version": "1.0.0",
|
|
"services": {
|
|
"database": "connected",
|
|
"redis": "connected",
|
|
"ai": "available"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Paginación
|
|
|
|
Todas las listas soportan paginación con:
|
|
- `limit`: Número de items (max: 100)
|
|
- `offset`: Índice inicial (0-based)
|
|
- `cursor`: Para paginación basada en cursor (alternativa)
|
|
|
|
Formato de respuesta paginada:
|
|
```json
|
|
{
|
|
"data": [...],
|
|
"pagination": {
|
|
"total": 150,
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"hasMore": true,
|
|
"nextCursor": "eyJpZCI6..."
|
|
}
|
|
}
|
|
```
|