ng-easycommerce-v18 0.2.29 → 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 (51) hide show
  1. package/README.md +45 -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-ec.component.mjs +8 -2
  15. package/esm2022/lib/ec-components/cart-ec/cart-item-ec/cart-item-ec.component.mjs +42 -10
  16. package/esm2022/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.mjs +141 -75
  17. package/esm2022/lib/ec-components/filters-ec/filters-ec.component.mjs +9 -1
  18. package/esm2022/lib/ec-components/header-ec/header-ec.component.mjs +53 -2
  19. package/esm2022/lib/ec-components/product-detail-ec/product-detail-ec.component.mjs +23 -15
  20. package/esm2022/lib/ec-components/product-ec/product-ec.component.mjs +143 -5
  21. package/esm2022/lib/ec-components/widgets-ec/decidir-ec/decidir-ec.component.mjs +3 -3
  22. package/esm2022/lib/ec-components/widgets-ec/index.mjs +2 -1
  23. package/esm2022/lib/ec-components/widgets-ec/redsys-catch-ec/redsys-catch-ec.component.mjs +193 -0
  24. package/esm2022/lib/ec-services/cart.service.mjs +23 -5
  25. package/esm2022/lib/ec-services/checkout.service.mjs +46 -21
  26. package/esm2022/lib/ec-services/form.service.mjs +13 -1
  27. package/esm2022/lib/ec-services/order-utility.service.mjs +12 -6
  28. package/esm2022/lib/ec-services/product-detail.service.mjs +11 -1
  29. package/esm2022/lib/interfaces/environment.mjs +1 -1
  30. package/esm2022/lib/interfaces/product.mjs +1 -1
  31. package/fesm2022/ng-easycommerce-v18.mjs +843 -147
  32. package/fesm2022/ng-easycommerce-v18.mjs.map +1 -1
  33. package/lib/classes/component-helper.d.ts +1 -1
  34. package/lib/constants/core.constants.service.d.ts +24 -0
  35. package/lib/ec-components/abstractions-components/menu-ec.component.d.ts +6 -0
  36. package/lib/ec-components/auth-ec/login-form-ec/login-form-ec.component.d.ts +2 -0
  37. package/lib/ec-components/auth-ec/register-form-ec/register-form-ec.component.d.ts +2 -0
  38. package/lib/ec-components/cart-ec/cart-ec.component.d.ts +3 -0
  39. package/lib/ec-components/cart-ec/cart-item-ec/cart-item-ec.component.d.ts +6 -1
  40. package/lib/ec-components/checkout-ec/payment-ec/payment-methods/mp-redirect-ec/mp-redirect-ec.component.d.ts +38 -16
  41. package/lib/ec-components/filters-ec/filters-ec.component.d.ts +6 -0
  42. package/lib/ec-components/header-ec/header-ec.component.d.ts +16 -2
  43. package/lib/ec-components/product-detail-ec/product-detail-ec.component.d.ts +2 -1
  44. package/lib/ec-components/product-ec/product-ec.component.d.ts +14 -1
  45. package/lib/ec-components/widgets-ec/index.d.ts +1 -0
  46. package/lib/ec-components/widgets-ec/redsys-catch-ec/redsys-catch-ec.component.d.ts +47 -0
  47. package/lib/ec-services/cart.service.d.ts +1 -1
  48. package/lib/ec-services/form.service.d.ts +6 -0
  49. package/lib/interfaces/environment.d.ts +1 -0
  50. package/lib/interfaces/product.d.ts +6 -0
  51. 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
  /**
@@ -4538,7 +4672,7 @@ class CartService {
4538
4672
  * @returns
4539
4673
  */
4540
4674
  getSubTotalAmount() {
4541
- return this.cart$.pipe(map((cart) => cart?.totals?.items));
4675
+ return this.cart$.pipe(map((cart) => cart?.totals?.subtotal));
4542
4676
  }
