The following has evaluated to null or missing:
==> categoryResponse[0] [in template "62757443695457#32456#108258" at line 10, column 27]
----
Tip: It's the final [] step that caused this error, not those before it.
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign categoryId = categoryResponse... [in template "62757443695457#32456#108258" at line 10, column 5]
----
1<#assign contents = []>
2<#assign categoryName = "">
3<#assign peti = "/headless-delivery/v1.0/sites/" + groupId + "/content-structures?filter=name eq 'Hotel Card'"/>
4<#assign structures = restClient.get("/headless-delivery/v1.0/sites/" + groupId + "/content-structures?filter=name eq 'Hotel Card'").items/>
5<#if (structures?size > 0)>
6 <#assign categoryName = (shortName.getData()!"")>
7 <#assign vocabularyResponse = restClient.get("/headless-admin-taxonomy/v1.0/sites/" + groupId + "/taxonomy-vocabularies?filter=name eq 'Destinations'").items>
8 <#assign vocabularyId = vocabularyResponse[0].id>
9 <#assign categoryResponse = restClient.get("/headless-admin-taxonomy/v1.0/taxonomy-vocabularies/" + vocabularyId + "/taxonomy-categories?filter=name eq '" + categoryName + "'").items>
10 <#assign categoryId = categoryResponse[0].id>
11
12 <#assign response = restClient.get("/headless-delivery/v1.0/content-structures/" + structures[0].id + "/structured-contents?filter=taxonomyCategoryIds/any(t:t eq " + categoryId + ")&pageSize=100")>
13 <#assign contents = response.items>
14</#if>
15<#if (contents?size >0)>
16 <section class="mp-max-width-section">
17 <div class="our-accomodations__wrapper" id="hotelesDestino">
18 <h2 class="mp-title-section mp-text-center">
19
20 <#if containsCircuits?? && getterUtil.getBoolean(containsCircuits.getData())>
21 Información adicional y alojamientos en ${categoryName?lower_case?cap_first}
22 <#elseif containCruises?? && getterUtil.getBoolean(containCruises.getData())>
23 Información adicional y tipos de camarotes en ${categoryName?lower_case?cap_first}
24 <#else>
25 Nuestros alojamientos en ${categoryName?lower_case?cap_first}
26 </#if>
27
28 </h2>
29 <div class="swiper">
30 <ul class="cards-list__wrapper swiper-wrapper" aria-label="Carousel">
31 <#list contents as content>
32
33 <li class="swiper-slide">
34 <a href="/viajes/${content.friendlyUrlPath}">
35 <div class="card-2__wrapper card-2__wrapper--resume">
36 <div class="image-container">
37 <#assign imageField = getFieldByName(content.contentFields, "images")>
38 <#if imageField?has_content && imageField.contentFieldValue.image?has_content>
39 <img src="${imageField.contentFieldValue.image.contentUrl}"
40 alt="${imageField.contentFieldValue.image.description}"
41 loading="lazy">
42 </#if>
43 </div>
44 <div class="data__wrapper">
45 <#assign nameField = getFieldByName(content.contentFields, "name")>
46 <#if nameField?has_content>
47 <h3>${nameField.contentFieldValue.data}</h3>
48 </#if>
49 <#assign categoryField = getFieldByName(content.contentFields, "category")>
50 <#if categoryField?has_content>
51 <#assign category = categoryField.contentFieldValue.data>
52 <#attempt>
53 <#assign stars = category?split("*")[0]?number>
54 <#recover>
55 <#assign stars = 0>
56 </#recover>
57 <#if (stars > 0)>
58 <ul class="stars__wrapper" role="img" aria-label="Rating: ${stars} de 5 estrellas">
59 <#list 1..stars as i>
60 <li aria-hidden="true">
61 <svg width="16" height="16" viewBox="0 0 16 16" data-icon="star">
62 <symbol id="ai:local:star">
63 <path fill="currentColor" d="m8 12.689-3.89 2.346a.72.72 0 0 1-.436.107.79.79 0 0 1-.396-.147.84.84 0 0 1-.273-.332.648.648 0 0 1-.03-.453l1.032-4.417L.573 6.82a.737.737 0 0 1-.245-.388.702.702 0 0 1 .027-.428.845.845 0 0 1 .233-.34.807.807 0 0 1 .416-.171l4.532-.396L7.296.925a.68.68 0 0 1 .291-.344.819.819 0 0 1 .825 0 .68.68 0 0 1 .291.344l1.76 4.172 4.533.396a.807.807 0 0 1 .415.172.846.846 0 0 1 .233.34.703.703 0 0 1 .028.427.737.737 0 0 1-.245.388l-3.435 2.973 1.033 4.417a.648.648 0 0 1-.031.453.839.839 0 0 1-.273.332.79.79 0 0 1-.396.147.72.72 0 0 1-.435-.107L8 12.689Z"></path>
64 </symbol>
65 <use xlink:href="#ai:local:star"></use>
66 </svg>
67 </li>
68 </#list>
69 </ul>
70 </#if>
71 </#if>
72 <div class="info__description">
73 <svg width="24" height="24" viewBox="0 0 12 20" data-icon="distance">
74 <symbol id="ai:local:distance">
75 <path fill="currentColor"
76 d="M6.441 19.5c-1.612 0-2.932-.231-3.96-.693C1.456 18.345.942 17.742.942 17c0-.391.168-.749.504-1.073.336-.324.8-.605 1.394-.842l.287.957c-.347.148-.619.303-.813.467-.195.163-.315.327-.36.491.114.446.598.808 1.452 1.085.854.276 1.866.415 3.036.415 1.158 0 2.168-.139 3.03-.415.862-.277 1.35-.639 1.465-1.085-.045-.164-.165-.325-.36-.482a3.956 3.956 0 0 0-.813-.476l.305-.957c.594.237 1.055.518 1.382.842.328.324.491.682.491 1.073 0 .742-.513 1.345-1.54 1.807-1.027.462-2.347.693-3.96.693Zm0-5.137c.3-.588.656-1.188 1.066-1.8.41-.613.831-1.198 1.263-1.755.643-.839 1.157-1.601 1.543-2.288.385-.686.578-1.543.578-2.57 0-1.228-.434-2.277-1.303-3.146C8.718 1.934 7.67 1.5 6.44 1.5c-1.228 0-2.277.435-3.146 1.304-.869.87-1.304 1.918-1.304 3.146 0 1.027.197 1.884.589 2.57a22.38 22.38 0 0 0 1.533 2.288c.432.557.853 1.142 1.263 1.755.41.612.765 1.212 1.065 1.8Zm0 1.522a.603.603 0 0 1-.403-.163 1.077 1.077 0 0 1-.287-.399c-.435-1.055-.924-1.94-1.47-2.658l-1.565-2.06a12.42 12.42 0 0 1-1.224-2.043c-.334-.707-.5-1.577-.5-2.612 0-1.526.526-2.815 1.58-3.87C3.626 1.028 4.916.5 6.442.5c1.525 0 2.815.527 3.869 1.58 1.053 1.055 1.58 2.344 1.58 3.87 0 1.035-.162 1.905-.488 2.612a10.973 10.973 0 0 1-1.237 2.044c-.482.656-1 1.343-1.552 2.06-.554.716-1.048 1.602-1.482 2.657a1.09 1.09 0 0 1-.287.4.603.603 0 0 1-.404.162Zm0-8.406c.43 0 .792-.148 1.087-.442.295-.295.442-.658.442-1.087 0-.43-.147-.792-.442-1.087a1.477 1.477 0 0 0-1.087-.442c-.43 0-.791.148-1.086.442a1.477 1.477 0 0 0-.442 1.087c0 .43.147.792.442 1.087a1.48 1.48 0 0 0 1.086.442Z"></path>
77 </symbol>
78 <use xlink:href="#ai:local:distance"></use>
79 </svg>
80 <#assign streetField = getFieldByName(content.contentFields, "street")>
81 <#if streetField?has_content>
82 <p>${streetField.contentFieldValue.data}</p>
83 </#if>
84 </div>
85 <#assign codeField = getFieldByName(content.contentFields, "code")>
86 <#assign dataHotel = "">
87 <#if codeField?has_content>
88 <#assign dataHotel = "data-hotel='" + codeField.contentFieldValue.data + "'">
89 <div class="data__wrapper__footer d-none">
90 <p class="data__wrapper__footer__price">
91 <span class="price-line">
92 Desde
93 <span ${dataHotel} class="price"></span>
94 </span>
95
96 <#-- asigno subtitulo del precio -->
97 <#assign subtitleForPriceField = getFieldByName(content.contentFields, "subtitleForPrice")>
98 <#if subtitleForPriceField.contentFieldValue.data?has_content>
99 <#-- muestro subtitulo del precio (por defecto muestra "por persona") -->
100 <span class="extra-fees">${subtitleForPriceField.contentFieldValue.data!"por persona"}</span>
101 <#else>
102 <span class="extra-fees">por persona</span>
103 </#if>
104 </p>
105 </div>
106 </#if>
107 </div>
108 </div>
109 </a>
110 <li>
111 </#list>
112 </ul>
113 </div>
114 </div>
115 </section>
116</#if>
117
118<#function getFieldByName contentFields, name>
119 <#list contentFields as field>
120 <#if field.name == name>
121 <#return field>
122 </#if>
123 </#list>
124 <#return "">
125</#function>
126
127<script>
128(function () {
129 let hotelElements = [
130 <#list contents as content>
131 {
132 <#assign nameField = getFieldByName(content.contentFields, "name")>
133 <#if nameField?has_content>
134 <#assign title = nameField.contentFieldValue.data>
135 </#if>
136 'title': '${title!""}',
137
138 <#assign codeField = getFieldByName(content.contentFields, "code")>
139 <#if codeField?has_content>
140 <#assign code = codeField.contentFieldValue.data>
141 </#if>
142 'code': '${code!""}',
143 'price': 0,
144 'index': ${content?index + 1},
145 <#assign categoryField = getFieldByName(content.contentFields, "category")>
146 <#if categoryField?has_content>
147 <#assign category = categoryField.contentFieldValue.data>
148 <#attempt>
149 <#assign stars = category?split("*")[0]?number>
150 <#recover>
151 <#assign stars = 0>
152 </#recover>
153 </#if>
154 'stars': ${stars},
155 <#assign isCircuitField = getFieldByName(content.contentFields, "isCircuit")>
156 <#if isCircuitField?has_content>
157 <#assign isCircuit = isCircuitField.contentFieldValue.data>
158 </#if>
159 'isCircuit': ${isCircuit!""},
160 <#assign isCruiseField = getFieldByName(content.contentFields, "isCruise")>
161 <#if isCruiseField?has_content>
162 <#assign isCruise = isCruiseField.contentFieldValue.data>
163 </#if>
164 'isCruise': ${isCruise!""},
165 <#assign cruiseFeeDiscountField = getFieldByName(content.contentFields, "cruiseFeeDiscount")>
166 <#assign cruiseFeeDiscountValue = 0>
167 <#if cruiseFeeDiscountField?has_content>
168 <#attempt>
169 <#assign cruiseFeeDiscountValue = cruiseFeeDiscountField.contentFieldValue.data?number>
170 <#recover>
171 <#assign cruiseFeeDiscountValue = 0>
172 </#recover>
173 </#if>
174 'cruiseFeeDiscount': ${cruiseFeeDiscountValue?int?abs},
175 },
176 </#list>
177 ];
178 console.log(hotelElements);
179 //let currentDestination = undefined;
180
181 new Swiper(".our-accomodations__wrapper .swiper", {
182 direction: 'horizontal',
183 spaceBetween: 0,
184 slidesPerView: 'auto',
185 centeredSlides: true,
186 breakpoints: {
187 768: {
188 allowTouchMove: false,
189 }
190 },
191 navigation: {
192 nextEl: '.swiper-button-next',
193 prevEl: '.swiper-button-prev',
194 },
195 pagination: {
196 el: '.swiper-pagination',
197 },
198 keyboard: {
199 enabled: true,
200 onlyInViewport: true,
201 },
202 });
203
204 async function fetchLiferayDocument(url) {
205 return fetchLiferayApi("documents/d/guest/" + url);
206 }
207
208 function numberWithCommas (x){
209 return x.toFixed(Number.isInteger(x) ? 0 : 2).toString().replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, ".");
210 }
211
212 /* Ajustar el precio para cruceros para descontar las tasas que vienen incluidas en el datos desde API */
213 function getPriceExcludingCruiseFees(hotelApiData){
214 /*
215 busco en los elementos globales el elemento [hotelElements] (hotel, circuito o crucero) que
216 tenga el mismo código que el hotelApiData
217 */
218 const code = +hotelApiData.code;
219 const hotel = hotelElements.find(h => +h.code === code);
220 const price = Number(hotelApiData.bestPrice);
221 const cruiseFeeDiscount = Math.abs(Number(hotel?.cruiseFeeDiscount || 0));
222
223 // Si no se encuentra hotel, no es crucero o el precio no es numerico, devolvemos el precio del API sin tocar
224 if (!hotel || !hotel.isCruise || !Number.isFinite(price) ) return hotelApiData.bestPrice;
225
226 // Cruceros: el API trae tasas incluidas, las descontamos
227 return price - cruiseFeeDiscount;
228 }
229
230 async function loadHotelsPrice() {
231 const hotelsResult = await fetchLiferayDocument("hotels");
232 const hotelsJson = await hotelsResult.json();
233
234 const hotelElementsDom = document.querySelectorAll("span[data-hotel]");
235 hotelElementsDom.forEach(hotelElement => {
236 const hotelCode = hotelElement.dataset.hotel;
237 const hotel = hotelsJson.find(hotel => hotel.code === +hotelCode);
238 if (!!hotel && !!hotel.bestPrice) {
239 let updatedPrice = getPriceExcludingCruiseFees(hotel);
240 hotelElement.innerText = numberWithCommas(updatedPrice) + "€";
241 //hotelElement.innerText = numberWithCommas(hotel.bestPrice) + "€";
242
243 const parent = hotelElement.closest(".data__wrapper__footer");
244 parent.classList.remove("d-none");
245 }
246 })
247 }
248
249 loadHotelsPrice();
250
251})(); //IIFE
252
253</script>
Conoce Noruega
Noruega combina una deslumbrante naturaleza de fiordos y auroras boreales con una calidad de vida que la sitúa entre las más altas del planeta. Sus ciudades equilibran modernidad, diseño vanguardista y una vibrante oferta cultural con el respeto absoluto por su entorno natural.
Es el destino perfecto para quienes buscan explorar paisajes majestuosos, sumergirse en una rica historia vikinga y disfrutar de un ambiente acogedor, seguro y cosmopolita.
INFORMACIÓN DE INTERÉS

