From 37d87dbd50e55c13f55b70d3d3e553d05aa4ba63 Mon Sep 17 00:00:00 2001 From: luis cespedes Date: Mon, 5 May 2025 14:51:44 -0400 Subject: [PATCH] botones excell y prueba exportar --- package-lock.json | 127 +++++++++++ package.json | 4 + .../actualizacion-pd.component.html | 204 +++++++++++------ .../actualizacion-pd.component.ts | 101 ++++++++- .../pages/ajuste-pd/ajuste-pd.component.html | 211 ++++++++++-------- .../pages/ajuste-pd/ajuste-pd.component.ts | 129 ++++++++++- 6 files changed, 607 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9940a9f..42380ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,18 +18,22 @@ "@fortawesome/fontawesome-free": "^6.7.2", "@primeng/themes": "^19.1.0", "animate.css": "^4.1.1", + "file-saver": "^2.0.5", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primeng": "^19.1.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "xlsx": "^0.18.5", "zone.js": "~0.15.0" }, "devDependencies": { "@angular-devkit/build-angular": "^19.2.9", "@angular/cli": "^19.2.9", "@angular/compiler-cli": "^19.2.0", + "@types/file-saver": "^2.0.7", "@types/jasmine": "~5.1.0", + "@types/xlsx": "^0.0.35", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -5160,6 +5164,13 @@ "@types/send": "*" } }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -5292,6 +5303,13 @@ "@types/node": "*" } }, + "node_modules/@types/xlsx": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@types/xlsx/-/xlsx-0.0.35.tgz", + "integrity": "sha512-s0x3DYHZzOkxtjqOk/Nv1ezGzpbN7I8WX+lzlV/nFfTDOv7x4d8ZwGHcnaiB8UCx89omPsftQhS5II3jeWePxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", @@ -5563,6 +5581,15 @@ "node": ">=8.9.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -6313,6 +6340,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6555,6 +6595,15 @@ "node": ">=0.10.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6852,6 +6901,18 @@ } } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7847,6 +7908,12 @@ } } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8008,6 +8075,15 @@ "node": ">= 0.6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -13030,6 +13106,18 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", @@ -14624,6 +14712,24 @@ "dev": true, "license": "MIT" }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -14797,6 +14903,27 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index d5778c5..8c63519 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,22 @@ "@fortawesome/fontawesome-free": "^6.7.2", "@primeng/themes": "^19.1.0", "animate.css": "^4.1.1", + "file-saver": "^2.0.5", "primeflex": "^4.0.0", "primeicons": "^7.0.0", "primeng": "^19.1.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "xlsx": "^0.18.5", "zone.js": "~0.15.0" }, "devDependencies": { "@angular-devkit/build-angular": "^19.2.9", "@angular/cli": "^19.2.9", "@angular/compiler-cli": "^19.2.0", + "@types/file-saver": "^2.0.7", "@types/jasmine": "~5.1.0", + "@types/xlsx": "^0.0.35", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.html b/src/app/pages/actualizacion-pd/actualizacion-pd.component.html index 2d53ead..37ba6b5 100644 --- a/src/app/pages/actualizacion-pd/actualizacion-pd.component.html +++ b/src/app/pages/actualizacion-pd/actualizacion-pd.component.html @@ -1,84 +1,144 @@
-
-
-
-
Filtro Empresa
- -
+
+
+
+
Filtro Empresa
+ +
-
-
Filtro Código Cronograma
- -
+
+
Filtro Código Cronograma
+ +
- -
Título de la tabla:
- +
+
Título de la tabla:
+ + +
+ - - - Empresa - Código de cronograma - Etapa del Servicio - Nombre sistema - Tipo de inversión - Código de glosa PD - Descripción glosa - Monto Inversión Total (UF) - Año de Inicio - Año de Término - Mes de Término - Nota - Estado aprobación - Observación - - - - - {{ product.empresa }} - {{ product.codigoCronograma }} - {{ product.codigoCronogramaAjuste }} - {{ product.tipoCarga }} - {{ product.estadoRevision }} - {{ product.estadoRevision }} - {{ product.fechaIngreso }} - -
- -
- - {{ product.dato9 }} - {{ product.dato10 }} - {{ product.dato11 }} - {{ product.dato12 }} - -
- -
- - - - -
-
-
- -
+ [rowsPerPageOptions]="[5, 10, 20]" + styleClass="p-datatable-sm" + > + + + Empresa + Código de cronograma + Etapa del Servicio + Nombre sistema + Tipo de inversión + Código de glosa PD + Descripción glosa + Monto Inversión Total (UF) + Año de Inicio + Año de Término + Mes de Término + Nota + Estado aprobación + Observación + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + {{ product.estadoRevision }} + {{ product.fechaIngreso }} + +
+ +
+ + {{ product.dato9 }} + {{ product.dato10 }} + {{ product.dato11 }} + {{ product.dato12 }} + +
+ +
+ + + + + +
+
+ +
+ +
diff --git a/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts b/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts index bd93f78..3a024b9 100644 --- a/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts +++ b/src/app/pages/actualizacion-pd/actualizacion-pd.component.ts @@ -1,14 +1,27 @@ import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { TableModule } from 'primeng/table'; +import { Table, TableModule } from 'primeng/table'; import { InputTextModule } from 'primeng/inputtext'; import { SelectModule } from 'primeng/select'; +import { ButtonModule } from 'primeng/button'; +import { TooltipModule } from 'primeng/tooltip'; +import * as FileSaver from 'file-saver'; +import * as XLSX from 'xlsx'; @Component({ selector: 'app-actualizacion-pd', - imports: [FormsModule, TableModule, InputTextModule, SelectModule], + imports: [ + FormsModule, + TableModule, + InputTextModule, + SelectModule, + ButtonModule, + TooltipModule + + ], templateUrl: './actualizacion-pd.component.html', - styleUrl: './actualizacion-pd.component.scss' + styleUrl: './actualizacion-pd.component.scss', + standalone: true }) export class ActualizacionPdComponent { pageTitle: string = 'Cronogramas cargados:'; @@ -19,7 +32,6 @@ export class ActualizacionPdComponent { { name: 'Aprobado', value: 'Aprobado' }, { name: 'Rechazado', value: 'Rechazado' }, ]; - products: any[] = [ { empresa: 'Empresa A', @@ -81,4 +93,83 @@ export class ActualizacionPdComponent { }, ]; -} + /** + * Exporta la tabla a Excel + * @param table Referencia a la tabla PrimeNG + */ + exportExcel(table: Table) { + // Preparamos los datos para exportar + const exportData = this.prepareDataForExport(table); + + // Creamos el libro de Excel + const worksheet = XLSX.utils.json_to_sheet(exportData); + const workbook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] }; + + // Aplicamos estilos a las celdas (encabezados en negrita con fondo azul) + this.applyStyles(worksheet); + + // Opciones de exportación + const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); + + // Guardamos el archivo + this.saveAsExcelFile(excelBuffer, 'cronogramas_exportados'); + } + + /** + * Prepara los datos para la exportación, filtrando según sea necesario + */ + prepareDataForExport(table: Table): any[] { + // Si hay filtros aplicados, usamos los datos filtrados + const data = table.filteredValue || table.value; + + // Mapeamos los datos para tener solo las propiedades que queremos exportar + return data.map(item => { + return { + 'Empresa': item.empresa, + 'Código de cronograma': item.codigoCronograma, + 'Etapa del Servicio': item.codigoCronogramaAjuste, + 'Nombre sistema': item.tipoCarga, + 'Tipo de inversión': item.estadoRevision, + 'Código de glosa PD': item.analista, + 'Descripción glosa': item.fechaIngreso, + 'Monto Inversión Total (UF)': '', + 'Año de Inicio': item.dato9, + 'Año de Término': item.dato10, + 'Mes de Término': item.dato11 || '', + 'Nota': item.dato12 || '', + 'Estado aprobación': item.dato13, + 'Observación': '' + }; + }); + } + + /** + * Aplica estilos al worksheet de Excel + */ + applyStyles(worksheet: XLSX.WorkSheet) { + // Establecemos el estilo para los encabezados + const headerStyle = { + font: { bold: true, color: { rgb: 'FFFFFF' } }, + fill: { fgColor: { rgb: '0070C0' } }, + alignment: { horizontal: 'center' } + }; + + // Aplicar estilos a los encabezados (primera fila) + const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1'); + for (let col = range.s.c; col <= range.e.c; col++) { + const cellRef = XLSX.utils.encode_cell({ r: 0, c: col }); + if (!worksheet[cellRef]) worksheet[cellRef] = { t: 's', v: '' }; + worksheet[cellRef].s = headerStyle; + } + } + + /** + * Guarda el buffer como un archivo Excel + */ + saveAsExcelFile(buffer: any, fileName: string): void { + const EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; + const EXCEL_EXTENSION = '.xlsx'; + const data: Blob = new Blob([buffer], { type: EXCEL_TYPE }); + FileSaver.saveAs(data, fileName + '_' + new Date().getTime() + EXCEL_EXTENSION); + } +} \ No newline at end of file diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.html b/src/app/pages/ajuste-pd/ajuste-pd.component.html index 6ac2ee6..5b30fc6 100644 --- a/src/app/pages/ajuste-pd/ajuste-pd.component.html +++ b/src/app/pages/ajuste-pd/ajuste-pd.component.html @@ -1,98 +1,135 @@
-
-
-
-
Filtro Empresa
- -
+
+
+
+
Filtro Empresa
+ +
-
-
Filtro Código Cronograma
- -
+
+
Filtro Código Cronograma
+ +
- - - + + - - - - Cronograma base vigente - Cronograma base ajustado - - - Empresa - Código de cronograma - Etapa del Servicio - Nombre sistema - Nombre sistema - Nombre localidad - Tipo de inversión - Código de glosa PD - Descripción glosa - Monto Inversión Total (UF) - Año de Inicio - Año de Término - Mes de Término - Tipo de ajuste - Año de Inicio - Año de Término - Mes de Término - Nota - Estado aprobación - Observación - - - - - {{ product.empresa }} - {{ product.codigoCronograma }} - {{ product.codigoCronogramaAjuste }} - {{ product.tipoCarga }} - {{ product.estadoRevision }} - {{ product.estadoRevision }} - {{ product.fechaIngreso }} - {{ product.estadoRevision }} - {{ product.dato9 }} - {{ product.dato10 }} - {{ product.dato13 }} - {{ product.dato14 }} - {{ product.dato15 }} - {{ product.dato16 }} - {{ product.dato17 }} - {{ product.dato18 }} - {{ product.dato11 }} - {{ product.dato12 }} - -
- -
- - - - -
-
-
- -
- - + [rowsPerPageOptions]="[5, 10, 20]" + > + + + + Cronograma base vigente + Cronograma base ajustado + + + Empresa + Código de cronograma + Etapa del Servicio + Nombre sistema + Nombre sistema + Nombre localidad + Tipo de inversión + Código de glosa PD + Descripción glosa + Monto Inversión Total (UF) + Año de Inicio + Año de Término + Mes de Término + Tipo de ajuste + Año de Inicio + Año de Término + Mes de Término + Nota + Estado aprobación + Observación + + + + + {{ product.empresa }} + {{ product.codigoCronograma }} + {{ product.codigoCronogramaAjuste }} + {{ product.tipoCarga }} + {{ product.estadoRevision }} + {{ product.estadoRevision }} + {{ product.fechaIngreso }} + {{ product.estadoRevision }} + {{ product.dato9 }} + {{ product.dato10 }} + {{ product.dato13 }} + {{ product.dato14 }} + {{ product.dato15 }} + {{ product.dato16 }} + {{ product.dato17 }} + {{ product.dato18 }} + {{ product.dato11 }} + {{ product.dato12 }} + +
+ +
+ + + + + +
+
+
+ +
diff --git a/src/app/pages/ajuste-pd/ajuste-pd.component.ts b/src/app/pages/ajuste-pd/ajuste-pd.component.ts index 5bc6924..33454ac 100644 --- a/src/app/pages/ajuste-pd/ajuste-pd.component.ts +++ b/src/app/pages/ajuste-pd/ajuste-pd.component.ts @@ -1,22 +1,31 @@ import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { TableModule } from 'primeng/table'; +import { TableModule ,Table} from 'primeng/table'; import { InputTextModule } from 'primeng/inputtext'; import { SelectModule } from 'primeng/select'; +import { TooltipModule } from 'primeng/tooltip'; +import { ButtonModule } from 'primeng/button'; +import * as FileSaver from 'file-saver'; +import * as XLSX from 'xlsx'; @Component({ selector: 'app-ajuste-pd', - imports: [FormsModule, TableModule, InputTextModule, SelectModule], + imports: [ + FormsModule, + TableModule, + InputTextModule, + SelectModule, + TooltipModule, + ButtonModule + ], templateUrl: './ajuste-pd.component.html', styleUrl: './ajuste-pd.component.scss' }) export class AjustePdComponent { selectedCity: any = ''; empresas: any[] = [{name: 'Empresa A'}, {name: 'Empresa B'}, {name: 'Empresa C'}]; - select1: any = ''; estadoAprobacion: any[] = [{name:'Rechazado'}, {name:'Aprobado'}]; - products: any[] = [ { empresa: 'Empresa A', @@ -92,4 +101,114 @@ export class AjustePdComponent { }, ]; -} + /** + * Exporta los datos de la tabla a un archivo Excel + * @param table Referencia a la tabla PrimeNG + */ + exportExcel(table: Table) { + try { + // Obtenemos los datos a exportar (usamos los datos filtrados si existen) + const dataToExport = this.prepareDataForExport(table); + + // Creamos el libro de Excel + const worksheet = XLSX.utils.json_to_sheet(dataToExport); + const workbook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] }; + + // Aplicamos estilos a las celdas + this.applyExcelStyles(worksheet); + + // Opciones de exportación + const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); + + // Guardamos el archivo + this.saveAsExcelFile(excelBuffer, 'ajuste_pd'); + + console.log('Exportación a Excel completada con éxito'); + } catch (error) { + console.error('Error al exportar a Excel:', error); + } + } + + /** + * Prepara los datos para la exportación, traduciendo los nombres de columnas + * @param table Referencia a la tabla PrimeNG + * @returns Array de objetos con los datos formateados para exportar + */ + private prepareDataForExport(table: Table): any[] { + // Si hay filtros aplicados, usamos los datos filtrados, de lo contrario todos los datos + const data = table.filteredValue || table.value; + + // Mapeamos los datos para tener nombres de columnas en español y legibles + return data.map(item => { + return { + 'Empresa': item.empresa, + 'Código de cronograma': item.codigoCronograma, + 'Etapa del Servicio': item.codigoCronogramaAjuste, + 'Nombre sistema': item.tipoCarga, + 'Tipo de inversión': item.estadoRevision, + 'Código de glosa PD': item.analista, + 'Descripción glosa': item.fechaIngreso, + 'Año de Inicio': item.dato9, + 'Año de Término': item.dato10, + 'Estado aprobación': item.dato13 + }; + }); + } + + /** + * Aplica estilos al worksheet de Excel para mejorar la presentación + * @param worksheet Hoja de Excel a la que aplicar estilos + */ + private applyExcelStyles(worksheet: XLSX.WorkSheet) { + // Definimos estilos para los encabezados + const headerStyle = { + font: { bold: true, color: { rgb: 'FFFFFF' } }, + fill: { fgColor: { rgb: '007ACC' } }, // Color azul para los encabezados + alignment: { horizontal: 'center', vertical: 'center' } + }; + + // Aplicamos estilos a los encabezados (primera fila) + const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1'); + for (let col = range.s.c; col <= range.e.c; col++) { + const cellRef = XLSX.utils.encode_cell({ r: 0, c: col }); + if (!worksheet[cellRef]) worksheet[cellRef] = { t: 's', v: '' }; + + // Asignamos el estilo al encabezado + worksheet[cellRef].s = headerStyle; + } + + // Ajustamos el ancho de las columnas automáticamente + const colWidths = []; + for (let col = range.s.c; col <= range.e.c; col++) { + colWidths.push({ wch: 15 }); // Ancho predeterminado + } + worksheet['!cols'] = colWidths; + } + + /** + * Guarda el buffer como un archivo Excel + * @param buffer Datos del archivo Excel + * @param fileName Nombre base del archivo + */ + private saveAsExcelFile(buffer: any, fileName: string): void { + // Definimos el tipo MIME para archivos Excel + const EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; + const EXCEL_EXTENSION = '.xlsx'; + + // Creamos un Blob con los datos + const data: Blob = new Blob([buffer], { type: EXCEL_TYPE }); + + // Generamos un nombre de archivo con timestamp para evitar duplicados + const today = new Date(); + const formattedDate = + today.getFullYear().toString() + + (today.getMonth() + 1).toString().padStart(2, '0') + + today.getDate().toString().padStart(2, '0') + + '_' + + today.getHours().toString().padStart(2, '0') + + today.getMinutes().toString().padStart(2, '0'); + + // Guardamos el archivo + FileSaver.saveAs(data, `${fileName}_${formattedDate}${EXCEL_EXTENSION}`); + } +} \ No newline at end of file