Preguntas frecuentes del producto
Sección de preguntas frecuentes del producto: un título, hasta 10 pares pregunta/respuesta y una galería de hasta 15 imágenes y videos con un archivo principal. El comerciante la ubica en una página de producto puntual.
| En el panel | tipo en el JSON | Token de la clave |
|---|---|---|
| Preguntas frecuentes del producto | PRODUCT_FAQS | product_faqs |
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 y las preguntas dentro de items.
{ tipo: 'PRODUCT_FAQS'; // identificador del tipo en el bundle titulo: string | null; // columna compartida; hoy puede venir null nombre_personalizado?: string; // nombre interno del panel (opcional) items: Array<{ orden: number; // posición de la pregunta (0-based) campos: { question: string | null; // la pregunta answer: string | null; // la respuesta }; }>; campos: { productFaqsTitle: string | null; // título del producto productFaqsGallery: { // galería (hasta 15), en orden de presentación items: Array<{ url: string | null; // imagen o video (URL pública del archivo) mimeType: string | null; // "image/webp", "video/mp4", "video/webm" durationSeconds?: number; // solo videos READY con duración registrada position: number; // orden dentro de la galería isPrimary: boolean; // exactamente un item es el principal }>; }; // Campos anteriores, ahora DERIVADOS de la galería (compatibilidad con temas existentes): productFaqsMainImageUrl: string | null; // el item principal (o el primero si ninguno lo es) productFaqsMainImageMimeType: string | null; // "image/webp", "video/mp4", …; null sin archivo productFaqsMainImageDurationSeconds?: number; // solo videos READY con duración registrada productFaqsCollageImage1Url: string | null; // los 4 items siguientes, en orden y sin repetir el principal productFaqsCollageImage1MimeType: string | null; productFaqsCollageImage1DurationSeconds?: number; // …y lo mismo para CollageImage2, CollageImage3 y CollageImage4 }; estado: { lifecycle: string; storefrontVisible: boolean }; orden: number; indice?: number; // solo cuando hay más de una sección del mismo tipo}La numeración de preguntas (“Pregunta 1”, “Respuesta 1”) es solo del panel del
comerciante. No viaja en el JSON: el número se deriva del orden del items. No
lo persistas ni lo muestres en el storefront salvo que tu diseño lo pida.
Ejemplo
Sección titulada «Ejemplo»{ "sections": { "product_faqs-product-344443275-cmv16uy7d0001am0stxnh8zuv": { "tipo": "PRODUCT_FAQS", "titulo": null, "items": [ { "orden": 0, "campos": { "question": "¿Es apto para veganos?", "answer": "Sí, todos nuestros cereales son 100% de origen vegetal." } }, { "orden": 1, "campos": { "question": "¿Cuánto dura la frescura?", "answer": "12 meses cerrado; una vez abierto, consúmelo en 2 semanas." } } ], "campos": { "productFaqsTitle": "Nuevo Cereal Mexicano!", "productFaqsGallery": { "items": [ { "url": "https://cdn.example.com/cereal-principal.webp", "mimeType": "image/webp", "position": 0, "isPrimary": true }, { "url": "https://cdn.example.com/cereal-1.webp", "mimeType": "image/webp", "position": 1, "isPrimary": false }, { "url": "https://cdn.example.com/cereal-2.mp4", "mimeType": "video/mp4", "durationSeconds": 12, "position": 2, "isPrimary": false } ] }, "productFaqsMainImageUrl": "https://cdn.example.com/cereal-principal.webp", "productFaqsMainImageMimeType": "image/webp", "productFaqsCollageImage1Url": "https://cdn.example.com/cereal-1.webp", "productFaqsCollageImage1MimeType": "image/webp", "productFaqsCollageImage2Url": "https://cdn.example.com/cereal-2.mp4", "productFaqsCollageImage2MimeType": "video/mp4", "productFaqsCollageImage2DurationSeconds": 12, "productFaqsCollageImage3Url": null, "productFaqsCollageImage3MimeType": null, "productFaqsCollageImage4Url": null, "productFaqsCollageImage4MimeType": null }, "estado": { "lifecycle": "active", "storefrontVisible": true }, "orden": 0 } }}Renderizar
Sección titulada «Renderizar»La galería puede venir vacía y items también. Renderizá cada parte
condicionalmente. Usá productFaqsGallery para temas nuevos; los campos
productFaqsMainImage* y productFaqsCollageImage1..4* siguen disponibles y no
cambian de nombre:
const res = await fetch( `https://sectionfy.codefy-apps.com/api/public/content/${TOKEN}/product/${PRODUCT_ID}`,);const { sections } = await res.json();
const faqs = Object.entries(sections) .filter(([, s]) => s.tipo === 'PRODUCT_FAQS') .filter(([, s]) => s.estado.lifecycle === 'active' && s.estado.storefrontVisible) .sort(([, a], [, b]) => a.orden - b.orden);
const medio = (url, mime) => !url ? '' : mime?.startsWith('video/') ? `<video src="${url}" muted loop playsinline autoplay></video>` : `<img src="${url}" loading="lazy" alt="">`;
for (const [key, seccion] of faqs) { const { campos, items } = seccion; const galeria = campos.productFaqsGallery.items; const principal = galeria.find((m) => m.isPrimary) ?? galeria[0]; const resto = galeria.filter((m) => m !== principal);
contenedor.insertAdjacentHTML( 'beforeend', `<section class="sectionfy-product-faqs" data-block="${key}"> <div class="sectionfy-product-faqs__panel"> ${campos.productFaqsTitle ? `<h2>${escapar(campos.productFaqsTitle)}</h2>` : ''} <dl class="sectionfy-product-faqs__lista"> ${[...items] .sort((a, b) => a.orden - b.orden) .map( (item) => ` <details> <summary>${escapar(item.campos.question ?? '')}</summary> <p>${escapar(item.campos.answer ?? '')}</p> </details>`, ) .join('')} </dl> </div> <div class="sectionfy-product-faqs__principal"> ${principal ? medio(principal.url, principal.mimeType) : ''} </div> <div class="sectionfy-product-faqs__collage"> ${resto.map((m) => medio(m.url, m.mimeType)).join('')} </div> </section>`, );}- Máximo 10 preguntas. El panel bloquea la carga de la número 11; el JSON nunca trae más de 10.
- La numeración es del panel, no del contrato. “Pregunta 1” / “Respuesta 1” se
derivan del orden de
items; si tu diseño necesita números, calculalos vos conordeny no los persistas. - Pregunta y respuesta siempre viajan juntas. Cada item trae ambas o ninguna (el panel exige las dos).
- La galería admite hasta 15 imágenes y videos. Los items vienen ordenados por
position; exactamente uno tieneisPrimary: true(si la galería no está vacía).productFaqsGallerynunca expone IDs de archivos. - Los campos anteriores ahora se derivan de la galería, sin cambio de contrato.
productFaqsMainImage*es el item principal (o el primero si ninguno lo es) yproductFaqsCollageImage1..4*son los siguientes cuatro items en orden, sin repetir el principal. Si hay menos de cinco archivos, los últimos campos llegan comonull. - La galería admite hasta 15 archivos; los campos anteriores solo reflejan los
primeros cinco. Los extras únicamente están en
productFaqsGallery, así que un tema que quiera mostrarlos todos tiene que leer la galería. - Las imágenes pueden ser videos. Decidí
<img>o<video>por elmimeType/*MimeType(video/…), no por la extensión de la URL. LosdurationSeconds/*DurationSecondsson aditivos: solo llegan para videos. - Un archivo que no está
READYno aparece en la galería. Los campos derivados se compactan: toman los siguientes archivos disponibles por posición, sin dejar unnullen el lugar del que falta; solo los últimos campos llegan comonull(*Url). Renderizá la sección sin ese archivo, no la descartes. - Notas de la migración. Al pasar de los cinco campos de imagen a la galería,
los datos existentes se copiaron una sola vez, en el orden principal y collage 1
a 4. Dos casos cambian de comportamiento: si en un bloque la única imagen
conservada era un collage, en la galería queda como principal y por eso llega en
productFaqsMainImage*(antes seguía siendo un collage); y un archivo que todavía estaba pendiente (noREADY) al migrar no se copió y no se incorpora después, aunque más tarde quede listo: hay que volver a agregarlo desde el panel. - Placement
productsolamente. Llega por el bundle de contenido de la página de producto; no tiene endpoint propio.
