tickera-angular-components 0.0.1-dev.164 → 0.0.1-dev.166

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.
@@ -11,7 +11,7 @@ import { TippyDirective } from '@ngneat/helipopper';
11
11
  export { TippyDirective } from '@ngneat/helipopper';
12
12
  import * as i1 from '@angular/common/http';
13
13
  import { HttpHeaders } from '@angular/common/http';
14
- import { catchError, distinctUntilChanged, debounceTime, switchMap, takeUntil, map } from 'rxjs/operators';
14
+ import { catchError, map, tap, distinctUntilChanged, debounceTime, switchMap, takeUntil } from 'rxjs/operators';
15
15
  import { toast } from 'ngx-sonner';
16
16
  import { toObservable } from '@angular/core/rxjs-interop';
17
17
  import * as i3$1 from '@angular/router';
@@ -1718,7 +1718,11 @@ function processElementsToRenderData(elements, tickets, priceZones) {
1718
1718
  elementName,
1719
1719
  seatPositions,
1720
1720
  chairPositions,
1721
- zoneCapacity: element.total_capacity,
1721
+ // Disponibilidad real de la zona (no la capacidad total), para que el
1722
+ // tope de selección refleje lo que realmente queda por vender.
1723
+ zoneCapacity: element.type === RoomMapElementType.ZONE
1724
+ ? elementTickets.filter((t) => t.status === PerformanceTicketStatus.AVAILABLE).length
1725
+ : element.total_capacity,
1722
1726
  });
1723
1727
  }
1724
1728
  return result;
@@ -3444,6 +3448,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3444
3448
  args: [{ providedIn: 'root' }]
3445
3449
  }] });
3446
3450
 
