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
16 KiB
16 KiB
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
// 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
// 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
// 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)
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)
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)
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
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
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
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
# 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