Estamos mejorando nuestra Tienda, Portal de clientes y Aenormas para ofrecerte una mejor experiencia.
Si detectas cualquier incidencia o necesitas ayuda durante el proceso de compra, puedes contactarnos a través del chat o escribirnos a normas@aenor.com.
Nuestro equipo estará encantado de ayudarte.
DIN 6730:2003-08
Paper and board - Vocabulary
Se ha producido un error al procesar la plantilla.
The following has evaluated to null or missing:
==> languageEntries?filter(entry -> entry.key == keyValue)?first [in template "34352066712900#33336#null" at line 165, column 30]
----
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 match = languageEntries?filte... [in template "34352066712900#33336#null" in function "addAllMatchingLanguagesByField" at line 165, column 13]
----
1<#-- Variables -->
2<#assign isDebug = false>
3<#assign channelResponse = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels?filter=name eq 'Aenor Tienda'")>
4<#assign channel = channelResponse.items[0]>
5<#assign channelId = channel.id>
6<#assign product = getProduct(channelId, CPDefinition_cProductId.getData()) />
7<#assign siteGroup = themeDisplay.getSiteGroup() />
8<#assign currentLocale = themeDisplay.getLocale()>
9<#assign currentLanguage = currentLocale?substring(0,2)>
10
11<#-- Product data -->
12<#assign displayDateProduct = CPDefinition_displayDate.getData() />
13<#assign productId = product.productId />
14<#assign cpDefinitionId = product.id />
15
16<#assign categoriesProduct = getProductCategories(channelId, productId) />
17<#assign hasProductCategoriaTipoEntidadLibro = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'libro') />
18<#assign hasProductCategoriaTipoEntidadNorma = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'norma') />
19<#assign hasProductCategoriaTipoEntidadColeccionTematica = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'coleccion tematica') />
20<#assign hasProductCategoriaTipoOrganismoSAE = isVocabularyNameIntoCategories(categoriesProduct, 'organismos', 'SAE') />
21
22<#assign categoryProductInfoTematica = getCategoriesByVocabularyAsString(categoriesProduct, "temáticas", " / ", "title") />
23<#assign categoryProductInfoStatus = getCategoriesByVocabularyAsString(categoriesProduct, "status", " / ", "title") />
24
25<#-- PickList de languages -->
26<#assign ercOfListTypeEntryLanguages = 'IDIOMAS_NORMAS_PICKLIST' />
27<#assign languageEntries = getListTypeEntriesByERC(ercOfListTypeEntryLanguages) />
28
29<#-- Specifications/Specifications language -->
30<#assign specificationsLanguagesProduct = getSpecificationsProduct(channelId, productId, 'specificationKey', 'standard-languages') />
31<#assign filteredSpecificationsLanguagesProductTemp = filterOutItems(specificationsLanguagesProduct, 'value', ['BI', 'TR']) />
32<#assign filteredSpecificationsLanguagesProduct = addAllMatchingLanguagesByField(filteredSpecificationsLanguagesProductTemp, languageEntries, 'value', 'title') />
33
34<#-- Specifications/Specifications others -->
35<#assign specificationsCTNProduct = getSpecificationsProduct(channelId, productId, 'specificationKey', 'ctn') />
36<#assign specificationsICSProduct = getSpecificationsProduct(channelId, productId, 'specificationKey', 'ics') />
37<#assign specificationsCurrentStateDateProduct = getSpecificationsProduct(channelId, productId, 'specificationKey', 'current-state-date') />
38
39<#-- Standard/Norma data -->
40<#assign standardRelationsProduct = getStandardRelationsProduct(cpDefinitionId) />
41<#assign standardInfoProduct = getStandardInfoProduct(cpDefinitionId) />
42
43<#assign ercOfListTypeEntryTipoRelacionesNormasSections = 'TIPO_RELACIONES_NORMAS-SECTIONS' />
44<#assign tipoRelacionesNormasSectionsEntries = getListTypeEntriesByERC(ercOfListTypeEntryTipoRelacionesNormasSections) />
45<#assign ercOfListTypeEntryTipoRelacionesNormasTypes = 'TIPO_RELACIONES_NORMAS-TYPE' />
46<#assign tipoRelacionesNormasTypesEntries = getListTypeEntriesByERC(ercOfListTypeEntryTipoRelacionesNormasTypes) />
47<#assign standardRelationsTypesProduct = getStandardRelationsTypesProduct(tipoRelacionesNormasSectionsEntries, tipoRelacionesNormasTypesEntries) />
48
49
50<#-- Functions -->
51<#function getProduct channelId productId>
52 <#return restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}")>
53</#function>
54
55
56<#function getProductByERC erc>
57 <#-- TODO: estamos usando el endpoint del product admin, hay que usar la del Product de Liferay NO admin: headless-commerce-delivery-catalog -->
58 <#-- <#return restClient.get("/headless-commerce-admin-catalog/v1.0/products/by-externalReferenceCode/${erc}")> -->
59 <#assign response = restClient.get("/headless-commerce-admin-catalog/v1.0/products/by-externalReferenceCode/${erc}")>
60 <#-- Si el producto no existe o tiene status NOT_FOUND, devolvemos string vacío -->
61 <#if response?? && response.status?? && response.status == "NOT_FOUND">
62 <#return {}>
63 <#elseif !response??>
64 <#return {}>
65 <#else>
66 <#return response>
67 </#if>
68</#function>
69
70
71<#function getURLsOfProduct product baseURL="" siteFriendlyURL="" languageFieldName="language" urlFieldName="url">
72 <#-- Inicializamos un array vacío -->
73 <#assign urlsArray = []>
74
75 <#-- Validamos que product y product.urls existan -->
76 <#if product?? && product.urls??>
77 <#-- Iteramos sobre los idiomas -->
78 <#list product.urls?keys as lang>
79 <#assign urlValue = product.urls[lang] />
80 <#if urlValue?? && urlValue?has_content>
81 <#-- Aseguramos que la URL empiece con "/p/" -->
82 <#assign cleanUrl = urlValue?starts_with("/")?then(urlValue, "/p/" + urlValue) />
83
84 <#-- Generamos la URL completa -->
85 <#-- Si baseURL tiene contenido, construimos URL completa -->
86 <#if baseURL?? && baseURL?has_content>
87
88 <#-- Aseguramos que el slug empiece con /p/ -->
89 <#assign cleanUrl = urlValue?starts_with("/")?then(urlValue, "/p/" + urlValue) />
90
91 <#-- Tomamos las dos primeras letras del idioma para el prefijo -->
92 <#assign langPrefix = lang?substring(0,2)>
93
94 <#-- Construimos la URL completa -->
95 <#if siteFriendlyURL?? && siteFriendlyURL?has_content>
96 <#assign fullUrl = baseURL + "/" + langPrefix + siteFriendlyURL + cleanUrl>
97 <#else>
98 <#assign fullUrl = baseURL + "/" + langPrefix + cleanUrl>
99 </#if>
100
101 <#else>
102 <#assign fullUrl = cleanUrl>
103 </#if>
104
105 <#-- Creamos el objeto language+url -->
106 <#assign newItem = {(languageFieldName): lang, (urlFieldName): fullUrl} />
107
108 <#-- Lo agregamos al array -->
109 <#assign urlsArray = urlsArray + [newItem] />
110 </#if>
111 </#list>
112 </#if>
113
114 <#return urlsArray>
115</#function>
116
117
118<#function getProductCategories channelId productId>
119 <#return restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}/categories?sort=vocabulary").items>
120</#function>
121
122
123<#function getSpecificationsProduct channelId productId field="" value="">
124 <#assign response = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}/product-specifications?pageSize=100&sort")>
125 <#assign items = response.items>
126
127 <#-- Si se pasa campo y valor, filtrar -->
128 <#if field?has_content && value?has_content>
129 <#assign items = items?filter(item ->
130 (item[field]??) && (item[field]?string?lower_case == value?lower_case)
131 )>
132 </#if>
133
134 <#return items>
135</#function>
136
137
138<#function filterOutItems items field valuesToExclude>
139 <#-- Si el array o los valores no existen, devolver items sin cambios -->
140 <#if !items?? || !valuesToExclude??>
141 <#return items>
142 </#if>
143
144 <#-- Filtramos: solo mantener los items cuyo campo no esté en la lista -->
145 <#assign filteredItems = items?filter(item ->
146 !(item[field]?? && (valuesToExclude?seq_contains(item[field]?string)))
147 )>
148
149 <#return filteredItems>
150</#function>
151
152
153<#function addAllMatchingLanguagesByField specificationsLanguagesProduct languageEntries specValueField newSpecField>
154
155 <#if specificationsLanguagesProduct?? && languageEntries?? && specValueField?? && newSpecField??>
156
157 <#-- Creamos una copia para no modificar el original directamente -->
158 <#assign updatedSpecs = []>
159
160 <#list specificationsLanguagesProduct as spec>
161 <#-- Buscar coincidencia -->
162 <#assign keyValue = spec[specValueField]>
163 <#assign match = languageEntries?filter(entry -> entry.key == keyValue)?first>
164
165 <#-- Crear una copia del objeto actual -->
166 <#assign newSpec = spec>
167
168 <#-- Si hay match con nombre válido, añadir el nuevo campo -->
169 <#if match?? && match.name?? && match.name?has_content>
170 <#assign newSpec = newSpec + { (newSpecField): match.name }>
171 </#if>
172
173 <#-- Añadir al array final -->
174 <#assign updatedSpecs = updatedSpecs + [newSpec]>
175 </#list>
176
177 <#return updatedSpecs>
178 <#else>
179 <#return specificationsLanguagesProduct>
180 </#if>
181</#function>
182
183
184<#function getListTypeEntriesByERC erc fields="key,name,externalReferenceCode,name_i18n" sort="key" pageSize="1000">
185 <#attempt>
186 <#return restClient.get(
187 "/headless-admin-list-type/v1.0/list-type-definitions/by-external-reference-code/${erc}/list-type-entries?fields=${fields}&sort=${sort}&pageSize=${pageSize}"
188 ).items>
189 <#recover>
190 <#return []>
191 </#attempt>
192</#function>
193
194
195<#function getStandardRelationsProduct cpDefinitionId
196 ercProductFieldName="ercProduct" defaultLang = "es_ES" urlProductCurrentLanguageFieldName="urlProductCurrentLanguage" urlsProductByLangFieldName="urlsProductByLang"
197 languageFieldName="language" urlFieldName="url" sort="type">
198
199 <#assign result = [] />
200
201 <#-- Obtener relaciones desde la API -->
202 <#assign standardRelations = restClient.get("/c/standardrelationses/?filter=r_standardRelations_CPDefinitionId eq '${cpDefinitionId}'&sort=${sort}").items />
203
204 <#-- Iterar sobre cada relación -->
205 <#list standardRelations as standardRelationProduct>
206
207 <#-- Crear ERC del producto relacionado -->
208 <#assign ercProduct = (standardRelationProduct.relatedOrganismStandardName!"relatedOrganismStandardName") + "-" + (standardRelationProduct.relatedStandardId!"relatedStandardId") />
209
210 <#-- Obtener el producto por ERC -->
211 <#assign productByERC = getProductByERC(ercProduct) />
212 <#assign findProductByERC = (productByERC?? && productByERC?has_content)>
213
214 <#-- Obtener URLs del producto -->
215 <#assign baseURL = themeDisplay.getPortalURL()>
216 <#assign siteFriendlyURL = "/web" + siteGroup.getFriendlyURL()>
217 <#assign urlsProductByLang = getURLsOfProduct(productByERC, baseURL, siteFriendlyURL, languageFieldName, urlFieldName) />
218
219 <#-- Obtenemos URLs del producto default y current language -->
220 <#assign selectedURLCurrent = "">
221 <#assign selectedURLDefault = "">
222 <#list urlsProductByLang as item>
223 <#-- URL del idioma actual -->
224 <#if item.language == currentLocale && selectedURLCurrent == "">
225 <#assign selectedURLCurrent = item.url>
226 </#if>
227
228 <#-- URL del idioma por defecto -->
229 <#if item.language == defaultLang && selectedURLDefault == "">
230 <#assign selectedURLDefault = item.url>
231 </#if>
232
233 <#-- Salimos si ya tenemos ambas -->
234 <#if selectedURLCurrent != "" && selectedURLDefault != "">
235 <#break>
236 </#if>
237 </#list>
238
239 <#-- Usamos el idioma actual si existe, sino el por defecto -->
240 <#if selectedURLCurrent != "">
241 <#assign urlCurrentLanguage = selectedURLCurrent>
242 <#else>
243 <#assign urlCurrentLanguage = selectedURLDefault>
244 </#if>
245
246 <#-- Combinar todos los datos del objeto original + nuevos campos -->
247 <#assign resultItem = standardRelationProduct + {
248 (ercProductFieldName): ercProduct,
249 (urlProductCurrentLanguageFieldName): urlCurrentLanguage,
250 (urlsProductByLangFieldName): urlsProductByLang,
251 ("findProductByERC"): findProductByERC
252 } />
253
254 <#-- Añadir al array final -->
255 <#assign result = result + [resultItem] />
256 </#list>
257
258 <#return result>
259</#function>
260
261
262<#function getStandardInfoProduct cpDefinitionId>
263 <#assign response = restClient.get("/c/standardinfos/?filter=r_standardInfo_CPDefinitionId eq '${cpDefinitionId}'")!{} />
264 <#assign items = response.items![] />
265 <#return (items?size > 0)?then(items[0], {}) />
266</#function>
267
268
269<#function getStandardRelationsTypesProduct tipoRelacionesNormasSectionsEntries tipoRelacionesNormasTypesEntries>
270 <#attempt>
271
272 <#-- Obtener los items del endpoint -->
273 <#assign itemsStandardRelationsTypeProduct = restClient.get("/c/standardrelationtypeses/?pageSize=100").items>
274
275 <#-- Crear array vacío para ir acumulando los items enriquecidos -->
276 <#assign enrichedItemsStandardRelationsTypeProduct = []>
277
278 <#assign enrichedItemsStandardRelationsTypeProduct = applyTipoRelacionesNormasEntries(
279 itemsStandardRelationsTypeProduct, tipoRelacionesNormasSectionsEntries,
280 "relatedSection", "section"
281 )>
282
283 <#assign enrichedItemsStandardRelationsTypeProduct = applyTipoRelacionesNormasEntries(
284 enrichedItemsStandardRelationsTypeProduct, tipoRelacionesNormasTypesEntries,
285 "relatedType", "type"
286 )>
287
288 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
289 enrichedItemsStandardRelationsTypeProduct, "section", "name", "sectionName", "all")
290 >
291
292 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
293 enrichedItemsStandardRelationsTypeProduct, "section", "key", "sectionKey", "first")
294 >
295
296 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
297 enrichedItemsStandardRelationsTypeProduct, "section", "externalReferenceCode", "sectionKeyERC", "first", "relatedSection")
298 >
299
300 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
301 enrichedItemsStandardRelationsTypeProduct, "type", "name", "typeName", "all")
302 >
303
304 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
305 enrichedItemsStandardRelationsTypeProduct, "type", "key", "typeKey", "all")
306 >
307
308 <#assign enrichedItemsStandardRelationsTypeProduct = addKeysFieldNested(
309 enrichedItemsStandardRelationsTypeProduct, "type", "externalReferenceCode", "typeKeyERC", "all", "relatedType")
310 >
311
312 <#assign enrichedItemsStandardRelationsTypeProductSorted = sortByField(enrichedItemsStandardRelationsTypeProduct, "section?first.key")>
313 <#return enrichedItemsStandardRelationsTypeProductSorted>
314
315 <#recover>
316 <#return []>
317 </#attempt>
318</#function>
319
320
321<#function applyTipoRelacionesNormasEntries
322 itemsStandardRelationsTypeProduct tipoRelacionesNormasEntries
323 relatedEntriesFieldName="relatedEntries" putRelatiedEntriesFieldName="entries"
324 >
325
326 <#assign enrichedItems = []>
327
328 <#list itemsStandardRelationsTypeProduct as item>
329
330 <#assign relatedEntries = []>
331
332 <#list item[putRelatiedEntriesFieldName] as sec>
333 <#assign matchedEntry = {}>
334
335 <#list tipoRelacionesNormasEntries as te>
336 <#if te.key?string?trim == sec.key?string?trim>
337 <#assign matchedEntry = te>
338 <#break>
339 </#if>
340 </#list>
341
342 <#assign relatedEntry = []>
343 <#if matchedEntry?has_content>
344 <#assign relatedEntry = [ matchedEntry ]>
345 </#if>
346
347 <#assign relatedItem = sec + { (relatedEntriesFieldName): relatedEntry }>
348 <#assign relatedEntries = relatedEntries + [ relatedItem ]>
349
350 </#list>
351
352 <#assign enrichedItem = item + {
353 (putRelatiedEntriesFieldName): relatedEntries
354 }>
355
356 <#assign enrichedItems = enrichedItems + [ enrichedItem ]>
357
358 </#list>
359
360 <#return enrichedItems>
361</#function>
362
363
364<#function sortByField items fieldPath sortFieldName="sortKey" order="asc">
365
366 <#-- Array temporal con el campo auxiliar -->
367 <#assign prepared = []>
368
369 <#list items as e>
370 <#-- Evaluar el valor del campo dinámico -->
371 <#assign sortValue = "" />
372 <#if fieldPath == "section?first.key">
373 <#assign sortValue = (e.section?first.key)!""?lower_case>
374 <#elseif fieldPath == "section?first.name">
375 <#assign sortValue = (e.section?first.name)!""?lower_case>
376 <#elseif fieldPath == "type?first.key">
377 <#assign sortValue = (e.type?first.key)!""?lower_case>
378 <#else>
379 <#-- Si el campoPath no está mapeado, usar vacío -->
380 <#assign sortValue = "" />
381 </#if>
382
383 <#-- Agregar objeto enriquecido con campo auxiliar -->
384 <#assign prepared = prepared + [ e + { (sortFieldName): sortValue } ]>
385 </#list>
386
387 <#-- Ordenar -->
388 <#assign sorted = prepared?sort_by(sortFieldName)>
389
390 <#-- Si order = desc, invertir -->
391 <#if order?lower_case == "desc">
392 <#assign sorted = sorted?reverse>
393 </#if>
394
395 <#return sorted>
396</#function>
397
398
399<#function addKeysFieldNested items arrayField nestedField newFieldName mode="first" subArrayField="">
400
401 <#assign enrichedItems = []>
402
403 <#list items as e>
404 <#assign resultValue = "">
405
406 <#-- Detectamos el array del objeto principal -->
407 <#assign targetArray = e[arrayField]![]>
408
409 <#if targetArray?has_content>
410 <#if mode == "all">
411 <#assign keysList = []>
412 <#list targetArray as t>
413 <#if subArrayField?has_content>
414 <#assign subArray = t[subArrayField]![]>
415 <#if subArray?has_content>
416 <#list subArray as sub>
417 <#assign keysList = keysList + [ sub[nestedField]!"" ]>
418 </#list>
419 </#if>
420 <#else>
421 <#assign keysList = keysList + [ t[nestedField]!"" ]>
422 </#if>
423 </#list>
424 <#assign resultValue = keysList?join(", ")>
425 <#elseif mode == "first">
426 <#assign firstItem = targetArray?first>
427 <#if subArrayField?has_content>
428 <#assign subArray = firstItem[subArrayField]![]>
429 <#if subArray?has_content>
430 <#assign resultValue = subArray?first[nestedField]!"" >
431 <#else>
432 <#assign resultValue = "" >
433 </#if>
434 <#else>
435 <#assign resultValue = firstItem[nestedField]!"" >
436 </#if>
437 </#if>
438 <#else>
439 <#assign resultValue = "">
440 </#if>
441
442 <#-- Añadimos el nuevo campo al objeto -->
443 <#assign enrichedItems = enrichedItems + [
444 e + { (newFieldName): resultValue }
445 ]>
446 </#list>
447
448 <#return enrichedItems>
449</#function>
450
451
452<#function isVocabularyNameIntoCategories categories vocabulary name>
453 <#assign found = false />
454
455 <#if categories?has_content && vocabulary?has_content && name?has_content>
456
457 <#assign vocabNorm = normalize(vocabulary) />
458 <#assign nameNorm = normalize(name) />
459
460 <#list categories as category>
461 <#if !found>
462 <#assign catVocabNorm = normalize(category.vocabulary) />
463 <#assign catNameNorm = normalize(category.name) />
464
465 <#if catVocabNorm == vocabNorm && catNameNorm == nameNorm>
466 <#assign found = true />
467 </#if>
468 </#if>
469 </#list>
470
471 </#if>
472
473 <#return found>
474</#function>
475
476
477<#function normalize text onlyAccents = false>
478 <#-- proteger null -->
479 <#if !text?has_content>
480 <#return "">
481 </#if>
482
483 <#assign t = text />
484
485 <#-- quitar acentos -->
486 <#assign t = t
487 ?replace("á","a")?replace("é","e")?replace("í","i")
488 ?replace("ó","o")?replace("ú","u")?replace("ü","u")
489 ?replace("ñ","n")
490 ?replace("Á","A")?replace("É","E")?replace("Í","I")
491 ?replace("Ó","O")?replace("Ú","U")?replace("Ü","U")
492 ?replace("Ñ","N")
493 />
494
495 <#-- si NO es solo acentos, normalización completa -->
496 <#if !onlyAccents>
497 <#assign t = t?lower_case />
498 <#assign t = t?trim />
499 <#assign t = t?replace("\\s+", " ", "r") />
500 </#if>
501
502 <#return t>
503</#function>
504
505
506<#function filterStandardRelationsProductsByFieldType standardRelationsProducts types>
507 <#-- Función para filtrar y obtener elementos por type -->
508 <#-- Normalizamos types a lista en minúsculas -->
509 <#if types?is_string>
510 <#assign typeList = [types?lower_case]>
511 <#else>
512 <#assign typeList = types?map(t -> t?lower_case)>
513 </#if>
514
515 <#assign result = []>
516
517 <#list standardRelationsProducts as standardRelationsProduct>
518 <#if standardRelationsProduct.type?? && typeList?seq_contains(standardRelationsProduct.type?lower_case)>
519 <#assign result += [standardRelationsProduct]>
520 </#if>
521 </#list>
522
523 <#return result>
524</#function>
525
526
527<#function excludeStandardRelationsByFieldType standardRelationsProducts types>
528 <#-- Función para filtrar y quitar elementos por type -->
529 <#-- Normalizamos types a lista en minúsculas -->
530 <#if types?is_string>
531 <#assign typeList = [types?lower_case]>
532 <#else>
533 <#assign typeList = types?map(t -> t?lower_case)>
534 </#if>
535
536 <#assign result = []>
537
538 <#list standardRelationsProducts as standardRelationsProduct>
539 <#-- Solo agregamos si el type NO está en la lista -->
540 <#if !(standardRelationsProduct.type?? && typeList?seq_contains(standardRelationsProduct.type?lower_case))>
541 <#assign result += [standardRelationsProduct]>
542 </#if>
543 </#list>
544
545 <#return result>
546</#function>
547
548
549
550<#function getStandardRelationsProductsByField
551 itemStandardRelationsTypeProduct standardRelationsProducts itemStandardRelationsTypeProductField="typeKeyERC" standardRelationsProductField="type">
552
553 <#assign matched = []>
554
555 <#-- Verificar que el item tenga el campo indicado -->
556 <#if itemStandardRelationsTypeProduct[itemStandardRelationsTypeProductField]?? && itemStandardRelationsTypeProduct[itemStandardRelationsTypeProductField]?has_content>
557
558 <#-- Normalizamos (minusculas, sin espacios a al principio/final y separamos en lista si hay varios valores) -->
559 <#assign itemStandardRelationsTypeProductValues = itemStandardRelationsTypeProduct[itemStandardRelationsTypeProductField]?split(",")?map(v -> v?trim?lower_case)>
560
561 <#-- Recorrer los productos -->
562 <#list standardRelationsProducts as standardRelationsProduct>
563 <#-- Asegurar que el campo del producto existe -->
564 <#if standardRelationsProduct[standardRelationsProductField]?? && standardRelationsProduct[standardRelationsProductField]?has_content>
565
566 <#--
567 - Los elementos de [standardRelationsProducts.type] tienen datos como: REVISED_BY
568 - En los PickList, en este caso la picklist [erc: TIPO_RELACIONES_NORMAS], no permite guardar
569 keys con "_" por lo que existirá una key: REVISEDBY pero entonces comparamos por su
570 campo ERC que si permite "_", entonces tendremos como ERC: REVISED_BY y como key: REVISEDBY.
571 - Entonces buscaremos, usando de la picklist, la key (el ERC) del tipo de relación [itemStandardRelationsTypeProduct.typeKeyERC]
572 en [standardRelationsProducts.type].
573 - Convertimos el texto a minúsculas y quitamos los espacio del principio/fin.
574 - Dentro de [itemStandardRelationsTypeProduct.typeKeyERC] podemos tener
575 varios valores, por ejemplo [typeKey: REPLACED_BY, REPLACEDBY].
576 -->
577 <#assign normalizedProdValue = standardRelationsProduct[standardRelationsProductField]?trim?lower_case>
578
579 <#--
580 Verificar si coincide alguno de los valores del item StandardRelationsTypeProduct con
581 el valor normalizado de standardRelationsProduct
582 -->
583 <#if itemStandardRelationsTypeProductValues?seq_contains(normalizedProdValue)>
584 <#assign matched = matched + [standardRelationsProduct]>
585 </#if>
586 </#if>
587 </#list>
588 </#if>
589
590 <#return matched>
591</#function>
592
593
594<#-- Función que retorna los valores de categorías como string, normalizando vocabulario -->
595<#function getCategoriesByVocabularyAsString categories vocabulary separator=" / " field="name">
596 <#assign matchedValues = []>
597 <#if categories?has_content && vocabulary?has_content>
598 <#list categories as category>
599 <#-- Normalizamos tanto el vocabulary de la categoría como el buscado -->
600 <#if normalize(category.vocabulary, true)?lower_case == normalize(vocabulary, true)?lower_case>
601 <#if field == "title">
602 <#assign matchedValues += [category.title]>
603 <#else>
604 <#assign matchedValues += [category.name]>
605 </#if>
606 </#if>
607 </#list>
608 </#if>
609 <#return matchedValues?join(separator)>
610</#function>
611
612
613<#macro printObject obj>
614 <#-- Permite hacer un output de un array de objetos o un objeto que se pasa como parámetro -->
615 <#if obj?is_hash>
616 {
617 <#list obj?keys as k>
618 "${k}":
619 <#assign value = obj[k]>
620 <#if value?is_hash || value?is_sequence>
621 <@printObject obj=value/>
622 <#elseif value?is_boolean>
623 ${value?c}
624 <#elseif value?is_number>
625 ${value}
626 <#elseif value?has_content>
627 "${value?string}"
628 <#else>
629 null
630 </#if>
631 <#if k_has_next>, </#if>
632 </#list>
633 }
634 <#elseif obj?is_sequence>
635 [
636 <#list obj as item>
637 <@printObject obj=item/>
638 <#if item_has_next>, </#if>
639 </#list>
640 ]
641 <#elseif obj?is_boolean>
642 ${obj?c}
643 <#elseif obj?is_number>
644 ${obj}
645 <#elseif obj?has_content>
646 "${obj?string}"
647 <#else>
648 null
649 </#if>
650</#macro>
651
652
653<#macro renderStandardRelationsSectionsRows standardRelationsTypesProduct standardRelationsProduct typesToExclude=[] isDebug=false>
654
655 <#assign lastSectionKey = "">
656 <#assign openRow = false>
657 <#assign indexCount = 0>
658
659 <#assign filteredStandardRelationsProduct = standardRelationsProduct >
660 <#if typesToExclude?has_content>
661 <#assign filteredStandardRelationsProduct = excludeStandardRelationsByFieldType(standardRelationsProduct, typesToExclude)>
662 </#if>
663
664 <#list standardRelationsTypesProduct as standardRelationsTypeProduct>
665
666 <#assign currentSectionKey = (standardRelationsTypeProduct.sectionKey)!"" />
667 <#assign standardRelationsProductsByTypeKey = getStandardRelationsProductsByField(standardRelationsTypeProduct, filteredStandardRelationsProduct)>
668 <#assign hasElementsStandardRelationsProducts = (standardRelationsProductsByTypeKey?size > 0)>
669
670 <#if isDebug || hasElementsStandardRelationsProducts>
671
672 <#-- Si la sección cambia, cerramos la fila anterior -->
673 <#if openRow && currentSectionKey != lastSectionKey>
674 </td>
675 </tr>
676 <#assign openRow = false>
677 </#if>
678
679 <#-- Si es una nueva sección, abrimos una nueva fila -->
680 <#if !openRow>
681 <#assign indexCount = 0>
682 <tr
683 data-section-key="${currentSectionKey}"
684 data-section-name="${standardRelationsTypeProduct.sectionName}"
685 data-sort-key="${standardRelationsTypeProduct.sortKey}">
686 <th>
687 <p>${standardRelationsTypeProduct.sectionName}</p>
688 </th>
689 <td data-section-key="${currentSectionKey}">
690 <#assign openRow = true>
691 </#if>
692
693 <#if isDebug>
694 <div class="">
695 <p><strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}</p>
696 <p><strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}</p>
697 <p><strong>sectionName:</strong> ${standardRelationsTypeProduct.sectionName}</p>
698 <p><strong>sortKey:</strong> ${standardRelationsTypeProduct.sortKey}</p>
699 <p><strong>total standardRelationsProductsByTypeKey[${standardRelationsTypeProduct.typeKey}]: </strong> ${standardRelationsProductsByTypeKey?size}</p>
700
701 <p class="mb-0"><strong>standardRelationsTypeProduct:</strong></p>
702 <div class="print-object-json-content mb-3"
703 style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
704 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
705 <@printObject standardRelationsTypeProduct />
706 </div>
707
708 <p class="mb-0"><strong>standardRelationsProductsByTypeKey:</strong></p>
709 <div class="print-object-json-content mb-3"
710 style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
711 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
712 <@printObject standardRelationsProductsByTypeKey />
713 </div>
714 </div>
715 </#if>
716
717 <#if standardRelationsProductsByTypeKey?has_content>
718 <#list standardRelationsProductsByTypeKey as standardRelationProductsByTypeKey>
719 <#assign indexCount = indexCount + 1>
720 <#assign urlProductCurrentLanguage = standardRelationProductsByTypeKey.urlProductCurrentLanguage>
721
722 <p data-section-key="${currentSectionKey}"
723 data-type-key="${standardRelationsTypeProduct.typeKey}" data-type-index="${indexCount}">
724
725 ${standardRelationsTypeProduct.description}
726
727 <#--
728 <#if urlProductCurrentLanguage?? && urlProductCurrentLanguage?has_content>
729 <a href="${urlProductCurrentLanguage}" target="_blank">
730 ${standardRelationProductsByTypeKey.relatedStandardName!""}
731 </a>
732 <#else>
733 <a class="add-link-product-by-erc" data-product-erc="${standardRelationProductsByTypeKey.ercProduct}" href="" target="_blank">
734 ${standardRelationProductsByTypeKey.relatedStandardName!""}
735 </a>
736 </#if>
737 -->
738
739 <a class="add-link-product-by-erc text-decoration-none text-body" data-product-erc="${standardRelationProductsByTypeKey.ercProduct}" href="">
740 ${standardRelationProductsByTypeKey.relatedStandardName!""}
741 </a>
742
743 </p>
744
745 </#list>
746
747 <#if isDebug>
748 <p>
749 <strong>Info:</strong>SI hay (<strong>${standardRelationsProductsByTypeKey?size}</strong>) <strong>Standard/Norma Relation</strong> con el type [<strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}]
750 en la sección [<strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}]
751 </p>
752 </#if>
753 <#else>
754 <#if isDebug>
755 <p>
756 <strong>Info:</strong>NO hay <strong>Standard/Norma Relation</strong> con el type [<strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}]
757 en la sección [<strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}]
758 </p>
759 <#else>
760 <p>N/A</p>
761 </#if>
762 </#if>
763
764 <#-- Separador visual en debug entre elementos standardRelations dentro del mismo section -->
765 <#if isDebug>
766 <#-- Separador visual mejorado -->
767 <div style="margin:10px 0; padding:5px; border-top:2px dashed #007BFF; background-color:#f0f8ff;"></div>
768 </#if>
769
770 <#assign lastSectionKey = currentSectionKey>
771 </#if>
772 </#list>
773
774 <#-- Cerrar la última fila abierta -->
775 <#if openRow>
776 </td>
777 </tr>
778 </#if>
779
780</#macro>
781
782
783
784
785<#-- HTML -->
786<#if hasProductCategoriaTipoEntidadNorma>
787 <div id="ecom-norma-detail" class="ecom-norma">
788 <div class="tabs-section">
789 <div class="tab-content">
790
791 <table>
792
793 <#-- Custom object [StandardInfo] > mostrarAlerta, tituloAlerta, descAlerta (tb existe como _i18n) -->
794 <#if standardInfoProduct?? && standardInfoProduct.mostrarAlerta?? && standardInfoProduct.mostrarAlerta>
795 <tr class="alert-row">
796 <th class="alert-label">
797 <#if standardInfoProduct.tituloAlerta??>
798 <strong>${standardInfoProduct.tituloAlerta}</strong>:
799 </#if>
800 </th>
801 <td class="alert-content">
802 <#if standardInfoProduct.descAlerta??>
803 ${standardInfoProduct.descAlerta}
804 </#if>
805 </td>
806 </tr>
807 </#if>
808
809 <#-- Custom object [StandardRelations] > Buscamos si el producto tiene elementos [StandardRelations] con el campo key = ["MOD", "MODIFIED"] -->
810 <#assign findStandardRelationsProductsByTypeModified = filterStandardRelationsProductsByFieldType(standardRelationsProduct, ["MOD", "MODIFIED"])>
811 <#if findStandardRelationsProductsByTypeModified?has_content>
812 <tr class="alert-row">
813 <th class="alert-label">
814 <strong>${languageUtil.get(locale, "ecom-aviso")}</strong>:
815 </th>
816 <td class="alert-content">
817 ${languageUtil.get(locale, "ecom-aviso_modificaciones_text")}
818 </td>
819 </tr>
820 </#if>
821
822 <#-- Producto tiene [categoria: Organismo] > Si el producto tiene la [categoria: Organismo] = SAE -->
823 <#if hasProductCategoriaTipoOrganismoSAE>
824 <tr class="alert-row">
825 <th class="alert-label">
826 <span class="text-danger">
827 <strong>${languageUtil.get(locale, "ecom-aviso_pdf_secure")}</strong>:
828 </span>
829 </th>
830 <td class="alert-content">
831 ${languageUtil.get(locale, "ecom-aviso_pdf_secure_text")}
832 </td>
833 </tr>
834 </#if>
835
836 <#-- Categoria producto > Tematicas -->
837 <#if categoryProductInfoTematica?has_content>
838 <tr>
839 <th>${languageUtil.get(locale, "ecom-tematicas")}:</th>
840 <td>${categoryProductInfoTematica}</td>
841 </tr>
842 </#if>
843
844 <#-- Fecha edicion del producto > displayDate del Producto -->
845 <tr>
846 <th>${languageUtil.get(locale, "ecom-fecha_edicion")}:</th>
847 <td>
848
849 <#-- Comentado provisonalmente hasta cambio en integracion
850 <#if displayDateProduct?? && displayDateProduct?has_content>
851 ${displayDateProduct?datetime("d/MM/yy H:mm")?string("yyyy-MM-dd")}
852 <#else>
853 -
854 </#if>
855 -->
856
857 ${specificationsCurrentStateDateProduct
858 ?filter(spec -> spec.value??
859 && spec.value?has_content
860 && spec.value?length == 8)
861 ?map(spec -> spec.value?date("yyyyMMdd")?string("yyyy-MM-dd"))
862 ?join(", ")}
863
864 <#if categoryProductInfoStatus?? && categoryProductInfoStatus?has_content>
865 <#assign statusTagClass = ''>
866
867 <#if categoryProductInfoStatus?trim?upper_case == 'EN VIGOR'>
868 <#assign statusTagClass = 'tag-success'>
869 <#elseif categoryProductInfoStatus?trim?upper_case == 'ANULADA'>
870 <#assign statusTagClass = 'tag-danger'>
871 <#elseif categoryProductInfoStatus?trim?upper_case == 'PROYECTO'>
872 <#assign statusTagClass = 'tag-blue'>
873 </#if>
874
875 <#if statusTagClass?? && statusTagClass?has_content>
876 <#assign statusTagClass = "status-standard " + statusTagClass>
877 </#if>
878
879 <div class="badge ${statusTagClass}">${categoryProductInfoStatus}</div>
880 </#if>
881
882 </td>
883 </tr>
884
885 <#if categoryProductInfoStatus?trim?upper_case == 'ANULADA'>
886 <#-- Especificacion del producto > current-state-date (Nos indicaron que es la fecha de anulacion de la ficha) -->
887 <#if specificationsCurrentStateDateProduct?has_content>
888 <tr>
889 <th>${languageUtil.get(locale, "ecom-cancellationDate")}:</th>
890 <td>
891
892 ${specificationsCurrentStateDateProduct
893 ?filter(spec -> spec.value??
894 && spec.value?has_content
895 && spec.value?length == 8)
896 ?map(spec -> spec.value?date("yyyyMMdd")?string("yyyy-MM-dd"))
897 ?join(", ")}
898
899 </td>
900 </tr>
901 </#if>
902 </#if>
903
904 <#-- Custom object [StandardInfo] > confirmationDate -->
905 <#if standardInfoProduct?? && standardInfoProduct.confirmationDate??
906 && standardInfoProduct.confirmationDate?has_content && !standardInfoProduct.confirmationDate?starts_with("0001-01-01")>
907 <tr>
908 <th>${languageUtil.get(locale, "ecom-confirmationDate")}:</th>
909 <td>${standardInfoProduct.confirmationDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
910 </tr>
911 </#if>
912
913 <#-- Custom object [StandardInfo] > correctionDate -->
914 <#if standardInfoProduct?? && standardInfoProduct.correctionDate??
915 && standardInfoProduct.correctionDate?has_content && !standardInfoProduct.correctionDate?starts_with("0001-01-01")>
916 <tr>
917 <th>${languageUtil.get(locale, "ecom-correctionDate")}:</th>
918 <td>${standardInfoProduct.correctionDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
919 </tr>
920 </#if>
921
922 <#-- Custom object [StandardInfo] > ratificationDate -->
923 <#if standardInfoProduct?? && standardInfoProduct.ratificationDate??
924 && standardInfoProduct.ratificationDate?has_content && !standardInfoProduct.ratificationDate?starts_with("0001-01-01")>
925 <tr>
926 <th>${languageUtil.get(locale, "ecom-ratificationDate")}:</th>
927 <td>${standardInfoProduct.ratificationDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
928 </tr>
929 </#if>
930
931 <#-- Especificacion del producto > standard-languages (se aplica filtro para idiomas combinados) -->
932 <#if filteredSpecificationsLanguagesProduct?has_content>
933 <tr>
934 <th>${languageUtil.get(locale, "ecom-idiomas_disponibles")}:</th>
935 <td>${filteredSpecificationsLanguagesProduct?map(spec -> spec.title)?join(", ")}</td>
936 </tr>
937 </#if>
938
939 <#-- Custom object [StandardInfo] > resumen -->
940 <#if standardInfoProduct?? && standardInfoProduct.resumen?has_content>
941 <tr>
942 <th class="th-content">${languageUtil.get(locale, "ecom-resumen")}:</th>
943 <td class="td-content">${htmlUtil.unescape(standardInfoProduct.resumen)}</td>
944 </tr>
945 </#if>
946
947 <#-- Product > expando.keywords -->
948 <#if product?? && product.expando?? && product.expando.keywords?has_content>
949 <tr>
950 <th class="th-content">${languageUtil.get(locale, "ecom-keywords")}:</th>
951 <td class="td-content">${product.expando.keywords}</td>
952 </tr>
953 </#if>
954
955 <#-- Custom object [StandardInfo] > scope -->
956 <#if standardInfoProduct?? && standardInfoProduct.scope?has_content>
957 <tr>
958 <th class="th-content">${languageUtil.get(locale, "ecom-scope")}:</th>
959 <td class="td-content">${standardInfoProduct.scope}</td>
960 </tr>
961 </#if>
962
963 <#-- Especificacion del producto > ics -->
964 <#if specificationsICSProduct?has_content>
965 <tr>
966 <th>${languageUtil.get(locale, "ecom-ics")}:</th>
967 <td>
968 ${specificationsICSProduct?filter(spec -> spec.value?? && spec.value?has_content)?map(spec -> spec.value)?join(", ")}
969 </td>
970 </tr>
971 </#if>
972
973 <#-- Especificacion del producto > ctn -->
974 <#if specificationsCTNProduct?? && specificationsCTNProduct?size gt 0>
975 <tr>
976 <th>${languageUtil.get(locale, "ecom-ctn")}:</th>
977 <td>
978 ${specificationsCTNProduct?filter(spec -> spec.value?? && spec.value?has_content)?map(spec -> spec.value)?join(", ")}
979 </td>
980 </tr>
981 </#if>
982
983 <#-- Para imprimir el contenido de los Objects -->
984 <#if isDebug>
985 <div class="">
986 <p><strong>standardRelationsProduct:</strong></p>
987 <div class="print-object-json-content mb-3" style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
988 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
989 <@printObject standardRelationsProduct />
990 </div>
991 </div>
992 </#if>
993
994 <@renderStandardRelationsSectionsRows
995 standardRelationsTypesProduct = standardRelationsTypesProduct
996 standardRelationsProduct = standardRelationsProduct
997 typesToExclude = ["REFERENCE"]
998 isDebug = isDebug
999 />
1000
1001 </table>
1002 </div>
1003 </div>
1004 </div>
1005</#if>
Se ha producido un error al procesar la plantilla.
The following has evaluated to null or missing:
==> languageEntries?filter(entry -> entry.key == keyValue)?first [in template "34352066712900#33336#null" at line 149, column 30]
----
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 match = languageEntries?filte... [in template "34352066712900#33336#null" in function "addAllMatchingLanguagesByField" at line 149, column 13]
----
1<#-- Variables -->
2<#assign isDebug = false>
3<#assign channelResponse = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels?filter=name eq 'Aenor Tienda'")>
4<#assign channel = channelResponse.items[0]>
5<#assign channelId = channel.id>
6<#assign product = getProduct(channelId, CPDefinition_cProductId.getData()) />
7<#assign siteGroup = themeDisplay.getSiteGroup() />
8<#assign currentLocale = themeDisplay.getLocale()>
9<#assign currentLanguage = currentLocale?substring(0,2)>
10
11<#-- Product data -->
12<#assign displayDateProduct = CPDefinition_displayDate.getData() />
13<#assign productId = product.productId />
14<#assign cpDefinitionId = product.id />
15
16<#assign categoriesProduct = getProductCategories(channelId, productId) />
17<#assign hasProductCategoriaTipoEntidadLibro = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'libro') />
18<#assign hasProductCategoriaTipoEntidadNorma = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'norma') />
19<#assign hasProductCategoriaTipoEntidadColeccionTematica = isVocabularyNameIntoCategories(categoriesProduct, 'entity type', 'coleccion tematica') />
20<#assign categoryProductInfoTematica = getCategoriesByVocabularyAsString(categoriesProduct, "temáticas", " / ", "title") />
21<#assign categoryProductInfoStatus = getCategoriesByVocabularyAsString(categoriesProduct, "status", " / ", "title") />
22
23<#-- PickList de languages -->
24<#assign ercOfListTypeEntryLanguages = 'IDIOMAS_NORMAS_PICKLIST' />
25<#assign languageEntries = getListTypeEntriesByERC(ercOfListTypeEntryLanguages) />
26
27<#-- Specifications/Specifications language -->
28<#assign specificationsLanguagesProduct = getSpecificationsProduct(channelId, productId, 'specificationKey', 'standard-languages') />
29<#assign filteredSpecificationsLanguagesProductTemp = filterOutItems(specificationsLanguagesProduct, 'value', ['BI', 'TR']) />
30<#assign filteredSpecificationsLanguagesProduct = addAllMatchingLanguagesByField(filteredSpecificationsLanguagesProductTemp, languageEntries, 'value', 'title') />
31
32<#-- Thematic collection/Coleccion tematica data -->
33<#assign thematicCollectionsProduct = getThematicCollectionProduct(cpDefinitionId) />
34
35
36<#-- Functions -->
37<#function getProduct channelId productId>
38 <#return restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}")>
39</#function>
40
41
42<#function getProductByERC erc>
43 <#-- TODO: estamos usando el endpoint del product admin, hay que usar la del Product de Liferay NO admin: headless-commerce-delivery-catalog -->
44 <#-- <#return restClient.get("/headless-commerce-admin-catalog/v1.0/products/by-externalReferenceCode/${erc}")> -->
45 <#assign response = restClient.get("/headless-commerce-admin-catalog/v1.0/products/by-externalReferenceCode/${erc}")>
46 <#-- Si el producto no existe o tiene status NOT_FOUND, devolvemos string vacío -->
47 <#if response?? && response.status?? && response.status == "NOT_FOUND">
48 <#return {}>
49 <#elseif !response??>
50 <#return {}>
51 <#else>
52 <#return response>
53 </#if>
54</#function>
55
56
57<#function getURLsOfProduct product baseURL="" siteFriendlyURL="" languageFieldName="language" urlFieldName="url">
58 <#-- Inicializamos un array vacío -->
59 <#assign urlsArray = []>
60
61 <#-- Validamos que product y product.urls existan -->
62 <#if product?? && product.urls??>
63 <#-- Iteramos sobre los idiomas -->
64 <#list product.urls?keys as lang>
65 <#assign urlValue = product.urls[lang] />
66 <#if urlValue?? && urlValue?has_content>
67 <#-- Aseguramos que la URL empiece con "/p/" -->
68 <#assign cleanUrl = urlValue?starts_with("/")?then(urlValue, "/p/" + urlValue) />
69
70 <#-- Generamos la URL completa -->
71 <#-- Si baseURL tiene contenido, construimos URL completa -->
72 <#if baseURL?? && baseURL?has_content>
73
74 <#-- Aseguramos que el slug empiece con /p/ -->
75 <#assign cleanUrl = urlValue?starts_with("/")?then(urlValue, "/p/" + urlValue) />
76
77 <#-- Tomamos las dos primeras letras del idioma para el prefijo -->
78 <#assign langPrefix = lang?substring(0,2)>
79
80 <#-- Construimos la URL completa -->
81 <#if siteFriendlyURL?? && siteFriendlyURL?has_content>
82 <#assign fullUrl = baseURL + "/" + langPrefix + siteFriendlyURL + cleanUrl>
83 <#else>
84 <#assign fullUrl = baseURL + "/" + langPrefix + cleanUrl>
85 </#if>
86
87 <#else>
88 <#assign fullUrl = cleanUrl>
89 </#if>
90
91 <#-- Creamos el objeto language+url -->
92 <#assign newItem = {(languageFieldName): lang, (urlFieldName): fullUrl} />
93
94 <#-- Lo agregamos al array -->
95 <#assign urlsArray = urlsArray + [newItem] />
96 </#if>
97 </#list>
98 </#if>
99
100 <#return urlsArray>
101</#function>
102
103
104<#function getProductCategories channelId productId>
105 <#return restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}/categories?sort=vocabulary").items>
106</#function>
107
108
109<#function getSpecificationsProduct channelId productId field="" value="">
110 <#assign response = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${productId}/product-specifications?pageSize=100&sort")>
111 <#assign items = response.items>
112
113 <#-- Si se pasa campo y valor, filtrar -->
114 <#if field?has_content && value?has_content>
115 <#assign items = items?filter(item ->
116 (item[field]??) && (item[field]?string?lower_case == value?lower_case)
117 )>
118 </#if>
119
120 <#return items>
121</#function>
122
123
124<#function filterOutItems items field valuesToExclude>
125 <#-- Si el array o los valores no existen, devolver items sin cambios -->
126 <#if !items?? || !valuesToExclude??>
127 <#return items>
128 </#if>
129
130 <#-- Filtramos: solo mantener los items cuyo campo no esté en la lista -->
131 <#assign filteredItems = items?filter(item ->
132 !(item[field]?? && (valuesToExclude?seq_contains(item[field]?string)))
133 )>
134
135 <#return filteredItems>
136</#function>
137
138
139<#function addAllMatchingLanguagesByField specificationsLanguagesProduct languageEntries specValueField newSpecField>
140
141 <#if specificationsLanguagesProduct?? && languageEntries?? && specValueField?? && newSpecField??>
142
143 <#-- Creamos una copia para no modificar el original directamente -->
144 <#assign updatedSpecs = []>
145
146 <#list specificationsLanguagesProduct as spec>
147 <#-- Buscar coincidencia -->
148 <#assign keyValue = spec[specValueField]>
149 <#assign match = languageEntries?filter(entry -> entry.key == keyValue)?first>
150
151 <#-- Crear una copia del objeto actual -->
152 <#assign newSpec = spec>
153
154 <#-- Si hay match con nombre válido, añadir el nuevo campo -->
155 <#if match?? && match.name?? && match.name?has_content>
156 <#assign newSpec = newSpec + { (newSpecField): match.name }>
157 </#if>
158
159 <#-- Añadir al array final -->
160 <#assign updatedSpecs = updatedSpecs + [newSpec]>
161 </#list>
162
163 <#return updatedSpecs>
164 <#else>
165 <#return specificationsLanguagesProduct>
166 </#if>
167</#function>
168
169
170<#function getListTypeEntriesByERC erc fields="key,name,externalReferenceCode,name_i18n" sort="key" pageSize="1000">
171 <#attempt>
172 <#return restClient.get(
173 "/headless-admin-list-type/v1.0/list-type-definitions/by-external-reference-code/${erc}/list-type-entries?fields=${fields}&sort=${sort}&pageSize=${pageSize}"
174 ).items>
175 <#recover>
176 <#return []>
177 </#attempt>
178</#function>
179
180
181<#function getThematicCollectionProduct cpDefinitionId>
182 <#assign response = restClient.get("/c/thematiccollections/?filter=r_thematicCollection_CPDefinitionId eq '${cpDefinitionId}'")!{} />
183 <#assign items = response.items![] />
184 <#return (items?size > 0)?then(items[0], {}) />
185</#function>
186
187
188<#function sortByField items fieldPath sortFieldName="sortKey" order="asc">
189
190 <#-- Array temporal con el campo auxiliar -->
191 <#assign prepared = []>
192
193 <#list items as e>
194 <#-- Evaluar el valor del campo dinámico -->
195 <#assign sortValue = "" />
196 <#if fieldPath == "section?first.key">
197 <#assign sortValue = (e.section?first.key)!""?lower_case>
198 <#elseif fieldPath == "section?first.name">
199 <#assign sortValue = (e.section?first.name)!""?lower_case>
200 <#elseif fieldPath == "type?first.key">
201 <#assign sortValue = (e.type?first.key)!""?lower_case>
202 <#else>
203 <#-- Si el campoPath no está mapeado, usar vacío -->
204 <#assign sortValue = "" />
205 </#if>
206
207 <#-- Agregar objeto enriquecido con campo auxiliar -->
208 <#assign prepared = prepared + [ e + { (sortFieldName): sortValue } ]>
209 </#list>
210
211 <#-- Ordenar -->
212 <#assign sorted = prepared?sort_by(sortFieldName)>
213
214 <#-- Si order = desc, invertir -->
215 <#if order?lower_case == "desc">
216 <#assign sorted = sorted?reverse>
217 </#if>
218
219 <#return sorted>
220</#function>
221
222
223<#function addKeysFieldNested items arrayField nestedField newFieldName mode="first" subArrayField="">
224
225 <#assign enrichedItems = []>
226
227 <#list items as e>
228 <#assign resultValue = "">
229
230 <#-- Detectamos el array del objeto principal -->
231 <#assign targetArray = e[arrayField]![]>
232
233 <#if targetArray?has_content>
234 <#if mode == "all">
235 <#assign keysList = []>
236 <#list targetArray as t>
237 <#if subArrayField?has_content>
238 <#assign subArray = t[subArrayField]![]>
239 <#if subArray?has_content>
240 <#list subArray as sub>
241 <#assign keysList = keysList + [ sub[nestedField]!"" ]>
242 </#list>
243 </#if>
244 <#else>
245 <#assign keysList = keysList + [ t[nestedField]!"" ]>
246 </#if>
247 </#list>
248 <#assign resultValue = keysList?join(", ")>
249 <#elseif mode == "first">
250 <#assign firstItem = targetArray?first>
251 <#if subArrayField?has_content>
252 <#assign subArray = firstItem[subArrayField]![]>
253 <#if subArray?has_content>
254 <#assign resultValue = subArray?first[nestedField]!"" >
255 <#else>
256 <#assign resultValue = "" >
257 </#if>
258 <#else>
259 <#assign resultValue = firstItem[nestedField]!"" >
260 </#if>
261 </#if>
262 <#else>
263 <#assign resultValue = "">
264 </#if>
265
266 <#-- Añadimos el nuevo campo al objeto -->
267 <#assign enrichedItems = enrichedItems + [
268 e + { (newFieldName): resultValue }
269 ]>
270 </#list>
271
272 <#return enrichedItems>
273</#function>
274
275
276<#function isVocabularyNameIntoCategories categories vocabulary name>
277 <#assign found = false />
278
279 <#if categories?has_content && vocabulary?has_content && name?has_content>
280
281 <#assign vocabNorm = normalize(vocabulary) />
282 <#assign nameNorm = normalize(name) />
283
284 <#list categories as category>
285 <#if !found>
286 <#assign catVocabNorm = normalize(category.vocabulary) />
287 <#assign catNameNorm = normalize(category.name) />
288
289 <#if catVocabNorm == vocabNorm && catNameNorm == nameNorm>
290 <#assign found = true />
291 </#if>
292 </#if>
293 </#list>
294
295 </#if>
296
297 <#return found>
298</#function>
299
300
301<#function normalize text onlyAccents = false>
302 <#-- proteger null -->
303 <#if !text?has_content>
304 <#return "">
305 </#if>
306
307 <#assign t = text />
308
309 <#-- quitar acentos -->
310 <#assign t = t
311 ?replace("á","a")?replace("é","e")?replace("í","i")
312 ?replace("ó","o")?replace("ú","u")?replace("ü","u")
313 ?replace("ñ","n")
314 ?replace("Á","A")?replace("É","E")?replace("Í","I")
315 ?replace("Ó","O")?replace("Ú","U")?replace("Ü","U")
316 ?replace("Ñ","N")
317 />
318
319 <#-- si NO es solo acentos, normalización completa -->
320 <#if !onlyAccents>
321 <#assign t = t?lower_case />
322 <#assign t = t?trim />
323 <#assign t = t?replace("\\s+", " ", "r") />
324 </#if>
325
326 <#return t>
327</#function>
328
329
330<#-- Función que retorna los valores de categorías como string, normalizando vocabulario -->
331<#function getCategoriesByVocabularyAsString categories vocabulary separator=" / " field="name">
332 <#assign matchedValues = []>
333 <#if categories?has_content && vocabulary?has_content>
334 <#list categories as category>
335 <#-- Normalizamos tanto el vocabulary de la categoría como el buscado -->
336 <#if normalize(category.vocabulary, true)?lower_case == normalize(vocabulary, true)?lower_case>
337 <#if field == "title">
338 <#assign matchedValues += [category.title]>
339 <#else>
340 <#assign matchedValues += [category.name]>
341 </#if>
342 </#if>
343 </#list>
344 </#if>
345 <#return matchedValues?join(separator)>
346</#function>
347
348
349<#macro printObject obj>
350 <#-- Permite hacer un output de un array de objetos o un objeto que se pasa como parámetro -->
351 <#if obj?is_hash>
352 {
353 <#list obj?keys as k>
354 "${k}":
355 <#assign value = obj[k]>
356 <#if value?is_hash || value?is_sequence>
357 <@printObject obj=value/>
358 <#elseif value?is_boolean>
359 ${value?c}
360 <#elseif value?is_number>
361 ${value}
362 <#elseif value?has_content>
363 "${value?string}"
364 <#else>
365 null
366 </#if>
367 <#if k_has_next>, </#if>
368 </#list>
369 }
370 <#elseif obj?is_sequence>
371 [
372 <#list obj as item>
373 <@printObject obj=item/>
374 <#if item_has_next>, </#if>
375 </#list>
376 ]
377 <#elseif obj?is_boolean>
378 ${obj?c}
379 <#elseif obj?is_number>
380 ${obj}
381 <#elseif obj?has_content>
382 "${obj?string}"
383 <#else>
384 null
385 </#if>
386</#macro>
387
388
389
390
391<#-- HTML -->
392<#if hasProductCategoriaTipoEntidadColeccionTematica>
393 <div id="ecom-coleccion_tematica-detail" class="ecom-coleccion_tematica">
394
395 <div class="tabs-section">
396 <div class="tab-content">
397
398 <#-- Fecha edicion del producto > description del Producto -->
399 <div class="ecom-coleccion_tematica-detail-body">
400 ${product.description}
401 </div>
402
403 <#-- Para imprimir el contenido de los Objects -->
404 <#-- Thematic collection/Coleccion tematica data
405 <#if isDebug>
406 <div class="">
407 <p><strong>thematicCollectionsProduct:</strong></p>
408 <div class="print-object-json-content mb-3" style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
409 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
410 <@printObject thematicCollectionsProduct />
411 </div>
412 </div>
413 </#if>
414 -->
415
416 </div>
417 </div>
418 </div>
419</#if>










