diff --git a/codex/MEMORIA_CODEX.md b/codex/MEMORIA_CODEX.md index d59e062..43e7a49 100644 --- a/codex/MEMORIA_CODEX.md +++ b/codex/MEMORIA_CODEX.md @@ -1,6 +1,11 @@ # Memoria Codex - GKACHELE -**Fecha de corte:** 24 Febrero 2026 +Entendido. + +Queda memorizado el foco: mejorar sin retrocesos, calidad profesional y avance real por lotes verificables. +Cuando vuelvas, arranco leyendo memoria y ejecutando directo. + +**Fecha de corte:** 04 Marzo 2026 ## Fuente de verdad - `codex/VERSIONADO_IA.md` @@ -9,9 +14,10 @@ ## Estado acordado (retomar desde aqui) - Rama activa: `ai/ub24-builder-v1` -- Checkpoint actual: `208dca9` -- Tag local de checkpoint: `builder-social-blockfx-20260224-v1` +- Checkpoint actual: `8f56e84` +- Tag local de checkpoint: `builder-customizer-pro-20260304-v1` (pendiente crear si se solicita) - Base funcional estable previa: `e83e915` (tag local: `builder-stable-e83e915`) +- Estado de sync remoto verificado (`2026-03-04`): `0 0` ## Estado funcional verificado - Builder operativo en `/elementor/`. @@ -64,7 +70,7 @@ - Proximo paso operativo en esa linea: definir compose canonico unico y retirar variantes no usadas. - Estandar visual SaaS validado (2026-02-28): referencia `Campos Misiones` como base premium replicable. -## Pendientes criticos memorizados (24 Febrero 2026) +## Pendientes criticos memorizados (04 Marzo 2026) 1. Paridad real entre editor y preview final: - lo que se ve mientras se edita debe verse igual en `/elementor//preview-final`. 2. Movimiento libre real de objetos: @@ -78,3 +84,21 @@ - swap de contenido debe reflejarse claramente en editor y preview final. 6. Modularizacion tecnica: - dividir `elementor_builder.html` en archivos mas pequenos (CSS/JS por responsabilidades) para evitar regressiones y acelerar fixes. + +## Control de version operativo (obligatorio desde 2026-03-04) +1. Antes de responder estado de version: ejecutar y registrar + - `git rev-parse --short HEAD` + - `git rev-list --left-right --count ai/ub24-builder-v1...origin/ai/ub24-builder-v1` +2. Si la memoria no coincide con `HEAD`, corregir memoria en la misma sesion. +3. No declarar "sincronizado" sin evidencia `0 0` en el momento. + +## Actualizacion 2026-03-05 (builder restaurante) +- Rama objetivo reafirmada: `ai/ub24-builder-v1`. +- Lote tecnico aplicado en `elementor/templates/elementor_builder.html`: + 1. `Reset` vuelve a plantilla base del rubro, no vacia bloques. + 2. Se mantiene `free drag` como modo prioritario en restaurante. + 3. Subir/bajar reordena visualmente en modo libre. + 4. Menu reconoce targets por semantica y tipo de bloque (contacto/mapa/redes/resenas/carta). + 5. Se agrega autosave de borrador y propagacion de `device` a preview-final. +- Nota de operacion: + - Evitar forzado automatico de posiciones cuando el usuario esta ubicando bloques manualmente. diff --git a/codex/SESSION_STATE.md b/codex/SESSION_STATE.md new file mode 100644 index 0000000..6960c0b --- /dev/null +++ b/codex/SESSION_STATE.md @@ -0,0 +1,47 @@ +# SESSION STATE - Live Memory + +Last update: 2026-03-05 (sync) +Owner: Codex + user + +## Current objective +Stabilize the UB24 builder workflow with reproducible progress and no context loss between sessions. + +## Current context snapshot +- Canonical workspace: `C:\word` +- Active branch target: `ai/ub24-builder-v1` +- Runtime command: `python -m demo.app` +- Canonical URL: `http://127.0.0.1:5001/elementor/1` + +## Done recently +1. Defined memory/versioning strategy based on stable rules + live state + history. +2. Added startup/close hooks to enforce session continuity. +3. Updated startup protocol to always read `agent.md` in addition to core memory files. +4. Disabled legacy routes `/customizer2/` and `/customizer3/` in `demo/routes/customizer_ascii.py` (now return `404`). +5. Stabilized `elementor_builder` for restaurante in free-drag mode while preserving manual placement. +6. Reset action now restores base template per rubro (instead of wiping all blocks). +7. Menu mapping improved to resolve links by semantic intent + block type (contact/map/social/review/cards). +8. Added draft autosave and preview-final device propagation (`desktop/tablet/phone`). + +## In progress +1. Final QA of restaurante flow end-to-end (order, menu links, responsive, publish persistence). + +## Blockers +1. None active. + +## Next 3 steps +1. QA complete on `site_id=1` for reset -> reorder -> preview-final -> publish. +2. Tune spacing/heights in free-drag for blocks with dynamic content (contact/social/map) without auto-restack. +3. Consolidate docs/cross references for single customizer flow and mark legacy as deprecated. + +## Quick handoff template (copy and fill at close) +### What changed today +- + +### What was validated +- + +### What failed (if any) +- + +### Next immediate action +- diff --git a/codex/VERSIONADO_IA.md b/codex/VERSIONADO_IA.md index 9237cf4..0f6c9c7 100644 --- a/codex/VERSIONADO_IA.md +++ b/codex/VERSIONADO_IA.md @@ -1,4 +1,4 @@ -# Versionado IA - UB24 / Elementor +# Versionado IA - UB24 / Elementor ## 0) Protocolo Bloqueante (siempre) 1. Definir objetivo del lote en 1 linea. @@ -14,10 +14,12 @@ Definir un proceso de versionado auditable, reproducible y estable para el desarrollo del builder UB24. ## 2) Estado actual verificado -- Fecha de verificacion: `2026-02-22` +- Fecha de verificacion: `2026-03-04` - Rama activa: `ai/ub24-builder-v1` - Upstream: `origin/ai/ub24-builder-v1` -- Divergencia local/remoto: `3 0` +- Divergencia local/remoto: `0 0` +- HEAD actual: `8f56e84` +- Ultimo commit: `8f56e84 feat(customizer): add pro visual presets, social styles, and responsive map controls` - Comando usado: - `git rev-list --left-right --count ai/ub24-builder-v1...origin/ai/ub24-builder-v1` @@ -107,15 +109,20 @@ Definir un proceso de versionado auditable, reproducible y estable para el desar - Preview final separada habilitada: - `GET /elementor//preview-final` - `GET /ub24//preview-final` +- Customizer activo unificado: + - `GET /customizer/` +- Rutas legacy retiradas: + - `GET /customizer2/` -> `404` + - `GET /customizer3/` -> `404` - Motor de reordenamiento seleccionado para DnD: - `SortableJS` (estrategia unica) ## 10) Pendientes priorizados 1. QA manual de cada icono/accion del menu superior en preview final (siguiente lote). -2. Footer global obligatorio con autoria del proyecto. -3. Watermark de autoria en codigo bajo convencion unica. -4. Flujo dual estable de preview (editor/pagina real). -5. Mejora de interaccion touch/capacitiva del builder. +2. Cerrar y validar flujo unico del customizer en docs cruzadas (sin rutas legacy). +3. Footer global obligatorio con autoria del proyecto. +4. Watermark de autoria en codigo bajo convencion unica. +5. Flujo dual estable de preview (editor/pagina real) + mejora touch/capacitiva. ## 11) Referencias cruzadas - Historial funcional detallado: `codex/HISTORIAL_CAMBIOS.md` @@ -125,3 +132,49 @@ Definir un proceso de versionado auditable, reproducible y estable para el desar ## 12) Nota operativa Este archivo define el estandar de trabajo. Cualquier cambio de proceso debe registrarse en un commit `chore(versioning)` y quedar reflejado aqui. +Memorizado. + +En la próxima sesión haré esto como primer paso: +1. Revisar en internet referencias de diseño (layouts, bloques, redes, animaciones, efectos pro). +2. Extraer patrones aplicables al builder. +3. Convertirlos en mejoras concretas y ordenadas en tu proyecto. + +Entendido. + +Queda memorizado el foco: mejorar sin retrocesos, calidad profesional y avance real por lotes verificables. +Cuando vuelvas, arranco leyendo memoria y ejecutando directo. + +## 13) Continuidad de sesion (agregado 2026-03-04) +- Se adopta esquema de memoria en 3 capas: + 1. `AGENTS.md` (reglas estables) + 2. `codex/SESSION_STATE.md` (estado vivo de trabajo) + 3. `codex/VERSIONADO_IA.md` (historial de decisiones/versionado) +- Hook de arranque oficial: + - `powershell -ExecutionPolicy Bypass -File .\codex\start-session.ps1` +- Hook de cierre oficial: + - `powershell -ExecutionPolicy Bypass -File .\codex\end-session.ps1` + +## 14) Ajuste de operacion (2026-03-04) +- A partir de esta fecha, la lectura de contexto de inicio es automatica por politica del asistente. +- No se requiere ejecutar scripts manuales para que el asistente cargue memoria. +- `start-session.ps1` y `end-session.ps1` quedan como herramientas opcionales de soporte. + +## 15) Regla de sincronizacion estricta (2026-03-04) +1. Toda respuesta sobre "version actual" debe salir de git en tiempo real, no de memoria previa. +2. Al detectar desfase entre memoria y `HEAD`, actualizar `codex/VERSIONADO_IA.md` y `codex/MEMORIA_CODEX.md` en la misma sesion. +3. Toda afirmacion de push/sync debe incluir evidencia de `rev-list`: + - `0 0` = sincronizado + - distinto de `0 0` = no sincronizado + +## 16) Lote aplicado (2026-03-05) +- Rama de trabajo confirmada: `ai/ub24-builder-v1` +- Base de partida: `8f56e84` +- Archivo principal intervenido: + - `elementor/templates/elementor_builder.html` +- Cambios del lote: + 1. `Reset` restaura plantilla base por rubro (ya no borra todo). + 2. Flujo restaurante en `free_drag` preserva posicion manual (sin auto-restack agresivo por render). + 3. Botones subir/bajar aplican reordenamiento visible. + 4. Mapeo de menu a bloques por semantica + tipo (`contact`, `map`, `social`, `review`, `cards/gallery/hero`). + 5. Autosave borrador en cambios de inspector/settings. + 6. Preview final recibe `device` (`desktop/tablet/phone`) desde el editor. diff --git a/elementor/templates/elementor_builder.html b/elementor/templates/elementor_builder.html index 52ff9dd..3d5095c 100644 --- a/elementor/templates/elementor_builder.html +++ b/elementor/templates/elementor_builder.html @@ -70,6 +70,7 @@ .block.style-dark-glow{--site-block-bg:linear-gradient(165deg,rgba(7,12,22,.92),rgba(3,8,16,.88));--site-block-border:#1f3657;--site-block-shadow:0 16px 36px rgba(2,8,20,.46);--site-block-shadow-hover:0 24px 56px rgba(8,24,56,.58);--site-block-ring-hover:0 0 0 1px rgba(37,99,235,.42);--site-block-sheen-opacity:.08;--site-block-sheen-opacity-hover:.16;--site-block-accent-opacity:.24;--site-block-accent-opacity-hover:.38;color:#e6edf8} .block.style-glass{--site-block-bg:linear-gradient(155deg,rgba(255,255,255,.52),rgba(255,255,255,.28));--site-block-border:rgba(255,255,255,.48);--site-block-shadow:0 16px 34px rgba(15,23,42,.13);--site-block-shadow-hover:0 24px 44px rgba(15,23,42,.2);--site-block-sheen-opacity:.46;--site-block-sheen-opacity-hover:.58;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)} .block.style-soft-gradient{--site-block-bg:linear-gradient(150deg,#f8fafc 0%,#eef2ff 55%,#e2e8f0 100%);--site-block-border:#d5deef;--site-block-shadow:0 12px 28px rgba(15,23,42,.1);--site-block-sheen-opacity:.36;--site-block-sheen-opacity-hover:.48;--site-block-accent-opacity:.12;--site-block-accent-opacity-hover:.22} + .block.style-liquid-glass{--site-block-bg:linear-gradient(145deg,rgba(255,255,255,.72) 0%,rgba(226,237,255,.48) 45%,rgba(203,226,255,.38) 100%);--site-block-border:rgba(255,255,255,.7);--site-block-shadow:0 20px 38px rgba(15,23,42,.14);--site-block-shadow-hover:0 30px 52px rgba(15,23,42,.2);--site-block-sheen-opacity:.56;--site-block-sheen-opacity-hover:.72;--site-block-accent-opacity:.24;--site-block-accent-opacity-hover:.34;backdrop-filter:blur(14px) saturate(130%);-webkit-backdrop-filter:blur(14px) saturate(130%)} .block.bg-motion-flow::before{background:linear-gradient(120deg,rgba(255,255,255,.3) 0%,rgba(255,255,255,.06) 34%,rgba(59,130,246,.2) 52%,rgba(255,255,255,.06) 68%,rgba(255,255,255,.25) 100%);background-size:220% 220%;animation:bgFlow var(--block-motion-duration,18s) linear infinite} .block.bg-motion-aurora::after{opacity:var(--site-block-accent-opacity-hover,.26);animation:bgAurora var(--block-motion-duration,22s) var(--ease-emphasis) infinite} .block.bg-motion-parallax::before{animation:bgParallax var(--block-motion-duration,20s) var(--ease-standard) infinite} @@ -281,7 +282,8 @@ .restaurant-site .block.style-clean-landing, .restaurant-site .block.style-dark-glow, .restaurant-site .block.style-glass, - .restaurant-site .block.style-soft-gradient{ + .restaurant-site .block.style-soft-gradient, + .restaurant-site .block.style-liquid-glass{ background:var(--site-block-bg,var(--restaurant-surface,#fff)); border-color:var(--site-block-border,var(--restaurant-border,#dbe3ee)); box-shadow:var(--site-block-shadow,0 8px 20px rgba(15,23,42,.06)); @@ -306,7 +308,7 @@ .preview-shell.size-phone .hero-media, .preview-shell.size-tablet .hero-media{min-height:220px} @keyframes slowGradient{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}} - .free-drag a{pointer-events:none} + body:not(.preview-mode) .free-drag a{pointer-events:none} .block-actions{position:absolute;top:8px;right:8px;display:flex;gap:6px} .block-actions button{border:0;background:#0f172a;color:#e2e8f0;border-radius:8px;padding:4px 6px;cursor:pointer;font-size:12px} .block-actions button:hover{background:#1f2937} @@ -516,7 +518,7 @@
Layout
-
+
@@ -682,6 +684,8 @@ const state = { }; let selectedBlockId = null; let currentPage = "home"; + let autosaveTimer = null; + let autosaveReady = false; let dropIndicator = null; let isDraggingFree = false; let dragStart = { x: 0, y: 0, left: 0, top: 0, id: null }; @@ -726,7 +730,20 @@ const state = { } function normalizeBlockPreset(value){ const raw = String(value || "").trim().toLowerCase(); - return ["clean-landing","dark-glow","glass","soft-gradient","inherit"].includes(raw) ? raw : "inherit"; + return ["clean-landing","dark-glow","glass","liquid-glass","soft-gradient","inherit"].includes(raw) ? raw : "inherit"; + } + function scheduleDraftAutosave(){ + if (PREVIEW_ONLY || !autosaveReady || isSaving) return; + if (autosaveTimer){ clearTimeout(autosaveTimer); } + setSaveStatus("Borrador pendiente...", "busy"); + autosaveTimer = setTimeout(async ()=>{ + try { + await saveDraftSilently(); + setSaveStatus("Borrador guardado", "ok"); + } catch (_e){ + setSaveStatus("Error guardando borrador", "error"); + } + }, 900); } function normalizeBlockMotion(value){ const raw = String(value || "").trim().toLowerCase(); @@ -1120,7 +1137,7 @@ const state = { .filter((b)=>b.type!=="menu") .map((b, i)=>{ const title = (b.data && (b.data.title || b.data.text || b.type)) || b.type; - return { id: b.id, label: String(title).slice(0, 24), index: i+1 }; + return { id: b.id, label: String(title).slice(0, 24), type: String(b.type || "").toLowerCase(), index: i+1 }; }); const norm = (v)=>String(v || "") .toLowerCase() @@ -1128,21 +1145,31 @@ const state = { .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z0-9]+/g, " ") .trim(); + const resolveMenuTarget = (label, index)=>{ + const key = norm(label); + const has = (...words)=>words.some((w)=>key.includes(w)); + const findType = (...types)=>contentBlocks.find((b)=>types.includes(b.type)); + // Prioridad semantica real por tipo de bloque + if (has("inicio","home","portada")) return findType("hero") || contentBlocks[0] || null; + if (has("carta","menu","platos","especialidades")) return findType("cards","gallery","hero") || contentBlocks[index] || null; + if (has("resena","resenas","review","testimonio","testimonios")) return findType("review") || contentBlocks[index] || null; + if (has("redes","social","sociales","instagram","facebook","tiktok","youtube")) return findType("social") || contentBlocks[index] || null; + if (has("mapa","ubicacion","llegar","direccion")) return findType("map") || contentBlocks[index] || null; + if (has("contacto","reserva","reservas","telefono","email","correo","whatsapp")) return findType("contact") || contentBlocks[index] || null; + const exact = contentBlocks.find((b)=>norm(b.label) === key); + if (exact) return exact; + return contentBlocks[index] || null; + }; const items = manualItems.length - ? (manualItems.length >= 3 - ? manualItems.map((label, i)=>{ - const key = norm(label); - const exact = contentBlocks.find((b)=>norm(b.label) === key); - const byIndex = contentBlocks[i]; - const target = exact || byIndex || null; + ? manualItems.map((label, i)=>{ + const target = resolveMenuTarget(label, i); return { id: target ? target.id : "", label: String(label).slice(0, 24), index: i+1 }; }) - : []) : contentBlocks; const safeItems = items.length ? items : [ - { id: "menu_item_1", label: "Inicio", index: 1 }, - { id: "menu_item_2", label: "Productos", index: 2 }, - { id: "menu_item_3", label: "Contacto", index: 3 } + { id: "", label: "Inicio", index: 1 }, + { id: "", label: "Productos", index: 2 }, + { id: "", label: "Contacto", index: 3 } ]; const links = safeItems.map(it=>`${escapeHtml(it.label)}`).join(""); const mobileLinks = safeItems.map(it=>`${escapeHtml(it.label)}`).join(""); @@ -1777,6 +1804,7 @@ const state = { spacer.style.width = "100%"; spacer.style.pointerEvents = "none"; inner.appendChild(spacer); + // No auto-restack here: free-drag must preserve user placement. } const footer = document.createElement("footer"); footer.className = "site-global-footer"; @@ -1956,6 +1984,7 @@ const state = { if (BUILDER_MODE === "ub24"){ b.page = currentPage; } if (state.settings.free_drag){ b.pos = getDefaultPos(); } state.blocks.splice(index,0,b); selectedBlockId=b.id; renderInspector(); renderPreview(); + scheduleDraftAutosave(); } function moveBlock(id,toIndex){ const from=state.blocks.findIndex(b=>b.id===id); @@ -1967,6 +1996,9 @@ const state = { const target = Math.max(0, Math.min(state.blocks.length, clamped)); state.blocks.splice(target,0,b); selectedBlockId = b.id; + if (state.settings.free_drag){ + applyStackedFreeDragLayout(state.blocks); + } renderPreview(); } function moveBlockByDelta(id, delta){ @@ -1981,6 +2013,7 @@ const state = { const current = Math.max(30, Math.min(100, Number(block.data.width || 100))); block.data.width = current >= 95 ? 50 : 100; renderPreview(); + scheduleDraftAutosave(); } function renderInspector(){ @@ -2101,7 +2134,7 @@ const state = { html+=`
`; html+=`
`; const visualPreset = escapeHtml(normalizeBlockPreset(data.visual_preset || "inherit")); - html+=`
`; + html+=`
`; const blockMotion = escapeHtml(normalizeBlockMotion(data.bg_motion_style || "inherit")); html+=`
`; const blockMotionSpeed = escapeHtml(normalizeMotionSpeed(data.bg_motion_speed || "inherit")); @@ -2307,6 +2340,7 @@ const state = { if (animDurationEl){ block.data.anim_duration = Math.max(120, Math.min(900, Number(animDurationEl.value || 250))); } } renderPreview(); + scheduleDraftAutosave(); } function wireSidebar(){ @@ -2337,6 +2371,7 @@ const state = { selectedBlockId = b.id; renderInspector(); renderPreview(); + scheduleDraftAutosave(); } function reorderVisibleBlocks(orderedIds){ if (!Array.isArray(orderedIds) || !orderedIds.length) return; @@ -2747,27 +2782,28 @@ const state = { registeredBrand.value = s.registered_brand || s.site_name || ""; developerBrand.value = s.developer_brand || "GKACHELE™"; siteAuthor.value = s.site_author || ""; - siteName.addEventListener("input",()=>{ s.site_name=siteName.value; renderPreview(); }); - primary.addEventListener("input",()=>{ s.primary_color=primary.value; renderPreview(); }); - bgColor.addEventListener("input",()=>{ s.bg_color=bgColor.value; renderPreview(); }); - bgColor2.addEventListener("input",()=>{ s.bg_color2=bgColor2.value; renderPreview(); }); - bgGradient.addEventListener("change",()=>{ s.bg_gradient=bgGradient.checked; renderPreview(); }); - bgMotion.addEventListener("change",()=>{ s.bg_motion=bgMotion.value; renderPreview(); }); - textColor.addEventListener("input",()=>{ s.text_color=textColor.value; renderPreview(); }); - mutedColor.addEventListener("input",()=>{ s.muted_color=mutedColor.value; renderPreview(); }); - fontBody.addEventListener("change",()=>{ s.font_body=fontBody.value; renderPreview(); }); - fontHeading.addEventListener("change",()=>{ s.font_heading=fontHeading.value; renderPreview(); }); - bgVideo.addEventListener("input",()=>{ s.bg_video_url=bgVideo.value; renderPreview(); }); - bgAnim.addEventListener("input",()=>{ s.bg_anim_url=bgAnim.value; renderPreview(); }); - animToggle.addEventListener("change",()=>{ s.animations=animToggle.checked; renderPreview(); }); - if (globalBlockPreset){ globalBlockPreset.addEventListener("change",()=>{ s.global_block_preset = normalizeBlockPreset(globalBlockPreset.value || "clean-landing"); renderPreview(); }); } - if (globalBlockMotion){ globalBlockMotion.addEventListener("change",()=>{ s.global_block_motion = normalizeBlockMotion(globalBlockMotion.value || "none"); renderPreview(); }); } - if (globalBlockMotionSpeed){ globalBlockMotionSpeed.addEventListener("change",()=>{ s.global_block_motion_speed = normalizeMotionSpeed(globalBlockMotionSpeed.value || "normal"); renderPreview(); }); } - canvasMinHeight.addEventListener("input",()=>{ s.canvas_min_height = Math.max(700, Number(canvasMinHeight.value || 1200)); renderPreview(); }); - canvasBottomSpace.addEventListener("input",()=>{ s.canvas_bottom_space = Math.max(0, Number(canvasBottomSpace.value || 0)); renderPreview(); }); - registeredBrand.addEventListener("input",()=>{ s.registered_brand = registeredBrand.value; renderPreview(); }); - developerBrand.addEventListener("input",()=>{ s.developer_brand = developerBrand.value; renderPreview(); }); - siteAuthor.addEventListener("input",()=>{ s.site_author = siteAuthor.value; renderPreview(); }); + const renderAndSave = ()=>{ renderPreview(); scheduleDraftAutosave(); }; + siteName.addEventListener("input",()=>{ s.site_name=siteName.value; renderAndSave(); }); + primary.addEventListener("input",()=>{ s.primary_color=primary.value; renderAndSave(); }); + bgColor.addEventListener("input",()=>{ s.bg_color=bgColor.value; renderAndSave(); }); + bgColor2.addEventListener("input",()=>{ s.bg_color2=bgColor2.value; renderAndSave(); }); + bgGradient.addEventListener("change",()=>{ s.bg_gradient=bgGradient.checked; renderAndSave(); }); + bgMotion.addEventListener("change",()=>{ s.bg_motion=bgMotion.value; renderAndSave(); }); + textColor.addEventListener("input",()=>{ s.text_color=textColor.value; renderAndSave(); }); + mutedColor.addEventListener("input",()=>{ s.muted_color=mutedColor.value; renderAndSave(); }); + fontBody.addEventListener("change",()=>{ s.font_body=fontBody.value; renderAndSave(); }); + fontHeading.addEventListener("change",()=>{ s.font_heading=fontHeading.value; renderAndSave(); }); + bgVideo.addEventListener("input",()=>{ s.bg_video_url=bgVideo.value; renderAndSave(); }); + bgAnim.addEventListener("input",()=>{ s.bg_anim_url=bgAnim.value; renderAndSave(); }); + animToggle.addEventListener("change",()=>{ s.animations=animToggle.checked; renderAndSave(); }); + if (globalBlockPreset){ globalBlockPreset.addEventListener("change",()=>{ s.global_block_preset = normalizeBlockPreset(globalBlockPreset.value || "clean-landing"); renderAndSave(); }); } + if (globalBlockMotion){ globalBlockMotion.addEventListener("change",()=>{ s.global_block_motion = normalizeBlockMotion(globalBlockMotion.value || "none"); renderAndSave(); }); } + if (globalBlockMotionSpeed){ globalBlockMotionSpeed.addEventListener("change",()=>{ s.global_block_motion_speed = normalizeMotionSpeed(globalBlockMotionSpeed.value || "normal"); renderAndSave(); }); } + canvasMinHeight.addEventListener("input",()=>{ s.canvas_min_height = Math.max(700, Number(canvasMinHeight.value || 1200)); renderAndSave(); }); + canvasBottomSpace.addEventListener("input",()=>{ s.canvas_bottom_space = Math.max(0, Number(canvasBottomSpace.value || 0)); renderAndSave(); }); + registeredBrand.addEventListener("input",()=>{ s.registered_brand = registeredBrand.value; renderAndSave(); }); + developerBrand.addEventListener("input",()=>{ s.developer_brand = developerBrand.value; renderAndSave(); }); + siteAuthor.addEventListener("input",()=>{ s.site_author = siteAuthor.value; renderAndSave(); }); bindDrop(logoDrop, logoFile, (data)=>{ s.logo_url = data; }, "Logo cargado"); bindDrop(bgDrop, bgFile, (data)=>{ s.bg_image_url = data; }, "Fondo cargado"); @@ -2794,7 +2830,14 @@ const state = { document.getElementById("btnSizePhone").addEventListener("click",()=>setSize("520px","btnSizePhone")); document.getElementById("btnSizeTablet").addEventListener("click",()=>setSize("820px","btnSizeTablet")); document.getElementById("btnSizeDesktop").addEventListener("click",()=>setSize("100%","btnSizeDesktop")); - setShellClass("size-desktop"); + const requestedDevice = new URLSearchParams(window.location.search).get("device"); + if (requestedDevice === "phone"){ + setSize("520px","btnSizePhone"); + } else if (requestedDevice === "tablet"){ + setSize("820px","btnSizeTablet"); + } else { + setSize("100%","btnSizeDesktop"); + } } function wirePreviewToggle(){ const btnPreview = document.getElementById("btnPreview"); @@ -2806,6 +2849,10 @@ const state = { try{ await saveDraftSilently(); const url = new URL(`${BUILDER_BASE_PATH}/preview-final`, window.location.origin); + const shell = document.querySelector(".preview-shell"); + if (shell && shell.classList.contains("size-phone")) url.searchParams.set("device", "phone"); + else if (shell && shell.classList.contains("size-tablet")) url.searchParams.set("device", "tablet"); + else url.searchParams.set("device", "desktop"); window.open(url.toString(), "_blank", "noopener,noreferrer"); } catch(_e){ window.alert("No se pudo abrir la vista previa. Reintenta."); @@ -2982,10 +3029,10 @@ const state = { if (type === "hero") return 520; if (type === "gallery") return 330; if (type === "map") return 360; - if (type === "contact") return 340; - if (type === "cards") return 280; + if (type === "contact") return 520; + if (type === "cards") return 320; if (type === "review") return 250; - if (type === "social") return 280; + if (type === "social") return 220; return 240; } function hasSevereFreeDragOverlap(blocks){ @@ -3030,6 +3077,28 @@ const state = { y += estimateFreeDragBlockHeight(b) + 22; }); } + function restackFreeDragWithMeasuredHeights(){ + const canvas = document.getElementById("previewCanvas"); + if (!canvas || !state.settings.free_drag) return; + const nodes = [...canvas.querySelectorAll(".block")]; + if (!nodes.length) return; + const byId = new Map(nodes.map((n)=>[n.dataset.blockId, n])); + let y = 20; + state.blocks.forEach((b)=>{ + if (!b) return; + b.data = (b.data && typeof b.data === "object") ? b.data : {}; + if (b.type === "menu"){ + b.data.width = 100; + } else { + b.data.width = snapBlockWidth(b.type, Number(b.data.width || 92)); + } + b.pos = { x: 20, y }; + const node = byId.get(b.id); + const measured = node ? Math.max(120, node.offsetHeight || 0) : 0; + const fallback = estimateFreeDragBlockHeight(b); + y += Math.max(measured, fallback) + 22; + }); + } function applyRestaurantTemplateOrder(blocks){ const list = Array.isArray(blocks) ? blocks : []; const rank = { @@ -3066,6 +3135,37 @@ const state = { }); return indexed.map((x)=>x.b); } + function hasRestaurantCoreBlocks(blocks){ + const list = Array.isArray(blocks) ? blocks : []; + const types = new Set(list.map((b)=>b && b.type).filter(Boolean)); + return types.has("menu") && types.has("hero") && types.has("contact"); + } + function applyTemplateState(key){ + if (!key || !templates[key]) return false; + const t = templates[key]; + state.settings = { ...state.settings, ...t.settings }; + state.settings.business_rubro = normalizeRubro(key); + if (BUILDER_MODE === "ub24"){ + state.settings.free_drag = false; + } else { + // Mantener modo libre en restaurante segun requerimiento. + state.settings.free_drag = state.settings.business_rubro === "restaurante"; + } + state.blocks = t.blocks.map((b)=>({ + ...b, + id: makeId(), + page: (BUILDER_MODE === "ub24" ? "home" : b.page) + })); + if (state.settings.free_drag){ + state.blocks = applyRestaurantTemplateOrder(state.blocks); + state.settings.restaurant_layout_repaired_v1 = true; + } + selectedBlockId = null; + renderInspector(); + renderPreview(); + wireSettings(); + return true; + } async function saveContent(){ if (isSaving) return; isSaving = true; @@ -3102,24 +3202,18 @@ const state = { } async function resetBlocks(){ if (isSaving) return; - const ok = window.confirm("Esto borrara todos los bloques actuales. Deseas continuar?"); + const ok = window.confirm("Se restaurara la plantilla base del rubro actual. Deseas continuar?"); if (!ok) return; const keepRubro = normalizeRubro(state.settings.business_rubro || SERVER_RUBRO || "restaurante"); - state.blocks = []; - state.settings = { ...defaultSettings, business_rubro: keepRubro }; - if (BUILDER_MODE === "ub24"){ - state.settings.free_drag = false; - } else { - state.settings.free_drag = keepRubro === "restaurante"; + const restored = applyTemplateState(keepRubro); + if (!restored){ + setSaveStatus("No hay plantilla base para este rubro", "error"); + return; } - selectedBlockId = null; - renderInspector(); - renderPreview(); - wireSettings(); setSaveStatus("Reseteando...", "busy"); try{ await saveDraftSilently(); - setSaveStatus("Reset aplicado", "ok"); + setSaveStatus("Plantilla base restaurada", "ok"); } catch(_e){ setSaveStatus("Error al resetear", "error"); } @@ -3147,7 +3241,7 @@ const state = { if (needsTemplateRepair){ state.blocks = applyRestaurantTemplateOrder(state.blocks); state.settings.restaurant_layout_repaired_v1 = true; - } else if (!hasMeaningfulFreeDragPositions(state.blocks) || hasSevereFreeDragOverlap(state.blocks)){ + } else if (!hasMeaningfulFreeDragPositions(state.blocks)){ applyStackedFreeDragLayout(state.blocks); normalizeDuplicatedFreeDragPositions(state.blocks); } @@ -3170,23 +3264,7 @@ const state = { } const templateSelect = document.getElementById("templateSelect"); const applyTemplate = (key)=>{ - if (!key || !templates[key]) return; - const t = templates[key]; - state.settings = { ...state.settings, ...t.settings }; - state.settings.business_rubro = normalizeRubro(key); - if (BUILDER_MODE === "ub24"){ - state.settings.free_drag = false; - } else { - state.settings.free_drag = state.settings.business_rubro === "restaurante"; - } - state.blocks = t.blocks.map(b=>({ ...b, id: makeId(), page: (BUILDER_MODE==="ub24" ? "home" : b.page) })); - if (state.settings.free_drag){ - state.blocks = applyRestaurantTemplateOrder(state.blocks); - state.settings.restaurant_layout_repaired_v1 = true; - } - selectedBlockId = null; - renderInspector(); renderPreview(); - wireSettings(); + applyTemplateState(key); }; if (templateSelect){ templateSelect.value = state.settings.business_rubro || ""; @@ -3196,18 +3274,34 @@ const state = { } const hasSavedBlocks = Array.isArray(SERVER_CONTENT && SERVER_CONTENT.blocks) && SERVER_CONTENT.blocks.length > 0; const hasSavedSettings = !!(SERVER_CONTENT && SERVER_CONTENT.settings && Object.keys(SERVER_CONTENT.settings).length); - const shouldAutoloadTemplate = !hasSavedBlocks && !hasSavedSettings; + const looksIncompleteRestaurant = ( + initialRubro === "restaurante" && + ( + state.blocks.length < 5 || + !hasRestaurantCoreBlocks(state.blocks) || + !hasMeaningfulFreeDragPositions(state.blocks) + ) + ); + const shouldAutoloadTemplate = !hasSavedBlocks || looksIncompleteRestaurant; if (!state.blocks.length && templates[initialRubro] && shouldAutoloadTemplate){ applyTemplate(initialRubro); if (templateSelect){ templateSelect.value = initialRubro; } + } else if (looksIncompleteRestaurant){ + applyTemplate(initialRubro); + if (templateSelect){ templateSelect.value = initialRubro; } } + autosaveReady = true; document.getElementById("previewCanvas").addEventListener("click",()=>{ selectedBlockId=null; renderInspector(); renderPreview(); }); document.getElementById("btnSave").addEventListener("click",saveContent); const resetBtn = document.getElementById("btnReset"); if (resetBtn){ resetBtn.addEventListener("click", resetBlocks); } renderInspector(); renderPreview(); + scheduleDraftAutosave(); } init(); + + +