4543
4677
  /**
4544
4678
  * Retorna un observable con el total del monto de las promociones aplicadas.
@@ -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 });
@@ -5039,15 +5183,21 @@ class OrderUtilityService {
5039
5183
  if (cart.checkoutState && cart.checkoutState == "completed")
5040
5184
  return;
5041
5185
  let new_state = [];
5042
- let subtotal = { type: 'subtotal', name: 'subtotal', amount: cart.totals.items };
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);
@@ -5208,33 +5358,58 @@ class CheckoutService {
5208
5358
  return { ok: false, require_login: false, message: 'An error occurred' };
5209
5359
  }
5210
5360
  };
5361
+ // updateAsociatedData = (cart: any) => {
5362
+ // let result = this._orderUtilityService.getPromotions(cart);
5363
+ // const userProfile: User = User.fromJson(this._authService.getUserProfileAsUser());
5364
+ // if (result) {
5365
+ // if (userProfile.isWholesaler() && cart.totals.taxes != 0) {
5366
+ // let total = { ...result[result.length - 1] };
5367
+ // result[result.length - 1] = {
5368
+ // type: 'taxes',
5369
+ // name: 'taxes',
5370
+ // amount: cart.totals.taxes
5371
+ // }
5372
+ // result.push(total);
5373
+ // }
5374
+ // let shipment = result?.find((promotion: any) => promotion.type == "shipment");
5375
+ // if (!shipment && cart.totals.shipping != 0) {
5376
+ // if (result?.length && !result.find((item: any) => item.type === "shipment")) {
5377
+ // let total = { ...result[result.length - 1] };
5378
+ // result[result.length - 1] = {
5379
+ // type: 'shipment',
5380
+ // name: 'shipment',
5381
+ // amount: cart.totals.shipping
5382
+ // }
5383
+ // result.push(total);
5384
+ // }
5385
+ // }
5386
+ // this._associatedDataSubject.next(result);
5387
+ // }
5388
+ // }
5211
5389
  updateAsociatedData = (cart) => {
5212
5390
  let result = this._orderUtilityService.getPromotions(cart);
5213
- const userProfile = User.fromJson(this._authService.getUserProfileAsUser());
5214
- if (result) {
5215
- if (userProfile.isWholesaler() && cart.totals.taxes != 0) {
5391
+ if (cart.totals.taxes != 0 && result && result.length > 0) {
5392
+ let total = { ...result[result.length - 1] };
5393
+ result[result.length - 1] = {
5394
+ type: 'taxes',
5395
+ name: 'taxes',
5396
+ amount: cart.totals.taxes
5397
+ };
5398
+ result.push(total);
5399
+ }
5400
+ let shipment = result?.find(promotion => promotion.type == "shipment");
5401
+ if (!shipment && cart.totals.shipping != 0) {
5402
+ if (result?.length && !result.find(item => item.type === "shipment")) {
5216
5403
  let total = { ...result[result.length - 1] };
5217
5404
  result[result.length - 1] = {
5218
- type: 'taxes',
5219
- name: 'taxes',
5220
- amount: cart.totals.taxes
5405
+ type: 'shipment',
5406
+ name: 'shipment',
5407
+ amount: cart.totals.shipping
5221
5408
  };
5222
5409
  result.push(total);
5223
5410
  }
5224
- let shipment = result?.find((promotion) => promotion.type == "shipment");
5225
- if (!shipment && cart.totals.shipping != 0) {
5226
- if (result?.length && !result.find((item) => item.type === "shipment")) {
5227
- let total = { ...result[result.length - 1] };
5228
- result[result.length - 1] = {
5229
- type: 'shipment',
5230
- name: 'shipment',
5231
- amount: cart.totals.shipping
5232
- };
5233
- result.push(total);
5234
- }
5235
- }
5236
- this._associatedDataSubject.next(result);
5237
5411
  }
5412
+ this._associatedDataSubject.next(result ?? null);
5238
5413
  };
5239
5414
  getStepData = (step_name) => {
5240
5415
  let step_result = this._stateSubject.getValue().find(step => step.name == step_name);
@@ -5859,6 +6034,14 @@ class MenuEcComponent {
5859
6034
  });
5860
6035
  }));
5861
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
+ }
5862
6045
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MenuEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5863
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 });
5864
6047
  }
@@ -6005,6 +6188,13 @@ class HeaderEcComponent extends MenuEcComponent {
6005
6188
  hidePrices = false;
6006
6189
  __authService = inject(AuthService);
6007
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$;
6008
6198
  isAuthenticated$ = this.__authService.isAuthenticated();
6009
6199
  constructor() {
6010
6200
  super();
@@ -6022,6 +6212,22 @@ class HeaderEcComponent extends MenuEcComponent {
6022
6212
  this.channel = this.coreConstantsService.getChannel();
6023
6213
  this.onWindowScroll();
6024
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
+ }
6025
6231
  }
6026
6232
  ngAfterViewInit() {
6027
6233
  this.setupMobileMenu(); // Inicializamos el menú móvil
@@ -6158,6 +6364,33 @@ class HeaderEcComponent extends MenuEcComponent {
6158
6364
  });
6159
6365
  });
6160
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
+ }
6161
6394
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeaderEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6162
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 }] });
6163
6396
  }
@@ -6725,6 +6958,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
6725
6958
  class ProductEcComponent {
6726
6959
  injector = inject(Injector);
6727
6960
  routerService = inject(Router);
6961
+ _cartService = inject(CartService);
6962
+ _toastService = inject(ToastService);
6963
+ isAddingToCart = signal(false);
6964
+ quantity = signal(1);
6728
6965
  /**
6729
6966
  * Navega al detalle del producto y sube al inicio de la página
6730
6967
  * @param productId ID del producto
@@ -6797,12 +7034,145 @@ class ProductEcComponent {
6797
7034
  const discount = ((originalPrice - salePrice) / originalPrice) * 100;
6798
7035
  return Math.round(discount);
6799
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
+ }
6800
7170
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ProductEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6801
- 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 }] });
6802
7172
  }
6803
7173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ProductEcComponent, decorators: [{
6804
7174
  type: Component,
6805
- 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>" }]
6806
7176
  }], ctorParameters: () => [], propDecorators: { product: [{
6807
7177
  type: Input,
6808
7178
  args: [{
@@ -7070,8 +7440,14 @@ class BlockNewsletterEcComponent {
7070
7440
  this._toastService.show(this.success_message || success_message);
7071
7441
  this.loading = false;
7072
7442
  }, (err) => {
7073
- this._toastService.error('inquiry-error');
7074
- 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
+ }
7075
7451
  });
7076
7452
  }
7077
7453
  else {
@@ -7376,6 +7752,212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
7376
7752
  args: ['mainCanvas', { static: true }]
7377
7753
  }] } });
7378
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
+
7379
7961
  // export * from './rating-ec/rating-ec.component';
7380
7962
 
7381
7963
  class BlockFormContactEcComponent extends BlockEcComponent {
@@ -7651,6 +8233,7 @@ class LoginFormEcComponent {
7651
8233
  _formBuilder = inject(FormBuilder);
7652
8234
  _toastService = inject(ToastService);
7653
8235
  _router = inject(Router);
8236
+ showPassword = false;
7654
8237
  /**
7655
8238
  * Parametro para indicar si tras loguear
7656
8239
  * debe redireccionar o no.
@@ -7739,6 +8322,9 @@ class LoginFormEcComponent {
7739
8322
  ? resolverFunction()
7740
8323
  : this._router.navigateByUrl(this.redirectTo);
7741
8324
  }
8325
+ togglePassword() {
8326
+ this.showPassword = !this.showPassword;
8327
+ }
7742
8328
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LoginFormEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7743
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"] }] });
7744
8330
  }
@@ -7838,6 +8424,7 @@ class RegisterFormEcComponent {
7838
8424
  _analyticsService = inject(AnalyticsService);
7839
8425
  _formBuilder = inject(FormBuilder);
7840
8426
  channelConfigService = inject(ChannelService);
8427
+ showPassword = false;
7841
8428
  /**
7842
8429
  * Indica si debe redireccionar o se queda en la misma pantalla
7843
8430
  */
