✨ 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 ✅
14 KiB
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
- Registro/Login: Obtiene access token (15 min) + refresh token (7 días)
- API Calls: Usa access token en header
- Refresh: Cuando expira, usa refresh token para obtener nuevo access token
- Logout: Invalida refresh token (agregado a blacklist en Redis)
Endpoints
Auth
POST /auth/register
Registra nuevo usuario.
Request:
{
"email": "user@example.com",
"password": "SecurePass123!",
"firstName": "John",
"lastName": "Doe"
}
Response:
{
"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:
{
"email": "user@example.com",
"password": "SecurePass123!"
}
Response:
{
"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:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
Response:
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
}
POST /auth/logout
Invalida tokens (agrega refresh token a blacklist).
Headers:
Authorization: Bearer {access_token}
Request:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
Response:
{
"success": true,
"message": "Logout successful"
}
GET /auth/me
Obtiene perfil del usuario autenticado.
Response:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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ódulotopicId(optional): Filtrar por temadifficulty(optional): BASIC | INTERMEDIATE | ADVANCED | EXPERTtype(optional): MULTIPLE_CHOICE | OPEN_RESPONSE | CALCULATION | PROOF | TRUE_FALSElimit(optional): Número de resultados (default: 20)offset(optional): Paginación (default: 0)
Response:
{
"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:
{
"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:
{
"answer": "(4, 6)",
"timeSpent": 120,
"showHints": true
}
Response:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"topicId": "topic-1",
"difficulty": "INTERMEDIATE",
"type": "CALCULATION",
"context": "Vectores en 3D"
}
Response:
{
"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:
{
"exerciseId": "ex-1",
"userAnswer": "(4, 6)",
"context": "Suma de vectores 2D"
}
Response:
{
"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
{
"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:
{
"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:
{
"data": [...],
"pagination": {
"total": 150,
"limit": 20,
"offset": 0,
"hasMore": true,
"nextCursor": "eyJpZCI6..."
}
}