ng-easycommerce-v18 0.3.0 → 0.3.1-9.beta-1

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 (48) hide show
  1. package/README.md +40 -0
  2. package/assets/ec-i18n/ct.json +1 -0
  3. package/assets/ec-i18n/en.json +88 -83
  4. package/assets/ec-i18n/es.json +6 -1
  5. package/assets/ec-i18n/fr.json +1 -0
  6. package/assets/ec-i18n/gl.json +1 -0
  7. package/assets/ec-i18n/pr.json +1 -0
  8. package/esm2022/lib/classes/component-helper.mjs +11 -2
  9. package/esm2022/lib/constants/core.constants.service.mjs +107 -2
  10. package/esm2022/lib/ec-components/abstractions-components/menu-ec.component.mjs +9 -1
  11. package/esm2022/lib/ec-components/auth-ec/login-form-ec/login-form-ec.component.mjs +5 -1
  12. package/esm2022/lib/ec-components/auth-ec/register-form-ec/register-form-ec.component.mjs +5 -1
  13. package/esm2022/lib/ec-components/blocks-ec/block-newsletter-ec/block-newsletter-ec.component.mjs +9 -3
  14. package/esm2022/lib/ec-components/cart-ec/cart-item-ec/cart-item-ec.component.mjs +42 -10
  15. package/esm2022/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.mjs +141 -75
  16. package/esm2022/lib/ec-components/filters-ec/filters-ec.component.mjs +9 -1
  17. package/esm2022/lib/ec-components/header-ec/header-ec.component.mjs +53 -2
  18. package/esm2022/lib/ec-components/product-detail-ec/product-detail-ec.component.mjs +7 -5
  19. package/esm2022/lib/ec-components/product-ec/product-ec.component.mjs +143 -5
  20. package/esm2022/lib/ec-components/widgets-ec/decidir-ec/decidir-ec.component.mjs +3 -3
  21. package/esm2022/lib/ec-components/widgets-ec/index.mjs +2 -1
  22. package/esm2022/lib/ec-components/widgets-ec/redsys-catch-ec/redsys-catch-ec.component.mjs +193 -0
  23. package/esm2022/lib/ec-services/cart.service.mjs +22 -4
  24. package/esm2022/lib/ec-services/form.service.mjs +13 -1
  25. package/esm2022/lib/ec-services/order-utility.service.mjs +11 -5
  26. package/esm2022/lib/ec-services/product-detail.service.mjs +11 -1
  27. package/esm2022/lib/interfaces/environment.mjs +1 -1
  28. package/esm2022/lib/interfaces/product.mjs +1 -1
  29. package/fesm2022/ng-easycommerce-v18.mjs +775 -116
  30. package/fesm2022/ng-easycommerce-v18.mjs.map +1 -1
  31. package/lib/classes/component-helper.d.ts +1 -1
  32. package/lib/constants/core.constants.service.d.ts +24 -0
  33. package/lib/ec-components/abstractions-components/menu-ec.component.d.ts +6 -0
  34. package/lib/ec-components/auth-ec/login-form-ec/login-form-ec.component.d.ts +2 -0
  35. package/lib/ec-components/auth-ec/register-form-ec/register-form-ec.component.d.ts +2 -0
  36. package/lib/ec-components/cart-ec/cart-item-ec/cart-item-ec.component.d.ts +6 -1
  37. package/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.d.ts +38 -16
  38. package/lib/ec-components/filters-ec/filters-ec.component.d.ts +6 -0
  39. package/lib/ec-components/header-ec/header-ec.component.d.ts +16 -2
  40. package/lib/ec-components/product-detail-ec/product-detail-ec.component.d.ts +1 -0
  41. package/lib/ec-components/product-ec/product-ec.component.d.ts +14 -1
  42. package/lib/ec-components/widgets-ec/index.d.ts +1 -0
  43. package/lib/ec-components/widgets-ec/redsys-catch-ec/redsys-catch-ec.component.d.ts +47 -0
  44. package/lib/ec-services/cart.service.d.ts +1 -1
  45. package/lib/ec-services/form.service.d.ts +6 -0
  46. package/lib/interfaces/environment.d.ts +1 -0
  47. package/lib/interfaces/product.d.ts +6 -0
  48. package/package.json +1 -1
@@ -1,7 +1,7 @@
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, computed, Renderer2, ChangeDetectionStrategy, Directive, Inject } 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
- import { isPlatformBrowser, DOCUMENT, AsyncPipe, CommonModule, TitleCasePipe, JsonPipe, UpperCasePipe, Location } from '@angular/common';
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';
6
6
  import { HttpClient, HttpHeaders } from '@angular/common/http';
7
7
  import * as i1$1 from '@ngx-translate/core';
@@ -14,7 +14,7 @@ import { ToastrService } from 'ngx-toastr';
14
14
  import moment from 'moment';
15
15
  import { StorageMap } from '@ngx-pwa/local-storage';
16
16
  import * as i1$3 from '@angular/forms';
17
- import { Validators, FormBuilder, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';
17
+ import { Validators, FormsModule, FormBuilder, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
18
18
  import { register } from 'swiper/element/bundle';
19
19
  import { register as register$1 } from 'swiper/element';
20
20
  import * as i1$4 from '@angular/platform-browser';
@@ -225,6 +225,14 @@ class CoreConstantsService {
225
225
  * Provee el ID de la plataforma en donde se esta ejecutando el codigo, esto sirve para poder diferenciar si es de un servidor o un navegador web.
226
226
  */
227
227
  platformId = inject(PLATFORM_ID);
228
+ /**
229
+ * Document token para acceso SSR-compatible al documento
230
+ */
231
+ document = inject(DOCUMENT);
232
+ /**
233
+ * Contiene los datos provisto por el frontend en el archivo environment.ts
234
+ */
235
+ environment = inject(ENVIRONMENT_TOKEN);
228
236
  /**
229
237
  * Guarda la variable window del web browser.
230
238
  */
@@ -264,6 +272,102 @@ class CoreConstantsService {
264
272
  * @returns
265
273
  */
266
274
  url = () => this.apiConstants.API_URL.replace(/\/$/, ''); // remove last slash
275
+ /**
276
+ * URL del sitio frontend - Compatible con Angular SSR
277
+ */
278
+ get FRONTEND_URL() {
279
+ // Verificar si estamos en el navegador
280
+ if (isPlatformBrowser(this.platformId)) {
281
+ // Primero intenta leer de window.__env (configurado por Docker/CI)
282
+ const windowEnv = this.window?.__env;
283
+ if (windowEnv?.frontendUrl) {
284
+ // Log para debugging en desarrollo
285
+ if (!windowEnv.frontendUrl.startsWith('http')) {
286
+ console.warn('[FRONTEND_URL] Invalid frontendUrl format:', windowEnv.frontendUrl);
287
+ }
288
+ return windowEnv.frontendUrl;
289
+ }
290
+ // Si no hay configuración específica, construir desde window.location
291
+ if (this.window?.location) {
292
+ const url = `${this.window.location.protocol}//${this.window.location.host}`;
293
+ // Verificar que no sea localhost o 127.0.0.1 en producción
294
+ if (url.includes('127.0.0.1') || url.includes('localhost')) {
295
+ console.warn('[FRONTEND_URL] Using localhost URL in production, consider setting window.__env.frontendUrl');
296
+ }
297
+ return url;
298
+ }
299
+ }
300
+ // Para SSR, intentar construir desde el document si está disponible
301
+ if (this.document?.location) {
302
+ return `${this.document.location.protocol}//${this.document.location.host}`;
303
+ }
304
+ // Intentar obtener desde environment
305
+ if (this.environment.frontendUrl) {
306
+ return this.environment.frontendUrl;
307
+ }
308
+ // Último fallback - usar la URL del API si está configurada y es absoluta
309
+ const apiUrl = this.apiConstants.API_URL;
310
+ if (apiUrl && (apiUrl.startsWith('http://') || apiUrl.startsWith('https://'))) {
311
+ try {
312
+ const url = new URL(apiUrl);
313
+ return `${url.protocol}//${url.host}`;
314
+ }
315
+ catch (e) {
316
+ console.warn('[FRONTEND_URL] Could not parse API URL as fallback:', apiUrl);
317
+ }
318
+ }
319
+ // Fallback final - retornar URL vacía
320
+ console.warn('[FRONTEND_URL] No valid frontend URL found, this may cause issues with meta tags');
321
+ return '';
322
+ }
323
+ /**
324
+ * Retorna la URL completa del frontend con una ruta opcional
325
+ * @param path - Ruta opcional para agregar a la URL base
326
+ * @returns {string} URL completa del frontend
327
+ */
328
+ getFrontendUrl = (path) => {
329
+ const baseUrl = this.FRONTEND_URL;
330
+ if (!path) {
331
+ return baseUrl;
332
+ }
333
+ // Asegurar que la ruta comience con '/' y no tenga doble slash
334
+ const cleanPath = path.startsWith('/') ? path : `/${path}`;
335
+ const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
336
+ return `${cleanBaseUrl}${cleanPath}`;
337
+ };
338
+ /**
339
+ * Retorna la URL absoluta de una imagen para meta tags (compatible con SSR)
340
+ * @param postMedia - Ruta de la imagen
341
+ * @returns {string} URL absoluta de la imagen
342
+ */
343
+ getAbsoluteImageUrl = (postMedia) => {
344
+ // Si la URL ya es absoluta, devolverla tal como está
345
+ if (postMedia && (postMedia.startsWith('http://') || postMedia.startsWith('https://'))) {
346
+ return postMedia;
347
+ }
348
+ // Construir la URL de la imagen usando la URL del API
349
+ const imageUrl = this.mediaImageUrl(postMedia);
350
+ // Si la URL de la imagen ya es absoluta, devolverla
351
+ if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
352
+ return imageUrl;
353
+ }
354
+ // Si es una URL relativa, convertirla a absoluta usando la URL base del API
355
+ const apiBaseUrl = this.apiConstants.API_URL;
356
+ if (apiBaseUrl.startsWith('http://') || apiBaseUrl.startsWith('https://')) {
357
+ // API_URL ya es absoluta
358
+ return imageUrl;
359
+ }
360
+ // Si API_URL es relativa, usar la URL del frontend como base
361
+ const frontendUrl = this.FRONTEND_URL;
362
+ if (!frontendUrl) {
363
+ return imageUrl; // Fallback si no podemos determinar la URL del frontend
364
+ }
365
+ // Construir URL absoluta
366
+ const cleanApiUrl = apiBaseUrl.startsWith('/') ? apiBaseUrl.substring(1) : apiBaseUrl;
367
+ const cleanFrontendUrl = frontendUrl.endsWith('/') ? frontendUrl.slice(0, -1) : frontendUrl;
368
+ const cleanImageUrl = imageUrl.startsWith('/') ? imageUrl.substring(1) : imageUrl;
369
+ return `${cleanFrontendUrl}/${cleanApiUrl}${cleanImageUrl}`;
370
+ };
267
371
  /**
268
372
  * Retorna la url de las imagenes de los banners
269
373
  * @returns
@@ -2426,6 +2530,12 @@ class FormService {
2426
2530
  sendResponse(response_obj) {
2427
2531
  return this.connection.post(this.contactFormResponseAPI(), this.prepareData(response_obj));
2428
2532
  }
2533
+ /**
2534
+ * @description contiene un arreglo de string cuyo contenido son los codigos de los paises a excluir.
2535
+ * @example ['GB']
2536
+ */
2537
+ excludedCountries = [];
2538
+ allowedCountries = [];
2429
2539
  /**
2430
2540
  * Obtiene los paises y los guarda en un observable.
2431
2541
  * @returns
@@ -2441,6 +2551,12 @@ class FormService {
2441
2551
  const documentTypesData = documentTypesLocale['documentTypes'];
2442
2552
  this.documentTypesSubject.next(documentTypesData);
2443
2553
  })).subscribe();
2554
+ if (this.excludedCountries.length > 0) {
2555
+ this.countries$ = this.countries$.pipe(map(countries => countries.filter(country => !this.excludedCountries.includes(country.code))));
2556
+ }
2557
+ if (this.allowedCountries.length > 0) {
2558
+ this.countries$ = this.countries$.pipe(map(countries => countries.filter(country => this.allowedCountries.includes(country.code))));
2559
+ }
2444
2560
  return this.countries$;
2445
2561
  }
2446
2562
  /**
@@ -4377,8 +4493,15 @@ class CartService {
4377
4493
  * @param quantity la cantidad nueva.
4378
4494
  */
4379
4495
  updateItemQuantity(item, quantity) {
4496
+ // Validar antes de proceder
4380
4497
  if (this.validateQuantity(item, quantity) && (this.validatePriceAndCredits(item, quantity))) {
4381
- firstValueFrom(this._connection.put(this.updateItemQuantityApi(this.findItemByIdVariant(item.variant_id)), { 'quantity': Number(quantity) })).then(res => this.updateCartObj(res) && this.updateCartItemQuantity(item.variant_id, Number(quantity)), err => this.handleError(err));
4498
+ // Si pasa las validaciones, hacer la petición HTTP
4499
+ firstValueFrom(this._connection.put(this.updateItemQuantityApi(this.findItemByIdVariant(item.variant_id)), { 'quantity': Number(quantity) }))
4500
+ .then(res => this.updateCartObj(res) && this.updateCartItemQuantity(item.variant_id, Number(quantity)), err => this.handleError(err));
4501
+ return true; // Validaciones pasaron, operación iniciada
4502
+ }
4503
+ else {
4504
+ return false; // Validaciones fallaron
4382
4505
  }
4383
4506
  }
4384
4507
  /**
@@ -4459,14 +4582,25 @@ class CartService {
4459
4582
  * @returns
4460
4583
  */
4461
4584
  validateQuantity(item, quantity) {
4462
- if ((this.getCountFromItemInCart(item.productCode) + quantity) > item.product.variants[0].maximumItemsQuantity) {
4585
+ const actualQuantity = Number(this.getCountFromItemInCart(item.productCode)); // <-- Forzar a número
4586
+ const newQuantity = Number(quantity); // <-- Forzar a número
4587
+ if ((actualQuantity + newQuantity) > item.product.variants[0].maximumItemsQuantity) {
4463
4588
  this._toastService.show('maximum-items-quantity', { quantity: item.product.variants[0].maximumItemsQuantity });
4464
4589
  return false;
4465
4590
  }
4466
- if ((this.getCountFromItemInCart(item.productCode) + quantity) < item.product.variants[0].minimumItemsQuantity) {
4591
+ if ((actualQuantity + newQuantity) < item.product.variants[0].minimumItemsQuantity) {
4467
4592
  this._toastService.show('minimum-items-quantity', { quantity: item.product.variants[0].minimumItemsQuantity });
4468
4593
  return false;
4469
4594
  }
4595
+ if (item.product.variants[0].multipleQuantity && item.product.variants[0].multipleQuantity > 1) {
4596
+ if ((actualQuantity + newQuantity) % item.product.variants[0].multipleQuantity !== 0) {
4597
+ this._toastService.show('multiple-quantity-required', {
4598
+ multiple: item.product.variants[0].multipleQuantity,
4599
+ current: actualQuantity + newQuantity // <-- Ahora suma correctamente
4600
+ });
4601
+ return false;
4602
+ }
4603
+ }
4470
4604
  return true;
4471
4605
  }
4472
4606
  /**
@@ -4814,6 +4948,7 @@ class ProductDetailService {
4814
4948
  associated.minimumItemsQuantity = v.stock > 0 && 'minimumItemsQuantity' in v && v.minimumItemsQuantity > 0
4815
4949
  ? v.minimumItemsQuantity
4816
4950
  : (v.multipleQuantity ?? 1);
4951
+ associated.multipleQuantity = v.multipleQuantity ?? 1;
4817
4952
  // Precios al consumidor final
4818
4953
  associated.finalConsumer = {
4819
4954
  finalConsumerPrice: v.final_consumer_price,
@@ -5016,6 +5151,15 @@ class ProductDetailService {
5016
5151
  this._toastService.show('minimum-items-quantity', { quantity: asociatedData.minimumItemsQuantity });
5017
5152
  return false;
5018
5153
  }
5154
+ if (asociatedData.multipleQuantity && asociatedData.multipleQuantity > 1) {
5155
+ if ((actualQuantity + quantity) % asociatedData.multipleQuantity !== 0) {
5156
+ this._toastService.show('multiple-quantity-required', {
5157
+ multiple: asociatedData.multipleQuantity,
5158
+ current: actualQuantity + quantity
5159
+ });
5160
+ return false;
5161
+ }
5162
+ }
5019
5163
  return true;
5020
5164
  };
5021
5165
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ProductDetailService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -5042,12 +5186,18 @@ class OrderUtilityService {
5042
5186
  let subtotal = { type: 'subtotal', name: 'subtotal', amount: cart.totals.subtotal };
5043
5187
  let total = { type: 'total', name: 'total', amount: cart.totals.total };
5044
5188
  !forCart && new_state.push(subtotal);
5189
+ 0;
5045
5190
  for (let index = 0; index < cart.cartDiscounts.length; index++) {
5046
- cart.cartDiscounts[index].type = this.formatType(cart.cartDiscounts[index].type);
5191
+ const disc = cart.cartDiscounts[index];
5192
+ disc.type = this.formatType(disc.type);
5193
+ let amount = this.getAmount(disc, cart);
5194
+ if (disc.type === 'discount') {
5195
+ amount = cart.totals.promotion;
5196
+ }
5047
5197
  new_state.push({
5048
- type: cart.cartDiscounts[index].type,
5049
- name: (cart.cartDiscounts[index].type != 'coupon') ? cart.cartDiscounts[index].name : 'coupon',
5050
- amount: this.getAmount(cart.cartDiscounts[index], cart)
5198
+ type: disc.type,
5199
+ name: (disc.type != 'coupon') ? disc.name : 'coupon',
5200
+ amount: amount
5051
5201
  });
5052
5202
  }
5053
5203
  !forCart && new_state.push(total);
@@ -5884,6 +6034,14 @@ class MenuEcComponent {
5884
6034
  });
5885
6035
  }));
5886
6036
  }
6037
+ /**
6038
+ * Verifica si una categoría tiene la propiedad isVisible y está marcada como visible
6039
+ * @param category - La categoría a verificar
6040
+ * @returns true si la categoría es visible, false en caso contrario
6041
+ */
6042
+ hasVisibleProperty(category) {
6043
+ return category.isVisible === true;
6044
+ }
5887
6045
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MenuEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5888
6046
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: MenuEcComponent, isStandalone: true, selector: "lib-footer-ec", ngImport: i0, template: '<p>Menu and Footer Helper Component</p>', isInline: true });
5889
6047
  }
@@ -6030,6 +6188,13 @@ class HeaderEcComponent extends MenuEcComponent {
6030
6188
  hidePrices = false;
6031
6189
  __authService = inject(AuthService);
6032
6190
  _channelService = inject(ChannelService);
6191
+ changeDetector = inject(ChangeDetectorRef);
6192
+ appRouter = inject(Router);
6193
+ platformId = inject(PLATFORM_ID);
6194
+ mobileDropdownOpen = signal(false);
6195
+ isMenuOpen = signal(false);
6196
+ // Observable del estado de autenticación
6197
+ logged$;
6033
6198
  isAuthenticated$ = this.__authService.isAuthenticated();
6034
6199
  constructor() {
6035
6200
  super();
@@ -6047,6 +6212,22 @@ class HeaderEcComponent extends MenuEcComponent {
6047
6212
  this.channel = this.coreConstantsService.getChannel();
6048
6213
  this.onWindowScroll();
6049
6214
  this.detectRouteChange(); // Llamamos a la función que detecta el cambio de ruta
6215
+ // Usar el Observable del AuthService
6216
+ this.logged$ = this.__authService.loggedIn$;
6217
+ // Suscribirse al Observable y forzar detección de cambios cuando sea necesario
6218
+ this.logged$.subscribe(isLoggedIn => {
6219
+ this.changeDetector.detectChanges();
6220
+ });
6221
+ if (isPlatformBrowser(this.platformId)) {
6222
+ this.appRouter.events.subscribe(evt => {
6223
+ if (evt instanceof NavigationEnd) {
6224
+ // Forzar detección de cambios después de navegación
6225
+ setTimeout(() => {
6226
+ this.changeDetector.detectChanges();
6227
+ }, 100);
6228
+ }
6229
+ });
6230
+ }
6050
6231
  }
6051
6232
  ngAfterViewInit() {
6052
6233
  this.setupMobileMenu(); // Inicializamos el menú móvil
@@ -6183,6 +6364,33 @@ class HeaderEcComponent extends MenuEcComponent {
6183
6364
  });
6184
6365
  });
6185
6366
  }
6367
+ togglePanel(id) {
6368
+ // Solo ejecutar en el navegador para evitar problemas con SSR
6369
+ if (isPlatformBrowser(this.platformId)) {
6370
+ this.openPanels.update(panels => ({
6371
+ ...panels,
6372
+ [id]: !panels[id]
6373
+ }));
6374
+ }
6375
+ }
6376
+ openPanels = signal({
6377
+ collapseUno: false,
6378
+ collapseDos: true // Productos abierto por defecto
6379
+ });
6380
+ collapseAllMenus() {
6381
+ // Solo ejecutar en el navegador para evitar problemas con SSR
6382
+ if (isPlatformBrowser(this.platformId)) {
6383
+ this.openPanels.update(panels => {
6384
+ const newPanels = { ...panels };
6385
+ Object.keys(newPanels).forEach(key => {
6386
+ if (key !== 'collapseDos') {
6387
+ newPanels[key] = false;
6388
+ }
6389
+ });
6390
+ return newPanels;
6391
+ });
6392
+ }
6393
+ }
6186
6394
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeaderEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6187
6395
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeaderEcComponent, isStandalone: true, selector: "lib-header-ec", host: { listeners: { "window:scroll": "onWindowScroll()" } }, usesInheritance: true, ngImport: i0, template: "<p>header-ec works!</p>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
6188
6396
  }
