ng-easycommerce-v18 0.3.21-beta.3 → 0.3.22-beta.2

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.
Files changed (23) hide show
  1. package/README.md +7 -7
  2. package/esm2022/lib/classes/filters/filter.mjs +27 -2
  3. package/esm2022/lib/constants/api.constants.service.mjs +7 -1
  4. package/esm2022/lib/ec-components/blocks-ec/block-products-ec/block-products-ec.component.mjs +5 -3
  5. package/esm2022/lib/ec-components/checkout-ec/payment-ec/payment-ec.component.mjs +1 -5
  6. package/esm2022/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.mjs +44 -53
  7. package/esm2022/lib/ec-components/header-ec/header-ec.component.mjs +32 -23
  8. package/esm2022/lib/ec-components/related-products-ec/related-products-ec.component.mjs +6 -4
  9. package/esm2022/lib/ec-components/widgets-ec/magnizoom-ec/magnizoom-ec.component.mjs +4 -2
  10. package/esm2022/lib/ec-components/widgets-ec/redsys-catch-ec/redsys-catch-ec.component.mjs +7 -17
  11. package/esm2022/lib/ec-services/analytics/facebook-pixel.service.mjs +4 -2
  12. package/esm2022/lib/ec-services/analytics/google-analytics.service.mjs +4 -2
  13. package/esm2022/lib/ec-services/analytics/gtm.service.mjs +90 -30
  14. package/esm2022/lib/ec-services/options.service.mjs +27 -3
  15. package/esm2022/lib/ec-services/parameters.service.mjs +41 -1
  16. package/fesm2022/ng-easycommerce-v18.mjs +445 -296
  17. package/fesm2022/ng-easycommerce-v18.mjs.map +1 -1
  18. package/lib/constants/api.constants.service.d.ts +4 -0
  19. package/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.d.ts +0 -1
  20. package/lib/ec-services/analytics/gtm.service.d.ts +5 -0
  21. package/lib/ec-services/options.service.d.ts +4 -0
  22. package/lib/ec-services/parameters.service.d.ts +6 -0
  23. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, Injectable, PLATFORM_ID, RendererFactory2, afterNextRender, signal, EnvironmentInjector, runInInjectionContext, Component, ChangeDetectorRef, HostListener, CUSTOM_ELEMENTS_SCHEMA, Input, Pipe, Injector, EventEmitter, Output, forwardRef, afterRender, ViewChild, Inject, computed, NgZone, Renderer2, ChangeDetectionStrategy, Directive } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, Injectable, PLATFORM_ID, RendererFactory2, afterNextRender, signal, EnvironmentInjector, runInInjectionContext, Component, ChangeDetectorRef, HostListener, CUSTOM_ELEMENTS_SCHEMA, Input, Pipe, Injector, EventEmitter, Output, forwardRef, afterRender, ViewChild, Inject, computed, Renderer2, ChangeDetectionStrategy, Directive } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { DOCUMENT, isPlatformBrowser, AsyncPipe, CommonModule, TitleCasePipe, JsonPipe, UpperCasePipe, Location } from '@angular/common';
5
5
  import { take, BehaviorSubject, shareReplay, map, catchError, of, filter, ReplaySubject, firstValueFrom, concatMap, throwError, switchMap, combineLatest } from 'rxjs';
@@ -123,6 +123,12 @@ class ApiConstantsService {
123
123
  this._translate.use(locale.split('_')[0]);
124
124
  this.LOCALE = locale;
125
125
  }
126
+ /**
127
+ * Indica si la aplicación está corriendo en producción.
128
+ */
129
+ get IS_PRODUCTION() {
130
+ return this.environment.production === true;
131
+ }
126
132
  //Storage key
127
133
  LOCALE_KEY = 'LOCALE';
128
134
  CHANNEL_KEY = 'CHANNEL';