@@ -7953,6 +8540,9 @@ class RegisterFormEcComponent {
7953
8540
  this.register_loading = false;
7954
8541
  }
7955
8542
  }
8543
+ togglePassword() {
8544
+ this.showPassword = !this.showPassword;
8545
+ }
7956
8546
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: RegisterFormEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7957
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" }] });
7958
8548
  }
@@ -8107,20 +8697,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
8107
8697
  type: Output
8108
8698
  }] } });
8109
8699
 
8110
- class ComponentHelper {
8111
- constructor() {
8112
- this.ecOnConstruct();
8113
- }
8114
- ecOnInit = (params = {}) => {
8115
- };
8116
- ecOnConstruct = (params = {}) => {
8117
- };
8118
- hasParams = (params, searched) => params && params != null && params.find(param => param['code']?.toLowerCase().includes(searched.toLowerCase())) !== undefined;
8119
- navigateOnRouter(router, url) {
8120
- router.navigateByUrl(`/${url}`);
8121
- }
8122
- }
8123
-
8124
8700
  class PasswordResetEcComponent extends ComponentHelper {
8125
8701
  authService;
8126
8702
  toastr;
@@ -8330,6 +8906,14 @@ class FiltersEcComponent {
8330
8906
  });
8331
8907
  }) ?? false;
