// Use bootstrap mixin for transition .transition-all() { .transition('all 0.3s ease-in-out'); } // Condition select if @color is a color // This mixin will do nothing if @color is not a color (like false or etc) .if_color (@color, @property, @value) when (iscolor(@color)) { @{property}: @value !important; } //Content .vc_grid { &.vc_row { padding-left: 0; padding-right: 0; margin-left: 0; margin-right: 0; } } .vc-row[data-vc-full-width] { .vc_grid.vc_row { overflow: hidden; } } @import "vc_grid_gap.less"; @import "../../params/vc_grid_item/vc_grid_item.less"; @import "vc_grid_style.less"; @import "vc_grid_loading.less"; // Import all other extensions @import "vc_grid_filter.less"; @import "vc_grid_carousel.less"; /** * API utilities - unified fetch wrapper and block-state loader * * Reads (from existing globals): * SFE.ManagerData.restUrl - REST base URL * SFE.ManagerData.nonce - WP nonce * SFE.ManagerData.postId - current post ID * SFE.ListBlockTracker * SFE.Context.activeEditor - live reference via getter * * Exposes: SFE.Api * { * apiCall, * hydrateEditorBlockStateOnOpen, * fetchBlockAttributes, * resolveMediaAttributes, * queueResolvedMediaAttributes, * ensureResolvedMediaAttributes * } */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; /** * Unified API call handler with consistent error handling */ async function apiCall(endpoint, data, button = null) { const restBase = (SFE.ManagerData.restUrl || '').replace(/\/$/, ''); const path = endpoint.startsWith('/') ? endpoint : '/' + endpoint; const originalText = button?.textContent; const wasDisabled = button?.disabled; if (button) { button.disabled = true; button.setAttribute('mwp-sfe-btn-loading', 'true'); } try { const response = await fetch(restBase + path, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': SFE.ManagerData.nonce }, body: JSON.stringify(data) }); const result = await response.json(); if (!response.ok) { const error = new Error(result?.error || `HTTP ${response.status}: ${response.statusText}`); error.payload = result; throw error; } // Check for error in response if (result.success === false || result.error) { const error = new Error(result.error || 'Operation failed'); error.payload = result; throw error; } return result; } catch (error) { // Reset button state on error if (button) { button.disabled = wasDisabled || false; button.removeAttribute('mwp-sfe-btn-loading'); if (originalText) button.textContent = originalText; } throw error; } } function resolveBlockStateOverride(editorState) { const publicApiBridge = SFE.PublicApiBridge || null; const editorUuid = String(editorState?.uuid || '').trim(); if ( publicApiBridge && editorUuid && publicApiBridge.stagedBlockStates instanceof Map && publicApiBridge.stagedBlockStates.has(editorUuid) ) { const stagedEntry = publicApiBridge.stagedBlockStates.get(editorUuid) || null; publicApiBridge.stagedBlockStates.delete(editorUuid); if (stagedEntry && stagedEntry.blockState) { return publicApiBridge.clonePlainData(stagedEntry.blockState); } } if (typeof SFE.ResolveBlockState === 'function') { try { const resolved = SFE.ResolveBlockState(editorState); if (resolved) return resolved; } catch (error) { console.warn('FrontEdit: block-state resolver hook failed', error); } } const batchManager = SFE.BatchEditManager || null; if ( editorState?.saveStrategy === 'batch' && batchManager && typeof batchManager.isSessionActive === 'function' && batchManager.isSessionActive() && typeof batchManager.getBlockStateForUuid === 'function' ) { return batchManager.getBlockStateForUuid(editorState.uuid); } return null; } function resolveMediaSourceFromAttributes(editorState, blockState) { const schemaRuntime = SFE.SchemaRuntime || null; if (!schemaRuntime || typeof schemaRuntime.resolveInitialMediaSource !== 'function') { return ''; } const resolved = schemaRuntime.resolveInitialMediaSource(editorState, blockState); if (typeof resolved === 'string' && resolved.trim()) { return resolved; } return ''; } function shouldInitListTracker(editorState, blockState) { const tagName = editorState?.element?.tagName || ''; const isListElement = tagName === 'OL' || tagName === 'UL'; if (!isListElement) return false; const schemaRuntime = SFE.SchemaRuntime || null; const hasSchemaListBinding = ( schemaRuntime && typeof schemaRuntime.hasListBinding === 'function' && schemaRuntime.hasListBinding(editorState) ); return hasSchemaListBinding || blockState?.blockName === 'core/list'; } /** * Resolve canonical attachment attributes for the current editor state. * * This lets schema-driven media saves preserve block-level intent such as an * existing `sizeSlug` on `core/image` while a new attachment is selected from * the frontend editor. * * @param {object} editorState Current editor state. * @param {object} [options] * @param {string} [options.expectedResolutionKey] Stable selection key that * must still match before the * resolved payload is applied. * @returns {Promise} Resolved media change patch, or null when no resolution is needed. */ async function resolveMediaAttributes(editorState, options = {}) { const rootElement = editorState?.element || null; const mediaChanges = rootElement?._mwpMediaChanges; if (!mediaChanges || typeof mediaChanges !== 'object') return null; const expectedResolutionKey = typeof options.expectedResolutionKey === 'string' ? options.expectedResolutionKey : ''; const attachmentId = mediaChanges.id ?? null; if (!attachmentId) return null; const blockAttrs = editorState?.blockState?.attrs; if (!blockAttrs || typeof blockAttrs !== 'object') return null; const sizeSlug = typeof blockAttrs.sizeSlug === 'string' ? blockAttrs.sizeSlug.trim() : ''; const response = await apiCall('/resolve-media-attributes', { post_id: SFE.ManagerData.postId, attachment_id: attachmentId, size_slug: sizeSlug }); if (!response || response.success === false || !response.url) { return null; } const nextChanges = { ...mediaChanges, url: response.url, resolvedUrl: response.url, resolvedWidth: Number.isFinite(response.width) ? response.width : null, resolvedHeight:Number.isFinite(response.height) ? response.height : null, }; if (rootElement) { const currentResolutionKey = buildMediaResolutionKey(rootElement._mwpMediaChanges); if (expectedResolutionKey && currentResolutionKey !== expectedResolutionKey) { return null; } rootElement._mwpMediaChanges = nextChanges; } return nextChanges; } /** * Build one stable identity key for the current pending media selection. * * Media replacement can change again before async attribute resolution * finishes. This key lets the shared save/runtime layer ignore late results * from an older selection instead of overwriting the current canonical media * state with stale data. * * @param {object|null} mediaChanges Current media change payload. * @returns {string} Stable resolution key, or an empty string when invalid. */ function buildMediaResolutionKey(mediaChanges) { if (!mediaChanges || typeof mediaChanges !== 'object') { return ''; } const attachmentId = mediaChanges.id ?? ''; const url = typeof mediaChanges.url === 'string' ? mediaChanges.url.trim() : ''; return `${attachmentId}::${url}`; } /** * Queue shared media resolution and cache the in-flight promise on the element. * * @param {object} editorState Current editor state. * @param {object} [options] * @param {HTMLElement|null} [options.mediaElement] * @param {function|null} [options.syncRootChanges] * @returns {Promise} */ function queueResolvedMediaAttributes(editorState, options = {}) { if (!editorState?.element) { return Promise.resolve(null); } const rootElement = editorState.element; const requestedChanges = ( rootElement._mwpMediaChanges && typeof rootElement._mwpMediaChanges === 'object' ) ? { ...rootElement._mwpMediaChanges } : null; const requestedResolutionKey = buildMediaResolutionKey(requestedChanges); const existingPromise = rootElement._mwpResolvedMediaPromise; if ( existingPromise && typeof existingPromise.then === 'function' && rootElement._mwpResolvedMediaKey === requestedResolutionKey ) { return existingPromise; } const mediaElement = options.mediaElement || null; const syncRootChanges = typeof options.syncRootChanges === 'function' ? options.syncRootChanges : null; const pending = Promise.resolve() .then(() => { const currentChanges = rootElement._mwpMediaChanges; if ( !currentChanges || buildMediaResolutionKey(currentChanges) !== requestedResolutionKey ) { return null; } return resolveMediaAttributes(editorState, { expectedResolutionKey: requestedResolutionKey }); }) .then((nextChanges) => { const currentChanges = rootElement._mwpMediaChanges; if ( !nextChanges || !currentChanges || buildMediaResolutionKey(currentChanges) !== requestedResolutionKey ) { return null; } if (nextChanges && mediaElement) { mediaElement._mwpMediaChanges = { ...nextChanges }; } if (syncRootChanges) { syncRootChanges(nextChanges); } return nextChanges; }); rootElement._mwpResolvedMediaKey = requestedResolutionKey; rootElement._mwpResolvedMediaPromise = pending.finally(() => { if (rootElement._mwpResolvedMediaPromise === pending) { delete rootElement._mwpResolvedMediaPromise; delete rootElement._mwpResolvedMediaKey; } }); return rootElement._mwpResolvedMediaPromise; } /** * Ensure resolved media attributes are settled before serialization/capture. * * @param {object} editorState Current editor state. * @returns {Promise} */ async function ensureResolvedMediaAttributes(editorState) { const pending = editorState?.element?._mwpResolvedMediaPromise; if (pending && typeof pending.then === 'function') { return pending; } const resolutionKey = buildMediaResolutionKey(editorState?.element?._mwpMediaChanges || null); return resolveMediaAttributes(editorState, { expectedResolutionKey: resolutionKey }); } /** * Shared editor-open hydration hook. * * Every editor entry path (text, mixed, media, container) should call this once * immediately after SFE.Context.activeEditor is assigned. It marks the editor as * having requested attrs and runs the shared block-state hydration pipeline. * * In free mode this results in per-block /get-block-state hydration on each open. * In batch mode this first calls ensureSession() so the full block tree is * loaded once, then resolves the active block from the batch cache. * * @param {object} editorState Current editor state. * @returns {void} */ function hydrateEditorBlockStateOnOpen(editorState) { if (!editorState || editorState._mwpBlockAttrsRequested) { return; } editorState._mwpBlockAttrsRequested = true; fetchBlockAttributes(editorState).catch((error) => { console.warn('FrontEdit: failed to hydrate editor block state on open', error); }); } /** * Fetch block attributes in background and update editorState * Disables primary button until loaded */ async function fetchBlockAttributes(editorState) { const postId = SFE.ManagerData.postId; const ListBlockTracker = SFE.ListBlockTracker; const { uuid, actionsContainer } = editorState; const batchManager = SFE.BatchEditManager || null; const getLivePrimaryButton = () => ( actionsContainer ? actionsContainer.querySelector('.mwp-sfe-btn-primary-inline') : null ); const initialPrimaryBtn = getLivePrimaryButton(); /** * Return the initial and current primary buttons (deduped) so loading state * can be removed even if the action bar swapped UIs during attr fetch. * * @return {HTMLElement[]} */ const getTrackedPrimaryButtons = () => { const buttons = []; if (initialPrimaryBtn) buttons.push(initialPrimaryBtn); const livePrimaryBtn = getLivePrimaryButton(); if (livePrimaryBtn && livePrimaryBtn !== initialPrimaryBtn) { buttons.push(livePrimaryBtn); } return buttons; }; /** * Remove loading attributes from tracked primary buttons and restore * disabled state based on button type. * * @param {string|null} originalBtnText Original text from the initial primary button. * @return {void} */ const clearPrimaryLoadingState = (originalBtnText = null) => { const tracked = getTrackedPrimaryButtons(); tracked.forEach((btn) => { if (!btn || !btn.hasAttribute('data-loading-attrs')) return; btn.removeAttribute('data-loading-attrs'); btn.removeAttribute('mwp-sfe-btn-loading'); if (btn === initialPrimaryBtn && originalBtnText !== null) { btn.textContent = originalBtnText; } if (btn.hasAttribute('data-url-gated')) { const editorWrap = btn.closest('.mwp-sfe-inline-media-editor'); const input = editorWrap ? editorWrap.querySelector('#mwp-sfe-media-upload') : null; const value = input && typeof input.value === 'string' ? input.value : ''; btn.disabled = !value.trim(); return; } btn.disabled = false; }); }; const disableSaveButton = (msg) => { const primaryBtn = getLivePrimaryButton() || initialPrimaryBtn; if (primaryBtn) { primaryBtn.removeAttribute('data-loading-attrs'); primaryBtn.removeAttribute('mwp-sfe-btn-loading'); primaryBtn.disabled = true; primaryBtn.textContent = 'Error'; primaryBtn.title = msg; primaryBtn.style.background = '#dc3232'; primaryBtn.style.borderColor = '#dc3232'; primaryBtn.style.cursor = 'not-allowed'; } }; const applyBlockState = (blockState, originalBtnText = null) => { if (SFE.Context.activeEditor !== editorState) return; editorState.originalAttributes = blockState.attrs || {}; editorState.blockName = blockState.blockName || ''; editorState.originalHTML = blockState.html || ''; if (editorState.isMediaEditor && !editorState._originalSrcResolved) { const srcFromAttrs = resolveMediaSourceFromAttributes(editorState, blockState); editorState.originalContent = srcFromAttrs; editorState.originalSrc = srcFromAttrs; editorState._originalSrcResolved = true; } editorState.blockState = blockState; const schemaRuntime = SFE.SchemaRuntime || null; if ( editorState._mwpSchemaRuntime && schemaRuntime && typeof schemaRuntime.refreshEditorState === 'function' ) { schemaRuntime.refreshEditorState(editorState, { blockState, attributeChanges: editorState.attributeChanges || null, }); if ( Array.isArray(editorState.editableComponents) && SFE.TextEditor && typeof SFE.TextEditor.refreshEditableComponents === 'function' ) { SFE.TextEditor.refreshEditableComponents(editorState); } } if (shouldInitListTracker(editorState, blockState)) { editorState.listTracker = ListBlockTracker.init(editorState.element, blockState); } clearPrimaryLoadingState(originalBtnText); if ( editorState?.saveStrategy === 'batch' && batchManager && typeof batchManager._captureEditorOpenBaseline === 'function' ) { batchManager._captureEditorOpenBaseline(editorState); } }; // If we're editing a draft, skip the server fetch entirely. // The draft element is already in the DOM with the correct content, // and its serialized block markup is stored in draftEditState.draftRawContent. // Fetching /get-block-state would return the published version, which has // different attributes (e.g. text alignment) and would silently revert them on save. const draftEditState = SFE.Context.draftEditState; if (draftEditState && draftEditState.draftRawContent && draftEditState.draftElement === editorState.element) { const rawContent = draftEditState.draftRawContent; let parsedBlock; try { [ parsedBlock ] = wp.blocks.parse( rawContent ); } catch (e) { disableSaveButton('Cannot save: Draft block data failed to parse'); return; } if (!parsedBlock) { disableSaveButton('Cannot save: Draft block data failed to parse'); return; } // Recursively map JS block format to PHP block format const mapToPhpBlock = (jsBlock) => { if (!jsBlock) return null; return { blockName: jsBlock.name || '', attrs: jsBlock.attributes || {}, innerHTML: jsBlock.originalContent || '', innerBlocks: (jsBlock.innerBlocks || []).map(mapToPhpBlock), innerContent: jsBlock.innerContent || [] }; }; editorState.blockState = mapToPhpBlock(parsedBlock); editorState.blockState.rawContent = rawContent; // Persist raw string editorState.originalAttributes = editorState.blockState.attrs; editorState.blockName = editorState.blockState.blockName; if (shouldInitListTracker(editorState, editorState.blockState)) { editorState.listTracker = ListBlockTracker.init(editorState.element, editorState.blockState); } clearPrimaryLoadingState(); return; } const resolvedBlockState = resolveBlockStateOverride(editorState); if (resolvedBlockState) { applyBlockState(resolvedBlockState); clearPrimaryLoadingState(); return; } const primaryBtn = initialPrimaryBtn; const originalText = primaryBtn ? primaryBtn.textContent : null; if (primaryBtn) { primaryBtn.disabled = true; primaryBtn.setAttribute('data-loading-attrs', 'true'); primaryBtn.setAttribute('mwp-sfe-btn-loading', 'true'); // Keep original text, let CSS handle the loading visual } if ( editorState?.saveStrategy === 'batch' && batchManager && typeof batchManager.isEnabled === 'function' && batchManager.isEnabled() && typeof batchManager.ensureSession === 'function' ) { try { await batchManager.ensureSession(); } catch (error) { console.warn('FrontEdit: batch session ensure failed, falling back to per-block fetch', error); } } const resolvedBlockStateAfterEnsure = resolveBlockStateOverride(editorState); if (resolvedBlockStateAfterEnsure) { applyBlockState(resolvedBlockStateAfterEnsure, originalText); clearPrimaryLoadingState(originalText); return; } if (primaryBtn) { try { const blockState = await apiCall('/get-block-state', { post_id: postId, element_uuid: uuid }); applyBlockState(blockState, originalText); } catch (error) { console.warn('Failed to fetch block state:', error); alert('Failed to load block data. Editing is disabled to prevent data loss. Please refresh the page.'); if (SFE.Context.activeEditor === editorState) { disableSaveButton('Cannot save: Block data failed to load'); } } } else { try { const blockState = await apiCall('/get-block-state', { post_id: postId, element_uuid: uuid }); applyBlockState(blockState); } catch (error) { console.warn('Failed to fetch block state:', error); editorState.originalAttributes = {}; editorState.blockName = ''; } } } SFE.Api = { apiCall, hydrateEditorBlockStateOnOpen, fetchBlockAttributes, resolveMediaAttributes, queueResolvedMediaAttributes, ensureResolvedMediaAttributes }; })(); Política de Cookies – kibekis