@@ -533,6 +539,10 @@ class OptionsService {
533
539
  * Maneja las peticiones a la API
534
540
  */
535
541
  connection = inject(ConnectionService);
542
+ /**
543
+ * Platform ID para verificar si estamos en el navegador
544
+ */
545
+ platformId = inject(PLATFORM_ID);
536
546
  /**
537
547
  * Constantes del core
538
548
  */
@@ -643,7 +653,26 @@ class OptionsService {
643
653
  * @returns
644
654
  */
645
655
  removeAccents(str) {
646
- return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
656
+ if (isPlatformBrowser(this.platformId) && typeof String.prototype.normalize === 'function') {
657
+ return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
658
+ }
659
+ // Fallback para SSR - remover acentos manualmente
660
+ return str.replace(/[àáâãäå]/g, 'a')
661
+ .replace(/[èéêë]/g, 'e')
662
+ .replace(/[ìíîï]/g, 'i')
663
+ .replace(/[òóôõö]/g, 'o')
664
+ .replace(/[ùúûü]/g, 'u')
665
+ .replace(/[ýÿ]/g, 'y')
666
+ .replace(/[ñ]/g, 'n')
667
+ .replace(/[ç]/g, 'c')
668
+ .replace(/[ÀÁÂÃÄÅ]/g, 'A')
669
+ .replace(/[ÈÉÊË]/g, 'E')
670
+ .replace(/[ÌÍÎÏ]/g, 'I')
671
+ .replace(/[ÒÓÔÕÖ]/g, 'O')
672
+ .replace(/[ÙÚÛÜ]/g, 'U')
673
+ .replace(/[ÝŸ]/g, 'Y')
674
+ .replace(/[Ñ]/g, 'N')
675
+ .replace(/[Ç]/g, 'C');
647
676
  }
648
677
  /**
649
678
  * Realiza un mapeo de los datos para darle un formato mas entendible a la
@@ -824,172 +853,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
824
853
  }]
825
854
  }], ctorParameters: () => [] });
826
855
 
827
- /**
828
- * Servicio que maneja los parametros de la aplicación.
829
- */
830
- class ParametersService {
831
- /**
832
- * Constantes de la API
833
- */
834
- apiConstants = inject(ApiConstantsService);
835
- /**
836
- * Maneja las peticiones a la API
837
- */
838
- connection = inject(ConnectionService);
839
- //API URLS
840
- /**
841
- * Devuelve la URL para obtener todos los parametros.
842
- * @returns
843
- */
844
- getAllParametersAPI() { return `${this.apiConstants.SHOP_API_URL}${this.apiConstants.CHANNEL}/parameters`; }
845
- //OBSERVABLES
846
- /**
847
- * Subject para manejar los cambios en los datos de los parametros
848
- */
849
- parametersSubject = new BehaviorSubject(null);
850
- parameters$ = this.parametersSubject.asObservable();
851
- //METHODS
852
- /**
853
- * Obtiene los parámetros del backend.
854
- * @returns
855
- */
856
- getParameters() {
857
- if (this.parametersSubject.getValue() === null) {
858
- this.connection.get(this.getAllParametersAPI()).pipe(shareReplay(1), map((response) => {
859
- this.parametersSubject.next(response);
860
- return response;
861
- }), catchError((error) => {
862
- console.log(`Error: ${error}`);
863
- this.parametersSubject.next([]);
864
- return of([]);
865
- })).subscribe();
866
- }
867
- return this.parameters$;
868
- }
869
- /**
870
- * Comprueba si el código de un parámetro existe en la lista de parámetros.
871
- * @param params Lista de parámetros
872
- * @param code Código del parámetro a buscar
873
- * @returns El objeto con los datos del parámetro encontrado, sino retorna `null`
874
- */
875
- hasParams(params, code) { return params && params.find((param) => param.code?.toLowerCase().includes(code.toLowerCase())); }
876
- constructor() { }
877
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
878
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, providedIn: 'root' });
879
- }
880
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, decorators: [{
881
- type: Injectable,
882
- args: [{
883
- providedIn: 'root'
884
- }]
885
- }], ctorParameters: () => [] });
886
-
887
- /**
888
- * Servicio que maneja los bloques como repositorio.
889
- * @class BlocksRepositoryService
890
- */
891
- class BlocksRepositoryService {
892
- /**
893
- * Constantes de la API
894
- */
895
- apiConstants = inject(ApiConstantsService);
896
- /**
897
- * Menaja las conexiones con la API
898
- */
899
- connection = inject(ConnectionService);
900
- /**
901
- * Subject para manejar los bloques recibidos.
902
- */
903
- blocksSubject = new BehaviorSubject([]);
904
- /**
905
- * Observable de bloques.
906
- */
907
- blocks$ = this.blocksSubject.asObservable();
908
- //API URLS
909
- /**
910
- * Devuelve la url para obtener los bloques segun la sección.
911
- * @param section
912
- * @returns
913
- */
914
- blocksAPI(section) { return `${this.apiConstants.CMS_URL}${this.apiConstants.CHANNEL}/blocks/section/${section}?locale=${this.apiConstants.LOCALE}`; }
915
- /**
916
- * Obtiene los bloques segun la seccion del Backend.
917
- * @param section
918
- * @returns Un observable de los bloques.
919
- */
920
- getBlocks(section) {
921
- //if(this.blocksSubject.getValue() != null){
922
- this.connection.get(this.blocksAPI(section)).pipe(shareReplay(1), map((response) => {
923
- //console.log(response)
924
- this.blocksSubject.next(response.items);
925
- return response;
926
- })).subscribe();
927
- //}
928
- return this.blocks$;
929
- }
930
- /* private hasSectionBlocks = (section: string) => {
931
-
932
- } */
933
- appendBlock = (blocks, section) => {
934
- const new_block = { section, blocks };
935
- };
936
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
937
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, providedIn: 'root' });
938
- }
939
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, decorators: [{
940
- type: Injectable,
941
- args: [{
942
- providedIn: 'root'
943
- }]
944
- }] });
945
-
946
- /**
947
- * Servicio de tipo adapter para manejar los bloques.
948
- * @class BlocksService
949
- */
950
- class BlocksService {
951
- /**
952
- * Constantes de la API
953
- */
954
- apiConstants = inject(ApiConstantsService);
955
- /**
956
- * Servicio de BlocksRepository.
957
- */
958
- blocksRepositoryService = inject(BlocksRepositoryService);
959
- /**
960
- * Subject para manejar los bloques recibidos.
961
- */
962
- blocksSubject = new BehaviorSubject([]);
963
- /**
964
- * Observable de bloques.
965
- */
966
- blocks$ = this.blocksSubject.asObservable();
967
- //API URLS
968
- /**
969
- * Devuelve la url para interactuar con el bloque de contacto.
970
- * @returns
971
- */
972
- contactFormResponseAPI() { return `${this.apiConstants.CMS_URL}${this.apiConstants.LOCALE}/contact-form/response`; }
973
- //METHODS
974
- /**
975
- * Obtiene los bloques de una sección en especifico.
976
- * @param section
977
- * @returns Observable de los bloques.
978
- */
979
- getBlocks(section) {
980
- return this.blocksRepositoryService.getBlocks(section);
981
- }
982
- constructor() { }
983
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
984
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, providedIn: 'root' });
985
- }
986
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, decorators: [{
987
- type: Injectable,
988
- args: [{
989
- providedIn: 'root'
990
- }]
991
- }], ctorParameters: () => [] });
992
-
993
856
  /**
994
857
  * Servicio que se encarga de manejar el Analytics de Facebook.
995
858
  * @class FacebookPixelService
@@ -1026,7 +889,9 @@ class FacebookPixelService {
1026
889
  this.renderer.appendChild(this.document?.body, new_analityc_script);
1027
890
  this.enabled = true;
1028
891
  }
1029
- setTimeout(() => this.callEvent('initialize'), 1000);
892
+ if (isPlatformBrowser(this.platformId)) {
893
+ setTimeout(() => this.callEvent('initialize'), 1000);
894
+ }
1030
895
  }
1031
896
  /**
1032
897
  * Ejecuta el evento pasado por parametro.
@@ -1243,7 +1108,9 @@ class GoogleAnalyticsService {
1243
1108
  this.renderer.appendChild(this.document?.head, declaration);
1244
1109
  this.enabled = true;
1245
1110
  }
1246
- setTimeout(() => this.startListeningPageViews(gtm_id), 1000);
1111
+ if (isPlatformBrowser(this.platformId)) {
1112
+ setTimeout(() => this.startListeningPageViews(gtm_id), 1000);
1113
+ }
1247
1114
  }
1248
1115
  /**
1249
1116
  *
@@ -1516,16 +1383,29 @@ class GTMService {
1516
1383
  this.document = inject(DOCUMENT);
1517
1384
  }
1518
1385
  }
1386
+ get isBrowser() {
1387
+ return isPlatformBrowser(this.platformId);
1388
+ }
1389
+ /** Acceso al window SSR-safe */
1390
+ get windowRef() {
1391
+ if (!this.isBrowser)
1392
+ return null;
1393
+ return window;
1394
+ }
1395
+ /** Acceso al document SSR-safe */
1396
+ get documentRef() {
1397
+ return this.isBrowser ? this.document : null;
1398
+ }
1519
1399
  /**
1520
1400
  * Obtiene el dataLayer del objecto window
1521
1401
  * @returns
1522
1402
  */