8332
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
+ }
8333
8917
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FiltersEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8334
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: [""] });
8335
8919
  }
@@ -8410,6 +8994,7 @@ class ProductDetailEcComponent {
8410
8994
  routeSubscription;
8411
8995
  route = inject(ActivatedRoute);
8412
8996
  currentProductId;
8997
+ _router = inject(Router);
8413
8998
  ngOnDestroy() {
8414
8999
  this.routeSubscription?.unsubscribe();
8415
9000
  }
@@ -8471,11 +9056,12 @@ class ProductDetailEcComponent {
8471
9056
  }
8472
9057
  updateMetaTags(product) {
8473
9058
  const descripcionLimpia = he.decode(product.description || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
8474
- 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);
8475
9061
  this._meta.updateTag({ property: 'og:title', content: product.name || '' });
8476
9062
  this._meta.updateTag({ property: 'og:description', content: descripcionLimpia || '' });
8477
- this._meta.updateTag({ property: 'og:image', content: this._consts.mediaImageUrl(Array.isArray(product.picturesdefault) ? product.picturesdefault[0] : product.picturesdefault) || '' });
8478
- 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 });
8479
9065
  this._meta.updateTag({ property: 'og:type', content: 'product' });
8480
9066
  }
8481
9067
  decodeHtml(html) {
@@ -8497,25 +9083,31 @@ class ProductDetailEcComponent {
8497
9083
  }, 2000);
8498
9084
  }
8499
9085
  plus(stock, multipleQuantity) {
9086
+ const current = Number(this.quantity()); // <-- fuerza a número
8500
9087
  if (multipleQuantity && multipleQuantity > 0) {
8501
- stock ? (this.quantity() < stock
8502
- ? this.quantity.update(value => value + multipleQuantity)
8503
- : this._toastService.show('out-of-stock-actually'))
8504
- : this.quantity.update(value => value + multipleQuantity);
9088
+ stock
9089
+ ? (current < stock
9090
+ ? this.quantity.set(current + multipleQuantity)
9091
+ : this._toastService.show('out-of-stock-actually'))
9092
+ : this.quantity.set(current + multipleQuantity);
8505
9093
  }
8506
9094
  else {
8507
- stock ? (this.quantity() < stock
8508
- ? this.quantity.update(value => value + 1)
8509
- : this._toastService.show('out-of-stock-actually'))
8510
- : this.quantity.update(value => value + 1);
9095
+ stock
9096
+ ? (current < stock
9097
+ ? this.quantity.set(current + 1)
9098
+ : this._toastService.show('out-of-stock-actually'))
9099
+ : this.quantity.set(current + 1);
8511
9100
  }
8512
9101
  }
8513
9102
  less(multipleQuantity) {
9103
+ const current = Number(this.quantity()); // <-- fuerza a número
8514
9104
  if (multipleQuantity && multipleQuantity > 0) {
8515
- this.quantity() > multipleQuantity ? this.quantity.update(value => value - multipleQuantity) : null;
9105
+ current > multipleQuantity
9106
+ ? this.quantity.set(current - multipleQuantity)
9107
+ : null;
8516
9108
  }
8517
9109
  else {
8518
- this.quantity() > 1 ? this.quantity.update(value => value - 1) : null;
9110
+ current > 1 ? this.quantity.set(current - 1) : null;
8519
9111
  }
8520
9112
  }
