@acontplus/ng-common 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -36
- package/fesm2022/acontplus-ng-common.mjs +296 -123
- package/fesm2022/acontplus-ng-common.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acontplus-ng-common.d.ts +203 -16
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ import {
|
|
|
134
134
|
export class ReportsComponent {
|
|
135
135
|
constructor(private reportFacade: ReportFacade) {}
|
|
136
136
|
|
|
137
|
-
// Ejemplo 1:
|
|
137
|
+
// Ejemplo 1: Generar y abrir PDF automáticamente
|
|
138
138
|
generateInvoiceReport(id: number, codEstab: string) {
|
|
139
139
|
const reportOptions = ReportParamsBuilder.build(
|
|
140
140
|
{
|
|
@@ -142,16 +142,18 @@ export class ReportsComponent {
|
|
|
142
142
|
id,
|
|
143
143
|
codEstab
|
|
144
144
|
},
|
|
145
|
-
REPORT_FORMAT.PDF
|
|
146
|
-
true // returnBlob
|
|
145
|
+
REPORT_FORMAT.PDF
|
|
147
146
|
);
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// Se abre automáticamente en nueva ventana (o descarga en navegadores legacy)
|
|
149
|
+
this.reportFacade.openPDF(reportOptions.data, reportOptions.useV1Api)
|
|
150
|
+
.subscribe({
|
|
151
|
+
next: () => console.log('Reporte generado'),
|
|
152
|
+
error: (err) => console.error('Error:', err)
|
|
153
|
+
});
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
// Ejemplo 2:
|
|
156
|
+
// Ejemplo 2: Generar Excel y descargar automáticamente
|
|
155
157
|
generateSalesReport() {
|
|
156
158
|
const reportOptions = ReportParamsBuilder.build(
|
|
157
159
|
{
|
|
@@ -165,47 +167,54 @@ export class ReportsComponent {
|
|
|
165
167
|
REPORT_FORMAT.EXCEL
|
|
166
168
|
);
|
|
167
169
|
|
|
168
|
-
this.reportFacade.
|
|
170
|
+
this.reportFacade.downloadExcel(reportOptions.data, reportOptions.useV1Api)
|
|
171
|
+
.subscribe();
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
// Ejemplo 3:
|
|
172
|
-
|
|
174
|
+
// Ejemplo 3: Control total sobre el blob
|
|
175
|
+
generateCustomReport() {
|
|
173
176
|
const reportOptions = ReportParamsBuilder.build(
|
|
174
177
|
{
|
|
175
178
|
codDoc: INVENTORY_CODE_REPORT.ARTICULO_PVP,
|
|
176
179
|
tipo: 2,
|
|
177
180
|
stockStatusCode: 1,
|
|
178
|
-
idTarifa: 3
|
|
179
|
-
idMarca: 10,
|
|
180
|
-
fechaInicio: this.fromDate,
|
|
181
|
-
fechaFin: this.toDate,
|
|
182
|
-
// Cualquier parámetro adicional se incluye automáticamente
|
|
183
|
-
customParam: 'valor'
|
|
181
|
+
idTarifa: 3
|
|
184
182
|
},
|
|
185
183
|
REPORT_FORMAT.PDF
|
|
186
184
|
);
|
|
187
185
|
|
|
188
|
-
this.reportFacade.generate(reportOptions)
|
|
186
|
+
this.reportFacade.generate(reportOptions)
|
|
187
|
+
.subscribe({
|
|
188
|
+
next: (response) => {
|
|
189
|
+
// Tienes control total del blob
|
|
190
|
+
const blob = response.body;
|
|
191
|
+
if (blob) {
|
|
192
|
+
// Hacer lo que quieras: enviar por WhatsApp, guardar, etc.
|
|
193
|
+
this.sendViaWhatsApp(blob);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
189
197
|
}
|
|
190
198
|
|
|
191
|
-
// Ejemplo 4:
|
|
192
|
-
|
|
193
|
-
const reportCode = useCustom
|
|
194
|
-
? SALE_CODE_REPORT.SRRC
|
|
195
|
-
: SALE_CODE_REPORT.SRR;
|
|
196
|
-
|
|
199
|
+
// Ejemplo 4: Usar métodos genéricos
|
|
200
|
+
quickPDFReport() {
|
|
197
201
|
const reportOptions = ReportParamsBuilder.build(
|
|
198
|
-
{
|
|
199
|
-
codDoc: reportCode,
|
|
200
|
-
porcentajeRenta: 15,
|
|
201
|
-
porcentajeComision: 5,
|
|
202
|
-
fechaInicio: this.fromDate,
|
|
203
|
-
fechaFin: this.toDate
|
|
204
|
-
},
|
|
202
|
+
{ codDoc: SALE_CODE_REPORT.SRR },
|
|
205
203
|
REPORT_FORMAT.PDF
|
|
206
204
|
);
|
|
207
205
|
|
|
208
|
-
|
|
206
|
+
// Abre automáticamente
|
|
207
|
+
this.reportFacade.open(reportOptions).subscribe();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
quickExcelDownload() {
|
|
211
|
+
const reportOptions = ReportParamsBuilder.build(
|
|
212
|
+
{ codDoc: INVENTORY_CODE_REPORT.RCD },
|
|
213
|
+
REPORT_FORMAT.EXCEL
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Descarga automáticamente
|
|
217
|
+
this.reportFacade.download(reportOptions).subscribe();
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
// Verificar tipos soportados
|
|
@@ -213,11 +222,6 @@ export class ReportsComponent {
|
|
|
213
222
|
const types = ReportParamsBuilder.getSupportedDocumentTypes();
|
|
214
223
|
console.log('Tipos soportados:', types);
|
|
215
224
|
}
|
|
216
|
-
|
|
217
|
-
// Verificar si un tipo está soportado
|
|
218
|
-
isSupported(code: string) {
|
|
219
|
-
return ReportParamsBuilder.isDocumentTypeSupported(code);
|
|
220
|
-
}
|
|
221
225
|
}
|
|
222
226
|
```
|
|
223
227
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, Injectable, signal, Input, Component } from '@angular/core';
|
|
3
|
+
import { tap, Observable, throwError } from 'rxjs';
|
|
3
4
|
import { HttpClient } from '@angular/common/http';
|
|
4
|
-
import { Observable, throwError } from 'rxjs';
|
|
5
5
|
import * as i1 from '@angular/forms';
|
|
6
6
|
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
7
7
|
import { CommonModule } from '@angular/common';
|
|
@@ -168,9 +168,9 @@ var REPORT_FORMAT;
|
|
|
168
168
|
})(REPORT_FORMAT || (REPORT_FORMAT = {}));
|
|
169
169
|
|
|
170
170
|
const API_PATHS = {
|
|
171
|
-
WHATSAPP: '
|
|
172
|
-
REPORTS: '
|
|
173
|
-
CONFIG: '
|
|
171
|
+
WHATSAPP: 'common/whatsapp-cloud',
|
|
172
|
+
REPORTS: 'reports',
|
|
173
|
+
CONFIG: 'config',
|
|
174
174
|
};
|
|
175
175
|
const DOCUMENT_TYPES = {
|
|
176
176
|
NORMAL: 'NORMAL',
|
|
@@ -184,40 +184,307 @@ const WHATSAPP_MESSAGING_PORT = new InjectionToken('WHATSAPP_MESSAGING_PORT');
|
|
|
184
184
|
|
|
185
185
|
const PRINTER_PORT = new InjectionToken('PRINTER_PORT');
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Utilidades para manejo de archivos y respuestas HTTP
|
|
189
|
+
*/
|
|
190
|
+
class FileMapperUtil {
|
|
191
|
+
/**
|
|
192
|
+
* Extrae el nombre de archivo desde el header Content-Disposition
|
|
193
|
+
*/
|
|
194
|
+
static extractFileName(response) {
|
|
195
|
+
const contentDisposition = response.headers?.get('content-disposition');
|
|
196
|
+
if (!contentDisposition) {
|
|
197
|
+
return this.generateDefaultFileName();
|
|
198
|
+
}
|
|
199
|
+
// Método más robusto para extraer filename
|
|
200
|
+
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
|
|
201
|
+
if (matches && matches[1]) {
|
|
202
|
+
let fileName = matches[1].replace(/['"]/g, '');
|
|
203
|
+
// Limpiar caracteres especiales
|
|
204
|
+
fileName = fileName.replaceAll('+', ' ').trim();
|
|
205
|
+
return fileName || this.generateDefaultFileName();
|
|
206
|
+
}
|
|
207
|
+
// Fallback: método simple
|
|
208
|
+
const simpleParse = contentDisposition.split('filename=')[1];
|
|
209
|
+
if (simpleParse) {
|
|
210
|
+
return simpleParse.replace(/['"]/g, '').trim() || this.generateDefaultFileName();
|
|
211
|
+
}
|
|
212
|
+
return this.generateDefaultFileName();
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Crea un objeto File desde una respuesta HTTP
|
|
216
|
+
*/
|
|
217
|
+
static fromResponse(response) {
|
|
218
|
+
const fileName = this.extractFileName(response);
|
|
219
|
+
const contentType = response.headers?.get('content-type') || 'application/octet-stream';
|
|
220
|
+
return new File([response.body], fileName, { type: contentType });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Descarga un archivo usando la API nativa del navegador
|
|
224
|
+
*/
|
|
225
|
+
static downloadFile(blob, fileName) {
|
|
226
|
+
const url = URL.createObjectURL(blob);
|
|
227
|
+
const link = document.createElement('a');
|
|
228
|
+
link.href = url;
|
|
229
|
+
link.download = fileName;
|
|
230
|
+
link.style.display = 'none';
|
|
231
|
+
document.body.appendChild(link);
|
|
232
|
+
link.click();
|
|
233
|
+
document.body.removeChild(link);
|
|
234
|
+
// Limpiar el objeto URL
|
|
235
|
+
URL.revokeObjectURL(url);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Abre un archivo en una nueva ventana (para PDFs)
|
|
239
|
+
*/
|
|
240
|
+
static openFile(blob, fileName) {
|
|
241
|
+
const fileURL = URL.createObjectURL(blob);
|
|
242
|
+
window.open(fileURL, fileName);
|
|
243
|
+
// Limpiar URL después de un tiempo
|
|
244
|
+
setTimeout(() => URL.revokeObjectURL(fileURL), 1000);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Detecta si es un navegador legacy que requiere descarga forzada
|
|
248
|
+
*/
|
|
249
|
+
static isLegacyBrowser() {
|
|
250
|
+
const userAgent = navigator.userAgent;
|
|
251
|
+
return !!(userAgent.match(/Edge/g) || userAgent.match(/.NET/g) || userAgent.match(/MSIE/g));
|
|
252
|
+
}
|
|
253
|
+
static generateDefaultFileName() {
|
|
254
|
+
const timestamp = Date.now();
|
|
255
|
+
return `document_${timestamp}.pdf`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Facade para generación de reportes
|
|
261
|
+
*
|
|
262
|
+
* Proporciona una API de alto nivel para generar reportes con manejo automático de archivos.
|
|
263
|
+
* Todos los métodos devuelven Observables - el consumidor debe hacer subscribe().
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* // Control total del blob
|
|
268
|
+
* this.reportFacade.generate(options).subscribe(response => {
|
|
269
|
+
* const blob = response.body;
|
|
270
|
+
* // Procesar blob manualmente
|
|
271
|
+
* });
|
|
272
|
+
*
|
|
273
|
+
* // Descarga automática
|
|
274
|
+
* this.reportFacade.download(options).subscribe();
|
|
275
|
+
*
|
|
276
|
+
* // Abrir PDF automáticamente
|
|
277
|
+
* this.reportFacade.open(options).subscribe();
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
187
280
|
class ReportFacade {
|
|
188
281
|
reportPort = inject(REPORT_PORT);
|
|
189
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Genera un reporte y devuelve el Observable con el blob
|
|
284
|
+
*
|
|
285
|
+
* Este método proporciona control total sobre el blob generado.
|
|
286
|
+
* No realiza ninguna acción automática (descarga/apertura).
|
|
287
|
+
* Útil cuando necesitas procesar el blob manualmente (enviar por WhatsApp, guardar en servidor, etc.)
|
|
288
|
+
*
|
|
289
|
+
* @template T - Tipo de datos del reporte
|
|
290
|
+
* @param options - Opciones de generación del reporte
|
|
291
|
+
* @param options.data - Datos del reporte (usar ReportParamsBuilder.build())
|
|
292
|
+
* @param options.format - Formato del reporte: 'pdf' | 'excel' | 'word'
|
|
293
|
+
* @param options.useV1Api - Si debe usar la API v1 (por defecto false)
|
|
294
|
+
* @returns Observable con la respuesta HTTP que contiene el blob
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* const options = ReportParamsBuilder.build(
|
|
299
|
+
* { codDoc: ELECTRONIC_DOCUMENT_CODE.FV, id: 123 },
|
|
300
|
+
* 'pdf'
|
|
301
|
+
* );
|
|
302
|
+
*
|
|
303
|
+
* this.reportFacade.generate(options).subscribe({
|
|
304
|
+
* next: (response) => {
|
|
305
|
+
* const blob = response.body;
|
|
306
|
+
* const fileName = FileMapperUtil.extractFileName(response);
|
|
307
|
+
* // Procesar blob según necesidad
|
|
308
|
+
* },
|
|
309
|
+
* error: (err) => console.error('Error:', err)
|
|
310
|
+
* });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
190
313
|
generate(options) {
|
|
191
314
|
return this.reportPort.generate(options);
|
|
192
315
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Genera un reporte y lo descarga automáticamente
|
|
318
|
+
*
|
|
319
|
+
* Extrae el nombre del archivo desde los headers HTTP y descarga el archivo automáticamente.
|
|
320
|
+
* Funciona para cualquier formato (PDF, Excel, Word).
|
|
321
|
+
* El Observable se completa después de iniciar la descarga.
|
|
322
|
+
*
|
|
323
|
+
* @template T - Tipo de datos del reporte
|
|
324
|
+
* @param options - Opciones de generación del reporte
|
|
325
|
+
* @returns Observable que se completa después de iniciar la descarga
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* // Con formato dinámico
|
|
330
|
+
* const options = ReportParamsBuilder.build(
|
|
331
|
+
* { codDoc: SALE_CODE_REPORT.FG },
|
|
332
|
+
* this.selectedFormat // 'pdf' | 'excel' | 'word'
|
|
333
|
+
* );
|
|
334
|
+
* this.reportFacade.download(options).subscribe();
|
|
335
|
+
*
|
|
336
|
+
* // Con formato fijo
|
|
337
|
+
* const options = ReportParamsBuilder.build(
|
|
338
|
+
* { codDoc: INVENTORY_CODE_REPORT.RCD },
|
|
339
|
+
* 'excel'
|
|
340
|
+
* );
|
|
341
|
+
* this.reportFacade.download(options).subscribe();
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
download(options) {
|
|
345
|
+
return this.reportPort.generate(options).pipe(tap((response) => {
|
|
346
|
+
const fileName = FileMapperUtil.extractFileName(response);
|
|
347
|
+
if (response.body) {
|
|
348
|
+
FileMapperUtil.downloadFile(response.body, fileName);
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Genera un reporte y lo abre en nueva ventana (o descarga en navegadores legacy)
|
|
354
|
+
*
|
|
355
|
+
* Comportamiento inteligente según el navegador:
|
|
356
|
+
* - Navegadores modernos: Abre el archivo en nueva ventana/pestaña
|
|
357
|
+
* - Navegadores legacy (IE, Edge antiguo): Descarga el archivo
|
|
358
|
+
*
|
|
359
|
+
* Ideal para PDFs que el usuario quiere visualizar inmediatamente.
|
|
360
|
+
* También funciona con Excel/Word pero la experiencia puede variar según el navegador.
|
|
361
|
+
*
|
|
362
|
+
* @template T - Tipo de datos del reporte
|
|
363
|
+
* @param options - Opciones de generación del reporte
|
|
364
|
+
* @returns Observable que se completa después de abrir/descargar el archivo
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```typescript
|
|
368
|
+
* // Abrir PDF
|
|
369
|
+
* const options = ReportParamsBuilder.build(
|
|
370
|
+
* { codDoc: ELECTRONIC_DOCUMENT_CODE.FV, id: 123 },
|
|
371
|
+
* 'pdf'
|
|
372
|
+
* );
|
|
373
|
+
* this.reportFacade.open(options).subscribe();
|
|
374
|
+
*
|
|
375
|
+
* // Con formato dinámico
|
|
376
|
+
* const format = this.isPDF ? 'pdf' : 'excel';
|
|
377
|
+
* const options = ReportParamsBuilder.build({ codDoc: code }, format);
|
|
378
|
+
* this.reportFacade.open(options).subscribe();
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
open(options) {
|
|
382
|
+
return this.reportPort.generate(options).pipe(tap((response) => {
|
|
383
|
+
const fileName = FileMapperUtil.extractFileName(response);
|
|
384
|
+
if (response.body) {
|
|
385
|
+
if (FileMapperUtil.isLegacyBrowser()) {
|
|
386
|
+
FileMapperUtil.downloadFile(response.body, fileName);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
FileMapperUtil.openFile(response.body, fileName);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Genera un PDF y lo abre automáticamente (shortcut)
|
|
396
|
+
*
|
|
397
|
+
* Método de conveniencia para el caso común de generar y abrir PDFs.
|
|
398
|
+
* Equivalente a llamar open() con format: 'pdf'.
|
|
399
|
+
*
|
|
400
|
+
* @template T - Tipo de datos del reporte
|
|
401
|
+
* @param data - Datos del reporte (resultado de ReportParamsBuilder.build().data)
|
|
402
|
+
* @param useV1Api - Si debe usar la API v1 (por defecto false)
|
|
403
|
+
* @returns Observable que se completa después de abrir el PDF
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* const reportOptions = ReportParamsBuilder.build(
|
|
408
|
+
* { codDoc: ELECTRONIC_DOCUMENT_CODE.FV, id: 123 },
|
|
409
|
+
* 'pdf'
|
|
410
|
+
* );
|
|
411
|
+
*
|
|
412
|
+
* // Forma corta
|
|
413
|
+
* this.reportFacade.openPDF(
|
|
414
|
+
* reportOptions.data,
|
|
415
|
+
* reportOptions.useV1Api
|
|
416
|
+
* ).subscribe();
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
openPDF(data, useV1Api = false) {
|
|
420
|
+
return this.open({
|
|
196
421
|
data,
|
|
197
422
|
format: 'pdf',
|
|
198
|
-
|
|
423
|
+
useV1Api,
|
|
199
424
|
});
|
|
200
425
|
}
|
|
201
|
-
|
|
202
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Genera un Excel y lo descarga automáticamente (shortcut)
|
|
428
|
+
*
|
|
429
|
+
* Método de conveniencia para el caso común de generar y descargar archivos Excel.
|
|
430
|
+
* Equivalente a llamar download() con format: 'excel'.
|
|
431
|
+
*
|
|
432
|
+
* @template T - Tipo de datos del reporte
|
|
433
|
+
* @param data - Datos del reporte (resultado de ReportParamsBuilder.build().data)
|
|
434
|
+
* @param useV1Api - Si debe usar la API v1 (por defecto false)
|
|
435
|
+
* @returns Observable que se completa después de iniciar la descarga
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const reportOptions = ReportParamsBuilder.build(
|
|
440
|
+
* { codDoc: SALE_CODE_REPORT.FG, fechaInicio: '2024-01-01' },
|
|
441
|
+
* 'excel'
|
|
442
|
+
* );
|
|
443
|
+
*
|
|
444
|
+
* // Forma corta
|
|
445
|
+
* this.reportFacade.downloadExcel(
|
|
446
|
+
* reportOptions.data,
|
|
447
|
+
* reportOptions.useV1Api
|
|
448
|
+
* ).subscribe();
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
downloadExcel(data, useV1Api = false) {
|
|
452
|
+
return this.download({
|
|
203
453
|
data,
|
|
204
454
|
format: 'excel',
|
|
205
|
-
|
|
455
|
+
useV1Api,
|
|
206
456
|
});
|
|
207
457
|
}
|
|
208
|
-
|
|
209
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Genera un Word y lo descarga automáticamente (shortcut)
|
|
460
|
+
*
|
|
461
|
+
* Método de conveniencia para el caso común de generar y descargar archivos Word.
|
|
462
|
+
* Equivalente a llamar download() con format: 'word'.
|
|
463
|
+
*
|
|
464
|
+
* @template T - Tipo de datos del reporte
|
|
465
|
+
* @param data - Datos del reporte (resultado de ReportParamsBuilder.build().data)
|
|
466
|
+
* @param useV1Api - Si debe usar la API v1 (por defecto false)
|
|
467
|
+
* @returns Observable que se completa después de iniciar la descarga
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* const reportOptions = ReportParamsBuilder.build(
|
|
472
|
+
* { codDoc: CUSTOMER_CODE_REPORT.RCL },
|
|
473
|
+
* 'word'
|
|
474
|
+
* );
|
|
475
|
+
*
|
|
476
|
+
* // Forma corta
|
|
477
|
+
* this.reportFacade.downloadWord(
|
|
478
|
+
* reportOptions.data,
|
|
479
|
+
* reportOptions.useV1Api
|
|
480
|
+
* ).subscribe();
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
downloadWord(data, useV1Api = false) {
|
|
484
|
+
return this.download({
|
|
210
485
|
data,
|
|
211
486
|
format: 'word',
|
|
212
|
-
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
// Método para generar y retornar blob
|
|
216
|
-
generateBlob(data, format = 'pdf') {
|
|
217
|
-
return this.reportPort.generate({
|
|
218
|
-
data,
|
|
219
|
-
format,
|
|
220
|
-
returnBlob: true,
|
|
487
|
+
useV1Api,
|
|
221
488
|
});
|
|
222
489
|
}
|
|
223
490
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -615,113 +882,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
615
882
|
}] });
|
|
616
883
|
|
|
617
884
|
/**
|
|
618
|
-
*
|
|
885
|
+
* Adapter para generación de reportes
|
|
886
|
+
* Siempre devuelve Observable - el consumidor decide qué hacer con el blob
|
|
619
887
|
*/
|
|
620
|
-
class FileMapperUtil {
|
|
621
|
-
/**
|
|
622
|
-
* Extrae el nombre de archivo desde el header Content-Disposition
|
|
623
|
-
*/
|
|
624
|
-
static extractFileName(response) {
|
|
625
|
-
const contentDisposition = response.headers?.get('content-disposition');
|
|
626
|
-
if (!contentDisposition) {
|
|
627
|
-
return this.generateDefaultFileName();
|
|
628
|
-
}
|
|
629
|
-
// Método más robusto para extraer filename
|
|
630
|
-
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
|
|
631
|
-
if (matches && matches[1]) {
|
|
632
|
-
let fileName = matches[1].replace(/['"]/g, '');
|
|
633
|
-
// Limpiar caracteres especiales
|
|
634
|
-
fileName = fileName.replaceAll('+', ' ').trim();
|
|
635
|
-
return fileName || this.generateDefaultFileName();
|
|
636
|
-
}
|
|
637
|
-
// Fallback: método simple
|
|
638
|
-
const simpleParse = contentDisposition.split('filename=')[1];
|
|
639
|
-
if (simpleParse) {
|
|
640
|
-
return simpleParse.replace(/['"]/g, '').trim() || this.generateDefaultFileName();
|
|
641
|
-
}
|
|
642
|
-
return this.generateDefaultFileName();
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Crea un objeto File desde una respuesta HTTP
|
|
646
|
-
*/
|
|
647
|
-
static fromResponse(response) {
|
|
648
|
-
const fileName = this.extractFileName(response);
|
|
649
|
-
const contentType = response.headers?.get('content-type') || 'application/octet-stream';
|
|
650
|
-
return new File([response.body], fileName, { type: contentType });
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Descarga un archivo usando la API nativa del navegador
|
|
654
|
-
*/
|
|
655
|
-
static downloadFile(blob, fileName) {
|
|
656
|
-
const url = URL.createObjectURL(blob);
|
|
657
|
-
const link = document.createElement('a');
|
|
658
|
-
link.href = url;
|
|
659
|
-
link.download = fileName;
|
|
660
|
-
link.style.display = 'none';
|
|
661
|
-
document.body.appendChild(link);
|
|
662
|
-
link.click();
|
|
663
|
-
document.body.removeChild(link);
|
|
664
|
-
// Limpiar el objeto URL
|
|
665
|
-
URL.revokeObjectURL(url);
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Abre un archivo en una nueva ventana (para PDFs)
|
|
669
|
-
*/
|
|
670
|
-
static openFile(blob, fileName) {
|
|
671
|
-
const fileURL = URL.createObjectURL(blob);
|
|
672
|
-
window.open(fileURL, fileName);
|
|
673
|
-
// Limpiar URL después de un tiempo
|
|
674
|
-
setTimeout(() => URL.revokeObjectURL(fileURL), 1000);
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Detecta si es un navegador legacy que requiere descarga forzada
|
|
678
|
-
*/
|
|
679
|
-
static isLegacyBrowser() {
|
|
680
|
-
const userAgent = navigator.userAgent;
|
|
681
|
-
return !!(userAgent.match(/Edge/g) || userAgent.match(/.NET/g) || userAgent.match(/MSIE/g));
|
|
682
|
-
}
|
|
683
|
-
static generateDefaultFileName() {
|
|
684
|
-
const timestamp = Date.now();
|
|
685
|
-
return `document_${timestamp}.pdf`;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
888
|
class ReportAdapter {
|
|
690
889
|
http = inject(HttpClient);
|
|
691
890
|
generate(options) {
|
|
692
|
-
const { data,
|
|
693
|
-
const reportVersionPath = useV1Api ? '/
|
|
694
|
-
const fullPath = `${API_PATHS.REPORTS}${reportVersionPath}
|
|
695
|
-
|
|
891
|
+
const { data, useV1Api = false } = options;
|
|
892
|
+
const reportVersionPath = useV1Api ? '/reporte/download' : '/v2/reporte/download';
|
|
893
|
+
const fullPath = `${API_PATHS.REPORTS}${reportVersionPath}`;
|
|
894
|
+
return this.http.post(fullPath, data, {
|
|
696
895
|
observe: 'response',
|
|
697
896
|
responseType: 'blob',
|
|
698
|
-
};
|
|
699
|
-
if (returnBlob) {
|
|
700
|
-
return this.http.post(fullPath, data, requestOptions);
|
|
701
|
-
}
|
|
702
|
-
this.http.post(fullPath, data, requestOptions).subscribe({
|
|
703
|
-
next: (response) => this.saveFile(response, format, forceDownload),
|
|
704
|
-
error: (err) => {
|
|
705
|
-
console.error('Error al descargar el reporte:', err);
|
|
706
|
-
},
|
|
707
897
|
});
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
getFileName(response) {
|
|
711
|
-
return FileMapperUtil.extractFileName(response);
|
|
712
|
-
}
|
|
713
|
-
saveFile(response, format, forceDownload = false) {
|
|
714
|
-
const fileName = this.getFileName(response);
|
|
715
|
-
if (!response.body)
|
|
716
|
-
return;
|
|
717
|
-
if (format === 'pdf' && !forceDownload && !FileMapperUtil.isLegacyBrowser()) {
|
|
718
|
-
// Abrir PDF en nueva ventana para navegadores modernos
|
|
719
|
-
FileMapperUtil.openFile(response.body, fileName);
|
|
720
|
-
}
|
|
721
|
-
else {
|
|
722
|
-
// Descargar archivo (Excel, Word, o PDF forzado)
|
|
723
|
-
FileMapperUtil.downloadFile(response.body, fileName);
|
|
724
|
-
}
|
|
725
898
|
}
|
|
726
899
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
727
900
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportAdapter });
|