@acontplus/ng-common 1.0.0

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.
@@ -0,0 +1,1072 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, signal, Input, Component } from '@angular/core';
3
+ import { HttpClient } from '@angular/common/http';
4
+ import { Observable, throwError } from 'rxjs';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
7
+ import { CommonModule } from '@angular/common';
8
+ import * as i2 from '@angular/material/card';
9
+ import { MatCardModule } from '@angular/material/card';
10
+ import * as i3 from '@angular/material/form-field';
11
+ import { MatFormFieldModule } from '@angular/material/form-field';
12
+ import * as i4 from '@angular/material/input';
13
+ import { MatInputModule } from '@angular/material/input';
14
+ import * as i5 from '@angular/material/button';
15
+ import { MatButtonModule } from '@angular/material/button';
16
+ import * as i6 from '@angular/material/icon';
17
+ import { MatIconModule } from '@angular/material/icon';
18
+ import * as i7 from '@angular/material/progress-spinner';
19
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
20
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
21
+ import * as i8 from '@angular/material/select';
22
+ import { MatSelectModule } from '@angular/material/select';
23
+
24
+ const API_PATHS = {
25
+ WHATSAPP: '/api/common/whatsapp-cloud',
26
+ REPORTS: '/api/reports',
27
+ CONFIG: '/api/config',
28
+ };
29
+ const DOCUMENT_TYPES = {
30
+ NORMAL: 'NORMAL',
31
+ FACTURA: 'FACTURA',
32
+ NOTA_ENTREGA: 'NOTA_ENTREGA',
33
+ };
34
+
35
+ const REPORT_PORT = new InjectionToken('REPORT_PORT');
36
+
37
+ const WHATSAPP_MESSAGING_PORT = new InjectionToken('WHATSAPP_MESSAGING_PORT');
38
+
39
+ const PRINTER_PORT = new InjectionToken('PRINTER_PORT');
40
+
41
+ class ReportFacade {
42
+ reportPort = inject(REPORT_PORT);
43
+ // Método principal unificado
44
+ generate(options) {
45
+ return this.reportPort.generate(options);
46
+ }
47
+ // Métodos de conveniencia para casos comunes
48
+ generatePDF(data, forceDownload = false) {
49
+ this.reportPort.generate({
50
+ data,
51
+ format: 'pdf',
52
+ forceDownload,
53
+ });
54
+ }
55
+ generateExcel(data, forceDownload = false) {
56
+ this.reportPort.generate({
57
+ data,
58
+ format: 'excel',
59
+ forceDownload,
60
+ });
61
+ }
62
+ generateWord(data, forceDownload = false) {
63
+ this.reportPort.generate({
64
+ data,
65
+ format: 'word',
66
+ forceDownload,
67
+ });
68
+ }
69
+ // Método para generar y retornar blob
70
+ generateBlob(data, format = 'pdf') {
71
+ return this.reportPort.generate({
72
+ data,
73
+ format,
74
+ returnBlob: true,
75
+ });
76
+ }
77
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
78
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportFacade, providedIn: 'root' });
79
+ }
80
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportFacade, decorators: [{
81
+ type: Injectable,
82
+ args: [{ providedIn: 'root' }]
83
+ }] });
84
+
85
+ class WhatsAppMessageBuilder {
86
+ buildDocumentDeliveryMessages(data) {
87
+ const main = this.buildMainMessage(data);
88
+ const promo = this.buildPromoMessage(data.establecimiento);
89
+ return { main, promo };
90
+ }
91
+ buildWelcomeMessage(customerName, establishmentName) {
92
+ return `¡Hola *${customerName}*! 👋\n\nBienvenido a *${establishmentName}*. Estamos aquí para ayudarte.`;
93
+ }
94
+ buildOrderConfirmationMessage(orderNumber, customerName) {
95
+ return `Estimado(a) *${customerName}*,\n\n✅ Tu pedido #${orderNumber} ha sido confirmado.\n\nTe notificaremos cuando esté listo.`;
96
+ }
97
+ buildPaymentReminderMessage(customerName, amount, dueDate) {
98
+ return `Estimado(a) *${customerName}*,\n\n💰 Recordatorio de pago:\nMonto: $${amount}\nVencimiento: ${dueDate}\n\nGracias por tu preferencia.`;
99
+ }
100
+ buildMainMessage(data) {
101
+ const documentTypeDisplay = this.getDocumentTypeDisplay(data.tipo);
102
+ return (`Estimado(a) *${data.comprador}*,\n\n` +
103
+ `📄 Su ${documentTypeDisplay} ha sido generado exitosamente.\n\n` +
104
+ `🏪 Establecimiento: *${data.establecimiento}*\n` +
105
+ `📋 Serie: ${data.serie}\n\n` +
106
+ `Adjunto encontrará el documento solicitado.`);
107
+ }
108
+ buildPromoMessage(establishmentName) {
109
+ return (`📱 Documento enviado por *${establishmentName}*\n` +
110
+ `Powered by *Acontplus* 🚀\n\n` +
111
+ `¿Necesita ayuda? Contáctenos.`);
112
+ }
113
+ getDocumentTypeDisplay(tipo) {
114
+ const types = {
115
+ FACTURA: 'factura electrónica',
116
+ NOTA_ENTREGA: 'nota de entrega',
117
+ PROFORMA: 'proforma',
118
+ COTIZACION: 'cotización',
119
+ RECIBO: 'recibo',
120
+ };
121
+ return types[tipo] || 'documento';
122
+ }
123
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessageBuilder, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
124
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessageBuilder, providedIn: 'root' });
125
+ }
126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessageBuilder, decorators: [{
127
+ type: Injectable,
128
+ args: [{ providedIn: 'root' }]
129
+ }] });
130
+
131
+ /**
132
+ * Utilidades para formateo y validación de números telefónicos
133
+ */
134
+ class PhoneFormatterUtil {
135
+ /**
136
+ * Limpia el teléfono removiendo todos los caracteres no numéricos
137
+ */
138
+ static cleanPhone(phone) {
139
+ return phone.replace(/\D/g, '');
140
+ }
141
+ /**
142
+ * Valida si un teléfono ecuatoriano es válido
143
+ */
144
+ static validateEcuadorianPhone(phone) {
145
+ const cleanPhone = this.cleanPhone(phone);
146
+ if (!cleanPhone) {
147
+ return {
148
+ isValid: false,
149
+ cleanPhone,
150
+ errorMessage: 'El número de celular no puede estar vacío',
151
+ };
152
+ }
153
+ if (cleanPhone.length < 10) {
154
+ return {
155
+ isValid: false,
156
+ cleanPhone,
157
+ errorMessage: 'El número de celular no puede ser menor a diez dígitos',
158
+ };
159
+ }
160
+ if (cleanPhone.length > 10) {
161
+ return {
162
+ isValid: false,
163
+ cleanPhone,
164
+ errorMessage: 'El número de celular no puede ser mayor a diez dígitos',
165
+ };
166
+ }
167
+ // Validar formato ecuatoriano (09xxxxxxxx) o internacional (593xxxxxxxxx)
168
+ const isValidEcuadorian = /^09\d{8}$/.test(cleanPhone);
169
+ const isValidInternational = /^593\d{9}$/.test(cleanPhone);
170
+ if (!isValidEcuadorian && !isValidInternational) {
171
+ return {
172
+ isValid: false,
173
+ cleanPhone,
174
+ errorMessage: 'Formato inválido. Use: 09xxxxxxxx',
175
+ };
176
+ }
177
+ return {
178
+ isValid: true,
179
+ cleanPhone,
180
+ };
181
+ }
182
+ /**
183
+ * Convierte número local ecuatoriano a formato internacional
184
+ */
185
+ static toInternationalFormat(phone) {
186
+ let cleanPhone = this.cleanPhone(phone);
187
+ // Si empieza con 0, convertir a formato internacional ecuatoriano
188
+ if (cleanPhone.startsWith('0')) {
189
+ cleanPhone = '593' + cleanPhone.slice(1);
190
+ }
191
+ return cleanPhone;
192
+ }
193
+ /**
194
+ * Formatea teléfono para la API de WhatsApp Business (con @c.us)
195
+ */
196
+ static formatForWhatsAppApi(phone) {
197
+ const internationalPhone = this.toInternationalFormat(phone);
198
+ if (!internationalPhone.endsWith('@c.us')) {
199
+ return `${internationalPhone}@c.us`;
200
+ }
201
+ return internationalPhone;
202
+ }
203
+ /**
204
+ * Formatea teléfono para WhatsApp Web (sin @c.us)
205
+ */
206
+ static formatForWhatsAppWeb(phone) {
207
+ return this.toInternationalFormat(phone);
208
+ }
209
+ /**
210
+ * Formatea teléfono para mostrar al usuario (con espacios y +)
211
+ */
212
+ static formatForDisplay(phone) {
213
+ const cleanPhone = this.cleanPhone(phone);
214
+ if (!cleanPhone)
215
+ return '';
216
+ // Si es número local ecuatoriano (09xxxxxxxx)
217
+ if (/^09\d{8}$/.test(cleanPhone)) {
218
+ return `+593 ${cleanPhone.slice(1, 3)} ${cleanPhone.slice(3, 6)} ${cleanPhone.slice(6)}`;
219
+ }
220
+ // Si ya tiene formato internacional (593xxxxxxxxx)
221
+ if (/^593\d{9}$/.test(cleanPhone)) {
222
+ return `+593 ${cleanPhone.slice(3, 5)} ${cleanPhone.slice(5, 8)} ${cleanPhone.slice(8)}`;
223
+ }
224
+ // Para otros formatos internacionales
225
+ if (cleanPhone.length > 10) {
226
+ return `+${cleanPhone}`;
227
+ }
228
+ return phone;
229
+ }
230
+ /**
231
+ * Validación general para teléfonos internacionales
232
+ */
233
+ static isValidInternationalPhone(phone) {
234
+ const cleanPhone = this.cleanPhone(phone);
235
+ return /^\+?[1-9]\d{1,14}$/.test(cleanPhone);
236
+ }
237
+ /**
238
+ * Genera el hint apropiado según el estado del teléfono
239
+ */
240
+ static getPhoneHint(phone) {
241
+ if (!phone) {
242
+ return 'Ingrese número ecuatoriano (09xxxxxxxx)';
243
+ }
244
+ const validation = this.validateEcuadorianPhone(phone);
245
+ if (validation.isValid) {
246
+ return `Se enviará a: ${this.formatForDisplay(phone)}`;
247
+ }
248
+ return 'Formato: 09xxxxxxxx';
249
+ }
250
+ /**
251
+ * Obtiene el placeholder para el campo de teléfono
252
+ */
253
+ static getPhonePlaceholder() {
254
+ return 'Ej: 0987654321';
255
+ }
256
+ }
257
+
258
+ class WhatsAppMessagingFacade {
259
+ messagingPort = inject(WHATSAPP_MESSAGING_PORT);
260
+ messageBuilder = inject(WhatsAppMessageBuilder);
261
+ // Métodos de alto nivel con lógica de negocio
262
+ sendSimpleText(to, message, previewUrl = true) {
263
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(to);
264
+ return this.messagingPort.sendText({
265
+ to: formattedPhone,
266
+ message,
267
+ previewUrl,
268
+ });
269
+ }
270
+ sendTemplateText(to, templateName, params, languageCode = 'es_EC') {
271
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(to);
272
+ return this.messagingPort.sendTextTemplate({
273
+ to: formattedPhone,
274
+ templateName,
275
+ languageCode,
276
+ bodyParams: params,
277
+ });
278
+ }
279
+ sendDocument(to, documentUrl, caption, filename) {
280
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(to);
281
+ return this.messagingPort.sendDocument({
282
+ to: formattedPhone,
283
+ documentUrl,
284
+ caption,
285
+ filename,
286
+ });
287
+ }
288
+ // Caso de uso específico: Entrega de documentos
289
+ deliverDocument(params) {
290
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(params.phone);
291
+ const messages = this.messageBuilder.buildDocumentDeliveryMessages({
292
+ comprador: params.customerName,
293
+ establecimiento: params.establishmentName,
294
+ serie: params.documentSeries,
295
+ tipo: params.documentType,
296
+ });
297
+ return this.messagingPort.sendDocumentTemplate({
298
+ to: formattedPhone,
299
+ file: params.file,
300
+ templateName: 'notificacion_documento',
301
+ languageCode: 'es_EC',
302
+ bodyParams: [messages.main, messages.promo],
303
+ });
304
+ }
305
+ // Método mejorado para envío de documentos con template
306
+ sendDocumentWithTemplate(params) {
307
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(params.phone);
308
+ return this.messagingPort.sendDocumentTemplate({
309
+ to: formattedPhone,
310
+ file: params.file,
311
+ templateName: params.templateName,
312
+ languageCode: params.languageCode || 'es_EC',
313
+ bodyParams: params.bodyParams,
314
+ filename: params.filename,
315
+ });
316
+ }
317
+ // Método legacy para compatibilidad (pero mejorado)
318
+ sendDocumentWithMessages(params) {
319
+ return this.sendDocumentWithTemplate({
320
+ phone: params.phone,
321
+ file: params.file,
322
+ templateName: 'documento_generico',
323
+ bodyParams: [params.messages.main, params.messages.promo],
324
+ });
325
+ }
326
+ getProviderStatus() {
327
+ return this.messagingPort.getStatus();
328
+ }
329
+ // Métodos públicos que delegan a las utilidades estáticas
330
+ isValidPhone(phone) {
331
+ return PhoneFormatterUtil.isValidInternationalPhone(phone);
332
+ }
333
+ toDisplayFormat(phone) {
334
+ return PhoneFormatterUtil.formatForDisplay(phone);
335
+ }
336
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessagingFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
337
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessagingFacade, providedIn: 'root' });
338
+ }
339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppMessagingFacade, decorators: [{
340
+ type: Injectable,
341
+ args: [{ providedIn: 'root' }]
342
+ }] });
343
+
344
+ class PrinterFacade {
345
+ printerPort = inject(PRINTER_PORT);
346
+ // Métodos de impresión
347
+ askToPrint(params) {
348
+ this.printerPort.askToPrint(params);
349
+ }
350
+ printOrder(id) {
351
+ this.printerPort.createPrintOrder(id);
352
+ }
353
+ printCommanda(id) {
354
+ this.printerPort.createPrintCommanda(id);
355
+ }
356
+ printOrderWithCommanda(id) {
357
+ this.printerPort.createPrintOrderWithCommanda(id);
358
+ }
359
+ printInvoice(id, documentoId) {
360
+ this.printerPort.createInvoicePrint(id, documentoId);
361
+ }
362
+ printNotaEntrega(id, documentoId) {
363
+ this.printerPort.createNotaEntregaPrint(id, documentoId);
364
+ }
365
+ // Método para configurar automáticamente la impresión
366
+ autoPrint(data, codDoc) {
367
+ if (data.printerAutomatic) {
368
+ if (codDoc === 'FACTURA') {
369
+ this.printInvoice(data.orderId, data.idDocumento);
370
+ }
371
+ if (codDoc === 'NOTA_ENTREGA') {
372
+ this.printNotaEntrega(data.orderId, data.idDocumento);
373
+ }
374
+ }
375
+ }
376
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
377
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterFacade, providedIn: 'root' });
378
+ }
379
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterFacade, decorators: [{
380
+ type: Injectable,
381
+ args: [{ providedIn: 'root' }]
382
+ }] });
383
+
384
+ class MetaWhatsAppAdapter {
385
+ http = inject(HttpClient);
386
+ // 1. Texto directo (requiere ventana 24h)
387
+ sendText(request) {
388
+ return this.http.post(`${API_PATHS.WHATSAPP}/text`, request);
389
+ }
390
+ // 2. Texto con template (funciona siempre)
391
+ sendTextTemplate(request) {
392
+ return this.http.post(`${API_PATHS.WHATSAPP}/text-template`, request);
393
+ }
394
+ // 3. Documento directo via URL (requiere ventana 24h)
395
+ sendDocument(request) {
396
+ return this.http.post(`${API_PATHS.WHATSAPP}/document`, request);
397
+ }
398
+ // 4. Documento con template (funciona siempre)
399
+ sendDocumentTemplate(request) {
400
+ const formData = new FormData();
401
+ // Usar nombres de campos que espera tu API (con mayúsculas)
402
+ formData.append('To', request.to);
403
+ formData.append('File', request.file);
404
+ if (request.templateName)
405
+ formData.append('TemplateName', request.templateName);
406
+ if (request.languageCode)
407
+ formData.append('LanguageCode', request.languageCode);
408
+ if (request.filename)
409
+ formData.append('Filename', request.filename);
410
+ // Enviar bodyParams como array (formato que espera tu API)
411
+ if (request.bodyParams && request.bodyParams.length > 0) {
412
+ request.bodyParams.forEach((param, index) => {
413
+ formData.append(`BodyParams[${index}]`, param);
414
+ });
415
+ }
416
+ return this.http.post(`${API_PATHS.WHATSAPP}/document-template`, formData);
417
+ }
418
+ getStatus() {
419
+ return this.http.get(`${API_PATHS.WHATSAPP}/status`);
420
+ }
421
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MetaWhatsAppAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
422
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MetaWhatsAppAdapter });
423
+ }
424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MetaWhatsAppAdapter, decorators: [{
425
+ type: Injectable
426
+ }] });
427
+
428
+ /**
429
+ * Adaptador para Green API (legacy)
430
+ * Implementa la interfaz WhatsApp usando Green API
431
+ */
432
+ class GreenWhatsAppAdapter {
433
+ http = inject(HttpClient);
434
+ // Implementación para Green API - mapea los métodos de WhatsApp a Green
435
+ sendText(request) {
436
+ // Mapear a Green API call
437
+ return this.http.post('/api/green/send-text', {
438
+ chatId: request.to,
439
+ message: request.message,
440
+ });
441
+ }
442
+ sendTextTemplate(request) {
443
+ // Green API no soporta templates, enviar como texto simple
444
+ return this.http.post('/api/green/send-text', {
445
+ chatId: request.to,
446
+ message: `Template: ${request.templateName}`,
447
+ });
448
+ }
449
+ sendDocument(request) {
450
+ return this.http.post('/api/green/send-file', {
451
+ chatId: request.to,
452
+ fileUrl: request.documentUrl,
453
+ caption: request.caption,
454
+ });
455
+ }
456
+ sendDocumentTemplate(request) {
457
+ // Green API - enviar archivo con template como caption
458
+ const formData = new FormData();
459
+ formData.append('chatId', request.to);
460
+ formData.append('file', request.file);
461
+ formData.append('caption', `Template: ${request.templateName}`);
462
+ return this.http.post('/api/green/send-file-upload', formData);
463
+ }
464
+ getStatus() {
465
+ return this.http.get('/api/green/status');
466
+ }
467
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GreenWhatsAppAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
468
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GreenWhatsAppAdapter });
469
+ }
470
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GreenWhatsAppAdapter, decorators: [{
471
+ type: Injectable
472
+ }] });
473
+
474
+ /**
475
+ * Utilidades para manejo de archivos y respuestas HTTP
476
+ */
477
+ class FileMapperUtil {
478
+ /**
479
+ * Extrae el nombre de archivo desde el header Content-Disposition
480
+ */
481
+ static extractFileName(response) {
482
+ const contentDisposition = response.headers?.get('content-disposition');
483
+ if (!contentDisposition) {
484
+ return this.generateDefaultFileName();
485
+ }
486
+ // Método más robusto para extraer filename
487
+ const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
488
+ if (matches && matches[1]) {
489
+ let fileName = matches[1].replace(/['"]/g, '');
490
+ // Limpiar caracteres especiales
491
+ fileName = fileName.replaceAll('+', ' ').trim();
492
+ return fileName || this.generateDefaultFileName();
493
+ }
494
+ // Fallback: método simple
495
+ const simpleParse = contentDisposition.split('filename=')[1];
496
+ if (simpleParse) {
497
+ return simpleParse.replace(/['"]/g, '').trim() || this.generateDefaultFileName();
498
+ }
499
+ return this.generateDefaultFileName();
500
+ }
501
+ /**
502
+ * Crea un objeto File desde una respuesta HTTP
503
+ */
504
+ static fromResponse(response) {
505
+ const fileName = this.extractFileName(response);
506
+ const contentType = response.headers?.get('content-type') || 'application/octet-stream';
507
+ return new File([response.body], fileName, { type: contentType });
508
+ }
509
+ /**
510
+ * Descarga un archivo usando la API nativa del navegador
511
+ */
512
+ static downloadFile(blob, fileName) {
513
+ const url = URL.createObjectURL(blob);
514
+ const link = document.createElement('a');
515
+ link.href = url;
516
+ link.download = fileName;
517
+ link.style.display = 'none';
518
+ document.body.appendChild(link);
519
+ link.click();
520
+ document.body.removeChild(link);
521
+ // Limpiar el objeto URL
522
+ URL.revokeObjectURL(url);
523
+ }
524
+ /**
525
+ * Abre un archivo en una nueva ventana (para PDFs)
526
+ */
527
+ static openFile(blob, fileName) {
528
+ const fileURL = URL.createObjectURL(blob);
529
+ window.open(fileURL, fileName);
530
+ // Limpiar URL después de un tiempo
531
+ setTimeout(() => URL.revokeObjectURL(fileURL), 1000);
532
+ }
533
+ /**
534
+ * Detecta si es un navegador legacy que requiere descarga forzada
535
+ */
536
+ static isLegacyBrowser() {
537
+ const userAgent = navigator.userAgent;
538
+ return !!(userAgent.match(/Edge/g) || userAgent.match(/.NET/g) || userAgent.match(/MSIE/g));
539
+ }
540
+ static generateDefaultFileName() {
541
+ const timestamp = Date.now();
542
+ return `document_${timestamp}.pdf`;
543
+ }
544
+ }
545
+
546
+ class ReportAdapter {
547
+ http = inject(HttpClient);
548
+ generate(options) {
549
+ const { data, format = 'pdf', useV1Api = false, forceDownload = false, returnBlob = false, } = options;
550
+ const reportVersionPath = useV1Api ? '/v1/' : '/v2/';
551
+ const fullPath = `${API_PATHS.REPORTS}${reportVersionPath}download`;
552
+ const requestOptions = {
553
+ observe: 'response',
554
+ responseType: 'blob',
555
+ };
556
+ if (returnBlob) {
557
+ return this.http.post(fullPath, data, requestOptions);
558
+ }
559
+ this.http.post(fullPath, data, requestOptions).subscribe({
560
+ next: (response) => this.saveFile(response, format, forceDownload),
561
+ error: (err) => {
562
+ console.error('Error al descargar el reporte:', err);
563
+ },
564
+ });
565
+ return;
566
+ }
567
+ getFileName(response) {
568
+ return FileMapperUtil.extractFileName(response);
569
+ }
570
+ saveFile(response, format, forceDownload = false) {
571
+ const fileName = this.getFileName(response);
572
+ if (!response.body)
573
+ return;
574
+ if (format === 'pdf' && !forceDownload && !FileMapperUtil.isLegacyBrowser()) {
575
+ // Abrir PDF en nueva ventana para navegadores modernos
576
+ FileMapperUtil.openFile(response.body, fileName);
577
+ }
578
+ else {
579
+ // Descargar archivo (Excel, Word, o PDF forzado)
580
+ FileMapperUtil.downloadFile(response.body, fileName);
581
+ }
582
+ }
583
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
584
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportAdapter });
585
+ }
586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportAdapter, decorators: [{
587
+ type: Injectable
588
+ }] });
589
+
590
+ class PrinterAdapter {
591
+ http = inject(HttpClient);
592
+ askToPrint(obj) {
593
+ if (typeof window !== 'undefined') {
594
+ const confirmed = window.confirm('¿Desea Imprimir este comprobante?');
595
+ if (confirmed) {
596
+ this.getPrintConfig(obj).subscribe();
597
+ }
598
+ }
599
+ }
600
+ getPrintConfig(obj) {
601
+ const json = JSON.stringify({
602
+ codDoc: obj.codDoc,
603
+ getUrlPrintServer: true,
604
+ id: obj.id,
605
+ codEstab: obj.codEstab,
606
+ });
607
+ return new Observable((observer) => {
608
+ const url = `${API_PATHS.CONFIG}Print/?json=${json}`;
609
+ this.http.get(url).subscribe({
610
+ next: (response) => {
611
+ if (response.code === '0') {
612
+ console.warn(response.message);
613
+ }
614
+ if (response.code === '1') {
615
+ this.print(response.payload, obj).subscribe();
616
+ }
617
+ observer.next(response);
618
+ observer.complete();
619
+ },
620
+ error: (err) => {
621
+ console.error(err);
622
+ observer.error(err);
623
+ },
624
+ });
625
+ });
626
+ }
627
+ print(urlPrinter, paramsToPrint) {
628
+ return new Observable((observer) => {
629
+ this.http.post(`${urlPrinter}/Print`, paramsToPrint).subscribe({
630
+ next: (response) => {
631
+ console.log({
632
+ type: response.code === '1' ? 'success' : 'warning',
633
+ message: response.message,
634
+ });
635
+ observer.next(response);
636
+ observer.complete();
637
+ },
638
+ error: (err) => {
639
+ console.error(err);
640
+ observer.error(err);
641
+ },
642
+ });
643
+ });
644
+ }
645
+ createPrintOrder(id) {
646
+ this.getPrintConfig({
647
+ id,
648
+ documentoId: 0,
649
+ tipo: 5,
650
+ codDoc: DOCUMENT_TYPES.NORMAL,
651
+ }).subscribe();
652
+ }
653
+ createPrintCommanda(id) {
654
+ this.getPrintConfig({
655
+ id,
656
+ documentoId: 0,
657
+ tipo: 6,
658
+ codDoc: DOCUMENT_TYPES.NORMAL,
659
+ }).subscribe();
660
+ }
661
+ createPrintOrderWithCommanda(id) {
662
+ this.getPrintConfig({
663
+ id,
664
+ documentoId: 0,
665
+ tipo: 2,
666
+ codDoc: DOCUMENT_TYPES.NORMAL,
667
+ }).subscribe();
668
+ }
669
+ createInvoicePrint(id, documentoId) {
670
+ this.getPrintConfig({
671
+ id,
672
+ documentoId,
673
+ tipo: 3,
674
+ codDoc: DOCUMENT_TYPES.FACTURA,
675
+ }).subscribe();
676
+ }
677
+ createNotaEntregaPrint(id, documentoId) {
678
+ this.getPrintConfig({
679
+ id,
680
+ documentoId,
681
+ tipo: 4,
682
+ codDoc: DOCUMENT_TYPES.NOTA_ENTREGA,
683
+ }).subscribe();
684
+ }
685
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
686
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterAdapter });
687
+ }
688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PrinterAdapter, decorators: [{
689
+ type: Injectable
690
+ }] });
691
+
692
+ class ReportParamsBuilder {
693
+ reportConfigs = {
694
+ FV: {
695
+ codigo: 'FV',
696
+ hasService: true,
697
+ useV1Api: true,
698
+ idField: 'id',
699
+ includeEstabInData: true,
700
+ includeCodigoInData: true,
701
+ },
702
+ NE: {
703
+ codigo: 'NE',
704
+ hasService: true,
705
+ useV1Api: true,
706
+ idField: 'id',
707
+ extraDataParams: { tipo: 1 },
708
+ },
709
+ NORMAL: {
710
+ codigo: 'OR',
711
+ hasService: false,
712
+ useV1Api: false,
713
+ idField: 'id',
714
+ },
715
+ // Fácil agregar nuevos tipos sin modificar código
716
+ NC: {
717
+ codigo: 'NC',
718
+ hasService: true,
719
+ useV1Api: true,
720
+ idField: 'id',
721
+ includeEstabInData: true,
722
+ },
723
+ ND: {
724
+ codigo: 'ND',
725
+ hasService: true,
726
+ useV1Api: true,
727
+ idField: 'id',
728
+ includeEstabInData: true,
729
+ },
730
+ GR: {
731
+ codigo: 'GR',
732
+ hasService: true,
733
+ useV1Api: false,
734
+ idField: 'id',
735
+ },
736
+ };
737
+ /**
738
+ * Construye los parámetros de reporte según el tipo de documento
739
+ */
740
+ buildParams(docData, format = 'pdf') {
741
+ const config = this.getReportConfig(docData.codDoc);
742
+ return {
743
+ hasService: config.hasService,
744
+ reportParams: this.buildReportParams(docData, config, format),
745
+ dataParams: this.buildDataParams(docData, config, format),
746
+ };
747
+ }
748
+ /**
749
+ * Determina si debe usar la API v1 según el tipo de documento
750
+ */
751
+ shouldUseV1Api(codDoc) {
752
+ const config = this.getReportConfig(codDoc);
753
+ return config.useV1Api;
754
+ }
755
+ /**
756
+ * Obtiene la configuración para un tipo de documento
757
+ */
758
+ getReportConfig(codDoc) {
759
+ const config = this.reportConfigs[codDoc];
760
+ if (!config) {
761
+ throw new Error(`Tipo de documento no soportado: ${codDoc}. Tipos disponibles: ${Object.keys(this.reportConfigs).join(', ')}`);
762
+ }
763
+ return config;
764
+ }
765
+ /**
766
+ * Construye los parámetros del reporte
767
+ */
768
+ buildReportParams(docData, config, format) {
769
+ const params = {
770
+ codigo: config.codigo,
771
+ codEstab: docData.codEstab,
772
+ format,
773
+ };
774
+ // Solo agregar hasParams y aditionalParams si hasService es true
775
+ if (config.hasService) {
776
+ params.hasParams = true;
777
+ params.aditionalParams = [];
778
+ }
779
+ return JSON.stringify(params);
780
+ }
781
+ /**
782
+ * Construye los parámetros de datos
783
+ */
784
+ buildDataParams(docData, config, format) {
785
+ const params = {
786
+ id: docData[config.idField],
787
+ };
788
+ // Agregar parámetros adicionales según configuración
789
+ if (config.includeEstabInData) {
790
+ params.codEstab = docData.codEstab;
791
+ }
792
+ if (config.includeCodigoInData) {
793
+ params.codigo = config.codigo;
794
+ }
795
+ if (config.hasService) {
796
+ params.hasParams = true;
797
+ if (config.includeCodigoInData) {
798
+ params.format = format;
799
+ }
800
+ }
801
+ // Agregar parámetros extra específicos del tipo
802
+ if (config.extraDataParams) {
803
+ Object.assign(params, config.extraDataParams);
804
+ }
805
+ return JSON.stringify(params);
806
+ }
807
+ /**
808
+ * Método de conveniencia para generar reporte directamente
809
+ */
810
+ generateReportFor(docData, format = 'pdf', returnBlob = false) {
811
+ return {
812
+ data: this.buildParams(docData, format),
813
+ format,
814
+ useV1Api: this.shouldUseV1Api(docData.codDoc),
815
+ returnBlob,
816
+ };
817
+ }
818
+ /**
819
+ * Obtiene la lista de tipos de documento soportados
820
+ */
821
+ getSupportedDocumentTypes() {
822
+ return Object.keys(this.reportConfigs);
823
+ }
824
+ /**
825
+ * Verifica si un tipo de documento está soportado
826
+ */
827
+ isDocumentTypeSupported(codDoc) {
828
+ return codDoc in this.reportConfigs;
829
+ }
830
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportParamsBuilder, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
831
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportParamsBuilder, providedIn: 'root' });
832
+ }
833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReportParamsBuilder, decorators: [{
834
+ type: Injectable,
835
+ args: [{ providedIn: 'root' }]
836
+ }] });
837
+
838
+ class WhatsAppSender {
839
+ config;
840
+ fb = inject(FormBuilder);
841
+ whatsappFacade = inject(WhatsAppMessagingFacade);
842
+ reportFacade = inject(ReportFacade);
843
+ snackBar = inject(MatSnackBar);
844
+ reportBuilder = inject(ReportParamsBuilder);
845
+ whatsappForm;
846
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
847
+ messageLength = 0;
848
+ phoneNumbers = signal([], ...(ngDevMode ? [{ debugName: "phoneNumbers" }] : []));
849
+ showWhatsAppWebLink = signal(false, ...(ngDevMode ? [{ debugName: "showWhatsAppWebLink" }] : []));
850
+ whatsAppWebUrl = signal('', ...(ngDevMode ? [{ debugName: "whatsAppWebUrl" }] : []));
851
+ // Datos del documento y establecimiento
852
+ establishmentPhone = '';
853
+ establishmentName = '';
854
+ customerName = '';
855
+ ngOnInit() {
856
+ this.initializeForm();
857
+ this.setupMessageLengthCounter();
858
+ this.loadConfigData();
859
+ }
860
+ loadConfigData() {
861
+ if (this.config) {
862
+ if (this.config.phoneNumbers) {
863
+ this.phoneNumbers.set(this.config.phoneNumbers);
864
+ if (this.config.phoneNumbers.length > 0) {
865
+ this.whatsappForm.patchValue({ phoneNumber: this.config.phoneNumbers[0] });
866
+ }
867
+ }
868
+ if (this.config.defaultMessage) {
869
+ this.whatsappForm.patchValue({ message: this.config.defaultMessage });
870
+ }
871
+ this.establishmentPhone = this.config.establishmentPhone || '';
872
+ this.establishmentName = this.config.establishmentName || '';
873
+ this.customerName = this.config.customerName || '';
874
+ }
875
+ }
876
+ initializeForm() {
877
+ this.whatsappForm = this.fb.group({
878
+ phoneNumber: ['', [Validators.required, this.ecuadorianPhoneValidator.bind(this)]],
879
+ message: ['', [Validators.required, Validators.maxLength(500)]],
880
+ });
881
+ }
882
+ // Validador personalizado para números ecuatorianos
883
+ ecuadorianPhoneValidator(control) {
884
+ if (!control.value)
885
+ return null;
886
+ const validation = PhoneFormatterUtil.validateEcuadorianPhone(control.value);
887
+ return validation.isValid ? null : { invalidPhone: true };
888
+ }
889
+ setupMessageLengthCounter() {
890
+ this.whatsappForm.get('message')?.valueChanges.subscribe((value) => {
891
+ this.messageLength = value ? value.length : 0;
892
+ });
893
+ }
894
+ sendWhatsApp() {
895
+ if (this.whatsappForm.valid) {
896
+ if (!this.validatePhoneNumber())
897
+ return;
898
+ const { phoneNumber, message } = this.whatsappForm.value;
899
+ // Si hay configuración de documento, generar y enviar archivo
900
+ if (this.config?.documentData) {
901
+ this.sendWithDocument(phoneNumber, message);
902
+ }
903
+ else {
904
+ // Envío simple de texto
905
+ this.sendSimpleMessage(phoneNumber, message);
906
+ }
907
+ }
908
+ }
909
+ validatePhoneNumber() {
910
+ const phoneNumber = this.whatsappForm.get('phoneNumber')?.value;
911
+ const validation = PhoneFormatterUtil.validateEcuadorianPhone(phoneNumber);
912
+ if (!validation.isValid) {
913
+ this.showError(validation.errorMessage || 'Número de teléfono inválido');
914
+ return false;
915
+ }
916
+ return true;
917
+ }
918
+ sendSimpleMessage(phoneNumber, message) {
919
+ this.isLoading.set(true);
920
+ this.whatsappFacade.sendSimpleText(phoneNumber, message).subscribe({
921
+ next: (response) => {
922
+ this.isLoading.set(false);
923
+ if (response.success) {
924
+ this.showSuccess('Mensaje enviado exitosamente');
925
+ this.clearForm();
926
+ }
927
+ else {
928
+ this.showError(response.error?.message || 'Error al enviar mensaje');
929
+ this.prepareWhatsAppWebLink(phoneNumber, message);
930
+ }
931
+ },
932
+ error: (_error) => {
933
+ this.isLoading.set(false);
934
+ this.showError('Error de conexión');
935
+ this.prepareWhatsAppWebLink(phoneNumber, message);
936
+ },
937
+ });
938
+ }
939
+ sendWithDocument(phoneNumber, message) {
940
+ if (!this.config?.documentData)
941
+ return;
942
+ this.isLoading.set(true);
943
+ // Generar el reporte primero
944
+ this.generateReport().subscribe({
945
+ next: (reportResponse) => {
946
+ if (reportResponse) {
947
+ const { file, fileName } = this.processReportFile(reportResponse);
948
+ this.sendMediaWithMessages(phoneNumber, message, file, fileName);
949
+ }
950
+ },
951
+ error: (error) => {
952
+ this.isLoading.set(false);
953
+ console.error('Error generando reporte:', error);
954
+ this.showError('Error al generar el documento');
955
+ },
956
+ });
957
+ }
958
+ generateReport() {
959
+ const docData = this.config.documentData;
960
+ // Usar el builder centralizado para generar parámetros
961
+ const reportOptions = this.reportBuilder.generateReportFor(docData, 'pdf', true);
962
+ const reportResult = this.reportFacade.generate(reportOptions);
963
+ // Manejar el caso donde generate puede retornar void
964
+ return reportResult || throwError(() => new Error('No se pudo generar el reporte'));
965
+ }
966
+ processReportFile(response) {
967
+ const file = FileMapperUtil.fromResponse(response);
968
+ const fileName = FileMapperUtil.extractFileName(response);
969
+ return { file, fileName };
970
+ }
971
+ sendMediaWithMessages(phoneNumber, message, file, fileName) {
972
+ const formattedPhone = PhoneFormatterUtil.formatForWhatsAppApi(phoneNumber);
973
+ // Enviar solo el documento con el mensaje como caption
974
+ this.whatsappFacade
975
+ .sendDocumentWithTemplate({
976
+ phone: formattedPhone,
977
+ file: file,
978
+ templateName: 'documento_generico',
979
+ bodyParams: [message],
980
+ filename: fileName,
981
+ })
982
+ .subscribe({
983
+ next: () => {
984
+ this.isLoading.set(false);
985
+ this.showSuccess('Documento enviado correctamente');
986
+ this.clearForm();
987
+ },
988
+ error: (error) => {
989
+ this.isLoading.set(false);
990
+ console.error('Error enviando documento:', error);
991
+ this.showError('Error al enviar el documento');
992
+ this.prepareWhatsAppWebLink(phoneNumber, message);
993
+ },
994
+ });
995
+ }
996
+ prepareWhatsAppWebLink(phoneNumber, message) {
997
+ const cleanPhoneNumber = PhoneFormatterUtil.formatForWhatsAppWeb(phoneNumber);
998
+ const encodedMessage = encodeURIComponent(message);
999
+ const whatsappUrl = `https://wa.me/${cleanPhoneNumber}?text=${encodedMessage}`;
1000
+ this.whatsAppWebUrl.set(whatsappUrl);
1001
+ this.showWhatsAppWebLink.set(true);
1002
+ }
1003
+ openWhatsAppWeb() {
1004
+ const url = this.whatsAppWebUrl();
1005
+ if (url) {
1006
+ window.open(url, '_blank', 'noopener,noreferrer');
1007
+ }
1008
+ }
1009
+ hideWhatsAppWebLink() {
1010
+ this.showWhatsAppWebLink.set(false);
1011
+ this.whatsAppWebUrl.set('');
1012
+ }
1013
+ showSuccess(message) {
1014
+ this.snackBar.open(message, 'Cerrar', {
1015
+ duration: 3000,
1016
+ panelClass: ['success-snackbar'],
1017
+ });
1018
+ }
1019
+ showError(message) {
1020
+ this.snackBar.open(message, 'Cerrar', {
1021
+ duration: 5000,
1022
+ panelClass: ['error-snackbar'],
1023
+ });
1024
+ }
1025
+ clearForm() {
1026
+ this.whatsappForm.reset();
1027
+ this.messageLength = 0;
1028
+ this.hideWhatsAppWebLink();
1029
+ }
1030
+ // Método para mostrar el formato del teléfono
1031
+ get formattedPhoneDisplay() {
1032
+ const phone = this.whatsappForm.get('phoneNumber')?.value;
1033
+ return PhoneFormatterUtil.formatForDisplay(phone || '');
1034
+ }
1035
+ // Método para obtener placeholder dinámico
1036
+ get phonePlaceholder() {
1037
+ return PhoneFormatterUtil.getPhonePlaceholder();
1038
+ }
1039
+ // Método para obtener el hint del campo
1040
+ get phoneHint() {
1041
+ const phone = this.whatsappForm.get('phoneNumber')?.value;
1042
+ return PhoneFormatterUtil.getPhoneHint(phone || '');
1043
+ }
1044
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppSender, deps: [], target: i0.ɵɵFactoryTarget.Component });
1045
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: WhatsAppSender, isStandalone: true, selector: "acp-whatsapp-sender", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"whatsapp-sender-container\">\n <mat-card class=\"sender-card\">\n <mat-card-header>\n <div mat-card-avatar class=\"whatsapp-avatar\">\n <mat-icon>chat</mat-icon>\n </div>\n <mat-card-title>Enviar por WhatsApp</mat-card-title>\n <mat-card-subtitle>Comparte contenido directamente</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form [formGroup]=\"whatsappForm\" (ngSubmit)=\"sendWhatsApp()\">\n <!-- Campo para n\u00FAmero de tel\u00E9fono -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00FAmero de tel\u00E9fono</mat-label>\n @if (phoneNumbers().length > 1) {\n <mat-select formControlName=\"phoneNumber\">\n @for (phone of phoneNumbers(); track phone) {\n <mat-option [value]=\"phone\">{{ phone }}</mat-option>\n }\n </mat-select>\n } @else {\n <input\n matInput\n formControlName=\"phoneNumber\"\n [placeholder]=\"phonePlaceholder\"\n type=\"tel\"\n />\n }\n <mat-icon matSuffix>phone</mat-icon>\n <mat-hint>{{ phoneHint }}</mat-hint>\n @if (whatsappForm.get('phoneNumber')?.hasError('required')) {\n <mat-error>El n\u00FAmero de tel\u00E9fono es requerido</mat-error>\n }\n @if (whatsappForm.get('phoneNumber')?.hasError('invalidPhone')) {\n <mat-error>Ingrese un n\u00FAmero ecuatoriano v\u00E1lido (09xxxxxxxx)</mat-error>\n }\n </mat-form-field>\n\n <!-- Campo para mensaje -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Mensaje</mat-label>\n <textarea\n matInput\n formControlName=\"message\"\n placeholder=\"Escribe tu mensaje aqu\u00ED...\"\n rows=\"4\"\n maxlength=\"500\"\n >\n </textarea>\n <mat-icon matSuffix>message</mat-icon>\n <mat-hint align=\"end\">{{ messageLength }}/500 caracteres</mat-hint>\n @if (whatsappForm.get('message')?.hasError('required')) {\n <mat-error>El mensaje es requerido</mat-error>\n }\n </mat-form-field>\n\n <!-- Botones de acci\u00F3n -->\n <div class=\"action-buttons\">\n <button\n mat-raised-button\n color=\"primary\"\n type=\"submit\"\n [disabled]=\"whatsappForm.invalid || isLoading()\"\n class=\"send-button\"\n >\n @if (!isLoading()) {\n <mat-icon>send</mat-icon>\n }\n @if (isLoading()) {\n <mat-spinner diameter=\"20\"></mat-spinner>\n }\n {{ isLoading() ? 'Enviando...' : 'Enviar por WhatsApp' }}\n </button>\n\n <button mat-button type=\"button\" (click)=\"clearForm()\" [disabled]=\"isLoading()\">\n <mat-icon>clear</mat-icon>\n Limpiar\n </button>\n </div>\n\n <!-- Enlace opcional a WhatsApp Web -->\n @if (showWhatsAppWebLink()) {\n <div class=\"whatsapp-web-link\">\n <mat-icon>info</mat-icon>\n <span>\u00BFNo se pudo enviar? Prueba enviando directamente:</span>\n <button mat-button color=\"accent\" (click)=\"openWhatsAppWeb()\">\n <mat-icon>open_in_new</mat-icon>\n Abrir WhatsApp Web\n </button>\n <button mat-icon-button (click)=\"hideWhatsAppWebLink()\" aria-label=\"Cerrar\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n }\n </form>\n </mat-card-content>\n </mat-card>\n</div>\n", styles: [".whatsapp-sender-container{max-width:500px;margin:0 auto;padding:16px}.whatsapp-sender-container .sender-card{box-shadow:0 4px 8px #0000001a;border-radius:12px}.whatsapp-sender-container .sender-card mat-card-header{padding-bottom:16px}.whatsapp-sender-container .sender-card mat-card-header .whatsapp-avatar{background:linear-gradient(135deg,#25d366,#128c7e);color:#fff;display:flex;align-items:center;justify-content:center;border-radius:50%;width:40px;height:40px}.whatsapp-sender-container .sender-card mat-card-header .whatsapp-avatar mat-icon{font-size:24px}.whatsapp-sender-container .sender-card mat-card-header mat-card-title{color:#128c7e;font-weight:600}.whatsapp-sender-container .sender-card mat-card-header mat-card-subtitle{color:#666}.whatsapp-sender-container .sender-card mat-card-content{padding-top:0}.whatsapp-sender-container .sender-card mat-card-content .full-width{width:100%;margin-bottom:16px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons{display:flex;gap:12px;justify-content:flex-end;margin-top:24px;flex-wrap:wrap}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button{background:linear-gradient(135deg,#25d366,#128c7e);color:#fff;min-width:180px;height:44px;border-radius:22px;font-weight:500}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button:hover:not(:disabled){background:linear-gradient(135deg,#128c7e,#25d366);transform:translateY(-1px);box-shadow:0 4px 12px #25d3664d}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button:disabled{background:#ccc;color:#666}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button mat-icon{margin-right:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button mat-spinner{margin-right:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button]{color:#666;border-radius:22px;height:44px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button]:hover:not(:disabled){background-color:#0000000a}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button] mat-icon{margin-right:4px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link{display:flex;align-items:center;gap:8px;margin-top:16px;padding:12px 16px;background-color:#f5f5f5;border-radius:8px;border-left:4px solid #25d366;flex-wrap:wrap}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link mat-icon:first-child{color:#25d366;font-size:20px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link span{color:#666;font-size:14px;flex:1;min-width:200px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]{color:#25d366;font-weight:500;border-radius:20px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]:hover{background-color:#25d3661a}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button] mat-icon{margin-right:4px;font-size:18px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]{color:#999;width:32px;height:32px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]:hover{background-color:#0000001a}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button] mat-icon{font-size:18px}@media(max-width:600px){.whatsapp-sender-container{padding:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons{flex-direction:column;align-items:stretch}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button{width:100%;margin-bottom:8px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link{flex-direction:column;align-items:flex-start;gap:12px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link span{min-width:auto;width:100%}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]{width:100%;justify-content:center}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]{align-self:flex-end;position:absolute;top:8px;right:8px}}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline .mat-form-field-outline{color:#e0e0e0}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline.mat-focused .mat-form-field-outline-thick{color:#25d366}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline .mat-form-field-label{color:#666}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline.mat-focused .mat-form-field-label{color:#25d366}.whatsapp-sender-container mat-form-field mat-icon[matSuffix]{color:#25d366}.whatsapp-sender-container mat-form-field .mat-hint{color:#999;font-size:12px}.whatsapp-sender-container mat-form-field .mat-error{color:#f44336;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2.MatCardAvatar, selector: "[mat-card-avatar], [matCardAvatar]" }, { kind: "directive", type: i2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i7.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] });
1046
+ }
1047
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: WhatsAppSender, decorators: [{
1048
+ type: Component,
1049
+ args: [{ selector: 'acp-whatsapp-sender', imports: [
1050
+ CommonModule,
1051
+ ReactiveFormsModule,
1052
+ MatCardModule,
1053
+ MatFormFieldModule,
1054
+ MatInputModule,
1055
+ MatButtonModule,
1056
+ MatIconModule,
1057
+ MatProgressSpinnerModule,
1058
+ MatSnackBarModule,
1059
+ MatSelectModule,
1060
+ ], template: "<div class=\"whatsapp-sender-container\">\n <mat-card class=\"sender-card\">\n <mat-card-header>\n <div mat-card-avatar class=\"whatsapp-avatar\">\n <mat-icon>chat</mat-icon>\n </div>\n <mat-card-title>Enviar por WhatsApp</mat-card-title>\n <mat-card-subtitle>Comparte contenido directamente</mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form [formGroup]=\"whatsappForm\" (ngSubmit)=\"sendWhatsApp()\">\n <!-- Campo para n\u00FAmero de tel\u00E9fono -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00FAmero de tel\u00E9fono</mat-label>\n @if (phoneNumbers().length > 1) {\n <mat-select formControlName=\"phoneNumber\">\n @for (phone of phoneNumbers(); track phone) {\n <mat-option [value]=\"phone\">{{ phone }}</mat-option>\n }\n </mat-select>\n } @else {\n <input\n matInput\n formControlName=\"phoneNumber\"\n [placeholder]=\"phonePlaceholder\"\n type=\"tel\"\n />\n }\n <mat-icon matSuffix>phone</mat-icon>\n <mat-hint>{{ phoneHint }}</mat-hint>\n @if (whatsappForm.get('phoneNumber')?.hasError('required')) {\n <mat-error>El n\u00FAmero de tel\u00E9fono es requerido</mat-error>\n }\n @if (whatsappForm.get('phoneNumber')?.hasError('invalidPhone')) {\n <mat-error>Ingrese un n\u00FAmero ecuatoriano v\u00E1lido (09xxxxxxxx)</mat-error>\n }\n </mat-form-field>\n\n <!-- Campo para mensaje -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Mensaje</mat-label>\n <textarea\n matInput\n formControlName=\"message\"\n placeholder=\"Escribe tu mensaje aqu\u00ED...\"\n rows=\"4\"\n maxlength=\"500\"\n >\n </textarea>\n <mat-icon matSuffix>message</mat-icon>\n <mat-hint align=\"end\">{{ messageLength }}/500 caracteres</mat-hint>\n @if (whatsappForm.get('message')?.hasError('required')) {\n <mat-error>El mensaje es requerido</mat-error>\n }\n </mat-form-field>\n\n <!-- Botones de acci\u00F3n -->\n <div class=\"action-buttons\">\n <button\n mat-raised-button\n color=\"primary\"\n type=\"submit\"\n [disabled]=\"whatsappForm.invalid || isLoading()\"\n class=\"send-button\"\n >\n @if (!isLoading()) {\n <mat-icon>send</mat-icon>\n }\n @if (isLoading()) {\n <mat-spinner diameter=\"20\"></mat-spinner>\n }\n {{ isLoading() ? 'Enviando...' : 'Enviar por WhatsApp' }}\n </button>\n\n <button mat-button type=\"button\" (click)=\"clearForm()\" [disabled]=\"isLoading()\">\n <mat-icon>clear</mat-icon>\n Limpiar\n </button>\n </div>\n\n <!-- Enlace opcional a WhatsApp Web -->\n @if (showWhatsAppWebLink()) {\n <div class=\"whatsapp-web-link\">\n <mat-icon>info</mat-icon>\n <span>\u00BFNo se pudo enviar? Prueba enviando directamente:</span>\n <button mat-button color=\"accent\" (click)=\"openWhatsAppWeb()\">\n <mat-icon>open_in_new</mat-icon>\n Abrir WhatsApp Web\n </button>\n <button mat-icon-button (click)=\"hideWhatsAppWebLink()\" aria-label=\"Cerrar\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n }\n </form>\n </mat-card-content>\n </mat-card>\n</div>\n", styles: [".whatsapp-sender-container{max-width:500px;margin:0 auto;padding:16px}.whatsapp-sender-container .sender-card{box-shadow:0 4px 8px #0000001a;border-radius:12px}.whatsapp-sender-container .sender-card mat-card-header{padding-bottom:16px}.whatsapp-sender-container .sender-card mat-card-header .whatsapp-avatar{background:linear-gradient(135deg,#25d366,#128c7e);color:#fff;display:flex;align-items:center;justify-content:center;border-radius:50%;width:40px;height:40px}.whatsapp-sender-container .sender-card mat-card-header .whatsapp-avatar mat-icon{font-size:24px}.whatsapp-sender-container .sender-card mat-card-header mat-card-title{color:#128c7e;font-weight:600}.whatsapp-sender-container .sender-card mat-card-header mat-card-subtitle{color:#666}.whatsapp-sender-container .sender-card mat-card-content{padding-top:0}.whatsapp-sender-container .sender-card mat-card-content .full-width{width:100%;margin-bottom:16px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons{display:flex;gap:12px;justify-content:flex-end;margin-top:24px;flex-wrap:wrap}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button{background:linear-gradient(135deg,#25d366,#128c7e);color:#fff;min-width:180px;height:44px;border-radius:22px;font-weight:500}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button:hover:not(:disabled){background:linear-gradient(135deg,#128c7e,#25d366);transform:translateY(-1px);box-shadow:0 4px 12px #25d3664d}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button:disabled{background:#ccc;color:#666}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button mat-icon{margin-right:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons .send-button mat-spinner{margin-right:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button]{color:#666;border-radius:22px;height:44px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button]:hover:not(:disabled){background-color:#0000000a}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button[mat-button] mat-icon{margin-right:4px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link{display:flex;align-items:center;gap:8px;margin-top:16px;padding:12px 16px;background-color:#f5f5f5;border-radius:8px;border-left:4px solid #25d366;flex-wrap:wrap}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link mat-icon:first-child{color:#25d366;font-size:20px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link span{color:#666;font-size:14px;flex:1;min-width:200px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]{color:#25d366;font-weight:500;border-radius:20px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]:hover{background-color:#25d3661a}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button] mat-icon{margin-right:4px;font-size:18px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]{color:#999;width:32px;height:32px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]:hover{background-color:#0000001a}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button] mat-icon{font-size:18px}@media(max-width:600px){.whatsapp-sender-container{padding:8px}.whatsapp-sender-container .sender-card mat-card-content .action-buttons{flex-direction:column;align-items:stretch}.whatsapp-sender-container .sender-card mat-card-content .action-buttons button{width:100%;margin-bottom:8px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link{flex-direction:column;align-items:flex-start;gap:12px}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link span{min-width:auto;width:100%}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-button]{width:100%;justify-content:center}.whatsapp-sender-container .sender-card mat-card-content .whatsapp-web-link button[mat-icon-button]{align-self:flex-end;position:absolute;top:8px;right:8px}}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline .mat-form-field-outline{color:#e0e0e0}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline.mat-focused .mat-form-field-outline-thick{color:#25d366}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline .mat-form-field-label{color:#666}.whatsapp-sender-container mat-form-field.mat-form-field-appearance-outline.mat-focused .mat-form-field-label{color:#25d366}.whatsapp-sender-container mat-form-field mat-icon[matSuffix]{color:#25d366}.whatsapp-sender-container mat-form-field .mat-hint{color:#999;font-size:12px}.whatsapp-sender-container mat-form-field .mat-error{color:#f44336;font-size:12px}\n"] }]
1061
+ }], propDecorators: { config: [{
1062
+ type: Input
1063
+ }] } });
1064
+
1065
+ // Contracts
1066
+
1067
+ /**
1068
+ * Generated bundle index. Do not edit.
1069
+ */
1070
+
1071
+ export { API_PATHS, DOCUMENT_TYPES, FileMapperUtil, GreenWhatsAppAdapter, MetaWhatsAppAdapter, PRINTER_PORT, PhoneFormatterUtil, PrinterAdapter, PrinterFacade, REPORT_PORT, ReportAdapter, ReportFacade, ReportParamsBuilder, WHATSAPP_MESSAGING_PORT, WhatsAppMessageBuilder, WhatsAppMessagingFacade, WhatsAppSender };
1072
+ //# sourceMappingURL=acontplus-ng-common.mjs.map