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 Portugal Monumental
DESCARGAR ITINERARIOSalidas desde Madrid. Consultar salidas desde otros aeropuertos.
Portugal está situado en el extremo sudoeste de Europa e incluye los archipiélagos de Madeira y Azores en el Océano Atlántico. Este país ha sabido conjugar la belleza de sus paradisíacas playas y sus ciudades cosmopolitas con un interior rural y tradicional y es por ello que es conocida como "el jardín junto al océano". Y mucho más que un jardín, un territorio que salpica de maravillas naturales toda su extensión. Existen un sinfín de castillos, villas fortificadas, campos de olivos y encinas, pequeñas calas y coquetos puertos donde sumergirse en la sosegada vida de esta región.
Otro de los encantos de Portugal es su vida local, y esta se encuentra en las pequeñas villas esparcidas aquí y allá. Pero si hay municipios que llaman la atención son los pueblos medievales, con un gran encanto para los visitantes, con sus cascos amurallados, su acento portugués, su música de fondo y su gastronomía envidiable, su historia, sus costumbres y sus fiestas, sin olvidar su arquitectura y sus pequeños y encantadores negocios, regentados por habitantes simpáticos y risueños.
"LOS IMPRESCINDIBLES" DE PORTUGAL MONUMENTAL

FÁTIMA
El Santuario de Fátima está en la ciudad del mismo nombre y se encuentra ubicada en la región centro de Portugal. Lugar sagrado para los católicos y visita cultural obligada para todo viajero en Portugal, el Santuario de Fátima es un complejo con gran número de monumentos religiosos para ser visitados; no sólo podrás conocer el lugar de las apariciones católicas más significativas de la historia reciente, también otros espacios de culto, formación, cultura y memoria.
COIMBRA
Una de las ciudades más importantes de Portugal, Coímbra, con su magnífica ubicación a orillas del Mondego, fue en el pasado la capital del reino portugués. Sigue siendo una ciudad profundamente fascinante, con una historia gloriosa, fantásticos monumentos y hitos históricos. La antigua Universidad de Coímbra, clasificada por la UNESCO como Patrimonio de la Humanidad, es un faro del conocimiento, así como un símbolo perdurable del mundo de expresión portuguesa
ÓBIDOS
Es un bello museo al aire libre, un pueblo de casas blancas dentro de un castillo medieval en el que el tiempo parece haberse parado. Pero este es también un lugar lleno de vida, además de arqueología. Pasee por los arenales de una gran laguna, pruebe uno de los mejores licores portugueses y déjese llevar por el misticismo de las antiguas murallas de Óbidos.

LISBOA
Situada en un enclave entre siete colinas, esta ciudad ideal para caminar se erige como una alternativa fascinante a las capitales más populares de Europa. La animada vida nocturna de Lisboa, sus alegres mercados y sus vibrantes museos proporcionan montones de opciones entre las que no pueden faltar unas copas de vino de Oporto, una buena ración de bacalao y pastéis de nata.
LEIRIA
Seguramente una de las ciudades más sorprendentes del Centro de Portugal, Leiria, bellamente situada junto al río Lis, es una ciudad llena de curiosidades. Una ciudad que siempre ha sido un poderoso centro de creación cultural, sede de un sinfín de asociaciones culturales, profundamente comprometidas con el cambio del mundo a través del conocimiento