publico
Se ha producido un error al procesar la plantilla.
Java method "com.liferay.commerce.product.type.grouped.model.impl.CPDefinitionGroupedEntryImpl.getEntryCProduct()" threw an exception when invoked on com.liferay.commerce.product.type.grouped.model.impl.CPDefinitionGroupedEntryImpl object "{\"mvccVersion\": 0, \"uuid\": \"1d802fbb-fa77-2df9-0a6a-b98c0fff90df\", \"CPDefinitionGroupedEntryId\": 290907, \"groupId\": 59881, \"companyId\": 20097, \"userId\": 20125, \"userName\": \"Test Test\", \"createDate\": \"2023-11-06 14:00:15.82\", \"modifiedDate\": \"2023-11-06 14:00:15.82\", \"CPDefinitionId\": 290728, \"entryCProductId\": 290758, \"priority\": 0.0, \"quantity\": 1}"; see cause exception in the Java stack trace.
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign childProduct = groupedEntry.g... [in template "20097#20123#1714650" at line 141, column 17]
----
1<#--
2 BTI - Detalle de producto Commerce publico y privado
3-->
4
5<#assign enableSalesforcePrices = true>
6<#assign isLoggedIn = themeDisplay.isSignedIn()>
7<#assign CPDefinitionLocalService = serviceLocator.findService("com.liferay.commerce.product.service.CPDefinitionLocalService")>
8<#assign specificationValueService = serviceLocator.findService("com.liferay.commerce.product.service.CPDefinitionSpecificationOptionValueLocalService")>
9<#assign fileEntryLocalService = serviceLocator.findService("com.liferay.document.library.kernel.service.DLAppLocalService")>
10<#assign dlurlHelper = serviceLocator.findService("com.liferay.document.library.util.DLURLHelper")>
11<#assign jsonFactoryUtil = serviceLocator.findService("com.liferay.portal.kernel.json.JSONFactoryUtil")>
12<#assign commerceContext = renderRequest.getAttribute("COMMERCE_CONTEXT")>
13<#assign cpDefinition = CPDefinitionLocalService.getCPDefinition(cpCatalogEntry.getCPDefinitionId())>
14<#assign cpDefinitions = [cpDefinition]>
15<#assign productType = cpCatalogEntry.getProductTypeName()!"simple">
16<#assign cpInstances = cpDefinition.getCPInstances()![]>
17<#assign cpInstanceId = 0>
18<#if cpInstances?has_content>
19 <#assign cpInstanceId = cpInstances[0].getCPInstanceId()>
20</#if>
21
22<#assign commerceOrderId = 0>
23<#assign accountEntryId = 0>
24<#assign blocked = false>
25<#if isLoggedIn>
26 <#attempt>
27 <#assign commerceOrderId = (commerceContext.getCommerceOrder().getCommerceOrderId())!0>
28 <#recover>
29 </#attempt>
30 <#attempt>
31 <#assign accountEntry = commerceContext.getAccountEntry()>
32 <#assign accountEntryId = accountEntry.getAccountEntryId()>
33 <#assign blocked = accountEntry.getExpandoBridge().getAttribute("Blocked")!false>
34 <#recover>
35 </#attempt>
36</#if>
37
38<#assign productName = cpCatalogEntry.getName()!"">
39<#assign shortDescription = cpCatalogEntry.getShortDescription()!"">
40<#assign description = cpDefinition.getDescription()!"">
41<#assign externalReferenceCode = cpDefinition.getCProduct().getExternalReferenceCode()!"">
42<#assign specificationValues = specificationValueService.getCPDefinitionSpecificationOptionValues(cpDefinition.getCPDefinitionId(), 0)![]>
43
44<#assign productImageURL = "">
45<#attempt>
46 <#assign productImages = cpDefinition.getCPAttachmentFileEntries(0, -1)>
47 <#if productImages?has_content>
48 <#assign productAttachment = productImages?sort_by("createDate")?last>
49 <#assign productFileEntry = fileEntryLocalService.getFileEntry(productAttachment.getFileEntryId())>
50 <#assign productImageURL = dlurlHelper.getDownloadURL(productFileEntry, productFileEntry.getFileVersion(), themeDisplay, "")>
51 </#if>
52<#recover>
53</#attempt>
54<#if !productImageURL?has_content>
55 <#attempt>
56 <#assign productImageURL = cpContentHelper.getDefaultImageFileURL(accountEntryId, cpDefinition.getCPDefinitionId())!"">
57 <#recover>
58 </#attempt>
59</#if>
60
61<#assign groupedEntries = []>
62<#assign groupedLoadError = false>
63<#if productType == "grouped">
64 <#attempt>
65 <#assign groupedEntries = groupedCPTypeHelper.getCPDefinitionGroupedEntry(accountEntryId, commerceContext.getCommerceChannelGroupId(), cpDefinition.getCPDefinitionId())![]>
66 <#recover>
67 <#assign groupedLoadError = true>
68 </#attempt>
69</#if>
70
71<#assign pricesService = "">
72<#assign pricesServiceAvailable = false>
73<#assign simplePriceAvailable = false>
74<#assign simpleUnitValue = 0>
75<#assign simpleOriginalValue = 0>
76<#assign simpleFormattedPrice = "">
77<#assign simpleFormattedOriginalPrice = "">
78<#if isLoggedIn && enableSalesforcePrices>
79 <#attempt>
80 <#assign pricesService = serviceLocator.findService("com.bti.commerce.integration.api.services.ext.PricesServiceExt")>
81 <#assign pricesServiceAvailable = true>
82 <#if productType == "simple">
83 <#assign simplePrice = pricesService.getPricesWithDiscount(cpDefinitions, false, commerceContext)?values[0]>
84 <#assign simpleUnitValue = simplePrice.getUnitValue()!0>
85 <#assign simpleOriginalValue = simplePrice.getUnitValueOriginal()!simpleUnitValue>
86 <#assign simpleFormattedPrice = pricesService.formatValue(simpleUnitValue)!"">
87 <#assign simpleFormattedOriginalPrice = pricesService.formatValue(simpleOriginalValue)!"">
88 <#assign simplePriceAvailable = simpleUnitValue gt 0>
89 </#if>
90 <#recover>
91 <#assign pricesServiceAvailable = false>
92 <#assign simplePriceAvailable = false>
93 </#attempt>
94</#if>
95
96<#assign detailId = "bti-product-detail-" + cpDefinition.getCPDefinitionId()?c>
97
98<article class="bti-product-detail" id="${detailId}">
99 <div class="bti-product-detail__inner">
100 <div class="bti-product-detail__gallery">
101 <#if productImageURL?has_content>
102 <img class="bti-product-detail__image" src="${htmlUtil.escapeAttribute(productImageURL)}" alt="${htmlUtil.escapeAttribute(productName)}">
103 </#if>
104 </div>
105
106 <div class="bti-product-detail__info">
107 <#if isLoggedIn>
108 <div class="bti-product-detail__commerce-actions">
109 <@liferay_commerce_ui["add-to-wish-list"] CPCatalogEntry=cpCatalogEntry />
110 </div>
111 </#if>
112
113 <div class="bti-product-detail__summary">
114 <#if externalReferenceCode?has_content>
115 <p class="bti-product-detail__reference"><span class="bti-product-detail__reference-label">REF.</span><span class="bti-product-detail__reference-value">${externalReferenceCode?html}</span></p>
116 </#if>
117 <h1 class="bti-product-detail__title">${productName?html}</h1>
118 <#if shortDescription?has_content><div class="bti-product-detail__description">${shortDescription}</div></#if>
119 <#if isLoggedIn && enableSalesforcePrices>
120 <div class="bti-product-detail__price">
121 <#if simplePriceAvailable>
122 <#if simpleOriginalValue gt simpleUnitValue><span class="bti-product-detail__price-original">${simpleFormattedOriginalPrice?html}</span></#if>
123 <span class="bti-product-detail__price-current">${simpleFormattedPrice?html}</span>
124 <#else><span class="bti-product-detail__price-status">Precio no disponible</span></#if>
125 </div>
126 </#if>
127 </div>
128
129 <div class="bti-product-detail__delivery">
130 <svg class="bti-product-detail__delivery-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path fill="currentColor" d="M5.73 18.27A2.49 2.49 0 0 1 5 16.5H2.77l.22-1h2.27A2.5 2.5 0 0 1 10 15.5h4.36L16.56 6H5.61l.02-.1A1.12 1.12 0 0 1 6.73 5h11.08L17 8.5h2.08L21.79 12l-.87 4.5h-1.15a2.5 2.5 0 0 1-5 0H10a2.5 2.5 0 0 1-4.27 1.77ZM15.89 13.25h4.65l.18-.89-2.14-2.86h-1.82l-.87 3.75ZM7.5 18a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm9.77 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg>
131 <p>Entrega gratis en 24h si haces tu pedido antes de las 17h.</p>
132 </div>
133
134 <#if productType == "grouped">
135 <div class="bti-product-detail__variants">
136 <#if groupedLoadError>
137 <p class="bti-product-detail__status" role="status">No se han podido cargar las variantes.</p>
138 <#elseif groupedEntries?has_content>
139 <div class="bti-product-detail__table-scroll"><table class="bti-product-detail__table"><thead><tr><th scope="col"><span class="sr-only">Seleccionar</span></th><th scope="col">Referencia</th><th scope="col">Cantidad</th></tr></thead><tbody>
140 <#list groupedEntries as groupedEntry>
141 <#assign childProduct = groupedEntry.getEntryCProduct()>
142 <#assign childDefinition = groupedEntry.getEntryCPDefinition()>
143 <#assign childInstances = childDefinition.getCPInstances()![]>
144 <#if childInstances?has_content>
145 <#assign childInstanceId = childInstances[0].getCPInstanceId()>
146 <tr><td><input class="bti-product-detail__variant-check" type="checkbox" value="${childInstanceId?c}" aria-label="Seleccionar ${childProduct.getExternalReferenceCode()?html}"></td><td>${childProduct.getExternalReferenceCode()?html}</td><td><input class="bti-product-detail__variant-quantity" type="number" min="1" max="10000" value="1" data-sku-id="${childInstanceId?c}" aria-label="Cantidad para ${childProduct.getExternalReferenceCode()?html}"></td></tr>
147 </#if>
148 </#list>
149 </tbody></table></div>
150 <#else><p class="bti-product-detail__status" role="status">Producto no disponible.</p></#if>
151 </div>
152 </#if>
153
154 <div class="bti-product-detail__purchase">
155 <div class="bti-product-detail__buttons">
156 <#if isLoggedIn>
157 <#if blocked>
158 <span class="bti-product-detail__button bti-product-detail__button--disabled" aria-disabled="true">Compra no disponible</span>
159 <#elseif productType == "simple" && cpInstanceId gt 0>
160 <div class="bti-product-detail__quantity" aria-label="Cantidad"><button class="bti-product-detail__quantity-button" type="button" data-quantity-step="-1" aria-label="Reducir cantidad">-</button><output class="bti-product-detail__quantity-value" data-quantity-value aria-live="polite">1</output><button class="bti-product-detail__quantity-button" type="button" data-quantity-step="1" aria-label="Aumentar cantidad">+</button></div>
161 <div class="bti-product-detail__native-cart"><@liferay_commerce_ui["add-to-cart"] CPCatalogEntry=cpCatalogEntry cPInstanceId=cpInstanceId /></div>
162 <#elseif productType == "grouped" && groupedEntries?has_content>
163 <button class="bti-product-detail__button bti-product-detail__button--secondary" type="button" data-grouped-add disabled>Añadir al carrito</button>
164 </#if>
165 <#else>
166 <a class="bti-product-detail__button bti-product-detail__button--primary" href="#formulario-contacto">Solicitar información</a>
167 <a class="bti-product-detail__button bti-product-detail__button--secondary" href="${themeDisplay.getURLSignIn()?html}">Iniciar sesión o registrarse</a>
168 </#if>
169 </div>
170 <div class="bti-product-detail__catalog-link">
171 <@liferay_commerce_ui["compare-checkbox"] CPCatalogEntry=cpCatalogEntry label="Comparar con otros productos" />
172 </div>
173 <p class="bti-product-detail__status" data-purchase-status aria-live="polite" hidden></p>
174 </div>
175 </div>
176 </div>
177
178 <#assign notesContent = "">
179 <#attempt><#assign notesRaw = cpDefinition.getExpandoBridge().getAttribute("notes")!"">
180 <#if notesRaw?has_content && notesRaw != '{"de":"","pt":"","en":"","it":"","fr":"","es":""}'><#assign notesJson = jsonFactoryUtil.createJSONObject(notesRaw)><#assign notesContent = notesJson.getString(locale?split("_")[0])!""></#if>
181 <#recover></#attempt>
182 <#assign clinicalContent = "">
183 <#attempt><#assign clinicalRaw = cpDefinition.getExpandoBridge().getAttribute("clinicalIndications")!"">
184 <#if clinicalRaw?has_content && clinicalRaw != '{"de":"","pt":"","en":"","it":"","fr":"","es":""}'><#assign clinicalJson = jsonFactoryUtil.createJSONObject(clinicalRaw)><#assign clinicalContent = clinicalJson.getString(locale?split("_")[0])!""></#if>
185 <#recover></#attempt>
186 <#function hasAdditionalContent value>
187 <#return htmlUtil.stripHtml(value)?replace(" ", " ")?replace("[\\s\\u00A0\\u200B-\\u200D\\uFEFF]", "", "r")?has_content>
188 </#function>
189 <#assign accordionDescription = description>
190 <#if !hasAdditionalContent(accordionDescription)><#assign accordionDescription = shortDescription></#if>
191 <#assign populatedSpecifications = specificationValues?filter(value -> hasAdditionalContent(value.getValueCurrentValue()!""))>
192 <#assign hasDescription = hasAdditionalContent(accordionDescription)>
193 <#assign hasSpecifications = populatedSpecifications?has_content && productType != "grouped">
194 <#assign hasClinicalContent = hasAdditionalContent(clinicalContent)>
195 <#assign hasNotesContent = hasAdditionalContent(notesContent)>
196
197 <#if hasNotesContent || hasDescription || hasSpecifications || hasClinicalContent>
198 <div class="bti-product-detail__additional">
199 <#if hasNotesContent><div class="bti-product-detail__notice" role="note">${notesContent}</div></#if>
200 <#if hasDescription>
201 <details class="bti-product-detail__accordion"><summary>Descripción</summary><div class="bti-product-detail__accordion-content">${accordionDescription}</div></details>
202 </#if>
203 <#if hasSpecifications>
204 <details class="bti-product-detail__accordion"><summary>Especificaciones</summary><dl class="bti-product-detail__specifications"><#list populatedSpecifications as specificationValue><div class="bti-product-detail__specification"><dt>${specificationValue.getCPSpecificationOption().getTitleCurrentValue()?html}</dt><dd>${specificationValue.getValueCurrentValue()?html}</dd></div></#list></dl></details>
205 </#if>
206 <#if hasClinicalContent>
207 <details class="bti-product-detail__accordion"><summary>Más información</summary><div class="bti-product-detail__accordion-content">${clinicalContent}</div></details>
208 </#if>
209 </div>
210 </#if>
211</article>
212
213<style>
214 .bti-product-detail { background-color: var(--bti-color-brand-light); color: var(--bti-color-content-high); width: 100%; }
215 .bti-product-detail__inner { display: grid; gap: var(--bti-spacing-8); grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); margin: 0 auto; max-width: var(--bti-content-max-width); padding: var(--bti-spacing-8) var(--bti-spacing-6); }
216 .bti-product-detail__gallery { align-items: center; display: flex; justify-content: center; min-width: 0; }
217 .bti-product-detail__image { display: block; height: auto; max-height: calc(var(--bti-spacing-10) * 8); max-width: 100%; object-fit: contain; width: 100%; }
218 .bti-product-detail__info, .bti-product-detail__summary, .bti-product-detail__purchase { display: flex; flex-direction: column; }
219 .bti-product-detail__info { gap: var(--bti-spacing-8); min-width: 0; }
220 .bti-product-detail__summary { gap: var(--bti-spacing-4); }
221 .bti-product-detail__commerce-actions { align-items: center; display: flex; gap: var(--bti-spacing-4); justify-content: flex-end; }
222 .bti-product-detail__commerce-actions .add-to-wish-list, .bti-product-detail__commerce-actions .add-to-wish-list-wrapper { margin: 0; }
223 .bti-product-detail__commerce-actions .add-to-wish-list button, .bti-product-detail__commerce-actions .btn-add-to-wish-list { align-items: center; background: transparent; border: 0; color: var(--bti-color-content-high); display: inline-flex; height: var(--bti-spacing-6); justify-content: center; padding: 0; width: var(--bti-spacing-6); }
224 .bti-product-detail__commerce-actions .text-truncate-inline, .bti-product-detail__commerce-actions .text-truncate { display: none !important; }
225 .bti-product-detail__reference { align-items: center; display: flex; gap: var(--bti-spacing-2); margin: 0; }
226 .bti-product-detail__reference-label { border: var(--bti-border-width) var(--bti-border-style) var(--bti-color-sand-70); border-radius: var(--bti-radius-xs); font-family: var(--bti-font-body); font-size: var(--bti-fs-body-s); line-height: var(--bti-lh-body-m); padding: var(--bti-spacing-2) var(--bti-spacing-3); }
227 .bti-product-detail__reference-value, .bti-product-detail__title, .bti-product-detail__price-current { font-family: var(--bti-font-headline); }
228 .bti-product-detail__reference-value { font-size: var(--bti-fs-title-m); font-weight: var(--bti-fw-semibold); line-height: var(--bti-lh-title-m); }
229 .bti-product-detail__title { color: var(--bti-color-content-high); font-size: var(--bti-fs-headline-s); font-weight: var(--bti-fw-regular); line-height: var(--bti-lh-headline-s); margin: 0; }
230 .bti-product-detail__description, .bti-product-detail__accordion-content { color: var(--bti-color-content-medium); font-family: var(--bti-font-body); font-size: var(--bti-fs-body-l); line-height: var(--bti-lh-body-l); padding-bottom: var(--bti-spacing-4); }
231 .bti-product-detail__description p { margin: 0; }
232 .bti-product-detail__price { align-items: baseline; display: flex; gap: var(--bti-spacing-3); }
233 .bti-product-detail__price-current { color: var(--bti-color-brand-purple); font-size: var(--bti-fs-headline-s); line-height: var(--bti-lh-headline-s); }
234 .bti-product-detail__price-original { color: var(--bti-color-content-medium); font-family: var(--bti-font-body); text-decoration: line-through; }
235 .bti-product-detail__price-status, .bti-product-detail__status { color: var(--bti-color-content-medium); font-family: var(--bti-font-body); font-size: var(--bti-fs-body-s); line-height: var(--bti-lh-body-s); }
236 .bti-product-detail__delivery { align-items: center; display: flex; gap: var(--bti-spacing-2); }
237 .bti-product-detail__delivery p { font-family: var(--bti-font-body); font-size: var(--bti-fs-body-m); line-height: var(--bti-lh-body-m); margin: 0; }
238 .bti-product-detail__delivery-icon { flex-shrink: 0; height: var(--bti-spacing-5); width: var(--bti-spacing-5); }
239 .bti-product-detail__variants, .bti-product-detail__table-scroll { max-width: 100%; overflow-x: auto; }
240 .bti-product-detail__table { border-collapse: collapse; font-family: var(--bti-font-body); font-size: var(--bti-fs-body-s); line-height: var(--bti-lh-body-s); width: 100%; }
241 .bti-product-detail__table th, .bti-product-detail__table td { border-bottom: var(--bti-border-width) var(--bti-border-style) var(--bti-color-content-disabled); padding: var(--bti-spacing-3); text-align: left; }
242 .bti-product-detail__variant-check { accent-color: var(--bti-color-brand-purple); height: var(--bti-spacing-4); width: var(--bti-spacing-4); }
243 .bti-product-detail__variant-quantity { border: var(--bti-border-width) var(--bti-border-style) var(--bti-color-content-disabled); color: var(--bti-color-content-high); font: inherit; max-width: calc(var(--bti-spacing-10) + var(--bti-spacing-4)); padding: var(--bti-spacing-2); }
244 .bti-product-detail__purchase { gap: var(--bti-spacing-3); }
245 .bti-product-detail__buttons { align-items: stretch; display: flex; gap: var(--bti-spacing-3); }
246 .bti-product-detail__quantity { align-items: center; display: inline-flex; flex-shrink: 0; gap: var(--bti-spacing-3); }
247 .bti-product-detail__quantity-button, .bti-product-detail__quantity-value { align-items: center; background: transparent; border: var(--bti-border-width) var(--bti-border-style) var(--bti-color-sand-70); color: var(--bti-color-content-high); display: inline-flex; font-family: var(--bti-font-body); font-size: var(--bti-fs-body-l); height: calc(var(--bti-spacing-6) * 2); justify-content: center; margin: 0; padding: 0; width: calc(var(--bti-spacing-6) * 2); }
248 .bti-product-detail__quantity-button { cursor: pointer; }
249 .bti-product-detail__quantity-button:disabled { background-color: var(--bti-color-content-disabled); cursor: not-allowed; }
250 .bti-product-detail__button, .bti-product-detail__native-cart .btn-add-to-cart { align-items: center; border: var(--bti-border-width) var(--bti-border-style) transparent; border-radius: 0; display: inline-flex; font-family: var(--bti-font-body); font-size: var(--bti-fs-body-m); font-weight: var(--bti-fw-semibold); justify-content: center; line-height: var(--bti-lh-body-m); min-height: calc(var(--bti-spacing-6) * 2); padding: var(--bti-spacing-3) var(--bti-spacing-6); text-align: center; text-decoration: none; width: 100%; }
251 .bti-product-detail__button--primary, .bti-product-detail__native-cart .btn-add-to-cart { background-color: var(--bti-color-brand-purple); color: var(--bti-color-content-inverse); }
252 .bti-product-detail__button--secondary { background-color: transparent; border-color: var(--bti-color-content-disabled); color: var(--bti-color-brand-purple); }
253 .bti-product-detail__button--disabled { background-color: var(--bti-color-content-disabled); color: var(--bti-color-content-medium); }
254 .bti-product-detail__button:hover, .bti-product-detail__button:focus-visible, .bti-product-detail__native-cart .btn-add-to-cart:hover, .bti-product-detail__native-cart .btn-add-to-cart:focus-visible { background-color: var(--bti-color-brand-pink); border-color: var(--bti-color-brand-pink); color: var(--bti-color-content-inverse); text-decoration: none; }
255 .bti-product-detail__native-cart { flex: 1; min-width: 0; }
256 .bti-product-detail__native-cart .add-to-cart.mb-2 { margin-bottom: 0 !important; }
257 .bti-product-detail__native-cart .add-to-cart-wrapper > .mb-3 { margin-bottom: 0 !important; }
258 .bti-product-detail__native-cart .btn-add-to-cart { gap: var(--bti-spacing-2); }
259 .bti-product-detail__native-cart .btn-add-to-cart .text-truncate-inline, .bti-product-detail__native-cart .btn-add-to-cart .text-truncate { display: inline !important; max-width: none; overflow: visible; }
260 .bti-product-detail__native-cart .quantity-selector, .bti-product-detail__native-cart .quantity-selector-container, .bti-product-detail__native-cart [data-quantity-selector], .bti-product-detail__native-cart .quantity-selector__input { display: none !important; }
261 .bti-product-detail__catalog-link { align-items: center; color: var(--bti-color-brand-purple); display: inline-flex; font-family: var(--bti-font-body); font-size: var(--bti-fs-body-m); font-weight: var(--bti-fw-semibold); gap: var(--bti-spacing-1); line-height: var(--bti-lh-body-m); text-decoration: none; width: fit-content; }
262 .bti-product-detail__catalog-link .compare-checkbox-root { margin: 0; }
263 .bti-product-detail__catalog-link .custom-control-label { color: inherit; cursor: pointer; font: inherit; }
264 .bti-product-detail__catalog-link .custom-control-label-text { color: inherit; }
265 .bti-product-detail__catalog-link .custom-control-label::before { background-color: transparent !important; border: 0 !important; height: var(--bti-spacing-5); left: 0; top: 50%; transform: translateY(-50%); width: var(--bti-spacing-5); }
266 .bti-product-detail__catalog-link .custom-control-label::after { background-image: none !important; border: 0 !important; content: "+"; color: currentColor; font-family: var(--bti-font-body); font-size: var(--bti-fs-title-m); height: auto; left: 0; line-height: var(--bti-lh-title-m); opacity: 1; top: 50%; transform: translateY(-50%); width: var(--bti-spacing-5); }
267 .bti-product-detail__catalog-link .custom-control-input:checked ~ .custom-control-label::after { content: "-"; }
268 .bti-product-detail__catalog-link .custom-control-input:focus-visible ~ .custom-control-label { outline: var(--bti-border-width) var(--bti-border-style) currentColor; outline-offset: var(--bti-spacing-1); }
269 .bti-product-detail__catalog-link:hover, .bti-product-detail__catalog-link:focus-within { color: var(--bti-color-brand-pink); text-decoration: none; }
270 .bti-product-detail__additional { margin: 0 auto; max-width: var(--bti-content-max-width); padding: 0 var(--bti-spacing-6) var(--bti-spacing-10); }
271 .bti-product-detail__notice { background-color: var(--bti-color-info-bg); color: var(--bti-color-info-dark); font-family: var(--bti-font-body); margin-bottom: var(--bti-spacing-6); padding: var(--bti-spacing-3); }
272 .bti-product-detail__accordion { border-bottom: var(--bti-border-width) var(--bti-border-style) var(--bti-color-content-disabled); }
273 .bti-product-detail__accordion:first-of-type { border-top: var(--bti-border-width) var(--bti-border-style) var(--bti-color-content-disabled); }
274 .bti-product-detail__accordion summary { align-items: center; color: var(--bti-color-content-high); cursor: pointer; display: flex; font-family: var(--bti-font-body); font-size: var(--bti-fs-title-m); font-weight: var(--bti-fw-medium); justify-content: space-between; line-height: var(--bti-lh-title-m); list-style: none; padding: var(--bti-spacing-4) 0; }
275 .bti-product-detail__accordion summary::-webkit-details-marker { display: none; }
276 .bti-product-detail__accordion summary::after { content: "+"; flex-shrink: 0; font-family: var(--bti-font-body); font-size: var(--bti-fs-title-l); line-height: var(--bti-lh-title-l); }
277 .bti-product-detail__accordion[open] summary::after { content: "-"; }
278 .bti-product-detail__specifications { margin: 0; padding-bottom: var(--bti-spacing-6); }
279 .bti-product-detail__specification { display: grid; gap: var(--bti-spacing-4); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); padding: var(--bti-spacing-2) 0; }
280 .bti-product-detail__specification dt, .bti-product-detail__specification dd { font-family: var(--bti-font-body); margin: 0; }
281 .bti-product-detail__specification dd { color: var(--bti-color-content-medium); text-align: right; }
282 @media (max-width: 991px) { .bti-product-detail__inner { gap: var(--bti-spacing-6); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); padding: var(--bti-spacing-6) var(--bti-spacing-5); } .bti-product-detail__buttons { flex-direction: column; gap: var(--bti-spacing-4); } .bti-product-detail__additional { padding-left: var(--bti-spacing-5); padding-right: var(--bti-spacing-5); } }
283 @media (max-width: 767px) { .bti-product-detail__inner { display: flex; flex-direction: column; padding: var(--bti-spacing-5) var(--bti-spacing-4); } .bti-product-detail__info { gap: var(--bti-spacing-6); } .bti-product-detail__description, .bti-product-detail__accordion-content { font-size: var(--bti-fs-body-m); line-height: var(--bti-lh-body-m); } .bti-product-detail__commerce-actions { justify-content: flex-start; } .bti-product-detail__delivery { align-items: flex-start; } .bti-product-detail__quantity { gap: var(--bti-spacing-2); justify-content: center; } .bti-product-detail__additional { padding: 0 var(--bti-spacing-4) var(--bti-spacing-8); } }
284</style>
285
286<#if isLoggedIn && productType == "simple" && cpInstanceId gt 0 && !blocked>
287<script>
288(function () {
289 var root = document.getElementById('${detailId}');
290 if (!root) { return; }
291 var cartButton = root.querySelector('.bti-product-detail__native-cart .btn-add-to-cart');
292 var nativeQuantity = root.querySelector('.bti-product-detail__native-cart input.quantity-selector, .bti-product-detail__native-cart input[name*="quantity"], .bti-product-detail__native-cart [data-quantity-selector] input');
293 var quantityValue = root.querySelector('[data-quantity-value]');
294 var stepButtons = Array.prototype.slice.call(root.querySelectorAll('[data-quantity-step]'));
295 var quantity = Math.max(1, parseInt(nativeQuantity ? nativeQuantity.value : '1', 10) || 1);
296 if (cartButton && !cartButton.querySelector('[data-bti-cart-label]')) {
297 var existingLabel = cartButton.querySelector('.text-truncate');
298 if (existingLabel) { existingLabel.textContent = 'Añadir al carrito'; existingLabel.setAttribute('data-bti-cart-label', 'true'); }
299 else { var cartLabel = document.createElement('span'); cartLabel.setAttribute('data-bti-cart-label', 'true'); cartLabel.textContent = 'Añadir al carrito'; cartButton.insertBefore(cartLabel, cartButton.firstChild); }
300 }
301 function renderQuantity() {
302 if (quantityValue) { quantityValue.textContent = String(quantity); }
303 if (nativeQuantity) { nativeQuantity.value = String(quantity); nativeQuantity.dispatchEvent(new Event('change', {bubbles: true})); nativeQuantity.dispatchEvent(new Event('input', {bubbles: true})); }
304 stepButtons.forEach(function (button) { button.disabled = button.getAttribute('data-quantity-step') === '-1' && quantity <= 1; });
305 }
306 stepButtons.forEach(function (button) { button.addEventListener('click', function () { quantity = Math.max(1, quantity + parseInt(button.getAttribute('data-quantity-step'), 10)); renderQuantity(); }); });
307 renderQuantity();
308})();
309</script>
310</#if>
311
312<#if isLoggedIn && productType == "grouped" && groupedEntries?has_content && !blocked>
313<script>
314(function () {
315 var root = document.getElementById('${detailId}');
316 if (!root) { return; }
317 var addButton = root.querySelector('[data-grouped-add]');
318 var status = root.querySelector('[data-purchase-status]');
319 var checks = Array.prototype.slice.call(root.querySelectorAll('.bti-product-detail__variant-check'));
320 function selectedChecks() { return checks.filter(function (check) { return check.checked; }); }
321 function updateButton() { if (addButton) { addButton.disabled = selectedChecks().length === 0; } }
322 function showStatus(message, error) { if (!status) { return; } status.hidden = !message; status.textContent = message; status.setAttribute('data-error', error ? 'true' : 'false'); }
323 checks.forEach(function (check) { check.addEventListener('change', updateButton); });
324 if (addButton) { addButton.addEventListener('click', async function () {
325 var selected = selectedChecks();
326 if (!selected.length || !${commerceOrderId?c}) { return; }
327 addButton.disabled = true; showStatus('Añadiendo productos al carrito...', false);
328 try {
329 for (var index = 0; index < selected.length; index++) {
330 var check = selected[index];
331 var quantityInput = root.querySelector('.bti-product-detail__variant-quantity[data-sku-id="' + check.value + '"]');
332 var quantity = Math.max(1, parseInt(quantityInput ? quantityInput.value : '1', 10) || 1);
333 var response = await Liferay.Util.fetch('/o/headless-commerce-delivery-cart/v1.0/carts/${commerceOrderId?c}/items/', {body: JSON.stringify({options: '[]', quantity: quantity, skuId: Number(check.value)}), headers: {'Content-Type': 'application/json'}, method: 'POST'});
334 if (!response.ok) { throw new Error(String(response.status)); }
335 }
336 showStatus('Productos añadidos al carrito.', false); Liferay.fire('commerce:product-added-to-cart', {});
337 } catch (error) { showStatus('No se han podido añadir los productos al carrito.', true); console.error('[BTI Product Detail] grouped add-to-cart', error); }
338 finally { updateButton(); }
339 }); }
340})();
341</script>
342</#if>
Recursos
Productos destacados
Ponemos a disposición de los profesionales contenido actualizado sobre nuestras tecnologías, aplicaciones clínicas y avances en investigación.
Consultar catálogo completoContacta

¡Gracias!


