Backend modular de producción para cualificación de leads con IA. API REST, JWT, DTOs validados, queues BullMQ y configuración por entorno.
async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true, }); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true }, }), ); app.useGlobalInterceptors( new ClassSerializerInterceptor(app.get(Reflector)), ); app.useGlobalFilters(new HttpExceptionFilter()); // Rate limiting en endpoints públicos app.use(rateLimit({ windowMs: 60_000, max: 100 })); await app.listen( process.env.PORT ?? 3000 ); } bootstrap();
export enum LeadSource { WEB = 'web', CRM = 'crm', LINKEDIN = 'linkedin', } export class CreateLeadDto { @IsEmail() email: string; @IsString() @Length(2, 80) fullName: string; @IsString() @IsOptional() company?: string; @IsEnum(LeadSource) source: LeadSource; @IsObject() @IsOptional() metadata?: Record<string, unknown>; }
@Controller('leads') @UseGuards(JwtAuthGuard, RolesGuard) export class LeadsController { constructor( private readonly leadsService: LeadsService, ) {} @Post() @Roles('admin', 'analyst') @HttpCode(202) async create( @Body() dto: CreateLeadDto, @Req() req: AuthenticatedRequest, ) { return this.leadsService.ingest(dto, req.user.id); } @Get(':id') findOne( @Param('id', ParseUUIDPipe) id: string ) { return this.leadsService.findById(id); } }
@Catch() export class HttpExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const res = host.switchToHttp() .getResponse<Response>(); const req = host.switchToHttp() .getRequest<Request>(); if (exception instanceof HttpException) { return res.status(exception.getStatus()).json({ path: req.url, ts: new Date().toISOString(), error: exception.getResponse(), }); } return res.status(500).json({ path: req.url, ts: new Date().toISOString(), error: 'Internal server error', }); } }
@Processor('scoring') export class ScoringProcessor extends WorkerHost { constructor(private readonly scoringService: ScoringService) { super(); } async process(job: Job<ScoringJobDto>): Promise<void> { const { leadId, features } = job.data; const score = await this.scoringService.predict(features); // llamada al modelo IA await this.scoringService.saveScore(leadId, score); // persiste en Prisma } } // En LeadsService — encola tras persistir async ingest(dto: CreateLeadDto, userId: string) { const lead = await this.prisma.lead.create({ data: { ...dto, userId } }); await this.scoringQueue.add('score-lead', { leadId: lead.id, features: dto.metadata }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 }, }); return { id: lead.id, status: 'queued' }; }
Cada feature vive en su módulo: leads, scoring, webhooks, analytics. Cero cruce de capas.
Solo parsean input HTTP y devuelven respuesta. La lógica vive en los servicios.
Validación con class-validator y whitelist:true. Ningún campo no declarado pasa.
JwtAuthGuard primero, luego RolesGuard. Autorización de recurso en el service.
Scoring pesado en BullMQ. La API responde 202 y el worker procesa con reintentos.
ConfigModule carga y valida todas las variables de entorno antes del primer request.
Prisma solo se toca en servicios. Las transacciones multi-paso no escalan a controladores.
createTestingModule replica pipes/filtros de producción. Los guards se testean con requests HTTP.