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









