feat(validators): agregar validadores con mensajes en español a 12 DTOs adicionales

Se completaron validadores en los siguientes DTOs:
- storage: GetUploadUrlDto, ConfirmUploadDto, ListFilesDto, UpdateFileDto
- billing: CreatePaymentMethodDto, CreateCheckoutSessionDto, CreateBillingPortalSessionDto
- email: EmailAddressDto, AttachmentDto, SendEmailDto, SendTemplateEmailDto, BulkSendEmailDto
- rbac: CreateRoleDto, UpdateRoleDto, AssignRoleDto
- sales: CreateActivityDto, UpdateActivityDto, ActivityListQueryDto
- sales: CreatePipelineStageDto, UpdatePipelineStageDto, ReorderStagesDto
- commissions: CreateAssignmentDto, UpdateAssignmentDto, AssignmentListQueryDto
- commissions: CreatePeriodDto, UpdatePeriodDto, ClosePeriodDto, MarkPaidDto, PeriodListQueryDto
- portfolio: CreateCategoryDto, UpdateCategoryDto, CategoryListQueryDto
- ai: ChatMessageDto, ChatRequestDto
- whatsapp: CreateWhatsAppConfigDto, UpdateWhatsAppConfigDto, TestConnectionDto

Validadores agregados:
- @IsNotEmpty() con mensajes descriptivos en español
- @MaxLength() en campos de texto
- @Min()/@Max() en campos numéricos y porcentajes
- @IsUUID() en campos de identificadores
- @IsEmail() en campos de email
- @IsUrl() en campos de URLs
- @IsEnum() para valores fijos
- @Matches() para patrones específicos (colores hex, códigos de moneda, slugs)
- @ArrayMaxSize() en arreglos

Build y lint exitosos.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 17:31:56 -06:00
parent c4262498ee
commit 2bbf07405b
12 changed files with 427 additions and 260 deletions

View File

@ -4,8 +4,12 @@ import {
IsArray,
IsOptional,
IsNumber,
IsBoolean,
IsNotEmpty,
Min,
Max,
MaxLength,
ArrayMaxSize,
ValidateNested,
IsIn,
} from 'class-validator';
@ -13,50 +17,55 @@ import { Type } from 'class-transformer';
export class ChatMessageDto {
@ApiProperty({ description: 'Message role', enum: ['system', 'user', 'assistant'] })
@IsString()
@IsIn(['system', 'user', 'assistant'])
@IsString({ message: 'El rol debe ser texto' })
@IsIn(['system', 'user', 'assistant'], { message: 'El rol debe ser system, user o assistant' })
role: 'system' | 'user' | 'assistant';
@ApiProperty({ description: 'Message content' })
@IsString()
@IsString({ message: 'El contenido del mensaje debe ser texto' })
@IsNotEmpty({ message: 'El contenido del mensaje es requerido' })
@MaxLength(100000, { message: 'El contenido del mensaje no debe exceder 100,000 caracteres' })
content: string;
}
export class ChatRequestDto {
@ApiProperty({ description: 'Array of chat messages', type: [ChatMessageDto] })
@IsArray()
@IsArray({ message: 'Los mensajes deben ser un arreglo' })
@ArrayMaxSize(100, { message: 'No se pueden enviar más de 100 mensajes a la vez' })
@ValidateNested({ each: true })
@Type(() => ChatMessageDto)
messages: ChatMessageDto[];
@ApiPropertyOptional({ description: 'Model to use (e.g., anthropic/claude-3-haiku)' })
@IsOptional()
@IsString()
@IsString({ message: 'El modelo debe ser texto' })
@MaxLength(100, { message: 'El nombre del modelo no debe exceder 100 caracteres' })
model?: string;
@ApiPropertyOptional({ description: 'Temperature (0-2)', default: 0.7 })
@IsOptional()
@IsNumber()
@Min(0)
@Max(2)
@IsNumber({}, { message: 'La temperatura debe ser un número' })
@Min(0, { message: 'La temperatura debe ser mayor o igual a 0' })
@Max(2, { message: 'La temperatura no debe exceder 2' })
temperature?: number;
@ApiPropertyOptional({ description: 'Maximum tokens to generate', default: 2048 })
@IsOptional()
@IsNumber()
@Min(1)
@Max(32000)
@IsNumber({}, { message: 'El máximo de tokens debe ser un número' })
@Min(1, { message: 'El máximo de tokens debe ser mayor o igual a 1' })
@Max(32000, { message: 'El máximo de tokens no debe exceder 32,000' })
max_tokens?: number;
@ApiPropertyOptional({ description: 'Top P sampling (0-1)', default: 1.0 })
@IsOptional()
@IsNumber()
@Min(0)
@Max(1)
@IsNumber({}, { message: 'Top P debe ser un número' })
@Min(0, { message: 'Top P debe ser mayor o igual a 0' })
@Max(1, { message: 'Top P no debe exceder 1' })
top_p?: number;
@ApiPropertyOptional({ description: 'Stream response', default: false })
@IsOptional()
@IsBoolean({ message: 'El indicador de streaming debe ser verdadero o falso' })
stream?: boolean;
}

View File