@@ -6750,6 +6958,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
6750
6958
  class ProductEcComponent {
6751
6959
  injector = inject(Injector);
6752
6960
  routerService = inject(Router);
6961
+ _cartService = inject(CartService);
6962
+ _toastService = inject(ToastService);
6963
+ isAddingToCart = signal(false);
6964
+ quantity = signal(1);
6753
6965
  /**
6754
6966
  * Navega al detalle del producto y sube al inicio de la página
6755
6967
  * @param productId ID del producto
@@ -6822,12 +7034,145 @@ class ProductEcComponent {
6822
7034
  const discount = ((originalPrice - salePrice) / originalPrice) * 100;
6823
7035
  return Math.round(discount);
6824
7036
  }
7037
+ plus(stock, multipleQuantity) {
7038
+ const current = Number(this.quantity()); // <-- fuerza a número
7039
+ if (multipleQuantity && multipleQuantity > 0) {
7040
+ stock
7041
+ ? (current < stock
7042
+ ? this.quantity.set(current + multipleQuantity)
7043
+ : this._toastService.show('out-of-stock-actually'))
7044
+ : this.quantity.set(current + multipleQuantity);
7045
+ }
7046
+ else {
7047
+ stock
7048
+ ? (current < stock
7049
+ ? this.quantity.set(current + 1)
7050
+ : this._toastService.show('out-of-stock-actually'))
7051
+ : this.quantity.set(current + 1);
7052
+ }
7053
+ }
7054
+ less(multipleQuantity) {
7055
+ const current = Number(this.quantity()); // <-- fuerza a número
7056
+ if (multipleQuantity && multipleQuantity > 0) {
7057
+ current > multipleQuantity
7058
+ ? this.quantity.set(current - multipleQuantity)
7059
+ : null;
7060
+ }
7061
+ else {
7062
+ current > 1 ? this.quantity.set(current - 1) : null;
7063
+ }
7064
+ }
7065
+ addToCart() {
7066
+ if (this.isAddingToCart() || this.quantity() <= 0)
7067
+ return;
7068
+ // Verificar que tenemos el producto y sus variantes
7069
+ if (!this.product || !this.product.variants || this.product.variants.length === 0) {
7070
+ this._toastService.show('cant-buy');
7071
+ return;
7072
+ }
7073
+ // Verificar stock
7074
+ const firstVariant = this.product.variants[0];
7075
+ if (!firstVariant.stock || firstVariant.stock <= 0) {
7076
+ this._toastService.show('out-of-stock');
7077
+ return;
7078
+ }
7079
+ // Verificar que no se supere el stock disponible
7080
+ if (this.quantity() > firstVariant.stock) {
7081
+ this._toastService.show('out-of-stock-actually');
7082
+ return;
7083
+ }
7084
+ // NUEVAS VALIDACIONES: Verificar restricciones del producto
7085
+ if (!this.validateQuantity(this.quantity())) {
7086
+ return; // La validación ya mostró el toast correspondiente
7087
+ }
7088
+ this.isAddingToCart.set(true);
7089
+ try {
7090
+ // Agregar al carrito usando CartService directamente
7091
+ this._cartService.addToCart(this.product, this.quantity(), firstVariant.code);
7092
+ // Resetear cantidad a 1 después de agregar al carrito
7093
+ this.quantity.set(1);
7094
+ }
7095
+ catch (error) {
7096
+ this._toastService.show('cant-buy');
7097
+ }
7098
+ setTimeout(() => {
7099
+ this.isAddingToCart.set(false);
7100
+ }, 2000);
7101
+ }
7102
+ /**
7103
+ * Valida las restricciones de cantidad del producto
7104
+ */
7105
+ validateQuantity(quantity) {
7106
+ if (!this.product.variants || this.product.variants.length === 0) {
7107
+ this._toastService.show('cant-buy');
7108
+ return false;
7109
+ }
7110
+ const variant = this.product.variants[0];
7111
+ // Obtener cantidad actual en el carrito
7112
+ const actualQuantity = this._cartService.getCountFromItemInCart(variant.code);
7113
+ const totalQuantity = Number(actualQuantity) + Number(quantity);
7114
+ // Obtener restricciones del producto/variante (con valores por defecto)
7115
+ const maximumItemsQuantity = this.product.maximumItemsQuantity ||
7116
+ variant.maximumItemsQuantity ||
7117
+ 999999;
7118
+ const minimumItemsQuantity = this.product.minimumItemsQuantity ||
7119
+ variant.minimumItemsQuantity ||
7120
+ 1;
7121
+ const multipleQuantity = this.product.multipleQuantity ||
7122
+ variant.multipleQuantity ||
7123
+ 1;
7124
+ // Validar cantidad máxima
7125
+ if (totalQuantity > maximumItemsQuantity) {
7126
+ this._toastService.show('maximum-items-quantity', { quantity: maximumItemsQuantity });
7127
+ return false;
7128
+ }
7129
+ // Validar cantidad mínima
7130
+ if (totalQuantity < minimumItemsQuantity) {
7131
+ this._toastService.show('minimum-items-quantity', { quantity: minimumItemsQuantity });
7132
+ return false;
7133
+ }
7134
+ // Validar múltiplo de cantidad
7135
+ if (multipleQuantity && multipleQuantity > 1) {
7136
+ if (totalQuantity % multipleQuantity !== 0) {
7137
+ this._toastService.show('multiple-quantity-required', {
7138
+ multiple: multipleQuantity,
7139
+ current: totalQuantity
7140
+ });
7141
+ return false;
7142
+ }
7143
+ }
7144
+ // Validar que no exceda el stock total
7145
+ if (totalQuantity > variant.stock) {
7146
+ this._toastService.show('out-of-stock-actually');
7147
+ return false;
7148
+ }
7149
+ return true; // Todas las validaciones pasaron
7150
+ }
7151
+ checkStock(stock) {
7152
+ if (this.quantity() >= stock)
7153
+ this.quantity.set(stock);
7154
+ }
7155
+ onQuantityChange(event) {
7156
+ const target = event.target;
7157
+ const value = +target.value;
7158
+ // Validar que el valor sea mayor a 0
7159
+ if (value > 0) {
7160
+ this.quantity.set(value);
7161
+ // Validar contra el stock disponible
7162
+ const maxStock = this.product?.variants?.[0]?.stock || this.product?.stock || 0;
7163
+ this.checkStock(maxStock);
7164
+ }
7165
+ else {
7166
+ // Si el valor es 0 o negativo, resetear a 1
7167
+ this.quantity.set(1);
7168
+ }
7169
+ }
6825
7170
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ProductEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6826
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ProductEcComponent, isStandalone: true, selector: "app-product-ec", inputs: { product: "product", isProductBox: "isProductBox", isCollection: "isCollection" }, outputs: { loaded: "loaded" }, ngImport: i0, template: "<a [routerLink]=\"['/product', product.id]\" class=\"text-decoration-none producto\">\r\n <!-- Marca especial y descuento -->\r\n <!-- <div *ngIf=\"product.saleprice || (product.special_mark && product.special_mark !== null && product.special_mark !== undefined && product.special_mark.length >0)\"\r\n class=\"marcas\">\r\n <div *ecProductStock=\"product\" [ecProductMini]=\"product.special_mark\"></div>\r\n <ng-container *ngIf=\"shouldShowPrice\">\r\n <div *ecProductStock=\"product\" [ngClass]=\"{'tag-dsc float-right': product.saleprice}\"\r\n [ecProductOff]=\"product\">\r\n </div>\r\n </ng-container>\r\n </div> -->\r\n\r\n <!-- Imagen del producto -->\r\n <div class=\"foto\">\r\n @if(product.picturesdefault){\r\n @if (product.picturesdefault && product.picturesdefault.length > 1 ) {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n <img [src]=\"mediaUrl + product.picturesdefault[1]\" alt=\"Imagen secundaria\" class=\"w-100 pic02\" />\r\n } @else {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n }\r\n }\r\n </div>\r\n <!-- Precio -->\r\n\r\n\r\n <!-- Nombre del producto -->\r\n <h6 class=\"title\">{{ product.name | titlecase }}</h6>\r\n\r\n <div class=\"sku\" [innerHTML]=\"product.shortdetails\"></div>\r\n\r\n @if (shouldShowPrice) {\r\n <app-price-ec [price]=\"product.price\" [saleprice]=\"product.saleprice\" class=\"\" />\r\n }\r\n @if(!hidePrices){\r\n @if(!showPricesOnlyToLoggedUsers || isAuthenticated$){\r\n\r\n <div class=\"fixBottom\">\r\n\r\n <!-- Bot\u00F3n de acciones -->\r\n <!-- <ng-container *ecProductStock=\"product; else noStock\"> -->\r\n <!-- Cuando no tiene marca especial o es de tipo 'standard' -->\r\n @if (!product.special_mark || product.special_mark.length === 0 || product.special_mark[0]?.type ===\r\n 'standard') {\r\n <button class=\"btn standard\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n <ng-container *ngIf=\"isCollection; else normalText\">\r\n <span> {{(\"buy\" | translate) | uppercase}} </span>\r\n </ng-container>\r\n <ng-template #normalText>\r\n {{(\"buy\" | translate) | uppercase}}\r\n </ng-template>\r\n </button>\r\n }@else {\r\n <!-- Caso 1: Agotado o Disponible muy pronto -->\r\n @if (product.special_mark[0]?.type === 'out_of_stock' || product.special_mark[0]?.type === 'coming_soon') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0].name | uppercase }} </span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>}\r\n <!-- Caso 2: Contacto por WhatsApp -->\r\n @if (product.special_mark[0].type === 'whatsapp_contact') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\"\r\n (click)=\"openWhatsApp(product.special_mark[0]?.whatsappContact)\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n\r\n </button>\r\n }\r\n <!-- Caso 3: Solicitar m\u00E1s informaci\u00F3n -->\r\n @if (product.special_mark[0]?.type === 'more_info') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>\r\n }\r\n }\r\n </div>\r\n }}\r\n</a>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "component", type: PriceEcComponent, selector: "app-price-ec", inputs: ["price", "saleprice", "basePrice", "taxeAmount", "taxes", "priceSize", "showTaxLegendOnly", "disableTaxInfo", "customPriceTemplate", "customSalePriceTemplate", "customSimplePriceTemplate", "customSimpleSalePriceTemplate", "customTaxTemplate", "customOnlyTaxLabelTemplate"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
7171
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ProductEcComponent, isStandalone: true, selector: "app-product-ec", inputs: { product: "product", isProductBox: "isProductBox", isCollection: "isCollection" }, outputs: { loaded: "loaded" }, ngImport: i0, template: "<a [routerLink]=\"['/product', product.id]\" class=\"text-decoration-none producto\">\r\n <!-- Marca especial y descuento -->\r\n <!-- <div *ngIf=\"product.saleprice || (product.special_mark && product.special_mark !== null && product.special_mark !== undefined && product.special_mark.length >0)\"\r\n class=\"marcas\">\r\n <div *ecProductStock=\"product\" [ecProductMini]=\"product.special_mark\"></div>\r\n <ng-container *ngIf=\"shouldShowPrice\">\r\n <div *ecProductStock=\"product\" [ngClass]=\"{'tag-dsc float-right': product.saleprice}\"\r\n [ecProductOff]=\"product\">\r\n </div>\r\n </ng-container>\r\n </div> -->\r\n\r\n <!-- Imagen del producto -->\r\n <div class=\"foto\">\r\n @if(product.picturesdefault){\r\n @if (product.picturesdefault && product.picturesdefault.length > 1 ) {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n <img [src]=\"mediaUrl + product.picturesdefault[1]\" alt=\"Imagen secundaria\" class=\"w-100 pic02\" />\r\n } @else {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n }\r\n }\r\n </div>\r\n <!-- Precio -->\r\n\r\n\r\n <!-- Nombre del producto -->\r\n <h6 class=\"title\">{{ product.name | titlecase }}</h6>\r\n\r\n <div class=\"sku\" [innerHTML]=\"product.shortdetails\"></div>\r\n\r\n @if (shouldShowPrice) {\r\n <app-price-ec [price]=\"product.price\" [saleprice]=\"product.saleprice\" class=\"\" />\r\n }\r\n @if(!hidePrices){\r\n @if(!showPricesOnlyToLoggedUsers || isAuthenticated$){\r\n\r\n <div class=\"fixBottom\">\r\n\r\n <!-- Bot\u00F3n de acciones -->\r\n <!-- <ng-container *ecProductStock=\"product; else noStock\"> -->\r\n <!-- Cuando no tiene marca especial o es de tipo 'standard' -->\r\n @if (!product.special_mark || product.special_mark.length === 0 || product.special_mark[0]?.type ===\r\n 'standard') {\r\n <button class=\"btn standard\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n <ng-container *ngIf=\"isCollection; else normalText\">\r\n <span> {{(\"buy\" | translate) | uppercase}} </span>\r\n </ng-container>\r\n <ng-template #normalText>\r\n {{(\"buy\" | translate) | uppercase}}\r\n </ng-template>\r\n </button>\r\n }@else {\r\n <!-- Caso 1: Agotado o Disponible muy pronto -->\r\n @if (product.special_mark[0]?.type === 'out_of_stock' || product.special_mark[0]?.type === 'coming_soon') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0].name | uppercase }} </span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>}\r\n <!-- Caso 2: Contacto por WhatsApp -->\r\n @if (product.special_mark[0].type === 'whatsapp_contact') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\"\r\n (click)=\"openWhatsApp(product.special_mark[0]?.whatsappContact)\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n\r\n </button>\r\n }\r\n <!-- Caso 3: Solicitar m\u00E1s informaci\u00F3n -->\r\n @if (product.special_mark[0]?.type === 'more_info') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>\r\n }\r\n }\r\n </div>\r\n }}\r\n</a>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "component", type: PriceEcComponent, selector: "app-price-ec", inputs: ["price", "saleprice", "basePrice", "taxeAmount", "taxes", "priceSize", "showTaxLegendOnly", "disableTaxInfo", "customPriceTemplate", "customSalePriceTemplate", "customSimplePriceTemplate", "customSimpleSalePriceTemplate", "customTaxTemplate", "customOnlyTaxLabelTemplate"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: FormsModule }] });
6827
7172
  }