DOCUMENTACIÓN
Los ciudadanos españoles precisan llevar el documento de identidad válido o pasaporte en vigor.
CAMBIO
La moneda oficial es la Corona Noruega o Kroner.
VESTUARIO
Se aconseja ropa y calzado cómodo, apropiados para la estación del año en la que se visite.
ELECTRICIDAD
230 voltios.
REFERENCIA HORARIA
Se aplica la hora Central de Europa (CET), misma que en España.
TELÉFONO
Código de acceso desde España: + 47
Código de acceso desde Noruega: +34
LOS IMPRESCINDIBLES DE NORUEGA

OSLO
Oslo, capital de Noruega, es una pequeña ciudad que ha sabido crecer sin perder sus señas de identidad, llegando a convertirse en una de las capitales más bellas de Europa. Aunque se trata de una ciudad muy moderna, aún conserva la magia de los pequeños pueblos y desde el centro de la ciudad es posible llegar hasta un bosque en cuestión de minutos.
LILLEHAMMER
La zona de Lillehammer se encuentra aproximadamente dos horas por carretera al norte de Oslo. La localidad es famosa por haber acogido los Juegos olímpicos de invierno en 1994. La zona, con montañas, ríos y el lago más grande de Noruega, es un auténtico paraíso para realizar actividades al aire libre.
REGIÓN DE GELIO
Geilo es un pequeño pueblo de montaña de unos 2500 habitantes, a mitad de camino entre Oslo y Bergen. Se encuentra en un valle con cordilleras montañosas a cada lado, y viajando en tren desde su estación resulta una experiencia inolvidable por su increíble paisaje. Es conocido por su estación de esquí en invierno, y tiene otras actividades como senderismo, ciclismo y pesca en verano. ¡Te gustará!.

