feat: Add Dockerfile and initial Docker setup files
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
FROM python:3.11-slim
|
||||
# Usa una imagen oficial de Python como base
|
||||
FROM python:3.9-slim-buster
|
||||
|
||||
# Establece el directorio de trabajo dentro del contenedor
|
||||
WORKDIR /app
|
||||
|
||||
# Instalar dependencias del sistema
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copiar requirements e instalar
|
||||
COPY demo/requirements.txt .
|
||||
# Copia el archivo de requisitos e instálalos
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copiar el resto del código
|
||||
# Copia el resto de la aplicación al directorio de trabajo
|
||||
COPY . .
|
||||
|
||||
# Exponer el puerto
|
||||
EXPOSE 5000
|
||||
# Expone el puerto en el que corre la aplicación Flask (definido en config.py)
|
||||
EXPOSE 5001
|
||||
|
||||
# Comando para arrancar
|
||||
CMD ["python", "demo/app.py"]
|
||||
# Comando para correr la aplicación
|
||||
# Asegúrate de que app.py esté en el directorio raíz de WORKDIR (/app)
|
||||
# Y que las variables de entorno si son necesarias para SECRET_KEY y PORT se pasen al docker run o compose
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
BIN
demo/__pycache__/config.cpython-314.pyc
Normal file
BIN
demo/__pycache__/config.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/__pycache__/database.cpython-314.pyc
Normal file
BIN
demo/__pycache__/database.cpython-314.pyc
Normal file
Binary file not shown.
188
demo/customizer.html
Normal file
188
demo/customizer.html
Normal file
@@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>GKACHELE Customizer</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: system-ui, Arial; margin:0; background:#f0f0f1; }
|
||||
.wrap { display:flex; height:100vh; }
|
||||
.sidebar { width:340px; background:#fff; border-right:1px solid #ddd; padding:12px; overflow:auto }
|
||||
.preview { flex:1; display:flex; flex-direction:column }
|
||||
.preview-header { padding:12px; background:#fff; border-bottom:1px solid #ddd }
|
||||
.preview-body { padding:20px; overflow:auto; }
|
||||
.btn { padding:8px 12px; border-radius:4px; border:0; cursor:pointer }
|
||||
.btn-primary { background:#2271b1; color:#fff }
|
||||
.btn-secondary { background:#f0f0f1 }
|
||||
.block-item { padding:10px; border:1px solid #e5e5e5; margin-bottom:8px; border-radius:4px; display:flex; justify-content:space-between }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="sidebar">
|
||||
<h3>GKACHELE™ Customizer</h3>
|
||||
<div>
|
||||
<h4>Ajustes</h4>
|
||||
<label>Nombre sitio<br><input id="siteName" placeholder="Mi Sitio"></label>
|
||||
<label>Color primario<br><input type="color" id="colorPrimary" value="#2271b1"></label>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<h4>Añadir Bloque</h4>
|
||||
<button class="btn btn-primary" onclick="addBlock('heading')">Encabezado</button>
|
||||
<button class="btn btn-primary" onclick="addBlock('paragraph')">Párrafo</button>
|
||||
<button class="btn btn-primary" onclick="addBlock('image')">Imagen</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<h4>Bloques</h4>
|
||||
<div id="blocksList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview">
|
||||
<div class="preview-header">
|
||||
<span id="previewTitle">Vista previa</span>
|
||||
<div style="float:right">
|
||||
<button class="btn btn-secondary" onclick="discardChanges()">Descartar</button>
|
||||
<button class="btn btn-primary" onclick="saveChanges()">Guardar y Publicar</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div id="previewBlocks"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Simple customizer adapted to GKACHELE backend
|
||||
let state = {
|
||||
siteId: (new URLSearchParams(window.location.search)).get('site_id') || null,
|
||||
blocks: [],
|
||||
settings: { siteName:'Mi Sitio', colorPrimary:'#2271b1' },
|
||||
hasChanges: false,
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// populate from backend if editing a real site
|
||||
await loadFromBackendIfAvailable();
|
||||
// fallback: localStorage
|
||||
loadFromLocalStorage();
|
||||
renderUI();
|
||||
});
|
||||
|
||||
function loadFromLocalStorage(){
|
||||
if (!state.siteId) {
|
||||
const saved = localStorage.getItem('gkachele_customizer_demo');
|
||||
if (saved) {
|
||||
const d = JSON.parse(saved);
|
||||
state.blocks = d.blocks||state.blocks;
|
||||
state.settings = {...state.settings, ...(d.settings||{})};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFromBackendIfAvailable(){
|
||||
if (!state.siteId) return;
|
||||
try{
|
||||
const r = await fetch(`/api/customizer/get-content/${state.siteId}`);
|
||||
const j = await r.json();
|
||||
if (j && j.success) {
|
||||
const c = j.content || {};
|
||||
state.blocks = c.blocks || [];
|
||||
state.settings = {...state.settings, ...(c.settings||{})};
|
||||
state.hasChanges = false;
|
||||
updateSettingsUI();
|
||||
}
|
||||
}catch(e){ console.warn('Backend load failed', e); }
|
||||
}
|
||||
|
||||
function updateSettingsUI(){
|
||||
document.getElementById('siteName').value = state.settings.siteName||'';
|
||||
document.getElementById('colorPrimary').value = state.settings.colorPrimary||'#2271b1';
|
||||
}
|
||||
|
||||
function renderUI(){
|
||||
updateSettingsUI();
|
||||
renderBlocksList();
|
||||
renderPreview();
|
||||
}
|
||||
|
||||
function addBlock(type){
|
||||
const b = { id:'b_'+Date.now(), type:type, data: getDefault(type) };
|
||||
state.blocks.push(b);
|
||||
state.hasChanges = true;
|
||||
renderUI();
|
||||
}
|
||||
|
||||
function getDefault(type){
|
||||
if (type==='heading') return {text:'Título'};
|
||||
if (type==='paragraph') return {text:'Párrafo de ejemplo'};
|
||||
if (type==='image') return {url:'https://via.placeholder.com/600x300'};
|
||||
return {};
|
||||
}
|
||||
|
||||
function renderBlocksList(){
|
||||
const el = document.getElementById('blocksList');
|
||||
if (!state.blocks.length) { el.innerHTML='<div style="color:#888">No hay bloques</div>'; return; }
|
||||
el.innerHTML = state.blocks.map((b,i)=>`<div class="block-item"><div>${b.type}</div><div><button onclick="editBlock(${i})">✏️</button> <button onclick="deleteBlock(${i})">🗑️</button></div></div>`).join('');
|
||||
}
|
||||
|
||||
function renderPreview(){
|
||||
const el = document.getElementById('previewBlocks');
|
||||
if (!state.blocks.length) { el.innerHTML='<div style="color:#999">Añade bloques para ver la preview</div>'; return; }
|
||||
el.innerHTML = state.blocks.map(b=>renderBlockHtml(b)).join('');
|
||||
document.getElementById('previewTitle').textContent = state.settings.siteName || 'Vista previa';
|
||||
}
|
||||
|
||||
function renderBlockHtml(b){
|
||||
if (b.type==='heading') return `<h2>${escapeHtml(b.data.text)}</h2>`;
|
||||
if (b.type==='paragraph') return `<p>${escapeHtml(b.data.text)}</p>`;
|
||||
if (b.type==='image') return `<img src="${escapeHtml(b.data.url)}" style="max-width:100%;height:auto"/>`;
|
||||
return `<div>${b.type}</div>`;
|
||||
}
|
||||
|
||||
function editBlock(idx){
|
||||
const b = state.blocks[idx];
|
||||
const value = prompt('Editar contenido', b.type==='image'?b.data.url:b.data.text);
|
||||
if (value!==null){
|
||||
if (b.type==='image') b.data.url = value; else b.data.text = value;
|
||||
state.hasChanges = true; renderUI();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteBlock(idx){ if (confirm('Eliminar bloque?')) { state.blocks.splice(idx,1); state.hasChanges=true; renderUI(); } }
|
||||
|
||||
function escapeHtml(s){ if (!s) return ''; return s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); }
|
||||
|
||||
function discardChanges(){ if (!state.hasChanges){ alert('No hay cambios'); return;} if (confirm('Descartar cambios?')){ state.blocks=[]; state.settings={siteName:'Mi Sitio',colorPrimary:'#2271b1'}; localStorage.removeItem('gkachele_customizer_demo'); renderUI(); state.hasChanges=false;} }
|
||||
|
||||
function updateSaveIndicator(status){ console.log('save:',status); }
|
||||
|
||||
function saveChanges(){
|
||||
// collect settings from UI
|
||||
state.settings.siteName = document.getElementById('siteName').value;
|
||||
state.settings.colorPrimary = document.getElementById('colorPrimary').value;
|
||||
|
||||
const content = { blocks: state.blocks, settings: state.settings };
|
||||
|
||||
// save local demo
|
||||
if (!state.siteId) localStorage.setItem('gkachele_customizer_demo', JSON.stringify(content));
|
||||
|
||||
// send to backend using API expected by server: { site_id, content }
|
||||
if (!state.siteId) { alert('Guardado local (demo). Para guardar en SaaS abre el customizer con ?site_id=ID'); state.hasChanges=false; return; }
|
||||
|
||||
updateSaveIndicator('saving');
|
||||
fetch('/api/customizer/save', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ site_id: state.siteId, content: content })
|
||||
}).then(r=>r.json()).then(j=>{
|
||||
if (j && j.success) { state.hasChanges=false; updateSaveIndicator('saved'); alert('Guardado en servidor'); }
|
||||
else { updateSaveIndicator('error'); alert('Error guardando'); }
|
||||
}).catch(e=>{ updateSaveIndicator('error'); alert('Error guardando: '+e); });
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -150,6 +150,6 @@ def init_db():
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("✅ Base de datos GKACHELE inicializada correctamente.")
|
||||
print("Base de datos GKACHELE inicializada correctamente.")
|
||||
except Exception as e:
|
||||
print(f"❌ Error inicializando DB: {e}")
|
||||
print(f" Error inicializando DB: {e}")
|
||||
|
||||
Binary file not shown.
@@ -1,2 +1,8 @@
|
||||
blinker==1.9.0
|
||||
click==8.3.1
|
||||
colorama==0.4.6
|
||||
Flask==2.3.3
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.3
|
||||
Werkzeug==2.3.7
|
||||
|
||||
BIN
demo/routes/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/routes/__pycache__/admin.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/admin.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/routes/__pycache__/auth.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/auth.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/routes/__pycache__/customizer.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/customizer.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/routes/__pycache__/dashboard.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/dashboard.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/routes/__pycache__/public.cpython-314.pyc
Normal file
BIN
demo/routes/__pycache__/public.cpython-314.pyc
Normal file
Binary file not shown.
@@ -9,6 +9,19 @@ from utils.auth_decorators import login_required
|
||||
|
||||
customizer_bp = Blueprint('customizer', __name__)
|
||||
|
||||
|
||||
@customizer_bp.route('/customizer')
|
||||
def customizer_demo():
|
||||
"""Ruta de conveniencia: si se pasa ?site_id=ID delega a customizer_view, si no muestra demo"""
|
||||
sid = request.args.get('site_id')
|
||||
if sid:
|
||||
try:
|
||||
return customizer_view(int(sid))
|
||||
except Exception:
|
||||
pass
|
||||
# Render demo template with empty content
|
||||
return render_template('customizer.html', site_id='demo', slug='demo', theme=None, content={}, theme_template=None, theme_config={}, available_themes={}, user_plan='base')
|
||||
|
||||
@customizer_bp.route('/api/themes')
|
||||
def list_themes():
|
||||
"""Listar todos los templates disponibles filtrados por plan"""
|
||||
@@ -35,12 +48,13 @@ def customizer_view(site_id):
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT user_id, slug, theme, content_json FROM sites WHERE id = ?', (site_id,))
|
||||
site = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not site:
|
||||
conn.close()
|
||||
return "Sitio no encontrado", 404
|
||||
|
||||
if 'user_id' in session and site[0] != session['user_id']:
|
||||
conn.close()
|
||||
return "No autorizado", 403
|
||||
|
||||
content = json.loads(site[3]) if site[3] else {}
|
||||
@@ -55,9 +69,10 @@ def customizer_view(site_id):
|
||||
theme_config = get_theme_config(theme)
|
||||
|
||||
# Obtener plan del usuario para filtrar templates
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT plan, rubro FROM users WHERE id = ?', (site[0],))
|
||||
user_data = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
user_plan = user_data[0] if user_data else 'base'
|
||||
user_rubro = user_data[1] if user_data else 'restaurante'
|
||||
|
||||
@@ -91,6 +106,43 @@ def save_customizer():
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
@customizer_bp.route('/api/customizer/get-blocks/<int:site_id>', methods=['GET'])
|
||||
def get_blocks(site_id):
|
||||
"""Retorna los bloques de un sitio"""
|
||||
conn = sqlite3.connect(MAIN_DB)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT content_json FROM sites WHERE id = ?', (site_id,))
|
||||
result = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not result or not result[0]:
|
||||
return jsonify([])
|
||||
|
||||
try:
|
||||
content = json.loads(result[0])
|
||||
return jsonify(content.get('blocks', []))
|
||||
except:
|
||||
return jsonify([])
|
||||
|
||||
|
||||
@customizer_bp.route('/api/customizer/get-content/<int:site_id>', methods=['GET'])
|
||||
def get_content(site_id):
|
||||
"""Retorna el contenido completo (blocks + settings) de un sitio"""
|
||||
conn = sqlite3.connect(MAIN_DB)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT content_json FROM sites WHERE id = ?', (site_id,))
|
||||
result = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not result or not result[0]:
|
||||
return jsonify({'success': True, 'content': {}})
|
||||
|
||||
try:
|
||||
content = json.loads(result[0])
|
||||
return jsonify({'success': True, 'content': content})
|
||||
except Exception:
|
||||
return jsonify({'success': True, 'content': {}})
|
||||
|
||||
@customizer_bp.route('/api/customizer/add-block', methods=['POST'])
|
||||
def add_block():
|
||||
data = request.get_json()
|
||||
@@ -102,6 +154,10 @@ def add_block():
|
||||
c.execute('SELECT content_json FROM sites WHERE id = ?', (site_id,))
|
||||
result = c.fetchone()
|
||||
|
||||
if not result:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'error': 'Sitio no encontrado'}), 404
|
||||
|
||||
content = json.loads(result[0]) if result[0] else {}
|
||||
if 'blocks' not in content: content['blocks'] = []
|
||||
|
||||
|
||||
25
demo/routes/get_blocks_fix.py
Normal file
25
demo/routes/get_blocks_fix.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import json
|
||||
|
||||
customizer_bp = Blueprint('customizer_api', __name__)
|
||||
|
||||
@customizer_bp.route('/api/customizer/get-blocks/<int:site_id>', methods=['GET'])
|
||||
def get_blocks(site_id):
|
||||
"""Retorna los bloques de un sitio"""
|
||||
import sqlite3
|
||||
from config import MAIN_DB
|
||||
|
||||
conn = sqlite3.connect(MAIN_DB)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT content_json FROM sites WHERE id = ?', (site_id,))
|
||||
result = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not result or not result[0]:
|
||||
return jsonify([])
|
||||
|
||||
try:
|
||||
content = json.loads(result[0])
|
||||
return jsonify(content.get('blocks', []))
|
||||
except:
|
||||
return jsonify([])
|
||||
13
demo/static/test.html
Normal file
13
demo/static/test.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Test Customizer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Abre la Consola (F12) y verás los errores del Customizer</h1>
|
||||
<iframe src="http://localhost:5001/customizer/1" width="100%" height="800px"></iframe>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,36 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
background: white;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
th, td {
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 5px 15px;
|
||||
background: #4caf50;
|
||||
@@ -41,11 +53,12 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔧 Panel Admin</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Solicitudes Pendientes</h2>
|
||||
<table>
|
||||
<tr>
|
||||
@@ -67,10 +80,12 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not requests %}
|
||||
<tr><td colspan="5">No hay solicitudes pendientes</td></tr>
|
||||
<tr>
|
||||
<td colspan="5">No hay solicitudes pendientes</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<h2>👥 Usuarios Registrados</h2>
|
||||
<table>
|
||||
<tr>
|
||||
@@ -104,10 +119,12 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not users %}
|
||||
<tr><td colspan="9">No hay usuarios registrados</td></tr>
|
||||
<tr>
|
||||
<td colspan="9">No hay usuarios registrados</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
|
||||
<h2>🌐 Todos los Sitios</h2>
|
||||
<table>
|
||||
<tr>
|
||||
@@ -131,48 +148,50 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<style>
|
||||
.btn-danger {
|
||||
background: #d63638;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #b32d2e;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
function approve(requestId) {
|
||||
if (confirm('¿Aprobar este sitio?')) {
|
||||
fetch(`/admin/approve/${requestId}`, {method: 'POST'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Sitio aprobado');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
fetch(`/admin/approve/${requestId}`, { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Sitio aprobado');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function deleteUser(userId, email) {
|
||||
if (confirm(`⚠️ ¿Eliminar usuario ${userId} (${email})?\n\nEsto eliminará TODOS sus datos:\n- Sitios\n- Menús\n- Widgets\n- Media\n- Solicitudes\n\nEsta acción NO se puede deshacer.`)) {
|
||||
fetch(`/admin/users/delete/${userId}`, {method: 'POST'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Usuario eliminado exitosamente');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.error || 'Error al eliminar'));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('❌ Error: ' + err);
|
||||
});
|
||||
fetch(`/admin/users/delete/${userId}`, { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Usuario eliminado exitosamente');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.error || 'Error al eliminar'));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('❌ Error: ' + err);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
1704
demo/templates/customizer.html.bak
Normal file
1704
demo/templates/customizer.html.bak
Normal file
File diff suppressed because it is too large
Load Diff
1704
demo/templates/customizer.html.v2.bak
Normal file
1704
demo/templates/customizer.html.v2.bak
Normal file
File diff suppressed because it is too large
Load Diff
1704
demo/templates/customizer_pro_ready.html
Normal file
1704
demo/templates/customizer_pro_ready.html
Normal file
File diff suppressed because it is too large
Load Diff
597
demo/templates/demo_pro_v3.html
Normal file
597
demo/templates/demo_pro_v3.html
Normal file
@@ -0,0 +1,597 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GKACHELE Builder - Premium V3</title>
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary: #2563eb;
|
||||
--primary-hover: #1d4ed8;
|
||||
--bg-sidebar: #ffffff;
|
||||
--bg-canvas: #f3f4f6;
|
||||
--border: #e5e7eb;
|
||||
--text-main: #111827;
|
||||
--text-muted: #6b7280;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-canvas);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* SIDEBAR */
|
||||
.sidebar {
|
||||
width: 380px;
|
||||
background: var(--bg-sidebar);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.04);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.5px;
|
||||
background: linear-gradient(135deg, #2563eb, #7c3aed);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.logo i {
|
||||
-webkit-text-fill-color: #2563eb;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.nav-sections {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.section-item:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.section-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #f0f9ff;
|
||||
color: #0369a1;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.section-title h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.section-title p {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ACTIVE PANEL OVERLAY */
|
||||
.panel-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 380px;
|
||||
background: #fff;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--border);
|
||||
box-shadow: 10px 0 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.panel-drawer.active {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.drawer-close {
|
||||
border: none;
|
||||
background: #f3f4f6;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #4b5563;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.drawer-close:hover {
|
||||
background: #e5e7eb;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* FORM CONTROLS */
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input,
|
||||
.textarea,
|
||||
.select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
transition: 0.2s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.textarea:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* MENU CARD DESIGN */
|
||||
.dish-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.dish-card:hover {
|
||||
border-color: #cbd5e1;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dish-img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #eee;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dish-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dish-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dish-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dish-price {
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dish-desc {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #f0f9ff;
|
||||
color: #0369a1;
|
||||
border: 1px dashed #bae6fd;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: #e0f2fe;
|
||||
border-color: #7dd3fc;
|
||||
}
|
||||
|
||||
/* PREVIEW CANVAS */
|
||||
.canvas-area {
|
||||
flex: 1;
|
||||
background-color: #e5e5e5;
|
||||
background-image: radial-gradient(#d4d4d4 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-bar {
|
||||
background: #fff;
|
||||
padding: 6px;
|
||||
border-radius: 50px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.device-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.device-btn.active {
|
||||
background: var(--bg-canvas);
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.device-btn:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.preview-frame-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- SIDEBAR NAVIGATION -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo"><i class="fa-solid fa-cube"></i> GKACHELE™</div>
|
||||
<a href="#" class="back-link"><i class="fa-solid fa-arrow-left"></i> Salir</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-sections">
|
||||
<div class="section-item" onclick="openDrawer('identity')">
|
||||
<div class="section-info">
|
||||
<div class="section-icon"><i class="fa-solid fa-store"></i></div>
|
||||
<div class="section-title">
|
||||
<h3>Identidad del Sitio</h3>
|
||||
<p>Logo, Nombre, Slogan</p>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
|
||||
</div>
|
||||
|
||||
<div class="section-item" onclick="openDrawer('menus')">
|
||||
<div class="section-info">
|
||||
<div class="section-icon" style="background:#fff7ed; color:#c2410c;"><i
|
||||
class="fa-solid fa-utensils"></i></div>
|
||||
<div class="section-title">
|
||||
<h3>Menú & Platos</h3>
|
||||
<p>Gestionar carta digital</p>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
|
||||
</div>
|
||||
|
||||
<div class="section-item" onclick="openDrawer('style')">
|
||||
<div class="section-info">
|
||||
<div class="section-icon" style="background:#fdf4ff; color:#a21caf;"><i
|
||||
class="fa-solid fa-palette"></i></div>
|
||||
<div class="section-title">
|
||||
<h3>Estilo & Marca</h3>
|
||||
<p>Colores, Tipografía</p>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 24px; border-top: 1px solid var(--border);">
|
||||
<button
|
||||
style="width:100%; padding: 14px; background: var(--text-main); color: #fff; border:none; border-radius: 10px; font-weight: 600; cursor: pointer;">Publicar
|
||||
Cambios</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DRAWERS (HIDDEN PANELS) -->
|
||||
|
||||
<!-- MENU DRAWER -->
|
||||
<div id="menus" class="panel-drawer">
|
||||
<div class="drawer-header">
|
||||
<button class="drawer-close" onclick="closeDrawers()"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<span class="drawer-title">Gestionar Menú</span>
|
||||
</div>
|
||||
<div class="drawer-content">
|
||||
<button class="btn-add"><i class="fa-solid fa-plus"></i> Añadir Nuevo Plato</button>
|
||||
|
||||
<div class="dish-card">
|
||||
<img src="https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=200&h=200"
|
||||
class="dish-img">
|
||||
<div class="dish-info">
|
||||
<div class="dish-header">
|
||||
<span class="dish-name">Bowl Saludable</span>
|
||||
<span class="dish-price">$12.50</span>
|
||||
</div>
|
||||
<p class="dish-desc">Quinoa, aguacate, tomate cherry, huevo pouche y aderezo de sésamo.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dish-card">
|
||||
<img src="https://images.unsplash.com/photo-1482049016688-2d3e1b311543?auto=format&fit=crop&w=200&h=200"
|
||||
class="dish-img">
|
||||
<div class="dish-info">
|
||||
<div class="dish-header">
|
||||
<span class="dish-name">Tostada de Aguacate</span>
|
||||
<span class="dish-price">$8.00</span>
|
||||
</div>
|
||||
<p class="dish-desc">Pan de masa madre, aguacate triturado, semillas de girasol.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IDENTITY DRAWER -->
|
||||
<div id="identity" class="panel-drawer">
|
||||
<div class="drawer-header">
|
||||
<button class="drawer-close" onclick="closeDrawers()"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<span class="drawer-title">Identidad</span>
|
||||
</div>
|
||||
<div class="drawer-content">
|
||||
<div class="form-group">
|
||||
<label class="label">Nombre del Negocio</label>
|
||||
<input type="text" class="input" id="inputName" value="Green Bowl Madrid"
|
||||
oninput="updatePreviewText('.logo', this.value)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label">Descripción Corta</label>
|
||||
<textarea class="textarea" rows="3" id="inputDesc"
|
||||
oninput="updatePreviewText('.hero p', this.value)">Comida saludable y fresca en el corazón de la ciudad.</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label">Título Hero</label>
|
||||
<input type="text" class="input" id="inputHero" value="Sabor Natural"
|
||||
oninput="updatePreviewText('.hero h1', this.value)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PREVIEW AREA -->
|
||||
<div class="canvas-area">
|
||||
<div class="device-bar">
|
||||
<button class="device-btn active" onclick="setDevice('100%')"><i class="fa-solid fa-desktop"></i></button>
|
||||
<button class="device-btn" onclick="setDevice('768px')"><i
|
||||
class="fa-solid fa-tablet-screen-button"></i></button>
|
||||
<button class="device-btn" onclick="setDevice('390px')"><i
|
||||
class="fa-solid fa-mobile-screen-button"></i></button>
|
||||
</div>
|
||||
<div class="preview-frame-wrapper" style="max-width: 100%">
|
||||
<!-- MOCK FRAME CONTENT FOR DEMO -->
|
||||
<iframe id="previewFrame" srcdoc='
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { margin: 0; font-family: "Helvetica Neue", sans-serif; }
|
||||
header { padding: 20px 40px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; }
|
||||
.logo { font-weight: bold; font-size: 20px; }
|
||||
.hero { padding: 80px 40px; text-align: center; background: #f9f9f9; }
|
||||
h1 { font-size: 48px; margin: 0 0 20px 0; }
|
||||
p { font-size: 18px; color: #666; max-width: 600px; margin: 0 auto; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 30px; padding: 40px; max-width: 1200px; margin: 0 auto; }
|
||||
.card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
|
||||
.card img { width: 100%; height: 200px; object-fit: cover; }
|
||||
.card-body { padding: 20px; }
|
||||
.price { float: right; font-weight: bold; color: #2563eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">Green Bowl Madrid</div>
|
||||
<nav>
|
||||
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Menú</a>
|
||||
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Reservas</a>
|
||||
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Contacto</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="hero">
|
||||
<h1>Sabor Natural</h1>
|
||||
<p>Los mejores ingredientes orgánicos seleccionados para ti cada mañana.</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<img src="https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=500&q=60">
|
||||
<div class="card-body">
|
||||
<span class="price">$12.50</span>
|
||||
<h3>Bowl Saludable</h3>
|
||||
<p style="font-size: 14px">Quinoa, aguacate, tomate cherry...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<img src="https://images.unsplash.com/photo-1482049016688-2d3e1b311543?auto=format&fit=crop&w=500&q=60">
|
||||
<div class="card-body">
|
||||
<span class="price">$8.00</span>
|
||||
<h3>Tostada de Aguacate</h3>
|
||||
<p style="font-size: 14px">Pan de masa madre tostado...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=500&q=60">
|
||||
<div class="card-body">
|
||||
<span class="price">$10.00</span>
|
||||
<h3>Ensalada César</h3>
|
||||
<p style="font-size: 14px">Lechuga romana, crutones, parmesano...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openDrawer(id) {
|
||||
document.querySelectorAll(".panel-drawer").forEach(d => d.classList.remove("active"));
|
||||
const target = document.getElementById(id);
|
||||
if (target) target.classList.add("active");
|
||||
}
|
||||
|
||||
function closeDrawers() {
|
||||
document.querySelectorAll(".panel-drawer").forEach(d => d.classList.remove("active"));
|
||||
}
|
||||
|
||||
function setDevice(size) {
|
||||
const wrapper = document.querySelector(".preview-frame-wrapper");
|
||||
wrapper.style.maxWidth = size;
|
||||
|
||||
document.querySelectorAll(".device-btn").forEach(b => b.classList.remove("active"));
|
||||
event.currentTarget.classList.add("active");
|
||||
}
|
||||
|
||||
// REAL-TIME PREVIEW LOGIC
|
||||
function updatePreviewText(selector, value) {
|
||||
const iframe = document.getElementById('previewFrame');
|
||||
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
const el = doc.querySelector(selector);
|
||||
if (el) el.innerText = value;
|
||||
}
|
||||
|
||||
// Initialize preview editability after load
|
||||
document.getElementById('previewFrame').onload = function () {
|
||||
// Optional: Add click-to-edit logic inside iframe if needed later
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
BIN
demo/utils/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
demo/utils/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/utils/__pycache__/auth_decorators.cpython-314.pyc
Normal file
BIN
demo/utils/__pycache__/auth_decorators.cpython-314.pyc
Normal file
Binary file not shown.
BIN
demo/utils/__pycache__/theme_engine.cpython-314.pyc
Normal file
BIN
demo/utils/__pycache__/theme_engine.cpython-314.pyc
Normal file
Binary file not shown.
@@ -164,6 +164,7 @@ def render_gkachele_template(theme, content, site_id=None, user_id=None):
|
||||
template_data = {
|
||||
'site_name': content.get('site_name', 'GKACHELE Site'),
|
||||
'hero_title': content.get('hero_title', 'Bienvenido'),
|
||||
'hero_description': content.get('hero_description', ''),
|
||||
'colors': content.get('colors', {}),
|
||||
'typography': content.get('typography', {}),
|
||||
'horarios': content.get('horarios', {}),
|
||||
@@ -171,6 +172,14 @@ def render_gkachele_template(theme, content, site_id=None, user_id=None):
|
||||
'blocks': content.get('blocks', []),
|
||||
'menus': menus,
|
||||
'widgets': widgets,
|
||||
'especialidad_culinaria': content.get('especialidad_culinaria', {}),
|
||||
'menu_items': content.get('menu_items', {}),
|
||||
'menu_url': content.get('menu_url', ''),
|
||||
'capacidad': content.get('capacidad', '50'),
|
||||
'direccion': content.get('direccion', ''),
|
||||
'telefono': content.get('telefono', ''),
|
||||
'email': content.get('email', ''),
|
||||
'mapa_url': content.get('mapa_url', ''),
|
||||
**content
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user