DIN EN ISO/IEEE 11073-10103:2025-06
Health informatics - Device interoperability - Part 10103: Nomenclature - Implantable device, cardiac (ISO/IEEE FDIS 11073-10103:2025); English version prEN ISO/IEEE 11073-10103:2025 / Note: Date of issue 2025-05-02
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<#function getStatusInfo status class="">
623 <#local defaultResult = {
624 "status": status!"",
625 "tagClass": "",
626 "isInForce": false,
627 "isCancelled": false,
628 "isProject": false
629 }>
630
631 <#local trimmedStatus = (status!"")?trim>
632 <#if trimmedStatus == "">
633 <#return defaultResult>
634 </#if>
635
636 <#local normalizedStatus = trimmedStatus?upper_case>
637 <#local tagClass = "">
638 <#if class?has_content>
639 <#local tagClass = class>
640 </#if>
641
642 <#local isInForce = false>
643 <#local isCancelled = false>
644 <#local isProject = false>
645
646 <#if ["EN VIGOR", "IN FORCE", "EM VIGOR", "IN VIGORE"]?seq_contains(normalizedStatus)>
647 <#local tagClass = tagClass + " tag-success">
648 <#local isInForce = true>
649 <#elseif ["ANULADA", "CANCELLED", "ANULADA", "ANNULLATA"]?seq_contains(normalizedStatus)>
650 <#local tagClass = tagClass + " tag-danger">
651 <#local isCancelled = true>
652 <#elseif ["PROYECTO", "PROJECT", "PROJETO", "PROGETTO"]?seq_contains(normalizedStatus)>
653 <#local tagClass = tagClass + " tag-blue">
654 <#local isProject = true>
655 </#if>
656
657 <#return {
658 "status": status,
659 "tagClass": tagClass?trim,
660 "isInForce": isInForce,
661 "isCancelled": isCancelled,
662 "isProject": isProject
663 }>
664</#function>
665
666
667<#macro printObject obj>
668 <#-- Permite hacer un output de un array de objetos o un objeto que se pasa como parámetro -->
669 <#if obj?is_hash>
670 {
671 <#list obj?keys as k>
672 "${k}":
673 <#assign value = obj[k]>
674 <#if value?is_hash || value?is_sequence>
675 <@printObject obj=value/>
676 <#elseif value?is_boolean>
677 ${value?c}
678 <#elseif value?is_number>
679 ${value}
680 <#elseif value?has_content>
681 "${value?string}"
682 <#else>
683 null
684 </#if>
685 <#if k_has_next>, </#if>
686 </#list>
687 }
688 <#elseif obj?is_sequence>
689 [
690 <#list obj as item>
691 <@printObject obj=item/>
692 <#if item_has_next>, </#if>
693 </#list>
694 ]
695 <#elseif obj?is_boolean>
696 ${obj?c}
697 <#elseif obj?is_number>
698 ${obj}
699 <#elseif obj?has_content>
700 "${obj?string}"
701 <#else>
702 null
703 </#if>
704</#macro>
705
706
707<#macro renderStandardRelationsSectionsRows standardRelationsTypesProduct standardRelationsProduct typesToExclude=[] isDebug=false>
708
709 <#assign lastSectionKey = "">
710 <#assign openRow = false>
711 <#assign indexCount = 0>
712
713 <#assign filteredStandardRelationsProduct = standardRelationsProduct >
714 <#if typesToExclude?has_content>
715 <#assign filteredStandardRelationsProduct = excludeStandardRelationsByFieldType(standardRelationsProduct, typesToExclude)>
716 </#if>
717
718 <#list standardRelationsTypesProduct as standardRelationsTypeProduct>
719
720 <#assign currentSectionKey = (standardRelationsTypeProduct.sectionKey)!"" />
721 <#assign standardRelationsProductsByTypeKey = getStandardRelationsProductsByField(standardRelationsTypeProduct, filteredStandardRelationsProduct)>
722 <#assign hasElementsStandardRelationsProducts = (standardRelationsProductsByTypeKey?size > 0)>
723
724 <#if isDebug || hasElementsStandardRelationsProducts>
725
726 <#-- Si la sección cambia, cerramos la fila anterior -->
727 <#if openRow && currentSectionKey != lastSectionKey>
728 </td>
729 </tr>
730 <#assign openRow = false>
731 </#if>
732
733 <#-- Si es una nueva sección, abrimos una nueva fila -->
734 <#if !openRow>
735 <#assign indexCount = 0>
736 <tr
737 data-section-key="${currentSectionKey}"
738 data-section-name="${standardRelationsTypeProduct.sectionName}"
739 data-sort-key="${standardRelationsTypeProduct.sortKey}">
740 <th>
741 <p>${standardRelationsTypeProduct.sectionName}</p>
742 </th>
743 <td data-section-key="${currentSectionKey}">
744 <#assign openRow = true>
745 </#if>
746
747 <#if isDebug>
748 <div class="">
749 <p><strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}</p>
750 <p><strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}</p>
751 <p><strong>sectionName:</strong> ${standardRelationsTypeProduct.sectionName}</p>
752 <p><strong>sortKey:</strong> ${standardRelationsTypeProduct.sortKey}</p>
753 <p><strong>total standardRelationsProductsByTypeKey[${standardRelationsTypeProduct.typeKey}]: </strong> ${standardRelationsProductsByTypeKey?size}</p>
754
755 <p class="mb-0"><strong>standardRelationsTypeProduct:</strong></p>
756 <div class="print-object-json-content mb-3"
757 style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
758 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
759 <@printObject standardRelationsTypeProduct />
760 </div>
761
762 <p class="mb-0"><strong>standardRelationsProductsByTypeKey:</strong></p>
763 <div class="print-object-json-content mb-3"
764 style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
765 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
766 <@printObject standardRelationsProductsByTypeKey />
767 </div>
768 </div>
769 </#if>
770
771 <#if standardRelationsProductsByTypeKey?has_content>
772 <#list standardRelationsProductsByTypeKey as standardRelationProductsByTypeKey>
773 <#assign indexCount = indexCount + 1>
774 <#assign urlProductCurrentLanguage = standardRelationProductsByTypeKey.urlProductCurrentLanguage>
775
776 <p data-section-key="${currentSectionKey}"
777 data-type-key="${standardRelationsTypeProduct.typeKey}" data-type-index="${indexCount}">
778
779 ${standardRelationsTypeProduct.description}
780
781 <#--
782 <#if urlProductCurrentLanguage?? && urlProductCurrentLanguage?has_content>
783 <a href="${urlProductCurrentLanguage}" target="_blank">
784 ${standardRelationProductsByTypeKey.relatedStandardName!""}
785 </a>
786 <#else>
787 <a class="add-link-product-by-erc" data-product-erc="${standardRelationProductsByTypeKey.ercProduct}" href="" target="_blank">
788 ${standardRelationProductsByTypeKey.relatedStandardName!""}
789 </a>
790 </#if>
791 -->
792
793 <a class="add-link-product-by-erc text-decoration-none text-body" data-product-erc="${standardRelationProductsByTypeKey.ercProduct}" href="">
794 ${standardRelationProductsByTypeKey.relatedStandardName!""}
795 </a>
796
797 </p>
798
799 </#list>
800
801 <#if isDebug>
802 <p>
803 <strong>Info:</strong>SI hay (<strong>${standardRelationsProductsByTypeKey?size}</strong>) <strong>Standard/Norma Relation</strong> con el type [<strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}]
804 en la sección [<strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}]
805 </p>
806 </#if>
807 <#else>
808 <#if isDebug>
809 <p>
810 <strong>Info:</strong>NO hay <strong>Standard/Norma Relation</strong> con el type [<strong>typeKey:</strong> ${standardRelationsTypeProduct.typeKey}]
811 en la sección [<strong>sectionKey:</strong> ${standardRelationsTypeProduct.sectionKey}]
812 </p>
813 <#else>
814 <p>N/A</p>
815 </#if>
816 </#if>
817
818 <#-- Separador visual en debug entre elementos standardRelations dentro del mismo section -->
819 <#if isDebug>
820 <#-- Separador visual mejorado -->
821 <div style="margin:10px 0; padding:5px; border-top:2px dashed #007BFF; background-color:#f0f8ff;"></div>
822 </#if>
823
824 <#assign lastSectionKey = currentSectionKey>
825 </#if>
826 </#list>
827
828 <#-- Cerrar la última fila abierta -->
829 <#if openRow>
830 </td>
831 </tr>
832 </#if>
833
834</#macro>
835
836
837
838
839<#-- HTML -->
840<#if hasProductCategoriaTipoEntidadNorma>
841 <div id="ecom-norma-detail" class="ecom-norma">
842 <div class="tabs-section">
843 <div class="tab-content">
844
845 <table>
846
847 <#-- Custom object [StandardWarning] -->
848 <#if standardWarning?? && standardWarning?has_content>
849 <tr class="alert-row">
850 <th class="alert-label">
851 <#if standardWarning.warningTitle??>
852 <strong>${standardWarning.warningTitle}</strong>
853 </#if>
854 </th>
855 <td class="alert-content">
856 <#if standardWarning.warningDescription??>
857 ${standardWarning.warningDescription}
858 </#if>
859 </td>
860 </tr>
861 </#if>
862
863 <#-- Custom object [StandardRelations] > Buscamos si el producto tiene elementos [StandardRelations] con el campo key = ["MOD", "MODIFIED"] -->
864 <#assign findStandardRelationsProductsByTypeModified = filterStandardRelationsProductsByFieldType(standardRelationsProduct, ["MOD", "MODIFIED"])>
865 <#if findStandardRelationsProductsByTypeModified?has_content>
866 <tr class="alert-row">
867 <th class="alert-label">
868 <strong>${languageUtil.get(locale, "ecom-aviso")}</strong>:
869 </th>
870 <td class="alert-content">
871 ${languageUtil.get(locale, "ecom-aviso_modificaciones_text")}
872 </td>
873 </tr>
874 </#if>
875
876 <#-- Producto tiene [categoria: Organismo] > Si el producto tiene la [categoria: Organismo] = SAE -->
877 <#if hasProductCategoriaTipoOrganismoSAE>
878 <tr class="alert-row">
879 <th class="alert-label">
880 <span class="text-danger">
881 <strong>${languageUtil.get(locale, "ecom-aviso_pdf_secure")}</strong>:
882 </span>
883 </th>
884 <td class="alert-content">
885 ${languageUtil.get(locale, "ecom-aviso_pdf_secure_text")}
886 </td>
887 </tr>
888 </#if>
889
890 <#-- Producto tiene [categoria: Organismo] > Si el producto tiene la [categoria: Organismo] = ASTM -->
891 <#if hasProductCategoriaTipoOrganismoASTM>
892 <tr class="alert-row">
893 <th class="alert-label">
894 <strong>${languageUtil.get(locale, "ecom-aviso_astm")}</strong>
895 </th>
896 <td class="alert-content">
897 ${languageUtil.get(locale, "ecom-aviso_astm_text")}
898 </td>
899 </tr>
900 </#if>
901
902 <#-- Categoria producto > Tematicas -->
903 <#if categoryProductInfoTematica?has_content>
904 <tr>
905 <th>${languageUtil.get(locale, "ecom-tematicas")}:</th>
906 <td>${categoryProductInfoTematica}</td>
907 </tr>
908 </#if>
909
910 <#-- Fecha edicion del producto > displayDate del Producto -->
911 <#assign statusInfo = getStatusInfo(categoryProductInfoStatus, "badge status-standard")>
912 <#if statusInfo?? && !statusInfo.isCancelled>
913 <tr>
914 <th>${languageUtil.get(locale, "ecom-fecha_edicion")}:</th>
915 <td>
916
917 <#-- Comentado provisonalmente hasta cambio en integracion
918 <#if displayDateProduct?? && displayDateProduct?has_content>
919 ${displayDateProduct?datetime("d/MM/yy H:mm")?string("yyyy-MM-dd")}
920 <#else>
921 -
922 </#if>
923 -->
924
925 ${specificationsCurrentStateDateProduct
926 ?filter(spec -> spec.value??
927 && spec.value?has_content
928 && spec.value?length == 8)
929 ?map(spec -> spec.value?date("yyyyMMdd")?string("yyyy-MM-dd"))
930 ?join(", ")}
931
932 <#if categoryProductInfoStatus?? && categoryProductInfoStatus?has_content>
933 <div class="${statusInfo.tagClass}">${statusInfo.status}</div>
934 </#if>
935
936 </td>
937 </tr>
938 </#if>
939
940 <#-- Especificacion del producto > current-state-date (Nos indicaron que es la fecha de anulacion de la ficha) -->
941 <#-- Fecha de cancelacion del producto -->
942 <#if statusInfo?? && statusInfo.isCancelled>
943 <#if specificationsCurrentStateDateProduct?has_content>
944 <tr>
945 <th>${languageUtil.get(locale, "ecom-cancellationDate")}:</th>
946 <td>
947
948 ${specificationsCurrentStateDateProduct
949 ?filter(spec -> spec.value??
950 && spec.value?has_content
951 && spec.value?length == 8)
952 ?map(spec -> spec.value?date("yyyyMMdd")?string("yyyy-MM-dd"))
953 ?join(", ")}
954
955 <div class="${statusInfo.tagClass}">${statusInfo.status}</div>
956
957 </td>
958 </tr>
959 </#if>
960 </#if>
961
962 <#-- Custom object [StandardInfo] > confirmationDate -->
963 <#if standardInfoProduct?? && standardInfoProduct.confirmationDate??
964 && standardInfoProduct.confirmationDate?has_content && !standardInfoProduct.confirmationDate?starts_with("0001-01-01")>
965 <tr>
966 <th>${languageUtil.get(locale, "ecom-confirmationDate")}:</th>
967 <td>${standardInfoProduct.confirmationDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
968 </tr>
969 </#if>
970
971 <#-- Custom object [StandardInfo] > correctionDate -->
972 <#if standardInfoProduct?? && standardInfoProduct.correctionDate??
973 && standardInfoProduct.correctionDate?has_content && !standardInfoProduct.correctionDate?starts_with("0001-01-01")>
974 <tr>
975 <th>${languageUtil.get(locale, "ecom-correctionDate")}:</th>
976 <td>${standardInfoProduct.correctionDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
977 </tr>
978 </#if>
979
980 <#-- Custom object [StandardInfo] > ratificationDate -->
981 <#if standardInfoProduct?? && standardInfoProduct.ratificationDate??
982 && standardInfoProduct.ratificationDate?has_content && !standardInfoProduct.ratificationDate?starts_with("0001-01-01")>
983 <tr>
984 <th>${languageUtil.get(locale, "ecom-ratificationDate")}:</th>
985 <td>${standardInfoProduct.ratificationDate?datetime("yyyy-MM-dd'T'HH:mm:ss.SSSX")?string("yyyy-MM-dd")}</td>
986 </tr>
987 </#if>
988
989 <#-- Especificacion del producto > standard-languages (se aplica filtro para idiomas combinados) -->
990 <#if filteredSpecificationsLanguagesProduct?has_content>
991 <tr>
992 <th>${languageUtil.get(locale, "ecom-idiomas_disponibles")}:</th>
993 <td>${filteredSpecificationsLanguagesProduct?map(spec -> spec.title)?join(", ")}</td>
994 </tr>
995 </#if>
996
997 <#-- Custom object [StandardInfo] > resumen -->
998 <#if standardInfoProduct?? && standardInfoProduct.resumen?has_content>
999 <tr>
1000 <th class="th-content">${languageUtil.get(locale, "ecom-resumen")}:</th>
1001 <td class="td-content">${htmlUtil.unescape(standardInfoProduct.resumen)}</td>
1002 </tr>
1003 </#if>
1004
1005 <#-- Product > expando.keywords -->
1006 <#if product?? && product.expando?? && product.expando.keywords?has_content>
1007 <tr>
1008 <th class="th-content">${languageUtil.get(locale, "ecom-keywords")}:</th>
1009 <td class="td-content">${product.expando.keywords}</td>
1010 </tr>
1011 </#if>
1012
1013 <#-- Custom object [StandardInfo] > scope -->
1014 <#if standardInfoProduct?? && standardInfoProduct.scope?has_content>
1015 <tr>
1016 <th class="th-content">${languageUtil.get(locale, "ecom-scope")}:</th>
1017 <td class="td-content">${standardInfoProduct.scope}</td>
1018 </tr>
1019 </#if>
1020
1021 <#-- Especificacion del producto > ics -->
1022 <#if specificationsICSProduct?has_content>
1023 <tr>
1024 <th>${languageUtil.get(locale, "ecom-ics")}:</th>
1025 <td>
1026 ${specificationsICSProduct?filter(spec -> spec.value?? && spec.value?has_content)?map(spec -> spec.value)?join(", ")}
1027 </td>
1028 </tr>
1029 </#if>
1030
1031 <#-- Especificacion del producto > ctn -->
1032 <#if specificationsCTNProduct?? && specificationsCTNProduct?size gt 0>
1033 <tr>
1034 <th>${languageUtil.get(locale, "ecom-ctn")}:</th>
1035 <td>
1036 ${specificationsCTNProduct?filter(spec -> spec.value?? && spec.value?has_content)?map(spec -> spec.value)?join(", ")}
1037 </td>
1038 </tr>
1039 </#if>
1040
1041 <#-- Para imprimir el contenido de los Objects -->
1042 <#if isDebug>
1043 <div class="">
1044 <p><strong>standardRelationsProduct:</strong></p>
1045 <div class="print-object-json-content mb-3" style="max-height:200px; overflow:auto; border:1px solid #ccc; padding:5px; cursor:pointer;"
1046 onclick="const r=document.createRange(); r.selectNodeContents(this); const s=window.getSelection(); s.removeAllRanges(); s.addRange(r);">
1047 <@printObject standardRelationsProduct />
1048 </div>
1049 </div>
1050 </#if>
1051
1052 <@renderStandardRelationsSectionsRows
1053 standardRelationsTypesProduct = standardRelationsTypesProduct
1054 standardRelationsProduct = standardRelationsProduct
1055 typesToExclude = ["REFERENCE"]
1056 isDebug = isDebug
1057 />
1058
1059 </table>
1060 </div>
1061 </div>
1062 </div>
1063</#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>