Política de Cookies

¿Qué son las cookies?

En inglés, el término "cookie" significa galleta, pero en el ámbito de la navegación web, una "cookie" es algo completamente distinto. Cuando accede a nuestro Sitio Web, en el navegador de su dispositivo se almacena una pequeña cantidad de texto que se denomina "cookie". Este texto contiene información variada sobre su navegación, hábitos, preferencias, personalizaciones de contenidos, etc...

Existen otras tecnologías que funcionan de manera similar y que también se usan para recopilar datos sobre tu actividad de navegación. Llamaremos "cookies" a todas estas tecnologías en su conjunto.

Los usos concretos que hacemos de estas tecnologías se describen en el presente documento.

¿Para qué se utilizan las cookies en esta web?

Las cookies son una parte esencial de cómo funciona el Sitio Web. El objetivo principal de nuestras cookies es mejorar su experiencia en la navegación. Por ejemplo, para recordar sus preferencias (idioma, país, etc.) durante la navegación y en futuras visitas. La información recogida en las cookies nos permite además mejorar la web, adaptarla a sus intereses como usuario, acelerar las búsquedas que realice, etc..

En determinados casos, si hemos obtenido su previo consentimiento informado, podremos utilizar cookies para otros usos, como por ejemplo para obtener información que nos permita mostrarle publicidad basada en el análisis de sus hábitos de navegación.