6828
7173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ProductEcComponent, decorators: [{
6829
7174
  type: Component,
6830
- args: [{ selector: 'app-product-ec', standalone: true, imports: [CommonModule, PriceEcComponent, RouterLink, TranslateModule], template: "<a [routerLink]=\"['/product', product.id]\" class=\"text-decoration-none producto\">\r\n <!-- Marca especial y descuento -->\r\n <!-- <div *ngIf=\"product.saleprice || (product.special_mark && product.special_mark !== null && product.special_mark !== undefined && product.special_mark.length >0)\"\r\n class=\"marcas\">\r\n <div *ecProductStock=\"product\" [ecProductMini]=\"product.special_mark\"></div>\r\n <ng-container *ngIf=\"shouldShowPrice\">\r\n <div *ecProductStock=\"product\" [ngClass]=\"{'tag-dsc float-right': product.saleprice}\"\r\n [ecProductOff]=\"product\">\r\n </div>\r\n </ng-container>\r\n </div> -->\r\n\r\n <!-- Imagen del producto -->\r\n <div class=\"foto\">\r\n @if(product.picturesdefault){\r\n @if (product.picturesdefault && product.picturesdefault.length > 1 ) {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n <img [src]=\"mediaUrl + product.picturesdefault[1]\" alt=\"Imagen secundaria\" class=\"w-100 pic02\" />\r\n } @else {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n }\r\n }\r\n </div>\r\n <!-- Precio -->\r\n\r\n\r\n <!-- Nombre del producto -->\r\n <h6 class=\"title\">{{ product.name | titlecase }}</h6>\r\n\r\n <div class=\"sku\" [innerHTML]=\"product.shortdetails\"></div>\r\n\r\n @if (shouldShowPrice) {\r\n <app-price-ec [price]=\"product.price\" [saleprice]=\"product.saleprice\" class=\"\" />\r\n }\r\n @if(!hidePrices){\r\n @if(!showPricesOnlyToLoggedUsers || isAuthenticated$){\r\n\r\n <div class=\"fixBottom\">\r\n\r\n <!-- Bot\u00F3n de acciones -->\r\n <!-- <ng-container *ecProductStock=\"product; else noStock\"> -->\r\n <!-- Cuando no tiene marca especial o es de tipo 'standard' -->\r\n @if (!product.special_mark || product.special_mark.length === 0 || product.special_mark[0]?.type ===\r\n 'standard') {\r\n <button class=\"btn standard\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n <ng-container *ngIf=\"isCollection; else normalText\">\r\n <span> {{(\"buy\" | translate) | uppercase}} </span>\r\n </ng-container>\r\n <ng-template #normalText>\r\n {{(\"buy\" | translate) | uppercase}}\r\n </ng-template>\r\n </button>\r\n }@else {\r\n <!-- Caso 1: Agotado o Disponible muy pronto -->\r\n @if (product.special_mark[0]?.type === 'out_of_stock' || product.special_mark[0]?.type === 'coming_soon') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0].name | uppercase }} </span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>}\r\n <!-- Caso 2: Contacto por WhatsApp -->\r\n @if (product.special_mark[0].type === 'whatsapp_contact') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\"\r\n (click)=\"openWhatsApp(product.special_mark[0]?.whatsappContact)\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n\r\n </button>\r\n }\r\n <!-- Caso 3: Solicitar m\u00E1s informaci\u00F3n -->\r\n @if (product.special_mark[0]?.type === 'more_info') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>\r\n }\r\n }\r\n </div>\r\n }}\r\n</a>" }]
7175
+ args: [{ selector: 'app-product-ec', standalone: true, imports: [CommonModule, PriceEcComponent, RouterLink, TranslateModule, FormsModule], template: "<a [routerLink]=\"['/product', product.id]\" class=\"text-decoration-none producto\">\r\n <!-- Marca especial y descuento -->\r\n <!-- <div *ngIf=\"product.saleprice || (product.special_mark && product.special_mark !== null && product.special_mark !== undefined && product.special_mark.length >0)\"\r\n class=\"marcas\">\r\n <div *ecProductStock=\"product\" [ecProductMini]=\"product.special_mark\"></div>\r\n <ng-container *ngIf=\"shouldShowPrice\">\r\n <div *ecProductStock=\"product\" [ngClass]=\"{'tag-dsc float-right': product.saleprice}\"\r\n [ecProductOff]=\"product\">\r\n </div>\r\n </ng-container>\r\n </div> -->\r\n\r\n <!-- Imagen del producto -->\r\n <div class=\"foto\">\r\n @if(product.picturesdefault){\r\n @if (product.picturesdefault && product.picturesdefault.length > 1 ) {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n <img [src]=\"mediaUrl + product.picturesdefault[1]\" alt=\"Imagen secundaria\" class=\"w-100 pic02\" />\r\n } @else {\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"Imagen principal\" class=\"w-100 pic01\" />\r\n }\r\n }\r\n </div>\r\n <!-- Precio -->\r\n\r\n\r\n <!-- Nombre del producto -->\r\n <h6 class=\"title\">{{ product.name | titlecase }}</h6>\r\n\r\n <div class=\"sku\" [innerHTML]=\"product.shortdetails\"></div>\r\n\r\n @if (shouldShowPrice) {\r\n <app-price-ec [price]=\"product.price\" [saleprice]=\"product.saleprice\" class=\"\" />\r\n }\r\n @if(!hidePrices){\r\n @if(!showPricesOnlyToLoggedUsers || isAuthenticated$){\r\n\r\n <div class=\"fixBottom\">\r\n\r\n <!-- Bot\u00F3n de acciones -->\r\n <!-- <ng-container *ecProductStock=\"product; else noStock\"> -->\r\n <!-- Cuando no tiene marca especial o es de tipo 'standard' -->\r\n @if (!product.special_mark || product.special_mark.length === 0 || product.special_mark[0]?.type ===\r\n 'standard') {\r\n <button class=\"btn standard\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n <ng-container *ngIf=\"isCollection; else normalText\">\r\n <span> {{(\"buy\" | translate) | uppercase}} </span>\r\n </ng-container>\r\n <ng-template #normalText>\r\n {{(\"buy\" | translate) | uppercase}}\r\n </ng-template>\r\n </button>\r\n }@else {\r\n <!-- Caso 1: Agotado o Disponible muy pronto -->\r\n @if (product.special_mark[0]?.type === 'out_of_stock' || product.special_mark[0]?.type === 'coming_soon') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0].name | uppercase }} </span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>}\r\n <!-- Caso 2: Contacto por WhatsApp -->\r\n @if (product.special_mark[0].type === 'whatsapp_contact') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\"\r\n (click)=\"openWhatsApp(product.special_mark[0]?.whatsappContact)\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n\r\n </button>\r\n }\r\n <!-- Caso 3: Solicitar m\u00E1s informaci\u00F3n -->\r\n @if (product.special_mark[0]?.type === 'more_info') {\r\n <button class=\"btn\" [ngClass]=\"isCollection ? 'px-2 w-100 d-sm-block' : 'py-2 px-4'\">\r\n @if(isCollection){\r\n <span>{{ product.special_mark[0]?.name | uppercase }}</span>\r\n }@else {\r\n {{ product.special_mark[0]?.name | uppercase }}\r\n }\r\n </button>\r\n }\r\n }\r\n </div>\r\n }}\r\n</a>" }]
6831
7176
  }], ctorParameters: () => [], propDecorators: { product: [{
6832
7177
  type: Input,
6833
7178
  args: [{
@@ -7095,8 +7440,14 @@ class BlockNewsletterEcComponent {
7095
7440
  this._toastService.show(this.success_message || success_message);
7096
7441
  this.loading = false;
7097
7442
  }, (err) => {
7098
- this._toastService.error('inquiry-error');
7099
- this.loading = false;
7443
+ if (err.status === 400) {
7444
+ this._toastService.error('newsletter_email_already_registered');
7445
+ this.loading = false;
7446
+ }
7447
+ else {
7448
+ this._toastService.error('inquiry-error');
7449
+ this.loading = false;
7450
+ }
7100
7451
  });
7101
7452
  }
7102
7453
  else {
@@ -7401,6 +7752,212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
7401
7752
  args: ['mainCanvas', { static: true }]
7402
7753
  }] } });