8521
9113
  checkStock(stock) {
@@ -8621,13 +9213,19 @@ class CartEcComponent {
8621
9213
  isAuthenticated$ = this._authService.isAuthenticated();
8622
9214
  getTotalAmount = this._cartService.getTotalAmount();
8623
9215
  couponCode$ = this._cartService.getCouponCode();
9216
+ hideTaxes = false;
9217
+ injector;
8624
9218
  constructor() {
8625
9219
  //console.log("constructo.....");
9220
+ this.injector = inject(Injector);
8626
9221
  this._channelService.channel$.subscribe((res) => {
8627
9222
  //console.log("construct")
8628
9223
  this.channel = res;
8629
9224
  //this.initializeSteps();
8630
9225
  });
9226
+ this.injector.get(ChannelService).channel$.subscribe(channel => {
9227
+ this.hideTaxes = !!channel.hideTaxes;
9228
+ });
8631
9229
  }
8632
9230
  removeCoupon() {
8633
9231
  // console.log(this.couponCode$)
@@ -8699,6 +9297,7 @@ class CartItemEcComponent {
8699
9297
  _cartService = inject(CartService);
8700
9298
  _toastService = inject(ToastService);
8701
9299
  _constants = inject(CoreConstantsService);
9300
+ parametersService = inject(ParametersService);
8702
9301
  mediaUrl = this._constants.mediaUrl();
8703
9302
  quantity = 0;
8704
9303
  variantsToShow = ['TALLA', 'COLOR'];
@@ -8709,28 +9308,56 @@ class CartItemEcComponent {
8709
9308
  // console.log(this.item, this.mediaUrl);
8710
9309
  }
8711
9310
  updateQuantity(stock) {
9311
+ const originalQuantity = this.item.quantity; // Guardar cantidad original
8712
9312
  if (this.quantity > 0 && this.quantity <= stock) {
8713
- 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
+ }
8714
9318
  }
8715
9319
  else {
8716
- this.quantity = this.item.quantity;
9320
+ // Restaurar cantidad original si está fuera de rango
9321
+ this.quantity = originalQuantity;
8717
9322
  this._toastService.show('out-of-stock-actually');
8718
9323
  }
8719
9324
  }
8720
- less(stock, value = 1) {
9325
+ plus(stock, value = 1) {
8721
9326
  if (this.isQuantityUpdating)
8722
9327
  return;
8723
9328
  this.isQuantityUpdating = true;
8724
- let quantity = this.item.quantity - value;
8725
- 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()
8726
9342
  setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
8727
9343
  }
8728
- plus(stock, value = 1) {
9344
+ less(stock, value = 1) {
8729
9345
  if (this.isQuantityUpdating)
8730
9346
  return;
8731
9347
  this.isQuantityUpdating = true;
8732
- let quantity = this.item.quantity + value;
8733
- 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()
8734
9361
  setTimeout(() => { this.isQuantityUpdating = false; }, 1000);
8735
9362
  }
8736
9363
  deleteCartItem() {
@@ -8785,6 +9412,9 @@ class CartItemEcComponent {
8785
9412
  }
8786
9413
  return false; // Solo se ejecuta si no se cumple la condición
8787
9414
  }
9415
+ // PARAMETROS
9416
+ parameters$ = this.parametersService.getParameters();
9417
+ hasParams = this.parametersService.hasParams;
8788
9418
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CartItemEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8789
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"] }] });
8790
9420
  }
@@ -9296,108 +9926,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
9296
9926
  class MpRedirectEcComponent {
9297
9927
  _paymentService = inject(PaymentService);
9298
9928
  _toastService = inject(ToastService);
9929
+ platformId = inject(PLATFORM_ID);
9930
+ finished = false;
9299
9931
  method = null;
9300
9932
  total_amount = 0;
9301
9933
  allData;
9302
9934
  ready = new EventEmitter();
9303
9935
  preference;
9304
- loading = false;
9305
9936
  url;
9306
- closeModal = '';
9307
- ventana;
9308
- window;
9309
- localStorage;
9310
- platformId = inject(PLATFORM_ID);
9311
- 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() {
9312
9949
  if (isPlatformBrowser(this.platformId)) {
9313
- this.window = window;
9314
- 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);
9315
9957
  }
9316
- }
9317
- ngOnInit() {
9318
9958
  this.getPreference();
9319
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". */
9320
9970
  clickClose = () => {
9321
- /* this.closeModal = 'cancel'
9322
- this.ventana?.close() */
9971
+ if (this.finished)
9972
+ return;
9973
+ this.finishWithState('cancel');
9323
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
+ */
9324
9980
  iniciar = () => {
9325
- this.closeModal = '';
9326
- this.clearStorageState();
9327
- this.ventana = this.window?.open(this.url);
9328
- this.callState();
9329
- };
9330
- callState = () => {
9331
- let state = this.closeModal != '' ? this.closeModal : this.localStorage?.getItem('state');
9332
- !state && this.ventana.closed && (state = 'cancel');
9333
- this.loading = true;
9334
- state && console.log(state);
9335
- if (state) {
9336
- this.loading = false;
9337
- this.localStorage?.removeItem('state');
9338
- if (state == 'success') {
9339
- this.ventana?.close();
9340
- this.ready.emit(true);
9341
- return;
9342
- }
9343
- if (state == 'pending') {
9344
- this.ventana?.close();
9345
- this.ready.emit(true);
9346
- return;
9347
- }
9348
- if (state == 'failure') {
9349
- this.ventana?.close();
9350
- this.processError('');
9351
- return;
9352
- }
9353
- if (state == 'cancel') {
9354
- this.ventana?.close();
9355
- 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.');
9356
10003
  return;
9357
10004
  }
9358
- this.ventana?.close();
9359
- 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)
9360
10014
  return;
10015
+ if (e.key === `mp:state:${this.sid}` && e.newValue) {
10016
+ const state = e.newValue;
10017
+ this.finishWithState(state);
9361
10018
  }
9362
- setTimeout(() => {
9363
- this.callState();
9364
- }, 5000);
9365
10019
  };
9366
- processError = (err) => {
9367
- this._toastService.show(err != '' ? err : 'payment-error');
9368
- // 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);
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');
9369
10068
  };
10069
+ /** Limpia posibles residuos de estado en storages. */
9370
10070
  clearStorageState = () => {
9371
- sessionStorage.removeItem('state');
9372
- 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'));
9373
10085
  };
9374
- getPreference = () => this._paymentService.getPreference(this.allData).then(res => {
9375
- this.preference = res;
9376
- this.url = this.preference.init_point;
9377
- this.renderMP(this.preference);
9378
- }, err => this.setError('operation-error'));
9379
10086
  setError = (message) => {
9380
10087
  //this.error = message;
9381
10088
  };
9382
- renderMP = (preference) => {
9383
- this.window?.addEventListener("message", (event) => {
9384
- if (event.origin !== 'https://www.mercadopago.com.ar' || !event.data.type) {
9385
- return;
9386
- }
9387
- let dataType = event.data.type;
9388
- if (dataType === 'submit') {
9389
- const paymentData = event.data.value;
9390
- return;
9391
- }
9392
- });
9393
- };
10089
+ renderMP = (_pref) => { };
9394
10090
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MpRedirectEcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9395
- 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" }] });
9396
10092
  }
9397
10093
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MpRedirectEcComponent, decorators: [{
9398
10094
  type: Component,
9399
- 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>" }]
9400
- }], 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: [{
9401
10097
  type: Input
9402
10098
  }], total_amount: [{
9403
10099
  type: Input
@@ -9640,11 +10336,11 @@ class DecidirEcComponent extends ComponentHelper {
9640
10336
  obj.style.width = obj.contentWindow.document.body.scrollWidth + 'px';
9641
10337
  };
9642
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 });
9643
- 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" }] });
9644
10340
  }
9645
10341
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DecidirEcComponent, decorators: [{
9646
10342
  type: Component,
9647
- 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"] }]
9648
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: [{
9649
10345
  type: Input
9650
10346
  }], method: [{
@@ -11515,5 +12211,5 @@ const directives = [
11515
12211
  * Generated bundle index. Do not edit.
11516
12212
  */
11517
12213
 
11518
- 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 };
11519
12215
  //# sourceMappingURL=ng-easycommerce-v18.mjs.map