¿Para qué NO se utilizan las cookies en esta web?

En las cookies que utilizamos no se almacena información sensible de identificación personal como su nombre, dirección, tu contraseña, etc...

¿Quién utiliza la información almacenada en las cookies?

La información almacenada en las cookies de nuestro Sitio Web es utilizada exclusivamente por nosotros, a excepción de aquellas identificadas más adelante como "cookie de terceros", que son utilizadas y gestionadas por entidades externas que nos proporcionan servicios que mejoran la experiencia del usuario. Por ejemplo las estadísticas que se recogen sobre el número de visitas, el contenido que más gusta, etc...

¿Cómo puede evitar el uso de cookies en este Sitio Web?

Si prefiere evitar el uso de las cookies, puede RECHAZAR su uso o puede CONFIGURAR las que quiere evitar y las que permite utilizar (en este documento le damos información ampliada al respecto de cada tipo de cookie, su finalidad, destinatario, temporalidad, etc... ).

Si las ha aceptado, no volveremos a preguntarle a menos que borre las cookies en su dispositivo según se indica en el apartado siguiente. Si quiere revocar el consentimiento tendrá que eliminar las cookies y volver a configurarlas.

¿Cómo deshabilito y elimino la utilización de cookies?

Para restringir, bloquear o borrar las cookies de este Sitio Web (y las usada por terceros) puede hacerlo, en cualquier momento, modificando la configuración de su navegador. Tenga en cuenta que esta configuración es diferente en cada navegador.

