- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.1 KiB
9.1 KiB
Convenciones de API REST
Versión: 1.0.0 Última Actualización: 2025-11-28 Aplica a: apps/backend/src/modules/*/controllers/
Resumen
Este documento define las convenciones para diseñar y documentar endpoints REST en GAMILIT. Todos los endpoints siguen estándares RESTful con documentación OpenAPI/Swagger.
Estructura de URLs
Patrón Base
/api/v1/{resource}
/api/v1/{resource}/{id}
/api/v1/{resource}/{id}/{sub-resource}
Ejemplos
| Operación | Método | URL |
|---|---|---|
| Listar usuarios | GET | /api/v1/users |
| Obtener usuario | GET | /api/v1/users/:id |
| Crear usuario | POST | /api/v1/users |
| Actualizar usuario | PATCH | /api/v1/users/:id |
| Eliminar usuario | DELETE | /api/v1/users/:id |
| Logros del usuario | GET | /api/v1/users/:id/achievements |
Códigos de Estado HTTP
| Código | Significado | Cuándo Usar |
|---|---|---|
| 200 | OK | GET exitoso, PATCH exitoso |
| 201 | Created | POST exitoso |
| 204 | No Content | DELETE exitoso |
| 400 | Bad Request | Validación fallida |
| 401 | Unauthorized | No autenticado |
| 403 | Forbidden | Sin permisos |
| 404 | Not Found | Recurso no existe |
| 409 | Conflict | Duplicado o conflicto de estado |
| 422 | Unprocessable Entity | Lógica de negocio fallida |
| 500 | Internal Server Error | Error del servidor |
Formato de Respuestas
Respuesta Exitosa Simple
{
"id": "uuid",
"name": "Ejemplo",
"createdAt": "2025-11-28T10:00:00Z"
}
Respuesta Exitosa Paginada
{
"data": [
{ "id": "uuid1", "name": "Item 1" },
{ "id": "uuid2", "name": "Item 2" }
],
"meta": {
"total": 100,
"page": 1,
"limit": 20,
"totalPages": 5
}
}
Respuesta de Error
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
{
"field": "email",
"message": "must be a valid email"
}
],
"timestamp": "2025-11-28T10:00:00Z"
}
Decoradores de Controlador
Controlador Básico
@Controller('api/v1/achievements')
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiTags('Achievements')
@ApiBearerAuth()
export class AchievementsController {
constructor(private readonly achievementsService: AchievementsService) {}
}
Endpoint GET (Listar)
@Get()
@Roles('admin', 'teacher', 'student')
@ApiOperation({ summary: 'List all achievements' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'category', required: false, type: String })
@ApiResponse({ status: 200, description: 'List of achievements', type: [AchievementResponseDto] })
async findAll(
@Query() query: ListAchievementsDto,
): Promise<PaginatedResponse<AchievementResponseDto>> {
return this.achievementsService.findAll(query);
}
Endpoint GET (Uno)
@Get(':id')
@Roles('admin', 'teacher', 'student')
@ApiOperation({ summary: 'Get achievement by ID' })
@ApiParam({ name: 'id', type: String, description: 'Achievement UUID' })
@ApiResponse({ status: 200, description: 'Achievement details', type: AchievementResponseDto })
@ApiResponse({ status: 404, description: 'Achievement not found' })
async findOne(
@Param('id', ParseUUIDPipe) id: string,
): Promise<AchievementResponseDto> {
return this.achievementsService.findOne(id);
}
Endpoint POST
@Post()
@Roles('admin')
@ApiOperation({ summary: 'Create new achievement' })
@ApiBody({ type: CreateAchievementDto })
@ApiResponse({ status: 201, description: 'Achievement created', type: AchievementResponseDto })
@ApiResponse({ status: 400, description: 'Validation failed' })
async create(
@Body() createDto: CreateAchievementDto,
@CurrentUser() user: UserEntity,
): Promise<AchievementResponseDto> {
return this.achievementsService.create(createDto, user.id);
}
Endpoint PATCH
@Patch(':id')
@Roles('admin')
@ApiOperation({ summary: 'Update achievement' })
@ApiParam({ name: 'id', type: String })
@ApiBody({ type: UpdateAchievementDto })
@ApiResponse({ status: 200, description: 'Achievement updated', type: AchievementResponseDto })
@ApiResponse({ status: 404, description: 'Achievement not found' })
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateDto: UpdateAchievementDto,
): Promise<AchievementResponseDto> {
return this.achievementsService.update(id, updateDto);
}
Endpoint DELETE
@Delete(':id')
@Roles('admin')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete achievement' })
@ApiParam({ name: 'id', type: String })
@ApiResponse({ status: 204, description: 'Achievement deleted' })
@ApiResponse({ status: 404, description: 'Achievement not found' })
async remove(
@Param('id', ParseUUIDPipe) id: string,
): Promise<void> {
return this.achievementsService.remove(id);
}
DTOs de Request
CreateDto
export class CreateAchievementDto {
@ApiProperty({ description: 'Achievement name', example: 'First Steps' })
@IsString()
@IsNotEmpty()
@MaxLength(100)
name: string;
@ApiProperty({ description: 'Achievement description' })
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: 'XP reward', example: 50 })
@IsInt()
@Min(0)
@Max(1000)
xpReward: number;
@ApiProperty({ description: 'Category ID', format: 'uuid' })
@IsUUID()
categoryId: string;
}
UpdateDto
export class UpdateAchievementDto extends PartialType(
OmitType(CreateAchievementDto, ['categoryId'] as const)
) {}
ListDto (Query Params)
export class ListAchievementsDto extends PaginationDto {
@ApiPropertyOptional({ description: 'Filter by category' })
@IsOptional()
@IsUUID()
categoryId?: string;
@ApiPropertyOptional({ description: 'Filter by active status' })
@IsOptional()
@Transform(({ value }) => value === 'true')
@IsBoolean()
isActive?: boolean;
@ApiPropertyOptional({ enum: ['name', 'createdAt', 'xpReward'] })
@IsOptional()
@IsIn(['name', 'createdAt', 'xpReward'])
sortBy?: string;
}
DTOs de Response
export class AchievementResponseDto {
@ApiProperty({ format: 'uuid' })
id: string;
@ApiProperty()
name: string;
@ApiProperty({ required: false })
description?: string;
@ApiProperty()
xpReward: number;
@ApiProperty()
isActive: boolean;
@ApiProperty({ type: String, format: 'date-time' })
createdAt: Date;
@ApiProperty({ type: String, format: 'date-time' })
updatedAt: Date;
}
Validación
Pipes Globales
// main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Eliminar propiedades no definidas
forbidNonWhitelisted: true, // Error si hay propiedades extras
transform: true, // Transformar a tipos correctos
transformOptions: {
enableImplicitConversion: true,
},
}),
);
Pipes Específicos
@Get(':id')
async findOne(
@Param('id', ParseUUIDPipe) id: string, // Valida UUID
@Query('limit', ParseIntPipe) limit: number, // Valida entero
) { }
Documentación Swagger
Acceso
- URL:
http://localhost:3000/api/docs - JSON:
http://localhost:3000/api/docs-json
Configuración
// main.ts
const config = new DocumentBuilder()
.setTitle('GAMILIT API')
.setDescription('API de gamificación educativa')
.setVersion('2.3.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
Endpoints por Módulo
Auth (/api/v1/auth)
- POST
/register- Registrar usuario - POST
/login- Iniciar sesión - POST
/logout- Cerrar sesión - POST
/refresh- Refrescar token - POST
/forgot-password- Solicitar reset - POST
/reset-password- Aplicar reset
Gamification (/api/v1/gamification)
- GET
/stats- Estadísticas del usuario - GET
/achievements- Logros disponibles - GET
/achievements/user- Logros del usuario - GET
/leaderboard- Tabla de posiciones - GET
/ranks- Rangos Maya - POST
/comodines/purchase- Comprar comodín - POST
/comodines/use- Usar comodín
Educational (/api/v1/educational)
- GET
/modules- Módulos educativos - GET
/exercises- Ejercicios - POST
/exercises/:id/submit- Enviar respuesta
Progress (/api/v1/progress)
- GET
/sessions- Sesiones de aprendizaje - GET
/submissions- Entregas del usuario - GET
/module/:id- Progreso por módulo
Buenas Prácticas
- Versionado en URL: Siempre
/api/v1/ - Recursos en plural:
/users, no/user - Verbos HTTP correctos: GET lee, POST crea, PATCH actualiza, DELETE elimina
- IDs en URL: No en query params para recursos específicos
- Filtros en query:
?status=active&page=1 - Documentar todo: Cada endpoint con @ApiOperation
- Validar entrada: Usar DTOs con class-validator
- Respuestas consistentes: Mismo formato siempre
Ver También
- ERROR-HANDLING.md - Manejo de errores
- ESTRUCTURA-MODULOS.md - Estructura de módulos
- Swagger UI:
http://localhost:3000/api/docs