3451
+ const MAX_TICKETS_SETTING_KEY = 'max_tickets_per_purchase';
3452
+ /**
3453
+ * Tope de entradas por selección/compra. Por defecto es `null` (sin límite),
3454
+ * que es lo que necesita el backoffice (admin puede seleccionar todo).
3455
+ * El ecommerce, que sí debe limitar, llama a `load()` una vez al iniciar la
3456
+ * app para traer el valor configurado en el formulario de personalización.
3457
+ */
3458
+ class PurchaseLimitService {
3459
+ apiService;
3460
+ _maxTicketsPerSelection = signal(null, ...(ngDevMode ? [{ debugName: "_maxTicketsPerSelection" }] : []));
3461
+ maxTicketsPerSelection = this._maxTicketsPerSelection.asReadonly();
3462
+ constructor(apiService) {
3463
+ this.apiService = apiService;
3464
+ }
3465
+ setMaxTicketsPerSelection(max) {
3466
+ this._maxTicketsPerSelection.set(max && max > 0 ? max : null);
3467
+ }
3468
+ load() {
3469
+ return this.apiService.get('/settings?category=personalization').pipe(map(({ data }) => {
3470
+ const setting = data?.find((s) => s.key === MAX_TICKETS_SETTING_KEY);
3471
+ const parsed = setting?.value ? parseInt(setting.value, 10) : NaN;
3472
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
3473
+ }), tap((max) => this.setMaxTicketsPerSelection(max)), catchError(() => {
3474
+ // Si falla la carga, no bloqueamos la compra: queda sin límite hasta
3475
+ // que se pueda leer el setting correctamente.
3476
+ this.setMaxTicketsPerSelection(null);
3477
+ return of(null);
3478
+ }));
3479
+ }
3480
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: PurchaseLimitService, deps: [{ token: ApiService }], target: i0.ɵɵFactoryTarget.Injectable });
3481
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: PurchaseLimitService, providedIn: 'root' });
3482
+ }
3483
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: PurchaseLimitService, decorators: [{
3484
+ type: Injectable,
3485
+ args: [{ providedIn: 'root' }]
3486
+ }], ctorParameters: () => [{ type: ApiService }] });
3487
+
3447
3488
  class OrderTransactionDetailsModalService {
3448
3489
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
3449
3490
  transaction = signal(null, ...(ngDevMode ? [{ debugName: "transaction" }] : []));
@@ -3684,6 +3725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3684
3725
  class TicketSelectionService {
3685
3726
  bookingDataService = inject(PerformanceBookingDataService);
3686
3727
  feedbackModalService = inject(FeedbackModalService);
3728
+ purchaseLimitService = inject(PurchaseLimitService);
3687
3729
  selectionDetailsService = inject(TicketSelectionDetailsService);
3688
3730
  selectionDiscountService = inject(TicketSelectionDiscountService);
3689
3731
  _zoneQuantities = signal(new Map(), ...(ngDevMode ? [{ debugName: "_zoneQuantities" }] : []));
@@ -3705,6 +3747,27 @@ class TicketSelectionService {
3705
3747
  selectedTicketIds = computed(() => this._selectedTickets(), ...(ngDevMode ? [{ debugName: "selectedTicketIds" }] : []));
3706
3748
  selectedBlockedTicketIds = computed(() => this.selectedBlockedTickets().map((t) => t.id), ...(ngDevMode ? [{ debugName: "selectedBlockedTicketIds" }] : []));
3707
3749
  selectedTicketIdList = computed(() => this.selectedTickets().map((t) => t.id), ...(ngDevMode ? [{ debugName: "selectedTicketIdList" }] : []));
3750
+ // Agrupa los tickets seleccionados de una "Zona de público" en un solo item
3751
+ // (con element_qty) para el resumen de selección; asientos y mesas se listan
3752
+ // individualmente, igual que antes.
3753
+ summaryItems = computed(() => {
3754
+ const zoneGroups = new Map();
3755
+ const result = [];
3756
+ for (const ticket of this.selectedTickets()) {
3757
+ if (ticket.element_type === RoomMapElementType.ZONE) {
3758
+ const group = zoneGroups.get(ticket.room_map_element_id) ?? [];
3759
+ group.push(ticket);
3760
+ zoneGroups.set(ticket.room_map_element_id, group);
3761
+ }
3762
+ else {
3763
+ result.push(ticket);
3764
+ }
3765
+ }
3766
+ for (const group of zoneGroups.values()) {
3767
+ result.push({ ...group[0], element_qty: group.length });
3768
+ }
3769
+ return result;
3770
+ }, ...(ngDevMode ? [{ debugName: "summaryItems" }] : []));
3708
3771
  products = computed(() => this._products(), ...(ngDevMode ? [{ debugName: "products" }] : []));
3709
3772
  productQuantities = computed(() => this._productQuantities(), ...(ngDevMode ? [{ debugName: "productQuantities" }] : []));
3710
3773
  selectedProductEntries = computed(() => {
@@ -3755,28 +3818,39 @@ class TicketSelectionService {
3755
3818
  }
3756
3819
  toggle(ticket) {
3757
3820
  const list = this.getListByStatus(ticket);
3758
- if (list) {
3759
- list.update((ids) => {
3760
- const next = new Set(ids);
3761
- if (next.has(ticket)) {
3762
- next.delete(ticket);
3763
- }
3764
- else {
3765
- next.add(ticket);
3766
- }
3767
- return next;
3768
- });
3821
+ if (!list)
3822
+ return;
3823
+ const willAdd = !list().has(ticket);
3824
+ if (list === this._selectedTickets && willAdd && !this.canAddMoreToSelection(1)) {
3825
+ this.notifyPurchaseLimitReached();
3826
+ return;
3769
3827
  }
3828
+ list.update((ids) => {
3829
+ const next = new Set(ids);
3830
+ if (next.has(ticket)) {
3831
+ next.delete(ticket);
3832
+ }
3833
+ else {
3834
+ next.add(ticket);
3835
+ }
3836
+ return next;
3837
+ });
3770
3838
  }
3771
3839
  select(ticket) {
3772
3840
  const list = this.getListByStatus(ticket);
3773
- if (list) {
3774
- list.update((ids) => {
3775
- const next = new Set(ids);
3776
- next.add(ticket);
3777
- return next;
3778
- });
3841
+ if (!list)
3842
+ return;
3843
+ if (list === this._selectedTickets &&
3844
+ !list().has(ticket) &&
3845
+ !this.canAddMoreToSelection(1)) {
3846
+ this.notifyPurchaseLimitReached();
3847
+ return;
3779
3848
  }
3849
+ list.update((ids) => {
3850
+ const next = new Set(ids);
3851
+ next.add(ticket);
3852
+ return next;
3853
+ });
3780
3854
  }
3781
3855
  deselect(ticket) {
3782
3856
  const list = this.getListByStatus(ticket);
@@ -3801,16 +3875,46 @@ class TicketSelectionService {
3801
3875
  isSelected(ticket) {
3802
3876
  return this._selectedTickets().has(ticket);
3803
3877
  }
3878
+ // Se llama tanto al hacer click en el mapa como al pintar el resumen, así que
3879
+ // sincroniza siempre el tope con la disponibilidad más reciente, preservando
3880
+ // la cantidad ya seleccionada.
3804
3881
  initZoneQuantity(elementId, maxCapacity) {
3805
3882
  this._zoneQuantities.update((map) => {
3806
3883
  const next = new Map(map);
3807
- if (!next.has(elementId)) {
3808
- next.set(elementId, { elementId, selectedCount: 0, maxCapacity });
3809
- }
3884
+ const existing = next.get(elementId);
3885
+ next.set(elementId, {
3886
+ elementId,
3887
+ selectedCount: existing?.selectedCount ?? 0,
3888
+ maxCapacity,
3889
+ });
3810
3890
  return next;
3811
3891
  });
3812
3892
  }
3893
+ getZoneMax(elementId) {
3894
+ return this._zoneQuantities().get(elementId)?.maxCapacity ?? 0;
3895
+ }
3896
+ getZoneSelectedCount(elementId) {
3897
+ return this._zoneQuantities().get(elementId)?.selectedCount ?? 0;
3898
+ }
3899
+ // Fija la cantidad seleccionada de una zona a un valor absoluto (lo que emite
3900
+ // el number-input del resumen), reutilizando el clamp/validación de disponibilidad
3901
+ // que ya hace adjustZoneQuantity en vez de duplicar esa lógica.
3902
+ setZoneQuantity(elementId, target) {
3903
+ const current = this.getZoneSelectedCount(elementId);
3904
+ const delta = Math.max(0, Math.trunc(target)) - current;
3905
+ if (delta !== 0) {
3906
+ this.adjustZoneQuantity(elementId, delta);
3907
+ }
3908
+ }
3813
3909
  adjustZoneQuantity(elementId, delta) {
3910
+ if (delta > 0) {
3911
+ const allowance = this.remainingSelectionAllowance();
3912
+ if (allowance <= 0) {
3913
+ this.notifyPurchaseLimitReached();
3914
+ return;
3915
+ }
3916
+ delta = Math.min(delta, allowance);
3917
+ }
3814
3918
  const zoneTickets = this._allTickets()
3815
3919
  .filter((t) => t.room_map_element_id === elementId)
3816
3920
  .sort((a, b) => ((a.seat_label || '') < (b.seat_label || '') ? -1 : 1));
@@ -3840,6 +3944,30 @@ class TicketSelectionService {
3840
3944
  return next;
3841
3945
  });
3842
3946
  }
3947
+ // --- Tope de entradas por compra (solo activo si PurchaseLimitService tiene
3948
+ // un máximo cargado; el backoffice nunca lo carga, así que queda ilimitado) ---
3949
+ remainingSelectionAllowance() {
3950
+ const max = this.purchaseLimitService.maxTicketsPerSelection();
3951
+ if (max === null)
3952
+ return Infinity;
3953
+ return Math.max(0, max - this.selectedCount());
3954
+ }
3955
+ canAddMoreToSelection(additional) {
3956
+ return additional <= this.remainingSelectionAllowance();
3957
+ }
3958
+ notifyPurchaseLimitReached() {
3959
+ const max = this.purchaseLimitService.maxTicketsPerSelection();
3960
+ this.feedbackModalService.addModal({
3961
+ id: 'purchase-limit-reached',
3962
+ type: 'warning',
3963
+ title: 'Límite de entradas por compra',
3964
+ message: max != null
3965
+ ? `Puedes seleccionar hasta ${max} entradas por compra. Si necesitas más, contáctanos para coordinar tu compra directamente con el teatro.`
3966
+ : 'Has alcanzado el máximo de entradas permitido para esta compra.',
3967
+ showCloseButton: true,
3968
+ isOpen: signal(true),
3969
+ });
3970
+ }
3843
3971
  getZoneTickets(elementId) {
3844
3972
  return this._allTickets()
3845
3973
  .filter((t) => t.room_map_element_id === elementId)
@@ -9072,15 +9200,32 @@ class SeatSelectionSummaryItemComponent {
9072
9200
  this.selectionService = selectionService;
9073
9201
  }
9074
9202
  getItemTypeIcon = getItemTypeIcon;
9203
+ // Las "Zonas de público" se compran por cantidad (varios tickets agrupados en
9204
+ // un solo item con element_qty); asientos y mesas siguen siendo selección 1:1.
9205
+ get isZone() {
9206
+ return this.item.element_type === RoomMapElementType.ZONE;
9207
+ }
9208
+ get zoneMax() {
9209
+ return this.selectionService.getZoneMax(this.item.room_map_element_id);
9210
+ }
9211
+ onZoneQuantityChange(value) {
9212
+ if (value === null)
9213
+ return;
9214
+ this.selectionService.setZoneQuantity(this.item.room_map_element_id, value);
9215
+ }
9075
9216
  removeItem(ticket) {
9217
+ if (this.isZone) {
9218
+ this.selectionService.setZoneQuantity(ticket.room_map_element_id, 0);
9219
+ return;
9220
+ }
9076
9221
  this.selectionService.deselect(ticket);
9077
9222
  }
9078
9223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SeatSelectionSummaryItemComponent, deps: [{ token: TicketSelectionService }], target: i0.ɵɵFactoryTarget.Component });
9079
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SeatSelectionSummaryItemComponent, isStandalone: true, selector: "seat-selection-summary-item", inputs: { item: "item" }, ngImport: i0, template: "<div class=\"seat-selection-summary-item\">\n <div class=\"seat-selection-summary-item__icon\">\n {{ getItemTypeIcon(item) }}\n </div>\n\n <div class=\"seat-selection-summary-item__content\">\n <div class=\"seat-selection-summary-item__content-label\">{{ item.seat_label }}</div>\n\n <div class=\"seat-selection-summary-item__content-price\">\n ${{ item.normal_price | number: '1.0' }}\n </div>\n\n @if (item.element_qty && item.element_qty > 1) {\n <div class=\"seat-selection-summary-item__content-quantity\">\n Cantidad: {{ item.element_qty }}\n </div>\n }\n </div>\n\n <div class=\"seat-selection-summary-item__actions\">\n <app-button\n variant=\"transparent\"\n size=\"xs\"\n text=\"\"\n icon=\"ri-close-line\"\n (clicked)=\"removeItem(item)\"\n />\n </div>\n</div>\n", styles: [".seat-selection-summary-item{align-items:center;display:flex;flex-flow:row nowrap;gap:.75rem;justify-content:flex-start;border-bottom:1px solid #e5e7eb;padding:.75rem 0;transition:background-color .2s ease}.seat-selection-summary-item:last-child{border-bottom:none}.seat-selection-summary-item:hover{background-color:#f9fafb}.seat-selection-summary-item__icon{align-items:center;border:1px solid #e5e7eb;border-radius:.25rem;display:flex;font-size:1.5rem;height:44px;justify-content:center;padding:.25rem;width:44px}.seat-selection-summary-item__content{display:flex;flex-direction:column;gap:.25rem}.seat-selection-summary-item__content-label{font-size:.75rem;font-weight:500;color:#6b7280}.seat-selection-summary-item__content-price{font-size:.875rem;font-weight:600;color:#111827}.seat-selection-summary-item__content-quantity{font-size:.75rem;color:#6b7280}.seat-selection-summary-item__actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AppButtonComponent, selector: "app-button", inputs: ["disabled", "loading", "type", "variant", "text", "size", "loadingText", "icon", "tooltip"], outputs: ["clicked"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }] });
9224
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SeatSelectionSummaryItemComponent, isStandalone: true, selector: "seat-selection-summary-item", inputs: { item: "item" }, ngImport: i0, template: "<div class=\"seat-selection-summary-item\">\n <div class=\"seat-selection-summary-item__icon\">\n {{ getItemTypeIcon(item) }}\n </div>\n\n <div class=\"seat-selection-summary-item__content\">\n <div class=\"seat-selection-summary-item__content-label\">{{ item.seat_label }}</div>\n\n <div class=\"seat-selection-summary-item__content-price\">\n ${{ item.normal_price | number: '1.0' }}\n </div>\n </div>\n\n <div class=\"seat-selection-summary-item__actions\">\n @if (isZone) {\n <number-input\n name=\"zone-quantity\"\n [ngModel]=\"item.element_qty ?? 1\"\n (ngModelChange)=\"onZoneQuantityChange($event)\"\n [min]=\"0\"\n [max]=\"zoneMax\"\n [step]=\"1\"\n fieldGroupClass=\"seat-selection-summary-item__qty-input\"\n />\n }\n\n <app-button\n variant=\"transparent\"\n size=\"xs\"\n text=\"\"\n icon=\"ri-close-line\"\n (clicked)=\"removeItem(item)\"\n />\n </div>\n</div>\n", styles: [".seat-selection-summary-item{align-items:center;display:flex;flex-flow:row nowrap;gap:.75rem;justify-content:flex-start;border-bottom:1px solid #e5e7eb;padding:.75rem 0;transition:background-color .2s ease}.seat-selection-summary-item:last-child{border-bottom:none}.seat-selection-summary-item:hover{background-color:#f9fafb}.seat-selection-summary-item__icon{align-items:center;border:1px solid #e5e7eb;border-radius:.25rem;display:flex;font-size:1.5rem;height:44px;justify-content:center;padding:.25rem;width:44px}.seat-selection-summary-item__content{display:flex;flex-direction:column;gap:.25rem}.seat-selection-summary-item__content-label{font-size:.75rem;font-weight:500;color:#6b7280}.seat-selection-summary-item__content-price{font-size:.875rem;font-weight:600;color:#111827}.seat-selection-summary-item__content-quantity{font-size:.75rem;color:#6b7280}.seat-selection-summary-item__actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}.seat-selection-summary-item__qty-input{margin-right:.25rem;width:6.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AppButtonComponent, selector: "app-button", inputs: ["disabled", "loading", "type", "variant", "text", "size", "loadingText", "icon", "tooltip"], outputs: ["clicked"] }, { kind: "component", type: NumberInputComponent, selector: "number-input", inputs: ["label", "name", "id", "placeholder", "required", "readonly", "min", "max", "step", "fieldGroupClass"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }] });
9080
9225
  }
9081
9226
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SeatSelectionSummaryItemComponent, decorators: [{
9082
9227
  type: Component,
9083
- args: [{ selector: 'seat-selection-summary-item', standalone: true, imports: [CommonModule, AppButtonComponent], template: "<div class=\"seat-selection-summary-item\">\n <div class=\"seat-selection-summary-item__icon\">\n {{ getItemTypeIcon(item) }}\n </div>\n\n <div class=\"seat-selection-summary-item__content\">\n <div class=\"seat-selection-summary-item__content-label\">{{ item.seat_label }}</div>\n\n <div class=\"seat-selection-summary-item__content-price\">\n ${{ item.normal_price | number: '1.0' }}\n </div>\n\n @if (item.element_qty && item.element_qty > 1) {\n <div class=\"seat-selection-summary-item__content-quantity\">\n Cantidad: {{ item.element_qty }}\n </div>\n }\n </div>\n\n <div class=\"seat-selection-summary-item__actions\">\n <app-button\n variant=\"transparent\"\n size=\"xs\"\n text=\"\"\n icon=\"ri-close-line\"\n (clicked)=\"removeItem(item)\"\n />\n </div>\n</div>\n", styles: [".seat-selection-summary-item{align-items:center;display:flex;flex-flow:row nowrap;gap:.75rem;justify-content:flex-start;border-bottom:1px solid #e5e7eb;padding:.75rem 0;transition:background-color .2s ease}.seat-selection-summary-item:last-child{border-bottom:none}.seat-selection-summary-item:hover{background-color:#f9fafb}.seat-selection-summary-item__icon{align-items:center;border:1px solid #e5e7eb;border-radius:.25rem;display:flex;font-size:1.5rem;height:44px;justify-content:center;padding:.25rem;width:44px}.seat-selection-summary-item__content{display:flex;flex-direction:column;gap:.25rem}.seat-selection-summary-item__content-label{font-size:.75rem;font-weight:500;color:#6b7280}.seat-selection-summary-item__content-price{font-size:.875rem;font-weight:600;color:#111827}.seat-selection-summary-item__content-quantity{font-size:.75rem;color:#6b7280}.seat-selection-summary-item__actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}\n"] }]
9228
+ args: [{ selector: 'seat-selection-summary-item', standalone: true, imports: [CommonModule, FormsModule, AppButtonComponent, NumberInputComponent], template: "<div class=\"seat-selection-summary-item\">\n <div class=\"seat-selection-summary-item__icon\">\n {{ getItemTypeIcon(item) }}\n </div>\n\n <div class=\"seat-selection-summary-item__content\">\n <div class=\"seat-selection-summary-item__content-label\">{{ item.seat_label }}</div>\n\n <div class=\"seat-selection-summary-item__content-price\">\n ${{ item.normal_price | number: '1.0' }}\n </div>\n </div>\n\n <div class=\"seat-selection-summary-item__actions\">\n @if (isZone) {\n <number-input\n name=\"zone-quantity\"\n [ngModel]=\"item.element_qty ?? 1\"\n (ngModelChange)=\"onZoneQuantityChange($event)\"\n [min]=\"0\"\n [max]=\"zoneMax\"\n [step]=\"1\"\n fieldGroupClass=\"seat-selection-summary-item__qty-input\"\n />\n }\n\n <app-button\n variant=\"transparent\"\n size=\"xs\"\n text=\"\"\n icon=\"ri-close-line\"\n (clicked)=\"removeItem(item)\"\n />\n </div>\n</div>\n", styles: [".seat-selection-summary-item{align-items:center;display:flex;flex-flow:row nowrap;gap:.75rem;justify-content:flex-start;border-bottom:1px solid #e5e7eb;padding:.75rem 0;transition:background-color .2s ease}.seat-selection-summary-item:last-child{border-bottom:none}.seat-selection-summary-item:hover{background-color:#f9fafb}.seat-selection-summary-item__icon{align-items:center;border:1px solid #e5e7eb;border-radius:.25rem;display:flex;font-size:1.5rem;height:44px;justify-content:center;padding:.25rem;width:44px}.seat-selection-summary-item__content{display:flex;flex-direction:column;gap:.25rem}.seat-selection-summary-item__content-label{font-size:.75rem;font-weight:500;color:#6b7280}.seat-selection-summary-item__content-price{font-size:.875rem;font-weight:600;color:#111827}.seat-selection-summary-item__content-quantity{font-size:.75rem;color:#6b7280}.seat-selection-summary-item__actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}.seat-selection-summary-item__qty-input{margin-right:.25rem;width:6.5rem}\n"] }]
9084
9229
  }], ctorParameters: () => [{ type: TicketSelectionService }], propDecorators: { item: [{
9085
9230
  type: Input,
9086
9231
  args: [{ required: true }]
@@ -9093,8 +9238,15 @@ class SeatSelectionSummaryComponent {
9093
9238
  constructor(selectionService) {
9094
9239
  this.selectionService = selectionService;
9095
9240
  }
9241
+ // Un item de zona representa un grupo (puede cambiar de ticket representante
9242
+ // al variar la cantidad), así que se trackea por elemento y no por ticket.id.
9243
+ trackByItem(item) {
9244
+ return item.element_type === RoomMapElementType.ZONE
9245
+ ? `zone-${item.room_map_element_id}`
9246
+ : item.id;
9247
+ }
9096
9248
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SeatSelectionSummaryComponent, deps: [{ token: TicketSelectionService }], target: i0.ɵɵFactoryTarget.Component });
9097
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SeatSelectionSummaryComponent, isStandalone: true, selector: "seat-selection-summary", inputs: { title: "title", clearButtonText: "clearButtonText" }, ngImport: i0, template: "<ticket-map-widget>\n <ticket-map-widget-header [title]=\"title\" />\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-list\">\n @for (item of selectionService.selectedTickets(); track item.id) {\n <seat-selection-summary-item [item]=\"item\" />\n }\n </div>\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-actions\">\n <app-button\n size=\"xs\"\n [text]=\"clearButtonText\"\n icon=\"ri-delete-bin-line\"\n (clicked)=\"selectionService.clearSelection()\"\n />\n </div>\n }\n } @else {\n <seat-selection-empty-summary />\n }\n</ticket-map-widget>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AppButtonComponent, selector: "app-button", inputs: ["disabled", "loading", "type", "variant", "text", "size", "loadingText", "icon", "tooltip"], outputs: ["clicked"] }, { kind: "component", type: SeatSelectionEmptySummaryComponent, selector: "seat-selection-empty-summary" }, { kind: "component", type: SeatSelectionSummaryItemComponent, selector: "seat-selection-summary-item", inputs: ["item"] }, { kind: "component", type: TicketMapWidgetComponent, selector: "ticket-map-widget" }, { kind: "component", type: TicketMapWidgetHeaderComponent, selector: "ticket-map-widget-header", inputs: ["title"] }] });
9249
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SeatSelectionSummaryComponent, isStandalone: true, selector: "seat-selection-summary", inputs: { title: "title", clearButtonText: "clearButtonText" }, ngImport: i0, template: "<ticket-map-widget>\n <ticket-map-widget-header [title]=\"title\" />\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-list\">\n @for (item of selectionService.summaryItems(); track trackByItem(item)) {\n <seat-selection-summary-item [item]=\"item\" />\n }\n </div>\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-actions\">\n <app-button\n size=\"xs\"\n [text]=\"clearButtonText\"\n icon=\"ri-delete-bin-line\"\n (clicked)=\"selectionService.clearSelection()\"\n />\n </div>\n }\n } @else {\n <seat-selection-empty-summary />\n }\n</ticket-map-widget>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AppButtonComponent, selector: "app-button", inputs: ["disabled", "loading", "type", "variant", "text", "size", "loadingText", "icon", "tooltip"], outputs: ["clicked"] }, { kind: "component", type: SeatSelectionEmptySummaryComponent, selector: "seat-selection-empty-summary" }, { kind: "component", type: SeatSelectionSummaryItemComponent, selector: "seat-selection-summary-item", inputs: ["item"] }, { kind: "component", type: TicketMapWidgetComponent, selector: "ticket-map-widget" }, { kind: "component", type: TicketMapWidgetHeaderComponent, selector: "ticket-map-widget-header", inputs: ["title"] }] });
9098
9250
  }
9099
9251
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SeatSelectionSummaryComponent, decorators: [{
9100
9252
  type: Component,
@@ -9105,7 +9257,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9105
9257
  SeatSelectionSummaryItemComponent,
9106
9258
  TicketMapWidgetComponent,
9107
9259
  TicketMapWidgetHeaderComponent,
9108
- ], template: "<ticket-map-widget>\n <ticket-map-widget-header [title]=\"title\" />\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-list\">\n @for (item of selectionService.selectedTickets(); track item.id) {\n <seat-selection-summary-item [item]=\"item\" />\n }\n </div>\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-actions\">\n <app-button\n size=\"xs\"\n [text]=\"clearButtonText\"\n icon=\"ri-delete-bin-line\"\n (clicked)=\"selectionService.clearSelection()\"\n />\n </div>\n }\n } @else {\n <seat-selection-empty-summary />\n }\n</ticket-map-widget>\n" }]
9260
+ ], template: "<ticket-map-widget>\n <ticket-map-widget-header [title]=\"title\" />\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-list\">\n @for (item of selectionService.summaryItems(); track trackByItem(item)) {\n <seat-selection-summary-item [item]=\"item\" />\n }\n </div>\n\n @if (selectionService.selectedCount() > 0) {\n <div class=\"tickets-actions\">\n <app-button\n size=\"xs\"\n [text]=\"clearButtonText\"\n icon=\"ri-delete-bin-line\"\n (clicked)=\"selectionService.clearSelection()\"\n />\n </div>\n }\n } @else {\n <seat-selection-empty-summary />\n }\n</ticket-map-widget>\n" }]
9109
9261
  }], ctorParameters: () => [{ type: TicketSelectionService }], propDecorators: { title: [{
9110
9262
  type: Input
9111
9263
  }], clearButtonText: [{
@@ -9864,5 +10016,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
9864
10016
  * Generated bundle index. Do not edit.
9865
10017
  */
9866
10018
 
9867
- export { ADMIN_API_ROUTES, ALERT_ICONS, AdminSelectComponent, AdminService, ApiService, AppAlertComponent, AppBadgeComponent, AppBreadcumbComponent, AppButtonComponent, AppLinkButtonComponent, AppModalComponent, AsyncSelectComponent, AuditInformationComponent, BASE_BUTTON_CLASSES, BASE_INPUT_CLASSES, BUTTON_SIZES_CLASSES, BUTTON_VARIANT_CLASSES, BaseModalService, CITY_API_ENDPOINTS, COUNTRY_API_ENDPOINTS, CUSTOMERS_API_ROUTES, ChangePasswordFormComponent, ChangePasswordFormService, CitiesService, CitySelectComponent, ColorPickerComponent, ConfirmationOrderModalComponent, ConfirmationOrderModalService, CorporateBondStatus, CountrySelectComponent, CountryService, CouponApplicability, CouponDiscountType, CouponStatus, CreatorType, CustomerSelectComponent, CustomerService, DEFAULT_STAGE_CONFIG, DOCUMENT_TYPES_OPTIONS, DateInputComponent, DateService, DaySelectorGridComponent, DeleteConfirmationComponent, DeleteConfirmationService, DynamicTableComponent, ERROR_INPUT_CLASSES, EndDateGreaterThanStartValidator, FORM_ERROR_MESSAGES, FeedbackModalComponent, FeedbackModalService, FileType, FileUploadComponent, FileUploadPreviewComponent, FormEditorComponent, FormInputComponent, FormSelectComponent, FormTextareaComponent, GiftBondPurchaseStatus, GiftBondStatus, KONVA_SHAPE_MAPPINGS, LanguageSwitcherComponent, MembershipStatus, MinTodayValidator, MustMatchValidator, NumberInputComponent, ORDER_DISCOUNT_COLORS, ORDER_ITEM_TYPES_COLORS, ORDER_STATUS_COLORS, OrderBillingInfoComponent, OrderDiscountAppliedComponent, OrderInformationComponent, OrderItemType, OrderItemTypeBadgeComponent, OrderItemsComponent, OrderStatus, OrderStatusBadgeComponent, OrderTransactionDetailsModalComponent, OrderTransactionDetailsModalService, OrderTransactionsComponent, PAYMENT_METHODS_OPTIONS, PERFORMANCES_API_ROUTES, PERFORMANCE_STATUS_COLORS, PERFORMANCE_TICKET_STATUS_COLORS, PHONE_COUNTRIES, PRICE_ZONES_API_ROUTES, PRODUCTS_API_ROUTES, PRODUCT_CATEGORIES_API_ROUTES, PRODUCT_TAGS_API_ROUTES, PRODUCT_TYPES_API_ROUTES, PaymentMethod, PerformanceBookingDataService, PerformanceCardComponent, PerformanceCardListComponent, PerformanceMultiSelectComponent, PerformanceSelectComponent, PerformanceService, PerformanceStatus, PerformanceStatusBadgeComponent, PerformanceStepperComponent, PerformanceTicketMapComponent, PerformanceTicketStatus, PerformanceTicketStatusBadgeComponent, PerformancesListEventsService, PhoneInputComponent, PriceZoneEventService, PriceZoneFormModalService, PriceZoneSelectComponent, PriceZoneService, ProductCategoryService, ProductMultiSelectComponent, ProductSelectComponent, ProductService, ProductTagService, ProductTypeService, RoomMapElementOrientation, RoomMapElementType, SHOWS_API_ROUTES, SHOW_TYPE_COLORS, STATE_API_ENDPOINTS, SeatSelectionEmptySummaryComponent, SeatSelectionSummaryComponent, SeatSelectionSummaryItemComponent, SelectedDiscountCardType, ShowCardComponent, ShowCardSkeletonComponent, ShowMultiSelectComponent, ShowSelectComponent, ShowService, ShowType, ShowTypeBadgeComponent, ShowsFilterComponent, StateSelectComponent, StatesService, TICKERA_COMPONENTS_CONFIG, TICKET_ELEMENT_TYPES, TICKET_MAP_GRID_SIZE, TICKET_MAP_LAST_TICKETS_LIMIT, TICKET_MAP_TITLE_HEIGHT, TICKET_STATUS_COLORS, TICKET_STATUS_FILLS, TICKET_STATUS_LABELS, TIcketMapProductSelectionComponent, TRANSACTION_STATUS_COLORS, TextExpandableComponent, TickeraTranslocoLoader, TicketMapFloorSelectorComponent, TicketMapPriceZonesComponent, TicketMapProductSelectionItemComponent, TicketMapTotalsComponent, TicketMapWidgetComponent, TicketMapWidgetHeaderComponent, TicketMapWrapperComponent, TicketMapZoomControlsComponent, TicketMapZoomService, TicketQrComponent, TicketQrModalComponent, TicketQrModalService, TicketQrService, TicketSelectionDetailsService, TicketSelectionDiscountService, TicketSelectionService, TicketSelectionTotalsService, ToastService, ToggleSwitchComponent, TransactionStatus, VENUES_API_ROUTES, VIP_CARDS_API_ROUTES, VipBalanceCardComponent, VipCardService, VipCardStatus, VipCardStatusBadgeComponent, VipRechargeModalComponent, VipTransactionType, VipTransactionsTableComponent, ZonePriceItemComponent, ZonePriceListComponent, authInterceptor, drawChairIcon$1 as drawChairIcon, drawHappyFace, drawRoundedRect, drawWheelchairIcon, emailValidator, findElementAtPosition, findTicketAtPosition, formatCitiesResponseToSelect, generateExitScene, generateHallwayScene, generateProductionAreaScene, generateSeatBlockScene, generateSeatBlockTicketScene, generateStageScene, generateStairScene, generateTableScene, generateTableTicketScene, generateUnavailableSpaceScene, generateZoneScene, getBrowserLanguage, getCustomerFullname, getItemTypeIcon, getOrderDiscount, getStoredLanguage, numberToLetter$1 as numberToLetter, parseJsonToFormDataAdvanced, phoneCountryFlag, processElementsToRenderData, provideTickeraComponents, resolveLanguage, setStoredLanguage, ticketCanOpenQr, tintColor, transformImageToFile, transformUrlParams };
10019
+ export { ADMIN_API_ROUTES, ALERT_ICONS, AdminSelectComponent, AdminService, ApiService, AppAlertComponent, AppBadgeComponent, AppBreadcumbComponent, AppButtonComponent, AppLinkButtonComponent, AppModalComponent, AsyncSelectComponent, AuditInformationComponent, BASE_BUTTON_CLASSES, BASE_INPUT_CLASSES, BUTTON_SIZES_CLASSES, BUTTON_VARIANT_CLASSES, BaseModalService, CITY_API_ENDPOINTS, COUNTRY_API_ENDPOINTS, CUSTOMERS_API_ROUTES, ChangePasswordFormComponent, ChangePasswordFormService, CitiesService, CitySelectComponent, ColorPickerComponent, ConfirmationOrderModalComponent, ConfirmationOrderModalService, CorporateBondStatus, CountrySelectComponent, CountryService, CouponApplicability, CouponDiscountType, CouponStatus, CreatorType, CustomerSelectComponent, CustomerService, DEFAULT_STAGE_CONFIG, DOCUMENT_TYPES_OPTIONS, DateInputComponent, DateService, DaySelectorGridComponent, DeleteConfirmationComponent, DeleteConfirmationService, DynamicTableComponent, ERROR_INPUT_CLASSES, EndDateGreaterThanStartValidator, FORM_ERROR_MESSAGES, FeedbackModalComponent, FeedbackModalService, FileType, FileUploadComponent, FileUploadPreviewComponent, FormEditorComponent, FormInputComponent, FormSelectComponent, FormTextareaComponent, GiftBondPurchaseStatus, GiftBondStatus, KONVA_SHAPE_MAPPINGS, LanguageSwitcherComponent, MembershipStatus, MinTodayValidator, MustMatchValidator, NumberInputComponent, ORDER_DISCOUNT_COLORS, ORDER_ITEM_TYPES_COLORS, ORDER_STATUS_COLORS, OrderBillingInfoComponent, OrderDiscountAppliedComponent, OrderInformationComponent, OrderItemType, OrderItemTypeBadgeComponent, OrderItemsComponent, OrderStatus, OrderStatusBadgeComponent, OrderTransactionDetailsModalComponent, OrderTransactionDetailsModalService, OrderTransactionsComponent, PAYMENT_METHODS_OPTIONS, PERFORMANCES_API_ROUTES, PERFORMANCE_STATUS_COLORS, PERFORMANCE_TICKET_STATUS_COLORS, PHONE_COUNTRIES, PRICE_ZONES_API_ROUTES, PRODUCTS_API_ROUTES, PRODUCT_CATEGORIES_API_ROUTES, PRODUCT_TAGS_API_ROUTES, PRODUCT_TYPES_API_ROUTES, PaymentMethod, PerformanceBookingDataService, PerformanceCardComponent, PerformanceCardListComponent, PerformanceMultiSelectComponent, PerformanceSelectComponent, PerformanceService, PerformanceStatus, PerformanceStatusBadgeComponent, PerformanceStepperComponent, PerformanceTicketMapComponent, PerformanceTicketStatus, PerformanceTicketStatusBadgeComponent, PerformancesListEventsService, PhoneInputComponent, PriceZoneEventService, PriceZoneFormModalService, PriceZoneSelectComponent, PriceZoneService, ProductCategoryService, ProductMultiSelectComponent, ProductSelectComponent, ProductService, ProductTagService, ProductTypeService, PurchaseLimitService, RoomMapElementOrientation, RoomMapElementType, SHOWS_API_ROUTES, SHOW_TYPE_COLORS, STATE_API_ENDPOINTS, SeatSelectionEmptySummaryComponent, SeatSelectionSummaryComponent, SeatSelectionSummaryItemComponent, SelectedDiscountCardType, ShowCardComponent, ShowCardSkeletonComponent, ShowMultiSelectComponent, ShowSelectComponent, ShowService, ShowType, ShowTypeBadgeComponent, ShowsFilterComponent, StateSelectComponent, StatesService, TICKERA_COMPONENTS_CONFIG, TICKET_ELEMENT_TYPES, TICKET_MAP_GRID_SIZE, TICKET_MAP_LAST_TICKETS_LIMIT, TICKET_MAP_TITLE_HEIGHT, TICKET_STATUS_COLORS, TICKET_STATUS_FILLS, TICKET_STATUS_LABELS, TIcketMapProductSelectionComponent, TRANSACTION_STATUS_COLORS, TextExpandableComponent, TickeraTranslocoLoader, TicketMapFloorSelectorComponent, TicketMapPriceZonesComponent, TicketMapProductSelectionItemComponent, TicketMapTotalsComponent, TicketMapWidgetComponent, TicketMapWidgetHeaderComponent, TicketMapWrapperComponent, TicketMapZoomControlsComponent, TicketMapZoomService, TicketQrComponent, TicketQrModalComponent, TicketQrModalService, TicketQrService, TicketSelectionDetailsService, TicketSelectionDiscountService, TicketSelectionService, TicketSelectionTotalsService, ToastService, ToggleSwitchComponent, TransactionStatus, VENUES_API_ROUTES, VIP_CARDS_API_ROUTES, VipBalanceCardComponent, VipCardService, VipCardStatus, VipCardStatusBadgeComponent, VipRechargeModalComponent, VipTransactionType, VipTransactionsTableComponent, ZonePriceItemComponent, ZonePriceListComponent, authInterceptor, drawChairIcon$1 as drawChairIcon, drawHappyFace, drawRoundedRect, drawWheelchairIcon, emailValidator, findElementAtPosition, findTicketAtPosition, formatCitiesResponseToSelect, generateExitScene, generateHallwayScene, generateProductionAreaScene, generateSeatBlockScene, generateSeatBlockTicketScene, generateStageScene, generateStairScene, generateTableScene, generateTableTicketScene, generateUnavailableSpaceScene, generateZoneScene, getBrowserLanguage, getCustomerFullname, getItemTypeIcon, getOrderDiscount, getStoredLanguage, numberToLetter$1 as numberToLetter, parseJsonToFormDataAdvanced, phoneCountryFlag, processElementsToRenderData, provideTickeraComponents, resolveLanguage, setStoredLanguage, ticketCanOpenQr, tintColor, transformImageToFile, transformUrlParams };
9868
10020
  //# sourceMappingURL=tickera-angular-components.mjs.map