En los siguientes enlaces encontrará instrucciones para habilitar o deshabilitar las cookies en los navegadores más comunes.

¿Qué tipos de cookies se utilizan en esta página web?

Cada página web utiliza sus propias cookies. En nuestra web utilizamos las que se indican a continuación:

SEGÚN LA ENTIDAD QUE LO GESTIONA

Cookies propias:

Son aquellas que se envían al equipo terminal del Usuario desde un equipo o dominio gestionado por el propio editor y desde el que se presta el servicio solicitado por el Usuario.

Cookies de terceros:

Son aquellas que se envían al equipo terminal del Usuario desde un equipo o dominio que no es gestionado por el editor, sino por otra entidad que trata los datos obtenidos través de las cookies.

En el caso de que las cookies sean servidas desde un equipo o dominio gestionado por el propio editor, pero la información que se recoja mediante estas sea gestionada por un tercero, no pueden ser consideradas como cookies propias si el tercero las utiliza para sus propias finalidades (por ejemplo, la mejora de los servicios que presta o la prestación de servicios de carácter publicitario a favor de otras entidades).

SEGÚN SU FINALIDAD

Cookies técnicas:

Son aquellas necesarias para la navegación y el buen funcionamiento de nuestro Sitio Web, como por ejemplo, controlar el tráfico y la comunicación de datos, identificar la sesión, acceder a partes de acceso restringido, realizar la solicitud de inscripción o participación en un evento, contar visitas a efectos de la facturación de licencias del software con el que funciona el servicio del Sitio Web, utilizar elementos de seguridad durante la navegación, almacenar contenidos para la difusión de vídeos o sonido, habilitar contenidos dinámicos (por ejemplo, animación de carga de un texto o imagen).