BORGUND
Borgund es conocido principalmente por su famosa iglesia de madera, la Borgund Stave Church, que es una de las mejor conservadas de Noruega. Esta iglesia, construida en el siglo XII, es un excelente ejemplo de la arquitectura de iglesias de estacas, con su distintivo diseño en forma de barco y elaborados detalles decorativos. El entorno natural que rodea a Borgund es igualmente impresionante, con montañas y paisajes típicos de Noruega que ofrecen oportunidades para el senderismo y la exploración. Además, el área tiene un ambiente tranquilo y pintoresco, ideal para quienes buscan escapar del bullicio de la vida urbana.
STAVANGER/ZONA DE SOLA
Es la cuarta ciudad más grande de Noruega y es conocida como la capital del petróleo por su importancia en la exploración de petróleo, capital de la cultura y la gastronomía, famosa por su arte callejero … es una acogedora ciudad con mucho que ofrecer. Calles adoquinadas, una catedral de casi 900 años de antigüedad, «Fargegaten» (la calle colorida), y un casco antiguo con el asentamiento de casas de madera mejor conservado de Europa, que incluye más de 170 casas, muchas de las cuales datan del s. XVIII. En 2008 Stavanger fue designada Capital Europea de la Cultura, lo que refleja la gran variedad de eventos culturales y actividades en la región.

FIORDOS DE LOS SUEÑOS
La región de los fiordos en Noruega es uno de los destinos turísticos más demandados. Esta región es un icono de la belleza natural del norte de Europa. alberga ciudades con una historia y cultura dignas de conocer con una excelente gastronomía y ambiente nocturno.
ZONA ASERAL
Aseral se encuentra en la región de Agder y es conocido por su hermosa naturaleza, incluyendo montañas y lagos. Es un lugar ideal para disfrutar de actividades al aire libre como senderismo y pesca.
BERGEN
La conocida «puerta de los fiordos», con un centro histórico envidiable además de contar con el barrio de Bryggen, patrimonio mundial de la UNESCO, y por su animada vida cultural. Bergen siempre ha tenido carácter propio y por eso ha sido capaz de mantener toda su esencia y toda su historia pese a seguir siendo la segunda localidad con más habitantes de Noruega. Bucear por sus calles, es una auténtica delicia.