1523
1403
  getDataLayer() {
1524
- const window = this.window;
1525
- /* if(window)
1526
- window.dataLayer = window.dataLayer || [];
1527
- return window.dataLayer; */
1528
- return [];
1404
+ const win = this.windowRef;
1405
+ if (!win)
1406
+ return [];
1407
+ win.dataLayer = win.dataLayer || [];
1408
+ return win.dataLayer;
1529
1409
  }
1530
1410
  /**
1531
1411
  * Agrega datos al dataLayer.
@@ -1533,6 +1413,8 @@ class GTMService {
1533
1413
  */
1534
1414
  pushOnDataLayer(obj) {
1535
1415
  const dataLayer = this.getDataLayer();
1416
+ if (!dataLayer.length && !this.isBrowser)
1417
+ return;
1536
1418
  dataLayer.push(obj);
1537
1419
  }
1538
1420
  /**
@@ -1541,24 +1423,45 @@ class GTMService {
1541
1423
  */
1542
1424
  addGtmToDom() {
1543
1425
  return new Promise((resolve, reject) => {
1426
+ if (!this.isBrowser) {
1427
+ return resolve(false);
1428
+ }
1544
1429
  if (this.isLoaded) {
1545
1430
  return resolve(this.isLoaded);
1546
1431
  }
1547
- //const doc = this.browserGlobals.documentRef();
1548
- this.pushOnDataLayer({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
1549
- const gtmScript = this.document?.createElement('script'); //doc.createElement('script');
1550
- if (gtmScript) {
1551
- gtmScript.id = 'GTMscript';
1552
- gtmScript.async = true;
1553
- gtmScript.src = this.applyGtmQueryParams(this.config["gtm_resource_path"] ? this.config["gtm_resource_path"] : 'https://www.googletagmanager.com/gtm.js');
1554
- gtmScript.addEventListener('load', () => {
1555
- return resolve(this.isLoaded = true);
1556
- });
1557
- gtmScript.addEventListener('error', () => {
1558
- return reject(false);
1559
- });
1560
- this.document?.head.insertBefore(gtmScript, this.document.head.firstChild);
1432
+ const doc = this.documentRef;
1433
+ if (!doc) {
1434
+ return reject(false);
1435
+ }
1436
+ const existing = doc.getElementById('GTMscript');
1437
+ if (existing) {
1438
+ this.isLoaded = true;
1439
+ return resolve(true);
1561
1440
  }
1441
+ this.pushOnDataLayer({
1442
+ 'gtm.start': new Date().getTime(),
1443
+ event: 'gtm.js'
1444
+ });
1445
+ const gtmScript = doc.createElement('script');
1446
+ if (!gtmScript) {
1447
+ return reject(false);
1448
+ }
1449
+ gtmScript.id = 'GTMscript';
1450
+ gtmScript.async = true;
1451
+ gtmScript.src = this.applyGtmQueryParams(this.config['gtm_resource_path']
1452
+ ? this.config['gtm_resource_path']
1453
+ : 'https://www.googletagmanager.com/gtm.js');
1454
+ gtmScript.addEventListener('load', () => {
1455
+ this.isLoaded = true;
1456
+ return resolve(true);
1457
+ });
1458
+ gtmScript.addEventListener('error', () => {
1459
+ this.isLoaded = false;
1460
+ return reject(false);
1461
+ });
1462
+ doc.head
1463
+ ? doc.head.insertBefore(gtmScript, doc.head.firstChild)
1464
+ : doc.appendChild(gtmScript);
1562
1465
  });
1563
1466
  }
1564
1467
  applyGtmQueryParams(url) {
@@ -1572,29 +1475,52 @@ class GTMService {
1572
1475
  * @param config Configuraciones de Google Tag Manager
1573
1476
  */
1574
1477
  initialize(config) {
1478
+ if (!this.isBrowser)
1479
+ return;
1480
+ if (this.isLoaded)
1481
+ return;
1575
1482
  this.config = config || this.config;
1483
+ if (!config?.id)
1484
+ return;
1576
1485
  this.addGtmToDom().then(() => {
1577
- this.router.events.forEach(item => {
1578
- if (item instanceof NavigationEnd) {
1579
- const gtmTag = {
1580
- event: 'page',
1581
- pageName: item.url
1582
- };
1583
- this.pushTag(gtmTag);
1584
- }
1486
+ this.isLoaded = true;
1487
+ this.enabled = true;
1488
+ this.router.events
1489
+ .pipe(filter(event => event instanceof NavigationEnd))
1490
+ .subscribe((event) => {
1491
+ const gtmTag = {
1492
+ event: 'page',
1493
+ pageName: event.urlAfterRedirects ?? event.url
1494
+ };
1495
+ this.pushTag(gtmTag);
1585
1496
  });
1497
+ }).catch(() => {
1498
+ this.isLoaded = false;
1499
+ this.enabled = false;
1586
1500
  });
1587
1501
  }
1588
1502
  pushTag(item) {
1589
1503
  return new Promise((resolve, reject) => {
1504
+ if (!this.isBrowser) {
1505
+ return resolve();
1506
+ }
1507
+ if (!this.config?.id) {
1508
+ return resolve();
1509
+ }
1590
1510
  if (!this.isLoaded) {
1591
1511
  this.addGtmToDom().then(() => {
1512
+ this.isLoaded = true;
1513
+ this.enabled = true;
1592
1514
  this.pushOnDataLayer(item);
1593
1515
  return resolve();
1594
- }).catch(() => reject());
1516
+ }).catch(err => {
1517
+ return reject(err);
1518
+ });
1595
1519
  }
1596
1520
  else {
1597
- this.pushOnDataLayer(item);
1521
+ if (this.enabled) {
1522
+ this.pushOnDataLayer(item);
1523
+ }
1598
1524
  return resolve();
1599
1525
  }
1600
1526
  });
@@ -2056,6 +1982,211 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
2056
1982
  }]
2057
1983
  }], ctorParameters: () => [] });
