diff --git a/codex/VERSIONADO_IA.md b/codex/VERSIONADO_IA.md index e46d59d..36c27de 100644 --- a/codex/VERSIONADO_IA.md +++ b/codex/VERSIONADO_IA.md @@ -1,4 +1,4 @@ -# Versionado IA - UB24/Elementor +# Versionado IA - UB24/Elementor ## Rama de trabajo - `ai/ub24-builder-v1` @@ -36,6 +36,16 @@ - `git push origin ai/ub24-builder-v1` 6. Registrar hash y objetivo en este archivo y en `codex/HISTORIAL_CAMBIOS.md`. +## Protocolo de inicio de sesion (obligatorio) +1. Levantar entorno al iniciar cada sesion, sin esperar pedido del usuario. +2. Verificar que servicios locales de trabajo esten activos (app/builder segun tarea). +3. Si la tarea requiere validacion remota, conectarse a Raspberry y comprobar estado antes de editar. + +## Regla operativa de contexto/token +1. Trabajar por iteraciones cortas y versionar rapido para no perder avance cuando el presupuesto de tokens sea bajo. +2. Priorizar cambios de alto impacto visual/funcional antes de detalles menores. +3. Si el usuario avisa limite de tokens, cerrar cada bloque con commit y registro en este archivo. + ## Registro de hashes ### Baseline - Commit: `cb99f26` @@ -157,3 +167,201 @@ Notas: 1. Fase 1 (UI Pro base): navbar premium, hero premium, sistema de espaciado/grid, pulido visual consistente. 2. Fase 2 (estructura): separar renderers por bloque y reducir inline styles para automatizacion. 3. Fase 3 (presets): presets por rubro + reglas responsive + variantes exportables. + +## Verificacion tecnica (15 Febrero 2026) +- Rama activa verificada: `ai/ub24-builder-v1` +- Divergencia local/remoto: `0 0` (sin diferencias con `origin/ai/ub24-builder-v1`) +- DB local: tabla `sites` operativa con registros de prueba (`id=1`, `id=2`) +- Smoke test builder: + - `GET /elementor/1` -> `200 OK` + - `POST /api/elementor/save` con `site_id=1` -> `200 OK`, `{"success": true, "published": false}` + +## Requisitos minimos para operar Elementor (base SaaS) +1. Arrancar con `python -m demo.app` desde `c:\word`. +2. Mantener URL canonica local en `http://127.0.0.1:5001` (no `localhost`). +3. Confirmar que exista un `site_id` valido en DB (ejemplo activo: `1`). +4. Mantener una sola ruta de guardado activa: `/api/elementor/save`. +5. Validar guardado/publicacion con prueba rapida antes de cada lote. + +## Regla memorizada de preview (15 Febrero 2026) +- La preview del builder debe usar un solo flujo en `Bloques` con acomodo estable. +- No mantener ni mezclar modos paralelos de preview. + +## Ajuste operativo UX (15 Febrero 2026) +- Se elimina `Completo` del builder. +- Se elimina `Modo libre` como toggle separado. +- El layout operativo queda unico en bloques: + - cada bloque puede ir en `50%` (dos columnas) o `100%` (ancho completo), + - controlado por `Ancho completo` en inspector o por drop lateral/centro. + +## Regla de estabilidad (15 Febrero 2026) +- Lo que ya funciona no se toca sin requerimiento explicito. +- Los bloques del panel izquierdo no deben ocultarse al usarse. +- Cada tipo de bloque debe poder agregarse cantidad ilimitada de veces. +- Debe existir boton de `Vista previa` funcional en el builder. +- El ancho completo/parcial se controla solo con `Ancho bloque (%)` del mismo bloque (sin checkbox separado). +- La vista previa se abre en modo push/pop (`preview=1`) y muestra boton `Atras`. + +## Estado real de cierre (15 Febrero 2026) +- Tema bloqueante: drag & drop de bloques sigue inestable en el flujo actual. +- Sintoma reportado por usuario: mover con mouse no queda consistente para alinear y ubicar bloques como espera. +- Decision acordada: pausar cambios hoy para no seguir consumiendo tiempo/tokens. +- Proximo reinicio: rehacer DnD con enfoque estable desde base limpia (una sola estrategia de movimiento), validar con pruebas manuales guiadas y recien despues tocar extras. +- Regla reforzada: no tocar lo que ya funciona mientras se corrige solo el DnD. + +## Regla operativa de sesion (15 Febrero 2026) +- Al iniciar cada sesion de trabajo, levantar primero el entorno local del builder: + - `python -m demo.app` +- Verificar puerto antes de editar: + - `http://127.0.0.1:5001` + +## Acuerdo definitivo DnD (15 Febrero 2026) +- Decision: implementar una solucion definitiva de drag & drop con una sola estrategia, sin mezclar motores. +- Motor elegido: `SortableJS` para reordenamiento estable (desktop + touch). +- Alcance de la correccion: + - reemplazar reordenamiento actual por `SortableJS` en el canvas de bloques, + - usar `handle` de arrastre para no interferir con edicion inline, + - configurar fallback estable (`forceFallback` + umbrales de toque/movimiento), + - eliminar handlers duplicados de drag que hoy se pisan entre si. +- Regla de seguridad: no tocar guardado, preview ni bloques que ya funcionan. +- Proxima sesion: implementar, hacer smoke test completo en `/elementor/1`, y recien despues validar cierre para lanzamiento SaaS. + +## Cierre de sesion (16 Febrero 2026) +- Rama de trabajo usada: `ai/ub24-builder-v1` +- Entorno validado en sesion: `GET /elementor/1 -> 200 OK` +- Commit de cierre funcional del dia: + - Hash: `6f14308` + - Mensaje: `fix(builder): unificar layout y estabilizar redimension en canvas` + - Alcance: `elementor/templates/elementor_builder.html` + +### Resultado operativo del dia +- Se recupero el flujo de insercion de bloques desde panel izquierdo al canvas. +- Se estabilizo el modelo de layout del canvas para evitar mezcla de estrategias. +- Se dejo redimension por ancho de bloque en flujo normal y control desde inspector. +- Se corrigio comportamiento por defecto para que bloques nuevos entren apilados (ancho completo) y no en paralelo automatico. +- Se mantuvo `SortableJS` para reordenamiento de bloques en canvas. + +### Decisiones memorizadas para siguiente sesion +- Mantener una sola estrategia de layout y reordenamiento (sin volver a mezclar motores/flows). +- No reabrir cambios globales cuando un fix local sea suficiente. +- Si algo ya funciona, no rehacerlo completo: tocar solo el bloque minimo necesario. + +### Pendientes acordados (proxima sesion) +1. Footer obligatorio global en todas las paginas: + - marca registrada del usuario + empresa desarrolladora. +2. Watermark de autoria en codigo (convencion consistente del proyecto). +3. Preview dual: + - modo bloques (editor), + - modo pagina completa real (resultado final). +4. Mejorar landing base para nivel visual profesional. +5. Redes sociales con posicion libre (ubicacion configurable). +6. Menu superior: corregir modo acordeon (hoy no queda operativo como se espera). +7. Mejorar interaccion touch/capacitiva del builder. + +## Registro operativo (17 Febrero 2026) +- TAG: `RUBROS_OFICIALES_BLOQUEADOS` +- Regla fija: no usar rubros inventados; solo los oficiales definidos por usuario. +- Rubros oficiales memorizados: + - `restaurante` + - `danza` + - `cosmeticos` + - `despachos` + - `gimnasios` + - `educacion` + - `base_otro` (`Base (Otro)`) + +- TAG: `PREVIEW_FINAL_SEPARADA` +- Se agrega flujo dedicado de preview final real separado del hash del editor: + - `GET /elementor//preview-final` + - `GET /ub24//preview-final` +- En preview final: + - se fuerza `preview-mode` desde servidor (`preview_only=true`), + - boton `Atras` vuelve al builder base. + +- TAG: `UNIFICACION_TOPBAR_Y_DRAWER_FIX` +- Ajustes aplicados: + - se elimina duplicidad operativa de preview en topbar y se deja `Pagina real` como flujo principal, + - selector de plantillas migra a `Plantillas por rubro` (catalogo oficial), + - `Drawer Pro` se mueve a capa global para evitar render roto dentro del bloque/canvas, + - etiqueta de pagina `Servicios` se neutraliza a `Seccion 2` para no mezclar paginas con rubros. + +## Registro infraestructura Raspberry (17 Febrero 2026) +- TAG: `KOMKIDA_WEB_ACCESS_RECOVERY` +- Objetivo: recuperar `komkida.duckdns.org` como acceso web remoto a Raspberry con login (estilo Motion), sin romper: + - `gk-saas.komkida.duckdns.org` + - `git.gk-saas.komkida.duckdns.org` + - `motion.komkida.duckdns.org` + +### Diagnostico real +1. El `502 Bad Gateway` en subdominios (`gk-saas`, `git`, `motion`) venia por `proxy_pass http://localhost:PUERTO`: + - Nginx resolvia `localhost` a IPv6 (`[::1]`) en varios casos. + - servicios escuchando en IPv4 (`127.0.0.1` / `0.0.0.0`) devolvian `connection refused`. +2. `komkida.duckdns.org` quedo mezclado con proxy a metricas (`192.168.1.133:5000`) y luego con rutas no validas (`Not Found`). +3. Hubo drift de configuracion: + - archivo suelto viejo en `/etc/nginx/sites-enabled/komkida.duckdns.org` (no symlink) con reglas desactualizadas. +4. El navegador intentaba `https://komkida.duckdns.org` y no habia listener en `443`: + - error visible: `ERR_CONNECTION_REFUSED`. + +### Cambios aplicados en Raspberry +1. Normalizacion de upstreams Nginx a IPv4: + - `proxy_pass http://127.0.0.1:;` en vhosts activos. +2. Limpieza de duplicados/conflictos: + - quitar backups cargados en `sites-enabled`. + - asegurar `sites-enabled/komkida.duckdns.org` como symlink al archivo de `sites-available`. +3. Definicion final de `komkida.duckdns.org`: + - auth_basic con `.htpasswd`. + - `/` proxyeado a `https://127.0.0.1:4200` (ShellInABox). + - `/api/` mantiene proxy a `127.0.0.1:9999` (no bloquea resto del sistema). +4. Habilitacion HTTPS para `komkida`: + - servidor Nginx en `443 ssl`. + - certificado local en: + - `/etc/nginx/ssl/komkida.crt` + - `/etc/nginx/ssl/komkida.key` +5. Verificacion final: + - Nginx escuchando en `80` y `443`. + - `http://komkida.duckdns.org` -> `401` (esperado: login). + - `https://komkida.duckdns.org` -> `401` (esperado: login). + - `gk-saas` y `git` siguen en `200`. + +### Bots (estado para no cargar la Pi) +- `clawdbot`: deshabilitado por OOM recurrente (`JavaScript heap out of memory`). +- `moltobot`: deshabilitado temporalmente. +- Decision: priorizar estabilidad de infraestructura y SaaS. + +### Archivos clave tocados en la Pi +- `/etc/nginx/sites-available/komkida.duckdns.org` +- `/etc/nginx/sites-enabled/komkida.duckdns.org` +- `/etc/nginx/ssl/komkida.crt` +- `/etc/nginx/ssl/komkida.key` + +### Checklist rapido de recuperacion futura +1. `sudo nginx -t` +2. `sudo systemctl restart nginx` +3. `ss -tulpen | egrep ':80 |:443 '` +4. `curl -I http://komkida.duckdns.org/` (debe pedir auth) +5. `curl -k -I https://komkida.duckdns.org/` (debe pedir auth) +6. `curl -I http://gk-saas.komkida.duckdns.org/` (debe responder `200`) + +A ESTO SE TIENEN QUE PARECER: +Correcto. Busqué referencias reales y modernas; vamos a dejar de “inventar” estilos internos. + +Referencias que sí sirven para copiar estructura profesional: +1. Webflow Education Templates (catálogo): https://webflow.com/templates/category/education-websites +2. Hopedu (estructura académica limpia + CMS): https://webflow.com/templates/html/hopedu-school-website-template +3. Eduvolv (hero + admissions + secciones institucionales): https://webflow.com/templates/html/eduvolv-website-template +4. Thema (variantes de layout + menú fullscreen): https://webflow.com/made-in-webflow/website/thema-education-template-showcase +5. Campus (layout simple usable): https://webflow.com/templates/html/campus-education-website-template +6. ThemeForest Elementor kit (referencia de páginas mínimas): https://themeforest.net/item/educita-education-elementor-template-kit/37245135 + +Plan profesional (sin clones): +1. Rehacer templates por rubro con 3 arquitecturas distintas, no solo cambiar color. +2. Cada rubro tendrá: +- header propio +- hero propio +- grid propio +- CTA propio +- footer propio +3. El builder cargará esas arquitecturas base y luego se edita contenido. +4. Eliminaré el estilo único actual que hace que todo se vea igual. + +Si quieres, empiezo ya con `Educación` copiando patrón `Eduvolv/Hopedu` y luego replico la metodología al resto de rubros. diff --git a/elementor/templates/elementor_builder.html b/elementor/templates/elementor_builder.html index 4c1df92..43e9efa 100644 --- a/elementor/templates/elementor_builder.html +++ b/elementor/templates/elementor_builder.html @@ -50,14 +50,15 @@ .apple-dot{width:9px;height:9px;border-radius:50%} .red{background:#f87171}.yellow{background:#fbbf24}.green{background:#4ade80} .canvas{min-height:700px;padding:18px;background:#f6f7fb;transition:background .6s ease,color .4s ease; color:var(--site-text,#0b0c10)} - .block{background:var(--site-card,#fff);border-radius:var(--radius-md);padding:var(--space-3);margin-bottom:var(--space-4);border:1px solid transparent;box-shadow:0 12px 30px rgba(15,23,42,.08);transition:transform .25s ease,box-shadow .25s ease,border-color .25s ease;animation:fadeUp .25s ease;color:var(--site-text,#0b0c10);position:relative;will-change:transform;cursor:grab} + .canvas-inner{width:min(100%, var(--canvas-max, 1280px));margin:0 auto} + .block{background:var(--site-block-bg,var(--site-card,#fff));border-radius:var(--site-block-radius,var(--radius-md));padding:var(--space-3);margin-bottom:var(--space-4);border:1px solid var(--site-block-border,transparent);box-shadow:var(--site-block-shadow,0 12px 30px rgba(15,23,42,.08));transition:transform .25s ease,box-shadow .25s ease,border-color .25s ease;animation:fadeUp .25s ease;color:var(--site-text,#0b0c10);position:relative;cursor:grab;touch-action:pan-y} .block.dragging,.block.resizing{transition:none;cursor:grabbing} body.dragging{user-select:none} .block:hover{transform:translateY(-2px);box-shadow:0 20px 46px rgba(15,23,42,.14)} .block.selected{border-color:#7aa7ff} .empty{padding:32px;border:1px dashed #cbd5e1;border-radius:12px;text-align:center;color:#64748b;background:#fff} .drop{height:8px;border-radius:6px;background:rgba(122,167,255,.4);margin:6px 0} - .drag-handle{cursor:grab} + .drag-handle{cursor:grab;touch-action:none} .block-drag-handle{ position:absolute;left:8px;top:8px;z-index:3; width:24px;height:24px;border-radius:8px;border:1px solid #e5e7eb; @@ -134,31 +135,96 @@ .contact-send{display:inline-flex;align-items:center;justify-content:center;gap:8px;border:0;border-radius:10px;padding:10px 14px;background:var(--site-primary);color:#0b0f16;font-weight:700;cursor:pointer} .contact-send i{font-size:12px} @media (max-width:860px){.contact-pro{grid-template-columns:1fr}} - .site-nav{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:10px 12px;border:1px solid #dde4ef;border-radius:var(--radius-md);background:rgba(255,255,255,.72);backdrop-filter:blur(8px)} - .site-brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.2px;min-width:0} + .site-nav{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:10px 12px;border:1px solid var(--site-nav-border,#dde4ef);border-radius:var(--site-nav-radius,var(--radius-md));background:var(--site-nav-bg,rgba(255,255,255,.72));backdrop-filter:blur(8px)} + .site-brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.2px;min-width:120px;max-width:320px;flex:0 1 auto} .site-brand img{height:28px;width:auto;border-radius:8px;border:1px solid #dbe3ee} + .site-brand-badge{height:28px;min-width:28px;padding:0 8px;border-radius:8px;background:var(--site-primary);display:inline-flex;align-items:center;justify-content:center;color:#0b0f16;font-weight:800;font-size:12px} .site-brand span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis} - .site-nav-links{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center} + .site-nav-links{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;flex:1 1 auto} .site-nav-link{display:inline-flex;align-items:center;height:32px;padding:0 12px;border-radius:999px;border:1px solid transparent;text-decoration:none;font-size:13px;color:var(--site-text);transition:background .2s ease,border-color .2s ease,transform .2s ease} .site-nav-link:hover{background:#f1f5f9;border-color:#dbe3ee;transform:translateY(-1px)} .site-nav-cta{display:inline-flex;align-items:center;justify-content:center;padding:9px 14px;border-radius:999px;background:var(--site-primary);color:#0b0f16;text-decoration:none;font-weight:700;white-space:nowrap} .menu-empty{font-size:12px;color:var(--site-muted)} .menu-inline{display:flex;align-items:center;gap:10px;flex-wrap:wrap} - .menu-accordion{display:none;border:1px solid #e5e7eb;border-radius:var(--radius-md);padding:8px 10px;background:var(--site-card);width:100%} - .menu-accordion summary{list-style:none;cursor:pointer;font-weight:600} + .menu-drawer-toggle{display:none;align-items:center;justify-content:center;width:42px;height:42px;border-radius:10px;border:1px solid #2b2b2b;background:#202020;color:#f5f5f5;cursor:pointer} + .menu-drawer-toggle:hover{border-color:#4b5563} + .menu-drawer-overlay{display:none;position:fixed;inset:0;background:rgba(8,11,16,.5);z-index:1200} + .menu-drawer{display:none;position:fixed;top:0;right:0;height:100vh;width:min(92vw,360px);background:#1f1f1f;color:#f5f5f5;border-left:1px solid #2d2d2d;z-index:1201;box-shadow:-14px 0 30px rgba(0,0,0,.25)} + .menu-drawer.open,.menu-drawer-overlay.open{display:block} + .menu-drawer-head{height:74px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #2d2d2d} + .menu-drawer-brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:24px} + .menu-drawer-close{width:42px;height:42px;border-radius:10px;border:2px solid #fbbf24;background:transparent;color:#f8fafc;font-size:22px;line-height:1;cursor:pointer} + .menu-drawer-links{display:grid;gap:16px;padding:34px 28px} + .menu-drawer-links .site-nav-link{height:auto;padding:0;border:0;border-radius:0;color:#f8fafc;font-size:clamp(22px,4vw,34px);line-height:1.15} + .menu-drawer-links .site-nav-link:hover{background:transparent;border:0;transform:none;color:#cbd5e1} + .menu-accordion{display:none;border:1px solid #e5e7eb;border-radius:var(--radius-md);padding:8px 10px;background:var(--site-card);width:auto;min-width:220px;max-width:420px;margin-left:auto;flex:0 1 auto} + .menu-accordion summary{list-style:none;cursor:pointer;font-weight:600;touch-action:manipulation} .menu-accordion summary::-webkit-details-marker{display:none} .menu-links{display:flex;flex-direction:column;gap:8px;margin-top:8px} .menu-accordion summary::after{content:"v";float:right;color:var(--site-muted)} + .edu-nav{display:grid;grid-template-columns:auto 1fr auto;gap:16px;align-items:center;padding:14px 16px;border:1px solid #d8e2f2;border-radius:16px;background:#ffffff} + .edu-nav-links{display:flex;gap:8px;justify-content:center;flex-wrap:wrap} + .edu-nav-link{padding:9px 12px;border-radius:10px;text-decoration:none;color:#274168;font-weight:600;font-size:13px} + .edu-nav-link:hover{background:#eef4ff;color:#0a4dcf} + .edu-nav-cta{padding:10px 14px;border-radius:10px;background:#0a4dcf;color:#fff;text-decoration:none;font-weight:700;font-size:13px} + .edu-hero{display:grid;grid-template-columns:minmax(320px,1.15fr) minmax(280px,.85fr);gap:18px;align-items:stretch} + .edu-hero-copy{padding:6px 4px} + .edu-kicker{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:#e9f0ff;color:#0a4dcf;font-size:11px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;margin-bottom:10px} + .edu-hero h2.editable{font-size:clamp(36px,4.8vw,62px);line-height:1.03;letter-spacing:-.5px;margin:0 0 10px} + .edu-hero p.editable{font-size:20px;line-height:1.45;color:#3e5378;max-width:58ch} + .edu-hero-panel{background:#ffffff;border:1px solid #d8e2f2;border-radius:16px;padding:16px;display:grid;gap:12px} + .edu-stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px} + .edu-stat{border:1px solid #e3ebf8;border-radius:12px;padding:12px;background:#f8fbff} + .edu-stat strong{display:block;font-size:22px;color:#0a4dcf} + .edu-stat span{font-size:12px;color:#4f6286} + .edu-step-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px} + .edu-step{background:#fff;border:1px solid #d9e4f5;border-radius:14px;padding:14px} + .edu-step-num{width:30px;height:30px;border-radius:999px;background:#0a4dcf;color:#fff;display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;margin-bottom:8px} + .edu-cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px} + .edu-card{background:#fff;border:1px solid #d9e4f5;border-radius:14px;padding:16px} + .edu-card .card-pro-title{font-size:22px;line-height:1.15;color:#0b1733} + .edu-card .card-pro-desc{font-size:14px;line-height:1.6;color:#41557a} + .education-site{padding:26px;background:linear-gradient(180deg,#f3f7ff 0%,#eef3fb 100%);border-radius:18px} + .education-site .block{background:transparent;border:0;box-shadow:none;padding:0} + .education-site .block[data-block-type="menu"]{margin-bottom:6px} + .education-site .block[data-block-type="hero"]{background:linear-gradient(140deg,#ffffff 0%,#f6f9ff 100%);border:1px solid #d8e2f2;border-radius:18px;padding:22px} + .education-site .block[data-block-type="cards"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block[data-block-type="iconlist"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block[data-block-type="features"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block[data-block-type="gallery"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block[data-block-type="review"]{background:linear-gradient(140deg,#0a4dcf 0%,#123c8a 100%);color:#fff;border-radius:18px;padding:20px} + .education-site .block[data-block-type="review"] .editable{color:#fff} + .education-site .block[data-block-type="review"] i{color:#ffd86b !important} + .education-site .block[data-block-type="contact"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block[data-block-type="map"]{background:#ffffff;border:1px solid #d8e2f2;border-radius:18px;padding:20px} + .education-site .block h3{font-size:32px;line-height:1.1;margin-bottom:14px !important;letter-spacing:-.3px} + .education-site .edu-nav-link{border:1px solid transparent} + .education-site .edu-nav-link:hover{border-color:#cddcf6;background:#f3f7ff} + .education-site .edu-hero{position:relative} + .education-site .edu-card:nth-child(1){border-top:4px solid #0a4dcf} + .education-site .edu-card:nth-child(2){border-top:4px solid #1f6feb} + .education-site .edu-card:nth-child(3){border-top:4px solid #58a6ff} + .education-site .edu-step{position:relative;overflow:hidden} + .education-site .edu-step:after{content:"";position:absolute;right:-26px;top:-26px;width:90px;height:90px;border-radius:999px;background:rgba(10,77,207,.07)} + .edu-apply{border:1px solid #d8e2f2;background:#fff;border-radius:14px;padding:12px} + .edu-apply h4{margin:0 0 8px;font-size:14px;color:#17356e} + .edu-apply ul{margin:0;padding-left:18px;color:#38527f;font-size:13px;line-height:1.5} + .edu-apply .deadline{display:inline-flex;margin-top:8px;padding:4px 8px;border-radius:999px;background:#e9f0ff;color:#0a4dcf;font-size:11px;font-weight:700} + @media (max-width:980px){.edu-hero{grid-template-columns:1fr}} + .site-global-footer{margin-top:26px;padding:16px 18px;border-radius:12px;border:1px solid #dbe3ee;background:rgba(255,255,255,.72);color:var(--site-muted);font-size:12px;line-height:1.4;text-align:center;width:100%;grid-column:1 / -1;justify-self:stretch} .float-whatsapp{position:fixed;right:22px;bottom:22px;width:52px;height:52px;border-radius:999px;background:#25d366;color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 14px 30px rgba(0,0,0,.22);z-index:999;text-decoration:none} .float-whatsapp:hover{transform:translateY(-2px)} @media (max-width:980px){ .menu-inline{display:none} - .menu-accordion{display:block} + .menu-drawer-toggle{display:inline-flex} + .menu-accordion{display:block;width:100%;min-width:0;max-width:none;margin-left:0} } .preview-shell.size-phone .menu-inline, .preview-shell.size-tablet .menu-inline{display:none} + .preview-shell.size-phone .menu-drawer-toggle, + .preview-shell.size-tablet .menu-drawer-toggle{display:inline-flex} .preview-shell.size-phone .menu-accordion, - .preview-shell.size-tablet .menu-accordion{display:block} + .preview-shell.size-tablet .menu-accordion{display:block;width:100%;min-width:0;max-width:none;margin-left:0} .preview-shell.size-phone .hero-layout, .preview-shell.size-tablet .hero-layout{grid-template-columns:1fr} .preview-shell.size-phone .hero-media, @@ -236,7 +302,7 @@ body.ub24 input, body.ub24 textarea, body.ub24 select{background:#f9fafb;color:#0b0f16;border-color:#e5e7eb} - +
-
{{ 'Nueva pagina' if builder_mode == 'ub24' else 'Pagina Apple' }}
+
{{ 'Nueva pagina' if builder_mode == 'ub24' else 'Page Builder' }}
Arrastra bloques desde la izquierda.
Bloques: 0 (Ilimitado)
{% if builder_mode == 'ub24' %} {% endif %} + @@ -386,6 +456,8 @@
+
+
@@ -398,6 +470,18 @@ const SITE_SLUG = "{{ slug }}"; const SERVER_CONTENT = {{ content|tojson }}; const BUILDER_MODE = "{{ builder_mode or 'default' }}"; + const PREVIEW_ONLY = {{ 'true' if preview_only else 'false' }}; + const SERVER_RUBRO = "{{ rubro|default('restaurante') }}"; + const BUILDER_BASE_PATH = BUILDER_MODE === "ub24" ? `/ub24/${SITE_ID}` : `/elementor/${SITE_ID}`; + const OFFICIAL_RUBROS = [ + { value: "restaurante", label: "Restaurante" }, + { value: "danza", label: "Danza" }, + { value: "cosmeticos", label: "Cosméticos" }, + { value: "despachos", label: "Despachos" }, + { value: "gimnasios", label: "Gimnasios" }, + { value: "educacion", label: "Educación" }, + { value: "base_otro", label: "Base (Otro)" } + ]; const defaultSettings = { site_name: "{{ slug }}", primary_color: "#59d9c8", @@ -418,30 +502,33 @@ bg_video_url: "", canvas_min_height: 1200, canvas_bottom_space: 180, - site_author: "" + site_author: "", + registered_brand: "", + developer_brand: "GKACHELE™", + business_rubro: "restaurante" }; const templates = { - servicios: { + base_otro: { settings: { primary_color: '#1d4ed8', bg_color: '#f6f7fb', text_color: '#0b0c10', muted_color: '#6b7280', font_body: 'Manrope', font_heading: 'Manrope', bg_gradient: true, bg_color2: '#e9eef5' }, blocks: [ { id: makeId(), type: 'menu', data: defaultData('menu') }, - { id: makeId(), type: 'hero', data: { title: 'Servicios profesionales para tu negocio', subtitle: 'Crecemos contigo con soluciones claras y resultados medibles.', button_text: 'Cotizar ahora', button_url: '#contacto', image_url: '' } }, + { id: makeId(), type: 'hero', data: { title: 'Solucion profesional para tu negocio', subtitle: 'Crecemos contigo con ejecucion clara y resultados medibles.', button_text: 'Cotizar ahora', button_url: '#contacto', image_url: '' } }, { id: makeId(), type: 'features', data: { title: 'Beneficios', items: ['Rapido','Profesional','Confiable'] } }, - { id: makeId(), type: 'cards', data: { title: 'Servicios', items: ['Consultoria|Estrategia y ejecucion','Marketing|Crecimiento real','Soporte|Atencion prioritaria'] } }, + { id: makeId(), type: 'cards', data: { title: 'Propuesta', items: ['Consultoria|Estrategia y ejecucion','Marketing|Crecimiento real','Soporte|Atencion prioritaria'] } }, { id: makeId(), type: 'gallery', data: { title: 'Proyectos', images: ['','',''], captions: ['','',''], fit: 'cover' } }, { id: makeId(), type: 'contact', data: { title: 'Hablemos', email: '', phone: '', address: '' } }, { id: makeId(), type: 'social', data: defaultData('social') } ] }, - industrial: { + gimnasios: { settings: { primary_color: '#f97316', bg_color: '#f8fafc', text_color: '#0b0c10', muted_color: '#6b7280', font_body: 'IBM Plex Sans', font_heading: 'Space Grotesk', bg_gradient: false }, blocks: [ { id: makeId(), type: 'menu', data: defaultData('menu') }, - { id: makeId(), type: 'hero', data: { title: 'Soluciones industriales confiables', subtitle: 'Calidad, seguridad y cumplimiento en cada proyecto.', button_text: 'Solicitar presupuesto', button_url: '#contacto', image_url: '' } }, - { id: makeId(), type: 'iconlist', data: { title: 'Diferenciales', items: ['Certificados|Normas y seguridad','Experiencia|Equipos expertos','Entrega|Plazos claros'] } }, - { id: makeId(), type: 'features', data: { title: 'Servicios', items: ['Mantenimiento','Montajes','Ingenieria'] } }, - { id: makeId(), type: 'gallery', data: { title: 'Proyectos', images: ['','',''], captions: ['','',''], fit: 'cover' } }, - { id: makeId(), type: 'contact', data: { title: 'Contacto', email: '', phone: '', address: '' } } + { id: makeId(), type: 'hero', data: { title: 'Entrena mejor con acompañamiento profesional', subtitle: 'Planes personalizados, equipos modernos y seguimiento real.', button_text: 'Prueba gratis', button_url: '#contacto', image_url: '' } }, + { id: makeId(), type: 'iconlist', data: { title: 'Diferenciales', items: ['Entrenadores|Acompañamiento experto','Programas|Objetivos medibles','Comunidad|Motivación constante'] } }, + { id: makeId(), type: 'features', data: { title: 'Áreas', items: ['Fuerza','Cardio','Funcional'] } }, + { id: makeId(), type: 'gallery', data: { title: 'Instalaciones', images: ['','',''], captions: ['','',''], fit: 'cover' } }, + { id: makeId(), type: 'contact', data: { title: 'Inscripción', email: '', phone: '', address: '' } } ] }, restaurante: { @@ -456,7 +543,7 @@ { id: makeId(), type: 'map', data: { title: 'Ubicacion', address: '' } } ] }, - streaming: { + cosmeticos: { settings: { primary_color: '#4f8cff', bg_color: '#0b0f16', text_color: '#e7ebf0', muted_color: '#98a3b6', font_body: 'DM Sans', font_heading: 'Space Grotesk', bg_gradient: true, bg_color2: '#111827' }, blocks: [ { id: makeId(), type: 'menu', data: defaultData('menu') }, @@ -466,6 +553,44 @@ { id: makeId(), type: 'button', data: { text: 'Crear mi cuenta', url: '#contacto', style: 'primary', size: 'lg' } }, { id: makeId(), type: 'contact', data: { title: 'Contacto', email: '', phone: '', address: '' } } ] + }, + danza: { + settings: { primary_color: '#7c3aed', bg_color: '#f8f5ff', text_color: '#160f29', muted_color: '#6d5c8f', font_body: 'Outfit', font_heading: 'Playfair Display', bg_gradient: true, bg_color2: '#efe7ff' }, + blocks: [ + { id: makeId(), type: 'menu', data: defaultData('menu') }, + { id: makeId(), type: 'hero', data: { title: 'Escuela de danza con enfoque escénico', subtitle: 'Formación técnica, expresión artística y montaje profesional.', button_text: 'Agendar clase', button_url: '#contacto', image_url: '' } }, + { id: makeId(), type: 'video', data: { url: '', description: 'Video de presentacion' } }, + { id: makeId(), type: 'features', data: { title: 'Programas', items: ['Iniciación','Intermedio','Avanzado'] } }, + { id: makeId(), type: 'calendar', data: { title: 'Horarios', note: 'Consulta disponibilidad semanal.', embed_url: '' } }, + { id: makeId(), type: 'contact', data: { title: 'Inscripciones', email: '', phone: '', address: '' } } + ] + }, + despachos: { + settings: { primary_color: '#0f766e', bg_color: '#f7faf9', text_color: '#0b0c10', muted_color: '#54656a', font_body: 'IBM Plex Sans', font_heading: 'Merriweather', bg_gradient: false }, + blocks: [ + { id: makeId(), type: 'menu', data: defaultData('menu') }, + { id: makeId(), type: 'hero', data: { title: 'Asesoría estratégica para decisiones seguras', subtitle: 'Despacho especializado en gestión legal y administrativa.', button_text: 'Solicitar consulta', button_url: '#contacto', image_url: '' } }, + { id: makeId(), type: 'iconlist', data: { title: 'Áreas', items: ['Corporativo|Acompañamiento empresarial','Tributario|Planificación y cumplimiento','Laboral|Prevención y defensa'] } }, + { id: makeId(), type: 'review', data: { title: 'Casos', name: 'Cliente empresarial', text: 'Resolución eficiente y comunicación clara en cada etapa.', rating: 5, style: 'quote' } }, + { id: makeId(), type: 'contact', data: { title: 'Contacto', email: '', phone: '', address: '' } } + ] + }, + educacion: { + settings: { primary_color: '#0a4dcf', bg_color: '#f4f7ff', text_color: '#0b1733', muted_color: '#4f6286', font_body: 'IBM Plex Sans', font_heading: 'Space Grotesk', bg_gradient: true, bg_color2: '#dbe8ff' }, + blocks: [ + { id: makeId(), type: 'menu', data: { title: 'Academia', items: ['Inicio','Programas','Admisiones','Campus','Contacto'], menu_mode: 'both', menu_mobile_style: 'accordion', width: 100 } }, + { id: makeId(), type: 'hero', data: { title: 'Formacion academica con enfoque profesional', subtitle: 'Desarrolla competencias reales con docentes expertos, laboratorios modernos y acompanamiento continuo.', button_text: 'Iniciar admision', button_url: '#contacto', image_url: '' } }, + { id: makeId(), type: 'cards', data: { title: 'Programas destacados', items: ['Pregrado|Carreras con plan curricular actualizado y enfoque en empleabilidad.','Diplomados|Especializacion intensiva para perfiles tecnicos y profesionales.','Educacion continua|Trayectos cortos para actualizar habilidades de alto impacto.'] } }, + { id: makeId(), type: 'iconlist', data: { title: 'Ruta de admision', items: ['Postula online|Completa tu solicitud y adjunta documentos en minutos.','Entrevista academica|Recibe orientacion personalizada segun tu perfil.','Matricula guiada|Formaliza tu ingreso con soporte del equipo academico.'] } }, + { id: makeId(), type: 'features', data: { title: 'Por que elegirnos', items: ['Docentes activos en industria','Modelo hibrido flexible','Convenios empresariales','Tutoria academica permanente'] } }, + { id: makeId(), type: 'gallery', data: { title: 'Vida universitaria', images: ['','',''], captions: ['Laboratorios y practica aplicada','Clases colaborativas','Eventos academicos y comunidad'], fit: 'cover' } }, + { id: makeId(), type: 'calendar', data: { title: 'Fechas clave', note: 'Convocatoria abierta. Inicio de clases y cronograma de admision disponible para edicion.', embed_url: '' } }, + { id: makeId(), type: 'review', data: { title: 'Historias de estudiantes', name: 'Estudiante de Ingenieria', text: 'La metodologia es practica y exigente; pude aplicar lo aprendido desde el primer ciclo.', rating: 5, style: 'quote' } }, + { id: makeId(), type: 'button', data: { text: 'Descargar brochure academico', url: '#contacto', style: 'primary', size: 'lg' } }, + { id: makeId(), type: 'contact', data: { title: 'Oficina de admisiones', email: 'admisiones@tuinstitucion.edu', phone: '+54 9 11 0000 0000', address: 'Campus Central - Av. Principal 123' } }, + { id: makeId(), type: 'map', data: { title: 'Visita el campus', address: 'Campus Central - Av. Principal 123' } }, + { id: makeId(), type: 'social', data: { instagram: '', facebook: '', whatsapp: '', tiktok: '', youtube: '', icon_size: 18, icon_color: '#0b1733', show_text: true, icon_style: 'pill', width: 100 } } + ] } }; @@ -503,7 +628,7 @@ const state = { function makeId(){ return "block_" + Date.now() + "_" + Math.floor(Math.random()*1000); } function snapBlockWidth(type, raw){ if (type === "menu") return 100; - return Math.max(30, Math.min(100, Number(raw || 50))); + return Math.max(30, Math.min(100, Number(raw || 100))); } function getDefaultPos(){ const base = 20; @@ -513,13 +638,13 @@ const state = { } function defaultData(type){ switch(type){ - case "menu": return { title:"Menu", items:[], menu_mode:"both", width:100 }; + case "menu": return { title:"Menu", items:["Inicio","Productos","Blog","La Empresa","Contacto"], menu_mode:"both", menu_mobile_style:"accordion", width:100 }; case "hero": return { title:"Tu propuesta de valor", subtitle:"Explica en una frase por que elegirte.", button_text:"Contactar", button_url:"#contacto", image_url:"", width:100 }; case "text": return { text:"Describe tu negocio aqui.", width:100 }; case "image": return { url:"", alt:"", caption:"", fit:"cover", overlay_text:"", width:100 }; case "features": return { title:"Beneficios", items:["Rapido","Profesional","Confiable"], width:100 }; case "gallery": return { title:"Proyectos", images:["","",""], captions:["","",""], fit:"cover", width:100 }; - case "cards": return { title:"Servicios", items:["Titulo 1|Texto breve","Titulo 2|Texto breve","Titulo 3|Texto breve"], width:100 }; + case "cards": return { title:"Propuesta", items:["Titulo 1|Texto breve","Titulo 2|Texto breve","Titulo 3|Texto breve"], width:100 }; case "iconlist": return { title:"Diferenciales", items:["Rapido|Ahorra tiempo","Seguro|Datos protegidos","Soporte|Respuesta rapida"], width:100 }; case "contact": return { title:"Hablemos", email:"", phone:"", address:"", width:100 }; case "map": return { title:"Ubicacion", address:"", width:100 }; @@ -531,6 +656,42 @@ const state = { default: return {}; } } + function normalizeMenuMode(value){ + const raw = String(value || "both").trim().toLowerCase(); + if (raw === "acordeon" || raw === "accordion") return "accordion"; + if (raw === "horizontal" || raw === "inline") return "inline"; + if (raw === "ambos" || raw === "both") return "both"; + return "both"; + } + function normalizeKey(value){ + return String(value || "") + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + .trim(); + } + function findFirstBlockIdByTypes(types){ + const set = new Set(types || []); + const found = state.blocks.find((b)=>b && b.type !== "menu" && set.has(b.type)); + return found ? found.id : ""; + } + function resolveEducationMenuTarget(label){ + const key = normalizeKey(label); + if (key.includes("inicio")) return findFirstBlockIdByTypes(["hero","cards","features"]); + if (key.includes("program")) return findFirstBlockIdByTypes(["cards","features"]); + if (key.includes("admi")) return findFirstBlockIdByTypes(["iconlist","calendar","review"]); + if (key.includes("campus") || key.includes("galer")) return findFirstBlockIdByTypes(["gallery","map"]); + if (key.includes("contact")) return findFirstBlockIdByTypes(["contact","map"]); + return findFirstBlockIdByTypes(["hero","cards","features","iconlist","gallery","contact","map"]); + } + function isEducationRubro(){ + return normalizeRubro(state?.settings?.business_rubro || SERVER_RUBRO || "") === "educacion"; + } + function normalizeRubro(value){ + const raw = String(value || "").trim().toLowerCase(); + const allowed = new Set(OFFICIAL_RUBROS.map((r)=>r.value)); + return allowed.has(raw) ? raw : "restaurante"; + } function repairMojibake(text){ const raw = String(text || ""); if (!/[ÃÂ]/.test(raw)) return raw; @@ -725,28 +886,106 @@ const state = { function renderBlockHtml(block){ if (block.type==="menu"){ - const items = state.blocks - .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 }; - }); - const links = items.map(it=>`${escapeHtml(it.label)}`).join(""); - const mobileLinks = items.map(it=>`${escapeHtml(it.label)}`).join(""); - const logo = state.settings.logo_url ? `Logo` : ""; - const mode = (block.data?.menu_mode || "both").toLowerCase(); + if (isEducationRubro()){ + const items = Array.isArray(block.data?.items) && block.data.items.length ? block.data.items : ["Inicio","Programas","Admisiones","Campus","Contacto"]; + const links = items.map((it, idx)=>{ + const target = resolveEducationMenuTarget(it); + const href = target ? `#${target}` : "#"; + return `${escapeHtml(it)}`; + }).join(""); + const contactTarget = resolveEducationMenuTarget("contacto"); + const contactHref = contactTarget ? `#${contactTarget}` : "#"; + const brand = state.settings.site_name || "Academia"; + return `
+
${state.settings.logo_url ? `Logo` : `${escapeHtml(String(brand).slice(0,1).toUpperCase())}`}${escapeHtml(brand)}
+ + Postular ahora +
`; + } + const manualItems = Array.isArray(block.data?.items) ? block.data.items.map((x)=>String(x || "").trim()).filter(Boolean) : []; + const items = manualItems.length + ? (manualItems.length >= 3 + ? manualItems.map((label, i)=>({ id: `menu_item_${i+1}`, label: String(label).slice(0, 24), index: i+1 })) + : []) + : state.blocks + .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 }; + }); + 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 } + ]; + const links = safeItems.map(it=>`${escapeHtml(it.label)}`).join(""); + const mobileLinks = safeItems.map(it=>`${escapeHtml(it.label)}`).join(""); + const siteName = String(state.settings.site_name || "GKACHELE"); + const initial = escapeHtml(siteName.slice(0, 1).toUpperCase()); + const logo = state.settings.logo_url + ? `Logo` + : `${initial}`; + const mode = normalizeMenuMode(block.data?.menu_mode || "both"); + const mobileStyle = (block.data?.menu_mobile_style || "accordion").toLowerCase() === "drawer" ? "drawer" : "accordion"; const showInline = mode === "both" || mode === "inline"; const showAccordion = mode === "both" || mode === "accordion"; + const showDrawer = showAccordion && mobileStyle === "drawer"; + const menuDrawerId = `drawer_${block.id}`; + const menuOverlayId = `drawer_overlay_${block.id}`; return ``; } if (block.type==="hero"){ + if (isEducationRubro()){ + const admissionTarget = resolveEducationMenuTarget("admisiones"); + const programsTarget = resolveEducationMenuTarget("programas"); + const admissionHref = admissionTarget ? `#${admissionTarget}` : "#"; + const programsHref = programsTarget ? `#${programsTarget}` : "#"; + return `
+
+
Admisiones 2026 abiertas
+ ${editable("h2","title",block.data.title,"Titulo",false,"")} + ${editable("p","subtitle",block.data.subtitle,"Subtitulo",true,"")} + +
+
+
+
+4,200Estudiantes activos
+
92%Insercion laboral
+
+80Docentes especialistas
+
35Convenios empresariales
+
+
+

Proceso de postulacion

+
    +
  • Registro en linea y carga de documentos.
  • +
  • Entrevista de orientacion academica.
  • +
  • Evaluacion y confirmacion de vacante.
  • +
+ Cierre de convocatoria: 30 marzo +
+
${block.data.image_url ? `Campus` : `
Agrega imagen institucional para completar la portada.
`}
+
+
`; + } const image = block.data.image_url ? `Hero image` : `
Agrega imagen en "Imagen URL" para completar el hero.
`; return `
@@ -775,6 +1014,9 @@ const state = { } if (block.type==="features"){ const items = Array.isArray(block.data.items)?block.data.items:[]; + if (isEducationRubro()){ + return `${editable("h3","title",block.data.title,"Titulo",false,"margin:0 0 12px;font-size:28px")}
${items.map((i,idx)=>`
${idx+1}
${escapeHtml(i)}
`).join("")}
`; + } return `${editable("h3","title",block.data.title,"Titulo",false,"margin:0 0 10px")}
${items.map((i,idx)=>`
${escapeHtml(i)}
`).join("")}
`; } if (block.type==="gallery"){ @@ -786,10 +1028,16 @@ const state = { } if (block.type==="cards"){ const items = Array.isArray(block.data.items)?block.data.items:[]; + if (isEducationRubro()){ + return `${editable("h3","title",block.data.title,"Titulo",false,"font-size:30px;margin:0 0 14px")}
${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `
${escapeHtml(t)}
${escapeHtml(d)}
Ver plan de estudios
`;}).join("")}
`; + } return `${editable("h3","title",block.data.title,"Titulo",false,"")}
${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `
${escapeHtml(t)}
${escapeHtml(d)}
`;}).join("")}
`; } if (block.type==="iconlist"){ const items = Array.isArray(block.data.items)?block.data.items:[]; + if (isEducationRubro()){ + return `${editable("h3","title",block.data.title,"Titulo",false,"margin:0 0 12px;font-size:28px")}
${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `
${idx+1}
${escapeHtml(t)}
${escapeHtml(d)}
`;}).join("")}
`; + } return `${editable("h3","title",block.data.title,"Titulo",false,"margin:0 0 10px")}
${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";const letter=(t||"I").trim().slice(0,1).toUpperCase();return `
${escapeHtml(letter)}
${escapeHtml(t)}
${escapeHtml(d)}
`;}).join("")}
`; } if (block.type==="contact"){ @@ -945,12 +1193,15 @@ const state = { function renderPreview(){ const canvas = document.getElementById("previewCanvas"); + document.body.style.overflow = ""; + document.querySelectorAll(".menu-drawer-global").forEach((n)=>n.remove()); if (canvasSortable){ canvasSortable.destroy(); canvasSortable = null; } canvas.innerHTML = ""; canvas.classList.toggle("free-drag", !!state.settings.free_drag); + canvas.classList.toggle("education-site", isEducationRubro()); applySiteTheme(); if (state.settings.bg_anim_url){ canvas.style.background = `url('${state.settings.bg_anim_url}') center/cover no-repeat`; @@ -1009,6 +1260,7 @@ const state = { el.className = "block"; if (!state.settings.free_drag){ el.removeAttribute("draggable"); } el.dataset.blockId = block.id; + el.dataset.blockType = block.type; el.id = block.id; if (state.settings.free_drag){ const pos = block.pos || getDefaultPos(); @@ -1040,6 +1292,7 @@ const state = { } } el.innerHTML = renderBlockHtml(block); + if (block.type === "menu"){ wireMenuAccordionInteractions(el); } if (block.type === "image"){ bindInlineImageDrop(el, block); } if (block.type === "gallery"){ bindGalleryDrops(el, block); } if (block.type === "video"){ bindInlineVideoDrop(el, block); } @@ -1167,21 +1420,17 @@ const state = { }); canvas.style.minHeight = maxBottom + "px"; } else { - const minH = Math.max(700, Number(state.settings.canvas_min_height || 1200)); + const minH = Math.max(900, Number(state.settings.canvas_min_height || 1200)); canvas.style.minHeight = `${minH}px`; } - if (state.settings.site_author){ - const credit = document.createElement("div"); - credit.style.marginTop = "20px"; - credit.style.padding = "12px 14px"; - credit.style.borderRadius = "10px"; - credit.style.background = "rgba(15,23,42,.06)"; - credit.style.color = "var(--site-muted)"; - credit.style.fontSize = "12px"; - credit.style.textAlign = "center"; - credit.textContent = `Creado por ${state.settings.site_author}`; - inner.appendChild(credit); - } + const footer = document.createElement("footer"); + footer.className = "site-global-footer"; + const year = new Date().getFullYear(); + const registered = String(state.settings.registered_brand || state.settings.site_name || "Tu marca").trim(); + const devBrand = String(state.settings.developer_brand || "GKACHELE™").trim(); + const author = String(state.settings.site_author || "").trim(); + footer.innerHTML = `© ${year} ${escapeHtml(registered)}. Todos los derechos reservados.
Desarrollado por ${escapeHtml(devBrand)}${author ? ` · Creado por ${escapeHtml(author)}` : ""}`; + inner.appendChild(footer); const scrollBtn = document.createElement("button"); scrollBtn.className = "scroll-btn"; scrollBtn.innerHTML = ''; @@ -1382,8 +1631,11 @@ const state = { const input=(label,id,val)=>`
`; if (block.type==="menu"){ html+=input("Titulo","menuTitle",data.title); - const mm = escapeHtml(data.menu_mode || "both"); + const mm = escapeHtml(normalizeMenuMode(data.menu_mode || "both")); html+=`
`; + const mms = escapeHtml((data.menu_mobile_style || "accordion").toLowerCase() === "drawer" ? "drawer" : "accordion"); + html+=`
`; + html+=`
`; } else if (block.type==="hero"){ html+=input("Titulo","heroTitle",data.title); html+=input("Subtitulo","heroSubtitle",data.subtitle); @@ -1402,7 +1654,7 @@ const state = { html+=`
${data.url ? "Imagen cargada" : "Suelta imagen o click"}
`; } else if (block.type==="features"){ html+=input("Titulo","featuresTitle",data.title); - html+=`
`; + html+=`
`; } else if (block.type==="gallery"){ html+=input("Titulo","galleryTitle",data.title); const fitVal = escapeHtml(data.fit || "cover"); @@ -1412,14 +1664,14 @@ const state = { const has = (data.images||[])[i]; html+=`
${has ? "Imagen cargada" : "Suelta imagen o click"}
`; }); - html+=`
`; - html+=`
`; + html+=`
`; + html+=`
`; } else if (block.type==="cards"){ html+=input("Titulo","cardsTitle",data.title); - html+=`
`; + html+=`
`; } else if (block.type==="iconlist"){ html+=input("Titulo","iconTitle",data.title); - html+=`
`; + html+=`
`; } else if (block.type==="contact"){ html+=input("Titulo","contactTitle",data.title); html+=input("Email","contactEmail",data.email); @@ -1507,7 +1759,11 @@ const state = { if (block.type==="menu"){ block.data.title=document.getElementById("menuTitle").value; const mm = document.getElementById("menuMode"); - if (mm){ block.data.menu_mode = mm.value || "both"; } + if (mm){ block.data.menu_mode = normalizeMenuMode(mm.value || "both"); } + const mms = document.getElementById("menuMobileStyle"); + if (mms){ block.data.menu_mobile_style = (mms.value || "accordion") === "drawer" ? "drawer" : "accordion"; } + const mi = document.getElementById("menuItems"); + if (mi){ block.data.items = mi.value.split("\n").map((x)=>x.trim()).filter(Boolean); } } else if (block.type==="hero"){ block.data.title=document.getElementById("heroTitle").value; @@ -1526,17 +1782,17 @@ const state = { const fit = document.getElementById("imageFit"); if (fit){ block.data.fit = fit.value || "cover"; } } - else if (block.type==="features"){ block.data.title=document.getElementById("featuresTitle").value; block.data.items=document.getElementById("featuresItems").value.split("\\n").filter(Boolean); } + else if (block.type==="features"){ block.data.title=document.getElementById("featuresTitle").value; block.data.items=document.getElementById("featuresItems").value.split("\n").filter(Boolean); } else if (block.type==="gallery"){ block.data.title=document.getElementById("galleryTitle").value; - block.data.images=document.getElementById("galleryImages").value.split("\\n"); + block.data.images=document.getElementById("galleryImages").value.split("\n"); const caps = document.getElementById("galleryCaptions"); - if (caps){ block.data.captions = caps.value.split("\\n"); } + if (caps){ block.data.captions = caps.value.split("\n"); } const fit = document.getElementById("galleryFit"); if (fit){ block.data.fit = fit.value || "cover"; } } - else if (block.type==="cards"){ block.data.title=document.getElementById("cardsTitle").value; block.data.items=document.getElementById("cardsItems").value.split("\\n").filter(Boolean); } - else if (block.type==="iconlist"){ block.data.title=document.getElementById("iconTitle").value; block.data.items=document.getElementById("iconItems").value.split("\\n").filter(Boolean); } + else if (block.type==="cards"){ block.data.title=document.getElementById("cardsTitle").value; block.data.items=document.getElementById("cardsItems").value.split("\n").filter(Boolean); } + else if (block.type==="iconlist"){ block.data.title=document.getElementById("iconTitle").value; block.data.items=document.getElementById("iconItems").value.split("\n").filter(Boolean); } else if (block.type==="contact"){ block.data.title=document.getElementById("contactTitle").value; block.data.email=document.getElementById("contactEmail").value; block.data.phone=document.getElementById("contactPhone").value; block.data.address=document.getElementById("contactAddress").value; } else if (block.type==="button"){ block.data.text=document.getElementById("buttonText").value; @@ -1630,18 +1886,80 @@ const state = { }); } function wireSortableDnD(){ return; } + function wireMenuAccordionInteractions(blockEl){ + if (!blockEl) return; + blockEl.querySelectorAll(".menu-accordion, .menu-accordion summary, .menu-accordion a").forEach((node)=>{ + node.addEventListener("pointerdown",(e)=>{ e.stopPropagation(); }); + node.addEventListener("click",(e)=>{ e.stopPropagation(); }); + }); + blockEl.querySelectorAll(".menu-drawer-global").forEach((node)=>{ + if (node.parentElement !== document.body){ + document.body.appendChild(node); + } + }); + const closeDrawer = (drawerId)=>{ + if (!drawerId) return; + const panel = document.getElementById(drawerId); + const overlay = document.querySelector(`[data-drawer-overlay="${drawerId}"]`); + if (panel) panel.classList.remove("open"); + if (overlay) overlay.classList.remove("open"); + document.body.style.overflow = ""; + }; + const openDrawer = (drawerId)=>{ + if (!drawerId) return; + const shell = document.querySelector(".preview-shell"); + const forceMobile = shell && (shell.classList.contains("size-phone") || shell.classList.contains("size-tablet")); + if (!forceMobile && window.innerWidth > 980) return; + const panel = document.getElementById(drawerId); + const overlay = document.querySelector(`[data-drawer-overlay="${drawerId}"]`); + if (panel) panel.classList.add("open"); + if (overlay) overlay.classList.add("open"); + document.body.style.overflow = "hidden"; + }; + blockEl.querySelectorAll("[data-drawer-toggle]").forEach((btn)=>{ + btn.addEventListener("click",(e)=>{ + e.preventDefault(); + e.stopPropagation(); + openDrawer(btn.getAttribute("data-drawer-toggle")); + }); + }); + blockEl.querySelectorAll("[data-drawer-close]").forEach((btn)=>{ + btn.addEventListener("click",(e)=>{ + e.preventDefault(); + e.stopPropagation(); + closeDrawer(btn.getAttribute("data-drawer-close")); + }); + }); + blockEl.querySelectorAll("[data-drawer-overlay]").forEach((ov)=>{ + ov.addEventListener("click",(e)=>{ + e.preventDefault(); + e.stopPropagation(); + closeDrawer(ov.getAttribute("data-drawer-overlay")); + }); + }); + blockEl.querySelectorAll("[data-drawer-link]").forEach((a)=>{ + a.addEventListener("click",()=>{ + const parent = a.closest(".menu-drawer"); + if (parent && parent.id) closeDrawer(parent.id); + }); + }); + } function initCanvasSortable(container){ if (!hasSortable() || !container) return; canvasSortable = Sortable.create(container, { group: { name: "builderBlocks", pull: false, put: false }, draggable: ".block", handle: ".drag-handle", + filter: "input,textarea,select,a,summary,details,.editable,.menu-links,.menu-accordion", + preventOnFilter: false, animation: 150, forceFallback: true, + fallbackOnBody: true, fallbackTolerance: 8, delayOnTouchOnly: true, delay: 120, touchStartThreshold: 6, + bubbleScroll: true, onEnd: (evt)=>{ if (!evt || evt.from !== evt.to) return; const orderedIds = [...container.querySelectorAll(".block")].map((n)=>n.dataset.blockId).filter(Boolean); @@ -1930,6 +2248,8 @@ const state = { const animToggle=document.getElementById("animToggle"); const canvasMinHeight=document.getElementById("canvasMinHeightInput"); const canvasBottomSpace=document.getElementById("canvasBottomSpaceInput"); + const registeredBrand=document.getElementById("registeredBrandInput"); + const developerBrand=document.getElementById("developerBrandInput"); const siteAuthor=document.getElementById("siteAuthorInput"); siteName.value=s.site_name||""; primary.value=s.primary_color||"#59d9c8"; bgColor.value=s.bg_color||"#f6f7fb"; bgColor2.value=s.bg_color2||"#e9eef5"; bgGradient.checked=!!s.bg_gradient; @@ -1943,6 +2263,8 @@ const state = { animToggle.checked = s.animations !== false; canvasMinHeight.value = Math.max(700, Number(s.canvas_min_height || 1200)); canvasBottomSpace.value = Math.max(0, Number(s.canvas_bottom_space || 180)); + 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(); }); @@ -1959,6 +2281,8 @@ const state = { animToggle.addEventListener("change",()=>{ s.animations=animToggle.checked; 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(); }); bindDrop(logoDrop, logoFile, (data)=>{ s.logo_url = data; }, "Logo cargado"); @@ -1988,20 +2312,24 @@ const state = { setShellClass("size-desktop"); } function wirePreviewToggle(){ - const btn = document.getElementById("btnPreview"); - if (!btn) return; - btn.addEventListener("click", ()=>{ - const isPreview = document.body.classList.contains("preview-mode"); - if (isPreview){ - window.history.back(); - return; - } - const previewUrl = new URL(window.location.href); - previewUrl.hash = "preview"; - window.history.pushState({ preview: true }, "", previewUrl.toString()); - document.body.classList.add("preview-mode"); - ensurePreviewBackButton(); - }); + const btnPreview = document.getElementById("btnPreview"); + if (btnPreview){ + btnPreview.addEventListener("click", async ()=>{ + btnPreview.disabled = true; + const old = btnPreview.textContent; + btnPreview.textContent = "Abriendo..."; + try{ + await saveDraftSilently(); + const url = new URL(`${BUILDER_BASE_PATH}/preview-final`, window.location.origin); + window.open(url.toString(), "_blank", "noopener,noreferrer"); + } catch(_e){ + window.alert("No se pudo abrir la vista previa. Reintenta."); + } finally { + btnPreview.disabled = false; + btnPreview.textContent = old; + } + }); + } } function ensurePreviewBackButton(){ let back = document.getElementById("previewBackBtn"); @@ -2011,6 +2339,10 @@ const state = { back.className = "preview-back"; back.textContent = "Atras"; back.addEventListener("click", ()=>{ + if (PREVIEW_ONLY){ + window.location.href = BUILDER_BASE_PATH; + return; + } if (window.history.length > 1){ window.history.back(); return; @@ -2026,7 +2358,7 @@ const state = { } function initStandalonePreviewMode(){ const apply = ()=>{ - const isPreview = window.location.hash === "#preview"; + const isPreview = PREVIEW_ONLY; document.body.classList.toggle("preview-mode", isPreview); if (isPreview){ ensurePreviewBackButton(); @@ -2036,10 +2368,6 @@ const state = { } }; apply(); - if (!window.__previewPopBound){ - window.addEventListener("popstate", apply); - window.__previewPopBound = true; - } } function wireThemeToggle(){ const btn = document.getElementById("btnTheme"); @@ -2085,12 +2413,36 @@ const state = { status.textContent = msg; status.className = `save-status${kind ? ` ${kind}` : ""}`; } + async function saveDraftSilently(){ + const payload = { + site_id: SITE_ID, + publish: false, + content: { ...SERVER_CONTENT, settings: state.settings, blocks: state.blocks } + }; + const res = await fetch("/api/elementor/save",{ + method:"POST", + headers:{ "Content-Type":"application/json" }, + body: JSON.stringify(payload) + }); + const data = await res.json(); + if (!res.ok || !data.success) throw new Error(data.error || "save draft failed"); + return true; + } function normalizeLoadedBlocks(blocks){ if (!Array.isArray(blocks)) return []; return blocks .filter((b)=>b && typeof b === "object") .map((b)=>{ const data = (b.data && typeof b.data === "object") ? { ...b.data } : {}; + if (b.type === "menu"){ + data.menu_mode = normalizeMenuMode(data.menu_mode || "both"); + data.menu_mobile_style = (data.menu_mobile_style || "accordion") === "drawer" ? "drawer" : "accordion"; + if (!Array.isArray(data.items) || data.items.length < 3){ + data.items = ["Inicio","Productos","Blog","La Empresa","Contacto"]; + } else { + data.items = data.items.map((x)=>String(x || "").trim()).filter(Boolean); + } + } const fallback = 100; data.width = snapBlockWidth(b.type, (typeof data.width === "number" ? data.width : fallback)); delete data.full_width; @@ -2136,9 +2488,27 @@ const state = { } } } + async function resetBlocks(){ + if (isSaving) return; + const ok = window.confirm("Esto borrara todos los bloques actuales. Deseas continuar?"); + if (!ok) return; + state.blocks = []; + selectedBlockId = null; + renderInspector(); + renderPreview(); + setSaveStatus("Reseteando...", "busy"); + try{ + await saveDraftSilently(); + setSaveStatus("Reset aplicado", "ok"); + } catch(_e){ + setSaveStatus("Error al resetear", "error"); + } + } function init(){ // By default we keep section flow layout for stable full-page composition. state.settings.free_drag = false; + const initialRubro = normalizeRubro(state.settings.business_rubro || SERVER_RUBRO || "restaurante"); + state.settings.business_rubro = initialRubro; state.blocks = normalizeLoadedBlocks(state.blocks); if (BUILDER_MODE === "ub24"){ state.blocks.forEach(b=>{ if (!b.page) b.page = "home"; }); @@ -2166,18 +2536,26 @@ const state = { if (!key || !templates[key]) return; const t = templates[key]; state.settings = { ...state.settings, ...t.settings }; + state.settings.business_rubro = normalizeRubro(key); state.blocks = t.blocks.map(b=>({ ...b, id: makeId(), page: (BUILDER_MODE==="ub24" ? "home" : b.page) })); selectedBlockId = null; renderInspector(); renderPreview(); wireSettings(); }; if (templateSelect){ + templateSelect.value = state.settings.business_rubro || ""; templateSelect.addEventListener("change", ()=>{ applyTemplate(templateSelect.value); }); } + if (!state.blocks.length && templates[initialRubro]){ + applyTemplate(initialRubro); + if (templateSelect){ templateSelect.value = initialRubro; } + } 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(); } init();