@ -1,43 +1,53 @@
import { IsEnum, IsOptional, IsString, IsBoolean } from 'class-validator';
import { IsEnum, IsOptional, IsString, IsBoolean, IsNumber, Min, Max, MaxLength, Length } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { PaymentMethodType } from '../entities/payment-method.entity';
export class CreatePaymentMethodDto {
@ApiProperty({ description: 'Payment method type', enum: PaymentMethodType })
@IsEnum(PaymentMethodType)
@IsEnum(PaymentMethodType, { message: 'El tipo de método de pago debe ser válido' })
type: PaymentMethodType;
@ApiPropertyOptional({ description: 'Set as default payment method' })
@IsOptional()
@IsBoolean()
@IsBoolean({ message: 'El campo por defecto debe ser verdadero o falso' })
is_default?: boolean;
@ApiPropertyOptional({ description: 'External payment method ID from provider' })
@IsOptional()
@IsString()
@IsString({ message: 'El ID externo del método de pago debe ser texto' })
@MaxLength(255, { message: 'El ID externo no debe exceder 255 caracteres' })
external_payment_method_id?: string;
@ApiPropertyOptional({ description: 'Payment provider (stripe, conekta, etc)' })
@IsOptional()
@IsString()
@IsString({ message: 'El proveedor de pago debe ser texto' })
@MaxLength(50, { message: 'El proveedor de pago no debe exceder 50 caracteres' })
payment_provider?: string;
// Card details (when adding a card)
@ApiPropertyOptional({ description: 'Last 4 digits of card' })
@IsOptional()
@IsString()
@IsString({ message: 'Los últimos 4 dígitos deben ser texto' })
@Length(4, 4, { message: 'Los últimos 4 dígitos de la tarjeta deben ser exactamente 4 caracteres' })
card_last_four?: string;
@ApiPropertyOptional({ description: 'Card brand (visa, mastercard, etc)' })
@IsOptional()
@IsString()
@IsString({ message: 'La marca de la tarjeta debe ser texto' })
@MaxLength(30, { message: 'La marca de la tarjeta no debe exceder 30 caracteres' })
card_brand?: string;
@ApiPropertyOptional({ description: 'Card expiry month' })
@IsOptional()
@IsNumber({}, { message: 'El mes de expiración debe ser un número' })
@Min(1, { message: 'El mes de expiración debe ser entre 1 y 12' })
@Max(12, { message: 'El mes de expiración debe ser entre 1 y 12' })
card_exp_month?: number;
@ApiPropertyOptional({ description: 'Card expiry year' })
@IsOptional()
@IsNumber({}, { message: 'El año de expiración debe ser un número' })
@Min(2024, { message: 'El año de expiración debe ser válido' })
@Max(2100, { message: 'El año de expiración debe ser válido' })
card_exp_year?: number;
}

View File

