ctt-babylon 0.22.113 → 0.22.115
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.
- package/esm2022/lib/components/core/babylon-blog-details/babylon-blog-details.component.mjs +2 -2
- package/esm2022/lib/components/external/core/lis-c4-txt-des-cta/lis-c4-txt-des-cta.component.mjs +126 -102
- package/fesm2022/ctt-babylon.mjs +126 -102
- package/fesm2022/ctt-babylon.mjs.map +1 -1
- package/lib/components/external/core/lis-c4-txt-des-cta/lis-c4-txt-des-cta.component.d.ts +17 -3
- package/package.json +1 -1
package/fesm2022/ctt-babylon.mjs
CHANGED
|
@@ -7598,7 +7598,7 @@ class BabylonBlogDetailsComponent {
|
|
|
7598
7598
|
this.removeJsonLd();
|
|
7599
7599
|
}
|
|
7600
7600
|
updateJsonLd() {
|
|
7601
|
-
if (!this.title)
|
|
7601
|
+
if (!this.title || typeof window === 'undefined')
|
|
7602
7602
|
return;
|
|
7603
7603
|
let lang;
|
|
7604
7604
|
this.route.url.subscribe((segments) => {
|
|
@@ -23912,46 +23912,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
23912
23912
|
* - filter -> tipo principal (ej: hoteles, apartamentos, villas, etc.)
|
|
23913
23913
|
* - destination -> destino (por nombre)
|
|
23914
23914
|
* - brand -> marca / grupo
|
|
23915
|
+
* - experience -> experiencia asociada
|
|
23916
|
+
* - services -> servicios disponibles
|
|
23915
23917
|
*
|
|
23916
|
-
*
|
|
23917
|
-
*
|
|
23918
|
-
*
|
|
23919
|
-
*
|
|
23920
|
-
*
|
|
23921
|
-
*
|
|
23922
|
-
* 3. Al navegar con query params:
|
|
23923
|
-
* - Se leen los slugs de la URL
|
|
23924
|
-
* - Se resuelven a labels reales cuando los items están disponibles
|
|
23925
|
-
* 4. El filtrado interno SIEMPRE compara por slug (no por texto crudo).
|
|
23926
|
-
*
|
|
23927
|
-
* NORMALIZACIÓN
|
|
23928
|
-
* -------------
|
|
23929
|
-
* - Se usa `Utils.toSlug()` para normalizar textos (tildes, espacios, símbolos).
|
|
23930
|
-
* - Para el filtro principal (`filter`) se aplica además una singularización genérica
|
|
23931
|
-
* (hoteles -> hotel, apartamentos -> apartamento), SIN traducciones ni diccionarios.
|
|
23932
|
-
*
|
|
23933
|
-
* EXTENDER CON NUEVOS FILTROS
|
|
23934
|
-
* --------------------------
|
|
23935
|
-
* Para añadir un nuevo filtro (ej: category, experience, rating):
|
|
23936
|
-
*
|
|
23937
|
-
* 1. Añadir un nuevo queryParam (ej: `category`)
|
|
23938
|
-
* 2. Crear:
|
|
23939
|
-
* - selectedCategory
|
|
23940
|
-
* - categoryOptions
|
|
23941
|
-
* - categorySlugToLabel / categoryLabelToSlug
|
|
23942
|
-
* 3. Construir opciones en `buildFilterOptions()`
|
|
23943
|
-
* 4. Resolver desde URL en `resolvePendingFromMaps()`
|
|
23944
|
-
* 5. Aplicar condición en `filteredItems`
|
|
23945
|
-
* 6. Sincronizar en `syncQueryParams()`
|
|
23946
|
-
*
|
|
23947
|
-
* IMPORTANTE
|
|
23948
|
-
* ----------
|
|
23949
|
-
* - NUNCA comparar por texto directo.
|
|
23950
|
-
* - NUNCA meter valores crudos en la URL.
|
|
23951
|
-
* - Todo filtro nuevo debe pasar por slug.
|
|
23952
|
-
*
|
|
23953
|
-
* Con esto el componente es reutilizable entre proyectos,
|
|
23954
|
-
* idiomas y configuraciones distintas sin cambios estructurales.
|
|
23918
|
+
* ACTUALIZACIÓN: BÚSQUEDA FACETADA (Faceted Search)
|
|
23919
|
+
* -------------------------------------------------
|
|
23920
|
+
* Ahora el componente actualiza dinámicamente las opciones de los desplegables.
|
|
23921
|
+
* Al seleccionar un filtro, los demás ocultan las opciones que no tendrían resultados
|
|
23922
|
+
* cruzados con la selección actual. Para evitar bloqueos, siempre se mantiene visible
|
|
23923
|
+
* la opción que esté actualmente seleccionada.
|
|
23955
23924
|
*/
|
|
23956
23925
|
class LisC4TxtDesCtaComponent {
|
|
23957
23926
|
constructor() {
|
|
@@ -23963,15 +23932,22 @@ class LisC4TxtDesCtaComponent {
|
|
|
23963
23932
|
this.hideServicesFilter = false;
|
|
23964
23933
|
this.tagName = 'LisC4TxtDesCta';
|
|
23965
23934
|
// UI labels seleccionados (lo que se muestra en los selects)
|
|
23966
|
-
|
|
23967
|
-
this.
|
|
23968
|
-
this.
|
|
23969
|
-
this.selectedBrand = ''; // => queryParam "brand"
|
|
23935
|
+
this.selectedHotelType = '';
|
|
23936
|
+
this.selectedDestination = '';
|
|
23937
|
+
this.selectedBrand = '';
|
|
23970
23938
|
this.selectedExperience = '';
|
|
23939
|
+
// Opciones Dinámicas (Las que se muestran en el HTML y se actualizan al filtrar)
|
|
23971
23940
|
this.hotelTypeOptions = [];
|
|
23972
23941
|
this.destinationOptions = [];
|
|
23973
23942
|
this.brandOptions = [];
|
|
23974
23943
|
this.experienceOptions = [];
|
|
23944
|
+
this.availableServices = [];
|
|
23945
|
+
// Opciones Base (Listas maestras generadas al cargar, nunca se reducen)
|
|
23946
|
+
this.baseHotelTypeOptions = [];
|
|
23947
|
+
this.baseDestinationOptions = [];
|
|
23948
|
+
this.baseBrandOptions = [];
|
|
23949
|
+
this.baseExperienceOptions = [];
|
|
23950
|
+
this.baseAvailableServices = [];
|
|
23975
23951
|
this.pendingFilterSlug = null;
|
|
23976
23952
|
this.pendingDestinationSlug = null;
|
|
23977
23953
|
this.pendingBrandSlug = null;
|
|
@@ -23990,7 +23966,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
23990
23966
|
this.router = inject(Router);
|
|
23991
23967
|
this.cdr = inject(ChangeDetectorRef);
|
|
23992
23968
|
this.isSyncingFromUrl = false;
|
|
23993
|
-
this.availableServices = [];
|
|
23994
23969
|
this.selectedServices = new Set();
|
|
23995
23970
|
this.isServicesMenuOpen = false;
|
|
23996
23971
|
}
|
|
@@ -24007,8 +23982,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
24007
23982
|
this.pendingBrandSlug = params.get('brand');
|
|
24008
23983
|
this.pendingExperienceSlug = params.get('experience');
|
|
24009
23984
|
this.resolvePendingFromMaps();
|
|
24010
|
-
// Si aún no hay maps (items no llegó), intenta fallback (mantener algo decente)
|
|
24011
|
-
// Nota: no navegamos aquí.
|
|
24012
23985
|
if (!this.selectedHotelType && filterSlug)
|
|
24013
23986
|
this.selectedHotelType = filterSlug;
|
|
24014
23987
|
if (!this.selectedDestination && destinationSlug)
|
|
@@ -24017,6 +23990,7 @@ class LisC4TxtDesCtaComponent {
|
|
|
24017
23990
|
this.selectedBrand = brandSlug;
|
|
24018
23991
|
if (!this.selectedExperience && experienceSlug)
|
|
24019
23992
|
this.selectedExperience = experienceSlug;
|
|
23993
|
+
this.updateDynamicOptions(); // Actualiza los listados dinámicos con la hidratación
|
|
24020
23994
|
this.cdr.markForCheck();
|
|
24021
23995
|
queueMicrotask(() => (this.isSyncingFromUrl = false));
|
|
24022
23996
|
});
|
|
@@ -24024,7 +23998,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
24024
23998
|
ngOnChanges(changes) {
|
|
24025
23999
|
if (changes['items']) {
|
|
24026
24000
|
this.buildFilterOptions();
|
|
24027
|
-
// rehidrata desde URL ya con maps listos (sin navegar)
|
|
24028
24001
|
const qp = this.route.snapshot.queryParamMap;
|
|
24029
24002
|
const filterSlug = qp.get('filter') ?? '';
|
|
24030
24003
|
const destinationSlug = qp.get('destination') ?? '';
|
|
@@ -24039,6 +24012,7 @@ class LisC4TxtDesCtaComponent {
|
|
|
24039
24012
|
this.selectedExperience =
|
|
24040
24013
|
this.experienceSlugToLabel.get(experienceSlug) ?? '';
|
|
24041
24014
|
this.forceOptionMatch();
|
|
24015
|
+
this.updateDynamicOptions(); // Vuelve a filtrar las opciones según la nueva hidratación
|
|
24042
24016
|
this.cdr.markForCheck();
|
|
24043
24017
|
this.cdr.detectChanges();
|
|
24044
24018
|
queueMicrotask(() => (this.isSyncingFromUrl = false));
|
|
@@ -24050,16 +24024,17 @@ class LisC4TxtDesCtaComponent {
|
|
|
24050
24024
|
buildFilterOptions() {
|
|
24051
24025
|
const items = this.items ?? [];
|
|
24052
24026
|
const experiences = this.experiences ?? [];
|
|
24053
|
-
|
|
24027
|
+
// Llenamos las listas MAESTRAS
|
|
24028
|
+
this.baseHotelTypeOptions = Array.from(new Set(items
|
|
24054
24029
|
.map((i) => i?.hoteltype)
|
|
24055
24030
|
.filter(Boolean)
|
|
24056
24031
|
.map((v) => v.trim()))).sort((a, b) => a.localeCompare(b));
|
|
24057
|
-
this.
|
|
24032
|
+
this.baseDestinationOptions = Array.from(new Set(items
|
|
24058
24033
|
.flatMap((i) => (i?.destinations ?? []).map((d) => d?.name))
|
|
24059
24034
|
.filter(Boolean)
|
|
24060
24035
|
.map((v) => v.trim()))).sort((a, b) => a.localeCompare(b));
|
|
24061
|
-
this.
|
|
24062
|
-
this.
|
|
24036
|
+
this.baseBrandOptions = Array.from(new Set(items.map((i) => this.getItemBrand(i)).filter(Boolean))).sort((a, b) => a.localeCompare(b));
|
|
24037
|
+
this.baseExperienceOptions = Array.from(new Set(experiences.map((e) => e.nameIdentifier)?.filter(Boolean))).sort((a, b) => a.localeCompare(b));
|
|
24063
24038
|
// Reset maps
|
|
24064
24039
|
this.filterSlugToLabel.clear();
|
|
24065
24040
|
this.destinationSlugToLabel.clear();
|
|
@@ -24069,39 +24044,34 @@ class LisC4TxtDesCtaComponent {
|
|
|
24069
24044
|
this.destinationLabelToSlug.clear();
|
|
24070
24045
|
this.brandLabelToSlug.clear();
|
|
24071
24046
|
this.experienceLabelToSlug.clear();
|
|
24072
|
-
// used sets para sufijos -2 -3...
|
|
24073
24047
|
const usedFilter = new Set();
|
|
24074
24048
|
const usedDest = new Set();
|
|
24075
24049
|
const usedBrand = new Set();
|
|
24076
24050
|
const usedExperience = new Set();
|
|
24077
|
-
|
|
24078
|
-
for (const label of this.hotelTypeOptions) {
|
|
24051
|
+
for (const label of this.baseHotelTypeOptions) {
|
|
24079
24052
|
const base = this.canonicalSlug(label);
|
|
24080
24053
|
const slug = Utils.uniqueSlug(base, usedFilter);
|
|
24081
24054
|
this.filterSlugToLabel.set(slug, label);
|
|
24082
24055
|
this.filterLabelToSlug.set(label, slug);
|
|
24083
24056
|
}
|
|
24084
|
-
|
|
24085
|
-
for (const label of this.destinationOptions) {
|
|
24057
|
+
for (const label of this.baseDestinationOptions) {
|
|
24086
24058
|
const base = Utils.toSlug(label) || 'destination';
|
|
24087
24059
|
const slug = Utils.uniqueSlug(base, usedDest);
|
|
24088
24060
|
this.destinationSlugToLabel.set(slug, label);
|
|
24089
24061
|
this.destinationLabelToSlug.set(label, slug);
|
|
24090
24062
|
}
|
|
24091
|
-
|
|
24092
|
-
for (const label of this.brandOptions) {
|
|
24063
|
+
for (const label of this.baseBrandOptions) {
|
|
24093
24064
|
const base = Utils.toSlug(label) || 'brand';
|
|
24094
24065
|
const slug = Utils.uniqueSlug(base, usedBrand);
|
|
24095
24066
|
this.brandSlugToLabel.set(slug, label);
|
|
24096
24067
|
this.brandLabelToSlug.set(label, slug);
|
|
24097
24068
|
}
|
|
24098
|
-
for (const label of this.
|
|
24069
|
+
for (const label of this.baseExperienceOptions) {
|
|
24099
24070
|
const base = Utils.toSlug(label) || 'experience';
|
|
24100
24071
|
const slug = Utils.uniqueSlug(base, usedExperience);
|
|
24101
24072
|
this.experienceSlugToLabel.set(slug, label);
|
|
24102
24073
|
this.experienceLabelToSlug.set(label, slug);
|
|
24103
24074
|
}
|
|
24104
|
-
const allServices = new Set();
|
|
24105
24075
|
const allServicesNames = new Set();
|
|
24106
24076
|
const filteredServicesNames = new Set();
|
|
24107
24077
|
let requestedClasses = [];
|
|
@@ -24112,14 +24082,12 @@ class LisC4TxtDesCtaComponent {
|
|
|
24112
24082
|
.filter(Boolean);
|
|
24113
24083
|
}
|
|
24114
24084
|
items.forEach((it) => {
|
|
24115
|
-
// Soportamos camelCase o snake_case según como llegue de la API
|
|
24116
24085
|
const services = it?.servicesService || it?.services_service;
|
|
24117
24086
|
if (Array.isArray(services)) {
|
|
24118
24087
|
services.forEach((s) => {
|
|
24119
24088
|
if (s?.name) {
|
|
24120
24089
|
const name = s.name.trim();
|
|
24121
24090
|
allServicesNames.add(name);
|
|
24122
|
-
// Si hay clases solicitadas, validamos contra el atributo 'class'
|
|
24123
24091
|
if (requestedClasses.length > 0 && s.class) {
|
|
24124
24092
|
if (requestedClasses.includes(s.class.trim().toLowerCase())) {
|
|
24125
24093
|
filteredServicesNames.add(name);
|
|
@@ -24129,13 +24097,18 @@ class LisC4TxtDesCtaComponent {
|
|
|
24129
24097
|
});
|
|
24130
24098
|
}
|
|
24131
24099
|
});
|
|
24132
|
-
// 3. Decidimos qué lista mostrar
|
|
24133
24100
|
let finalServices = Array.from(filteredServicesNames);
|
|
24134
|
-
// Si no se pasaron clases por Input, o si lo que se pasó NO hizo match con nada, mostramos TODOS
|
|
24135
24101
|
if (requestedClasses.length === 0 || finalServices.length === 0) {
|
|
24136
24102
|
finalServices = Array.from(allServicesNames);
|
|
24137
24103
|
}
|
|
24138
|
-
this.
|
|
24104
|
+
this.baseAvailableServices = finalServices.sort((a, b) => a.localeCompare(b));
|
|
24105
|
+
// Inicializamos las opciones de UI con las maestras y luego filtramos
|
|
24106
|
+
this.hotelTypeOptions = [...this.baseHotelTypeOptions];
|
|
24107
|
+
this.destinationOptions = [...this.baseDestinationOptions];
|
|
24108
|
+
this.brandOptions = [...this.baseBrandOptions];
|
|
24109
|
+
this.experienceOptions = [...this.baseExperienceOptions];
|
|
24110
|
+
this.availableServices = [...this.baseAvailableServices];
|
|
24111
|
+
this.updateDynamicOptions();
|
|
24139
24112
|
}
|
|
24140
24113
|
getItemBrand(it) {
|
|
24141
24114
|
if (this.additional1LikeMarca)
|
|
@@ -24143,13 +24116,17 @@ class LisC4TxtDesCtaComponent {
|
|
|
24143
24116
|
return (it?.marca ?? it?.brand ?? it?.group ?? '').trim();
|
|
24144
24117
|
}
|
|
24145
24118
|
// ----------------------
|
|
24146
|
-
// FILTRADO
|
|
24119
|
+
// LÓGICA DE FILTRADO BASE (FACETED SEARCH)
|
|
24147
24120
|
// ----------------------
|
|
24148
|
-
|
|
24121
|
+
/**
|
|
24122
|
+
* Obtiene los items coincidentes excluyendo uno de los filtros.
|
|
24123
|
+
* Es clave para calcular qué opciones deben quedar disponibles en un desplegable
|
|
24124
|
+
* sin que su propia selección previa las borre por completo.
|
|
24125
|
+
*/
|
|
24126
|
+
getItemsMatching(excludeFilter) {
|
|
24149
24127
|
const items = this.items ?? [];
|
|
24150
|
-
if (!this.showFilters)
|
|
24128
|
+
if (!this.showFilters)
|
|
24151
24129
|
return items;
|
|
24152
|
-
}
|
|
24153
24130
|
let validHotelIdsForExperience = [];
|
|
24154
24131
|
if (this.selectedExperience && this.experiences) {
|
|
24155
24132
|
const targetExp = this.experiences.find((e) => e.nameIdentifier === this.selectedExperience);
|
|
@@ -24161,25 +24138,29 @@ class LisC4TxtDesCtaComponent {
|
|
|
24161
24138
|
const itemHotelType = it?.hoteltype ?? '';
|
|
24162
24139
|
const itemBrand = this.getItemBrand(it);
|
|
24163
24140
|
const itemId = it?.id;
|
|
24164
|
-
const hotelTypeOk =
|
|
24141
|
+
const hotelTypeOk = excludeFilter === 'hotelType' ||
|
|
24142
|
+
!this.selectedHotelType ||
|
|
24165
24143
|
this.canonicalSlug(itemHotelType) ===
|
|
24166
24144
|
this.canonicalSlug(this.selectedHotelType);
|
|
24167
|
-
const destinationOk =
|
|
24145
|
+
const destinationOk = excludeFilter === 'destination' ||
|
|
24146
|
+
!this.selectedDestination ||
|
|
24168
24147
|
(it?.destinations ?? []).some((d) => Utils.toSlug(d?.name ?? '') ===
|
|
24169
24148
|
Utils.toSlug(this.selectedDestination));
|
|
24170
|
-
const brandOk =
|
|
24149
|
+
const brandOk = excludeFilter === 'brand' ||
|
|
24150
|
+
!this.selectedBrand ||
|
|
24171
24151
|
Utils.toSlug(itemBrand) === Utils.toSlug(this.selectedBrand);
|
|
24172
|
-
const experienceOk =
|
|
24152
|
+
const experienceOk = excludeFilter === 'experience' ||
|
|
24153
|
+
!this.selectedExperience ||
|
|
24173
24154
|
validHotelIdsForExperience.includes(itemId);
|
|
24174
|
-
|
|
24175
|
-
|
|
24155
|
+
const servicesOk = excludeFilter === 'services' ||
|
|
24156
|
+
this.selectedServices.size === 0 ||
|
|
24176
24157
|
Array.from(this.selectedServices).every((selectedSrv) => {
|
|
24177
|
-
const itemServices = it?.servicesService
|
|
24158
|
+
const itemServices = it?.servicesService ||
|
|
24159
|
+
it?.services_service;
|
|
24178
24160
|
if (!Array.isArray(itemServices))
|
|
24179
24161
|
return false;
|
|
24180
24162
|
return itemServices.some((s) => s?.name?.trim() === selectedSrv);
|
|
24181
24163
|
});
|
|
24182
|
-
// Retorna validando también servicesOk
|
|
24183
24164
|
return (hotelTypeOk &&
|
|
24184
24165
|
destinationOk &&
|
|
24185
24166
|
brandOk &&
|
|
@@ -24187,35 +24168,93 @@ class LisC4TxtDesCtaComponent {
|
|
|
24187
24168
|
servicesOk);
|
|
24188
24169
|
});
|
|
24189
24170
|
}
|
|
24171
|
+
get filteredItems() {
|
|
24172
|
+
return this.getItemsMatching('none');
|
|
24173
|
+
}
|
|
24174
|
+
/**
|
|
24175
|
+
* Actualiza los arrays de opciones para ocultar aquellas que devolverían 0 resultados.
|
|
24176
|
+
*/
|
|
24177
|
+
updateDynamicOptions() {
|
|
24178
|
+
if (!this.items || this.items.length === 0)
|
|
24179
|
+
return;
|
|
24180
|
+
// -- Destinos --
|
|
24181
|
+
const itemsForDest = this.getItemsMatching('destination');
|
|
24182
|
+
const validDests = new Set(itemsForDest
|
|
24183
|
+
.flatMap((i) => (i?.destinations ?? []).map((d) => d?.name))
|
|
24184
|
+
.filter(Boolean)
|
|
24185
|
+
.map((v) => v.trim()));
|
|
24186
|
+
this.destinationOptions = this.baseDestinationOptions.filter((opt) => validDests.has(opt) || this.selectedDestination === opt);
|
|
24187
|
+
// -- Marcas --
|
|
24188
|
+
const itemsForBrand = this.getItemsMatching('brand');
|
|
24189
|
+
const validBrands = new Set(itemsForBrand.map((i) => this.getItemBrand(i)).filter(Boolean));
|
|
24190
|
+
this.brandOptions = this.baseBrandOptions.filter((opt) => validBrands.has(opt) || this.selectedBrand === opt);
|
|
24191
|
+
// -- Tipos de Hotel --
|
|
24192
|
+
const itemsForHotelType = this.getItemsMatching('hotelType');
|
|
24193
|
+
const validHotelTypes = new Set(itemsForHotelType
|
|
24194
|
+
.map((i) => i?.hoteltype)
|
|
24195
|
+
.filter(Boolean)
|
|
24196
|
+
.map((v) => v.trim()));
|
|
24197
|
+
this.hotelTypeOptions = this.baseHotelTypeOptions.filter((opt) => validHotelTypes.has(opt) || this.selectedHotelType === opt);
|
|
24198
|
+
// -- Experiencias --
|
|
24199
|
+
const itemsForExp = this.getItemsMatching('experience');
|
|
24200
|
+
const validExpIds = new Set(itemsForExp.map((i) => i?.id));
|
|
24201
|
+
this.experienceOptions = this.baseExperienceOptions.filter((opt) => {
|
|
24202
|
+
if (this.selectedExperience === opt)
|
|
24203
|
+
return true;
|
|
24204
|
+
const targetExp = this.experiences?.find((e) => e.nameIdentifier === opt);
|
|
24205
|
+
if (!targetExp?.hotels)
|
|
24206
|
+
return false;
|
|
24207
|
+
return targetExp.hotels.some((h) => validExpIds.has(h.id));
|
|
24208
|
+
});
|
|
24209
|
+
// -- Servicios --
|
|
24210
|
+
const itemsForServices = this.getItemsMatching('services');
|
|
24211
|
+
const validServices = new Set();
|
|
24212
|
+
itemsForServices.forEach((it) => {
|
|
24213
|
+
const services = it?.servicesService || it?.services_service;
|
|
24214
|
+
if (Array.isArray(services)) {
|
|
24215
|
+
services.forEach((s) => {
|
|
24216
|
+
if (s?.name) {
|
|
24217
|
+
validServices.add(s.name.trim());
|
|
24218
|
+
}
|
|
24219
|
+
});
|
|
24220
|
+
}
|
|
24221
|
+
});
|
|
24222
|
+
this.availableServices = this.baseAvailableServices.filter((opt) => validServices.has(opt) || this.selectedServices.has(opt));
|
|
24223
|
+
}
|
|
24190
24224
|
get hasActiveFilters() {
|
|
24191
24225
|
return !!(this.selectedHotelType ||
|
|
24192
24226
|
this.selectedDestination ||
|
|
24193
24227
|
this.selectedBrand ||
|
|
24194
|
-
this.selectedExperience
|
|
24228
|
+
this.selectedExperience ||
|
|
24229
|
+
this.selectedServices.size > 0);
|
|
24195
24230
|
}
|
|
24196
24231
|
// ----------------------
|
|
24197
24232
|
// HANDLERS UI
|
|
24198
24233
|
// ----------------------
|
|
24199
24234
|
onHotelTypeChange(value) {
|
|
24200
24235
|
this.selectedHotelType = value ?? '';
|
|
24236
|
+
this.updateDynamicOptions();
|
|
24201
24237
|
if (this.isSyncingFromUrl)
|
|
24202
24238
|
return;
|
|
24203
24239
|
this.syncQueryParams();
|
|
24204
24240
|
}
|
|
24205
24241
|
onDestinationChange(value) {
|
|
24206
24242
|
this.selectedDestination = value ?? '';
|
|
24243
|
+
this.updateDynamicOptions();
|
|
24207
24244
|
if (this.isSyncingFromUrl)
|
|
24208
24245
|
return;
|
|
24209
24246
|
this.syncQueryParams();
|
|
24210
24247
|
}
|
|
24211
24248
|
onBrandChange(value) {
|
|
24212
24249
|
this.selectedBrand = value ?? '';
|
|
24250
|
+
this.updateDynamicOptions();
|
|
24213
24251
|
if (this.isSyncingFromUrl)
|
|
24214
24252
|
return;
|
|
24215
24253
|
this.syncQueryParams();
|
|
24216
24254
|
}
|
|
24217
24255
|
onExperienceChange(value) {
|
|
24218
24256
|
this.selectedExperience = value ?? '';
|
|
24257
|
+
this.updateDynamicOptions();
|
|
24219
24258
|
if (this.isSyncingFromUrl)
|
|
24220
24259
|
return;
|
|
24221
24260
|
this.syncQueryParams();
|
|
@@ -24226,6 +24265,7 @@ class LisC4TxtDesCtaComponent {
|
|
|
24226
24265
|
this.selectedBrand = '';
|
|
24227
24266
|
this.selectedExperience = '';
|
|
24228
24267
|
this.selectedServices.clear();
|
|
24268
|
+
this.updateDynamicOptions();
|
|
24229
24269
|
this.syncQueryParams(true);
|
|
24230
24270
|
}
|
|
24231
24271
|
toggleService(service) {
|
|
@@ -24237,6 +24277,8 @@ class LisC4TxtDesCtaComponent {
|
|
|
24237
24277
|
newSelected.add(service);
|
|
24238
24278
|
}
|
|
24239
24279
|
this.selectedServices = newSelected;
|
|
24280
|
+
this.updateDynamicOptions();
|
|
24281
|
+
// Nota: Servicios no se sincan con URL históricamente, pero re-evaluamos dinámicamente.
|
|
24240
24282
|
}
|
|
24241
24283
|
toggleServicesMenu() {
|
|
24242
24284
|
this.isServicesMenuOpen = !this.isServicesMenuOpen;
|
|
@@ -24246,7 +24288,7 @@ class LisC4TxtDesCtaComponent {
|
|
|
24246
24288
|
// ----------------------
|
|
24247
24289
|
syncQueryParams(clearAll = false) {
|
|
24248
24290
|
const qp = clearAll
|
|
24249
|
-
? { filter: null, destination: null, brand: null }
|
|
24291
|
+
? { filter: null, destination: null, brand: null, experience: null }
|
|
24250
24292
|
: {
|
|
24251
24293
|
filter: this.selectedHotelType
|
|
24252
24294
|
? (this.filterLabelToSlug.get(this.selectedHotelType) ??
|
|
@@ -24270,29 +24312,20 @@ class LisC4TxtDesCtaComponent {
|
|
|
24270
24312
|
});
|
|
24271
24313
|
this.cdr.markForCheck();
|
|
24272
24314
|
}
|
|
24273
|
-
// ----------------------
|
|
24274
|
-
// CANONICAL SLUG (sin traductor)
|
|
24275
|
-
// - Garantiza singular/plural genérico
|
|
24276
|
-
// ----------------------
|
|
24277
24315
|
canonicalSlug(value) {
|
|
24278
24316
|
let s = Utils.toSlug(value);
|
|
24279
24317
|
if (!s)
|
|
24280
24318
|
return '';
|
|
24281
|
-
// Regla -es (hoteles -> hotel, luces -> luz, peces -> pez)
|
|
24282
24319
|
if (s.length > 3 && s.endsWith('es')) {
|
|
24283
24320
|
const base = s.slice(0, -2);
|
|
24284
24321
|
if (base.endsWith('c'))
|
|
24285
24322
|
return base.slice(0, -1) + 'z';
|
|
24286
24323
|
return base;
|
|
24287
24324
|
}
|
|
24288
|
-
// Regla -s (apartamentos -> apartamento)
|
|
24289
24325
|
if (s.length > 3 && s.endsWith('s'))
|
|
24290
24326
|
return s.slice(0, -1);
|
|
24291
24327
|
return s;
|
|
24292
24328
|
}
|
|
24293
|
-
// ----------------------
|
|
24294
|
-
// UI utils
|
|
24295
|
-
// ----------------------
|
|
24296
24329
|
getKeysArray(count) {
|
|
24297
24330
|
return count ? Array.from({ length: count }) : [];
|
|
24298
24331
|
}
|
|
@@ -24318,7 +24351,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
24318
24351
|
this.pendingFilterSlug = null;
|
|
24319
24352
|
}
|
|
24320
24353
|
}
|
|
24321
|
-
// DESTINATION
|
|
24322
24354
|
if (this.pendingDestinationSlug != null) {
|
|
24323
24355
|
const slug = Utils.toSlug(this.pendingDestinationSlug);
|
|
24324
24356
|
const label = this.destinationSlugToLabel.get(slug);
|
|
@@ -24327,7 +24359,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
24327
24359
|
this.pendingDestinationSlug = null;
|
|
24328
24360
|
}
|
|
24329
24361
|
}
|
|
24330
|
-
// BRAND
|
|
24331
24362
|
if (this.pendingBrandSlug != null) {
|
|
24332
24363
|
const slug = Utils.toSlug(this.pendingBrandSlug);
|
|
24333
24364
|
const label = this.brandSlugToLabel.get(slug);
|
|
@@ -24336,7 +24367,6 @@ class LisC4TxtDesCtaComponent {
|
|
|
24336
24367
|
this.pendingBrandSlug = null;
|
|
24337
24368
|
}
|
|
24338
24369
|
}
|
|
24339
|
-
//EXPERIENCES
|
|
24340
24370
|
if (this.pendingExperienceSlug != null) {
|
|
24341
24371
|
const slug = Utils.toSlug(this.pendingExperienceSlug);
|
|
24342
24372
|
const label = this.experienceSlugToLabel.get(slug);
|
|
@@ -24349,46 +24379,40 @@ class LisC4TxtDesCtaComponent {
|
|
|
24349
24379
|
forceOptionMatch() {
|
|
24350
24380
|
if (this.selectedHotelType) {
|
|
24351
24381
|
const wanted = this.canonicalSlug(this.selectedHotelType);
|
|
24352
|
-
const match = this.
|
|
24382
|
+
const match = this.baseHotelTypeOptions.find((o) => this.canonicalSlug(o) === wanted);
|
|
24353
24383
|
this.selectedHotelType = match ?? '';
|
|
24354
24384
|
}
|
|
24355
24385
|
if (this.selectedBrand) {
|
|
24356
24386
|
const wanted = Utils.toSlug(this.selectedBrand);
|
|
24357
|
-
const match = this.
|
|
24387
|
+
const match = this.baseBrandOptions.find((o) => Utils.toSlug(o) === wanted);
|
|
24358
24388
|
this.selectedBrand = match ?? '';
|
|
24359
24389
|
}
|
|
24360
24390
|
if (this.selectedDestination) {
|
|
24361
24391
|
const wanted = Utils.toSlug(this.selectedDestination);
|
|
24362
|
-
const match = this.
|
|
24392
|
+
const match = this.baseDestinationOptions.find((o) => Utils.toSlug(o) === wanted);
|
|
24363
24393
|
this.selectedDestination = match ?? '';
|
|
24364
24394
|
}
|
|
24365
24395
|
if (this.selectedExperience) {
|
|
24366
24396
|
const wanted = Utils.toSlug(this.selectedExperience);
|
|
24367
|
-
const match = this.
|
|
24397
|
+
const match = this.baseExperienceOptions.find((o) => Utils.toSlug(o) === wanted);
|
|
24368
24398
|
this.selectedExperience = match ?? '';
|
|
24369
24399
|
}
|
|
24370
24400
|
}
|
|
24371
24401
|
getButtonUrl(btn, item, index) {
|
|
24372
24402
|
const urlBase = btn.url || '';
|
|
24373
|
-
// Si no es el primer botón, devolvemos la URL base sin alterar
|
|
24374
24403
|
if (index !== 0) {
|
|
24375
24404
|
return urlBase;
|
|
24376
24405
|
}
|
|
24377
|
-
// Buscamos el identificador (priorizamos nameIdentifier, luego name para destinos)
|
|
24378
24406
|
const identifier = item?.nameIdentifier ?? item?.name_identifier ?? item?.name;
|
|
24379
24407
|
if (!identifier) {
|
|
24380
24408
|
return urlBase;
|
|
24381
24409
|
}
|
|
24382
24410
|
const separator = urlBase.includes('?') ? '&' : '?';
|
|
24383
|
-
// 1. Lógica para EXPERIENCIAS
|
|
24384
|
-
// Entra si el type es 'experience' o si tiene un nameIdentifier pero no tiene type (fallback)
|
|
24385
24411
|
if (item?.type === 'experience' ||
|
|
24386
24412
|
(!item?.type && (item?.nameIdentifier || item?.name_identifier))) {
|
|
24387
24413
|
return `${urlBase}${separator}experience=${identifier}`;
|
|
24388
24414
|
}
|
|
24389
|
-
// 2. Lógica para DESTINOS
|
|
24390
24415
|
if (item?.type === 'destination') {
|
|
24391
|
-
// Usamos tu Utils para normalizar espacios y mayúsculas (ej: "Andorra la Vella" -> "andorra-la-vella")
|
|
24392
24416
|
const destinationSlug = Utils.toSlug(identifier);
|
|
24393
24417
|
return `${urlBase}${separator}destination=${destinationSlug}`;
|
|
24394
24418
|
}
|