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