2058
1984
 
1985
+ /**
1986
+ * Servicio que maneja los parametros de la aplicación.
1987
+ */
1988
+ class ParametersService {
1989
+ /**
1990
+ * Constantes de la API
1991
+ */
1992
+ apiConstants = inject(ApiConstantsService);
1993
+ /**
1994
+ * Maneja las peticiones a la API
1995
+ */
1996
+ connection = inject(ConnectionService);
1997
+ /**
1998
+ * Servicio de Analytics
1999
+ */
2000
+ analyticsService = inject(AnalyticsService);
2001
+ //API URLS
2002
+ /**
2003
+ * Devuelve la URL para obtener todos los parametros.
2004
+ * @returns
2005
+ */
2006
+ getAllParametersAPI() { return `${this.apiConstants.SHOP_API_URL}${this.apiConstants.CHANNEL}/parameters`; }
2007
+ //OBSERVABLES
2008
+ /**
2009
+ * Subject para manejar los cambios en los datos de los parametros
2010
+ */
2011
+ parametersSubject = new BehaviorSubject(null);
2012
+ parameters$ = this.parametersSubject.asObservable();
2013
+ //METHODS
2014
+ /**
2015
+ * Obtiene los parámetros del backend.
2016
+ * @returns
2017
+ */
2018
+ getParameters() {
2019
+ if (this.parametersSubject.getValue() === null) {
2020
+ this.connection.get(this.getAllParametersAPI()).pipe(shareReplay(1), map((response) => {
2021
+ this.parametersSubject.next(response);
2022
+ this.initAnalyticsFromParams(response);
2023
+ return response;
2024
+ }), catchError((error) => {
2025
+ console.log(`Error: ${error}`);
2026
+ this.parametersSubject.next([]);
2027
+ return of([]);
2028
+ })).subscribe();
2029
+ }
2030
+ return this.parameters$;
2031
+ }
2032
+ /**
2033
+ * Comprueba si el código de un parámetro existe en la lista de parámetros.
2034
+ * @param params Lista de parámetros
2035
+ * @param code Código del parámetro a buscar
2036
+ * @returns El objeto con los datos del parámetro encontrado, sino retorna `null`
2037
+ */
2038
+ hasParams(params, code) { return params && params.find((param) => param.code?.toLowerCase().includes(code.toLowerCase())); }
2039
+ findAnalyticsParam(params, service) {
2040
+ const channel = this.apiConstants.CHANNEL?.toLowerCase() ?? '';
2041
+ // Código específico por canal: facebook_nombrecanal_id
2042
+ const codeChannel = `${service}_${channel}_id`;
2043
+ // Código genérico: facebook_id
2044
+ const codeGeneric = `${service}_id`;
2045
+ const normalize = (code) => (code || '').toLowerCase();
2046
+ let param = params.find(p => normalize(p.code) === codeChannel);
2047
+ if (!param) {
2048
+ param = params.find(p => normalize(p.code) === codeGeneric);
2049
+ }
2050
+ return param;
2051
+ }
2052
+ initAnalyticsFromParams(params) {
2053
+ if (!params || !params.length)
2054
+ return;
2055
+ if (!this.apiConstants.IS_PRODUCTION)
2056
+ return;
2057
+ const facebook = this.findAnalyticsParam(params, 'facebook');
2058
+ const google = this.findAnalyticsParam(params, 'google');
2059
+ const gtm = this.findAnalyticsParam(params, 'gtm');
2060
+ const metricool = this.findAnalyticsParam(params, 'metricool');
2061
+ const doppler = this.findAnalyticsParam(params, 'doppler');
2062
+ if (facebook)
2063
+ this.analyticsService.initializeFacebook(facebook.value);
2064
+ if (google)
2065
+ this.analyticsService.initializeGoogle(google.value);
2066
+ if (gtm)
2067
+ this.analyticsService.initializeGTM(gtm.value);
2068
+ if (metricool)
2069
+ this.analyticsService.initializeMetricool(metricool.value);
2070
+ if (doppler)
2071
+ this.analyticsService.initializeDoppler(doppler.value);
2072
+ }
2073
+ constructor() { }
2074
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2075
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, providedIn: 'root' });
2076
+ }
2077
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ParametersService, decorators: [{
2078
+ type: Injectable,
2079
+ args: [{
2080
+ providedIn: 'root'
2081
+ }]
2082
+ }], ctorParameters: () => [] });
2083
+
2084
+ /**
2085
+ * Servicio que maneja los bloques como repositorio.
2086
+ * @class BlocksRepositoryService
2087
+ */
2088
+ class BlocksRepositoryService {
2089
+ /**
2090
+ * Constantes de la API
2091
+ */
2092
+ apiConstants = inject(ApiConstantsService);
2093
+ /**
2094
+ * Menaja las conexiones con la API
2095
+ */
2096
+ connection = inject(ConnectionService);
2097
+ /**
2098
+ * Subject para manejar los bloques recibidos.
2099
+ */
2100
+ blocksSubject = new BehaviorSubject([]);
2101
+ /**
2102
+ * Observable de bloques.
2103
+ */
2104
+ blocks$ = this.blocksSubject.asObservable();
2105
+ //API URLS
2106
+ /**
2107
+ * Devuelve la url para obtener los bloques segun la sección.
2108
+ * @param section
2109
+ * @returns
2110
+ */
2111
+ blocksAPI(section) { return `${this.apiConstants.CMS_URL}${this.apiConstants.CHANNEL}/blocks/section/${section}?locale=${this.apiConstants.LOCALE}`; }
2112
+ /**
2113
+ * Obtiene los bloques segun la seccion del Backend.
2114
+ * @param section
2115
+ * @returns Un observable de los bloques.
2116
+ */
2117
+ getBlocks(section) {
2118
+ //if(this.blocksSubject.getValue() != null){
2119
+ this.connection.get(this.blocksAPI(section)).pipe(shareReplay(1), map((response) => {
2120
+ //console.log(response)
2121
+ this.blocksSubject.next(response.items);
2122
+ return response;
2123
+ })).subscribe();
2124
+ //}
2125
+ return this.blocks$;
2126
+ }
2127
+ /* private hasSectionBlocks = (section: string) => {
2128
+
2129
+ } */
2130
+ appendBlock = (blocks, section) => {
2131
+ const new_block = { section, blocks };
2132
+ };
2133
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2134
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, providedIn: 'root' });
2135
+ }
2136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksRepositoryService, decorators: [{
2137
+ type: Injectable,
2138
+ args: [{
2139
+ providedIn: 'root'
2140
+ }]
2141
+ }] });
2142
+
2143
+ /**
2144
+ * Servicio de tipo adapter para manejar los bloques.
2145
+ * @class BlocksService
2146
+ */
2147
+ class BlocksService {
2148
+ /**
2149
+ * Constantes de la API
2150
+ */
2151
+ apiConstants = inject(ApiConstantsService);
2152
+ /**
2153
+ * Servicio de BlocksRepository.
2154
+ */
2155
+ blocksRepositoryService = inject(BlocksRepositoryService);
2156
+ /**
2157
+ * Subject para manejar los bloques recibidos.
2158
+ */
2159
+ blocksSubject = new BehaviorSubject([]);
2160
+ /**
2161
+ * Observable de bloques.
2162
+ */
2163
+ blocks$ = this.blocksSubject.asObservable();
2164
+ //API URLS
2165
+ /**
2166
+ * Devuelve la url para interactuar con el bloque de contacto.
2167
+ * @returns
2168
+ */
2169
+ contactFormResponseAPI() { return `${this.apiConstants.CMS_URL}${this.apiConstants.LOCALE}/contact-form/response`; }
2170
+ //METHODS
2171
+ /**
2172
+ * Obtiene los bloques de una sección en especifico.
2173
+ * @param section
2174
+ * @returns Observable de los bloques.
2175
+ */
2176
+ getBlocks(section) {
2177
+ return this.blocksRepositoryService.getBlocks(section);
2178
+ }
2179
+ constructor() { }
2180
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2181
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, providedIn: 'root' });
2182
+ }
2183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BlocksService, decorators: [{
2184
+ type: Injectable,
2185
+ args: [{
2186
+ providedIn: 'root'
2187
+ }]
2188
+ }], ctorParameters: () => [] });
2189
+
2059
2190
  /**
2060
2191
  * Servicio para manejar los canales de la aplicación
2061
2192
  * @class ChannelService
@@ -2644,6 +2775,31 @@ class User {
2644
2775
  }
2645
2776
  }
2646
2777
 
2778
+ /**
2779
+ * Función auxiliar para remover acentos de forma segura para SSR
2780
+ */
2781
+ function safeRemoveAccents(str) {
2782
+ if (typeof window !== 'undefined' && typeof String.prototype.normalize === 'function') {
2783
+ return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2784
+ }
2785
+ // Fallback para SSR - remover acentos manualmente
2786
+ return str.replace(/[àáâãäå]/g, 'a')
2787
+ .replace(/[èéêë]/g, 'e')
2788
+ .replace(/[ìíîï]/g, 'i')
2789
+ .replace(/[òóôõö]/g, 'o')
2790
+ .replace(/[ùúûü]/g, 'u')
2791
+ .replace(/[ýÿ]/g, 'y')
2792
+ .replace(/[ñ]/g, 'n')
2793
+ .replace(/[ç]/g, 'c')
2794
+ .replace(/[ÀÁÂÃÄÅ]/g, 'A')
2795
+ .replace(/[ÈÉÊË]/g, 'E')
2796
+ .replace(/[ÌÍÎÏ]/g, 'I')
2797
+ .replace(/[ÒÓÔÕÖ]/g, 'O')
2798
+ .replace(/[ÙÚÛÜ]/g, 'U')
2799
+ .replace(/[ÝŸ]/g, 'Y')
2800
+ .replace(/[Ñ]/g, 'N')
2801
+ .replace(/[Ç]/g, 'C');
2802
+ }
2647
2803
  class Filter {
2648
2804
  data = [];
2649
2805
  multi = false;
@@ -2663,7 +2819,7 @@ class Filter {
2663
2819
  throw new Error("Method not implemented.");
2664
2820
  }
2665
2821
  removeAccents = (str) => {
2666
- return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2822
+ return safeRemoveAccents(str);
2667
2823
  };
2668
2824
  setSelected(element, value) {
2669
2825
  //console.log(element, value);
@@ -6218,17 +6374,21 @@ class HeaderEcComponent extends MenuEcComponent {
6218
6374
  });
6219
6375
  }
