Beneficios Destacados
Tarjetas de “beneficios o testimonios destacados”: una imagen o video, un título corto y
una estadística con su descripción (ej. 86% → “reportó un acabado más saludable”).
El comerciante carga hasta 3 tarjetas por sección, más un título principal.
| En el panel | tipo en el JSON | Token de la clave |
|---|---|---|
| Beneficios Destacados | FEATURED_BENEFITS | featured_benefits |
Shape de la sección
Sección titulada «Shape de la sección»Este tipo usa el sobre genérico de los tipos generados: los campos editables
viajan dentro de campos, no en propiedades con nombre propio.
{ titulo: string | null; // hoy siempre null: el título editable vive en campos.heading nombre_personalizado?: string; // nombre interno del panel (opcional) items: Array<{ orden: number; campos: { imageUrl: string | null; // imagen o video; null si no hay archivo o no está READY imageMimeType: string | null; // "image/webp", "video/mp4", …; null sin archivo imageDurationSeconds?: number; // solo videos READY con duración registrada; si no, la clave no viene cardTitle: string | null; statValue: string | null; // "86%", "2×", … statDescription: string | null; }; }>; campos: { heading: string | null; // el "Título principal" de la sección }; estado: { lifecycle: string; storefrontVisible: boolean }; orden: number; indice?: number; // solo cuando hay más de una sección del mismo tipo}Los items vienen sin filtrar: pueden traer tarjetas con todo en null
(el panel permite guardar una tarjeta vacía). Filtrá como en el ejemplo.
Ejemplo
Sección titulada «Ejemplo»{ "sections": { "featured_benefits-product-344443275-cmu7axqfa0001uhc8b8hdznmy": { "titulo": null, "items": [ { "orden": 0, "campos": { "imageUrl": "https://cdn.example.com/serum.mp4", "imageMimeType": "video/mp4", "imageDurationSeconds": 12, "cardTitle": "Glow sin maquillaje", "statValue": "86%", "statDescription": "reportó un acabado más saludable desde la primera semana." } } ], "campos": { "heading": "Por qué este serum entra en la rutina" }, "estado": { "lifecycle": "active", "storefrontVisible": true }, "orden": 0 } }}Renderizar
Sección titulada «Renderizar»La regla de render y el orden son los compartidos. Lo propio de este tipo es
resolver campos y tolerar imageUrl: null:
const res = await fetch( `https://sectionfy.codefy-apps.com/api/public/content/${TOKEN}/product/${PRODUCT_ID}`,);const { sections } = await res.json();
const beneficios = Object.entries(sections) .filter(([, s]) => s.tipo === 'FEATURED_BENEFITS') .filter(([, s]) => s.estado.lifecycle === 'active' && s.estado.storefrontVisible) .sort(([, a], [, b]) => a.orden - b.orden);
for (const [key, seccion] of beneficios) { const tarjetas = seccion.items .slice() .sort((a, b) => a.orden - b.orden) .filter((item) => item.campos.cardTitle || item.campos.statValue) .map( (item) => ` <article class="sectionfy-fb__card" data-item="${item.orden}"> ${ !item.campos.imageUrl ? '' : item.campos.imageMimeType?.startsWith('video/') ? `<video src="${item.campos.imageUrl}" muted loop playsinline autoplay></video>` : `<img src="${item.campos.imageUrl}" alt="${escapar(item.campos.cardTitle ?? '')}" loading="lazy">` } ${item.campos.cardTitle ? `<h3>${escapar(item.campos.cardTitle)}</h3>` : ''} ${ item.campos.statValue ? `<p class="sectionfy-fb__stat"> <strong>${escapar(item.campos.statValue)}</strong> ${escapar(item.campos.statDescription ?? '')} </p>` : '' } </article> `, ) .join('');
contenedor.insertAdjacentHTML( 'beforeend', `<section class="sectionfy-fb" data-block="${key}"> ${seccion.campos.heading ? `<h2>${escapar(seccion.campos.heading)}</h2>` : ''} <div class="sectionfy-fb__grid">${tarjetas}</div> </section>`, );}- El título principal no es
titulo. El campo editable por el comerciante llega encampos.heading;tituloes la columna compartida y hoy vanull. - Máximo 3 tarjetas. El panel bloquea la cuarta; no asumas más de tres
items. - Placements
home,categoryyproduct. En el endpoint propiocategoryyproductpidentarget_id(si falta,400) yhomeno lo admite; las secciones generales de category/product llegan por el bundle (/{page}o/{page}/{id}). imageUrlesnullmientras el asset no estéREADY. Renderizá la tarjeta sin imagen en ese caso, no la descartes.- La imagen de la tarjeta puede ser un video. Decidí
<img>o<video>porimageMimeType(video/…), no por la extensión de la URL.imageDurationSecondses aditivo: solo llega para videos y nunca para imágenes.
