Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
650 lines
16 KiB
Markdown
650 lines
16 KiB
Markdown
# SIMCO: OPERACIONES BACKEND (NestJS/TypeScript)
|
|
|
|
**Versión:** 1.0.0
|
|
**Fecha:** 2025-12-08
|
|
**Aplica a:** Todo agente que trabaje con código backend NestJS
|
|
**Prioridad:** OBLIGATORIA para operaciones de backend
|
|
|
|
---
|
|
|
|
## RESUMEN EJECUTIVO
|
|
|
|
> **Entity alineada con DDL + Service con lógica + Controller con Swagger = Backend completo.**
|
|
|
|
---
|
|
|
|
## PRINCIPIO FUNDAMENTAL
|
|
|
|
```
|
|
╔══════════════════════════════════════════════════════════════════════╗
|
|
║ ALINEACIÓN DB ↔ BACKEND ║
|
|
║ ║
|
|
║ • Entity DEBE coincidir 100% con tabla DDL ║
|
|
║ • Tipos TypeScript DEBEN coincidir con tipos PostgreSQL ║
|
|
║ • Nombres de columnas DEBEN ser idénticos ║
|
|
║ • Si DDL cambia, Entity DEBE cambiar ║
|
|
╚══════════════════════════════════════════════════════════════════════╝
|
|
```
|
|
|
|
---
|
|
|
|
## ESTRUCTURA DE MÓDULOS
|
|
|
|
```
|
|
{BACKEND_SRC}/
|
|
├── app.module.ts # Módulo raíz
|
|
├── main.ts # Entry point
|
|
├── shared/ # Compartido
|
|
│ ├── config/ # Configuraciones
|
|
│ ├── constants/ # Constantes (SSOT)
|
|
│ ├── database/ # TypeORM config
|
|
│ ├── guards/ # Guards compartidos
|
|
│ ├── decorators/ # Decorators custom
|
|
│ ├── interceptors/ # Interceptors
|
|
│ ├── filters/ # Exception filters
|
|
│ └── utils/ # Utilidades
|
|
└── modules/ # Módulos de negocio
|
|
└── {modulo}/
|
|
├── {modulo}.module.ts # Module definition
|
|
├── entities/
|
|
│ └── {nombre}.entity.ts # TypeORM Entity
|
|
├── dto/
|
|
│ ├── create-{nombre}.dto.ts
|
|
│ ├── update-{nombre}.dto.ts
|
|
│ └── {nombre}-response.dto.ts
|
|
├── services/
|
|
│ └── {nombre}.service.ts
|
|
├── controllers/
|
|
│ └── {nombre}.controller.ts
|
|
└── tests/
|
|
├── {nombre}.service.spec.ts
|
|
└── {nombre}.controller.spec.ts
|
|
```
|
|
|
|
---
|
|
|
|
## CONVENCIONES DE NOMENCLATURA
|
|
|
|
### Archivos
|
|
```typescript
|
|
// Entities: {nombre}.entity.ts
|
|
user.entity.ts
|
|
student-progress.entity.ts
|
|
badge-award.entity.ts
|
|
|
|
// Services: {nombre}.service.ts
|
|
user.service.ts
|
|
gamification.service.ts
|
|
|
|
// Controllers: {nombre}.controller.ts
|
|
user.controller.ts
|
|
auth.controller.ts
|
|
|
|
// DTOs: {accion}-{nombre}.dto.ts
|
|
create-user.dto.ts
|
|
update-user.dto.ts
|
|
login.dto.ts
|
|
```
|
|
|
|
### Clases
|
|
```typescript
|
|
// Entities: PascalCase + Entity suffix
|
|
export class UserEntity {}
|
|
export class StudentProgressEntity {}
|
|
export class BadgeAwardEntity {}
|
|
|
|
// Services: PascalCase + Service suffix
|
|
export class UserService {}
|
|
export class GamificationService {}
|
|
|
|
// Controllers: PascalCase + Controller suffix
|
|
export class UserController {}
|
|
export class AuthController {}
|
|
|
|
// DTOs: PascalCase + Dto suffix
|
|
export class CreateUserDto {}
|
|
export class UpdateUserDto {}
|
|
export class LoginDto {}
|
|
```
|
|
|
|
### Métodos y Variables
|
|
```typescript
|
|
// Métodos: camelCase con verbo
|
|
async createUser()
|
|
async findById()
|
|
async updateStatus()
|
|
async deleteUser()
|
|
|
|
// Variables: camelCase
|
|
const userRepository
|
|
const isActive
|
|
const createdAt
|
|
|
|
// Constantes: UPPER_SNAKE_CASE
|
|
const MAX_LOGIN_ATTEMPTS = 5
|
|
const DEFAULT_PAGE_SIZE = 20
|
|
```
|
|
|
|
---
|
|
|
|
## TEMPLATES
|
|
|
|
### Entity (TypeORM)
|
|
|
|
```typescript
|
|
import {
|
|
Entity,
|
|
PrimaryGeneratedColumn,
|
|
Column,
|
|
CreateDateColumn,
|
|
UpdateDateColumn,
|
|
ManyToOne,
|
|
JoinColumn,
|
|
} from 'typeorm';
|
|
|
|
/**
|
|
* {NombreEntity} - {descripción breve}
|
|
*
|
|
* Representa {descripción del dominio}.
|
|
* Mapea a: {schema}.{tabla}
|
|
*
|
|
* @see {DB_DDL_PATH}/schemas/{schema}/tables/{archivo}.sql
|
|
*/
|
|
@Entity({ schema: '{schema}', name: '{tabla}' })
|
|
export class {Nombre}Entity {
|
|
/**
|
|
* Identificador único (UUID)
|
|
*/
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
/**
|
|
* {descripción del campo}
|
|
*/
|
|
@Column({ type: 'varchar', length: 100, nullable: false })
|
|
name: string;
|
|
|
|
/**
|
|
* {descripción del campo}
|
|
*/
|
|
@Column({ type: 'varchar', length: 255, unique: true })
|
|
email: string;
|
|
|
|
/**
|
|
* {descripción del campo con valores válidos}
|
|
*/
|
|
@Column({
|
|
type: 'varchar',
|
|
length: 20,
|
|
default: 'active',
|
|
})
|
|
status: string;
|
|
|
|
/**
|
|
* Relación con {entidad relacionada}
|
|
*/
|
|
@ManyToOne(() => RelatedEntity, { nullable: true })
|
|
@JoinColumn({ name: 'related_id' })
|
|
related: RelatedEntity;
|
|
|
|
@Column({ type: 'uuid', nullable: true })
|
|
relatedId: string;
|
|
|
|
/**
|
|
* Fecha de creación
|
|
*/
|
|
@CreateDateColumn({ type: 'timestamp' })
|
|
createdAt: Date;
|
|
|
|
/**
|
|
* Fecha de última actualización
|
|
*/
|
|
@UpdateDateColumn({ type: 'timestamp' })
|
|
updatedAt: Date;
|
|
}
|
|
```
|
|
|
|
### DTO (Create)
|
|
|
|
```typescript
|
|
import {
|
|
IsString,
|
|
IsEmail,
|
|
IsNotEmpty,
|
|
IsOptional,
|
|
IsEnum,
|
|
IsUUID,
|
|
Length,
|
|
MinLength,
|
|
} from 'class-validator';
|
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
|
|
/**
|
|
* DTO para crear {nombre}
|
|
*/
|
|
export class Create{Nombre}Dto {
|
|
/**
|
|
* {descripción del campo}
|
|
*/
|
|
@ApiProperty({
|
|
description: '{descripción}',
|
|
example: '{ejemplo}',
|
|
minLength: 3,
|
|
maxLength: 100,
|
|
})
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@Length(3, 100)
|
|
name: string;
|
|
|
|
/**
|
|
* {descripción del campo}
|
|
*/
|
|
@ApiProperty({
|
|
description: 'Email del usuario',
|
|
example: 'user@example.com',
|
|
})
|
|
@IsEmail()
|
|
@IsNotEmpty()
|
|
email: string;
|
|
|
|
/**
|
|
* {descripción del campo opcional}
|
|
*/
|
|
@ApiPropertyOptional({
|
|
description: '{descripción}',
|
|
example: '{ejemplo}',
|
|
})
|
|
@IsOptional()
|
|
@IsString()
|
|
description?: string;
|
|
|
|
/**
|
|
* {descripción del campo enum}
|
|
*/
|
|
@ApiProperty({
|
|
description: '{descripción}',
|
|
enum: ['option1', 'option2', 'option3'],
|
|
example: 'option1',
|
|
})
|
|
@IsEnum(['option1', 'option2', 'option3'])
|
|
type: string;
|
|
|
|
/**
|
|
* Relación con {entidad}
|
|
*/
|
|
@ApiPropertyOptional({
|
|
description: 'ID de {entidad relacionada}',
|
|
example: 'uuid-here',
|
|
})
|
|
@IsOptional()
|
|
@IsUUID()
|
|
relatedId?: string;
|
|
}
|
|
```
|
|
|
|
### DTO (Update)
|
|
|
|
```typescript
|
|
import { PartialType } from '@nestjs/swagger';
|
|
import { Create{Nombre}Dto } from './create-{nombre}.dto';
|
|
|
|
/**
|
|
* DTO para actualizar {nombre}
|
|
*
|
|
* Todos los campos son opcionales (PartialType)
|
|
*/
|
|
export class Update{Nombre}Dto extends PartialType(Create{Nombre}Dto) {}
|
|
```
|
|
|
|
### Service
|
|
|
|
```typescript
|
|
import {
|
|
Injectable,
|
|
NotFoundException,
|
|
ConflictException,
|
|
} from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { {Nombre}Entity } from '../entities/{nombre}.entity';
|
|
import { Create{Nombre}Dto } from '../dto/create-{nombre}.dto';
|
|
import { Update{Nombre}Dto } from '../dto/update-{nombre}.dto';
|
|
|
|
/**
|
|
* Service para gestión de {nombre}
|
|
*
|
|
* Provee operaciones CRUD y lógica de negocio.
|
|
*/
|
|
@Injectable()
|
|
export class {Nombre}Service {
|
|
constructor(
|
|
@InjectRepository({Nombre}Entity)
|
|
private readonly repository: Repository<{Nombre}Entity>,
|
|
) {}
|
|
|
|
/**
|
|
* Crea un nuevo {nombre}
|
|
*
|
|
* @param dto - Datos para crear
|
|
* @returns {Nombre} creado
|
|
* @throws ConflictException si ya existe
|
|
*/
|
|
async create(dto: Create{Nombre}Dto): Promise<{Nombre}Entity> {
|
|
// Validar unicidad si aplica
|
|
const existing = await this.repository.findOne({
|
|
where: { email: dto.email },
|
|
});
|
|
if (existing) {
|
|
throw new ConflictException('Email already exists');
|
|
}
|
|
|
|
const entity = this.repository.create(dto);
|
|
return await this.repository.save(entity);
|
|
}
|
|
|
|
/**
|
|
* Obtiene todos los {nombre}
|
|
*
|
|
* @returns Lista de {nombre}
|
|
*/
|
|
async findAll(): Promise<{Nombre}Entity[]> {
|
|
return await this.repository.find({
|
|
order: { createdAt: 'DESC' },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Obtiene un {nombre} por ID
|
|
*
|
|
* @param id - UUID del {nombre}
|
|
* @returns {Nombre} encontrado
|
|
* @throws NotFoundException si no existe
|
|
*/
|
|
async findOne(id: string): Promise<{Nombre}Entity> {
|
|
const entity = await this.repository.findOne({
|
|
where: { id },
|
|
});
|
|
|
|
if (!entity) {
|
|
throw new NotFoundException(`{Nombre} with ID ${id} not found`);
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
/**
|
|
* Actualiza un {nombre}
|
|
*
|
|
* @param id - UUID del {nombre}
|
|
* @param dto - Datos a actualizar
|
|
* @returns {Nombre} actualizado
|
|
* @throws NotFoundException si no existe
|
|
*/
|
|
async update(id: string, dto: Update{Nombre}Dto): Promise<{Nombre}Entity> {
|
|
const entity = await this.findOne(id);
|
|
Object.assign(entity, dto);
|
|
return await this.repository.save(entity);
|
|
}
|
|
|
|
/**
|
|
* Elimina un {nombre}
|
|
*
|
|
* @param id - UUID del {nombre}
|
|
* @throws NotFoundException si no existe
|
|
*/
|
|
async remove(id: string): Promise<void> {
|
|
const entity = await this.findOne(id);
|
|
await this.repository.remove(entity);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Controller
|
|
|
|
```typescript
|
|
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Delete,
|
|
Body,
|
|
Param,
|
|
ParseUUIDPipe,
|
|
HttpCode,
|
|
HttpStatus,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import {
|
|
ApiTags,
|
|
ApiOperation,
|
|
ApiResponse,
|
|
ApiBearerAuth,
|
|
ApiParam,
|
|
} from '@nestjs/swagger';
|
|
import { {Nombre}Service } from '../services/{nombre}.service';
|
|
import { {Nombre}Entity } from '../entities/{nombre}.entity';
|
|
import { Create{Nombre}Dto } from '../dto/create-{nombre}.dto';
|
|
import { Update{Nombre}Dto } from '../dto/update-{nombre}.dto';
|
|
import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard';
|
|
|
|
/**
|
|
* Controller para gestión de {nombre}
|
|
*
|
|
* Endpoints:
|
|
* - GET /{nombre} - Listar todos
|
|
* - GET /{nombre}/:id - Obtener por ID
|
|
* - POST /{nombre} - Crear nuevo
|
|
* - PUT /{nombre}/:id - Actualizar
|
|
* - DELETE /{nombre}/:id - Eliminar
|
|
*/
|
|
@ApiTags('{Nombre}')
|
|
@Controller('{nombre}')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth()
|
|
export class {Nombre}Controller {
|
|
constructor(private readonly service: {Nombre}Service) {}
|
|
|
|
/**
|
|
* Lista todos los {nombre}
|
|
*/
|
|
@Get()
|
|
@ApiOperation({ summary: 'Listar todos los {nombre}' })
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: 'Lista de {nombre}',
|
|
type: [{Nombre}Entity],
|
|
})
|
|
async findAll(): Promise<{Nombre}Entity[]> {
|
|
return await this.service.findAll();
|
|
}
|
|
|
|
/**
|
|
* Obtiene un {nombre} por ID
|
|
*/
|
|
@Get(':id')
|
|
@ApiOperation({ summary: 'Obtener {nombre} por ID' })
|
|
@ApiParam({ name: 'id', description: 'UUID del {nombre}' })
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: '{Nombre} encontrado',
|
|
type: {Nombre}Entity,
|
|
})
|
|
@ApiResponse({ status: 404, description: '{Nombre} no encontrado' })
|
|
async findOne(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
): Promise<{Nombre}Entity> {
|
|
return await this.service.findOne(id);
|
|
}
|
|
|
|
/**
|
|
* Crea un nuevo {nombre}
|
|
*/
|
|
@Post()
|
|
@HttpCode(HttpStatus.CREATED)
|
|
@ApiOperation({ summary: 'Crear nuevo {nombre}' })
|
|
@ApiResponse({
|
|
status: 201,
|
|
description: '{Nombre} creado',
|
|
type: {Nombre}Entity,
|
|
})
|
|
@ApiResponse({ status: 400, description: 'Datos inválidos' })
|
|
@ApiResponse({ status: 409, description: 'Conflicto (ya existe)' })
|
|
async create(@Body() dto: Create{Nombre}Dto): Promise<{Nombre}Entity> {
|
|
return await this.service.create(dto);
|
|
}
|
|
|
|
/**
|
|
* Actualiza un {nombre}
|
|
*/
|
|
@Put(':id')
|
|
@ApiOperation({ summary: 'Actualizar {nombre}' })
|
|
@ApiParam({ name: 'id', description: 'UUID del {nombre}' })
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: '{Nombre} actualizado',
|
|
type: {Nombre}Entity,
|
|
})
|
|
@ApiResponse({ status: 404, description: '{Nombre} no encontrado' })
|
|
async update(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: Update{Nombre}Dto,
|
|
): Promise<{Nombre}Entity> {
|
|
return await this.service.update(id, dto);
|
|
}
|
|
|
|
/**
|
|
* Elimina un {nombre}
|
|
*/
|
|
@Delete(':id')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
@ApiOperation({ summary: 'Eliminar {nombre}' })
|
|
@ApiParam({ name: 'id', description: 'UUID del {nombre}' })
|
|
@ApiResponse({ status: 204, description: '{Nombre} eliminado' })
|
|
@ApiResponse({ status: 404, description: '{Nombre} no encontrado' })
|
|
async remove(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
|
return await this.service.remove(id);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Module
|
|
|
|
```typescript
|
|
import { Module } from '@nestjs/common';
|
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
import { {Nombre}Entity } from './entities/{nombre}.entity';
|
|
import { {Nombre}Service } from './services/{nombre}.service';
|
|
import { {Nombre}Controller } from './controllers/{nombre}.controller';
|
|
|
|
@Module({
|
|
imports: [TypeOrmModule.forFeature([{Nombre}Entity])],
|
|
controllers: [{Nombre}Controller],
|
|
providers: [{Nombre}Service],
|
|
exports: [{Nombre}Service],
|
|
})
|
|
export class {Nombre}Module {}
|
|
```
|
|
|
|
---
|
|
|
|
## VALIDACIONES OBLIGATORIAS
|
|
|
|
```bash
|
|
# 1. Build (OBLIGATORIO)
|
|
cd @BACKEND_ROOT
|
|
npm run build
|
|
# ✅ Debe completar sin errores
|
|
|
|
# 2. Lint (OBLIGATORIO)
|
|
npm run lint
|
|
# ✅ Debe pasar
|
|
|
|
# 3. Tests (si existen)
|
|
npm run test
|
|
# ✅ Deben pasar
|
|
|
|
# 4. Iniciar aplicación
|
|
npm run start:dev
|
|
# ✅ Debe iniciar sin errores
|
|
|
|
# 5. Verificar endpoint
|
|
curl http://localhost:3000/api/{recurso}
|
|
# ✅ Debe responder correctamente
|
|
```
|
|
|
|
---
|
|
|
|
## CHECKLIST BACKEND
|
|
|
|
```
|
|
ENTITY
|
|
├── [ ] Mapea correctamente a tabla DDL
|
|
├── [ ] Tipos coinciden (UUID, VARCHAR, etc.)
|
|
├── [ ] Nombres de columnas idénticos a DDL
|
|
├── [ ] Relaciones (@ManyToOne, @OneToMany) correctas
|
|
├── [ ] Decoradores TypeORM completos
|
|
└── [ ] JSDoc con referencia a DDL
|
|
|
|
DTO
|
|
├── [ ] Validaciones class-validator
|
|
├── [ ] Decoradores Swagger (@ApiProperty)
|
|
├── [ ] Tipos alineados con Entity
|
|
├── [ ] UpdateDto extiende PartialType(CreateDto)
|
|
└── [ ] Ejemplos en Swagger
|
|
|
|
SERVICE
|
|
├── [ ] Inyección de repositorio
|
|
├── [ ] Métodos CRUD implementados
|
|
├── [ ] Manejo de errores (NotFoundException, etc.)
|
|
├── [ ] JSDoc en métodos públicos
|
|
└── [ ] Lógica de negocio documentada
|
|
|
|
CONTROLLER
|
|
├── [ ] Decoradores Swagger completos
|
|
├── [ ] Guards de autenticación
|
|
├── [ ] ParseUUIDPipe en parámetros UUID
|
|
├── [ ] HttpCode correcto por método
|
|
└── [ ] JSDoc en endpoints
|
|
|
|
MODULE
|
|
├── [ ] TypeOrmModule.forFeature con entities
|
|
├── [ ] Controllers registrados
|
|
├── [ ] Providers registrados
|
|
└── [ ] Exports si se usa en otros módulos
|
|
|
|
VALIDACIÓN
|
|
├── [ ] npm run build pasa
|
|
├── [ ] npm run lint pasa
|
|
├── [ ] npm run test pasa
|
|
├── [ ] Aplicación inicia
|
|
└── [ ] Endpoints responden
|
|
```
|
|
|
|
---
|
|
|
|
## ERRORES COMUNES
|
|
|
|
| Error | Causa | Solución |
|
|
|-------|-------|----------|
|
|
| Entity no mapea | Schema/tabla incorrectos | Verificar @Entity({ schema, name }) |
|
|
| Tipos no coinciden | DDL vs TypeScript | Alinear tipos exactamente |
|
|
| Circular dependency | Imports cruzados | Usar forwardRef() |
|
|
| Swagger incompleto | Faltan decoradores | Agregar @ApiProperty, @ApiOperation |
|
|
| Build falla | Errores TypeScript | Corregir antes de continuar |
|
|
|
|
---
|
|
|
|
## REFERENCIAS
|
|
|
|
- **Crear archivos:** @CREAR (SIMCO-CREAR.md)
|
|
- **Validar:** @VALIDAR (SIMCO-VALIDAR.md)
|
|
- **DDL:** @OP_DDL (SIMCO-DDL.md)
|
|
- **Guías Backend:** @GUIAS_BE
|
|
- **Nomenclatura:** @DIRECTIVAS/ESTANDARES-NOMENCLATURA-BASE.md
|
|
|
|
---
|
|
|
|
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Mantenido por:** Tech Lead
|