6220
6376
  onWindowScroll() {
6221
- const scrollTop = window.scrollY;
6222
- this.isScrolled = scrollTop > 80;
6377
+ if (isPlatformBrowser(this.platformId)) {
6378
+ const scrollTop = window.scrollY;
6379
+ this.isScrolled = scrollTop > 80;
6380
+ }
6223
6381
  }
6224
6382
  isHomeFunction() {
6225
- const headerElement = document.querySelector('header');
6226
- if (headerElement) {
6227
- if (this.router.url !== '/home') {
6228
- headerElement.classList.add('show-menu');
6229
- }
6230
- else {
6231
- headerElement.classList.remove('show-menu');
6383
+ if (isPlatformBrowser(this.platformId)) {
6384
+ const headerElement = document.querySelector('header');
6385
+ if (headerElement) {
6386
+ if (this.router.url !== '/home') {
6387
+ headerElement.classList.add('show-menu');
6388
+ }
6389
+ else {
6390
+ headerElement.classList.remove('show-menu');
6391
+ }
6232
6392
  }
6233
6393
  }
6234
6394
  }
@@ -6252,26 +6412,30 @@ class HeaderEcComponent extends MenuEcComponent {
6252
6412
  }
6253
6413
  };
6254
6414
  borrarInput(inputId) {
6255
- if (inputId) {
6256
- const input = document.getElementById(inputId);
6257
- if (input) {
6258
- input.value = '';
6259
- }
6260
- }
6261
- else {
6262
- const inputs = ['searchInput1'];
6263
- inputs.forEach((id) => {
6264
- const input = document.getElementById(id);
6415
+ if (isPlatformBrowser(this.platformId)) {
6416
+ if (inputId) {
6417
+ const input = document.getElementById(inputId);
6265
6418
  if (input) {
6266
6419
  input.value = '';
6267
6420
  }
6268
- });
6421
+ }
6422
+ else {
6423
+ const inputs = ['searchInput1'];
6424
+ inputs.forEach((id) => {
6425
+ const input = document.getElementById(id);
6426
+ if (input) {
6427
+ input.value = '';
6428
+ }
6429
+ });
6430
+ }
6269
6431
  }
6270
6432
  this.searchValue = '';
6271
6433
  this.coreConstantsService.searchValue = '';
6272
6434
  this.getCollectionSearch();
6273
6435
  }
6274
6436
  setupMobileMenu() {
6437
+ if (!isPlatformBrowser(this.platformId))
6438
+ return;
6275
6439
  // console.log('setupMobileMenu called');
6276
6440
  const menuMobile = document.querySelector('.menuMobile');
6277
6441
  if (!(menuMobile instanceof HTMLElement))
@@ -6307,6 +6471,8 @@ class HeaderEcComponent extends MenuEcComponent {
6307
6471
  });
6308
6472
  }
6309
6473
  setupSearchInputs() {
6474
+ if (!isPlatformBrowser(this.platformId))
6475
+ return;
6310
6476
  const inputs = ['searchInput1', 'searchInput2'];
6311
6477
  inputs.forEach(id => {
6312
6478
  const input = document.getElementById(id);
@@ -7169,7 +7335,7 @@ class BlockProductsEcComponent extends BlockEcComponent {
7169
7335
  * Permite personalización de las imágenes de las flechas mediante @Input.
7170
7336
  */
7171
7337
  setupSwiperNavigation() {
7172
- if (this.meta?.styles?.carrousel !== false) {
7338
+ if (this.meta?.styles?.carrousel !== false && isPlatformBrowser(this.platformId)) {
7173
7339
  // Usar setTimeout para asegurar que el swiper esté inicializado
7174
7340
  setTimeout(() => {
7175
7341
  this.initializeSwiperWithCustomNavigation();
@@ -7181,6 +7347,8 @@ class BlockProductsEcComponent extends BlockEcComponent {
7181
7347
  * Esta función puede ser movida al componente base para reutilización.
7182
7348
  */
7183
7349
  initializeSwiperWithCustomNavigation() {
7350
+ if (!isPlatformBrowser(this.platformId))
7351
+ return;
7184
7352
  const prevButton = document.getElementById(`${this.meta?.code}-prev`);
7185
7353
  const nextButton = document.getElementById(`${this.meta?.code}-next`);
7186
7354
  const swiperElement = document.getElementById(this.meta?.code);
@@ -7546,7 +7714,9 @@ class MagnizoomEcComponent {
7546
7714
  this.image = this.document.createElement('img');
7547
7715
  this.image.onload = () => {
7548
7716
  this.lensSize = { width: this.image.width / 2, height: this.image.height / 2 };
7549
- setTimeout(() => this.render());
7717
+ if (isPlatformBrowser(this.platformId)) {
7718
+ setTimeout(() => this.render());
7719
+ }
7550
7720
  };
7551
7721
  this.image.src = src;
7552
7722
  }
@@ -7818,26 +7988,16 @@ class RedsysCatchEcComponent extends ComponentHelper {
7818
7988
  }
7819
7989
  /** Intenta cerrar la pestaña actual; si falla, ejecuta el callback de fallback. */
7820
7990
  tryCloseSelf(onFail) {
7821
- if (!isPlatformBrowser(this.platformId)) {
7822
- onFail();
7823
- return;
7824
- }
7825
- // Fallback de seguridad: si después de ~800 ms la página sigue visible,
7826
- // asumimos que window.close() fue ignorado.
7827
- const fallback = setTimeout(() => {
7828
- // Si la pestaña siguiera abierta, document.hidden suele ser false.
7829
- if (!document.hidden) {
7830
- onFail();
7831
- }
7832
- }, 800);
7991
+ if (!isPlatformBrowser(this.platformId))
7992
+ return onFail();
7993
+ let attempted = false;
7833
7994
  try {
7834
7995
  window.close();
7835
- // Si window.close() lanza excepción, limpiamos el timeout y hacemos fallback inmediato.
7996
+ attempted = true;
7836
7997
  }
7837
- catch {
7838
- clearTimeout(fallback);
7998
+ catch { }
7999
+ if (!attempted)
7839
8000
  onFail();
7840
- }
7841
8001
  }
7842
8002
  /** Oculta header/footer para esta pantalla mínima. */
7843
8003
  hideHeaderFooter() {
@@ -9834,7 +9994,6 @@ class MpRedirectEcComponent {
9834
9994
  _toastService = inject(ToastService);
9835
9995
  platformId = inject(PLATFORM_ID);
9836
9996
  finished = false;
9837
- ngZone = inject(NgZone);
9838
9997
  method = null;
9839
9998
  total_amount = 0;
9840
9999
  allData;
@@ -9857,10 +10016,10 @@ class MpRedirectEcComponent {
9857
10016
  this.windowRef = window;
9858
10017
  if ('BroadcastChannel' in window) {
9859
10018
  this.bc = new BroadcastChannel('mp_payment');
9860
- this.bc.onmessage = (e) => this.ngZone.run(() => this.onMpMessage(e?.data));
10019
+ this.bc.onmessage = (e) => this.onMpMessage(e?.data);
9861
10020
  }
9862
- window.addEventListener('storage', (e) => this.ngZone.run(() => this.onStorage(e)));
9863
- window.addEventListener('message', (e) => this.ngZone.run(() => this.onWindowMessage(e)));
10021
+ window.addEventListener('storage', this.onStorage);
10022
+ window.addEventListener('message', this.onWindowMessage);
9864
10023
  }
9865
10024
  this.getPreference();
9866
10025
  }
@@ -9913,29 +10072,23 @@ class MpRedirectEcComponent {
9913
10072
  }, 1000);
9914
10073
  };
9915
10074
  onWindowMessage = (event) => {
9916
- this.ngZone.run(() => {
9917
- const data = event?.data;
9918
- this.onMpMessage(data);
9919
- });
10075
+ const data = event?.data;
10076
+ this.onMpMessage(data);
9920
10077
  };
9921
10078
  onStorage = (e) => {
9922
- this.ngZone.run(() => {
9923
- if (!e.key || !this.sid)
9924
- return;
9925
- if (e.key === `mp:state:${this.sid}` && e.newValue) {
9926
- const state = e.newValue;
9927
- this.finishWithState(state);
9928
- }
9929
- });
10079
+ if (!e.key || !this.sid)
10080
+ return;
10081
+ if (e.key === `mp:state:${this.sid}` && e.newValue) {
10082
+ const state = e.newValue;
10083
+ this.finishWithState(state);
10084
+ }
9930
10085
  };
9931
10086
  onMpMessage = (data) => {
9932
- this.ngZone.run(() => {
9933
- if (!data || data.type !== 'mp:state')
9934
- return;
9935
- if (data.sid !== this.sid)
9936
- return;
9937
- this.finishWithState(data.state);
9938
- });
10087
+ if (!data || data.type !== 'mp:state')
10088
+ return;
10089
+ if (data.sid !== this.sid)
10090
+ return;
10091
+ this.finishWithState(data.state);
9939
10092
  };
9940
10093
  checkLocalStorageOnce() {
9941
10094
  if (!this.sid)
@@ -9946,34 +10099,32 @@ class MpRedirectEcComponent {
9946
10099
  }
9947
10100
  /** Cierra el flujo de pago con el estado final y notifica al padre. */
9948
10101
  finishWithState(state) {
9949
- this.ngZone.run(() => {
9950
- if (this.finished)
9951
- return;
9952
- this.finished = true;
9953
- if (this.pollTimer)
9954
- clearInterval(this.pollTimer);
9955
- this.pollTimer = null;
9956
- localStorage.removeItem(`mp:state:${this.sid}`);
9957
- localStorage.removeItem('mp:sid');
9958
- localStorage.removeItem('state');
9959
- try {
9960
- this.ventana && !this.ventana.closed && this.ventana.close();
9961
- }
9962
- catch { }
9963
- this.ventana = null;
9964
- if (state === 'success' || state === 'pending') {
9965
- this.phase = 'finalizing';
9966
- this.ready.emit(true);
9967
- }
9968
- else if (state === 'failure' || state === 'cancel') {
9969
- this.phase = 'idle';
9970
- this._toastService.show(state === 'cancel' ? 'Se canceló el pago con Mercado Pago' : 'payment-error');
9971
- }
9972
- else {
9973
- this.phase = 'idle';
9974
- this._toastService.show('payment-error');
9975
- }
9976
- });
10102
+ if (this.finished)
10103
+ return;
10104
+ this.finished = true;
10105
+ if (this.pollTimer)
10106
+ clearInterval(this.pollTimer);
10107
+ this.pollTimer = null;
10108
+ localStorage.removeItem(`mp:state:${this.sid}`);
10109
+ localStorage.removeItem('mp:sid');
10110
+ localStorage.removeItem('state');
10111
+ try {
10112
+ this.ventana && !this.ventana.closed && this.ventana.close();
10113
+ }
10114
+ catch { }
10115
+ this.ventana = null;
10116
+ if (state === 'success' || state === 'pending') {
10117
+ this.phase = 'finalizing';
10118
+ this.ready.emit(true);
10119
+ }
10120
+ else if (state === 'failure' || state === 'cancel') {
10121
+ this.phase = 'idle';
10122
+ this._toastService.show(state === 'cancel' ? 'Se canceló el pago con Mercado Pago' : 'payment-error');
10123
+ }
10124
+ else {
10125
+ this.phase = 'idle';
10126
+ this._toastService.show('payment-error');
10127
+ }
9977
10128
  }
9978
10129
  genSid() {
9979
10130
  return `mp_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
@@ -10339,10 +10490,6 @@ class PaymentEcComponent {
10339
10490
  return true;
10340
10491
  };
10341
10492
  verifyValidate = ($event) => {
10342
- if (!$event) {
10343
- this.setLoading(false);
10344
- return;
10345
- }
10346
10493
  this.setLoading(true);
10347
10494
  setTimeout(() => this.emitResult(), 1000);
10348
10495
  };
@@ -10876,9 +11023,11 @@ class RelatedProductsEcComponent extends BlockEcComponent {
10876
11023
  this._relatedProductsSubject.next(relatedProducts);
10877
11024
  res.map((products) => this._analyticsService.callEvent('view_item_list', { products: products.items, item_list_name: products.title || 'Related Products', item_list_id: products.id || 'related-products' }));
10878
11025
  // Inicializar swiper después de que los datos estén disponibles
10879
- setTimeout(() => {
10880
- this.initSwiper();
10881
- }, 100);
11026
+ if (isPlatformBrowser(this.platformId)) {
11027
+ setTimeout(() => {
11028
+ this.initSwiper();
11029
+ }, 100);
11030
+ }
10882
11031
  });
10883
11032
  }
10884
11033
  initSwiper() {