7403
7754
 
7755
+ class ComponentHelper {
7756
+ constructor() {
7757
+ this.ecOnConstruct();
7758
+ }
7759
+ ecOnInit = (params = {}) => {
7760
+ };
7761
+ ecOnConstruct = (params = {}) => {
7762
+ };
7763
+ hasParams = (params, searched) => {
7764
+ if (!params || !searched)
7765
+ return false;
7766
+ const q = searched.trim().toLowerCase();
7767
+ return params.some(p => {
7768
+ const code = p?.['code']?.toString().toLowerCase() ?? '';
7769
+ // Primero chequeo exacto, y si no, parcial
7770
+ return code === q || code.includes(q);
7771
+ });
7772
+ };
7773
+ navigateOnRouter(router, url) {
7774
+ router.navigateByUrl(`/${url}`);
7775
+ }
7776
+ }
7777
+
7778
+ /**
7779
+ * Catch genérico para redirecciones de pagos.
7780
+ * - Normaliza el estado recibido por params/query.
7781
+ * - Informa el resultado al opener (postMessage), BroadcastChannel y localStorage.
7782
+ * - Intenta cerrarse; si el cierre falla, redirige según el estado.
7783
+ */
7784
+ class RedsysCatchEcComponent extends ComponentHelper {
7785
+ activedRoute;
7786
+ router;
7787
+ checkoutService;
7788
+ renderer;
7789
+ elementRef;
7790
+ document;
7791
+ platformId;
7792
+ message = '';
7793
+ subscription = null;
7794
+ sid = '';
7795
+ bc;
7796
+ constructor(activedRoute, router, checkoutService, renderer, elementRef, document, platformId) {
7797
+ super();
7798
+ this.activedRoute = activedRoute;
7799
+ this.router = router;
7800
+ this.checkoutService = checkoutService;
7801
+ this.renderer = renderer;
7802
+ this.elementRef = elementRef;
7803
+ this.document = document;
7804
+ this.platformId = platformId;
7805
+ this.hideHeaderFooter();
7806
+ this.ecOnConstruct();
7807
+ }
7808
+ ngOnInit() {
7809
+ if (isPlatformBrowser(this.platformId) && 'BroadcastChannel' in window) {
7810
+ this.bc = new BroadcastChannel('mp_payment');
7811
+ }
7812
+ this.subscription = combineLatest([this.activedRoute.params, this.activedRoute.queryParams])
7813
+ .subscribe(([routeParams, q]) => {
7814
+ let stateStr = routeParams['state'];
7815
+ if (stateStr === 'statuspayment')
7816
+ stateStr = q['status'];
7817
+ const statusParam = (stateStr || q['status'] || q['state'] || '').toString();
7818
+ const state = this.normalizeState(statusParam);
7819
+ this.sid = (q['sid'] || (isPlatformBrowser(this.platformId) ? localStorage.getItem('mp:sid') : '') || '').toString();
7820
+ this.storeTotalAmount(q);
7821
+ this.setStateInLocal('Su pago fue procesado con éxito.', state);
7822
+ this.signalState(state);
7823
+ this.tryCloseSelf(() => {
7824
+ const target = (state === 'success' || state === 'pending')
7825
+ ? ['/checkout/order_success']
7826
+ : ['/checkout'];
7827
+ setTimeout(() => this.router.navigate(target), 4500);
7828
+ });
7829
+ });
7830
+ this.ecOnInit();
7831
+ }
7832
+ ngOnDestroy() {
7833
+ this.subscription?.unsubscribe();
7834
+ this.bc?.close();
7835
+ this.showHeaderFooter();
7836
+ }
7837
+ /** Guarda total_amount si viene desde el PSP. */
7838
+ storeTotalAmount(queryParams) {
7839
+ const totalAmount = queryParams['total_amount'];
7840
+ if (totalAmount && isPlatformBrowser(this.platformId)) {
7841
+ localStorage.setItem('total_amount', totalAmount);
7842
+ }
7843
+ }
7844
+ /** Setea mensaje y estado en storages. */
7845
+ setStateInLocal = (mensaje, state) => {
7846
+ this.message = mensaje;
7847
+ if (!isPlatformBrowser(this.platformId))
7848
+ return;
7849
+ try {
7850
+ localStorage.setItem('state', state);
7851
+ }
7852
+ catch { }
7853
+ try {
7854
+ sessionStorage.setItem('modalnews', 'false');
7855
+ }
7856
+ catch { }
7857
+ };
7858
+ /** Normaliza estados heterogéneos de distintos gateways. */
7859
+ normalizeState(raw) {
7860
+ const v = (raw || '').toLowerCase();
7861
+ if (v === '200' || v === 'ok' || v === 'success')
7862
+ return 'success';
7863
+ if (v === 'pending')
7864
+ return 'pending';
7865
+ if (v === 'cancel')
7866
+ return 'cancel';
7867
+ return 'failure'; // failure, 0, error, rejected, chargeback, desconocidos
7868
+ }
7869
+ /**
7870
+ * Informa el resultado: postMessage al opener, BroadcastChannel y localStorage
7871
+ * (este último permite polling en la pestaña madre).
7872
+ */
7873
+ signalState(state) {
7874
+ if (!isPlatformBrowser(this.platformId))
7875
+ return;
7876
+ const sid = this.sid || localStorage.getItem('mp:sid') || '';
7877
+ const payload = { type: 'mp:state', sid, state };
7878
+ try {
7879
+ window.opener && window.opener.postMessage(payload, '*');
7880
+ }
7881
+ catch { }
7882
+ try {
7883
+ this.bc?.postMessage(payload);
7884
+ }
7885
+ catch { }
7886
+ try {
7887
+ localStorage.setItem(`mp:state:${sid}`, state);
7888
+ }
7889
+ catch { }
7890
+ try {
7891
+ localStorage.setItem('state', state);
7892
+ }
7893
+ catch { }
7894
+ }
7895
+ /** Intenta cerrar la pestaña actual; si falla, ejecuta el callback de fallback. */
7896
+ tryCloseSelf(onFail) {
7897
+ if (!isPlatformBrowser(this.platformId))
7898
+ return onFail();
7899
+ let attempted = false;
7900
+ try {
7901
+ window.close();
7902
+ attempted = true;
7903
+ }
7904
+ catch { }
7905
+ if (!attempted)
7906
+ onFail();
7907
+ }
7908
+ /** Oculta header/footer para esta pantalla mínima. */
7909
+ hideHeaderFooter() {
7910
+ if (!isPlatformBrowser(this.platformId))
7911
+ return;
7912
+ const header = this.document.querySelector('header');
7913
+ const footer = this.document.querySelector('footer');
7914
+ if (header)
7915
+ this.renderer.setStyle(header, 'display', 'none');
7916
+ if (footer)
7917
+ this.renderer.setStyle(footer, 'display', 'none');
7918
+ }
7919
+ /** Restaura header/footer al salir. */
7920
+ showHeaderFooter() {
7921
+ if (!isPlatformBrowser(this.platformId))
7922
+ return;
7923
+ const header = this.document.querySelector('header');
7924
+ const footer = this.document.querySelector('footer');
7925
+ if (header)
7926
+ this.renderer.removeStyle(header, 'display');
7927
+ if (footer)
7928
+ this.renderer.removeStyle(footer, 'display');
7929
+ }
7930
+ setStateInSession(mensaje, state) {
7931
+ this.message = mensaje;
7932
+ if (!isPlatformBrowser(this.platformId))
7933
+ return;
7934
+ try {
7935
+ sessionStorage.setItem('state', state);
7936
+ }
7937
+ catch { }
7938
+ try {
7939
+ localStorage.setItem('state', state);
7940
+ }
7941
+ catch { }
7942
+ try {
7943
+ sessionStorage.setItem('modalnews', 'false');
7944
+ }
7945
+ catch { }
7946
+ }
7947
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: RedsysCatchEcComponent, deps: [{ token: i2.ActivatedRoute }, { token: i2.Router }, { token: CheckoutService }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component });
7948
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: RedsysCatchEcComponent, isStandalone: true, selector: "app-redsys-catch-ec", usesInheritance: true, ngImport: i0, template: "<div id=\"container\">\r\n <div class=\"row\">\r\n <div class=\"col align-self-center\">\r\n <h4 class=\"titpage center-block text-center font-nexa font-lg my-3\">{{ message | uppercase }}</h4>\r\n </div>\r\n </div>\r\n <div class=\"row\">\r\n <div class=\"col align-self-center\">\r\n <h5 class=\"center-block text-center font-nexa my-3\">Redirigiendo en segundos...</h5>\r\n <br>\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".loader{border:16px solid #f3f3f3;border-top:16px solid #dc3545;border-radius:50%;width:50px;height:50px;animation:spin 2s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.flex-container{display:flex;justify-content:center;align-items:center;height:80vh;width:100%}.flex-container>div{width:90%;height:100px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }, { kind: "component", type: LoadingFullEcComponent, selector: "app-loading-full-ec" }] });
7949
+ }
7950
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: RedsysCatchEcComponent, decorators: [{
7951
+ type: Component,
7952
+ args: [{ selector: 'app-redsys-catch-ec', standalone: true, imports: [CommonModule, LoadingFullEcComponent], template: "<div id=\"container\">\r\n <div class=\"row\">\r\n <div class=\"col align-self-center\">\r\n <h4 class=\"titpage center-block text-center font-nexa font-lg my-3\">{{ message | uppercase }}</h4>\r\n </div>\r\n </div>\r\n <div class=\"row\">\r\n <div class=\"col align-self-center\">\r\n <h5 class=\"center-block text-center font-nexa my-3\">Redirigiendo en segundos...</h5>\r\n <br>\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".loader{border:16px solid #f3f3f3;border-top:16px solid #dc3545;border-radius:50%;width:50px;height:50px;animation:spin 2s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.flex-container{display:flex;justify-content:center;align-items:center;height:80vh;width:100%}.flex-container>div{width:90%;height:100px}\n"] }]
7953
+ }], ctorParameters: () => [{ type: i2.ActivatedRoute }, { type: i2.Router }, { type: CheckoutService }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: Document, decorators: [{
7954
+ type: Inject,
7955
+ args: [DOCUMENT]
7956
+ }] }, { type: undefined, decorators: [{
7957
+ type: Inject,
7958
+ args: [PLATFORM_ID]
7959
+ }] }] });
7960
+
7404
7961
  // export * from './rating-ec/rating-ec.component';
7405
7962
 
