workspace/projects/gamilit/docs/95-guias-desarrollo/backend/DTO-CONVENTIONS.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- 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>
2025-12-08 10:44:23 -06:00

10 KiB

Convenciones de DTOs - Backend GAMILIT

Fecha de creacion: 2025-11-29 Version: 1.0 Estado: VIGENTE Contexto: Estandarizacion de DTOs para eliminar duplicaciones


1. Principios Fundamentales

1.1 Proposito de los DTOs

  • Data Transfer Objects: Transportar datos entre capas
  • Validacion: Aplicar reglas de validacion con class-validator
  • Documentacion API: Generar schemas Swagger con @nestjs/swagger
  • Transformacion: Convertir/serializar datos con class-transformer

1.2 NO Duplicar

Prohibido:

  • Crear DTOs con estructura identica a otros existentes
  • Copiar campos entre DTOs sin usar herencia/composicion

Permitido:

  • Extender DTOs base (extends)
  • Usar utility types (PartialType, OmitType, PickType)
  • Crear DTOs genericos reutilizables

2. DTOs Genericos Base

2.1 PaginatedResponseDto

Ubicacion: /shared/dto/common/paginated-response.dto.ts

import { ApiProperty } from '@nestjs/swagger';

export class PaginatedResponseDto<T> {
  @ApiProperty({ description: 'Array de elementos', isArray: true })
  data!: T[];

  @ApiProperty({ description: 'Total de elementos', example: 100 })
  total!: number;

  @ApiProperty({ description: 'Pagina actual', example: 1 })
  page!: number;

  @ApiProperty({ description: 'Elementos por pagina', example: 10 })
  limit!: number;

  @ApiProperty({ description: 'Total de paginas', example: 10 })
  total_pages!: number;
}

Uso:

import { PaginatedResponseDto } from '@shared/dto/common';

// Extender para tipo especifico
export class PaginatedUsersDto extends PaginatedResponseDto<UserDetailsDto> {}

// O usar directamente en controller
@ApiOkResponse({ type: () => PaginatedResponseDto<UserDetailsDto> })
async getUsers(): Promise<PaginatedResponseDto<UserDetailsDto>> { ... }

2.2 BaseResponseDto

Ubicacion: /shared/dto/common/base-response.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { Expose, Type } from 'class-transformer';
import { Exclude } from 'class-transformer';

@Exclude()
export class BaseResponseDto {
  @Expose()
  @ApiProperty({ description: 'ID unico', example: 'uuid-here' })
  id!: string;

  @Expose()
  @Type(() => Date)
  @ApiProperty({ description: 'Fecha de creacion' })
  created_at!: Date;

  @Expose()
  @Type(() => Date)
  @ApiProperty({ description: 'Fecha de actualizacion' })
  updated_at!: Date;
}

Uso:

import { BaseResponseDto } from '@shared/dto/common';

export class UserResponseDto extends BaseResponseDto {
  @Expose()
  @ApiProperty()
  email!: string;

  @Expose()
  @ApiProperty()
  username!: string;
}

3. Estructura de Carpetas

3.1 DTOs por Modulo

modules/[moduleName]/
└── dto/
    ├── index.ts                    # Barrel export
    ├── create-[entity].dto.ts      # DTO para crear
    ├── update-[entity].dto.ts      # DTO para actualizar
    ├── [entity]-response.dto.ts    # DTO de respuesta
    ├── [entity]-filters.dto.ts     # DTO para filtros/query
    └── [subdominio]/               # Subdominios si hay muchos
        ├── index.ts
        └── *.dto.ts

3.2 DTOs Compartidos

shared/
└── dto/
    ├── common/
    │   ├── index.ts
    │   ├── paginated-response.dto.ts
    │   └── base-response.dto.ts
    ├── auth/
    │   └── *.dto.ts
    └── notifications/
        └── *.dto.ts

4. Convenciones de Nombrado

4.1 Nombres de Archivos

Tipo Patron Ejemplo
Crear create-[entity].dto.ts create-user.dto.ts
Actualizar update-[entity].dto.ts update-user.dto.ts
Respuesta [entity]-response.dto.ts user-response.dto.ts
Filtros [entity]-filters.dto.ts user-filters.dto.ts
Paginado paginated-[entities].dto.ts paginated-users.dto.ts
Listado list-[entities].dto.ts list-users.dto.ts

4.2 Nombres de Clases

Tipo Patron Ejemplo
Crear Create[Entity]Dto CreateUserDto
Actualizar Update[Entity]Dto UpdateUserDto
Respuesta [Entity]ResponseDto UserResponseDto
Detalles [Entity]DetailsDto UserDetailsDto

4.3 Campos (snake_case para DB)

// Campos que mapean a base de datos: snake_case
export class UserResponseDto {
  user_id!: string;        // snake_case (DB)
  created_at!: Date;       // snake_case (DB)
  total_xp!: number;       // snake_case (DB)
}

// Campos calculados o de UI: camelCase
export class UserFiltersDto {
  searchQuery?: string;    // camelCase (no DB)
  sortOrder?: string;      // camelCase (no DB)
}

5. Decoradores de Validacion

5.1 Decoradores Requeridos

