Carrusel de testimonios en video
Sección de testimonios verticales. Cada tarjeta puede usar una imagen o un video, mostrar el nombre y la descripción del testimonio, y enlazar al producto. El comerciante carga hasta 10 testimonios por sección, más un título y una descripción de la sección. El storefront de la aplicación la presenta como un carrusel horizontal tipo coverflow, con la tarjeta activa al centro y las tarjetas laterales parcialmente superpuestas.
| En el panel | tipo en el JSON | Token de la clave |
|---|---|---|
| Carrusel de testimonios en video | VIDEO_REVIEWS | video_reviews |
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 de cada
testimonio viajan dentro de campos.
{ titulo: string | null; // título de la sección; null si el comerciante no lo escribió nombre_personalizado?: string; // nombre interno del panel (opcional) items: Array<{ orden: number; campos: { mediaUrl: string | null; // imagen o video; null si no hay archivo o no está READY mediaMimeType: string | null; // "video/mp4", "video/webm", "image/webp"; null sin archivo mediaDurationSeconds?: number; // solo videos READY con duración registrada; si no, la clave no viene heading: string | null; // nombre o título del testimonio body: string | null; // texto del testimonio productLinkUrl: string | null; // URL absoluta https:// o null productLinkLabel: string | null; }; }>; campos: { descripcion: string | null; // descripción 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: un testimonio cuyo archivo todavía no está READY (o
fue eliminado) conserva su orden pero trae mediaUrl y mediaMimeType en null.
Un testimonio sin archivo no tiene nada que mostrar: omítelo.
Ejemplo
Sección titulada «Ejemplo»{ "sections": { "video_reviews-product-344443275-cmu7axqfa0001uhc8b8hdznmy": { "titulo": "Historias reales", "items": [ { "orden": 0, "campos": { "mediaUrl": "https://cdn.example.com/ann.mp4", "mediaMimeType": "video/mp4", "mediaDurationSeconds": 42, "heading": "Ann F.", "body": "Pine Stripe Roll Bed", "productLinkUrl": "https://example.com/products/pine-stripe", "productLinkLabel": "Ver producto →" } } ], "campos": { "descripcion": "Mira lo que dicen nuestros clientes" }, "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, omitir los testimonios sin archivo y decidir entre <img> y <video> según
mediaMimeType:
const res = await fetch( `https://sectionfy.codefy-apps.com/api/public/content/${TOKEN}/product/${PRODUCT_ID}`,);const { sections } = await res.json();
const carruseles = Object.entries(sections) .filter(([, s]) => s.tipo === 'VIDEO_REVIEWS') .filter(([, s]) => s.estado.lifecycle === 'active' && s.estado.storefrontVisible) .sort(([, a], [, b]) => a.orden - b.orden);
for (const [key, seccion] of carruseles) { const tarjetas = seccion.items .slice() .sort((a, b) => a.orden - b.orden) .filter((item) => item.campos.mediaUrl) .map( (item) => ` <article class="sectionfy-vr__card" data-item="${item.orden}"> ${ item.campos.mediaMimeType?.startsWith('video/') ? `<video src="${item.campos.mediaUrl}" muted loop playsinline></video>` : `<img src="${item.campos.mediaUrl}" alt="${escapar(item.campos.heading ?? '')}" loading="lazy">` } ${item.campos.heading ? `<h3>${escapar(item.campos.heading)}</h3>` : ''} ${item.campos.body ? `<p>${escapar(item.campos.body)}</p>` : ''} ${ item.campos.productLinkUrl ? `<a href="${escapar(item.campos.productLinkUrl)}"> ${escapar(item.campos.productLinkLabel ?? 'Ver producto')} </a>` : '' } </article> `, ) .join('');
if (!tarjetas) continue;
contenedor.insertAdjacentHTML( 'beforeend', `<section class="sectionfy-vr" data-block="${key}"> ${seccion.titulo ? `<h2>${escapar(seccion.titulo)}</h2>` : ''} ${seccion.campos.descripcion ? `<p>${escapar(seccion.campos.descripcion)}</p>` : ''} <div class="sectionfy-vr__track">${tarjetas}</div> </section>`, );}El renderer NubeSDK incluido en la aplicación carga el widget video-reviews.html y le
entrega los datos mediante postMessage. El widget se encarga de la navegación, la
superposición visual, el botón de reproducción y el enlace al producto.
Si se integra la respuesta manualmente, se recomienda mantener la relación vertical
aproximada 9:16 y usar object-fit: cover.
tituloycampos.descripcion. Son el título y la descripción de la sección. Pueden venir ennull; el renderer NubeSDK usaCarrusel de testimonios en videocomo título de respaldo, pero la API no lo inventa.- Máximo 10 testimonios. El panel bloquea el undécimo; no asumas más de diez
items. productLinkUrles siempre una URL absolutahttps://(onull). El panel y la API rechazanhttp://,javascript:,data:y rutas relativas; un enlace vacío se guarda comonull.productLinkLabelpermite personalizar el texto; la interfaz del renderer NubeSDK usaVer producto →como respaldo visual.- 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}). mediaUrlesnullmientras el archivo no estéREADY. El archivo del testimonio es un video (video/mp4,video/webm) o una imagenimage/webpde la biblioteca de la tienda; un PDF no es válido. Decide<img>o<video>pormediaMimeType, no por la extensión de la URL.mediaDurationSecondses aditivo: solo llega para videos.