7406
7963
  class BlockFormContactEcComponent extends BlockEcComponent {
@@ -7676,6 +8233,7 @@ class LoginFormEcComponent {
7676
8233
  _formBuilder = inject(FormBuilder);
7677
8234
  _toastService = inject(ToastService);
7678
8235
  _router = inject(Router);
8236
+ showPassword = false;
7679
8237
  /**
7680
8238
  * Parametro para indicar si tras loguear
7681
8239
  * debe redireccionar o no.
@@ -7764,6 +8322,9 @@ class LoginFormEcComponent {
7764
8322
  ? resolverFunction()
7765
8323
  : this._router.navigateByUrl(this.redirectTo);
7766
8324
  }
8325
+ togglePassword() {
8326
+ this.showPassword = !this.showPassword;
8327
+ }
7767
8328
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginFormEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7768
8329
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: LoginFormEcComponent, isStandalone: true, selector: "app-login-form-ec", inputs: { redirect: "redirect", redirectTo: "redirectTo", inCart: "inCart" }, outputs: { ready: "ready" }, ngImport: i0, template: "<div class=\"d-flex flex-column position-relative\">\r\n <h1 class=\"right-line ff-ubuntu-light mb-4\"><span>Ingresar</span></h1>\r\n <p class=\"ff-ubuntu-light font-sm pr-4 mb-4\">\r\n Si ya est\u00E1s registrado. Ingresa en tu cuenta con tu email y la\r\n contrase\u00F1a adecuada.\r\n </p>\r\n <div class=\"w-md-50 w-100 text-center\">\r\n <form [formGroup]=\"loginForm()\" (submit)=\"login($event)\">\r\n <input class=\"form-control mb-4 radius-0\" type=\"email\" formControlName=\"username\"\r\n placeholder=\"Correo Electr\u00F3nico\">\r\n <input class=\"form-control mb-4 radius-0\" type=\"password\" formControlName=\"password\"\r\n placeholder=\"Contrase\u00F1a\">\r\n\r\n <div class=\"row d-flex flex-column\">\r\n <div class=\"col-12 mb-4\">\r\n <button type=\"submit\"\r\n class=\"bg-gray border-0 px-4 py-2 color-white ff-ubuntu-light\">INGRESAR</button>\r\n </div>\r\n <div class=\"col-12 d-flex justify-content-center align-items-center\">\r\n <a [routerLink]=\"'/auth/forgot-password'\" class=\"font-md ff-ubuntu-light\">\r\n \u00BFOlvid\u00F3 su contrase\u00F1a?\r\n </a>\r\n </div>\r\n </div>\r\n </form>\r\n </div>\r\n @if(loading){\r\n <app-loading-section-ec></app-loading-section-ec>\r\n }\r\n</div>", styles: [""], dependencies: [{ kind: "component", type: LoadingSectionEcComponent, selector: "app-loading-section-ec" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
7769
8330
  }
@@ -7863,6 +8424,7 @@ class RegisterFormEcComponent {
7863
8424
  _analyticsService = inject(AnalyticsService);
7864
8425
  _formBuilder = inject(FormBuilder);
7865
8426
  channelConfigService = inject(ChannelService);
8427
+ showPassword = false;
7866
8428
  /**
7867
8429
  * Indica si debe redireccionar o se queda en la misma pantalla
7868
8430
  */
@@ -7978,6 +8540,9 @@ class RegisterFormEcComponent {
7978
8540
  this.register_loading = false;
7979
8541
  }
7980
8542
  }
8543
+ togglePassword() {
8544
+ this.showPassword = !this.showPassword;
8545
+ }
7981
8546
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: RegisterFormEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7982
8547
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: RegisterFormEcComponent, isStandalone: true, selector: "app-register-form-ec", inputs: { redirect: "redirect" }, outputs: { ready: "ready" }, ngImport: i0, template: "<div class=\"w-100 pl-md-5 position-relative\" id=\"register\">\r\n <div class=\"py-2\">\r\n <h5>CREAR CUENTA</h5>\r\n </div>\r\n <form id=\"registro\" [formGroup]=\"registerForm\" (submit)=\"register($event)\">\r\n <div class=\"form-group\">\r\n <label for=\"\" class=\"form-label\">NOMBRE</label>\r\n <input formControlName=\"firstName\" class=\"form-control rounded-0\" type=\"text\" placeholder=\"Nombre\">\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"\" class=\"form-label\">APELLIDO</label>\r\n <input formControlName=\"lastName\" class=\"form-control rounded-0\" type=\"text\" placeholder=\"Apellido\">\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"\" class=\"\">CORREO ELECTRONICO</label>\r\n <input formControlName=\"email\" email required class=\"form-control rounded-0\" type=\"email\"\r\n placeholder=\"Correo electr\u00F3nico\">\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"\" class=\"form-label\">CONTRASE\u00D1A</label>\r\n <input formControlName=\"plainPassword\" required class=\"form-control rounded-0\" type=\"password\"\r\n placeholder=\"Contrase\u00F1a\">\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"\" class=\"form-label\">REPETIR CONTRASE\u00D1A</label>\r\n <input formControlName=\"plainPassword2\" required class=\"form-control rounded-0\" type=\"password\"\r\n placeholder=\"Repetir contrase\u00F1a\">\r\n </div>\r\n\r\n <div class=\"custom-control d-flex flex-row form-check custom-checkbox mr-sm-2 mt-4 mb-2\">\r\n <input type=\"checkbox\" formControlName=\"terms\" required class=\"custom-control-input form-check-input\" name=\"Color2\"\r\n id=\"Color2\">\r\n <label class=\"custom-control-label ff-ubuntu-light font-sm form-check-label\" for=\"Color2\"> He\r\n le\u00EDdo y acepto las pol\u00EDticas de privacidad y los t\u00E9rminos y\r\n condiciones</label>\r\n </div>\r\n\r\n <div class=\"custom-control d-flex flex-row form-check custom-checkbox mr-sm-2 mb-4\">\r\n <input type=\"checkbox\" formControlName=\"newsletter\" class=\"custom-control-input form-check-input\" name=\"Color3\" id=\"Color3\">\r\n <label class=\"custom-control-label form-check-label ff-ubuntu-light font-sm\" for=\"Color3\">\r\n Suscripci\u00F3n al Newsletter</label>\r\n </div>\r\n\r\n <div class=\"row\">\r\n <div class=\"col-12\">\r\n <button [disabled]=\"registerForm.invalid\" type=\"submit\"\r\n class=\"btn btn-primary px-5 py-2 h-fit\">CREAR</button>\r\n </div>\r\n </div>\r\n </form>\r\n @if(loading){\r\n <app-loading-section-ec />\r\n }\r\n \r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.CheckboxRequiredValidator, selector: "input[type=checkbox][required][formControlName],input[type=checkbox][required][formControl],input[type=checkbox][required][ngModel]" }, { kind: "directive", type: i1$3.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: LoadingSectionEcComponent, selector: "app-loading-section-ec" }] });
7983
8548
  }
@@ -8132,20 +8697,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
8132
8697
  type: Output
8133
8698
  }] } });
8134
8699
 
8135
- class ComponentHelper {
8136
- constructor() {
8137
- this.ecOnConstruct();
8138
- }
8139
- ecOnInit = (params = {}) => {
8140
- };
8141
- ecOnConstruct = (params = {}) => {
8142
- };
8143
- hasParams = (params, searched) => params && params != null && params.find(param => param['code']?.toLowerCase().includes(searched.toLowerCase())) !== undefined;
8144
- navigateOnRouter(router, url) {
8145
- router.navigateByUrl(`/${url}`);
8146
- }
8147
- }
8148
-
8149
8700
  class PasswordResetEcComponent extends ComponentHelper {
8150
8701
  authService;
8151
8702
  toastr;
@@ -8355,6 +8906,14 @@ class FiltersEcComponent {
8355
8906
  });
8356
8907
  }) ?? false;
8357
8908
  }
8909
+ /**
8910
+ * Verifica si una categoría tiene la propiedad isVisible y está marcada como visible
8911
+ * @param category - La categoría a verificar
8912
+ * @returns true si la categoría es visible, false en caso contrario
8913
+ */
8914
+ hasVisibleProperty(category) {
8915
+ return category.isVisible === true;
8916
+ }
8358
8917
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FiltersEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8359
8918
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FiltersEcComponent, isStandalone: true, selector: "lib-filters-ec", inputs: { setSelect: "setSelect" }, ngImport: i0, template: "<p>filters-ec works!</p>\r\n", styles: [""] });
8360
8919
  }