Cookies de análisis:

Permiten cuantificar el número de usuarios y así realizar la medición y análisis estadístico de la utilización que hacen los usuarios del Sitio Web.

Cookies de preferencias o personalización:

Son aquellas que permiten recordar información para que el Usuario acceda al servicio con determinadas características que pueden diferenciar su experiencia de la de otros usuarios, como, por ejemplo, el idioma, el número de resultados a mostrar cuando el Usuario realiza una búsqueda, el aspecto o contenido del servicio en función del tipo de navegador a través del cual el Usuario accede al servicio o de la región desde la que accede al servicio, etc.

SEGÚN EL PLAZO DE TIEMPO QUE PERMANECEN ACTIVADAS

Cookies de sesión:

Son aquellas diseñadas para recabar y almacenar datos mientras el Usuario accede a una página web.

Se suelen emplear para almacenar información que solo interesa conservar para la prestación del servicio solicitado por el Usuario en una sola ocasión (por ejemplo, una lista de productos adquiridos) y desaparecen al terminar la sesión.

Cookies persistentes:

Son aquellas en las que los datos siguen almacenados en el terminal y pueden ser accedidos y tratados durante un periodo definido por el responsable de la cookie, y que puede ir de unos minutos a varios años. A este respecto debe valorarse específicamente si es necesaria la utilización de cookies persistentes, puesto que los riesgos para la privacidad podrían reducirse mediante la utilización de cookies de sesión. En todo caso, cuando se instalen cookies persistentes, se recomienda reducir al mínimo necesario su duración temporal atendiendo a la finalidad de su uso. A estos efectos, el Dictamen 4/2012 del GT29 indicó que para que una cookie pueda estar exenta del deber de consentimiento informado, su caducidad debe estar relacionada con su finalidad. Debido a ello, es mucho más probable que se consideren como exceptuadas las cookies de sesión que las persistentes.

Detalle de cookies utilizadas en esta web:

Carrito de compras