Crear, modificar o extender módulos NestJS en AMSA Sender. Usar este skill siempre que se pida crear un módulo, servicio, controlador, guard, interceptor, pipe o cualquier artefacto del backend NestJS. También aplicar cuando se pida agregar un endpoint, refactorizar un servicio existente, o integrar una librería nueva al backend.
Cada módulo sigue esta estructura:
src/
└── <nombre>/
├── <nombre>.module.ts
├── <nombre>.controller.ts
├── <nombre>.service.ts
├── dto/
│ ├── create-<nombre>.dto.ts
│ └── update-<nombre>.dto.ts
└── interfaces/
└── <nombre>.interface.ts
Siempre inyectar y usar Winston. NUNCA console.log.
import { Logger } from '@nestjs/common';
@Injectable()
export class MiServicio {
private readonly logger = new Logger(MiServicio.name);
async hacerAlgo() {
this.logger.log('Iniciando proceso...');
try {
// lógica
} catch (error) {
this.logger.error('Error en hacerAlgo', error?.stack);
throw new HttpException('Error interno', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Siempre usar class-validator y class-transformer.
import { IsString, IsEmail, IsNotEmpty, IsOptional, IsInt, Min } from 'class-validator';
export class CreateCampanaDto {
@IsString()
@IsNotEmpty()
nombre: string;
@IsEmail()
destinatario: string;
@IsOptional()
@IsInt()
@Min(1)
batchSize?: number;
}
@ApiTags (Swagger).@HttpCode explícito en creación/eliminación.@ApiTags('campanas')
@Controller('campanas')
export class CampanaController {
constructor(private readonly campanaService: CampanaService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateCampanaDto) {
return this.campanaService.create(dto);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.campanaService.findOne(id);
}
}
exports para servicios que otros módulos consuman.PrismaModule si el servicio accede a la DB.@Module({
imports: [PrismaModule],
controllers: [CampanaController],
providers: [CampanaService],
exports: [CampanaService],
})
export class CampanaModule {}
// Preferir excepciones específicas de NestJS
throw new NotFoundException(`Campaña con id ${id} no encontrada`);
throw new BadRequestException('El campo nombre es requerido');
throw new ConflictException('Ya existe una campaña con ese nombre');
@Injectable()
export class CampanaService {
private readonly logger = new Logger(CampanaService.name);
constructor(private readonly prisma: PrismaService) {}
async findOne(id: number) {
const campana = await this.prisma.campana.findUnique({ where: { id } });
if (!campana) throw new NotFoundException(`Campaña ${id} no encontrada`);
return campana;
}
}
AppModule?class-validator?HttpException de NestJS?console.log residual? (eliminar)