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 Marrakech
DESCARGAR ITINERARIO¡Marrakech, la joya de Marruecos! Esta ciudad imperial es un importante centro económico con mezquitas, palacios y jardines, tiene una historia rica y enriquecedora gracias a las sucesivas dinastías que la han poblado. Su prestigio es tal, que se ha convertido en el nombre que identifica a todo el país. Marrakech es un centro cultural vibrante y una capital turística de renombre, la medina es una ciudad medieval amurallada y densamente poblada que data del período del Imperio Bereber, con pasajes que parecen laberintos y animados zocos que venden cerámicas, joyas y tejidos tradicionales, siendo el destino principal de aquellos que viajan a Marruecos en busca de experiencias inolvidables.
INFORMACIÓN DE INTERÉS

DOCUMENTACIÓN
Pasaporte con una vigencia mínimo de 6 meses.
CAMBIO
La moneda oficial es el Dirham.
VESTUARIO
Calzados cómodos y ropa ligera, adecuada a la estación en la que viajemos.
ELECTRICIDAD
El voltaje del suministro eléctrico normalizado es de 220 voltios.
REFERENCIA HORARIA
La hora local es 1 hora menos que en España.
TELÉFONO
Código de llamada desde España: (+212)
Código de llamada desde Marruecos: (+34)
"LOS IMPRESCINDIBLES" DE MARRAKECH

PLAZA JEMAA EL FNA
Es el corazón vibrante de Marrakech, Patrimonio Cultural de la Humanidad por la UNESCO, que se transforma día y noche. Es un lugar para sumergirse en la cultura, disfrutar de la gastronomía local y observar la vida de la plaza desde las terrazas de los cafés y restaurantes. Todas las rutas de la antigua medina terminan y empiezan en esta plaza, repleta de puestos de lo más variopinto.
VALLE DE OURIKA
Es uno de los destinos naturales más populares para quienes buscan escapar del bullicio de la ciudad para disfrutar de paisajes verdes y frescos y explorar las actividades al aire libre o aprender sobre la vida y costumbres en la región. Este valle ofrece una mezcla única de naturaleza, senderos para caminar, cascadas, cultura bereber y pequeños pueblos llenos de tradición.
PALMERAL DE MARRAKECH
Es un gran oasis de palmeras y sigue siendo una fuente de riqueza para la ciudad. Además de los dátiles y la madera que se obtiene de las propias palmeras, éstas crean un buen ecosistema para los árboles frutales y las plantaciones que allí conviven, se considera el más extenso del mundo y ofrece a los visitantes la oportunidad de experimentar la naturaleza.

MARRAKECH GUELIZ
Es la zona nueva de la ciudad, el corazón económico de Marrakech alberga la mayor parte de las sedes de instituciones y empresas. Emblemático de la vida cultural y un lugar propicio para el descanso con sus amplias terrazas de café y numerosas tiendas. Aunque no tiene la historia ni la monumentalidad de la medina, pasear o recorrer en coche este sector puede resultar interesante para apreciar el contraste entre lo antiguo y lo nuevo.
ZOCO
Es una experiencia en sí misma, donde se puede apreciar una enorme variedad de productos artesanales, desde alfombras, cueros, especias, joyas y telas hasta cerámica y objetos de decoración, dulces, comida típica y empaparse de la cultura de la ciudad. Un extenso laberinto de mercados tradicionales, destino turístico esencial que ofrece una experiencia sensorial completa.