@@ -8435,6 +8994,7 @@ class ProductDetailEcComponent {
8435
8994
  routeSubscription;
8436
8995
  route = inject(ActivatedRoute);
8437
8996
  currentProductId;
8997
+ _router = inject(Router);
8438
8998
  ngOnDestroy() {
8439
8999
  this.routeSubscription?.unsubscribe();
8440
9000
  }
@@ -8496,11 +9056,12 @@ class ProductDetailEcComponent {
8496
9056
  }
8497
9057
  updateMetaTags(product) {
8498
9058
  const descripcionLimpia = he.decode(product.description || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
8499
- const currentUrl = this._consts.url() + this._location.path(true); // `true` incluye el query string
9059
+ const currentUrl = this._consts.getFrontendUrl() + this._router.url; // URL absoluta del producto
9060
+ const imageUrl = this._consts.getAbsoluteImageUrl(Array.isArray(product.picturesdefault) ? product.picturesdefault[0] : product.picturesdefault);
8500
9061
  this._meta.updateTag({ property: 'og:title', content: product.name || '' });
8501
9062
  this._meta.updateTag({ property: 'og:description', content: descripcionLimpia || '' });
8502
- this._meta.updateTag({ property: 'og:image', content: this._consts.mediaImageUrl(Array.isArray(product.picturesdefault) ? product.picturesdefault[0] : product.picturesdefault) || '' });
8503
- this._meta.updateTag({ property: 'og:url', content: currentUrl });
9063
+ this._meta.updateTag({ property: 'og:image', content: imageUrl || '' });
9064
+ // this._meta.updateTag({ property: 'og:url', content: currentUrl });
8504
9065
  this._meta.updateTag({ property: 'og:type', content: 'product' });
8505
9066
  }
8506
9067
  decodeHtml(html) {
@@ -8736,6 +9297,7 @@ class CartItemEcComponent {
8736
9297
  _cartService = inject(CartService);
8737
9298
  _toastService = inject(ToastService);
8738
9299
  _constants = inject(CoreConstantsService);
9300
+ parametersService = inject(ParametersService);
8739
9301
  mediaUrl = this._constants.mediaUrl();
8740
9302
  quantity = 0;
8741
9303
  variantsToShow = ['TALLA', 'COLOR'];
@@ -8746,28 +9308,56 @@ class CartItemEcComponent {
8746
9308
  // console.log(this.item, this.mediaUrl);
8747
9309
  }
8748
9310
  updateQuantity(stock) {
9311
+ const originalQuantity = this.item.quantity; // Guardar cantidad original
8749
9312
  if (this.quantity > 0 && this.quantity <= stock) {
8750
- this._cartService.updateItemQuantity(this.item, this.quantity);
9313
+ const success = this._cartService.updateItemQuantity(this.item, this.quantity);
9314
+ // Si la validación falló, restaurar la cantidad original
9315
+ if (!success) {
9316
+ this.quantity = originalQuantity;
9317
+ }
8751
9318
  }
8752
9319
  else {
8753
- this.quantity = this.item.quantity;
9320
+ // Restaurar cantidad original si está fuera de rango
9321
+ this.quantity = originalQuantity;
8754
9322
  this._toastService.show('out-of-stock-actually');
8755
9323
  }
8756
9324
  }
8757
- less(stock, value = 1) {
9325
+ plus(stock, value = 1) {
8758
9326
  if (this.isQuantityUpdating)
8759
9327
  return;
8760
9328
  this.isQuantityUpdating = true;
8761
- let quantity = this.item.quantity - value;
8762
- quantity > 0 && quantity <= stock ? this._cartService.updateItemQuantity(this.item, quantity) : this._toastService.show('out-of-stock-actually');
9329
+ let newQuantity = Number(this.quantity) + value; // Forzar a número
9330
+ // Verificar stock ANTES de llamar al servicio
9331
+ if (newQuantity > stock) {
9332
+ this._toastService.show('out-of-stock-actually');
9333
+ setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
9334
+ return;
9335
+ }
9336
+ const success = this._cartService.updateItemQuantity(this.item, newQuantity);
9337
+ // Solo actualizar el input si la operación fue exitosa
9338
+ if (success) {
9339
+ this.quantity = newQuantity;
9340
+ }
9341
+ // Si success es false, NO mostrar mensaje aquí porque ya lo mostró validateQuantity()
8763
9342
  setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
8764
9343
  }
8765
- plus(stock, value = 1) {
9344
+ less(stock, value = 1) {
8766
9345
  if (this.isQuantityUpdating)
8767
9346
  return;
8768
9347
  this.isQuantityUpdating = true;
8769
- let quantity = this.item.quantity + value;
8770
- quantity > 0 && quantity <= stock ? this._cartService.updateItemQuantity(this.item, quantity) : this._toastService.show('out-of-stock-actually');
9348
+ let newQuantity = Number(this.quantity) - value; // Forzar a número
9349
+ // Verificar cantidad mínima ANTES de llamar al servicio
9350
+ if (newQuantity <= 0) {
9351
+ // No permitir cantidades menores o iguales a 0
9352
+ setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
9353
+ return;
9354
+ }
9355
+ const success = this._cartService.updateItemQuantity(this.item, newQuantity);
9356
+ // Solo actualizar el input si la operación fue exitosa
9357
+ if (success) {
9358
+ this.quantity = newQuantity;
9359
+ }
9360
+ // Si success es false, NO mostrar mensaje aquí porque ya lo mostró validateQuantity()
8771
9361
  setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
8772
9362
  }
8773
9363
  deleteCartItem() {
@@ -8822,6 +9412,9 @@ class CartItemEcComponent {
8822
9412
  }
8823
9413
  return false; // Solo se ejecuta si no se cumple la condición
8824
9414
  }
9415
+ // PARAMETROS
9416
+ parameters$ = this.parametersService.getParameters();
9417
+ hasParams = this.parametersService.hasParams;
8825
9418
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CartItemEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8826
9419
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: CartItemEcComponent, isStandalone: true, selector: "app-cart-item-ec", inputs: { item: "item", inSidebar: "inSidebar" }, ngImport: i0, template: "@if(!inSidebar){\r\n<p>cart-item-ec works!</p>\r\n}@else{\r\n\r\n<div class=\"row\">\r\n <div class=\"col-3\">\r\n @let product= item.product;\r\n @if(item.variant_id && product.variants.length>0){\r\n <img [src]=\"mediaUrl + product.variants[0].images[0]\" alt=\"\" class=\"img-fluid\">\r\n }@else{\r\n <img [src]=\"mediaUrl + product.picturesdefault[0]\" alt=\"\" class=\"img-fluid\">\r\n }\r\n </div>\r\n <div class=\"col-7\">\r\n <div class=\"info d-flex flex-column align-items-start\">\r\n @if (item.product.special_mark?.length > 0 || item.product.saleprice) {\r\n <div class=\"marcas\">\r\n <img [src]=\"mediaUrl + (item.product.special_mark?.[0]?.images[0] || '')\" alt=\"\">\r\n\r\n @if (item.product.saleprice) {\r\n <div class=\"tag-dsc\">\r\n {{\r\n createDiscountMessage(item.product.saleprice,\r\n item.product.price)\r\n }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n <a class=\"title text-dark text-decoration-none m-0 p-0 h6 mb-0\"\r\n [routerLink]=\"['/product', item.variant_id]\">{{\r\n item.product.name | titlecase\r\n }}</a>\r\n <div class=\"qty1\">\r\n <span>{{ item.product.id}}</span>\r\n </div>\r\n <div class=\"price h6 fw-bold mb-0 pb-0\">{{ item.product.price | ecCurrencySymbol\r\n }}</div>\r\n @if(getVariants(item); as options){\r\n <div class=\"d-flex align-items-center p-0\">\r\n @for(option of options; track $index){\r\n <span class=\"me-1\"> {{option.name | titlecase}}:</span>\r\n @if(option.name == 'COLOR'){\r\n <div class=\"p-2 rounded\" [style.background]=\"'#' + option.value\"></div>\r\n }@else{\r\n <b>{{option.value}}</b>\r\n }\r\n }\r\n </div>\r\n }\r\n <div class=\"campoCantidad mt-2\">\r\n <div class=\"numero\">\r\n <button (click)=\"less(item.product.variants[0]?.stock)\" class=\"btn btn-outline-secondary\"\r\n type=\"button\" id=\"button-addon1\">\r\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\r\n </button>\r\n <input type=\"text\" class=\"form-control text-center\" placeholder=\"\"\r\n aria-label=\"Example text with button addon\" aria-describedby=\"button-addon1\"\r\n [value]=\"item.quantity\" min=\"1\" step=\"1\" [(ngModel)]=\"quantity\"\r\n (change)=\"updateQuantity(item.product.variants[0]?.stock)\">\r\n <button (click)=\"plus(item.product.variants[0]?.stock)\" class=\"btn btn-outline-secondary\"\r\n type=\"button\" id=\"button-addon1\">\r\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"col-2\">\r\n <a (click)=\"deleteCartItem()\" class=\"btn botBorrar\"><i class=\"fa fa-trash\" aria-hidden=\"true\"></i></a>\r\n </div>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n}", styles: [""], dependencies: [{ kind: "pipe", type: TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: EcCurrencySymbolPipe, name: "ecCurrencySymbol" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
8827
9420
  }
@@ -9333,108 +9926,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
9333
9926
  class MpRedirectEcComponent {
9334
9927
  _paymentService = inject(PaymentService);
9335
9928
  _toastService = inject(ToastService);
9929
+ platformId = inject(PLATFORM_ID);
9930
+ finished = false;
9336
9931
  method = null;
9337
9932
  total_amount = 0;
9338
9933
  allData;
9339
9934
  ready = new EventEmitter();
9340
9935
  preference;
9341
- loading = false;
9342
9936
  url;
9343
- closeModal = '';
9344
- ventana;
9345
- window;
9346
- localStorage;
9347
- platformId = inject(PLATFORM_ID);
9348
- constructor() {
9937
+ // Fases de UI
9938
+ phase = 'idle';
9939
+ get isIdle() { return this.phase === 'idle'; }
9940
+ get isPaying() { return this.phase === 'paying'; }
9941
+ get isFinalizing() { return this.phase === 'finalizing'; }
9942
+ ventana = null;
9943
+ windowRef;
9944
+ sid = '';
9945
+ bc;
9946
+ pollTimer;
9947
+ pollStartedAt = 0;
9948
+ ngOnInit() {
9349
9949
  if (isPlatformBrowser(this.platformId)) {
9350
- this.window = window;
9351
- this.localStorage = localStorage;
9950
+ this.windowRef = window;
9951
+ if ('BroadcastChannel' in window) {
9952
+ this.bc = new BroadcastChannel('mp_payment');
9953
+ this.bc.onmessage = (e) => this.onMpMessage(e?.data);
9954
+ }
9955
+ window.addEventListener('storage', this.onStorage);
9956
+ window.addEventListener('message', this.onWindowMessage);
9352
9957
  }
9353
- }
9354
- ngOnInit() {
9355
9958
  this.getPreference();
9356
9959
  }
9960
+ ngOnDestroy() {
9961
+ if (!isPlatformBrowser(this.platformId))
9962
+ return;
9963
+ this.bc?.close();
9964
+ window.removeEventListener('storage', this.onStorage);
9965
+ window.removeEventListener('message', this.onWindowMessage);
9966
+ if (this.pollTimer)
9967
+ clearInterval(this.pollTimer);
9968
+ }
9969
+ /** Cancela manualmente el pago y finaliza el flujo con estado "cancel". */
9357
9970
  clickClose = () => {
9358
- /* this.closeModal = 'cancel'
9359
- this.ventana?.close() */
9971
+ if (this.finished)
9972
+ return;
9973
+ this.finishWithState('cancel');
9360
9974
  };
9975
+ /**
9976
+ * Inicia el pago abriendo `init_point` en una ventana/pestaña nueva.
9977
+ * Genera un SID y lo persiste para casar la respuesta del catch.
9978
+ * Si el popup es bloqueado, hace fallback navegando en la misma pestaña.
9979
+ */
9361
9980
  iniciar = () => {
9362
- this.closeModal = '';
9363
- this.clearStorageState();
9364
- this.ventana = this.window?.open(this.url);
9365
- this.callState();
9366
- };
9367
- callState = () => {
9368
- let state = this.closeModal != '' ? this.closeModal : this.localStorage?.getItem('state');
9369
- !state && this.ventana.closed && (state = 'cancel');
9370
- this.loading = true;
9371
- state && console.log(state);
9372
- if (state) {
9373
- this.loading = false;
9374
- this.localStorage?.removeItem('state');
9375
- if (state == 'success') {
9376
- this.ventana?.close();
9377
- this.ready.emit(true);
9378
- return;
9379
- }
9380
- if (state == 'pending') {
9381
- this.ventana?.close();
9382
- this.ready.emit(true);
9383
- return;
9384
- }
9385
- if (state == 'failure') {
9386
- this.ventana?.close();
9387
- this.processError('');
9388
- return;
9389
- }
9390
- if (state == 'cancel') {
9391
- this.ventana?.close();
9392
- this.processError('Se cancelo el pago con mercado pago');
9981
+ if (!isPlatformBrowser(this.platformId) || !this.windowRef || !this.url)
9982
+ return;
9983
+ this.phase = 'paying';
9984
+ this.sid = this.genSid();
9985
+ const url = new URL(this.url);
9986
+ localStorage.setItem('mp:sid', this.sid);
9987
+ this.ventana = this.windowRef.open(this.url, '_blank');
9988
+ // popup bloqueado fallback a navegación en misma pestaña
9989
+ if (!this.ventana || this.ventana.closed) {
9990
+ this.windowRef.location.href = this.url;
9991
+ return;
9992
+ }
9993
+ // polling de último recurso (hasta 10 minutos)
9994
+ this.pollStartedAt = Date.now();
9995
+ if (this.pollTimer)
9996
+ clearInterval(this.pollTimer);
9997
+ this.pollTimer = setInterval(() => {
9998
+ if (Date.now() - this.pollStartedAt > 10 * 60 * 1000) {
9999
+ clearInterval(this.pollTimer);
10000
+ this.pollTimer = null;
10001
+ this.phase = 'idle';
10002
+ this.processError('Tiempo de espera agotado al procesar el pago.');
9393
10003
  return;
9394
10004
  }
9395
- this.ventana?.close();
9396
- this.processError('');
10005
+ this.checkLocalStorageOnce();
10006
+ }, 1000);
10007
+ };
10008
+ onWindowMessage = (event) => {
10009
+ const data = event?.data;
10010
+ this.onMpMessage(data);
10011
+ };
10012
+ onStorage = (e) => {
10013
+ if (!e.key || !this.sid)
9397
10014
  return;
10015
+ if (e.key === `mp:state:${this.sid}` && e.newValue) {
10016
+ const state = e.newValue;
10017
+ this.finishWithState(state);
9398
10018
  }
9399
- setTimeout(() => {
9400
- this.callState();
9401
- }, 5000);
9402
10019
  };
9403
- processError = (err) => {
9404
- this._toastService.show(err != '' ? err : 'payment-error');
9405
- // console.log("ERROR ENVIO BACK ", err);
10020
+ onMpMessage = (data) => {
10021
+ if (!data || data.type !== 'mp:state')
10022
+ return;
10023
+ if (data.sid !== this.sid)
10024
+ return;
10025
+ this.finishWithState(data.state);
9406
10026
  };
10027
+ checkLocalStorageOnce() {
10028
+ if (!this.sid)
10029
+ return;
10030
+ const state = localStorage.getItem(`mp:state:${this.sid}`);
10031
+ if (state)
10032
+ this.finishWithState(state);
10033
+ }
10034
+ /** Cierra el flujo de pago con el estado final y notifica al padre. */
10035
+ finishWithState(state) {
10036
+ if (this.finished)
10037
+ return;
10038
+ this.finished = true;
10039
+ if (this.pollTimer)
10040
+ clearInterval(this.pollTimer);
10041
+ this.pollTimer = null;
10042
+ localStorage.removeItem(`mp:state:${this.sid}`);
10043
+ localStorage.removeItem('mp:sid');
10044
+ localStorage.removeItem('state');
10045
+ try {
10046
+ this.ventana && !this.ventana.closed && this.ventana.close();
10047
+ }
10048
+ catch { }
10049
+ this.ventana = null;
10050
+ if (state === 'success' || state === 'pending') {
10051
+ this.phase = 'finalizing';
10052
+ this.ready.emit(true);
10053
+ }
10054
+ else if (state === 'failure' || state === 'cancel') {
10055
+ this.phase = 'idle';
10056
+ this._toastService.show(state === 'cancel' ? 'Se canceló el pago con Mercado Pago' : 'payment-error');
10057
+ }
10058
+ else {
10059
+ this.phase = 'idle';
10060
+ this._toastService.show('payment-error');
10061
+ }
10062
+ }
10063
+ genSid() {
10064
+ return `mp_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
10065
+ }
10066
+ processError = (msg) => {
10067
+ this._toastService.show(msg || 'payment-error');
10068
+ };
10069
+ /** Limpia posibles residuos de estado en storages. */
9407
10070
  clearStorageState = () => {
9408
- sessionStorage.removeItem('state');
9409
- this.localStorage?.removeItem('state');
10071
+ if (!isPlatformBrowser(this.platformId))
10072
+ return;
10073
+ localStorage.removeItem('state');
10074
+ const sid = localStorage.getItem('mp:sid');
10075
+ if (sid)
10076
+ localStorage.removeItem(`mp:state:${sid}`);
10077
+ };
10078
+ /** Obtiene la preferencia e inicializa `url` (init_point). */
10079
+ getPreference = () => {
10080
+ this._paymentService.getPreference(this.allData).then((res) => {
10081
+ this.preference = res;
10082
+ this.url = this.preference?.init_point;
10083
+ this.renderMP(this.preference);
10084
+ }, () => this.setError('operation-error'));
9410
10085
  };
9411
- getPreference = () => this._paymentService.getPreference(this.allData).then(res => {
9412
- this.preference = res;
9413
- this.url = this.preference.init_point;
9414
- this.renderMP(this.preference);
9415
- }, err => this.setError('operation-error'));
9416
10086
  setError = (message) => {
9417
10087
  //this.error = message;
9418
10088
  };
9419
- renderMP = (preference) => {
9420
- this.window?.addEventListener("message", (event) => {
9421
- if (event.origin !== 'https://www.mercadopago.com.ar' || !event.data.type) {
9422
- return;
9423
- }
9424
- let dataType = event.data.type;
9425
- if (dataType === 'submit') {
9426
- const paymentData = event.data.value;
9427
- return;
9428
- }
9429
- });
9430
- };
10089
+ renderMP = (_pref) => { };
9431
10090
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MpRedirectEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9432
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: MpRedirectEcComponent, isStandalone: true, selector: "app-mp-redirect-ec", inputs: { method: "method", total_amount: "total_amount", allData: "allData" }, outputs: { ready: "ready" }, ngImport: i0, template: "<div class=\"text-center\">\r\n\t@if(url){\r\n\t\t@if(!loading){\r\n\t\t\t<button (click)=\"iniciar()\" class=\"btn btn-outline-primary rounded-0 comprar mt-3\">Pagar</button>\r\n\t\t} @else {\r\n\t\t\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t\t\t<h3>Procesando el pago por mercado pago</h3>\r\n\t\t\t\t<h5>Recuerde hacer click en \"Volver al sitio\" desde mercado pago para finalizar la compra.</h5>\r\n\t\t\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"container-fluid\">\r\n\t\t\t\t<div class=\"row\">\r\n\t\t\t\t\t<div class=\"col-5\">\r\n\t\t\t\t\t\t<hr>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"col-2 text-center\">\r\n\t\t\t\t\t\t<label for=\"\">o</label>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"col-5\">\r\n\t\t\t\t\t\t<hr>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t\t\t<button (click)=\"clickClose()\" class=\"btn btn-outline-secondary\">Cancelar pago</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\t} @else{\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n</div>", styles: [""], dependencies: [{ kind: "component", type: LoadingFullEcComponent, selector: "app-loading-full-ec" }] });
10091
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: MpRedirectEcComponent, isStandalone: true, selector: "app-mp-redirect-ec", inputs: { method: "method", total_amount: "total_amount", allData: "allData" }, outputs: { ready: "ready" }, ngImport: i0, template: "<div class=\"text-center\">\r\n\t@if(url) {\r\n\r\n\t@if(isIdle) {\r\n\t<button (click)=\"iniciar()\" class=\"btn btn-outline-primary rounded-0 comprar mt-3\">Pagar</button>\r\n\t}\r\n\r\n\t@if(isPaying) {\r\n\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t<h3>Procesando el pago por Mercado Pago</h3>\r\n\t\t<h5>Record\u00E1 tocar \u201CVolver al sitio\u201D en Mercado Pago para finalizar.</h5>\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\r\n\t<div class=\"container-fluid\">\r\n\t\t<div class=\"row\">\r\n\t\t\t<div class=\"col-5\">\r\n\t\t\t\t<hr>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"col-2 text-center\"><label>o</label></div>\r\n\t\t\t<div class=\"col-5\">\r\n\t\t\t\t<hr>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<button (click)=\"clickClose()\" class=\"btn btn-outline-secondary\">Cancelar pago</button>\r\n\t</div>\r\n\t}\r\n\r\n\t@if(isFinalizing) {\r\n\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t<h3>Confirmando pago y redirigiendo\u2026</h3>\r\n\t\t<h5>No cierres ni recargues esta p\u00E1gina.</h5>\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n\r\n\t} @else {\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n</div>", styles: [""], dependencies: [{ kind: "component", type: LoadingFullEcComponent, selector: "app-loading-full-ec" }] });
9433
10092
  }
9434
10093
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MpRedirectEcComponent, decorators: [{
9435
10094
  type: Component,
9436
- args: [{ selector: 'app-mp-redirect-ec', standalone: true, imports: [LoadingFullEcComponent], template: "<div class=\"text-center\">\r\n\t@if(url){\r\n\t\t@if(!loading){\r\n\t\t\t<button (click)=\"iniciar()\" class=\"btn btn-outline-primary rounded-0 comprar mt-3\">Pagar</button>\r\n\t\t} @else {\r\n\t\t\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t\t\t<h3>Procesando el pago por mercado pago</h3>\r\n\t\t\t\t<h5>Recuerde hacer click en \"Volver al sitio\" desde mercado pago para finalizar la compra.</h5>\r\n\t\t\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"container-fluid\">\r\n\t\t\t\t<div class=\"row\">\r\n\t\t\t\t\t<div class=\"col-5\">\r\n\t\t\t\t\t\t<hr>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"col-2 text-center\">\r\n\t\t\t\t\t\t<label for=\"\">o</label>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"col-5\">\r\n\t\t\t\t\t\t<hr>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t\t\t<button (click)=\"clickClose()\" class=\"btn btn-outline-secondary\">Cancelar pago</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\t} @else{\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n</div>" }]
9437
- }], ctorParameters: () => [], propDecorators: { method: [{
10095
+ args: [{ selector: 'app-mp-redirect-ec', standalone: true, imports: [LoadingFullEcComponent], template: "<div class=\"text-center\">\r\n\t@if(url) {\r\n\r\n\t@if(isIdle) {\r\n\t<button (click)=\"iniciar()\" class=\"btn btn-outline-primary rounded-0 comprar mt-3\">Pagar</button>\r\n\t}\r\n\r\n\t@if(isPaying) {\r\n\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t<h3>Procesando el pago por Mercado Pago</h3>\r\n\t\t<h5>Record\u00E1 tocar \u201CVolver al sitio\u201D en Mercado Pago para finalizar.</h5>\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\r\n\t<div class=\"container-fluid\">\r\n\t\t<div class=\"row\">\r\n\t\t\t<div class=\"col-5\">\r\n\t\t\t\t<hr>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"col-2 text-center\"><label>o</label></div>\r\n\t\t\t<div class=\"col-5\">\r\n\t\t\t\t<hr>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<button (click)=\"clickClose()\" class=\"btn btn-outline-secondary\">Cancelar pago</button>\r\n\t</div>\r\n\t}\r\n\r\n\t@if(isFinalizing) {\r\n\t<div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n\t\t<h3>Confirmando pago y redirigiendo\u2026</h3>\r\n\t\t<h5>No cierres ni recargues esta p\u00E1gina.</h5>\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n\r\n\t} @else {\r\n\t<div class=\"d-flex flex-column justify-content-center align-items-center mt-2\">\r\n\t\t<app-loading-full-ec></app-loading-full-ec>\r\n\t</div>\r\n\t}\r\n</div>" }]
10096
+ }], propDecorators: { method: [{
9438
10097
  type: Input
9439
10098
  }], total_amount: [{
9440
10099
  type: Input
@@ -9677,11 +10336,11 @@ class DecidirEcComponent extends ComponentHelper {
9677
10336
  obj.style.width = obj.contentWindow.document.body.scrollWidth + 'px';
9678
10337
  };
9679
10338
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DecidirEcComponent, deps: [{ token: i0.Renderer2 }, { token: ConnectionService }, { token: ToastService }, { token: CoreConstantsService }, { token: ApiConstantsService }, { token: CartService }, { token: i2.ActivatedRoute }, { token: i1$4.DomSanitizer }, { token: ParametersService }], target: i0.ɵɵFactoryTarget.Component });
9680
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: DecidirEcComponent, isStandalone: true, selector: "app-decidir-ec", inputs: { paymentServiceInst: "paymentServiceInst", method: "method", total_amount: "total_amount", allData: "allData", user_data: "user_data" }, outputs: { ready: "ready" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"text-center\">\r\n <h3>Continuar con el pago en Decidir</h3>\r\n @if (method) {\r\n <p class=\"px-5\">{{ method.description }}</p>\r\n <p class=\"px-5\">{{ method.instructions }}</p>\r\n }\r\n @if (!loading) {\r\n <button class=\"btn btn-outline-secondary comprar\" (click)=\"openModal()\">Pagar</button>\r\n } @else {\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n }\r\n </div>\r\n\r\n@if (showModal) {\r\n<div class=\"modal-backdrop\" (click)=\"clickClose()\">\r\n <div class=\"modal-dialog modal-lg\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-content\">\r\n\r\n <div class=\"modal-body\">\r\n <div class=\"payment-container\">\r\n \r\n <!-- Iframe del formulario de decidir -->\r\n <div class=\"iframe-container\">\r\n <iframe [src]=\"url\" frameborder=\"0\" class=\"payment-iframe\"></iframe>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#0009;display:flex;justify-content:center;align-items:center;z-index:1050;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-dialog{max-width:90%;max-height:90%;width:800px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d;animation:modalFadeIn .3s ease-out}.modal-dialog.modal-lg{max-width:900px;width:90%}@keyframes modalFadeIn{0%{opacity:0;transform:scale(.9) translateY(-20px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-content{display:flex;flex-direction:column;height:100%;border:none;border-radius:16px;background:#fff}.modal-header{padding:1.5rem 2rem;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#f8f9fa,#e9ecef);display:flex;justify-content:space-between;align-items:center;position:relative}.modal-header:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.modal-header .modal-title{margin:0;font-size:1.5rem;font-weight:700;color:#2c3e50;text-shadow:0 1px 2px rgba(0,0,0,.1)}.modal-header .btn-close{background:none;border:none;font-size:1.8rem;color:#6c757d;cursor:pointer;padding:0;width:35px;height:35px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;position:relative}.modal-header .btn-close:hover{background-color:#dc3545;color:#fff;transform:rotate(90deg);box-shadow:0 4px 12px #dc35454d}.modal-header .btn-close:active{transform:rotate(90deg) scale(.95)}.modal-body{padding:0;flex:1;overflow:hidden;background:#fff}.payment-container{height:100%;display:flex;flex-direction:column;background:#fff}.cards-accepted{padding:1.5rem 2rem;text-align:center;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#fff,#f8f9fa);position:relative}.cards-accepted:after{content:\"\";position:absolute;bottom:0;left:5%;right:5%;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.cards-accepted .cards-img{height:45px;max-width:100%;object-fit:contain;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1));transition:transform .2s ease}.cards-accepted .cards-img:hover{transform:scale(1.05)}.iframe-container{flex:1;padding:1.5rem;background:#fff;position:relative}.iframe-container:before{content:\"\";position:absolute;top:0;left:1.5rem;right:1.5rem;height:1px;background:linear-gradient(90deg,transparent 0%,#e9ecef 50%,transparent 100%)}.payment-iframe{width:100%;height:620px;border:none;border-radius:12px;background:#fff;box-shadow:inset 0 2px 8px #0000000d}.half-width{width:49%!important}.ml-1{margin-left:1%}#card-form{height:450px}.iframeStyle{height:520px;width:100%}@media only screen and (max-width: 1024px){.modal-dialog,.modal-dialog.modal-lg{max-width:95%;width:95%}.payment-iframe{height:650px}.modal-header{padding:1rem 1.5rem}.modal-header .modal-title{font-size:1.125rem}.cards-accepted{padding:.75rem 1.5rem}}@media only screen and (max-width: 680px){.modal-dialog{max-width:98%;width:98%;margin:1rem;max-height:calc(100vh - 2rem)}.modal-dialog.modal-lg{max-width:98%;width:98%}.payment-iframe{height:550px}.modal-header{padding:1rem}.modal-header .modal-title{font-size:1rem}.modal-header .btn-close{width:28px;height:28px;font-size:1.25rem}.cards-accepted{padding:.5rem 1rem}.cards-accepted .cards-img{height:35px}.iframe-container{padding:.5rem}}@media only screen and (max-width: 480px){.modal-dialog{margin:.5rem;max-height:calc(100vh - 1rem);border-radius:8px}.modal-content{border-radius:8px}.payment-iframe{height:500px}.cards-accepted .cards-img{height:30px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: LoadingFullEcComponent, selector: "app-loading-full-ec" }] });
10339
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: DecidirEcComponent, isStandalone: true, selector: "app-decidir-ec", inputs: { paymentServiceInst: "paymentServiceInst", method: "method", total_amount: "total_amount", allData: "allData", user_data: "user_data" }, outputs: { ready: "ready" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"text-center\">\r\n <h3>Continuar con el pago en Decidir</h3>\r\n @if (method) {\r\n <p class=\"px-5\">{{ method.description }}</p>\r\n <p class=\"px-5\">{{ method.instructions }}</p>\r\n }\r\n @if (!loading) {\r\n <button class=\"btn btn-outline-secondary comprar\" (click)=\"openModal()\">Pagar</button>\r\n } @else {\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n }\r\n </div>\r\n\r\n@if (showModal) {\r\n<div class=\"modal-backdrop\" (click)=\"clickClose()\">\r\n <div class=\"modal-dialog modal-lg\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-content\">\r\n\r\n <div class=\"modal-body\">\r\n <div class=\"payment-container\">\r\n \r\n <!-- Iframe del formulario de decidir -->\r\n <div class=\"iframe-container\">\r\n <iframe [src]=\"url\" frameborder=\"0\" class=\"payment-iframe\"></iframe>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#0009;display:flex;justify-content:center;align-items:center;z-index:1050;backdrop-filter:blur(2px)}.modal-dialog{max-width:90%;max-height:90%;width:800px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d;animation:modalFadeIn .3s ease-out}.modal-dialog.modal-lg{max-width:900px;width:90%}@keyframes modalFadeIn{0%{opacity:0;transform:scale(.9) translateY(-20px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-content{display:flex;flex-direction:column;height:100%;border:none;border-radius:16px;background:#fff}.modal-header{padding:1.5rem 2rem;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#f8f9fa,#e9ecef);display:flex;justify-content:space-between;align-items:center;position:relative}.modal-header:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.modal-header .modal-title{margin:0;font-size:1.5rem;font-weight:700;color:#2c3e50;text-shadow:0 1px 2px rgba(0,0,0,.1)}.modal-header .btn-close{background:none;border:none;font-size:1.8rem;color:#6c757d;cursor:pointer;padding:0;width:35px;height:35px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;position:relative}.modal-header .btn-close:hover{background-color:#dc3545;color:#fff;transform:rotate(90deg);box-shadow:0 4px 12px #dc35454d}.modal-header .btn-close:active{transform:rotate(90deg) scale(.95)}.modal-body{padding:0;flex:1;overflow:hidden;background:#fff}.payment-container{height:100%;display:flex;flex-direction:column;background:#fff}.cards-accepted{padding:1.5rem 2rem;text-align:center;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#fff,#f8f9fa);position:relative}.cards-accepted:after{content:\"\";position:absolute;bottom:0;left:5%;right:5%;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.cards-accepted .cards-img{height:45px;max-width:100%;object-fit:contain;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1));transition:transform .2s ease}.cards-accepted .cards-img:hover{transform:scale(1.05)}.iframe-container{flex:1;padding:1.5rem;background:#fff;position:relative}.iframe-container:before{content:\"\";position:absolute;top:0;left:1.5rem;right:1.5rem;height:1px;background:linear-gradient(90deg,transparent 0%,#e9ecef 50%,transparent 100%)}.payment-iframe{width:100%;height:620px;border:none;border-radius:12px;background:#fff;box-shadow:inset 0 2px 8px #0000000d}.half-width{width:49%!important}.ml-1{margin-left:1%}#card-form{height:450px}.iframeStyle{height:520px;width:100%}@media only screen and (max-width: 1024px){.modal-dialog,.modal-dialog.modal-lg{max-width:95%;width:95%}.payment-iframe{height:650px}.modal-header{padding:1rem 1.5rem}.modal-header .modal-title{font-size:1.125rem}.cards-accepted{padding:.75rem 1.5rem}}@media only screen and (max-width: 680px){.modal-dialog{max-width:98%;width:98%;margin:1rem;max-height:calc(100vh - 2rem)}.modal-dialog.modal-lg{max-width:98%;width:98%}.payment-iframe{height:550px}.modal-header{padding:1rem}.modal-header .modal-title{font-size:1rem}.modal-header .btn-close{width:28px;height:28px;font-size:1.25rem}.cards-accepted{padding:.5rem 1rem}.cards-accepted .cards-img{height:35px}.iframe-container{padding:.5rem}}@media only screen and (max-width: 480px){.modal-dialog{margin:.5rem;max-height:calc(100vh - 1rem);border-radius:8px}.modal-content{border-radius:8px}.payment-iframe{height:500px}.cards-accepted .cards-img{height:30px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: LoadingFullEcComponent, selector: "app-loading-full-ec" }] });
9681
10340
  }
9682
10341
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DecidirEcComponent, decorators: [{
9683
10342
  type: Component,
9684
- args: [{ selector: 'app-decidir-ec', standalone: true, imports: [CommonModule, FormsModule, LoadingFullEcComponent], template: "<div class=\"text-center\">\r\n <h3>Continuar con el pago en Decidir</h3>\r\n @if (method) {\r\n <p class=\"px-5\">{{ method.description }}</p>\r\n <p class=\"px-5\">{{ method.instructions }}</p>\r\n }\r\n @if (!loading) {\r\n <button class=\"btn btn-outline-secondary comprar\" (click)=\"openModal()\">Pagar</button>\r\n } @else {\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n }\r\n </div>\r\n\r\n@if (showModal) {\r\n<div class=\"modal-backdrop\" (click)=\"clickClose()\">\r\n <div class=\"modal-dialog modal-lg\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-content\">\r\n\r\n <div class=\"modal-body\">\r\n <div class=\"payment-container\">\r\n \r\n <!-- Iframe del formulario de decidir -->\r\n <div class=\"iframe-container\">\r\n <iframe [src]=\"url\" frameborder=\"0\" class=\"payment-iframe\"></iframe>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#0009;display:flex;justify-content:center;align-items:center;z-index:1050;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-dialog{max-width:90%;max-height:90%;width:800px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d;animation:modalFadeIn .3s ease-out}.modal-dialog.modal-lg{max-width:900px;width:90%}@keyframes modalFadeIn{0%{opacity:0;transform:scale(.9) translateY(-20px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-content{display:flex;flex-direction:column;height:100%;border:none;border-radius:16px;background:#fff}.modal-header{padding:1.5rem 2rem;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#f8f9fa,#e9ecef);display:flex;justify-content:space-between;align-items:center;position:relative}.modal-header:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.modal-header .modal-title{margin:0;font-size:1.5rem;font-weight:700;color:#2c3e50;text-shadow:0 1px 2px rgba(0,0,0,.1)}.modal-header .btn-close{background:none;border:none;font-size:1.8rem;color:#6c757d;cursor:pointer;padding:0;width:35px;height:35px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;position:relative}.modal-header .btn-close:hover{background-color:#dc3545;color:#fff;transform:rotate(90deg);box-shadow:0 4px 12px #dc35454d}.modal-header .btn-close:active{transform:rotate(90deg) scale(.95)}.modal-body{padding:0;flex:1;overflow:hidden;background:#fff}.payment-container{height:100%;display:flex;flex-direction:column;background:#fff}.cards-accepted{padding:1.5rem 2rem;text-align:center;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#fff,#f8f9fa);position:relative}.cards-accepted:after{content:\"\";position:absolute;bottom:0;left:5%;right:5%;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.cards-accepted .cards-img{height:45px;max-width:100%;object-fit:contain;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1));transition:transform .2s ease}.cards-accepted .cards-img:hover{transform:scale(1.05)}.iframe-container{flex:1;padding:1.5rem;background:#fff;position:relative}.iframe-container:before{content:\"\";position:absolute;top:0;left:1.5rem;right:1.5rem;height:1px;background:linear-gradient(90deg,transparent 0%,#e9ecef 50%,transparent 100%)}.payment-iframe{width:100%;height:620px;border:none;border-radius:12px;background:#fff;box-shadow:inset 0 2px 8px #0000000d}.half-width{width:49%!important}.ml-1{margin-left:1%}#card-form{height:450px}.iframeStyle{height:520px;width:100%}@media only screen and (max-width: 1024px){.modal-dialog,.modal-dialog.modal-lg{max-width:95%;width:95%}.payment-iframe{height:650px}.modal-header{padding:1rem 1.5rem}.modal-header .modal-title{font-size:1.125rem}.cards-accepted{padding:.75rem 1.5rem}}@media only screen and (max-width: 680px){.modal-dialog{max-width:98%;width:98%;margin:1rem;max-height:calc(100vh - 2rem)}.modal-dialog.modal-lg{max-width:98%;width:98%}.payment-iframe{height:550px}.modal-header{padding:1rem}.modal-header .modal-title{font-size:1rem}.modal-header .btn-close{width:28px;height:28px;font-size:1.25rem}.cards-accepted{padding:.5rem 1rem}.cards-accepted .cards-img{height:35px}.iframe-container{padding:.5rem}}@media only screen and (max-width: 480px){.modal-dialog{margin:.5rem;max-height:calc(100vh - 1rem);border-radius:8px}.modal-content{border-radius:8px}.payment-iframe{height:500px}.cards-accepted .cards-img{height:30px}}\n"] }]
10343
+ args: [{ selector: 'app-decidir-ec', standalone: true, imports: [CommonModule, FormsModule, LoadingFullEcComponent], template: "<div class=\"text-center\">\r\n <h3>Continuar con el pago en Decidir</h3>\r\n @if (method) {\r\n <p class=\"px-5\">{{ method.description }}</p>\r\n <p class=\"px-5\">{{ method.instructions }}</p>\r\n }\r\n @if (!loading) {\r\n <button class=\"btn btn-outline-secondary comprar\" (click)=\"openModal()\">Pagar</button>\r\n } @else {\r\n <div class=\"d-flex flex-column jusitfy-content-center align-items-center mt-2\">\r\n <app-loading-full-ec></app-loading-full-ec>\r\n </div>\r\n }\r\n </div>\r\n\r\n@if (showModal) {\r\n<div class=\"modal-backdrop\" (click)=\"clickClose()\">\r\n <div class=\"modal-dialog modal-lg\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-content\">\r\n\r\n <div class=\"modal-body\">\r\n <div class=\"payment-container\">\r\n \r\n <!-- Iframe del formulario de decidir -->\r\n <div class=\"iframe-container\">\r\n <iframe [src]=\"url\" frameborder=\"0\" class=\"payment-iframe\"></iframe>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#0009;display:flex;justify-content:center;align-items:center;z-index:1050;backdrop-filter:blur(2px)}.modal-dialog{max-width:90%;max-height:90%;width:800px;background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d;animation:modalFadeIn .3s ease-out}.modal-dialog.modal-lg{max-width:900px;width:90%}@keyframes modalFadeIn{0%{opacity:0;transform:scale(.9) translateY(-20px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-content{display:flex;flex-direction:column;height:100%;border:none;border-radius:16px;background:#fff}.modal-header{padding:1.5rem 2rem;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#f8f9fa,#e9ecef);display:flex;justify-content:space-between;align-items:center;position:relative}.modal-header:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.modal-header .modal-title{margin:0;font-size:1.5rem;font-weight:700;color:#2c3e50;text-shadow:0 1px 2px rgba(0,0,0,.1)}.modal-header .btn-close{background:none;border:none;font-size:1.8rem;color:#6c757d;cursor:pointer;padding:0;width:35px;height:35px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;position:relative}.modal-header .btn-close:hover{background-color:#dc3545;color:#fff;transform:rotate(90deg);box-shadow:0 4px 12px #dc35454d}.modal-header .btn-close:active{transform:rotate(90deg) scale(.95)}.modal-body{padding:0;flex:1;overflow:hidden;background:#fff}.payment-container{height:100%;display:flex;flex-direction:column;background:#fff}.cards-accepted{padding:1.5rem 2rem;text-align:center;border-bottom:1px solid #e9ecef;background:linear-gradient(135deg,#fff,#f8f9fa);position:relative}.cards-accepted:after{content:\"\";position:absolute;bottom:0;left:5%;right:5%;height:1px;background:linear-gradient(90deg,transparent 0%,#dee2e6 50%,transparent 100%)}.cards-accepted .cards-img{height:45px;max-width:100%;object-fit:contain;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1));transition:transform .2s ease}.cards-accepted .cards-img:hover{transform:scale(1.05)}.iframe-container{flex:1;padding:1.5rem;background:#fff;position:relative}.iframe-container:before{content:\"\";position:absolute;top:0;left:1.5rem;right:1.5rem;height:1px;background:linear-gradient(90deg,transparent 0%,#e9ecef 50%,transparent 100%)}.payment-iframe{width:100%;height:620px;border:none;border-radius:12px;background:#fff;box-shadow:inset 0 2px 8px #0000000d}.half-width{width:49%!important}.ml-1{margin-left:1%}#card-form{height:450px}.iframeStyle{height:520px;width:100%}@media only screen and (max-width: 1024px){.modal-dialog,.modal-dialog.modal-lg{max-width:95%;width:95%}.payment-iframe{height:650px}.modal-header{padding:1rem 1.5rem}.modal-header .modal-title{font-size:1.125rem}.cards-accepted{padding:.75rem 1.5rem}}@media only screen and (max-width: 680px){.modal-dialog{max-width:98%;width:98%;margin:1rem;max-height:calc(100vh - 2rem)}.modal-dialog.modal-lg{max-width:98%;width:98%}.payment-iframe{height:550px}.modal-header{padding:1rem}.modal-header .modal-title{font-size:1rem}.modal-header .btn-close{width:28px;height:28px;font-size:1.25rem}.cards-accepted{padding:.5rem 1rem}.cards-accepted .cards-img{height:35px}.iframe-container{padding:.5rem}}@media only screen and (max-width: 480px){.modal-dialog{margin:.5rem;max-height:calc(100vh - 1rem);border-radius:8px}.modal-content{border-radius:8px}.payment-iframe{height:500px}.cards-accepted .cards-img{height:30px}}\n"] }]
9685
10344
  }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: ConnectionService }, { type: ToastService }, { type: CoreConstantsService }, { type: ApiConstantsService }, { type: CartService }, { type: i2.ActivatedRoute }, { type: i1$4.DomSanitizer }, { type: ParametersService }], propDecorators: { paymentServiceInst: [{
9686
10345
  type: Input
9687
10346
  }], method: [{
@@ -11552,5 +12211,5 @@ const directives = [
11552
12211
  * Generated bundle index. Do not edit.
11553
12212
  */
11554
12213
 
11555
- export { AccountEcComponent, AddressingService, AnalyticsService, AuthEcComponent, AuthService, AuthStorageService, BlockBannerBoxEcComponent, BlockBannerFullEcComponent, BlockFormContactEcComponent, BlockHtmlEcComponent, BlockNewsletterEcComponent, BlockProductsEcComponent, BlocksEcComponent, BlocksRepositoryService, BlocksService, BreadcrumbEcComponent, CartEcComponent, CartItemEcComponent, CartService, ChannelService, CheckoutEcComponent, CheckoutService, CollectionEcComponent, ConfirmAccountEcComponent, ContactEcComponent, CoreConstantsService, CouponEcComponent, CurrencyService, DopplerService, ENVIRONMENT_TOKEN, EcCurrencySymbolPipe, EcSafeHtmlPipe, FacebookPixelService, FaqsEcComponent, FiltersEcComponent, FiltersService, FiltersSortEcComponent, FooterEcComponent, ForgotPasswordEcComponent, FormService, GTMService, GoogleAnalyticsService, HeaderEcComponent, HomeEcComponent, LoadingFullEcComponent, LoadingInlineEcComponent, LoadingSectionEcComponent, LocalStorageService, LoginFormEcComponent, MagnizoomEcComponent, MetricoolPixelService, NgxLocalStorageService, OptionsService, OrderEcComponent, OrderUtilityService, OrdersListEcComponent, OrdersService, PaginationService, ParametersService, ParamsContext, PasswordResetEcComponent, PaymentService, PriceEcComponent, PriceRangeFilterComponent, ProductDetailEcComponent, ProductDetailService, ProductEcComponent, ProductOffDirective, ProductStockDirective, ProductsService, ReCaptchaEcComponent, ReCaptchaService, RegisterFormEcComponent, RegisterWholesalerFormEcComponent, RelatedProductsEcComponent, ReviewsEcComponent, ReviewsFormEcComponent, SectionContainerEcComponent, ShareEcComponent, ShipmentService, SidebarEcComponent, StoresEcComponent, SuccessEcComponent, TestService, ToastService, VariantsEcComponent, authGuard, authInterceptor, directives, provideEnvironment };
12214
+ export { AccountEcComponent, AddressingService, AnalyticsService, AuthEcComponent, AuthService, AuthStorageService, BlockBannerBoxEcComponent, BlockBannerFullEcComponent, BlockFormContactEcComponent, BlockHtmlEcComponent, BlockNewsletterEcComponent, BlockProductsEcComponent, BlocksEcComponent, BlocksRepositoryService, BlocksService, BreadcrumbEcComponent, CartEcComponent, CartItemEcComponent, CartService, ChannelService, CheckoutEcComponent, CheckoutService, CollectionEcComponent, ConfirmAccountEcComponent, ContactEcComponent, CoreConstantsService, CouponEcComponent, CurrencyService, DopplerService, ENVIRONMENT_TOKEN, EcCurrencySymbolPipe, EcSafeHtmlPipe, FacebookPixelService, FaqsEcComponent, FiltersEcComponent, FiltersService, FiltersSortEcComponent, FooterEcComponent, ForgotPasswordEcComponent, FormService, GTMService, GoogleAnalyticsService, HeaderEcComponent, HomeEcComponent, LoadingFullEcComponent, LoadingInlineEcComponent, LoadingSectionEcComponent, LocalStorageService, LoginFormEcComponent, MagnizoomEcComponent, MetricoolPixelService, NgxLocalStorageService, OptionsService, OrderEcComponent, OrderUtilityService, OrdersListEcComponent, OrdersService, PaginationService, ParametersService, ParamsContext, PasswordResetEcComponent, PaymentService, PriceEcComponent, PriceRangeFilterComponent, ProductDetailEcComponent, ProductDetailService, ProductEcComponent, ProductOffDirective, ProductStockDirective, ProductsService, ReCaptchaEcComponent, ReCaptchaService, RedsysCatchEcComponent, RegisterFormEcComponent, RegisterWholesalerFormEcComponent, RelatedProductsEcComponent, ReviewsEcComponent, ReviewsFormEcComponent, SectionContainerEcComponent, ShareEcComponent, ShipmentService, SidebarEcComponent, StoresEcComponent, SuccessEcComponent, TestService, ToastService, VariantsEcComponent, authGuard, authInterceptor, directives, provideEnvironment };
11556
12215
  //# sourceMappingURL=ng-easycommerce-v18.mjs.map