From fb8991cb3520ef4c554421f77ea291ce53036a3e Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 16 Jun 2025 11:26:17 -0600 Subject: [PATCH] registro de movimientos de la carga, verificacion y descarga. falta probar --- src/app.module.ts | 4 +- src/entities/entities.ts | 16 +++-- src/excel/excel.controller.ts | 102 +++++++++++++++++++++++++-- src/excel/excel.documentation.ts | 41 +++++++++++ src/excel/excel.module.ts | 2 + src/excel/excel.service.ts | 63 +++++++++++++++++ src/movimiento/movimiento.module.ts | 12 ++++ src/movimiento/movimiento.service.ts | 41 +++++++++++ 8 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 src/movimiento/movimiento.module.ts create mode 100644 src/movimiento/movimiento.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index bd770ae..83172c0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import * as entities from './entities/entities'; import { ExcelModule } from './excel/excel.module'; import { AuthModule } from './auth/auth.module'; +import { MovimientoModule } from './movimiento/movimiento.module'; @Module({ @@ -46,7 +47,8 @@ import { AuthModule } from './auth/auth.module'; ExcelModule, - AuthModule + AuthModule, + MovimientoModule ], controllers: [AppController], diff --git a/src/entities/entities.ts b/src/entities/entities.ts index 8add556..a6f1d06 100644 --- a/src/entities/entities.ts +++ b/src/entities/entities.ts @@ -179,14 +179,22 @@ export class Movimiento { @Column({ type: 'varchar', length: 100 }) status: string; - @Column({ type: 'varchar', length: 200 }) - observaciones: string; + @Column({ + type: 'varchar', + length: 200, + nullable: true, // permite NULL + }) + observaciones?: string; @Column({ type: 'bit' }) flag: boolean; - @Column({ type: 'varchar', length: 200 }) - reporte: string; +@Column({ + type: 'varchar', + length: 200, + nullable: true, // permite NULL + }) + reporte?: string; @ManyToOne(() => Usuario, usuario => usuario.movimientos) @JoinColumn({ name: 'id_usuario' }) diff --git a/src/excel/excel.controller.ts b/src/excel/excel.controller.ts index 7c6d7d0..a4d0980 100644 --- a/src/excel/excel.controller.ts +++ b/src/excel/excel.controller.ts @@ -5,30 +5,120 @@ import { UploadedFile, UseInterceptors, BadRequestException, + Request, + Get, + Res, + HttpStatus, } from '@nestjs/common'; + import { FileInterceptor } from '@nestjs/platform-express'; import { ExcelService } from './excel.service'; import { ExcelDocumentation } from './excel.documentation'; +import { MovimientoService } from 'src/movimiento/movimiento.service'; +import { Response } from 'express'; @Controller('excel') export class ExcelController { - constructor(private readonly excelService: ExcelService) {} + constructor( + private readonly excelService: ExcelService, + + private readonly movimientoService: MovimientoService, + ) {} @Post('verify') @UseInterceptors(FileInterceptor('file')) @ExcelDocumentation.verifyExcel() - async verifyExcel(@UploadedFile() file: Express.Multer.File) { + async verifyExcel(@UploadedFile() file: Express.Multer.File, @Request() req) { if (!file) throw new BadRequestException('Se requiere un archivo .xlsx'); + const userId = req.user.userId; + const errors = await this.excelService.validateFile(file.buffer); + + await this.movimientoService.log( + userId, + 'VERIFY', + errors.length ? 'FAILED' : 'SUCCESS', + errors.join('; '), + ); + return { valid: errors.length === 0, errors }; } @Post('load') @UseInterceptors(FileInterceptor('file')) @ExcelDocumentation.loadExcel() - async loadExcel(@UploadedFile() file: Express.Multer.File) { - if (!file) throw new BadRequestException('Se requiere un archivo .xlsx'); - const result = await this.excelService.loadFile(file.buffer); - return result; // { inserted: X } + async loadExcel(@UploadedFile() file: Express.Multer.File, @Request() req) { + const userId = req.user.userId; + try { + const { inserted } = await this.excelService.loadFile(file.buffer); + await this.movimientoService.log( + userId, + 'LOAD', + 'SUCCESS', + undefined, + `inserted=${inserted}`, + ); + return { inserted }; + } catch (err) { + await this.movimientoService.log(userId, 'LOAD', 'FAILED', err.message); + throw err; + } } + + + @ExcelDocumentation.downloadExcel() // <-- decorador correcto + @Get('download') + async downloadData(@Request() req, @Res() res: Response) { + const userId: number = req.user.userId; // asegúrate de que sea primitivo + + try { + // 1) Genera los datos + const data = await this.excelService.generateCsv(); + + // 2) Log SUCCESS + await this.movimientoService.log( + userId, // <-- solo el valor, no userId() + 'DOWNLOAD', + 'SUCCESS', + undefined, + `size=${data.length}`, + ); + + // 3) Devuelve la respuesta + return res + .status(HttpStatus.OK) // <-- HttpStatus importado + .header('Content-Type', 'text/tab-separated-values') + .header('Content-Disposition', 'attachment; filename="usuarios.tsv"') + .send(data); + } catch (err) { + // 4) Log FAILED + await this.movimientoService.log( + userId, + 'DOWNLOAD', + 'FAILED', + err.message, + ); + + // 5) Responde el error + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ statusCode: 500, message: 'Error generando descarga.' }); + } + } + + + + + + + + + + + + + + + + } diff --git a/src/excel/excel.documentation.ts b/src/excel/excel.documentation.ts index 41b5f02..b93d5ca 100644 --- a/src/excel/excel.documentation.ts +++ b/src/excel/excel.documentation.ts @@ -93,4 +93,45 @@ export class ExcelDocumentation { }) ); } + + + + + + + static downloadExcel() { + return applyDecorators( + ApiTags('Excel'), + //ApiBearerAuth('bearer'), + ApiOperation({ + summary: 'Descargar usuarios', + description: 'Exporta todos los usuarios en un archivo TSV (o CSV) y registra el movimiento.' + }), + ApiResponse({ + status: 200, + description: 'TSV generado correctamente', + content: { + 'text/tab-separated-values': { + schema: { type: 'string', example: 'num_cuenta\\tnombre\\t...\\n...' } + } + } + }), + ApiResponse({ status: 401, description: 'No autorizado' }), + ApiResponse({ status: 500, description: 'Error al generar descarga' }), + ); + } + + + + + + + + + + + + + + } diff --git a/src/excel/excel.module.ts b/src/excel/excel.module.ts index c4eec57..2edd062 100644 --- a/src/excel/excel.module.ts +++ b/src/excel/excel.module.ts @@ -3,10 +3,12 @@ import { ExcelService } from './excel.service'; import { ExcelController } from './excel.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Usuario, Genero, Carrera } from '../entities/entities'; +import { MovimientoModule } from 'src/movimiento/movimiento.module'; @Module({ imports: [ TypeOrmModule.forFeature([Usuario, Genero, Carrera]), + MovimientoModule ], providers: [ExcelService], controllers: [ExcelController], diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 5950085..cde45a9 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -204,4 +204,67 @@ export class ExcelService { return { inserted: rows.length }; } + + + + + + + + + + + + + + + + + + + + + +async generateCsv(): Promise { + const users = await this.usuarioRepo.find({ + relations: ['genero', 'carreraUsuarios'], // si quieres más info + select: [ 'num_cuenta','nombre','a_paterno','a_materno','correo','rfc','fecha_nacimiento','generacion' ], + }); + + const header = [ + 'num_cuenta','nombre','a_paterno','a_materno', + 'correo','rfc','fecha_nacimiento','generacion' + ].join('\t') + '\n'; + + const rows = users.map(u => [ + u.num_cuenta, + u.nombre, + u.a_paterno, + u.a_materno, + u.correo ?? '', + u.rfc ?? '', + u.fecha_nacimiento, + (u.generacion != null ? u.generacion.toString() : '') + + ].join('\t')).join('\n'); + + return header + rows; + } + + + + + + + + + + + + + + + + + } diff --git a/src/movimiento/movimiento.module.ts b/src/movimiento/movimiento.module.ts new file mode 100644 index 0000000..7275540 --- /dev/null +++ b/src/movimiento/movimiento.module.ts @@ -0,0 +1,12 @@ +// src/movimiento/movimiento.module.ts +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MovimientoService } from './movimiento.service'; +import { Movimiento, Origen } from '../entities/entities'; + +@Module({ + imports: [TypeOrmModule.forFeature([Movimiento, Origen])], + providers: [MovimientoService], + exports: [MovimientoService], +}) +export class MovimientoModule {} diff --git a/src/movimiento/movimiento.service.ts b/src/movimiento/movimiento.service.ts new file mode 100644 index 0000000..a257777 --- /dev/null +++ b/src/movimiento/movimiento.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Movimiento } from '../entities/entities'; +import { Usuario } from '../entities/entities'; +import { Origen } from '../entities/entities'; + +@Injectable() +export class MovimientoService { + constructor( + @InjectRepository(Movimiento) + private readonly movRepo: Repository, + @InjectRepository(Origen) + private readonly origenRepo: Repository, + ) {} + + /** Crea un registro de movimiento */ + async log( + usuarioId: number, + fuente: string, + status: string, + observaciones?: string, + reporte?: string, + flag = false, + ) { + // 1) Obtén el origen o falla si no existe + const origen = await this.origenRepo.findOne({ where: { fuente } }); + if (!origen) throw new Error(`Origen desconocido: ${fuente}`); + + // 2) Inserta directamente usando los campos de FK + await this.movRepo.insert({ + fecha_mov: new Date(), + status, + observaciones, // aquí usas el valor que vino como parámetro + reporte, // idem + flag, + usuario: { id_usuario: usuarioId }, // relación en lugar de id_usuario + origen: { id_origen: origen.id_origen }, + }); + } +}