import {
  IsString, IsEmail, IsNotEmpty, IsOptional,
  IsUUID, IsEnum, IsBoolean, IsNumber,
  MinLength, MaxLength, Min, Max,
  IsArray, ArrayNotEmpty, ValidateNested
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';

5.2 Patron Estandar

export class CreateUserDto {
  // Campo requerido
  @ApiProperty({ description: 'Email del usuario', example: 'user@example.com' })
  @IsEmail({}, { message: 'Formato de email invalido' })
  @IsNotEmpty({ message: 'Email es requerido' })
  email!: string;

  // Campo opcional
  @ApiPropertyOptional({ description: 'Nombre del usuario' })
  @IsOptional()
  @IsString()
  @MaxLength(100)
  name?: string;

  // Campo enum
  @ApiProperty({ enum: UserRoleEnum, description: 'Rol del usuario' })
  @IsEnum(UserRoleEnum, { message: 'Rol invalido' })
  role!: UserRoleEnum;

  // Campo nested
  @ApiProperty({ type: () => AddressDto })
  @ValidateNested()
  @Type(() => AddressDto)
  address!: AddressDto;
}

5.3 Mensajes de Error

Siempre incluir mensajes personalizados:

@IsNotEmpty({ message: 'El campo {property} es requerido' })
@MinLength(8, { message: 'La contrasena debe tener al menos 8 caracteres' })
@IsEnum(Status, { message: 'El estado debe ser uno de: {constraints}' })

6. Utility Types de NestJS

6.1 PartialType

import { PartialType } from '@nestjs/mapped-types';

// Todos los campos de CreateUserDto se vuelven opcionales
export class UpdateUserDto extends PartialType(CreateUserDto) {}

6.2 OmitType

import { OmitType } from '@nestjs/mapped-types';

// Excluye campos especificos
export class UpdatePasswordDto extends OmitType(CreateUserDto, ['email', 'role']) {}

6.3 PickType

import { PickType } from '@nestjs/mapped-types';

// Solo incluye campos especificos
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}

6.4 Combinaciones

// Combinar: Parcial sin password
export class UpdateUserDto extends PartialType(
  OmitType(CreateUserDto, ['password'])
) {}

7. Serializacion con class-transformer

7.1 Expose/Exclude

import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class UserResponseDto {
  @Expose()
  id!: string;

  @Expose()
  email!: string;

  // No expuesto, nunca se serializa
  password!: string;
}

7.2 Transform

import { Transform } from 'class-transformer';

export class UserResponseDto {
  @Expose()
  @Transform(({ value }) => value?.toLowerCase())
  email!: string;

  @Expose()
  @Transform(({ value }) => new Date(value).toISOString())
  created_at!: string;
}

8. Barrel Exports

8.1 index.ts de Modulo

// modules/auth/dto/index.ts
export * from './create-user.dto';
export * from './update-user.dto';
export * from './user-response.dto';
export * from './login.dto';

8.2 index.ts de Shared

// shared/dto/common/index.ts
export * from './paginated-response.dto';
export * from './base-response.dto';

9. Mapeo DTO <-> Entity <-> Frontend

9.1 Flujo de Datos

Frontend Request
      ↓
  [CreateUserDto] ← Validacion + Transformacion
      ↓
  [UserEntity]    ← Logica de negocio
      ↓
  [UserResponseDto] ← Serializacion
      ↓
Frontend Response

9.2 Sincronizacion con Frontend

Los DTOs de respuesta deben coincidir con los types del frontend:

// Backend: UserResponseDto
export class UserResponseDto {
  id!: string;
  email!: string;
  created_at!: Date;
}

// Frontend: User type (debe coincidir)
interface User {
  id: string;
  email: string;
  created_at: string; // Date serializada
}

Herramienta de sincronizacion:

  • npm run generate:api-types genera types desde OpenAPI
  • Frontend debe usar /generated/api-types.ts como SSOT

10. Anti-patrones a Evitar

10.1 NO: DTOs Duplicados

// MAL: 8 DTOs con misma estructura
class PaginatedUsersDto { data, total, page, limit, total_pages }
class PaginatedAlertsDto { data, total, page, limit, total_pages }
// ... 6 mas

// BIEN: Usar generico
class PaginatedUsersDto extends PaginatedResponseDto<UserDto> {}

10.2 NO: Validaciones Inconsistentes

// MAL: Regex diferente en cada DTO
@Matches(/^[a-z]+$/)  // en CreateUserDto
@Matches(/^[A-Za-z]+$/)  // en UpdateUserDto

// BIEN: Constante compartida
const USERNAME_REGEX = /^[a-zA-Z0-9_]+$/;
@Matches(USERNAME_REGEX)

10.3 NO: Campos sin Decoradores

// MAL: Sin decoradores
export class CreateUserDto {
  email: string;  // Sin validacion
}

// BIEN: Siempre decorar
export class CreateUserDto {
  @ApiProperty()
  @IsEmail()
  @IsNotEmpty()
  email!: string;
}

11. Checklist de Creacion de DTO

Antes de crear un nuevo DTO:

  1. Verificar que no existe DTO similar
  2. Determinar si puede extender DTO base
  3. Usar nombres segun convenciones
  4. Agregar todos los decoradores necesarios
  5. Incluir mensajes de error personalizados
  6. Exportar en barrel file (index.ts)
  7. Documentar con JSDoc si es complejo
  8. Actualizar BACKEND_INVENTORY.yml

12. Referencias


13. Changelog

Version Fecha Cambios
1.0 2025-11-29 Creacion inicial - Plan de estandarizacion