|
|
@@ -24,7 +24,6 @@ export interface Document {
|
|
|
fecha_carga?: string
|
|
|
}
|
|
|
|
|
|
-
|
|
|
export interface ProcessedDocument {
|
|
|
nombre_del_archivo: string;
|
|
|
tamano_del_archivo: string;
|
|
|
@@ -43,8 +42,7 @@ export interface ZipFileEntry {
|
|
|
type?: string;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-export interface DocumentValidationResult {
|
|
|
+export interface ValidationResult {
|
|
|
isValid: boolean;
|
|
|
foundFiles: string[];
|
|
|
missingFiles: string[];
|
|
|
@@ -54,6 +52,11 @@ export interface DocumentValidationResult {
|
|
|
foundCount: number;
|
|
|
matchedCount: number;
|
|
|
};
|
|
|
+ tempFiles?: {
|
|
|
+ excel: string;
|
|
|
+ zip: string;
|
|
|
+ };
|
|
|
+ individualTempFiles?: any[];
|
|
|
}
|
|
|
|
|
|
export interface ExcelDocumentEntry {
|
|
|
@@ -80,15 +83,22 @@ interface TemplateStructure {
|
|
|
})
|
|
|
export class ZipfileUploadComponent {
|
|
|
|
|
|
-private originalZipFile: File | null = null;
|
|
|
+ // Archivos originales para validación
|
|
|
+ private originalExcelFile: File | null = null;
|
|
|
+ private originalZipFile: File | null = null;
|
|
|
|
|
|
-public processedDocuments: ProcessedDocument[] = [];
|
|
|
+ public processedDocuments: ProcessedDocument[] = [];
|
|
|
public isLoading: boolean = false;
|
|
|
public hasError: boolean = false;
|
|
|
public errorMessage: string = '';
|
|
|
public isUploading: boolean;
|
|
|
public currentFileType: 'excel' | 'zip' | null = null;
|
|
|
|
|
|
+ // Variables para validación
|
|
|
+ public validationResult: ValidationResult | null = null;
|
|
|
+ public isValidating: boolean = false;
|
|
|
+ public validationStep: string = '';
|
|
|
+
|
|
|
uploadedFile: TempFileInfo | null;
|
|
|
@ViewChild('file') private uploader?: ElementRef;
|
|
|
|
|
|
@@ -168,7 +178,6 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
this.uploader?.nativeElement.click();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
onDragOver(event: DragEvent): void {
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
@@ -329,9 +338,273 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
return errors;
|
|
|
}
|
|
|
|
|
|
+ async uploadExcelFile(fileData: File): Promise<void> {
|
|
|
+ try {
|
|
|
+ // Guardar referencia al archivo original
|
|
|
+ this.originalExcelFile = fileData;
|
|
|
+
|
|
|
+ // Primero validar la estructura antes de subir
|
|
|
+ const validationResult = await this.validateFileStructure(fileData);
|
|
|
+
|
|
|
+ if (!validationResult.isValid) {
|
|
|
+ this._resourcesService.openSnackBar(`Debe contener al menos un registro y cumplir con la estructura requerida`);
|
|
|
+ this.isUploading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.readExcelFile(fileData, 'temp-excel-' + Date.now());
|
|
|
+
|
|
|
+ const newDocument: Document = {
|
|
|
+ nombre_del_archivo: fileData.name,
|
|
|
+ tamano_del_archivo: this.formatBytes(fileData.size),
|
|
|
+ archivo_id: 'temp-excel-' + Date.now(),
|
|
|
+ tipo_archivo: 'excel',
|
|
|
+ fecha_carga: new Date().toLocaleString()
|
|
|
+ };
|
|
|
+
|
|
|
+ this.excelFile = newDocument;
|
|
|
+ this.updateTableData();
|
|
|
+ this._resourcesService.openSnackBar('Plantilla Excel válida y cargada exitosamente');
|
|
|
+
|
|
|
+ this.isUploading = false;
|
|
|
+ } catch (error: any) {
|
|
|
+ this.handleUploadError(error);
|
|
|
+ this.isUploading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async uploadZipFile(fileData: File): Promise<void> {
|
|
|
+ try {
|
|
|
+ // Guardar referencia al archivo original para validación
|
|
|
+ this.originalZipFile = fileData;
|
|
|
+
|
|
|
+ // Validar contenido del ZIP
|
|
|
+ const zipValidation = await this.validateZipContent(fileData);
|
|
|
+
|
|
|
+ if (!zipValidation.isValid) {
|
|
|
+ this._resourcesService.openSnackBar(`Error en el archivo ZIP: ${zipValidation.errors.join(', ')}`);
|
|
|
+ this.isUploading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const tempZipId = 'temp-zip-' + Date.now();
|
|
|
+
|
|
|
+ // Leer el contenido del ZIP para visualización
|
|
|
+ await this.readZipFile(fileData, tempZipId);
|
|
|
+
|
|
|
+ const newDocument: Document = {
|
|
|
+ nombre_del_archivo: fileData.name,
|
|
|
+ tamano_del_archivo: this.formatBytes(fileData.size),
|
|
|
+ archivo_id: tempZipId,
|
|
|
+ tipo_archivo: 'zip',
|
|
|
+ fecha_carga: new Date().toLocaleString()
|
|
|
+ };
|
|
|
+
|
|
|
+ this.zipFile = newDocument;
|
|
|
+ this.updateTableData();
|
|
|
+ this._resourcesService.openSnackBar('Archivo ZIP cargado y listo para validación');
|
|
|
+
|
|
|
+ this.isUploading = false;
|
|
|
+
|
|
|
+ } catch (error: any) {
|
|
|
+ this.handleUploadError(error);
|
|
|
+ this.isUploading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // NUEVA LÓGICA DE VALIDACIÓN USANDO EL BACKEND
|
|
|
+ public async validateAndProcessDocuments(): Promise<void> {
|
|
|
+ if (!this.canValidate) {
|
|
|
+ this._resourcesService.openSnackBar('Debe cargar tanto el archivo Excel como el ZIP antes de validar');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isValidating = true;
|
|
|
+ this.validationStep = 'Preparando archivos...';
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Obtener ID de usuario
|
|
|
+ const idUser = localStorage.getItem('idusuario');
|
|
|
+ if (!idUser) {
|
|
|
+ throw new Error('No se encontró ID de usuario');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Usar el endpoint de validación del backend
|
|
|
+ this.validationStep = 'Validando documentos...';
|
|
|
+ const validationFormData = new FormData();
|
|
|
+
|
|
|
+ if (this.originalExcelFile && this.originalZipFile) {
|
|
|
+ validationFormData.append('excel_file', this.originalExcelFile, this.originalExcelFile.name);
|
|
|
+ validationFormData.append('zip_file', this.originalZipFile, this.originalZipFile.name);
|
|
|
+ validationFormData.append('id_user', idUser);
|
|
|
+ validationFormData.append('linea', '1');
|
|
|
+
|
|
|
+ const validationResponse = await this._gdelService.validateZipFile(validationFormData).toPromise();
|
|
|
+
|
|
|
+ if (validationResponse.error) {
|
|
|
+ throw new Error(validationResponse.message || 'Error en validación');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Procesar respuesta de validación
|
|
|
+ this.processValidationResponse(validationResponse);
|
|
|
+
|
|
|
+ // Si la validación es exitosa, procesar archivos
|
|
|
+ if (this.validationResult?.isValid) {
|
|
|
+ this.validationStep = 'Procesando documentos validados...';
|
|
|
+ await this.processValidatedDocuments();
|
|
|
+
|
|
|
+ this._resourcesService.openSnackBar('Validación y procesamiento completado exitosamente');
|
|
|
+ this.dialogRef.close({
|
|
|
+ success: true,
|
|
|
+ validationResult: this.validationResult,
|
|
|
+ processedCount: this.validationResult.foundFiles.length
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ const missingCount = this.validationResult?.missingFiles.length || 0;
|
|
|
+ this._resourcesService.openSnackBar(
|
|
|
+ `Validación completada: Faltan ${missingCount} documento(s). Revise los detalles.`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new Error('Archivos originales no disponibles');
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('Error validando documentos:', error);
|
|
|
+ this._resourcesService.openSnackBar('Error en validación: ' + error.message);
|
|
|
+ } finally {
|
|
|
+ this.isValidating = false;
|
|
|
+ this.validationStep = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private processValidationResponse(response: any): void {
|
|
|
+ // El backend ya hace la comparación, usar sus resultados
|
|
|
+ this.validationResult = {
|
|
|
+ isValid: response.valid || false,
|
|
|
+ foundFiles: [], // El backend maneja esto internamente
|
|
|
+ missingFiles: response.missing_in_zip || [],
|
|
|
+ extraFiles: response.extra_in_zip || [],
|
|
|
+ validationDetails: {
|
|
|
+ expectedCount: 0, // Se calculará después si es necesario
|
|
|
+ foundCount: 0,
|
|
|
+ matchedCount: 0
|
|
|
+ },
|
|
|
+ tempFiles: response.temp_files || null,
|
|
|
+ individualTempFiles: response.individual_temp_files || []
|
|
|
+ };
|
|
|
+
|
|
|
+ // Calcular estadísticas
|
|
|
+ const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
+ this.validationResult.validationDetails.expectedCount = expectedDocuments.length;
|
|
|
+ this.validationResult.validationDetails.matchedCount =
|
|
|
+ expectedDocuments.length - this.validationResult.missingFiles.length;
|
|
|
+
|
|
|
+ console.log('Resultado de validación procesado:', this.validationResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async processValidatedDocuments(): Promise<void> {
|
|
|
+ if (!this.validationResult?.tempFiles || !this.validationResult?.individualTempFiles) {
|
|
|
+ throw new Error('No hay archivos temporales para procesar');
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const idUser = localStorage.getItem('idusuario');
|
|
|
+
|
|
|
+ // Usar el endpoint de procesamiento del backend
|
|
|
+ const processBody = {
|
|
|
+ id_user: idUser,
|
|
|
+ linea: 1,
|
|
|
+ temp_files: this.validationResult.tempFiles,
|
|
|
+ individual_temp_files: this.validationResult.individualTempFiles
|
|
|
+ };
|
|
|
+
|
|
|
+ const processResponse = await this._gdelService.processZipFile(processBody).toPromise();
|
|
|
+
|
|
|
+ if (processResponse.error) {
|
|
|
+ throw new Error(processResponse.message || 'Error en procesamiento');
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('Procesamiento completado:', processResponse);
|
|
|
+
|
|
|
+ // Actualizar estado de documentos procesados
|
|
|
+ this.updateProcessedDocuments(processResponse);
|
|
|
+
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('Error procesando documentos:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private updateProcessedDocuments(processResponse: any): void {
|
|
|
+ // Actualizar la lista de documentos procesados basado en la respuesta
|
|
|
+ if (processResponse.individual_files) {
|
|
|
+ this.processedDocuments = processResponse.individual_files.map((file: any) => ({
|
|
|
+ nombre_del_archivo: file.original_name,
|
|
|
+ tamano_del_archivo: '0 B', // El backend no devuelve tamaño
|
|
|
+ archivo_id: file.final_id,
|
|
|
+ tipo_archivo: this.getFileTypeFromExtension(file.original_name),
|
|
|
+ fecha_carga: new Date().toLocaleString(),
|
|
|
+ estado: 'final_saved' as const
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MÉTODOS AUXILIARES QUE SE MANTIENEN
|
|
|
+
|
|
|
+ private async validateFileStructure(file: File): Promise<{ isValid: boolean, errors: string[] }> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e: any) => {
|
|
|
+ try {
|
|
|
+ const data = new Uint8Array(e.target.result);
|
|
|
+ const workbook = XLSX.read(data, { type: 'array' });
|
|
|
+
|
|
|
+ const validation = this.validateExcelTemplate(workbook);
|
|
|
+ resolve(validation);
|
|
|
+ } catch (error) {
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.onerror = reject;
|
|
|
+ reader.readAsArrayBuffer(file);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async readExcelFile(file: File, fileId: any): Promise<void> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e: any) => {
|
|
|
+ try {
|
|
|
+ const data = new Uint8Array(e.target.result);
|
|
|
+ const workbook = XLSX.read(data, { type: 'array' });
|
|
|
+
|
|
|
+ const sheetNames = workbook.SheetNames;
|
|
|
+ const sheetsData: any = {};
|
|
|
+
|
|
|
+ sheetNames.forEach(sheetName => {
|
|
|
+ const worksheet = workbook.Sheets[sheetName];
|
|
|
+ sheetsData[sheetName] = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
|
+ });
|
|
|
+
|
|
|
+ this.excelData = {
|
|
|
+ fileName: file.name,
|
|
|
+ sheets: sheetsData,
|
|
|
+ sheetNames: sheetNames,
|
|
|
+ fileId: fileId,
|
|
|
+ originalFile: file // Mantener referencia al archivo original
|
|
|
+ };
|
|
|
|
|
|
+ resolve();
|
|
|
+ } catch (error) {
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.onerror = reject;
|
|
|
+ reader.readAsArrayBuffer(file);
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // Método para validar el contenido del ZIP
|
|
|
private async validateZipContent(file: File): Promise<{ isValid: boolean, errors: string[] }> {
|
|
|
return new Promise((resolve) => {
|
|
|
const reader = new FileReader();
|
|
|
@@ -357,13 +630,11 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
if (!zipEntry.dir) {
|
|
|
fileCount++;
|
|
|
|
|
|
- // Para obtener el tamaño real, necesitamos leer el contenido
|
|
|
try {
|
|
|
const content = await zipEntry.async('uint8array');
|
|
|
const fileSize = content.length;
|
|
|
|
|
|
- // Validar tamaño individual (10MB por archivo)
|
|
|
- if (fileSize > 1000 * 1024 * 1024) {
|
|
|
+ if (fileSize > 10 * 1024 * 1024) {
|
|
|
errors.push(`El archivo ${fileName} es demasiado grande (supera los 10MB)`);
|
|
|
}
|
|
|
|
|
|
@@ -374,12 +645,10 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Validar tamaño total descomprimido (100MB total)
|
|
|
if (totalSize > 250 * 1024 * 1024) {
|
|
|
- errors.push('El contenido total del ZIP supera los 100MB');
|
|
|
+ errors.push('El contenido total del ZIP supera los 250MB');
|
|
|
}
|
|
|
|
|
|
- // Validar número máximo de archivos (opcional)
|
|
|
if (fileCount > 1000) {
|
|
|
errors.push('El ZIP contiene demasiados archivos (máximo 1000)');
|
|
|
}
|
|
|
@@ -406,7 +675,6 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // Método para leer el contenido del ZIP
|
|
|
private async readZipFile(file: File, fileId: string): Promise<void> {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const reader = new FileReader();
|
|
|
@@ -420,40 +688,16 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
let totalSize = 0;
|
|
|
let fileCount = 0;
|
|
|
|
|
|
- // Procesar cada entrada del ZIP
|
|
|
for (const [relativePath, zipEntry] of Object.entries(zipContent.files)) {
|
|
|
if (!zipEntry.dir) {
|
|
|
- // Es un archivo
|
|
|
try {
|
|
|
- // Leer el contenido para obtener el tamaño real
|
|
|
const content = await zipEntry.async('uint8array');
|
|
|
const fileSize = content.length;
|
|
|
totalSize += fileSize;
|
|
|
fileCount++;
|
|
|
|
|
|
- // Determinar el tipo de archivo
|
|
|
const extension = relativePath.split('.').pop()?.toLowerCase();
|
|
|
- let fileType = 'unknown';
|
|
|
-
|
|
|
- if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(extension || '')) {
|
|
|
- fileType = 'image';
|
|
|
- } else if (['pdf'].includes(extension || '')) {
|
|
|
- fileType = 'pdf';
|
|
|
- } else if (['doc', 'docx'].includes(extension || '')) {
|
|
|
- fileType = 'document';
|
|
|
- } else if (['txt', 'md', 'readme'].includes(extension || '')) {
|
|
|
- fileType = 'text';
|
|
|
- } else if (['xlsx', 'xls', 'csv'].includes(extension || '')) {
|
|
|
- fileType = 'excel';
|
|
|
- } else if (['ppt', 'pptx'].includes(extension || '')) {
|
|
|
- fileType = 'presentation';
|
|
|
- } else if (['zip', 'rar', '7z'].includes(extension || '')) {
|
|
|
- fileType = 'archive';
|
|
|
- } else if (['mp4', 'avi', 'mov', 'wmv'].includes(extension || '')) {
|
|
|
- fileType = 'video';
|
|
|
- } else if (['mp3', 'wav', 'ogg'].includes(extension || '')) {
|
|
|
- fileType = 'audio';
|
|
|
- }
|
|
|
+ let fileType = this.determineFileType(extension);
|
|
|
|
|
|
files.push({
|
|
|
name: relativePath,
|
|
|
@@ -464,16 +708,14 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
|
|
|
} catch (fileError) {
|
|
|
console.warn(`No se pudo leer el archivo ${relativePath}:`, fileError);
|
|
|
- // Agregar el archivo con tamaño estimado
|
|
|
files.push({
|
|
|
name: relativePath,
|
|
|
- size: 0, // No pudimos obtener el tamaño
|
|
|
+ size: 0,
|
|
|
isDirectory: false,
|
|
|
type: 'unknown'
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
- // Es un directorio
|
|
|
files.push({
|
|
|
name: relativePath,
|
|
|
size: 0,
|
|
|
@@ -486,7 +728,6 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
this.zipData = {
|
|
|
fileName: file.name,
|
|
|
files: files.sort((a, b) => {
|
|
|
- // Ordenar: directorios primero, luego archivos por nombre
|
|
|
if (a.isDirectory && !b.isDirectory) return -1;
|
|
|
if (!a.isDirectory && b.isDirectory) return 1;
|
|
|
return a.name.localeCompare(b.name);
|
|
|
@@ -507,116 +748,32 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- async uploadExcelFile(fileData: File): Promise<void> {
|
|
|
- try {
|
|
|
- // Primero validar la estructura antes de subir
|
|
|
- const validationResult = await this.validateFileStructure(fileData);
|
|
|
-
|
|
|
- if (!validationResult.isValid) {
|
|
|
- this._resourcesService.openSnackBar(`Debe contener al menos un registro y cumplir con la estructura requerida`);
|
|
|
- this.isUploading = false;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
- const formData = new FormData();
|
|
|
-
|
|
|
- formData.append('file', fileData);
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('linea', "1");
|
|
|
- formData.append('tipo_archivo', 'excel');
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- this._resourcesService.openSnackBar(response.msg);
|
|
|
- } else {
|
|
|
- this.uploadedFile = {
|
|
|
- id: response.response.idArchivo,
|
|
|
- name: fileData.name,
|
|
|
- size: this.formatBytes(fileData.size)
|
|
|
- };
|
|
|
-
|
|
|
- await this.readExcelFile(fileData, response.response.idArchivo);
|
|
|
-
|
|
|
- const newDocument: Document = {
|
|
|
- nombre_del_archivo: fileData.name,
|
|
|
- tamano_del_archivo: this.formatBytes(fileData.size),
|
|
|
- archivo_id: response.response.idArchivo,
|
|
|
- tipo_archivo: 'excel',
|
|
|
- fecha_carga: new Date().toLocaleString()
|
|
|
- };
|
|
|
-
|
|
|
- this.excelFile = newDocument;
|
|
|
- this.updateTableData();
|
|
|
- this._resourcesService.openSnackBar('Plantilla Excel válida y cargada exitosamente');
|
|
|
- }
|
|
|
-
|
|
|
- this.isUploading = false;
|
|
|
- } catch (error: any) {
|
|
|
- this.handleUploadError(error);
|
|
|
- this.isUploading = false;
|
|
|
+ private determineFileType(extension?: string): string {
|
|
|
+ if (!extension) return 'unknown';
|
|
|
+
|
|
|
+ if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(extension)) {
|
|
|
+ return 'image';
|
|
|
+ } else if (['pdf'].includes(extension)) {
|
|
|
+ return 'pdf';
|
|
|
+ } else if (['doc', 'docx'].includes(extension)) {
|
|
|
+ return 'document';
|
|
|
+ } else if (['txt', 'md', 'readme'].includes(extension)) {
|
|
|
+ return 'text';
|
|
|
+ } else if (['xlsx', 'xls', 'csv'].includes(extension)) {
|
|
|
+ return 'excel';
|
|
|
+ } else if (['ppt', 'pptx'].includes(extension)) {
|
|
|
+ return 'presentation';
|
|
|
+ } else if (['zip', 'rar', '7z'].includes(extension)) {
|
|
|
+ return 'archive';
|
|
|
+ } else if (['mp4', 'avi', 'mov', 'wmv'].includes(extension)) {
|
|
|
+ return 'video';
|
|
|
+ } else if (['mp3', 'wav', 'ogg'].includes(extension)) {
|
|
|
+ return 'audio';
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private async testndtry (er: any) {
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- // Método para validar la estructura del archivo antes de subirlo
|
|
|
- private async validateFileStructure(file: File): Promise<{ isValid: boolean, errors: string[] }> {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const reader = new FileReader();
|
|
|
- reader.onload = (e: any) => {
|
|
|
- try {
|
|
|
- const data = new Uint8Array(e.target.result);
|
|
|
- const workbook = XLSX.read(data, { type: 'array' });
|
|
|
-
|
|
|
- const validation = this.validateExcelTemplate(workbook);
|
|
|
- resolve(validation);
|
|
|
- } catch (error) {
|
|
|
- reject(error);
|
|
|
- }
|
|
|
- };
|
|
|
- reader.onerror = reject;
|
|
|
- reader.readAsArrayBuffer(file);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private async readExcelFile(file: File, fileId: any): Promise<void> {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const reader = new FileReader();
|
|
|
- reader.onload = (e: any) => {
|
|
|
- try {
|
|
|
- const data = new Uint8Array(e.target.result);
|
|
|
- const workbook = XLSX.read(data, { type: 'array' });
|
|
|
-
|
|
|
- const sheetNames = workbook.SheetNames;
|
|
|
- const sheetsData: any = {};
|
|
|
-
|
|
|
- sheetNames.forEach(sheetName => {
|
|
|
- const worksheet = workbook.Sheets[sheetName];
|
|
|
- sheetsData[sheetName] = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
|
- });
|
|
|
-
|
|
|
- this.excelData = {
|
|
|
- fileName: file.name,
|
|
|
- sheets: sheetsData,
|
|
|
- sheetNames: sheetNames,
|
|
|
- fileId: fileId
|
|
|
- };
|
|
|
-
|
|
|
- resolve();
|
|
|
- } catch (error) {
|
|
|
- reject(error);
|
|
|
- }
|
|
|
- };
|
|
|
- reader.onerror = reject;
|
|
|
- reader.readAsArrayBuffer(file);
|
|
|
- });
|
|
|
+ return 'unknown';
|
|
|
}
|
|
|
|
|
|
-
|
|
|
formatBytes(bytes: number, decimals = 2): string {
|
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
|
|
@@ -674,1204 +831,296 @@ public processedDocuments: ProcessedDocument[] = [];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ // Método para mostrar previsualización del ZIP
|
|
|
+ previewZipFile(): void {
|
|
|
+ if (!this.zipData) {
|
|
|
+ this._resourcesService.openSnackBar('No hay datos del archivo ZIP para mostrar');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const dialogRef = this._matDialog.open(PreviewFileContentComponent, {
|
|
|
+ width: '1200px',
|
|
|
+ height: '800px',
|
|
|
+ maxWidth: '1200px',
|
|
|
+ data: {
|
|
|
+ fileName: this.zipData.fileName,
|
|
|
+ fileType: 'zip',
|
|
|
+ zipContent: this.zipData,
|
|
|
+ fileId: this.zipData.fileId,
|
|
|
+ originalFile: this.originalZipFile, // Pasar archivo original
|
|
|
+ excelData: this.excelData // Pasar datos del Excel para validación
|
|
|
+ },
|
|
|
+ disableClose: true
|
|
|
+ });
|
|
|
|
|
|
+ dialogRef.afterClosed().subscribe(result => {
|
|
|
+ if (result && result.action === 'validate' && result.validationResult) {
|
|
|
+ console.log('Validación desde preview completada:', result);
|
|
|
+
|
|
|
+ // Procesar resultado de validación desde preview
|
|
|
+ this.validationResult = result.validationResult;
|
|
|
+
|
|
|
+ if (this.validationResult?.isValid) {
|
|
|
+ this._resourcesService.openSnackBar('Validación exitosa desde preview');
|
|
|
+ // Opcionalmente cerrar el diálogo principal
|
|
|
+ this.dialogRef.close({
|
|
|
+ success: true,
|
|
|
+ validationResult: this.validationResult
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
// Métodos para eliminar archivos específicos
|
|
|
removeExcelFile(): void {
|
|
|
- this.deleteTempFile('excel');
|
|
|
+ this.originalExcelFile = null;
|
|
|
+ this.excelFile = null;
|
|
|
+ this.excelData = null;
|
|
|
+ this.updateTableData();
|
|
|
+ this._resourcesService.openSnackBar('Archivo Excel eliminado');
|
|
|
}
|
|
|
|
|
|
removeZipFile(): void {
|
|
|
- this.deleteTempFile('zip');
|
|
|
- }
|
|
|
-
|
|
|
- // Getters para verificar si hay archivos cargados
|
|
|
- get hasExcelFile(): boolean {
|
|
|
- return this.excelFile !== null;
|
|
|
+ this.originalZipFile = null;
|
|
|
+ this.zipFile = null;
|
|
|
+ this.zipData = null;
|
|
|
+ this.updateTableData();
|
|
|
+ this._resourcesService.openSnackBar('Archivo ZIP eliminado');
|
|
|
}
|
|
|
|
|
|
- get hasZipFile(): boolean {
|
|
|
- return this.zipFile !== null;
|
|
|
- }
|
|
|
+ // Método para extraer nombres de documentos del Excel (para mostrar estadísticas)
|
|
|
+ public extractDocumentNamesFromExcel(): ExcelDocumentEntry[] {
|
|
|
+ if (!this.excelData || !this.excelData.sheets['CARGA DE DOCUMENTOS']) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
|
|
|
- get hasAnyFile(): boolean {
|
|
|
- return this.hasExcelFile || this.hasZipFile;
|
|
|
- }
|
|
|
-
|
|
|
- // Getter para obtener información del ZIP
|
|
|
- get zipInfo(): string {
|
|
|
- if (!this.zipData) return '';
|
|
|
- return `${this.zipData.totalFiles} archivos (${this.formatBytes(this.zipData.totalSize)})`;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-private async processValidatedFiles(validationResult: any): Promise<void> {
|
|
|
- if (!validationResult.foundFiles || validationResult.foundFiles.length === 0) {
|
|
|
- this._resourcesService.openSnackBar('No hay archivos validados para procesar');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- this._resourcesService.openSnackBar(`Procesando ${validationResult.foundFiles.length} archivos validados...`);
|
|
|
-
|
|
|
- console.log('Archivos encontrados para procesar:', validationResult.foundFiles);
|
|
|
- console.log('Detalles de validación:', validationResult.validationDetails);
|
|
|
-
|
|
|
- await this.processIndividualFiles(validationResult.foundFiles);
|
|
|
+ const documentEntries: ExcelDocumentEntry[] = [];
|
|
|
+ const sheet = this.excelData.sheets['CARGA DE DOCUMENTOS'];
|
|
|
|
|
|
- } catch (error) {
|
|
|
- console.error('Error al procesar archivos validados:', error);
|
|
|
- this._resourcesService.openSnackBar('Error al procesar los archivos validados');
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
+ // Definir las columnas de documentos y sus categorías
|
|
|
+ const DOCUMENT_COLUMNS = [
|
|
|
+ { letter: 'H', index: 7, category: 'FICHA DE SEGURIDAD DE PRODUCTOS QUÍMICOS' },
|
|
|
+ { letter: 'J', index: 9, category: 'FICHA TÉCNICA' },
|
|
|
+ { letter: 'L', index: 11, category: 'FOTOGRAFÍAS - DIAGRAMAS' },
|
|
|
+ { letter: 'N', index: 13, category: 'DATOS DEL PROVEEDOR' },
|
|
|
+ { letter: 'P', index: 15, category: 'MANUALES DE OPERACIÓN' },
|
|
|
+ { letter: 'R', index: 17, category: 'MANUALES DE MANTENIMIENTO PREVENTIVO' },
|
|
|
+ { letter: 'T', index: 19, category: 'MANUALES DE MANTENIMIENTO CORRECTIVO' },
|
|
|
+ { letter: 'V', index: 21, category: 'DOCUMENTOS ADICIONALES' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const DATA_START_ROW = 9; // Fila 9 en Excel (índice 8 en array)
|
|
|
|
|
|
-private async processIndividualFiles(foundFiles: string[]): Promise<void> {
|
|
|
- for (const fileName of foundFiles) {
|
|
|
try {
|
|
|
- console.log(`Procesando archivo: ${fileName}`);
|
|
|
-
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error(`Error al procesar archivo ${fileName}:`, error);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// Método para mostrar resumen de archivos esperados (opcional):
|
|
|
-
|
|
|
-public getExpectedFilesFromExcel(): string[] {
|
|
|
- if (!this.excelData) return [];
|
|
|
-
|
|
|
- const expectedFiles: string[] = [];
|
|
|
- const TEMPLATE_COLUMNS = ['H', 'J', 'L', 'N', 'P', 'R', 'T', 'V'];
|
|
|
- const DATA_START_ROW = 9;
|
|
|
-
|
|
|
- try {
|
|
|
- const sheet = this.excelData.sheets['CARGA DE DOCUMENTOS'];
|
|
|
- if (!sheet) return [];
|
|
|
-
|
|
|
- for (let rowIndex = DATA_START_ROW - 1; rowIndex < sheet.length; rowIndex++) {
|
|
|
- const row = sheet[rowIndex];
|
|
|
- if (!row) continue;
|
|
|
-
|
|
|
- TEMPLATE_COLUMNS.forEach(column => {
|
|
|
- const columnIndex = this.getColumnIndex(column);
|
|
|
- const cellValue = row[columnIndex];
|
|
|
-
|
|
|
- if (cellValue && typeof cellValue === 'string' && cellValue.trim() !== '') {
|
|
|
- const fileName = cellValue.trim();
|
|
|
- if (this.hasValidFileExtension(fileName)) {
|
|
|
- expectedFiles.push(fileName);
|
|
|
- }
|
|
|
+ // Recorrer desde la fila 9 hacia abajo
|
|
|
+ for (let rowIndex = DATA_START_ROW - 1; rowIndex < sheet.length; rowIndex++) {
|
|
|
+ const row = sheet[rowIndex];
|
|
|
+ if (!row) continue;
|
|
|
+
|
|
|
+ // Verificar si la fila tiene datos principales (equipos)
|
|
|
+ const equipmentCode = row[3]; // Columna D
|
|
|
+ if (!equipmentCode || typeof equipmentCode !== 'string' || equipmentCode.trim() === '') {
|
|
|
+ continue;
|
|
|
}
|
|
|
- });
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('Error al extraer archivos esperados:', error);
|
|
|
- }
|
|
|
-
|
|
|
- return expectedFiles;
|
|
|
-}
|
|
|
-
|
|
|
-private getColumnIndex(column: string): number {
|
|
|
- const columnMap: { [key: string]: number } = {
|
|
|
- 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
|
|
|
- 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
|
|
|
- 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23
|
|
|
- };
|
|
|
- return columnMap[column] || 0;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
|
|
|
-// Método para extraer nombres de documentos del Excel
|
|
|
-public extractDocumentNamesFromExcel(): ExcelDocumentEntry[] {
|
|
|
- if (!this.excelData || !this.excelData.sheets['CARGA DE DOCUMENTOS']) {
|
|
|
- return [];
|
|
|
- }
|
|
|
-
|
|
|
- const documentEntries: ExcelDocumentEntry[] = [];
|
|
|
- const sheet = this.excelData.sheets['CARGA DE DOCUMENTOS'];
|
|
|
-
|
|
|
- // Definir las columnas de documentos y sus categorías
|
|
|
- const DOCUMENT_COLUMNS = [
|
|
|
- { letter: 'H', index: 7, category: 'FICHA DE SEGURIDAD DE PRODUCTOS QUÍMICOS' },
|
|
|
- { letter: 'J', index: 9, category: 'FICHA TÉCNICA' },
|
|
|
- { letter: 'L', index: 11, category: 'FOTOGRAFÍAS - DIAGRAMAS' },
|
|
|
- { letter: 'N', index: 13, category: 'DATOS DEL PROVEEDOR' },
|
|
|
- { letter: 'P', index: 15, category: 'MANUALES DE OPERACIÓN' },
|
|
|
- { letter: 'R', index: 17, category: 'MANUALES DE MANTENIMIENTO PREVENTIVO' },
|
|
|
- { letter: 'T', index: 19, category: 'MANUALES DE MANTENIMIENTO CORRECTIVO' },
|
|
|
- { letter: 'V', index: 21, category: 'DOCUMENTOS ADICIONALES' }
|
|
|
- ];
|
|
|
-
|
|
|
- const DATA_START_ROW = 9; // Fila 9 en Excel (índice 8 en array)
|
|
|
-
|
|
|
- try {
|
|
|
- // Recorrer desde la fila 9 hacia abajo
|
|
|
- for (let rowIndex = DATA_START_ROW - 1; rowIndex < sheet.length; rowIndex++) {
|
|
|
- const row = sheet[rowIndex];
|
|
|
- if (!row) continue;
|
|
|
-
|
|
|
- // Verificar si la fila tiene datos principales (columnas A, C, E)
|
|
|
- const hasMainData = row[1] || row[3] || row[5];
|
|
|
- if (!hasMainData) continue; // Saltar filas vacías
|
|
|
-
|
|
|
- // Revisar cada columna de documentos
|
|
|
- DOCUMENT_COLUMNS.forEach(column => {
|
|
|
- const cellValue = row[column.index];
|
|
|
-
|
|
|
- if (cellValue && typeof cellValue === 'string' && cellValue.trim() !== '') {
|
|
|
- const documentName = cellValue.trim();
|
|
|
+ // Revisar cada columna de documentos
|
|
|
+ DOCUMENT_COLUMNS.forEach(column => {
|
|
|
+ const cellValue = row[column.index];
|
|
|
|
|
|
- // Validar que tenga una extensión de archivo válida
|
|
|
- if (this.hasValidFileExtension(documentName)) {
|
|
|
- documentEntries.push({
|
|
|
- rowIndex: rowIndex + 1, // +1 para mostrar número de fila real
|
|
|
- columnLetter: column.letter,
|
|
|
- columnIndex: column.index,
|
|
|
- documentName: documentName,
|
|
|
- category: column.category
|
|
|
+ if (cellValue && typeof cellValue === 'string' && cellValue.trim() !== '') {
|
|
|
+ const documentNames = cellValue.split(',').map(name => name.trim());
|
|
|
+
|
|
|
+ documentNames.forEach(documentName => {
|
|
|
+ if (documentName && this.hasValidFileExtension(documentName)) {
|
|
|
+ documentEntries.push({
|
|
|
+ rowIndex: rowIndex + 1, // +1 para mostrar número de fila real
|
|
|
+ columnLetter: column.letter,
|
|
|
+ columnIndex: column.index,
|
|
|
+ documentName: documentName,
|
|
|
+ category: column.category
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('Error al extraer nombres de documentos del Excel:', error);
|
|
|
- }
|
|
|
-
|
|
|
- return documentEntries;
|
|
|
-}
|
|
|
-
|
|
|
-// Método para validar documentos entre Excel y ZIP
|
|
|
-public validateDocumentsInZip(): DocumentValidationResult {
|
|
|
- const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
- const expectedFileNames = expectedDocuments.map(doc => doc.documentName.toLowerCase());
|
|
|
-
|
|
|
- if (!this.zipData || !this.zipData.files) {
|
|
|
- return {
|
|
|
- isValid: false,
|
|
|
- foundFiles: [],
|
|
|
- missingFiles: expectedFileNames,
|
|
|
- extraFiles: [],
|
|
|
- validationDetails: {
|
|
|
- expectedCount: expectedFileNames.length,
|
|
|
- foundCount: 0,
|
|
|
- matchedCount: 0
|
|
|
+ });
|
|
|
}
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- // Obtener nombres de archivos del ZIP (solo archivos, no directorios)
|
|
|
- const zipFileNames = this.zipData.files
|
|
|
- .filter(file => !file.isDirectory)
|
|
|
- .map(file => {
|
|
|
- // Obtener solo el nombre del archivo sin la ruta
|
|
|
- const fileName = file.name.split('/').pop() || file.name;
|
|
|
- return fileName.toLowerCase();
|
|
|
- });
|
|
|
-
|
|
|
- // Encontrar archivos que coinciden
|
|
|
- const foundFiles: string[] = [];
|
|
|
- const missingFiles: string[] = [];
|
|
|
-
|
|
|
- expectedFileNames.forEach(expectedFile => {
|
|
|
- const found = zipFileNames.find(zipFile => {
|
|
|
- // Comparación exacta de nombres
|
|
|
- return zipFile === expectedFile;
|
|
|
- });
|
|
|
-
|
|
|
- if (found) {
|
|
|
- foundFiles.push(expectedFile);
|
|
|
- } else {
|
|
|
- missingFiles.push(expectedFile);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // Encontrar archivos extra (en ZIP pero no en Excel)
|
|
|
- const extraFiles = zipFileNames.filter(zipFile =>
|
|
|
- !expectedFileNames.includes(zipFile)
|
|
|
- );
|
|
|
-
|
|
|
- const isValid = missingFiles.length === 0 && foundFiles.length > 0;
|
|
|
-
|
|
|
- return {
|
|
|
- isValid,
|
|
|
- foundFiles,
|
|
|
- missingFiles,
|
|
|
- extraFiles,
|
|
|
- validationDetails: {
|
|
|
- expectedCount: expectedFileNames.length,
|
|
|
- foundCount: foundFiles.length,
|
|
|
- matchedCount: foundFiles.length
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error al extraer nombres de documentos del Excel:', error);
|
|
|
}
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
|
|
|
-
|
|
|
-// Método para procesar archivos en lotes
|
|
|
-private async processBatchFiles(validationResult: DocumentValidationResult): Promise<void> {
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
-
|
|
|
- if (!idUser || !this.uploadedFile) {
|
|
|
- throw new Error('No se encontró información del usuario o archivo');
|
|
|
+ return documentEntries;
|
|
|
}
|
|
|
|
|
|
- // Procesar archivos en lotes para evitar sobrecargar el servidor
|
|
|
- const batchSize = 3; // Procesar de 3 en 3
|
|
|
- const foundFiles = validationResult.foundFiles;
|
|
|
-
|
|
|
- for (let i = 0; i < foundFiles.length; i += batchSize) {
|
|
|
- const batch = foundFiles.slice(i, i + batchSize);
|
|
|
+ // Método auxiliar para validar extensiones de archivo
|
|
|
+ private hasValidFileExtension(fileName: string): boolean {
|
|
|
+ if (!fileName || !fileName.includes('.')) return false;
|
|
|
|
|
|
- // Procesar lote actual
|
|
|
- await Promise.all(batch.map(async (fileName) => {
|
|
|
- try {
|
|
|
- // Primero saveTempFile
|
|
|
- await this.saveTempFileForDocument(fileName, idUser);
|
|
|
-
|
|
|
- // Luego saveFinalFile
|
|
|
- await this.saveFinalFileForDocument(fileName, idUser);
|
|
|
-
|
|
|
- console.log(`Documento procesado exitosamente: ${fileName}`);
|
|
|
- } catch (error) {
|
|
|
- console.error(`Error al procesar documento ${fileName}:`, error);
|
|
|
- throw error; // Re-lanzar error para detener el proceso
|
|
|
- }
|
|
|
- }));
|
|
|
+ const validExtensions = [
|
|
|
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
|
+ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg', '.webp',
|
|
|
+ '.txt', '.rtf', '.csv', '.xml', '.json',
|
|
|
+ '.zip', '.rar', '.7z',
|
|
|
+ '.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv',
|
|
|
+ '.mp3', '.wav', '.ogg', '.aac', '.flac', '.dwg', '.dxf', '.ai', '.psd'
|
|
|
+ ];
|
|
|
|
|
|
- // Pequeña pausa entre lotes
|
|
|
- if (i + batchSize < foundFiles.length) {
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
- }
|
|
|
+ const extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));
|
|
|
+ return validExtensions.includes(extension);
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// Método para saveTempFile de un documento específico
|
|
|
-private async saveTempFileForDocument(fileName: string, idUser: string): Promise<any> {
|
|
|
- const formData = new FormData();
|
|
|
-
|
|
|
- formData.append('id_user', idUser);
|
|
|
- formData.append('file_name', fileName);
|
|
|
- formData.append('zip_file_id', this.uploadedFile!.id);
|
|
|
- formData.append('linea', '1');
|
|
|
- formData.append('tipo_archivo','');
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(`Error en saveTempFile para ${fileName}: ${response.msg}`);
|
|
|
- }
|
|
|
-
|
|
|
- return response;
|
|
|
-}
|
|
|
-
|
|
|
-// Método para saveFinalFile de un documento específico
|
|
|
-private async saveFinalFileForDocument(fileName: string, idUser: string): Promise<any> {
|
|
|
- const formData = new FormData();
|
|
|
-
|
|
|
- formData.append('id_user', idUser);
|
|
|
- formData.append('file_name', fileName);
|
|
|
- formData.append('zip_file_id', this.uploadedFile!.id);
|
|
|
- formData.append('linea', '1');
|
|
|
- formData.append('module', 'ADSI');
|
|
|
- formData.append('clasification', 'LA');
|
|
|
- formData.append('has_order', 'N');
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(`Error en saveFinalFile para ${fileName}: ${response.msg}`);
|
|
|
- }
|
|
|
-
|
|
|
- return response;
|
|
|
-}
|
|
|
-
|
|
|
-// Método actualizado para validar extensiones de archivo
|
|
|
-private hasValidFileExtension(fileName: string): boolean {
|
|
|
- if (!fileName || !fileName.includes('.')) return false;
|
|
|
-
|
|
|
- const validExtensions = [
|
|
|
- '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
|
- '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg', '.webp',
|
|
|
- '.txt', '.rtf', '.csv', '.xml', '.json',
|
|
|
- '.zip', '.rar', '.7z',
|
|
|
- '.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv',
|
|
|
- '.mp3', '.wav', '.ogg', '.aac', '.flac'
|
|
|
- ];
|
|
|
-
|
|
|
- const extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));
|
|
|
- return validExtensions.includes(extension);
|
|
|
-}
|
|
|
-
|
|
|
-// Método para obtener estadísticas de validación
|
|
|
-public getValidationSummary(): string {
|
|
|
- if (!this.canValidateFiles) return 'Cargue ambos archivos para ver el resumen';
|
|
|
-
|
|
|
- const validation = this.validateDocumentsInZip();
|
|
|
- const { expectedCount, foundCount, matchedCount } = validation.validationDetails;
|
|
|
-
|
|
|
- if (expectedCount === 0) return 'No se encontraron documentos en el Excel';
|
|
|
-
|
|
|
- return `${matchedCount}/${expectedCount} documentos encontrados en el ZIP`;
|
|
|
-}
|
|
|
-
|
|
|
-// Getter actualizado para verificar si se puede validar
|
|
|
-get canValidateFiles(): boolean {
|
|
|
- return this.hasExcelFile && this.hasZipFile &&
|
|
|
- this.excelData && this.zipData &&
|
|
|
- this.extractDocumentNamesFromExcel().length > 0;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// Método para procesar archivos en lotes desde preview
|
|
|
-private async processBatchFilesFromPreview(foundFiles: string[]): Promise<void> {
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
-
|
|
|
- if (!idUser || !this.uploadedFile) {
|
|
|
- throw new Error('No se encontró información del usuario o archivo');
|
|
|
- }
|
|
|
-
|
|
|
- // Procesar archivos en lotes para evitar sobrecargar el servidor
|
|
|
- const batchSize = 3; // Procesar de 3 en 3
|
|
|
-
|
|
|
- for (let i = 0; i < foundFiles.length; i += batchSize) {
|
|
|
- const batch = foundFiles.slice(i, i + batchSize);
|
|
|
+ private getFileTypeFromExtension(fileName: string): string {
|
|
|
+ const extension = fileName.toLowerCase().split('.').pop();
|
|
|
|
|
|
- // Procesar lote actual
|
|
|
- await Promise.all(batch.map(async (fileName) => {
|
|
|
- try {
|
|
|
- console.log(`Procesando documento: ${fileName}`);
|
|
|
-
|
|
|
- // Primero saveTempFile
|
|
|
- const tempResult = await this.saveTempFileForDocument(fileName, idUser);
|
|
|
- console.log(`saveTempFile exitoso para ${fileName}:`, tempResult);
|
|
|
-
|
|
|
- // Luego saveFinalFile
|
|
|
- const finalResult = await this.saveFinalFileForDocument(fileName, idUser);
|
|
|
- console.log(`saveFinalFile exitoso para ${fileName}:`, finalResult);
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error(`Error al procesar documento ${fileName}:`, error);
|
|
|
- throw error; // Re-lanzar error para detener el proceso
|
|
|
- }
|
|
|
- }));
|
|
|
+ const typeMap: { [key: string]: string } = {
|
|
|
+ 'pdf': 'pdf',
|
|
|
+ 'doc': 'docx',
|
|
|
+ 'docx': 'docx',
|
|
|
+ 'xlsx': 'excel',
|
|
|
+ 'xls': 'excel',
|
|
|
+ 'jpg': 'imagen',
|
|
|
+ 'jpeg': 'imagen',
|
|
|
+ 'png': 'imagen',
|
|
|
+ 'gif': 'imagen',
|
|
|
+ 'bmp': 'imagen',
|
|
|
+ 'svg': 'imagen',
|
|
|
+ 'txt': 'texto',
|
|
|
+ 'zip': 'archivo',
|
|
|
+ 'rar': 'archivo'
|
|
|
+ };
|
|
|
|
|
|
- // Pequeña pausa entre lotes
|
|
|
- if (i + batchSize < foundFiles.length) {
|
|
|
- await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
- }
|
|
|
+ return typeMap[extension || ''] || 'documento';
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-// Método alternativo para validar y procesar directamente (sin preview)
|
|
|
-public async validateAndProcessDocuments(): Promise<void> {
|
|
|
- if (!this.canValidateFiles) {
|
|
|
- this._resourcesService.openSnackBar('Debe cargar tanto el archivo Excel como el ZIP antes de validar');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- this.isLoading = true;
|
|
|
-
|
|
|
- // Extraer documentos esperados del Excel
|
|
|
- const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
-
|
|
|
- if (expectedDocuments.length === 0) {
|
|
|
- this._resourcesService.openSnackBar('No se encontraron documentos en el Excel para validar');
|
|
|
+ // Método para mostrar resumen de validación
|
|
|
+ public showValidationSummary(): void {
|
|
|
+ if (!this.canValidate) {
|
|
|
+ this._resourcesService.openSnackBar('Debe cargar tanto el archivo Excel como el ZIP antes de validar');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Validar documentos
|
|
|
- const validationResult = this.validateDocumentsInZip();
|
|
|
-
|
|
|
- if (!validationResult.isValid || validationResult.foundFiles.length === 0) {
|
|
|
- const missingCount = validationResult.missingFiles.length;
|
|
|
- const message = missingCount > 0
|
|
|
- ? `Faltan ${missingCount} documentos en el ZIP. Documentos faltantes: ${validationResult.missingFiles.slice(0, 3).join(', ')}${missingCount > 3 ? '...' : ''}`
|
|
|
- : 'No se encontraron documentos válidos para procesar';
|
|
|
-
|
|
|
- this._resourcesService.openSnackBar(message);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Mostrar resultado de validación
|
|
|
- const foundCount = validationResult.foundFiles.length;
|
|
|
- const expectedCount = validationResult.validationDetails.expectedCount;
|
|
|
+ const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
|
|
|
- this._resourcesService.openSnackBar(
|
|
|
- `Validación exitosa: ${foundCount}/${expectedCount} documentos encontrados. Procesando...`
|
|
|
- );
|
|
|
-
|
|
|
- // Procesar cada archivo encontrado
|
|
|
- await this.processBatchFilesFromPreview(validationResult.foundFiles);
|
|
|
-
|
|
|
- this._resourcesService.openSnackBar('Validación y procesamiento completado exitosamente');
|
|
|
- this.dialogRef.close({
|
|
|
- success: true,
|
|
|
- validationResult,
|
|
|
- processedCount: foundCount
|
|
|
- });
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error('Error en validación y procesamiento:', error);
|
|
|
- this._resourcesService.openSnackBar('Error: ' + (error.message || 'Error desconocido'));
|
|
|
- } finally {
|
|
|
- this.isLoading = false;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Método para mostrar resumen detallado de validación
|
|
|
-public showValidationSummary(): void {
|
|
|
- if (!this.canValidateFiles) {
|
|
|
- this._resourcesService.openSnackBar('Debe cargar tanto el archivo Excel como el ZIP antes de validar');
|
|
|
- return;
|
|
|
+ console.log('=== RESUMEN ANTES DE VALIDACIÓN ===');
|
|
|
+ console.log('Documentos esperados del Excel:', expectedDocuments.length);
|
|
|
+ console.log('Archivos en ZIP:', this.zipData?.totalFiles || 0);
|
|
|
+
|
|
|
+ const summary = `
|
|
|
+ RESUMEN PREVIO:
|
|
|
+ • Documentos esperados en Excel: ${expectedDocuments.length}
|
|
|
+ • Archivos en ZIP: ${this.zipData?.totalFiles || 0}
|
|
|
+ • Estado: Listo para validar
|
|
|
+ `;
|
|
|
+
|
|
|
+ this._resourcesService.openSnackBar(summary);
|
|
|
}
|
|
|
|
|
|
- const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
- const validationResult = this.validateDocumentsInZip();
|
|
|
-
|
|
|
- console.log('=== RESUMEN DE VALIDACIÓN ===');
|
|
|
- console.log('Documentos esperados del Excel:', expectedDocuments);
|
|
|
- console.log('Resultado de validación:', validationResult);
|
|
|
- console.log('Archivos encontrados:', validationResult.foundFiles);
|
|
|
- console.log('Archivos faltantes:', validationResult.missingFiles);
|
|
|
- console.log('Archivos extra en ZIP:', validationResult.extraFiles);
|
|
|
-
|
|
|
- const summary = `
|
|
|
- RESUMEN DE VALIDACIÓN:
|
|
|
- • Documentos esperados: ${validationResult.validationDetails.expectedCount}
|
|
|
- • Documentos encontrados: ${validationResult.validationDetails.foundCount}
|
|
|
- • Estado: ${validationResult.isValid ? ' VÁLIDO' : ' INCOMPLETO'}
|
|
|
- ${validationResult.missingFiles.length > 0 ? `\n• Faltan: ${validationResult.missingFiles.join(', ')}` : ''}
|
|
|
- `;
|
|
|
-
|
|
|
- this._resourcesService.openSnackBar(summary);
|
|
|
-}
|
|
|
-
|
|
|
-// Getter para verificar el estado de validación en tiempo real
|
|
|
-get validationStatus(): string {
|
|
|
- if (!this.canValidateFiles) return 'Sin datos para validar';
|
|
|
-
|
|
|
- const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
- if (expectedDocuments.length === 0) return 'Sin documentos en Excel';
|
|
|
-
|
|
|
- const validationResult = this.validateDocumentsInZip();
|
|
|
- const { expectedCount, foundCount } = validationResult.validationDetails;
|
|
|
-
|
|
|
- return `${foundCount}/${expectedCount} documentos encontrados`;
|
|
|
-}
|
|
|
-
|
|
|
-// Método para limpiar datos de validación
|
|
|
-public clearValidationData(): void {
|
|
|
- // Este método se puede usar si necesitas resetear el estado de validación
|
|
|
- console.log('Limpiando datos de validación...');
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// Método para exportar datos de validación
|
|
|
-public exportValidationReport(): void {
|
|
|
- if (!this.canValidateFiles) return;
|
|
|
-
|
|
|
- const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
- const validationResult = this.validateDocumentsInZip();
|
|
|
-
|
|
|
- const report = {
|
|
|
- timestamp: new Date().toISOString(),
|
|
|
- excelFile: this.excelFile?.nombre_del_archivo,
|
|
|
- zipFile: this.zipFile?.nombre_del_archivo,
|
|
|
- expectedDocuments: expectedDocuments,
|
|
|
- validation: validationResult,
|
|
|
- summary: {
|
|
|
- expected: validationResult.validationDetails.expectedCount,
|
|
|
- found: validationResult.validationDetails.foundCount,
|
|
|
- missing: validationResult.missingFiles.length,
|
|
|
- extra: validationResult.extraFiles.length,
|
|
|
- isComplete: validationResult.isValid
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' });
|
|
|
- const url = window.URL.createObjectURL(blob);
|
|
|
- const a = document.createElement('a');
|
|
|
- a.href = url;
|
|
|
- a.download = `validation-report-${Date.now()}.json`;
|
|
|
- a.click();
|
|
|
- window.URL.revokeObjectURL(url);
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-private async extractFileFromZip(fileName: string): Promise<File | null> {
|
|
|
- if (!this.zipData) return null;
|
|
|
-
|
|
|
- return new Promise((resolve) => {
|
|
|
- const reader = new FileReader();
|
|
|
-
|
|
|
- reader.onload = async (e: any) => {
|
|
|
- try {
|
|
|
- const zipData = new Uint8Array(e.target.result);
|
|
|
- const zip = new JSZip();
|
|
|
- const zipContent = await zip.loadAsync(zipData);
|
|
|
-
|
|
|
- // Buscar el archivo específico en el ZIP
|
|
|
- const targetFile = Object.entries(zipContent.files).find(([path, entry]) => {
|
|
|
- const entryFileName = path.split('/').pop()?.toLowerCase();
|
|
|
- return entryFileName === fileName.toLowerCase() && !entry.dir;
|
|
|
- });
|
|
|
-
|
|
|
- if (targetFile) {
|
|
|
- const [filePath, zipEntry] = targetFile;
|
|
|
- const content = await zipEntry.async('blob');
|
|
|
- const file = new File([content], fileName, {
|
|
|
- type: this.getMimeType(fileName)
|
|
|
- });
|
|
|
- resolve(file);
|
|
|
- } else {
|
|
|
- resolve(null);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('Error al extraer archivo del ZIP:', error);
|
|
|
- resolve(null);
|
|
|
- }
|
|
|
- };
|
|
|
- reader.onerror = () => resolve(null);
|
|
|
-
|
|
|
- // Aquí necesitas acceso al archivo ZIP original
|
|
|
- // Debes guardar una referencia al File original del ZIP
|
|
|
- if (this.originalZipFile) {
|
|
|
- reader.readAsArrayBuffer(this.originalZipFile);
|
|
|
- } else {
|
|
|
- resolve(null);
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-// Método auxiliar para obtener MIME type
|
|
|
-private getMimeType(fileName: string): string {
|
|
|
- const extension = fileName.toLowerCase().split('.').pop();
|
|
|
- const mimeTypes: { [key: string]: string } = {
|
|
|
- 'pdf': 'application/pdf',
|
|
|
- 'doc': 'application/msword',
|
|
|
- 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
- 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
- 'xls': 'application/vnd.ms-excel',
|
|
|
- 'jpg': 'image/jpeg',
|
|
|
- 'jpeg': 'image/jpeg',
|
|
|
- 'png': 'image/png',
|
|
|
- 'gif': 'image/gif',
|
|
|
- 'bmp': 'image/bmp',
|
|
|
- 'svg': 'image/svg+xml',
|
|
|
- 'txt': 'text/plain'
|
|
|
- };
|
|
|
- return mimeTypes[extension || ''] || 'application/octet-stream';
|
|
|
-}
|
|
|
-
|
|
|
-// Método corregido para subir archivo individual desde ZIP
|
|
|
-private async uploadTempFileFromZip(fileName: string): Promise<string | null> {
|
|
|
- try {
|
|
|
- // Extraer el archivo individual del ZIP
|
|
|
- const individualFile = await this.extractFileFromZip(fileName);
|
|
|
-
|
|
|
- if (!individualFile) {
|
|
|
- throw new Error(`No se pudo extraer el archivo ${fileName} del ZIP`);
|
|
|
- }
|
|
|
-
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
- const formData = new FormData();
|
|
|
-
|
|
|
- formData.append('file', individualFile);
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('linea', '1');
|
|
|
-
|
|
|
- // Determinar tipo de archivo basado en extensión
|
|
|
- const extension = fileName.toLowerCase().split('.').pop();
|
|
|
- let tipoArchivo = 'documento';
|
|
|
-
|
|
|
- if (['pdf'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'pdf';
|
|
|
- } else if (['doc', 'docx'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'docx';
|
|
|
- } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'img';
|
|
|
- } else if (['xlsx', 'xls'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'excel';
|
|
|
- }
|
|
|
-
|
|
|
- formData.append('tipo_archivo', tipoArchivo);
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(response.msg || `Error al subir ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
- console.log(` uploadTempFile exitoso para ${fileName}:`, response.response.idArchivo);
|
|
|
- return response.response.idArchivo;
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error(` Error en uploadTempFile para ${fileName}:`, error);
|
|
|
- throw error;
|
|
|
+ // Método para limpiar todos los datos
|
|
|
+ public clearAllData(): void {
|
|
|
+ this.originalExcelFile = null;
|
|
|
+ this.originalZipFile = null;
|
|
|
+ this.excelFile = null;
|
|
|
+ this.excelData = null;
|
|
|
+ this.zipFile = null;
|
|
|
+ this.zipData = null;
|
|
|
+ this.validationResult = null;
|
|
|
+ this.processedDocuments = [];
|
|
|
+ this.currentFileType = null;
|
|
|
+ this.updateTableData();
|
|
|
+ this._resourcesService.openSnackBar('Todos los datos han sido limpiados');
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-// Método corregido para guardar archivo final individual
|
|
|
-private async saveFinalFileIndividual(fileId: string, fileName: string): Promise<void> {
|
|
|
- try {
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
- const formData = new FormData();
|
|
|
|
|
|
- // Parámetros obligatorios
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('id_file', fileId);
|
|
|
- formData.append('linea', '1');
|
|
|
-
|
|
|
- // Parámetros específicos para equipamiento
|
|
|
- formData.append('module', 'ADSI');
|
|
|
- formData.append('clasification', 'LA');
|
|
|
- formData.append('has_order', 'N');
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(response.msg || `Error al guardar ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
- console.log(` saveFinalFile exitoso para ${fileName}:`, response);
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error(` Error en saveFinalFile para ${fileName}:`, error);
|
|
|
- throw error;
|
|
|
+ // Getters para verificar estado
|
|
|
+ get hasExcelFile(): boolean {
|
|
|
+ return this.excelFile !== null && this.originalExcelFile !== null;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// Método principal corregido para procesar documentos validados
|
|
|
-public async processValidatedDocuments(): Promise<void> {
|
|
|
- if (!this.canValidateFiles) {
|
|
|
- this._resourcesService.openSnackBar('Debe cargar tanto el archivo Excel como el ZIP antes de validar');
|
|
|
- return;
|
|
|
+ get hasZipFile(): boolean {
|
|
|
+ return this.zipFile !== null && this.originalZipFile !== null;
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- this.isLoading = true;
|
|
|
-
|
|
|
- // Validar documentos
|
|
|
- const validationResult = this.validateDocumentsInZip();
|
|
|
-
|
|
|
- if (!validationResult.isValid || validationResult.foundFiles.length === 0) {
|
|
|
- const missingCount = validationResult.missingFiles.length;
|
|
|
- const message = missingCount > 0
|
|
|
- ? `Faltan ${missingCount} documentos en el ZIP: ${validationResult.missingFiles.slice(0, 3).join(', ')}${missingCount > 3 ? '...' : ''}`
|
|
|
- : 'No se encontraron documentos válidos para procesar';
|
|
|
-
|
|
|
- this._resourcesService.openSnackBar(message);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const foundCount = validationResult.foundFiles.length;
|
|
|
- this._resourcesService.openSnackBar(
|
|
|
- `Validación exitosa: ${foundCount} documentos encontrados. Procesando...`
|
|
|
- );
|
|
|
-
|
|
|
- // Procesar cada documento individualmente
|
|
|
- let processedCount = 0;
|
|
|
- let errors: string[] = [];
|
|
|
-
|
|
|
- for (const fileName of validationResult.foundFiles) {
|
|
|
- try {
|
|
|
- console.log(` Procesando documento: ${fileName}`);
|
|
|
-
|
|
|
-
|
|
|
- const fileId = await this.uploadTempFileFromZip(fileName);
|
|
|
-
|
|
|
- if (!fileId) {
|
|
|
- throw new Error(`No se obtuvo ID del archivo para ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- await this.saveFinalFileIndividual(fileId, fileName);
|
|
|
-
|
|
|
- processedCount++;
|
|
|
- console.log(` Documento procesado exitosamente: ${fileName} (${processedCount}/${foundCount})`);
|
|
|
-
|
|
|
- // Actualizar progreso en la UI
|
|
|
- this._resourcesService.openSnackBar(
|
|
|
- `Procesado: ${fileName} (${processedCount}/${foundCount})`
|
|
|
- );
|
|
|
-
|
|
|
- // Pausa pequeña entre archivos para no sobrecargar el servidor
|
|
|
- await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- const errorMsg = `Error procesando ${fileName}: ${error.message}`;
|
|
|
- console.error('', errorMsg);
|
|
|
- errors.push(errorMsg);
|
|
|
-
|
|
|
- // Opcional: continuar con el siguiente archivo o detener todo
|
|
|
- // Para continuar, simplemente log el error y sigue
|
|
|
- // Para detener, descomenta la siguiente línea:
|
|
|
- // throw error;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Mostrar resultado final
|
|
|
- if (processedCount > 0) {
|
|
|
- const successMsg = ` ${processedCount} documentos procesados exitosamente`;
|
|
|
- this._resourcesService.openSnackBar(successMsg);
|
|
|
-
|
|
|
- if (errors.length > 0) {
|
|
|
- console.warn(' Errores durante el procesamiento:', errors);
|
|
|
- this._resourcesService.openSnackBar(`Procesados: ${processedCount}, Errores: ${errors.length}`);
|
|
|
- }
|
|
|
-
|
|
|
- // Cerrar diálogo con éxito
|
|
|
- this.dialogRef.close({
|
|
|
- success: true,
|
|
|
- validationResult,
|
|
|
- processedCount,
|
|
|
- errors: errors.length > 0 ? errors : undefined
|
|
|
- });
|
|
|
- } else {
|
|
|
- throw new Error('No se pudo procesar ningún documento');
|
|
|
- }
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error('Error general en procesamiento:', error);
|
|
|
- this._resourcesService.openSnackBar('Error: ' + (error.message || 'Error desconocido'));
|
|
|
- } finally {
|
|
|
- this.isLoading = false;
|
|
|
+ get hasAnyFile(): boolean {
|
|
|
+ return this.hasExcelFile || this.hasZipFile;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-//método uploadZipFile para guardar referencia al archivo original
|
|
|
-
|
|
|
-async uploadZipFile(fileData: File): Promise<void> {
|
|
|
- try {
|
|
|
- // Guardar referencia al archivo original para extracciones posteriores
|
|
|
- this.originalZipFile = fileData;
|
|
|
-
|
|
|
- // ... resto del código existente del método uploadZipFile ...
|
|
|
-
|
|
|
- // Validar contenido del ZIP
|
|
|
- const zipValidation = await this.validateZipContent(fileData);
|
|
|
-
|
|
|
- if (!zipValidation.isValid) {
|
|
|
- this._resourcesService.openSnackBar(`Error en el archivo ZIP: ${zipValidation.errors.join(', ')}`);
|
|
|
- this.isUploading = false;
|
|
|
- return;
|
|
|
- }
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- this.uploadedFile = {
|
|
|
- id: 'temp-zip-' + Date.now(), // ID temporal
|
|
|
- name: fileData.name,
|
|
|
- size: this.formatBytes(fileData.size)
|
|
|
- };
|
|
|
-
|
|
|
- // Leer el contenido del ZIP para visualización
|
|
|
- await this.readZipFile(fileData, this.uploadedFile.id);
|
|
|
-
|
|
|
- const newDocument: Document = {
|
|
|
- nombre_del_archivo: fileData.name,
|
|
|
- tamano_del_archivo: this.formatBytes(fileData.size),
|
|
|
- archivo_id: this.uploadedFile.id,
|
|
|
- tipo_archivo: 'zip',
|
|
|
- fecha_carga: new Date().toLocaleString()
|
|
|
- };
|
|
|
-
|
|
|
- this.zipFile = newDocument;
|
|
|
- this.updateTableData();
|
|
|
- this._resourcesService.openSnackBar('Archivo ZIP cargado y listo para validación');
|
|
|
-
|
|
|
- this.isUploading = false;
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- this.handleUploadError(error);
|
|
|
- this.isUploading = false;
|
|
|
+ get hasRequiredFiles(): boolean {
|
|
|
+ return this.hasExcelFile && this.hasZipFile;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-//Actualizar el método de limpieza
|
|
|
-async deleteTempFile(fileType: 'excel' | 'zip'): Promise<void> {
|
|
|
- try {
|
|
|
- if (fileType === 'zip') {
|
|
|
- // Para ZIP, solo limpiar referencias locales
|
|
|
- this.originalZipFile = null;
|
|
|
- this.zipFile = null;
|
|
|
- this.zipData = null;
|
|
|
- this.uploadedFile = null;
|
|
|
- } else if (fileType === 'excel' && this.uploadedFile) {
|
|
|
- // Para Excel, eliminar del servidor si fue subido
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
- const formData = new FormData();
|
|
|
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('id_file', this.uploadedFile.id);
|
|
|
- formData.append('linea', '1');
|
|
|
-
|
|
|
- await lastValueFrom(this._gdelService.deleteTempFile(formData));
|
|
|
-
|
|
|
- this.excelFile = null;
|
|
|
- this.excelData = null;
|
|
|
- this.uploadedFile = null;
|
|
|
- }
|
|
|
-
|
|
|
- this.currentFileType = null;
|
|
|
- this.updateTableData();
|
|
|
- this._resourcesService.openSnackBar('Archivo eliminado exitosamente');
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- this.handleUploadError(error);
|
|
|
+ get canValidate(): boolean {
|
|
|
+ return this.hasRequiredFiles &&
|
|
|
+ this.excelData &&
|
|
|
+ this.zipData &&
|
|
|
+ this.extractDocumentNamesFromExcel().length > 0;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// Nuevo método para subir archivos individuales del ZIP a uploadTempFile
|
|
|
-async uploadDocumentFromZip(fileName: string, zipFileContent: Uint8Array): Promise<string | null> {
|
|
|
- try {
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
-
|
|
|
- // Crear un File object desde el contenido del ZIP
|
|
|
- const file = new File([zipFileContent], fileName, {
|
|
|
- type: this.getMimeType(fileName)
|
|
|
- });
|
|
|
-
|
|
|
- const formData = new FormData();
|
|
|
- formData.append('file', file);
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('linea', '1');
|
|
|
-
|
|
|
- // Determinar tipo de archivo basado en extensión
|
|
|
- const extension = fileName.toLowerCase().split('.').pop();
|
|
|
- let tipoArchivo = 'documento';
|
|
|
-
|
|
|
- if (['pdf'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'pdf';
|
|
|
- } else if (['doc', 'docx'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'docx';
|
|
|
- } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'img';
|
|
|
- } else if (['xlsx', 'xls'].includes(extension || '')) {
|
|
|
- tipoArchivo = 'excel';
|
|
|
- }
|
|
|
-
|
|
|
- formData.append('tipo_archivo', tipoArchivo);
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(response.msg || `Error al subir ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
- console.log(` Documento subido a temp: ${fileName} - ID: ${response.response.idArchivo}`);
|
|
|
- return response.response.idArchivo;
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error(` Error subiendo ${fileName}:`, error);
|
|
|
- throw error;
|
|
|
- }
|
|
|
+get canProcessDocuments(): boolean {
|
|
|
+ return this.validationResult?.isValid === true &&
|
|
|
+ Array.isArray(this.validationResult?.tempFiles) &&
|
|
|
+ Array.isArray(this.validationResult?.individualTempFiles);
|
|
|
}
|
|
|
|
|
|
-// 2. Método para procesar documentos validados desde el preview
|
|
|
-private async processValidatedDocumentsFromPreview(validationResult: any): Promise<void> {
|
|
|
- if (!validationResult.foundFiles || validationResult.foundFiles.length === 0) {
|
|
|
- this._resourcesService.openSnackBar('No hay archivos validados para procesar');
|
|
|
- return;
|
|
|
+ get zipInfo(): string {
|
|
|
+ if (!this.zipData) return '';
|
|
|
+ return `${this.zipData.totalFiles} archivos (${this.formatBytes(this.zipData.totalSize)})`;
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- this.isLoading = true;
|
|
|
+ get validationStatus(): string {
|
|
|
+ if (!this.canValidate) return 'Sin datos para validar';
|
|
|
|
|
|
- const foundCount = validationResult.foundFiles.length;
|
|
|
- this._resourcesService.openSnackBar(`Procesando ${foundCount} documentos...`);
|
|
|
-
|
|
|
- // Procesar cada archivo encontrado
|
|
|
- const processedDocuments: ProcessedDocument[] = [];
|
|
|
-
|
|
|
- for (const fileName of validationResult.foundFiles) {
|
|
|
- try {
|
|
|
- console.log(` Procesando: ${fileName}`);
|
|
|
-
|
|
|
- // Extraer contenido del archivo desde el ZIP
|
|
|
- const fileContent = await this.extractFileContentFromZip(fileName);
|
|
|
-
|
|
|
- if (!fileContent) {
|
|
|
- throw new Error(`No se pudo extraer el contenido de ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
- // Subir a uploadTempFile
|
|
|
- const fileId = await this.uploadDocumentFromZip(fileName, fileContent);
|
|
|
-
|
|
|
- if (!fileId) {
|
|
|
- throw new Error(`No se obtuvo ID para ${fileName}`);
|
|
|
- }
|
|
|
-
|
|
|
- // Crear registro del documento procesado
|
|
|
- const processedDoc: ProcessedDocument = {
|
|
|
- nombre_del_archivo: fileName,
|
|
|
- tamano_del_archivo: this.formatBytes(fileContent.length),
|
|
|
- archivo_id: fileId,
|
|
|
- tipo_archivo: this.getFileTypeFromExtension(fileName),
|
|
|
- fecha_carga: new Date().toLocaleString(),
|
|
|
- estado: 'temp_uploaded'
|
|
|
- };
|
|
|
-
|
|
|
- processedDocuments.push(processedDoc);
|
|
|
-
|
|
|
- console.log(` Procesado exitosamente: ${fileName}`);
|
|
|
-
|
|
|
- // Pausa pequeña entre archivos
|
|
|
- await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error(` Error procesando ${fileName}:`, error);
|
|
|
-
|
|
|
- // Agregar documento con error
|
|
|
- processedDocuments.push({
|
|
|
- nombre_del_archivo: fileName,
|
|
|
- tamano_del_archivo: '0 B',
|
|
|
- archivo_id: '',
|
|
|
- tipo_archivo: 'error',
|
|
|
- fecha_carga: new Date().toLocaleString(),
|
|
|
- estado: 'error',
|
|
|
- error_message: error.message
|
|
|
- });
|
|
|
+ if (this.validationResult) {
|
|
|
+ if (this.validationResult.isValid) {
|
|
|
+ return '✅ Validación exitosa';
|
|
|
+ } else {
|
|
|
+ const missingCount = this.validationResult.missingFiles.length;
|
|
|
+ return `❌ Faltan ${missingCount} documento(s)`;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
+ return `Listo para validar (${expectedDocuments.length} documentos esperados)`;
|
|
|
+ }
|
|
|
|
|
|
- // Actualizar la lista de documentos procesados
|
|
|
- this.processedDocuments = processedDocuments;
|
|
|
- this.updateAllTableData();
|
|
|
-
|
|
|
- const successCount = processedDocuments.filter(doc => doc.estado === 'temp_uploaded').length;
|
|
|
- const errorCount = processedDocuments.filter(doc => doc.estado === 'error').length;
|
|
|
-
|
|
|
- if (successCount > 0) {
|
|
|
- this._resourcesService.openSnackBar(
|
|
|
- ` ${successCount} documentos procesados${errorCount > 0 ? `, ${errorCount} errores` : ''}`
|
|
|
- );
|
|
|
- } else {
|
|
|
- throw new Error('No se pudo procesar ningún documento');
|
|
|
+ get requirementsMessage(): string {
|
|
|
+ if (!this.hasExcelFile && !this.hasZipFile) {
|
|
|
+ return 'Se necesita cargar el archivo Excel y el ZIP';
|
|
|
+ } else if (!this.hasExcelFile) {
|
|
|
+ return 'Se necesita cargar el archivo Excel';
|
|
|
+ } else if (!this.hasZipFile) {
|
|
|
+ return 'Se necesita cargar el archivo ZIP';
|
|
|
}
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error('Error en procesamiento:', error);
|
|
|
- this._resourcesService.openSnackBar('Error: ' + (error.message || 'Error desconocido'));
|
|
|
- } finally {
|
|
|
- this.isLoading = false;
|
|
|
+ return 'Archivos cargados correctamente';
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-//Método para extraer contenido de archivo desde ZIP
|
|
|
-private async extractFileContentFromZip(fileName: string): Promise<Uint8Array | null> {
|
|
|
- if (!this.originalZipFile) {
|
|
|
- console.error('No hay archivo ZIP original disponible');
|
|
|
- return null;
|
|
|
+ get currentStep(): string {
|
|
|
+ return this.validationStep || (this.isValidating ? 'Validando...' : '');
|
|
|
}
|
|
|
|
|
|
- return new Promise((resolve) => {
|
|
|
- const reader = new FileReader();
|
|
|
- reader.onload = async (e: any) => {
|
|
|
- try {
|
|
|
- const zipData = new Uint8Array(e.target.result);
|
|
|
- const zip = new JSZip();
|
|
|
- const zipContent = await zip.loadAsync(zipData);
|
|
|
-
|
|
|
- // Buscar el archivo específico en el ZIP
|
|
|
- const targetFile = Object.entries(zipContent.files).find(([path, entry]) => {
|
|
|
- const entryFileName = path.split('/').pop()?.toLowerCase();
|
|
|
- return entryFileName === fileName.toLowerCase() && !entry.dir;
|
|
|
- });
|
|
|
-
|
|
|
- if (targetFile) {
|
|
|
- const [filePath, zipEntry] = targetFile;
|
|
|
- const content = await zipEntry.async('uint8array');
|
|
|
- resolve(content);
|
|
|
- } else {
|
|
|
- console.error(`Archivo ${fileName} no encontrado en ZIP`);
|
|
|
- resolve(null);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('Error extrayendo archivo del ZIP:', error);
|
|
|
- resolve(null);
|
|
|
- }
|
|
|
- };
|
|
|
- reader.onerror = () => resolve(null);
|
|
|
- reader.readAsArrayBuffer(this.originalZipFile!);
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-//Método auxiliar para obtener tipo de archivo
|
|
|
-private getFileTypeFromExtension(fileName: string): string {
|
|
|
- const extension = fileName.toLowerCase().split('.').pop();
|
|
|
-
|
|
|
- const typeMap: { [key: string]: string } = {
|
|
|
- 'pdf': 'pdf',
|
|
|
- 'doc': 'docx',
|
|
|
- 'docx': 'docx',
|
|
|
- 'xlsx': 'excel',
|
|
|
- 'xls': 'excel',
|
|
|
- 'jpg': 'imagen',
|
|
|
- 'jpeg': 'imagen',
|
|
|
- 'png': 'imagen',
|
|
|
- 'gif': 'imagen',
|
|
|
- 'bmp': 'imagen',
|
|
|
- 'svg': 'imagen',
|
|
|
- 'txt': 'texto',
|
|
|
- 'zip': 'archivo',
|
|
|
- 'rar': 'archivo'
|
|
|
- };
|
|
|
-
|
|
|
- return typeMap[extension || ''] || 'documento';
|
|
|
-}
|
|
|
-
|
|
|
-// Método actualizado para manejar el resultado del preview
|
|
|
-previewZipFile(): void {
|
|
|
- if (!this.zipData) {
|
|
|
- this._resourcesService.openSnackBar('No hay datos del archivo ZIP para mostrar');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const dialogRef = this._matDialog.open(PreviewFileContentComponent, {
|
|
|
- width: '1200px',
|
|
|
- height: '800px',
|
|
|
- maxWidth: '1200px',
|
|
|
- data: {
|
|
|
- fileName: this.zipData.fileName,
|
|
|
- fileType: 'zip',
|
|
|
- zipContent: this.zipData,
|
|
|
- fileId: this.zipData.fileId,
|
|
|
- excelData: this.excelData // Pasar datos del Excel para validación
|
|
|
- },
|
|
|
- disableClose: true
|
|
|
- });
|
|
|
-
|
|
|
- dialogRef.afterClosed().subscribe(result => {
|
|
|
- if (result && result.action === 'validate' && result.validationResult) {
|
|
|
- console.log('Validación completada:', result);
|
|
|
-
|
|
|
- // Procesar documentos validados
|
|
|
- this.processValidatedDocumentsFromPreview(result.validationResult);
|
|
|
+ get validationSummary(): string {
|
|
|
+ if (!this.validationResult) {
|
|
|
+ const expectedDocuments = this.extractDocumentNamesFromExcel();
|
|
|
+ return `${expectedDocuments.length} documentos por validar`;
|
|
|
}
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-// Método para actualizar todas las tablas
|
|
|
-private updateAllTableData(): void {
|
|
|
- // Esto se actualizará según el nuevo HTML
|
|
|
-}
|
|
|
-
|
|
|
-// Getters para validar estado
|
|
|
-get hasRequiredFiles(): boolean {
|
|
|
- return this.hasExcelFile && this.hasZipFile;
|
|
|
-}
|
|
|
-
|
|
|
-get canProcessDocuments(): boolean {
|
|
|
- return this.hasRequiredFiles && this.processedDocuments.length > 0;
|
|
|
-}
|
|
|
-
|
|
|
-get requirementsMessage(): string {
|
|
|
- if (!this.hasExcelFile && !this.hasZipFile) {
|
|
|
- return 'Se necesita cargar el archivo Excel y el ZIP';
|
|
|
- } else if (!this.hasExcelFile) {
|
|
|
- return 'Se necesita cargar el archivo Excel';
|
|
|
- } else if (!this.hasZipFile) {
|
|
|
- return 'Se necesita cargar el archivo ZIP';
|
|
|
- }
|
|
|
- return '';
|
|
|
-}
|
|
|
-
|
|
|
-async saveFinalFile(): Promise<void> {
|
|
|
- const documentsToSave = this.processedDocuments.filter(doc =>
|
|
|
- doc.estado === 'temp_uploaded' && doc.archivo_id
|
|
|
- );
|
|
|
-
|
|
|
- if (documentsToSave.length === 0) {
|
|
|
- this._resourcesService.openSnackBar('No hay documentos procesados para guardar');
|
|
|
- return;
|
|
|
- }
|
|
|
|
|
|
- try {
|
|
|
- this.isLoading = true;
|
|
|
- const idUser = localStorage.getItem('idusuario');
|
|
|
+ const { matchedCount, expectedCount } = this.validationResult.validationDetails;
|
|
|
+ const missing = this.validationResult.missingFiles.length;
|
|
|
+ const extra = this.validationResult.extraFiles.length;
|
|
|
|
|
|
- let savedCount = 0;
|
|
|
- let errorCount = 0;
|
|
|
-
|
|
|
- for (const doc of documentsToSave) {
|
|
|
- try {
|
|
|
- const formData = new FormData();
|
|
|
- formData.append('id_user', idUser!);
|
|
|
- formData.append('id_file', doc.archivo_id);
|
|
|
- formData.append('linea', '1');
|
|
|
- formData.append('module', 'ADSI');
|
|
|
- formData.append('clasification', 'LA');
|
|
|
- formData.append('has_order', 'N');
|
|
|
-
|
|
|
- const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
|
|
|
-
|
|
|
- if (response.error) {
|
|
|
- throw new Error(response.msg || 'Error al guardar');
|
|
|
- }
|
|
|
-
|
|
|
- // Actualizar estado del documento
|
|
|
- doc.estado = 'final_saved';
|
|
|
- savedCount++;
|
|
|
-
|
|
|
- console.log(` Guardado: ${doc.nombre_del_archivo}`);
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error(` Error guardando ${doc.nombre_del_archivo}:`, error);
|
|
|
- doc.estado = 'error';
|
|
|
- doc.error_message = error.message;
|
|
|
- errorCount++;
|
|
|
- }
|
|
|
+ let summary = `${matchedCount}/${expectedCount} documentos encontrados`;
|
|
|
+
|
|
|
+ if (missing > 0) {
|
|
|
+ summary += `, ${missing} faltantes`;
|
|
|
}
|
|
|
-
|
|
|
- // Actualizar tabla
|
|
|
- this.updateAllTableData();
|
|
|
-
|
|
|
- if (savedCount > 0) {
|
|
|
- const message = ` ${savedCount} documentos guardados exitosamente${errorCount > 0 ? `, ${errorCount} errores` : ''}`;
|
|
|
- this._resourcesService.openSnackBar(message);
|
|
|
-
|
|
|
- if (errorCount === 0) {
|
|
|
- // Solo cerrar si todos se guardaron exitosamente
|
|
|
- this.dialogRef.close(true);
|
|
|
- }
|
|
|
- } else {
|
|
|
- throw new Error('No se pudo guardar ningún documento');
|
|
|
+
|
|
|
+ if (extra > 0) {
|
|
|
+ summary += `, ${extra} extra`;
|
|
|
}
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error('Error al guardar archivos:', error);
|
|
|
- this._resourcesService.openSnackBar('Error: ' + (error.message || 'Error desconocido'));
|
|
|
- } finally {
|
|
|
- this.isLoading = false;
|
|
|
+
|
|
|
+ return summary;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
|
|
|
+ get validationClass(): string {
|
|
|
+ if (!this.validationResult) return '';
|
|
|
+ return this.validationResult.isValid ? 'validation-success' : 'validation-error';
|
|
|
+ }
|
|
|
}
|