@ -1,4 +1,4 @@
import { IsString, IsOptional, IsObject, IsNumber, IsEnum, IsBoolean } from 'class-validator';
import { IsString, IsOptional, IsObject, IsNumber, IsEnum, IsBoolean, IsNotEmpty, IsUrl, IsEmail, Min, Max, MaxLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export enum StripeWebhookEventType {
@ -35,104 +35,132 @@ export enum StripeWebhookEventType {
export class StripeWebhookDto {
@ApiProperty({ description: 'Webhook event ID from Stripe' })
@IsString()
@IsString({ message: 'El ID del evento debe ser texto' })
@IsNotEmpty({ message: 'El ID del evento de Stripe es requerido' })
id: string;
@ApiProperty({ description: 'Event type', enum: StripeWebhookEventType })
@IsString()
@IsString({ message: 'El tipo de evento debe ser texto' })
@IsNotEmpty({ message: 'El tipo de evento es requerido' })
type: string;
@ApiProperty({ description: 'Event data object' })
@IsObject()
@IsObject({ message: 'Los datos del evento deben ser un objeto' })
data: {
object: Record<string, any>;
previous_attributes?: Record<string, any>;
};
@ApiProperty({ description: 'API version used' })
@IsString()
@IsString({ message: 'La versión de API debe ser texto' })
api_version: string;
@ApiProperty({ description: 'Unix timestamp of event creation' })
@IsNumber()
@IsNumber({}, { message: 'El timestamp de creación debe ser un número' })
@Min(0, { message: 'El timestamp debe ser mayor o igual a 0' })
created: number;
@ApiPropertyOptional({ description: 'Whether this is a live mode event' })
@IsOptional()
@IsBoolean()
@IsBoolean({ message: 'El modo live debe ser verdadero o falso' })
livemode?: boolean;
}
export class CreateStripeCustomerDto {
@ApiProperty({ description: 'Tenant ID to link customer' })
@IsString()
@IsString({ message: 'El ID del tenant debe ser texto' })
@IsNotEmpty({ message: 'El ID del tenant es requerido' })
@MaxLength(100, { message: 'El ID del tenant no debe exceder 100 caracteres' })
tenant_id: string;
@ApiProperty({ description: 'Customer email' })
@IsString()
@IsEmail({}, { message: 'El email del cliente debe tener un formato válido' })
@IsNotEmpty({ message: 'El email del cliente es requerido' })
@MaxLength(255, { message: 'El email no debe exceder 255 caracteres' })
email: string;
@ApiPropertyOptional({ description: 'Customer name' })
@IsOptional()
@IsString()
@IsString({ message: 'El nombre del cliente debe ser texto' })
@MaxLength(255, { message: 'El nombre no debe exceder 255 caracteres' })
name?: string;
@ApiPropertyOptional({ description: 'Additional metadata' })
@IsOptional()
@IsObject()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, string>;
}
export class CreateStripeSubscriptionDto {
@ApiProperty({ description: 'Stripe customer ID' })
@IsString()
@IsString({ message: 'El ID del cliente de Stripe debe ser texto' })
@IsNotEmpty({ message: 'El ID del cliente de Stripe es requerido' })
@MaxLength(100, { message: 'El ID del cliente no debe exceder 100 caracteres' })
customer_id: string;
@ApiProperty({ description: 'Stripe price ID' })
@IsString()
@IsString({ message: 'El ID del precio de Stripe debe ser texto' })
@IsNotEmpty({ message: 'El ID del precio de Stripe es requerido' })
@MaxLength(100, { message: 'El ID del precio no debe exceder 100 caracteres' })
price_id: string;
@ApiPropertyOptional({ description: 'Trial period in days' })
@IsOptional()
@IsNumber()
@IsNumber({}, { message: 'Los días de prueba deben ser un número' })
@Min(0, { message: 'Los días de prueba deben ser mayor o igual a 0' })
@Max(365, { message: 'Los días de prueba no deben exceder 365' })
trial_period_days?: number;
@ApiPropertyOptional({ description: 'Additional metadata' })
@IsOptional()
@IsObject()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, string>;
}
export class CreateCheckoutSessionDto {
@ApiProperty({ description: 'Tenant ID' })
@IsString()
@IsString({ message: 'El ID del tenant debe ser texto' })
@IsNotEmpty({ message: 'El ID del tenant es requerido' })
@MaxLength(100, { message: 'El ID del tenant no debe exceder 100 caracteres' })
tenant_id: string;
@ApiProperty({ description: 'Stripe price ID' })
@IsString()
@IsString({ message: 'El ID del precio de Stripe debe ser texto' })
@IsNotEmpty({ message: 'El ID del precio de Stripe es requerido' })
@MaxLength(100, { message: 'El ID del precio no debe exceder 100 caracteres' })
price_id: string;
@ApiProperty({ description: 'Success redirect URL' })
@IsString()
@IsUrl({}, { message: 'La URL de éxito debe ser una URL válida' })
@IsNotEmpty({ message: 'La URL de éxito es requerida' })
@MaxLength(2000, { message: 'La URL de éxito no debe exceder 2000 caracteres' })
success_url: string;
@ApiProperty({ description: 'Cancel redirect URL' })
@IsString()
@IsUrl({}, { message: 'La URL de cancelación debe ser una URL válida' })
@IsNotEmpty({ message: 'La URL de cancelación es requerida' })
@MaxLength(2000, { message: 'La URL de cancelación no debe exceder 2000 caracteres' })
cancel_url: string;
@ApiPropertyOptional({ description: 'Trial period in days' })
@IsOptional()
@IsNumber()
@IsNumber({}, { message: 'Los días de prueba deben ser un número' })
@Min(0, { message: 'Los días de prueba deben ser mayor o igual a 0' })
@Max(365, { message: 'Los días de prueba no deben exceder 365' })
trial_period_days?: number;
}
export class CreateBillingPortalSessionDto {
@ApiProperty({ description: 'Tenant ID' })
@IsString()
@IsString({ message: 'El ID del tenant debe ser texto' })
@IsNotEmpty({ message: 'El ID del tenant es requerido' })
@MaxLength(100, { message: 'El ID del tenant no debe exceder 100 caracteres' })
tenant_id: string;
@ApiProperty({ description: 'Return URL after portal session' })
@IsString()
@IsUrl({}, { message: 'La URL de retorno debe ser una URL válida' })
@IsNotEmpty({ message: 'La URL de retorno es requerida' })
@MaxLength(2000, { message: 'La URL de retorno no debe exceder 2000 caracteres' })
return_url: string;
}

View File

@ -4,52 +4,53 @@ import {
IsNumber,
IsBoolean,
IsDateString,
IsInt,
Min,
Max,
} from 'class-validator';
export class CreateAssignmentDto {
@IsUUID()
@IsUUID('4', { message: 'El ID de usuario debe ser un UUID válido' })
userId: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de esquema debe ser un UUID válido' })
schemeId: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de inicio debe ser una fecha válida ISO 8601' })
@IsOptional()
startsAt?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de fin debe ser una fecha válida ISO 8601' })
@IsOptional()
endsAt?: string;
@IsNumber()
@Min(0)
@Max(100)
@IsNumber({}, { message: 'La tasa personalizada debe ser un número' })
@Min(0, { message: 'La tasa personalizada debe ser mayor o igual a 0%' })
@Max(100, { message: 'La tasa personalizada no debe exceder 100%' })
@IsOptional()
customRate?: number;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
}
export class UpdateAssignmentDto {
@IsDateString()
@IsDateString({}, { message: 'La fecha de inicio debe ser una fecha válida ISO 8601' })
@IsOptional()
startsAt?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de fin debe ser una fecha válida ISO 8601' })
@IsOptional()
endsAt?: string;
@IsNumber()
@Min(0)
@Max(100)
@IsNumber({}, { message: 'La tasa personalizada debe ser un número' })
@Min(0, { message: 'La tasa personalizada debe ser mayor o igual a 0%' })
@Max(100, { message: 'La tasa personalizada no debe exceder 100%' })
@IsOptional()
customRate?: number;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
}
@ -70,21 +71,26 @@ export class AssignmentResponseDto {
}
export class AssignmentListQueryDto {
@IsUUID()
@IsUUID('4', { message: 'El ID de usuario debe ser un UUID válido' })
@IsOptional()
userId?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de esquema debe ser un UUID válido' })
@IsOptional()
schemeId?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
@IsInt({ message: 'La página debe ser un número entero' })
@Min(1, { message: 'La página debe ser mayor o igual a 1' })
@IsOptional()
page?: number;
@IsInt({ message: 'El límite debe ser un número entero' })
@Min(1, { message: 'El límite debe ser mayor o igual a 1' })
@Max(100, { message: 'El límite no debe exceder 100 elementos' })
@IsOptional()
limit?: number;
}

View File

@ -3,55 +3,64 @@ import {
IsOptional,
IsEnum,
IsDateString,
IsInt,
Min,
Max,
MaxLength,
IsNotEmpty,
Matches,
} from 'class-validator';
import { PeriodStatus } from '../entities';
export class CreatePeriodDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@IsNotEmpty({ message: 'El nombre del período es requerido' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
name: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de inicio debe ser una fecha válida ISO 8601' })
startsAt: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de fin debe ser una fecha válida ISO 8601' })
endsAt: string;
@IsString()
@MaxLength(3)
@IsString({ message: 'La moneda debe ser texto' })
@MaxLength(3, { message: 'El código de moneda debe ser de 3 caracteres (ej: USD, MXN)' })
@Matches(/^[A-Z]{3}$/, { message: 'El código de moneda debe ser de 3 letras mayúsculas (ej: USD, MXN)' })
@IsOptional()
currency?: string;
}
export class UpdatePeriodDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
@IsOptional()
name?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de inicio debe ser una fecha válida ISO 8601' })
@IsOptional()
startsAt?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de fin debe ser una fecha válida ISO 8601' })
@IsOptional()
endsAt?: string;
}
export class ClosePeriodDto {
@IsString()
@IsString({ message: 'Las notas deben ser texto' })
@MaxLength(1000, { message: 'Las notas no deben exceder 1000 caracteres' })
@IsOptional()
notes?: string;
}
export class MarkPaidDto {
@IsString()
@MaxLength(255)
@IsString({ message: 'La referencia de pago debe ser texto' })
@MaxLength(255, { message: 'La referencia de pago no debe exceder 255 caracteres' })
@IsOptional()
paymentReference?: string;
@IsString()
@IsString({ message: 'Las notas de pago deben ser texto' })
@MaxLength(1000, { message: 'Las notas de pago no deben exceder 1000 caracteres' })
@IsOptional()
paymentNotes?: string;
}
@ -77,13 +86,18 @@ export class PeriodResponseDto {
}
export class PeriodListQueryDto {
@IsEnum(PeriodStatus)
@IsEnum(PeriodStatus, { message: 'El estado del período debe ser válido' })
@IsOptional()
status?: PeriodStatus;
@IsInt({ message: 'La página debe ser un número entero' })
@Min(1, { message: 'La página debe ser mayor o igual a 1' })
@IsOptional()
page?: number;
@IsInt({ message: 'El límite debe ser un número entero' })
@Min(1, { message: 'El límite debe ser mayor o igual a 1' })
@Max(100, { message: 'El límite no debe exceder 100 elementos' })
@IsOptional()
limit?: number;
}

View File

@ -1,24 +1,31 @@
import { IsString, IsEmail, IsOptional, IsArray, ValidateNested, IsObject } from 'class-validator';
import { IsString, IsEmail, IsOptional, IsArray, ValidateNested, IsObject, IsNotEmpty, MaxLength, ArrayMaxSize } from 'class-validator';
import { Type } from 'class-transformer';
export class EmailAddressDto {
@IsEmail()
@IsEmail({}, { message: 'El email debe tener un formato válido' })
@IsNotEmpty({ message: 'El email es requerido' })
@MaxLength(255, { message: 'El email no debe exceder 255 caracteres' })
email: string;
@IsOptional()
@IsString()
@IsString({ message: 'El nombre debe ser texto' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
name?: string;
}
export class AttachmentDto {
@IsString()
@IsString({ message: 'El nombre del archivo debe ser texto' })
@IsNotEmpty({ message: 'El nombre del archivo es requerido' })
@MaxLength(255, { message: 'El nombre del archivo no debe exceder 255 caracteres' })
filename: string;
@IsString()
@IsString({ message: 'El contenido debe ser texto (Base64)' })
@IsNotEmpty({ message: 'El contenido del archivo es requerido' })
content: string; // Base64 encoded
@IsOptional()
@IsString()
@IsString({ message: 'El tipo de contenido debe ser texto' })
@MaxLength(100, { message: 'El tipo de contenido no debe exceder 100 caracteres' })
contentType?: string;
}
@ -28,36 +35,41 @@ export class SendEmailDto {
to: EmailAddressDto;
@IsOptional()
@IsArray()
@IsArray({ message: 'CC debe ser un arreglo de direcciones' })
@ArrayMaxSize(50, { message: 'No se pueden incluir más de 50 destinatarios en CC' })
@ValidateNested({ each: true })
@Type(() => EmailAddressDto)
cc?: EmailAddressDto[];
@IsOptional()
@IsArray()
@IsArray({ message: 'BCC debe ser un arreglo de direcciones' })
@ArrayMaxSize(50, { message: 'No se pueden incluir más de 50 destinatarios en BCC' })
@ValidateNested({ each: true })
@Type(() => EmailAddressDto)
bcc?: EmailAddressDto[];
@IsString()
@IsString({ message: 'El asunto debe ser texto' })
@IsNotEmpty({ message: 'El asunto es requerido' })
@MaxLength(255, { message: 'El asunto no debe exceder 255 caracteres' })
subject: string;
@IsOptional()
@IsString()
@IsString({ message: 'El texto debe ser una cadena' })
text?: string;
@IsOptional()
@IsString()
@IsString({ message: 'El HTML debe ser una cadena' })
html?: string;
@IsOptional()
@IsArray()
@IsArray({ message: 'Los adjuntos deben ser un arreglo' })
@ArrayMaxSize(10, { message: 'No se pueden incluir más de 10 archivos adjuntos' })
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
attachments?: AttachmentDto[];
@IsOptional()
@IsObject()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, any>;
}
@ -67,37 +79,43 @@ export class SendTemplateEmailDto {
to: EmailAddressDto;
@IsOptional()
@IsArray()
@IsArray({ message: 'CC debe ser un arreglo de direcciones' })
@ArrayMaxSize(50, { message: 'No se pueden incluir más de 50 destinatarios en CC' })
@ValidateNested({ each: true })
@Type(() => EmailAddressDto)
cc?: EmailAddressDto[];
@IsOptional()
@IsArray()
@IsArray({ message: 'BCC debe ser un arreglo de direcciones' })
@ArrayMaxSize(50, { message: 'No se pueden incluir más de 50 destinatarios en BCC' })
@ValidateNested({ each: true })
@Type(() => EmailAddressDto)
bcc?: EmailAddressDto[];
@IsString()
@IsString({ message: 'La clave de plantilla debe ser texto' })
@IsNotEmpty({ message: 'La clave de plantilla es requerida' })
@MaxLength(100, { message: 'La clave de plantilla no debe exceder 100 caracteres' })
templateKey: string;
@IsOptional()
@IsObject()
@IsObject({ message: 'Las variables deben ser un objeto' })
variables?: Record<string, any>;
@IsOptional()
@IsArray()
@IsArray({ message: 'Los adjuntos deben ser un arreglo' })
@ArrayMaxSize(10, { message: 'No se pueden incluir más de 10 archivos adjuntos' })
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
attachments?: AttachmentDto[];
@IsOptional()
@IsObject()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, any>;
}
export class BulkSendEmailDto {
@IsArray()
@IsArray({ message: 'Los emails deben ser un arreglo' })
@ArrayMaxSize(100, { message: 'No se pueden enviar más de 100 emails a la vez' })
@ValidateNested({ each: true })
@Type(() => SendEmailDto)
emails: SendEmailDto[];

View File

@ -7,116 +7,132 @@ import {
IsObject,
MaxLength,
Min,
Max,
IsNotEmpty,
Matches,
IsUrl,
} from 'class-validator';
export class CreateCategoryDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@IsNotEmpty({ message: 'El nombre de la categoría es requerido' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
name: string;
@IsString()
@MaxLength(120)
@IsString({ message: 'El slug debe ser texto' })
@IsNotEmpty({ message: 'El slug de la categoría es requerido' })
@MaxLength(120, { message: 'El slug no debe exceder 120 caracteres' })
@Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { message: 'El slug solo puede contener letras minúsculas, números y guiones' })
slug: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de categoría padre debe ser un UUID válido' })
@IsOptional()
parentId?: string;
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@MaxLength(2000, { message: 'La descripción no debe exceder 2000 caracteres' })
@IsOptional()
description?: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La posición debe ser un número entero' })
@Min(0, { message: 'La posición debe ser mayor o igual a 0' })
@Max(1000, { message: 'La posición no debe exceder 1000' })
@IsOptional()
position?: number;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de imagen debe ser texto' })
@MaxLength(500, { message: 'La URL de imagen no debe exceder 500 caracteres' })
@IsOptional()
imageUrl?: string;
@IsString()
@MaxLength(7)
@IsString({ message: 'El color debe ser texto' })
@MaxLength(7, { message: 'El color no debe exceder 7 caracteres' })
@Matches(/^#[0-9A-Fa-f]{6}$/, { message: 'El color debe ser un código hexadecimal válido (ej: #FF5733)' })
@IsOptional()
color?: string;
@IsString()
@MaxLength(50)
@IsString({ message: 'El icono debe ser texto' })
@MaxLength(50, { message: 'El icono no debe exceder 50 caracteres' })
@IsOptional()
icon?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
@IsString()
@MaxLength(200)
@IsString({ message: 'El meta título debe ser texto' })
@MaxLength(200, { message: 'El meta título no debe exceder 200 caracteres' })
@IsOptional()
metaTitle?: string;
@IsString()
@IsString({ message: 'La meta descripción debe ser texto' })
@MaxLength(500, { message: 'La meta descripción no debe exceder 500 caracteres' })
@IsOptional()
metaDescription?: string;
@IsObject()
@IsObject({ message: 'Los campos personalizados deben ser un objeto' })
@IsOptional()
customFields?: Record<string, any>;
}
export class UpdateCategoryDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
@IsOptional()
name?: string;
@IsString()
@MaxLength(120)
@IsString({ message: 'El slug debe ser texto' })
@MaxLength(120, { message: 'El slug no debe exceder 120 caracteres' })
@Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { message: 'El slug solo puede contener letras minúsculas, números y guiones' })
@IsOptional()
slug?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de categoría padre debe ser un UUID válido' })
@IsOptional()
parentId?: string | null;
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@MaxLength(2000, { message: 'La descripción no debe exceder 2000 caracteres' })
@IsOptional()
description?: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La posición debe ser un número entero' })
@Min(0, { message: 'La posición debe ser mayor o igual a 0' })
@Max(1000, { message: 'La posición no debe exceder 1000' })
@IsOptional()
position?: number;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de imagen debe ser texto' })
@MaxLength(500, { message: 'La URL de imagen no debe exceder 500 caracteres' })
@IsOptional()
imageUrl?: string;
@IsString()
@MaxLength(7)
@IsString({ message: 'El color debe ser texto' })
@MaxLength(7, { message: 'El color no debe exceder 7 caracteres' })
@Matches(/^#[0-9A-Fa-f]{6}$/, { message: 'El color debe ser un código hexadecimal válido (ej: #FF5733)' })
@IsOptional()
color?: string;
@IsString()
@MaxLength(50)
@IsString({ message: 'El icono debe ser texto' })
@MaxLength(50, { message: 'El icono no debe exceder 50 caracteres' })
@IsOptional()
icon?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
@IsString()
@MaxLength(200)
@IsString({ message: 'El meta título debe ser texto' })
@MaxLength(200, { message: 'El meta título no debe exceder 200 caracteres' })
@IsOptional()
metaTitle?: string;
@IsString()
@IsString({ message: 'La meta descripción debe ser texto' })
@MaxLength(500, { message: 'La meta descripción no debe exceder 500 caracteres' })
@IsOptional()
metaDescription?: string;
@IsObject()
@IsObject({ message: 'Los campos personalizados deben ser un objeto' })
@IsOptional()
customFields?: Record<string, any>;
}
@ -149,21 +165,27 @@ export class CategoryTreeNodeDto extends CategoryResponseDto {
}
export class CategoryListQueryDto {
@IsUUID()
@IsUUID('4', { message: 'El ID de categoría padre debe ser un UUID válido' })
@IsOptional()
parentId?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
@IsString()
@IsString({ message: 'El término de búsqueda debe ser texto' })
@MaxLength(100, { message: 'El término de búsqueda no debe exceder 100 caracteres' })
@IsOptional()
search?: string;
@IsInt({ message: 'La página debe ser un número entero' })
@Min(1, { message: 'La página debe ser mayor o igual a 1' })
@IsOptional()
page?: number;
@IsInt({ message: 'El límite debe ser un número entero' })
@Min(1, { message: 'El límite debe ser mayor o igual a 1' })
@Max(100, { message: 'El límite no debe exceder 100 elementos' })
@IsOptional()
limit?: number;
}

View File

@ -1,55 +1,63 @@
import { IsString, IsNotEmpty, IsOptional, IsArray, IsUUID } from 'class-validator';
import { IsString, IsNotEmpty, IsOptional, IsArray, IsUUID, MaxLength, Matches, ArrayMaxSize } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateRoleDto {
@ApiProperty({ example: 'Manager' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El nombre debe ser texto' })
@IsNotEmpty({ message: 'El nombre del rol es requerido' })
@MaxLength(100, { message: 'El nombre del rol no debe exceder 100 caracteres' })
name: string;
@ApiProperty({ example: 'manager' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El código debe ser texto' })
@IsNotEmpty({ message: 'El código del rol es requerido' })
@MaxLength(50, { message: 'El código del rol no debe exceder 50 caracteres' })
@Matches(/^[a-z][a-z0-9_-]*$/, { message: 'El código debe comenzar con letra minúscula y solo contener letras, números, guiones y guiones bajos' })
code: string;
@ApiPropertyOptional({ example: 'Can manage team members' })
@IsOptional()
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@MaxLength(500, { message: 'La descripción no debe exceder 500 caracteres' })
description?: string;
@ApiPropertyOptional({ type: [String], example: ['users:read', 'users:write'] })
@IsOptional()
@IsArray()
@IsString({ each: true })
@IsArray({ message: 'Los permisos deben ser un arreglo' })
@ArrayMaxSize(100, { message: 'No se pueden asignar más de 100 permisos a un rol' })
@IsString({ each: true, message: 'Cada permiso debe ser texto' })
permissions?: string[];
}
export class UpdateRoleDto {
@ApiPropertyOptional({ example: 'Manager' })
@IsOptional()
@IsString()
@IsString({ message: 'El nombre debe ser texto' })
@MaxLength(100, { message: 'El nombre del rol no debe exceder 100 caracteres' })
name?: string;
@ApiPropertyOptional({ example: 'Can manage team members' })
@IsOptional()
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@MaxLength(500, { message: 'La descripción no debe exceder 500 caracteres' })
description?: string;
@ApiPropertyOptional({ type: [String] })
@IsOptional()
@IsArray()
@IsString({ each: true })
@IsArray({ message: 'Los permisos deben ser un arreglo' })
@ArrayMaxSize(100, { message: 'No se pueden asignar más de 100 permisos a un rol' })
@IsString({ each: true, message: 'Cada permiso debe ser texto' })
permissions?: string[];
}
export class AssignRoleDto {
@ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' })
@IsUUID()
@IsNotEmpty()
@IsUUID('4', { message: 'El ID de usuario debe ser un UUID válido' })
@IsNotEmpty({ message: 'El ID de usuario es requerido' })
userId: string;
@ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440001' })
@IsUUID()
@IsNotEmpty()
@IsUUID('4', { message: 'El ID de rol debe ser un UUID válido' })
@IsNotEmpty({ message: 'El ID de rol es requerido' })
roleId: string;
}

View File

@ -8,159 +8,171 @@ import {
IsArray,
MaxLength,
Min,
Max,
IsBoolean,
IsDateString,
IsNotEmpty,
IsUrl,
} from 'class-validator';
import { ActivityType, ActivityStatus } from '../entities';
export class CreateActivityDto {
@IsEnum(ActivityType)
@IsEnum(ActivityType, { message: 'El tipo de actividad debe ser válido' })
type: ActivityType;
@IsString()
@MaxLength(255)
@IsString({ message: 'El asunto debe ser texto' })
@IsNotEmpty({ message: 'El asunto de la actividad es requerido' })
@MaxLength(255, { message: 'El asunto no debe exceder 255 caracteres' })
subject: string;
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@IsOptional()
@MaxLength(2000, { message: 'La descripción no debe exceder 2000 caracteres' })
description?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID del lead debe ser un UUID válido' })
@IsOptional()
leadId?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de la oportunidad debe ser un UUID válido' })
@IsOptional()
opportunityId?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de vencimiento debe ser una fecha válida ISO 8601' })
@IsOptional()
dueDate?: string;
@IsString()
@IsString({ message: 'La hora de vencimiento debe ser texto' })
@IsOptional()
@MaxLength(10, { message: 'La hora de vencimiento no debe exceder 10 caracteres' })
dueTime?: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La duración debe ser un número entero' })
@Min(0, { message: 'La duración debe ser mayor o igual a 0 minutos' })
@Max(1440, { message: 'La duración no debe exceder 1440 minutos (24 horas)' })
@IsOptional()
durationMinutes?: number;
@IsUUID()
@IsUUID('4', { message: 'El ID del usuario asignado debe ser un UUID válido' })
@IsOptional()
assignedTo?: string;
@IsString()
@MaxLength(10)
@IsString({ message: 'La dirección de llamada debe ser texto' })
@MaxLength(10, { message: 'La dirección de llamada no debe exceder 10 caracteres' })
@IsOptional()
callDirection?: string;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de grabación debe ser texto' })
@MaxLength(500, { message: 'La URL de grabación no debe exceder 500 caracteres' })
@IsOptional()
callRecordingUrl?: string;
@IsString()
@MaxLength(255)
@IsString({ message: 'La ubicación debe ser texto' })
@MaxLength(255, { message: 'La ubicación no debe exceder 255 caracteres' })
@IsOptional()
location?: string;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de la reunión debe ser texto' })
@MaxLength(500, { message: 'La URL de la reunión no debe exceder 500 caracteres' })
@IsOptional()
meetingUrl?: string;
@IsArray()
@IsArray({ message: 'Los asistentes deben ser un arreglo' })
@IsOptional()
attendees?: any[];
@IsDateString()
@IsDateString({}, { message: 'La fecha del recordatorio debe ser una fecha válida ISO 8601' })
@IsOptional()
reminderAt?: string;
@IsObject()
@IsObject({ message: 'Los campos personalizados deben ser un objeto' })
@IsOptional()
customFields?: Record<string, any>;
}
export class UpdateActivityDto {
@IsEnum(ActivityType)
@IsEnum(ActivityType, { message: 'El tipo de actividad debe ser válido' })
@IsOptional()
type?: ActivityType;
@IsEnum(ActivityStatus)
@IsEnum(ActivityStatus, { message: 'El estado de la actividad debe ser válido' })
@IsOptional()
status?: ActivityStatus;
@IsString()
@MaxLength(255)
@IsString({ message: 'El asunto debe ser texto' })
@MaxLength(255, { message: 'El asunto no debe exceder 255 caracteres' })
@IsOptional()
subject?: string;
@IsString()
@IsString({ message: 'La descripción debe ser texto' })
@MaxLength(2000, { message: 'La descripción no debe exceder 2000 caracteres' })
@IsOptional()
description?: string;
@IsDateString()
@IsDateString({}, { message: 'La fecha de vencimiento debe ser una fecha válida ISO 8601' })
@IsOptional()
dueDate?: string;
@IsString()
@IsString({ message: 'La hora de vencimiento debe ser texto' })
@MaxLength(10, { message: 'La hora de vencimiento no debe exceder 10 caracteres' })
@IsOptional()
dueTime?: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La duración debe ser un número entero' })
@Min(0, { message: 'La duración debe ser mayor o igual a 0 minutos' })
@Max(1440, { message: 'La duración no debe exceder 1440 minutos (24 horas)' })
@IsOptional()
durationMinutes?: number;
@IsString()
@IsString({ message: 'El resultado debe ser texto' })
@MaxLength(500, { message: 'El resultado no debe exceder 500 caracteres' })
@IsOptional()
outcome?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID del usuario asignado debe ser un UUID válido' })
@IsOptional()
assignedTo?: string;
@IsString()
@MaxLength(10)
@IsString({ message: 'La dirección de llamada debe ser texto' })
@MaxLength(10, { message: 'La dirección de llamada no debe exceder 10 caracteres' })
@IsOptional()
callDirection?: string;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de grabación debe ser texto' })
@MaxLength(500, { message: 'La URL de grabación no debe exceder 500 caracteres' })
@IsOptional()
callRecordingUrl?: string;
@IsString()
@MaxLength(255)
@IsString({ message: 'La ubicación debe ser texto' })
@MaxLength(255, { message: 'La ubicación no debe exceder 255 caracteres' })
@IsOptional()
location?: string;
@IsString()
@MaxLength(500)
@IsString({ message: 'La URL de la reunión debe ser texto' })
@MaxLength(500, { message: 'La URL de la reunión no debe exceder 500 caracteres' })
@IsOptional()
meetingUrl?: string;
@IsArray()
@IsArray({ message: 'Los asistentes deben ser un arreglo' })
@IsOptional()
attendees?: any[];
@IsDateString()
@IsDateString({}, { message: 'La fecha del recordatorio debe ser una fecha válida ISO 8601' })
@IsOptional()
reminderAt?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de recordatorio enviado debe ser verdadero o falso' })
@IsOptional()
reminderSent?: boolean;
@IsObject()
@IsObject({ message: 'Los campos personalizados deben ser un objeto' })
@IsOptional()
customFields?: Record<string, any>;
}
export class CompleteActivityDto {
@IsString()
@IsString({ message: 'El resultado debe ser texto' })
@MaxLength(500, { message: 'El resultado no debe exceder 500 caracteres' })
@IsOptional()
outcome?: string;
}
@ -196,29 +208,34 @@ export class ActivityResponseDto {
}
export class ActivityListQueryDto {
@IsEnum(ActivityType)
@IsEnum(ActivityType, { message: 'El tipo de actividad debe ser válido' })
@IsOptional()
type?: ActivityType;
@IsEnum(ActivityStatus)
@IsEnum(ActivityStatus, { message: 'El estado de la actividad debe ser válido' })
@IsOptional()
status?: ActivityStatus;
@IsUUID()
@IsUUID('4', { message: 'El ID del lead debe ser un UUID válido' })
@IsOptional()
leadId?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID de la oportunidad debe ser un UUID válido' })
@IsOptional()
opportunityId?: string;
@IsUUID()
@IsUUID('4', { message: 'El ID del usuario asignado debe ser un UUID válido' })
@IsOptional()
assignedTo?: string;
@IsInt({ message: 'La página debe ser un número entero' })
@Min(1, { message: 'La página debe ser mayor o igual a 1' })
@IsOptional()
page?: number;
@IsInt({ message: 'El límite debe ser un número entero' })
@Min(1, { message: 'El límite debe ser mayor o igual a 1' })
@Max(100, { message: 'El límite no debe exceder 100 elementos' })
@IsOptional()
limit?: number;
}

View File

@ -5,67 +5,80 @@ import {
IsBoolean,
MaxLength,
Min,
Max,
IsNotEmpty,
IsUUID,
IsArray,
Matches,
ArrayMaxSize,
} from 'class-validator';
export class CreatePipelineStageDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@IsNotEmpty({ message: 'El nombre de la etapa es requerido' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
name: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La posición debe ser un número entero' })
@Min(0, { message: 'La posición debe ser mayor o igual a 0' })
@Max(100, { message: 'La posición no debe exceder 100' })
@IsOptional()
position?: number;
@IsString()
@MaxLength(7)
@IsString({ message: 'El color debe ser texto' })
@MaxLength(7, { message: 'El color no debe exceder 7 caracteres' })
@Matches(/^#[0-9A-Fa-f]{6}$/, { message: 'El color debe ser un código hexadecimal válido (ej: #FF5733)' })
@IsOptional()
color?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de ganado debe ser verdadero o falso' })
@IsOptional()
isWon?: boolean;
@IsBoolean()
@IsBoolean({ message: 'El indicador de perdido debe ser verdadero o falso' })
@IsOptional()
isLost?: boolean;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
}
export class UpdatePipelineStageDto {
@IsString()
@MaxLength(100)
@IsString({ message: 'El nombre debe ser texto' })
@MaxLength(100, { message: 'El nombre no debe exceder 100 caracteres' })
@IsOptional()
name?: string;
@IsInt()
@Min(0)
@IsInt({ message: 'La posición debe ser un número entero' })
@Min(0, { message: 'La posición debe ser mayor o igual a 0' })
@Max(100, { message: 'La posición no debe exceder 100' })
@IsOptional()
position?: number;
@IsString()
@MaxLength(7)
@IsString({ message: 'El color debe ser texto' })
@MaxLength(7, { message: 'El color no debe exceder 7 caracteres' })
@Matches(/^#[0-9A-Fa-f]{6}$/, { message: 'El color debe ser un código hexadecimal válido (ej: #FF5733)' })
@IsOptional()
color?: string;
@IsBoolean()
@IsBoolean({ message: 'El indicador de ganado debe ser verdadero o falso' })
@IsOptional()
isWon?: boolean;
@IsBoolean()
@IsBoolean({ message: 'El indicador de perdido debe ser verdadero o falso' })
@IsOptional()
isLost?: boolean;
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
@IsOptional()
isActive?: boolean;
}
export class ReorderStagesDto {
@IsString({ each: true })
@IsArray({ message: 'Los IDs de etapas deben ser un arreglo' })
@ArrayMaxSize(100, { message: 'No se pueden reordenar más de 100 etapas a la vez' })
@IsUUID('4', { each: true, message: 'Cada ID de etapa debe ser un UUID válido' })
stageIds: string[];
}

View File

@ -1,72 +1,83 @@
import { IsString, IsNumber, IsOptional, IsEnum, IsUUID, Min, Max } from 'class-validator';
import { IsString, IsNumber, IsOptional, IsEnum, IsUUID, IsObject, IsNotEmpty, Min, Max, MaxLength } from 'class-validator';
import { FileVisibility } from '../entities/file.entity';
// ==================== Request DTOs ====================
export class GetUploadUrlDto {
@IsString()
@IsString({ message: 'El nombre del archivo debe ser texto' })
@IsNotEmpty({ message: 'El nombre del archivo es requerido' })
@MaxLength(255, { message: 'El nombre del archivo no debe exceder 255 caracteres' })
filename: string;
@IsString()
@IsString({ message: 'El tipo MIME debe ser texto' })
@IsNotEmpty({ message: 'El tipo MIME es requerido' })
@MaxLength(100, { message: 'El tipo MIME no debe exceder 100 caracteres' })
mimeType: string;
@IsNumber()
@Min(1)
@Max(524288000) // 500 MB max
@IsNumber({}, { message: 'El tamaño debe ser un número' })
@Min(1, { message: 'El tamaño del archivo debe ser mayor a 0 bytes' })
@Max(524288000, { message: 'El tamaño del archivo no debe exceder 500 MB' })
sizeBytes: number;
@IsOptional()
@IsString()
@IsString({ message: 'La carpeta debe ser texto' })
@MaxLength(500, { message: 'La ruta de carpeta no debe exceder 500 caracteres' })
folder?: string;
@IsOptional()
@IsEnum(FileVisibility)
@IsEnum(FileVisibility, { message: 'La visibilidad debe ser un valor válido' })
visibility?: FileVisibility;
}
export class ConfirmUploadDto {
@IsUUID()
@IsUUID('4', { message: 'El ID de carga debe ser un UUID válido' })
uploadId: string;
@IsOptional()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, any>;
}
export class ListFilesDto {
@IsOptional()
@IsNumber()
@Min(1)
@IsNumber({}, { message: 'La página debe ser un número' })
@Min(1, { message: 'La página debe ser mayor o igual a 1' })
page?: number = 1;
@IsOptional()
@IsNumber()
@Min(1)
@Max(100)
@IsNumber({}, { message: 'El límite debe ser un número' })
@Min(1, { message: 'El límite debe ser mayor o igual a 1' })
@Max(100, { message: 'El límite no debe exceder 100 elementos' })
limit?: number = 20;
@IsOptional()
@IsString()
@IsString({ message: 'La carpeta debe ser texto' })
@MaxLength(500, { message: 'La ruta de carpeta no debe exceder 500 caracteres' })
folder?: string;
@IsOptional()
@IsString()
@IsString({ message: 'El tipo MIME debe ser texto' })
@MaxLength(100, { message: 'El tipo MIME no debe exceder 100 caracteres' })
mimeType?: string;
@IsOptional()
@IsString()
@IsString({ message: 'El término de búsqueda debe ser texto' })
@MaxLength(100, { message: 'El término de búsqueda no debe exceder 100 caracteres' })
search?: string;
}
export class UpdateFileDto {
@IsOptional()
@IsString()
@IsString({ message: 'La carpeta debe ser texto' })
@MaxLength(500, { message: 'La ruta de carpeta no debe exceder 500 caracteres' })
folder?: string;
@IsOptional()
@IsEnum(FileVisibility)
@IsEnum(FileVisibility, { message: 'La visibilidad debe ser un valor válido' })
visibility?: FileVisibility;
@IsOptional()
@IsObject({ message: 'Los metadatos deben ser un objeto' })
metadata?: Record<string, any>;
}

View File

@ -1,64 +1,74 @@
import { IsString, IsNotEmpty, IsOptional, IsBoolean, IsNumber, Min } from 'class-validator';
import { IsString, IsNotEmpty, IsOptional, IsBoolean, IsNumber, Min, Max, MaxLength, Matches } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateWhatsAppConfigDto {
@ApiProperty({ description: 'Meta Phone Number ID' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El ID del número de teléfono debe ser texto' })
@IsNotEmpty({ message: 'El ID del número de teléfono de Meta es requerido' })
@MaxLength(50, { message: 'El ID del número de teléfono no debe exceder 50 caracteres' })
phoneNumberId: string;
@ApiProperty({ description: 'Meta Business Account ID' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El ID de la cuenta de negocio debe ser texto' })
@IsNotEmpty({ message: 'El ID de la cuenta de negocio de Meta es requerido' })
@MaxLength(50, { message: 'El ID de la cuenta de negocio no debe exceder 50 caracteres' })
businessAccountId: string;
@ApiProperty({ description: 'Meta Cloud API Access Token' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El token de acceso debe ser texto' })
@IsNotEmpty({ message: 'El token de acceso de la API de Meta es requerido' })
@MaxLength(500, { message: 'El token de acceso no debe exceder 500 caracteres' })
accessToken: string;
@ApiPropertyOptional({ description: 'Webhook verify token' })
@IsOptional()
@IsString()
@IsString({ message: 'El token de verificación debe ser texto' })
@MaxLength(100, { message: 'El token de verificación no debe exceder 100 caracteres' })
webhookVerifyToken?: string;
@ApiPropertyOptional({ description: 'Daily message limit', default: 1000 })
@IsOptional()
@IsNumber()
@Min(1)
@IsNumber({}, { message: 'El límite diario debe ser un número' })
@Min(1, { message: 'El límite diario de mensajes debe ser mayor o igual a 1' })
@Max(100000, { message: 'El límite diario de mensajes no debe exceder 100,000' })
dailyMessageLimit?: number;
}
export class UpdateWhatsAppConfigDto {
@ApiPropertyOptional({ description: 'Meta Phone Number ID' })
@IsOptional()
@IsString()
@IsString({ message: 'El ID del número de teléfono debe ser texto' })
@MaxLength(50, { message: 'El ID del número de teléfono no debe exceder 50 caracteres' })
phoneNumberId?: string;
@ApiPropertyOptional({ description: 'Meta Business Account ID' })
@IsOptional()
@IsString()
@IsString({ message: 'El ID de la cuenta de negocio debe ser texto' })
@MaxLength(50, { message: 'El ID de la cuenta de negocio no debe exceder 50 caracteres' })
businessAccountId?: string;
@ApiPropertyOptional({ description: 'Meta Cloud API Access Token' })
@IsOptional()
@IsString()
@IsString({ message: 'El token de acceso debe ser texto' })
@MaxLength(500, { message: 'El token de acceso no debe exceder 500 caracteres' })
accessToken?: string;
@ApiPropertyOptional({ description: 'Webhook verify token' })
@IsOptional()
@IsString()
@IsString({ message: 'El token de verificación debe ser texto' })
@MaxLength(100, { message: 'El token de verificación no debe exceder 100 caracteres' })
webhookVerifyToken?: string;
@ApiPropertyOptional({ description: 'Daily message limit' })
@IsOptional()
@IsNumber()
@Min(1)
@IsNumber({}, { message: 'El límite diario debe ser un número' })
@Min(1, { message: 'El límite diario de mensajes debe ser mayor o igual a 1' })
@Max(100000, { message: 'El límite diario de mensajes no debe exceder 100,000' })
dailyMessageLimit?: number;
@ApiPropertyOptional({ description: 'Enable/disable WhatsApp integration' })
@IsOptional()
@IsBoolean()
@IsBoolean({ message: 'El indicador de activo debe ser verdadero o falso' })
isActive?: boolean;
}
@ -102,7 +112,8 @@ export class WhatsAppConfigResponseDto {
export class TestConnectionDto {
@ApiProperty({ description: 'Phone number to send test message to' })
@IsString()
@IsNotEmpty()
@IsString({ message: 'El número de teléfono debe ser texto' })
@IsNotEmpty({ message: 'El número de teléfono de prueba es requerido' })
@Matches(/^\+[1-9]\d{1,14}$/, { message: 'El número de teléfono debe estar en formato E.164 (ej: +521234567890)' })
testPhoneNumber: string;
}