Compare commits
5 Commits
8ae0017533
...
a15e3e17af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a15e3e17af | ||
|
|
d5f2b819bf | ||
|
|
b3bb7d57aa | ||
|
|
e83e915584 | ||
|
|
e3a1c9d17f |
@@ -4,6 +4,19 @@
|
||||
|
||||
## 🔄 Historial de Versiones
|
||||
|
||||
### Hash: `gkachele-elementor-restaurante-20260221-v22`
|
||||
**Fecha:** 21 Febrero 2026
|
||||
**Estado:** OK funcional (mejorable)
|
||||
|
||||
#### Cambios:
|
||||
- OK Drawer Pro estabilizado: apertura/cierre, overlay, z-index, lock scroll, Escape/foco y sync por viewport.
|
||||
- OK Reset de builder corrige limpieza de bloques + settings.
|
||||
- OK Se evita autoload de plantilla cuando ya hay estado guardado.
|
||||
- OK Tema restaurante sin colores duros por bloque; vuelve a depender de variables editables.
|
||||
- OK Se restaura comportamiento de ancho/contenedor para no romper bloques al 100%.
|
||||
- Pendiente: QA manual de cada icono del menu en preview final (plan siguiente lote).
|
||||
|
||||
---
|
||||
### Hash: `gkachele-elementor-apple-20260209-v14`
|
||||
**Fecha:** 09 Febrero 2026
|
||||
**Estado:** OK Fase 1
|
||||
@@ -238,6 +251,8 @@
|
||||
|
||||
**© 2025 GKACHELE™. Todos los derechos reservados.**
|
||||
|
||||
## 🔄 Historial de Versiones
|
||||
|
||||
### Hash: `gkachele-builder-docs-20260208-v1`
|
||||
**Fecha:** 08 Febrero 2026
|
||||
**Estado:** ✅ Documentado
|
||||
@@ -259,3 +274,6 @@
|
||||
- `memoria/ESTADO_ACTUAL.md` marcado como historico para evitar decisiones con contexto 2025.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Memoria Codex - GKACHELE
|
||||
|
||||
**Fecha de corte:** 14 Febrero 2026
|
||||
**Fecha de corte:** 22 Febrero 2026
|
||||
|
||||
## Fuente de verdad (estado IA)
|
||||
- `codex/VERSIONADO_IA.md`
|
||||
@@ -9,21 +9,23 @@
|
||||
|
||||
## Punto exacto
|
||||
- Rama activa: `ai/ub24-builder-v1`
|
||||
- Commit HEAD: `6d696c4`
|
||||
- Commit de referencia funcional: `e83e915`
|
||||
- Version base builder: `gkachele-elementor-templates-20260210-v21`
|
||||
- Estado de sync local/remoto al corte: `0/0`
|
||||
- Estado de sync local/remoto al corte: `3/0`
|
||||
|
||||
## Estado actual funcional
|
||||
- Builder visual operativo en `/elementor/<site_id>`.
|
||||
- Drag/drop y resize fluido en canvas.
|
||||
- Inspector para estilos y contenido.
|
||||
- Modo libre con snapping.
|
||||
- Preview limpio y menu por modos (horizontal/acordeon/ambos).
|
||||
- Menu mobile con modo `Drawer Pro` estabilizado (apertura/cierre, overlay, escape, foco, lock scroll).
|
||||
- Reset corrige bloques + settings y evita recarga de plantilla por autoload no deseado.
|
||||
- Tema restaurante mejorado, pero aun con ajustes pendientes de calidad fina.
|
||||
|
||||
## Pendientes inmediatos
|
||||
- Cargar bloques reales por plantilla de rubro (no solo look-and-feel).
|
||||
- Definir persistencia de resenas al publicar.
|
||||
- Integrar calendario real (Calendly/Google).
|
||||
- Revisar cada icono del menu en preview final (flujo completo, uno por uno).
|
||||
- Validar responsive del menu (desktop/tablet/mobile) con focus y accesibilidad.
|
||||
- Ajuste fino visual del tema restaurante (estado "mejorable").
|
||||
- Consolidar puente Builder -> SaaS (`/customizer/<id>`).
|
||||
|
||||
## Flujo operativo memorizado
|
||||
@@ -32,5 +34,11 @@
|
||||
3. Push a `origin/ai/ub24-builder-v1` en cada lote validado.
|
||||
4. Actualizar hash y estado en `codex/VERSIONADO_IA.md`.
|
||||
|
||||
## Metodo acordado (memorizado)
|
||||
- Misma rama + control por hash/revert.
|
||||
- Fixes acotados (un archivo por lote cuando aplique).
|
||||
- Probar inmediatamente cada cambio antes de avanzar.
|
||||
- Sin commit/push hasta orden explicita del usuario.
|
||||
|
||||
## Comando de arranque
|
||||
- `python -m demo.app` desde `c:\word`
|
||||
|
||||
@@ -1,373 +1,116 @@
|
||||
# Versionado IA - UB24/Elementor
|
||||
# Versionado IA - UB24 / Elementor
|
||||
|
||||
## Rama de trabajo
|
||||
- `ai/ub24-builder-v1`
|
||||
## 1) Objetivo
|
||||
Definir un proceso de versionado auditable, reproducible y estable para el desarrollo del builder UB24.
|
||||
|
||||
## Estado de sincronizacion (14 Febrero 2026)
|
||||
- Local: `ai/ub24-builder-v1`
|
||||
- Remoto: `origin/ai/ub24-builder-v1`
|
||||
- Divergencia verificada: `0/0` (sin commits pendientes entre local y remoto)
|
||||
## 2) Estado actual verificado
|
||||
- Fecha de verificacion: `2026-02-22`
|
||||
- Rama activa: `ai/ub24-builder-v1`
|
||||
- Upstream: `origin/ai/ub24-builder-v1`
|
||||
- Divergencia local/remoto: `3 0`
|
||||
- Comando usado:
|
||||
- `git rev-list --left-right --count ai/ub24-builder-v1...origin/ai/ub24-builder-v1`
|
||||
|
||||
## Regla de trabajo
|
||||
1. Cada cambio funcional se guarda en un commit separado.
|
||||
2. Cada commit se registra con su hash.
|
||||
3. Cada commit debe incluir comando de reversion rapida.
|
||||
4. La rama debe quedar sincronizada con remoto al cerrar bloque de trabajo.
|
||||
## 3) Politica de versionado
|
||||
1. Commits atomicos por cambio funcional.
|
||||
2. Mensajes bajo convencion semantica.
|
||||
3. Reversion siempre definida por commit.
|
||||
4. Cada bloque validado se empuja a remoto.
|
||||
5. No mezclar cambios de infraestructura con cambios de UX en el mismo commit.
|
||||
|
||||
## Convencion de mensaje
|
||||
## 4) Convencion de commits
|
||||
- `feat(builder): ...`
|
||||
- `fix(builder): ...`
|
||||
- `refactor(builder): ...`
|
||||
- `chore(versioning): ...`
|
||||
- `docs(builder): ...`
|
||||
|
||||
## Flujo con Gitea
|
||||
1. Trabajo local en `ai/ub24-builder-v1`.
|
||||
2. Push continuo a `origin/ai/ub24-builder-v1`.
|
||||
3. Merge cuando validemos en local y Raspberry.
|
||||
|
||||
## Protocolo fijo de sincronizacion (siempre)
|
||||
1. Verificar rama activa: `git branch --show-current`
|
||||
2. Actualizar referencias remotas: `git fetch origin --prune`
|
||||
3. Medir divergencia: `git rev-list --left-right --count ai/ub24-builder-v1...origin/ai/ub24-builder-v1`
|
||||
4. Si el resultado no es `0 0`, sincronizar antes de continuar.
|
||||
5. Despues de cada lote validado:
|
||||
## 5) Flujo obligatorio por sesion
|
||||
1. Verificar rama activa:
|
||||
- `git branch --show-current`
|
||||
2. Actualizar referencias remotas:
|
||||
- `git fetch origin --prune`
|
||||
3. Verificar divergencia:
|
||||
- `git rev-list --left-right --count ai/ub24-builder-v1...origin/ai/ub24-builder-v1`
|
||||
4. Si no es `0 0`, sincronizar antes de editar.
|
||||
5. Al cerrar lote validado:
|
||||
- `git add <archivos>`
|
||||
- `git commit -m "tipo(scope): mensaje"`
|
||||
- `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.
|
||||
## 5.1) Metodo de ejecucion memorizado (obligatorio)
|
||||
1. Misma rama activa (`ai/ub24-builder-v1`), sin ramas paralelas para fixes rapidos.
|
||||
2. Control por hash/revert: si un intento falla, revert inmediato por commit.
|
||||
3. Un archivo por fix cuando sea posible (evitar mezclar cambios laterales).
|
||||
4. Validacion funcional inmediata despues de cada microcambio.
|
||||
5. No commit/push sin orden explicita del usuario.
|
||||
|
||||
## 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`
|
||||
- Objetivo: crear rama y politica de versionado para trabajo IA.
|
||||
- Revert:
|
||||
- `git revert <hash>`
|
||||
- `git reset --hard <hash_anterior>` (solo con aprobacion explicita)
|
||||
|
||||
### Correccion historial
|
||||
- Commit: `fe8657e`
|
||||
- Objetivo: revertir commit no deseado y mantener separacion de cambios.
|
||||
- Revert:
|
||||
- `git revert fe8657e`
|
||||
|
||||
### Fix local Elementor
|
||||
- Commit: `22e564e`
|
||||
- Objetivo: robustecer arranque local y carga de themes en Windows (BOM + logs seguros).
|
||||
- Revert:
|
||||
- `git revert 22e564e`
|
||||
|
||||
### Fase 1 Builder (visual pro)
|
||||
- Commit: `1c04f04`
|
||||
- Objetivo: consolidar estilos reutilizables y subir calidad visual en hero, features, cards y contact del preview.
|
||||
- Revert:
|
||||
- `git revert 1c04f04`
|
||||
|
||||
### Ajustes Builder (limpieza + preview + ancho)
|
||||
- Commit: `7c5f671`
|
||||
- Objetivo: quitar texto en barra Apple, limpiar menu vacio, preview local funcional sin salir de builder, ancho desktop al 100%, control de ancho por bloque y descripcion en bloque video.
|
||||
- Revert:
|
||||
- `git revert 7c5f671`
|
||||
|
||||
### Ajustes Builder (preview limpio + menu modos)
|
||||
- Commit: `dd98e9d`
|
||||
- Objetivo: mejorar vista previa (forzar modo limpio y restaurar estado), eliminar precarga automatica de bloques, y agregar modo de menu (horizontal/acordeon/ambos).
|
||||
- Revert:
|
||||
- `git revert dd98e9d`
|
||||
|
||||
### Runtime unificado (app + elementor)
|
||||
- Commit: `1a5778b`
|
||||
- Objetivo: unificar arranque con `python -m demo.app` y registrar blueprint de Elementor en runtime principal.
|
||||
- Revert:
|
||||
|
||||
### Educacion V2 + regla token
|
||||
- Commit: `8ac360b`
|
||||
- Objetivo: mejorar template de Educacion (V2), corregir navegacion por anclas en menu/CTA y fijar regla operativa para sesiones con pocos tokens.
|
||||
- Revert:
|
||||
- `git revert 8ac360b`
|
||||
- `git revert 1a5778b`
|
||||
|
||||
### Fix SQLite wrapper (arranque sin error SQL)
|
||||
- Commit: `f6d8ab1`
|
||||
- Objetivo: evitar conversiones SQL invalidas en SQLite que rompian inicializacion y generaban reintentos.
|
||||
- Revert:
|
||||
- `git revert f6d8ab1`
|
||||
|
||||
### API Elementor save/publish
|
||||
- Commit: `b6fb4da`
|
||||
- Objetivo: agregar endpoint dedicado `/api/elementor/save` para guardar builder con opcion de publicar.
|
||||
- Revert:
|
||||
- `git revert b6fb4da`
|
||||
|
||||
### Builder persistencia y feedback de publicacion
|
||||
- Commit: `c2ee81d`
|
||||
- Objetivo: mantener bloques cargados al entrar, normalizar bloques sin id y mostrar estado de guardado/publicacion en topbar.
|
||||
- Revert:
|
||||
- `git revert c2ee81d`
|
||||
|
||||
### Preview full-page + layout estable
|
||||
- Commit: `e20f086`
|
||||
- Objetivo: hacer que vista previa ocupe pagina completa y forzar layout por secciones (sin modo libre por defecto) para alinear bloques.
|
||||
- Revert:
|
||||
- `git revert e20f086`
|
||||
|
||||
### Full width + dos columnas por bloque
|
||||
- Commit: `e5df6de`
|
||||
- Objetivo: expandir ancho util del canvas y habilitar 2 columnas reales con toggle "Ancho completo" por bloque.
|
||||
- Revert:
|
||||
- `git revert e5df6de`
|
||||
|
||||
### Drag inteligente columnas + preview completo
|
||||
- Commit: `a6089ee`
|
||||
- Objetivo: permitir decidir 1 o 2 columnas moviendo bloques al soltar (centro=ancho completo, lados=media columna) y agregar opcion de preview completo en nueva pestana.
|
||||
- Revert:
|
||||
- `git revert a6089ee`
|
||||
|
||||
### Quitar layout global + preview completo real
|
||||
- Commit: `f9f7d23`
|
||||
- Objetivo: eliminar toggle global de 2 columnas; mantener decision 1/2 columnas solo por movimiento de bloques; y forzar preview completo sin margenes.
|
||||
- Revert:
|
||||
- `git revert f9f7d23`
|
||||
|
||||
### Fix final dia: texto, modo libre y full preview
|
||||
- Commit: `f363eef`
|
||||
- Objetivo: reparar caracteres mojibake en UI, habilitar modo libre real para mover bloques completos y forzar modo completo con `?full=1`.
|
||||
- Revert:
|
||||
- `git revert f363eef`
|
||||
|
||||
## URL local canonica (unificada)
|
||||
- Base local: `http://127.0.0.1:5001`
|
||||
- Builder local: `http://127.0.0.1:5001/elementor/1`
|
||||
- Regla: usar siempre `127.0.0.1` (no `localhost`) en scripts, pruebas y documentacion local.
|
||||
|
||||
## Arranque rapido local (Windows)
|
||||
1. Desde `c:\word`, ejecutar:
|
||||
- `python -m demo.app`
|
||||
2. Abrir:
|
||||
- `http://127.0.0.1:5001/elementor/1`
|
||||
3. Verificacion rapida:
|
||||
- `Invoke-WebRequest http://127.0.0.1:5001/elementor/1 -UseBasicParsing`
|
||||
|
||||
Notas:
|
||||
- En el primer arranque puede tardar unos segundos adicionales por inicializacion de DB.
|
||||
- Logs:
|
||||
- `c:\word\logs_demo_app.txt`
|
||||
- `c:\word\logs_demo_app.err`
|
||||
|
||||
## Control de rama (local/remoto)
|
||||
- Rama local activa: `ai/ub24-builder-v1`
|
||||
- Upstream remoto: `origin/ai/ub24-builder-v1`
|
||||
- Estado al registrar: `en sync (0/0)` al 14 Febrero 2026
|
||||
- Politica: commits atomicos + push por lote validado + verificacion de divergencia al inicio y al cierre.
|
||||
|
||||
## Fases memorizadas (builder)
|
||||
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:
|
||||
## 6) Protocolo de arranque local
|
||||
- Comando canonico:
|
||||
- `python -m demo.app`
|
||||
- Verificar puerto antes de editar:
|
||||
- URL canonica:
|
||||
- `http://127.0.0.1:5001`
|
||||
- Builder:
|
||||
- `http://127.0.0.1:5001/elementor/1`
|
||||
- Verificacion rapida:
|
||||
- `Invoke-WebRequest http://127.0.0.1:5001/elementor/1 -UseBasicParsing`
|
||||
|
||||
## 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.
|
||||
## 7) Criterios de estabilidad (no negociables)
|
||||
1. No tocar funcionalidades estables sin requerimiento explicito.
|
||||
2. Mantener una sola estrategia de layout y DnD por flujo.
|
||||
3. Mantener una sola ruta de guardado activa:
|
||||
- `/api/elementor/save`
|
||||
4. Validar guardado/publicacion antes de cerrar lote.
|
||||
5. Evitar cambios amplios si un fix local resuelve el problema.
|
||||
|
||||
## 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`
|
||||
## 8) Registro de commits relevantes
|
||||
| Fecha | Commit | Tipo | Objetivo | Revert recomendado |
|
||||
|---|---|---|---|---|
|
||||
| 2026-02-21 | `e83e915` | fix | Estabilizar Drawer Pro, reset real de builder y restaurar tema restaurante editable + ancho | `git revert e83e915` |
|
||||
| 2026-02-14 | `cb99f26` | chore | Crear rama y politica inicial de versionado IA | `git revert cb99f26` |
|
||||
| 2026-02-14 | `fe8657e` | fix | Revertir commit no deseado y limpiar historial | `git revert fe8657e` |
|
||||
| 2026-02-14 | `22e564e` | fix | Robustecer arranque local/theme load en Windows | `git revert 22e564e` |
|
||||
| 2026-02-14 | `1c04f04` | feat | Mejora visual base (hero/features/cards/contact) | `git revert 1c04f04` |
|
||||
| 2026-02-14 | `7c5f671` | fix | Limpieza preview/menu y ajustes de ancho | `git revert 7c5f671` |
|
||||
| 2026-02-14 | `dd98e9d` | fix | Preview limpio y menu por modos | `git revert dd98e9d` |
|
||||
| 2026-02-15 | `1a5778b` | refactor | Runtime unificado `python -m demo.app` + blueprint Elementor | `git revert 1a5778b` |
|
||||
| 2026-02-15 | `8ac360b` | feat | Educacion V2 + correccion de anclas | `git revert 8ac360b` |
|
||||
| 2026-02-15 | `f6d8ab1` | fix | Corregir wrapper SQLite para evitar fallo de arranque | `git revert f6d8ab1` |
|
||||
| 2026-02-15 | `b6fb4da` | feat | API dedicada `/api/elementor/save` con opcion publicar | `git revert b6fb4da` |
|
||||
| 2026-02-15 | `c2ee81d` | fix | Persistencia de bloques + feedback de publish | `git revert c2ee81d` |
|
||||
| 2026-02-15 | `e20f086` | fix | Preview full-page y layout estable por secciones | `git revert e20f086` |
|
||||
| 2026-02-15 | `e5df6de` | feat | Full width + soporte real de 2 columnas | `git revert e5df6de` |
|
||||
| 2026-02-15 | `a6089ee` | feat | Drag inteligente por drop + preview en nueva pestana | `git revert a6089ee` |
|
||||
| 2026-02-15 | `f9f7d23` | fix | Quitar layout global y forzar preview completo | `git revert f9f7d23` |
|
||||
| 2026-02-15 | `f363eef` | fix | Correccion de texto/UI + modo libre + `?full=1` | `git revert f363eef` |
|
||||
| 2026-02-16 | `6f14308` | fix | Unificar layout y estabilizar redimension en canvas | `git revert 6f14308` |
|
||||
|
||||
### 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:
|
||||
## 9) Decisiones funcionales vigentes
|
||||
- Rubros oficiales permitidos:
|
||||
- `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:
|
||||
- `base_otro`
|
||||
- Preview final separada habilitada:
|
||||
- `GET /elementor/<site_id>/preview-final`
|
||||
- `GET /ub24/<site_id>/preview-final`
|
||||
- En preview final:
|
||||
- se fuerza `preview-mode` desde servidor (`preview_only=true`),
|
||||
- boton `Atras` vuelve al builder base.
|
||||
- Motor de reordenamiento seleccionado para DnD:
|
||||
- `SortableJS` (estrategia unica)
|
||||
|
||||
- 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.
|
||||
## 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.
|
||||
|
||||
## 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`
|
||||
## 11) Referencias cruzadas
|
||||
- Historial funcional detallado: `codex/HISTORIAL_CAMBIOS.md`
|
||||
- Arranque rapido local: `codex/ARRANQUE_RAPIDO_UB24.md`
|
||||
- Flujo general del proyecto: `codex/FLUJO_PROYECTO.md`
|
||||
|
||||
### 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:<puerto>;` 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.
|
||||
## 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.
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
.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 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-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,#ffffff)}
|
||||
.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}
|
||||
@@ -148,9 +148,10 @@
|
||||
.menu-inline{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||||
.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-overlay{position:fixed;inset:0;background:rgba(8,11,16,.5);z-index:1500;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .2s ease,visibility .2s ease}
|
||||
.menu-drawer{position:fixed;top:0;right:0;height:100vh;width:min(92vw,360px);background:#1f1f1f;color:#f5f5f5;border-left:1px solid #2d2d2d;z-index:1510;box-shadow:-14px 0 30px rgba(0,0,0,.25);transform:translateX(100%);opacity:0;visibility:hidden;pointer-events:none;transition:transform .24s ease,opacity .2s ease,visibility .2s ease}
|
||||
.menu-drawer.open,.menu-drawer-overlay.open{opacity:1;visibility:visible;pointer-events:auto}
|
||||
.menu-drawer.open{transform:translateX(0)}
|
||||
.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}
|
||||
@@ -211,7 +212,31 @@
|
||||
.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}
|
||||
.site-global-footer{margin-top:26px;padding:16px 18px;border-radius:12px;border:1px solid #dbe3ee;background:var(--site-footer-bg,#ffffff);color:var(--site-muted);font-size:12px;line-height:1.4;text-align:center;width:100%;grid-column:1 / -1;justify-self:stretch}
|
||||
body.menu-drawer-open{overflow:hidden;touch-action:none}
|
||||
.canvas-bg-overlay{position:absolute;inset:0;z-index:0;pointer-events:none}
|
||||
.restaurant-site .canvas-bg-overlay{background:linear-gradient(180deg,rgba(10,12,16,.12) 0%,rgba(10,12,16,.06) 40%,rgba(10,12,16,0) 100%);backdrop-filter:blur(1px)}
|
||||
.restaurant-site{padding:28px;background:linear-gradient(180deg,var(--restaurant-bg-1,#f8fafc) 0%,var(--restaurant-bg-2,#f1f5f9) 100%)}
|
||||
.restaurant-site .block{transform:none}
|
||||
.restaurant-site .block:hover{transform:none}
|
||||
.restaurant-site .block[data-block-type="menu"]{background:var(--restaurant-surface,#fff);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:16px;box-shadow:0 8px 24px rgba(15,23,42,.06)}
|
||||
.restaurant-site .site-nav{border-color:var(--restaurant-border,#dbe3ee);background:var(--restaurant-surface,#fff)}
|
||||
.restaurant-site .site-brand{font-size:clamp(17px,1.9vw,24px);font-family:var(--site-font-heading,Playfair Display),serif}
|
||||
.restaurant-site .site-nav-link{font-size:14px}
|
||||
.restaurant-site .block[data-block-type="hero"]{background:linear-gradient(160deg,var(--restaurant-surface,#fff) 0%,var(--restaurant-surface-soft,#f8fafc) 100%);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:20px;padding:26px;box-shadow:0 12px 28px rgba(15,23,42,.07)}
|
||||
.restaurant-site .hero-kicker{letter-spacing:2.2px}
|
||||
.restaurant-site .hero-pro h2.editable{font-size:clamp(40px,5.3vw,72px);line-height:1.02;max-width:13ch}
|
||||
.restaurant-site .hero-pro p.editable{font-size:clamp(16px,1.75vw,21px);line-height:1.58;max-width:54ch}
|
||||
.restaurant-site .hero-media{border-color:var(--restaurant-border,#dbe3ee);border-radius:18px;min-height:360px}
|
||||
.restaurant-site .block[data-block-type="gallery"]{background:var(--restaurant-surface,#fff);border:1px dashed var(--restaurant-border,#dbe3ee);border-radius:20px;padding:22px;box-shadow:0 8px 20px rgba(15,23,42,.06)}
|
||||
.restaurant-site .block[data-block-type="gallery"] .gallery-slot{border:1px solid var(--restaurant-border,#dbe3ee)}
|
||||
.restaurant-site .block[data-block-type="cards"]{background:var(--restaurant-surface,#fff);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:20px;padding:22px;box-shadow:0 8px 20px rgba(15,23,42,.06)}
|
||||
.restaurant-site .block[data-block-type="cards"] .card-pro{background:var(--restaurant-surface-soft,#f8fafc);border:1px solid var(--restaurant-border,#dbe3ee);box-shadow:none}
|
||||
.restaurant-site .block[data-block-type="review"]{background:var(--restaurant-surface,#fff);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:20px;padding:20px;box-shadow:0 8px 20px rgba(15,23,42,.06)}
|
||||
.restaurant-site .block[data-block-type="review"] h3{color:var(--site-text)}
|
||||
.restaurant-site .block[data-block-type="contact"]{background:var(--restaurant-surface,#fff);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:20px;padding:22px;box-shadow:0 8px 20px rgba(15,23,42,.06)}
|
||||
.restaurant-site .block[data-block-type="map"]{background:var(--restaurant-surface,#fff);border:1px solid var(--restaurant-border,#dbe3ee);border-radius:20px;padding:20px;box-shadow:0 8px 20px rgba(15,23,42,.06)}
|
||||
.restaurant-site .site-global-footer{background:var(--restaurant-surface,#fff);border-color:var(--restaurant-border,#dbe3ee)}
|
||||
.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){
|
||||
@@ -532,15 +557,15 @@
|
||||
]
|
||||
},
|
||||
restaurante: {
|
||||
settings: { primary_color: '#ef4444', bg_color: '#fff7ed', text_color: '#0b0c10', muted_color: '#6b7280', font_body: 'Poppins', font_heading: 'Playfair Display', bg_gradient: false },
|
||||
settings: { primary_color: '#ef4444', bg_color: '#fff7ed', text_color: '#0b0c10', muted_color: '#6b7280', font_body: 'IBM Plex Sans', font_heading: 'Playfair Display', bg_gradient: false },
|
||||
blocks: [
|
||||
{ id: makeId(), type: 'menu', data: defaultData('menu') },
|
||||
{ id: makeId(), type: 'hero', data: { title: 'Sabores que enamoran', subtitle: 'Cocina artesanal, ambiente unico y atencion cercana.', button_text: 'Reservar', button_url: '#contacto', image_url: '' } },
|
||||
{ id: makeId(), type: 'gallery', data: { title: 'Platos destacados', images: ['','',''], captions: ['','',''], fit: 'cover' } },
|
||||
{ id: makeId(), type: 'cards', data: { title: 'Especialidades', items: ['Entradas|Frescas y ligeras','Platos fuertes|Hechos al momento','Postres|Dulce final'] } },
|
||||
{ id: makeId(), type: 'review', data: { title: 'Rese?as', name: 'Cliente feliz', text: 'Excelente sabor y servicio impecable.', rating: 5, style: 'card' } },
|
||||
{ id: makeId(), type: 'contact', data: { title: 'Reservas', email: '', phone: '', address: '' } },
|
||||
{ id: makeId(), type: 'map', data: { title: 'Ubicacion', address: '' } }
|
||||
{ id: makeId(), type: 'menu', data: { ...defaultData('menu'), width: 100 } },
|
||||
{ id: makeId(), type: 'hero', data: { title: 'Sabores que enamoran', subtitle: 'Cocina artesanal, ambiente unico y atencion cercana.', kicker: 'Restaurante', button_text: 'Reservar', button_url: '#contacto', button_secondary_text: 'Ver menu', button_secondary_url: '#menu', image_url: '', align: 'left', width: 100 } },
|
||||
{ id: makeId(), type: 'gallery', data: { title: 'Platos destacados', images: ['','',''], captions: ['','',''], fit: 'cover', width: 100 } },
|
||||
{ id: makeId(), type: 'cards', data: { title: 'Especialidades', items: ['Entradas|Frescas y ligeras','Platos fuertes|Hechos al momento','Postres|Dulce final'], columns: 3, width: 100 } },
|
||||
{ id: makeId(), type: 'review', data: { title: 'Rese?as', name: 'Cliente feliz', text: 'Excelente sabor y servicio impecable.', rating: 5, style: 'card', width: 100 } },
|
||||
{ id: makeId(), type: 'contact', data: { title: 'Reservas', email: '', phone: '', address: '', cta_text: 'Reservar por WhatsApp', cta_url: '#', width: 100 } },
|
||||
{ id: makeId(), type: 'map', data: { title: 'Ubicacion', address: '', embed_url: '', height: 320, width: 100 } }
|
||||
]
|
||||
},
|
||||
cosmeticos: {
|
||||
@@ -615,6 +640,8 @@ const state = {
|
||||
let manualDrag = { active: false, id: null, index: null };
|
||||
let pointerDrag = { active: false, id: null, index: null };
|
||||
let canvasSortable = null;
|
||||
const menuDrawerState = { activeDrawerId: null, lastFocused: null };
|
||||
let drawerGlobalEventsBound = false;
|
||||
|
||||
function hasSortable(){ return typeof Sortable !== "undefined"; }
|
||||
function isBlockVisibleInCanvas(block){
|
||||
@@ -639,15 +666,15 @@ const state = {
|
||||
function defaultData(type){
|
||||
switch(type){
|
||||
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 "hero": return { title:"Tu propuesta de valor", subtitle:"Explica en una frase por que elegirte.", kicker:"", button_text:"Contactar", button_url:"#contacto", button_secondary_text:"Ver mas", button_secondary_url:"#", image_url:"", align:"left", 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:"Propuesta", 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"], columns:3, 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 };
|
||||
case "contact": return { title:"Hablemos", email:"", phone:"", address:"", cta_text:"Reservar ahora", cta_url:"#", width:100 };
|
||||
case "map": return { title:"Ubicacion", address:"", embed_url:"", height:320, width:100 };
|
||||
case "button": return { text:"Accion", url:"#", style:"primary", size:"md", width:100};
|
||||
case "social": return { instagram:"", facebook:"", whatsapp:"", tiktok:"", youtube:"", icon_size:18, icon_color:"#0b0c10", show_text:true, icon_style:"pill", width:100 };
|
||||
case "video": return { url:"", description:"", width:100 };
|
||||
@@ -756,6 +783,106 @@ const state = {
|
||||
if (u.includes("youtu.be/")){ const id=u.split("youtu.be/")[1].split(/[?&]/)[0]; return "https://www.youtube.com/embed/"+id; }
|
||||
return u;
|
||||
}
|
||||
function isMobilePreviewContext(){
|
||||
const shell = document.querySelector(".preview-shell");
|
||||
const forced = !!(shell && (shell.classList.contains("size-phone") || shell.classList.contains("size-tablet")));
|
||||
return forced || window.innerWidth <= 980;
|
||||
}
|
||||
function getDrawerNodes(drawerId){
|
||||
if (!drawerId) return { panel: null, overlay: null, toggle: null };
|
||||
return {
|
||||
panel: document.getElementById(drawerId),
|
||||
overlay: document.querySelector(`[data-drawer-overlay="${drawerId}"]`),
|
||||
toggle: document.querySelector(`[data-drawer-toggle="${drawerId}"]`)
|
||||
};
|
||||
}
|
||||
function closeMenuDrawer(drawerId, opts = {}){
|
||||
const { restoreFocus = true } = opts;
|
||||
if (!drawerId) return;
|
||||
const { panel, overlay, toggle } = getDrawerNodes(drawerId);
|
||||
if (panel){
|
||||
panel.classList.remove("open");
|
||||
panel.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
if (overlay){
|
||||
overlay.classList.remove("open");
|
||||
overlay.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
if (toggle){ toggle.setAttribute("aria-expanded", "false"); }
|
||||
document.body.classList.remove("menu-drawer-open");
|
||||
if (menuDrawerState.activeDrawerId === drawerId){
|
||||
menuDrawerState.activeDrawerId = null;
|
||||
const target = (restoreFocus && menuDrawerState.lastFocused && menuDrawerState.lastFocused.isConnected)
|
||||
? menuDrawerState.lastFocused
|
||||
: toggle;
|
||||
if (restoreFocus && target && typeof target.focus === "function"){ target.focus(); }
|
||||
}
|
||||
}
|
||||
function closeAllMenuDrawers(opts = {}){
|
||||
document.querySelectorAll(".menu-drawer.open").forEach((panel)=>{
|
||||
closeMenuDrawer(panel.id, opts);
|
||||
});
|
||||
document.querySelectorAll(".menu-drawer-overlay.open").forEach((overlay)=>{
|
||||
overlay.classList.remove("open");
|
||||
overlay.setAttribute("aria-hidden", "true");
|
||||
});
|
||||
document.body.classList.remove("menu-drawer-open");
|
||||
menuDrawerState.activeDrawerId = null;
|
||||
}
|
||||
function openMenuDrawer(drawerId, trigger){
|
||||
if (!drawerId || !isMobilePreviewContext()) return;
|
||||
if (menuDrawerState.activeDrawerId && menuDrawerState.activeDrawerId !== drawerId){
|
||||
closeMenuDrawer(menuDrawerState.activeDrawerId, { restoreFocus: false });
|
||||
}
|
||||
const { panel, overlay, toggle } = getDrawerNodes(drawerId);
|
||||
if (!panel || !overlay) return;
|
||||
menuDrawerState.lastFocused = trigger || document.activeElement;
|
||||
panel.classList.add("open");
|
||||
panel.setAttribute("aria-hidden", "false");
|
||||
overlay.classList.add("open");
|
||||
overlay.setAttribute("aria-hidden", "false");
|
||||
if (toggle){ toggle.setAttribute("aria-expanded", "true"); }
|
||||
menuDrawerState.activeDrawerId = drawerId;
|
||||
document.body.classList.add("menu-drawer-open");
|
||||
const focusTarget = panel.querySelector("[data-drawer-close], a, button");
|
||||
if (focusTarget && typeof focusTarget.focus === "function"){ focusTarget.focus(); }
|
||||
}
|
||||
function syncActiveDrawerForViewport(){
|
||||
if (!menuDrawerState.activeDrawerId) return;
|
||||
if (!isMobilePreviewContext()){
|
||||
closeMenuDrawer(menuDrawerState.activeDrawerId, { restoreFocus: false });
|
||||
}
|
||||
}
|
||||
function bindDrawerGlobalEvents(){
|
||||
if (drawerGlobalEventsBound) return;
|
||||
drawerGlobalEventsBound = true;
|
||||
document.addEventListener("keydown",(e)=>{
|
||||
if (!menuDrawerState.activeDrawerId) return;
|
||||
if (e.key === "Escape"){
|
||||
e.preventDefault();
|
||||
closeMenuDrawer(menuDrawerState.activeDrawerId);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Tab"){
|
||||
const { panel } = getDrawerNodes(menuDrawerState.activeDrawerId);
|
||||
if (!panel) return;
|
||||
const focusables = [...panel.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])')]
|
||||
.filter((node)=>node && node.offsetParent !== null);
|
||||
if (!focusables.length) return;
|
||||
const first = focusables[0];
|
||||
const last = focusables[focusables.length - 1];
|
||||
const active = document.activeElement;
|
||||
if (e.shiftKey && active === first){
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && active === last){
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
window.addEventListener("resize", syncActiveDrawerForViewport);
|
||||
}
|
||||
function buildCalendarHtml(){
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -987,14 +1114,18 @@ const state = {
|
||||
</div>`;
|
||||
}
|
||||
const image = block.data.image_url ? `<img src="${escapeHtml(block.data.image_url)}" alt="Hero image">` : `<div class="hero-media-empty">Agrega imagen en "Imagen URL" para completar el hero.</div>`;
|
||||
const align = (block.data.align || "left").toLowerCase() === "center" ? "center" : "left";
|
||||
const kicker = block.data.kicker || state.settings.site_name || "GKACHELE";
|
||||
const secondaryText = block.data.button_secondary_text || "Ver servicios";
|
||||
const secondaryUrl = block.data.button_secondary_url || "#servicios";
|
||||
return `<div class="hero-pro hero-layout">
|
||||
<div class="hero-copy">
|
||||
<div class="hero-kicker">${escapeHtml(state.settings.site_name||"GKACHELE")}</div>
|
||||
<div class="hero-copy" style="text-align:${align}">
|
||||
<div class="hero-kicker">${escapeHtml(kicker)}</div>
|
||||
${editable("h2","title",block.data.title,"Titulo",false,"")}
|
||||
${editable("p","subtitle",block.data.subtitle,"Subtitulo",true,"")}
|
||||
<div class="hero-actions">
|
||||
<a href="${escapeHtml(block.data.button_url||"#")}" class="editable hero-cta" data-field="button_text" data-placeholder="Boton" contenteditable="true">${escapeHtml(block.data.button_text)}</a>
|
||||
<a href="#servicios" class="hero-cta-secondary">Ver servicios</a>
|
||||
<a href="${escapeHtml(secondaryUrl)}" class="hero-cta-secondary">${escapeHtml(secondaryText)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-media">${image}</div>
|
||||
@@ -1028,10 +1159,11 @@ const state = {
|
||||
}
|
||||
if (block.type==="cards"){
|
||||
const items = Array.isArray(block.data.items)?block.data.items:[];
|
||||
const cols = Math.max(2, Math.min(4, Number(block.data.columns || 3)));
|
||||
if (isEducationRubro()){
|
||||
return `${editable("h3","title",block.data.title,"Titulo",false,"font-size:30px;margin:0 0 14px")}<div class="edu-cards-grid">${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `<div class="edu-card"><div class="editable card-pro-title" data-field="items.${idx}.title" data-placeholder="Titulo" contenteditable="true">${escapeHtml(t)}</div><div class="editable card-pro-desc" data-field="items.${idx}.desc" data-placeholder="Descripcion" contenteditable="true">${escapeHtml(d)}</div><a href="#contacto" style="display:inline-flex;margin-top:10px;color:#0a4dcf;font-weight:700;text-decoration:none">Ver plan de estudios</a></div>`;}).join("")}</div>`;
|
||||
}
|
||||
return `${editable("h3","title",block.data.title,"Titulo",false,"")}<div class="cards-grid">${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `<div class="card-pro"><div class="editable card-pro-title" data-field="items.${idx}.title" data-placeholder="Titulo" contenteditable="true">${escapeHtml(t)}</div><div class="editable card-pro-desc" data-field="items.${idx}.desc" data-placeholder="Descripcion" contenteditable="true">${escapeHtml(d)}</div></div>`;}).join("")}</div>`;
|
||||
return `${editable("h3","title",block.data.title,"Titulo",false,"")}<div class="cards-grid" style="grid-template-columns:repeat(${cols}, minmax(0,1fr))">${items.map((raw,idx)=>{const parts=String(raw).split("|");const t=parts[0]||"";const d=parts[1]||"";return `<div class="card-pro"><div class="editable card-pro-title" data-field="items.${idx}.title" data-placeholder="Titulo" contenteditable="true">${escapeHtml(t)}</div><div class="editable card-pro-desc" data-field="items.${idx}.desc" data-placeholder="Descripcion" contenteditable="true">${escapeHtml(d)}</div></div>`;}).join("")}</div>`;
|
||||
}
|
||||
if (block.type==="iconlist"){
|
||||
const items = Array.isArray(block.data.items)?block.data.items:[];
|
||||
@@ -1044,6 +1176,8 @@ const state = {
|
||||
const emailVal = escapeHtml(block.data.email || "");
|
||||
const phoneVal = escapeHtml(block.data.phone || "");
|
||||
const addressVal = escapeHtml(block.data.address || "");
|
||||
const ctaText = escapeHtml(block.data.cta_text || "Reservar ahora");
|
||||
const ctaUrl = escapeHtml(block.data.cta_url || "#");
|
||||
const email = emailVal ? `<a href="mailto:${emailVal}">${emailVal}</a>` : "Sin correo configurado";
|
||||
const phone = phoneVal ? `<a href="tel:${phoneVal}">${phoneVal}</a>` : "Sin telefono configurado";
|
||||
const address = addressVal || "Sin direccion configurada";
|
||||
@@ -1083,6 +1217,7 @@ const state = {
|
||||
<input placeholder="Email de contacto">
|
||||
<textarea placeholder="Cuéntanos brevemente en qué te ayudamos"></textarea>
|
||||
<button class="contact-send"><i class="fa-solid fa-paper-plane"></i>Enviar consulta</button>
|
||||
<a href="${ctaUrl}" class="hero-cta" style="margin-top:6px">${ctaText}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1127,9 +1262,11 @@ const state = {
|
||||
return `<h3 style="margin:0 0 10px">Redes</h3><div class="social-icons social-style-${escapeHtml(style)}">${items.map(([k,v],idx)=>`<a class="social-btn" href="${escapeHtml(linkFor(k,v)||"#")}" target="_blank" rel="noreferrer" style="font-size:${size}px"><i class="${icons[k]||'fa-solid fa-circle'}" style="font-size:${size}px;color:${escapeHtml(iconColor)}"></i>${showText ? `<span class="editable" data-field="${escapeHtml(k)}" data-placeholder="${escapeHtml(k)}" contenteditable="true" style="font-size:12px">${escapeHtml(v)}</span>` : ""}</a>`).join("")}</div>`;
|
||||
}
|
||||
if (block.type==="map"){
|
||||
const h = Math.max(220, Math.min(700, Number(block.data.height || 320)));
|
||||
const embed = normalizeLink(block.data.embed_url || "");
|
||||
const q = encodeURIComponent(block.data.address || "");
|
||||
const src = q ? `https://www.google.com/maps?q=${q}&z=15&output=embed` : "";
|
||||
return `<h3 style="margin:0 0 10px">${escapeHtml(block.data.title||"Ubicacion")}</h3>` + (src ? `<iframe title="Mapa" src="${src}" loading="lazy" referrerpolicy="no-referrer-when-downgrade" style="width:100%;height:260px;border:0;border-radius:12px"></iframe><div style="margin-top:6px;color:var(--site-muted);font-size:12px">${escapeHtml(block.data.address||"")}</div>` : `<div style="background:#e2e8f0;border-radius:12px;padding:20px;text-align:center;color:var(--site-muted)">Ingresa una direccion</div>`);
|
||||
const src = embed || (q ? `https://www.google.com/maps?q=${q}&z=15&output=embed` : "");
|
||||
return `<h3 style="margin:0 0 10px">${escapeHtml(block.data.title||"Ubicacion")}</h3>` + (src ? `<iframe title="Mapa" src="${src}" loading="lazy" referrerpolicy="no-referrer-when-downgrade" style="width:100%;height:${h}px;border:0;border-radius:12px"></iframe><div style="margin-top:6px;color:var(--site-muted);font-size:12px">${escapeHtml(block.data.address||"")}</div>` : `<div style="background:#e2e8f0;border-radius:12px;padding:20px;text-align:center;color:var(--site-muted)">Ingresa una direccion o URL embed</div>`);
|
||||
}
|
||||
if (block.type==="video"){
|
||||
const embed = normalizeVideoUrl(block.data.url);
|
||||
@@ -1189,11 +1326,16 @@ const state = {
|
||||
shell.style.setProperty("--site-card", s.theme === "dark" ? "#0f172a" : "#ffffff");
|
||||
shell.style.setProperty("--site-font-body", s.font_body || "Manrope");
|
||||
shell.style.setProperty("--site-font-heading", s.font_heading || "Manrope");
|
||||
shell.style.setProperty("--restaurant-surface", s.theme === "dark" ? "#111827" : "#ffffff");
|
||||
shell.style.setProperty("--restaurant-surface-soft", s.theme === "dark" ? "#0f172a" : "#f8fafc");
|
||||
shell.style.setProperty("--restaurant-border", s.theme === "dark" ? "#263043" : "#dbe3ee");
|
||||
shell.style.setProperty("--restaurant-bg-1", s.bg_color || "#f8fafc");
|
||||
shell.style.setProperty("--restaurant-bg-2", s.bg_gradient ? (s.bg_color2 || "#eef2f7") : (s.bg_color || "#f8fafc"));
|
||||
}
|
||||
|
||||
function renderPreview(){
|
||||
const canvas = document.getElementById("previewCanvas");
|
||||
document.body.style.overflow = "";
|
||||
closeAllMenuDrawers({ restoreFocus: false });
|
||||
document.querySelectorAll(".menu-drawer-global").forEach((n)=>n.remove());
|
||||
if (canvasSortable){
|
||||
canvasSortable.destroy();
|
||||
@@ -1202,6 +1344,7 @@ const state = {
|
||||
canvas.innerHTML = "";
|
||||
canvas.classList.toggle("free-drag", !!state.settings.free_drag);
|
||||
canvas.classList.toggle("education-site", isEducationRubro());
|
||||
canvas.classList.toggle("restaurant-site", normalizeRubro(state.settings.business_rubro || SERVER_RUBRO || "") === "restaurante");
|
||||
applySiteTheme();
|
||||
if (state.settings.bg_anim_url){
|
||||
canvas.style.background = `url('${state.settings.bg_anim_url}') center/cover no-repeat`;
|
||||
@@ -1221,6 +1364,7 @@ const state = {
|
||||
}
|
||||
canvas.style.paddingBottom = `${Math.max(0, Number(state.settings.canvas_bottom_space || 0))}px`;
|
||||
canvas.style.position = "relative";
|
||||
canvas.style.overflow = "hidden";
|
||||
if (state.settings.bg_video_url){
|
||||
const video = document.createElement("video");
|
||||
video.src = state.settings.bg_video_url;
|
||||
@@ -1236,7 +1380,13 @@ const state = {
|
||||
canvas.style.position = "relative";
|
||||
canvas.appendChild(video);
|
||||
}
|
||||
if (canvas.classList.contains("restaurant-site")){
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "canvas-bg-overlay";
|
||||
canvas.appendChild(overlay);
|
||||
}
|
||||
const inner = document.createElement("div");
|
||||
inner.className = "canvas-inner";
|
||||
inner.style.position = "relative";
|
||||
inner.style.zIndex = "1";
|
||||
if (!state.settings.free_drag){
|
||||
@@ -1461,6 +1611,10 @@ const state = {
|
||||
}
|
||||
|
||||
function removeDrop(){ if (dropIndicator && dropIndicator.parentNode) dropIndicator.parentNode.removeChild(dropIndicator); dropIndicator=null; }
|
||||
function getCanvasContainer(canvas){
|
||||
if (!canvas) return null;
|
||||
return canvas.querySelector(".canvas-inner") || canvas;
|
||||
}
|
||||
function getDropIndex(container,y,x){
|
||||
const blocks=[...container.querySelectorAll(".block")]
|
||||
.filter((n)=>n.dataset.blockId !== draggingBlockId);
|
||||
@@ -1505,7 +1659,7 @@ const state = {
|
||||
function startPointerDrag(blockId, e){
|
||||
if (!blockId || state.settings.free_drag) return;
|
||||
const canvas = document.getElementById("previewCanvas");
|
||||
const container = canvas ? (canvas.querySelector("div") || canvas) : null;
|
||||
const container = getCanvasContainer(canvas);
|
||||
if (!container) return;
|
||||
pointerDrag = { active: true, id: blockId, index: null, split: null };
|
||||
draggingBlockId = blockId;
|
||||
@@ -1560,7 +1714,7 @@ const state = {
|
||||
function startManualDrag(blockId, e){
|
||||
if (!blockId || state.settings.free_drag) return;
|
||||
const canvas = document.getElementById("previewCanvas");
|
||||
const container = canvas ? (canvas.querySelector("div") || canvas) : null;
|
||||
const container = getCanvasContainer(canvas);
|
||||
if (!container) return;
|
||||
manualDrag = { active: true, id: blockId, index: null };
|
||||
draggingBlockId = blockId;
|
||||
@@ -1601,9 +1755,11 @@ const state = {
|
||||
function moveBlock(id,toIndex){
|
||||
const from=state.blocks.findIndex(b=>b.id===id);
|
||||
if (from<0) return;
|
||||
const clamped = Math.max(0, Math.min(state.blocks.length, Number(toIndex)));
|
||||
const numericTo = Number(toIndex);
|
||||
if (!Number.isFinite(numericTo)) return;
|
||||
const clamped = Math.max(0, Math.min(state.blocks.length - 1, numericTo));
|
||||
const [b]=state.blocks.splice(from,1);
|
||||
const target = clamped > from ? clamped - 1 : clamped;
|
||||
const target = Math.max(0, Math.min(state.blocks.length, clamped));
|
||||
state.blocks.splice(target,0,b);
|
||||
selectedBlockId = b.id;
|
||||
renderPreview();
|
||||
@@ -1637,10 +1793,15 @@ const state = {
|
||||
html+=`<div class="row"><label>Movil</label><select id="menuMobileStyle"><option value="accordion" ${mms==="accordion"?"selected":""}>Acordeon</option><option value="drawer" ${mms==="drawer"?"selected":""}>Drawer Pro</option></select></div>`;
|
||||
html+=`<div class="row"><label>Items menu (una linea)</label><textarea id="menuItems">${escapeHtml((data.items||[]).join("\n"))}</textarea></div>`;
|
||||
} else if (block.type==="hero"){
|
||||
html+=input("Kicker","heroKicker",data.kicker);
|
||||
html+=input("Titulo","heroTitle",data.title);
|
||||
html+=input("Subtitulo","heroSubtitle",data.subtitle);
|
||||
html+=input("Texto boton","heroBtnText",data.button_text);
|
||||
html+=input("URL boton","heroBtnUrl",data.button_url);
|
||||
html+=input("Texto boton secundario","heroBtn2Text",data.button_secondary_text);
|
||||
html+=input("URL boton secundario","heroBtn2Url",data.button_secondary_url);
|
||||
const alignVal = escapeHtml((data.align || "left").toLowerCase());
|
||||
html+=`<div class="row"><label>Alineacion</label><select id="heroAlign"><option value="left" ${alignVal==="left"?"selected":""}>Izquierda</option><option value="center" ${alignVal==="center"?"selected":""}>Centro</option></select></div>`;
|
||||
html+=input("Imagen URL","heroImage",data.image_url);
|
||||
} else if (block.type==="text"){
|
||||
html+=`<div class="row"><label>Texto</label><textarea id="textBlock">${escapeHtml(data.text||"")}</textarea></div>`;
|
||||
@@ -1669,6 +1830,7 @@ const state = {
|
||||
} else if (block.type==="cards"){
|
||||
html+=input("Titulo","cardsTitle",data.title);
|
||||
html+=`<div class="row"><label>Tarjetas (Titulo|Texto por linea)</label><textarea id="cardsItems">${escapeHtml((data.items||[]).join("\n"))}</textarea></div>`;
|
||||
html+=`<div class="row"><label>Columnas</label><input id="cardsColumns" type="number" min="2" max="4" step="1" value="${Math.max(2, Math.min(4, Number(data.columns || 3)))}"></div>`;
|
||||
} else if (block.type==="iconlist"){
|
||||
html+=input("Titulo","iconTitle",data.title);
|
||||
html+=`<div class="row"><label>Items (Titulo|Texto por linea)</label><textarea id="iconItems">${escapeHtml((data.items||[]).join("\n"))}</textarea></div>`;
|
||||
@@ -1677,6 +1839,8 @@ const state = {
|
||||
html+=input("Email","contactEmail",data.email);
|
||||
html+=input("Telefono","contactPhone",data.phone);
|
||||
html+=input("Direccion","contactAddress",data.address);
|
||||
html+=input("CTA texto","contactCtaText",data.cta_text);
|
||||
html+=input("CTA URL","contactCtaUrl",data.cta_url);
|
||||
} else if (block.type==="button"){
|
||||
html+=input("Texto","buttonText",data.text);
|
||||
html+=input("URL","buttonUrl",data.url);
|
||||
@@ -1699,6 +1863,8 @@ const state = {
|
||||
} else if (block.type==="map"){
|
||||
html+=input("Titulo","mapTitle",data.title);
|
||||
html+=input("Direccion","mapAddress",data.address);
|
||||
html+=input("URL embed (opcional)","mapEmbedUrl",data.embed_url);
|
||||
html+=`<div class="row"><label>Alto mapa (px)</label><input id="mapHeight" type="number" min="220" max="700" step="10" value="${Math.max(220, Math.min(700, Number(data.height || 320)))}"></div>`;
|
||||
} else if (block.type==="review"){
|
||||
html+=input("Titulo","reviewTitle",data.title);
|
||||
html+=input("Nombre","reviewName",data.name);
|
||||
@@ -1766,10 +1932,18 @@ const state = {
|
||||
if (mi){ block.data.items = mi.value.split("\n").map((x)=>x.trim()).filter(Boolean); }
|
||||
}
|
||||
else if (block.type==="hero"){
|
||||
const hk = document.getElementById("heroKicker");
|
||||
if (hk){ block.data.kicker = hk.value; }
|
||||
block.data.title=document.getElementById("heroTitle").value;
|
||||
block.data.subtitle=document.getElementById("heroSubtitle").value;
|
||||
block.data.button_text=document.getElementById("heroBtnText").value;
|
||||
block.data.button_url=document.getElementById("heroBtnUrl").value;
|
||||
const hb2 = document.getElementById("heroBtn2Text");
|
||||
if (hb2){ block.data.button_secondary_text = hb2.value; }
|
||||
const hu2 = document.getElementById("heroBtn2Url");
|
||||
if (hu2){ block.data.button_secondary_url = hu2.value; }
|
||||
const ha = document.getElementById("heroAlign");
|
||||
if (ha){ block.data.align = ha.value || "left"; }
|
||||
block.data.image_url=document.getElementById("heroImage").value;
|
||||
} else if (block.type==="text"){ block.data.text=document.getElementById("textBlock").value; }
|
||||
else if (block.type==="image"){
|
||||
@@ -1791,9 +1965,9 @@ const state = {
|
||||
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==="cards"){ block.data.title=document.getElementById("cardsTitle").value; block.data.items=document.getElementById("cardsItems").value.split("\n").filter(Boolean); const cc = document.getElementById("cardsColumns"); if (cc){ block.data.columns = Math.max(2, Math.min(4, Number(cc.value || 3))); } }
|
||||
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==="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; const ctt = document.getElementById("contactCtaText"); if (ctt){ block.data.cta_text = ctt.value; } const ctu = document.getElementById("contactCtaUrl"); if (ctu){ block.data.cta_url = ctu.value; } }
|
||||
else if (block.type==="button"){
|
||||
block.data.text=document.getElementById("buttonText").value;
|
||||
block.data.url=document.getElementById("buttonUrl").value;
|
||||
@@ -1821,6 +1995,10 @@ const state = {
|
||||
else if (block.type==="map"){
|
||||
block.data.title=document.getElementById("mapTitle").value;
|
||||
block.data.address=document.getElementById("mapAddress").value;
|
||||
const me = document.getElementById("mapEmbedUrl");
|
||||
if (me){ block.data.embed_url = me.value; }
|
||||
const mh = document.getElementById("mapHeight");
|
||||
if (mh){ block.data.height = Math.max(220, Math.min(700, Number(mh.value || 320))); }
|
||||
} else if (block.type==="review"){
|
||||
block.data.title=document.getElementById("reviewTitle").value;
|
||||
block.data.name=document.getElementById("reviewName").value;
|
||||
@@ -1888,6 +2066,7 @@ const state = {
|
||||
function wireSortableDnD(){ return; }
|
||||
function wireMenuAccordionInteractions(blockEl){
|
||||
if (!blockEl) return;
|
||||
bindDrawerGlobalEvents();
|
||||
blockEl.querySelectorAll(".menu-accordion, .menu-accordion summary, .menu-accordion a").forEach((node)=>{
|
||||
node.addEventListener("pointerdown",(e)=>{ e.stopPropagation(); });
|
||||
node.addEventListener("click",(e)=>{ e.stopPropagation(); });
|
||||
@@ -1897,50 +2076,42 @@ const state = {
|
||||
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(".menu-drawer").forEach((panel)=>{
|
||||
panel.setAttribute("role", "dialog");
|
||||
panel.setAttribute("aria-modal", "true");
|
||||
panel.setAttribute("aria-hidden", panel.classList.contains("open") ? "false" : "true");
|
||||
panel.setAttribute("tabindex", "-1");
|
||||
});
|
||||
blockEl.querySelectorAll(".menu-drawer-overlay").forEach((overlay)=>{
|
||||
overlay.setAttribute("aria-hidden", overlay.classList.contains("open") ? "false" : "true");
|
||||
});
|
||||
blockEl.querySelectorAll("[data-drawer-toggle]").forEach((btn)=>{
|
||||
btn.setAttribute("aria-haspopup", "dialog");
|
||||
btn.setAttribute("aria-expanded", "false");
|
||||
btn.addEventListener("click",(e)=>{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDrawer(btn.getAttribute("data-drawer-toggle"));
|
||||
openMenuDrawer(btn.getAttribute("data-drawer-toggle"), btn);
|
||||
});
|
||||
});
|
||||
blockEl.querySelectorAll("[data-drawer-close]").forEach((btn)=>{
|
||||
btn.addEventListener("click",(e)=>{
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeDrawer(btn.getAttribute("data-drawer-close"));
|
||||
closeMenuDrawer(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"));
|
||||
closeMenuDrawer(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);
|
||||
if (parent && parent.id) closeMenuDrawer(parent.id, { restoreFocus: false });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1974,7 +2145,8 @@ const state = {
|
||||
canvas.addEventListener("dragover",(e)=>{
|
||||
e.preventDefault();
|
||||
if (!state.settings.free_drag){
|
||||
const container=canvas.querySelector("div")||canvas;
|
||||
const container=getCanvasContainer(canvas);
|
||||
if (!container) return;
|
||||
const index=getDropIndex(container,e.clientY,e.clientX);
|
||||
showDrop(container,index);
|
||||
}
|
||||
@@ -1990,7 +2162,8 @@ const state = {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
b.pos = { x: Math.max(0, e.clientX - rect.left - 20), y: Math.max(0, e.clientY - rect.top - 20) };
|
||||
} else {
|
||||
const container=canvas.querySelector("div")||canvas;
|
||||
const container=getCanvasContainer(canvas);
|
||||
if (!container) return;
|
||||
const index=getDropIndex(container,e.clientY,e.clientX);
|
||||
state.blocks.splice(index,0,b);
|
||||
selectedBlockId=b.id;
|
||||
@@ -2305,6 +2478,7 @@ const state = {
|
||||
shell.style.maxWidth = w;
|
||||
setActive(btnId);
|
||||
setShellClass(btnId==="btnSizePhone" ? "size-phone" : btnId==="btnSizeTablet" ? "size-tablet" : "size-desktop");
|
||||
syncActiveDrawerForViewport();
|
||||
};
|
||||
document.getElementById("btnSizePhone").addEventListener("click",()=>setSize("520px","btnSizePhone"));
|
||||
document.getElementById("btnSizeTablet").addEventListener("click",()=>setSize("820px","btnSizeTablet"));
|
||||
@@ -2492,10 +2666,13 @@ const state = {
|
||||
if (isSaving) return;
|
||||
const ok = window.confirm("Esto borrara todos los bloques actuales. Deseas continuar?");
|
||||
if (!ok) return;
|
||||
const keepRubro = normalizeRubro(state.settings.business_rubro || SERVER_RUBRO || "restaurante");
|
||||
state.blocks = [];
|
||||
state.settings = { ...defaultSettings, business_rubro: keepRubro };
|
||||
selectedBlockId = null;
|
||||
renderInspector();
|
||||
renderPreview();
|
||||
wireSettings();
|
||||
setSaveStatus("Reseteando...", "busy");
|
||||
try{
|
||||
await saveDraftSilently();
|
||||
@@ -2548,7 +2725,10 @@ const state = {
|
||||
applyTemplate(templateSelect.value);
|
||||
});
|
||||
}
|
||||
if (!state.blocks.length && templates[initialRubro]){
|
||||
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;
|
||||
if (!state.blocks.length && templates[initialRubro] && shouldAutoloadTemplate){
|
||||
applyTemplate(initialRubro);
|
||||
if (templateSelect){ templateSelect.value = initialRubro; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user