diff --git a/MEMORIA_PROYECTO.md b/MEMORIA_PROYECTO.md
new file mode 100644
index 0000000..bb97a0e
--- /dev/null
+++ b/MEMORIA_PROYECTO.md
@@ -0,0 +1,33 @@
+# Memoria del Proyecto: GKACHELE™ SaaS
+
+Este documento sirve como un resumen contextual para la IA y el desarrollador.
+
+## 1. Visión General del Proyecto
+
+* **Nombre:** GKACHELE™
+* **Tipo:** SaaS (Software as a Service) para la creación de sitios web.
+* **Concepto:** Un "WordPress" propio, auto-alojado y hecho a medida.
+
+## 2. Arquitectura y Tecnología
+
+* **Backend:** Aplicación monolítica desarrollada en **Python** con el microframework **Flask**.
+* **Código Principal:** Ubicado en el directorio `demo/`.
+* **Entrypoint:** `demo/app.py`.
+* **Base de Datos Actual:** SQLite, en el archivo `demo/database/main.db`.
+* **Motor de Plantillas:** Un sistema personalizado (`demo/utils/theme_engine.py`) que imita la lógica de temas de WordPress.
+
+## 3. Despliegue y Operaciones (DevOps)
+
+* **Entorno de Producción:** Una **Raspberry Pi**.
+* **Proceso de Despliegue Actual:** Manual, mediante scripts (`.sh`) que copian archivos vía `scp` y gestionan el servicio con `systemd`.
+* **Control de Versiones:** **Gitea**, autohospedado.
+
+## 4. Objetivo Estratégico Actual
+
+El objetivo principal es **modificar y modernizar la aplicación existente de forma incremental**, no reescribirla desde cero.
+
+El plan de acción es el siguiente:
+
+1. **Contenerización:** Empaquetar la aplicación Flask y sus servicios en contenedores **Docker**.
+2. **Migración de Base de Datos:** Reemplazar SQLite por **PostgreSQL**, ejecutándose en su propio contenedor Docker.
+3. **Automatización (CI/CD):** Configurar **Gitea Actions** para automatizar el proceso de construcción de imágenes Docker y el despliegue en la Raspberry Pi tras cada `git push`.
diff --git a/README.md b/README.md
index 5244b98..8f380cc 100644
--- a/README.md
+++ b/README.md
@@ -1,98 +1,75 @@
-# WordPress con Docker Compose
-
-Ejemplo básico de WordPress usando Docker Compose con MySQL y phpMyAdmin.
-
-## 🚀 Inicio Rápido
-
-### Requisitos
-- Docker instalado
-- Docker Compose instalado
-
-### Instalación
-
-1. **Clonar o descargar este proyecto**
-
-2. **Iniciar los contenedores:**
-```bash
-docker-compose up -d
-```
-
-3. **Acceder a WordPress:**
- - Abre tu navegador en: http://localhost:8080
- - Sigue el asistente de instalación de WordPress
-
-4. **Acceder a phpMyAdmin (opcional):**
- - Abre tu navegador en: http://localhost:8081
- - Usuario: `root`
- - Contraseña: `root_password`
-
-## 📝 Configuración
-
-### Cambiar puertos
-Si los puertos 8080 o 8081 están ocupados, edita `docker-compose.yml`:
-```yaml
-ports:
- - "TU_PUERTO:80" # Cambia TU_PUERTO por el que prefieras
-```
-
-### Cambiar credenciales
-Edita las variables de entorno en `docker-compose.yml`:
-- `MYSQL_PASSWORD`: Contraseña del usuario de WordPress
-- `MYSQL_ROOT_PASSWORD`: Contraseña del root de MySQL
-- `WORDPRESS_DB_PASSWORD`: Debe coincidir con `MYSQL_PASSWORD`
-
-## 🛠️ Comandos Útiles
-
-### Ver logs
-```bash
-docker-compose logs -f
-```
-
-### Detener contenedores
-```bash
-docker-compose down
-```
-
-### Detener y eliminar volúmenes (⚠️ borra los datos)
-```bash
-docker-compose down -v
-```
-
-### Reiniciar un servicio específico
-```bash
-docker-compose restart wordpress
-```
-
-### Ver contenedores en ejecución
-```bash
-docker-compose ps
-```
-
-## 📁 Estructura
-
-```
-.
-├── docker-compose.yml # Configuración de servicios
-├── wp-content/ # Temas y plugins personalizados (se crea automáticamente)
-└── README.md # Este archivo
-```
-
-## 🔒 Seguridad
-
-⚠️ **IMPORTANTE**: Este es un ejemplo básico para desarrollo. Para producción:
-- Cambia todas las contraseñas por defecto
-- Usa variables de entorno seguras
-- Configura SSL/TLS
-- Implementa un firewall
-- Usa secrets de Docker o un gestor de secretos
-
-## 🐳 Servicios Incluidos
-
-- **WordPress**: Aplicación principal (puerto 8080)
-- **MySQL 8.0**: Base de datos (puerto interno)
-- **phpMyAdmin**: Administrador de base de datos (puerto 8081)
-
-## 📚 Recursos
-
-- [Documentación de WordPress](https://wordpress.org/support/)
-- [Documentación de Docker Compose](https://docs.docker.com/compose/)
+# GKACHELE™ Agent - Configuración del Asistente y Documentación del Proyecto
+
+Este archivo define las reglas, el contexto y el flujo principal para el asistente de IA (y cualquier desarrollador) trabajando en el ecosistema GKACHELE™.
+
+## 🎯 Misión del Proyecto
+GKACHELE™ es un sistema SaaS modular para la creación y gestión de sitios web, enfocado en la flexibilidad, el control de versiones mediante Gitea y el despliegue optimizado en Raspberry Pi/Linux.
+
+## ⚠️ REGLAS CRÍTICAS (NUNCA ROMPER)
+
+### 1. Prohibición de Referencias Externas
+- **NUNCA** mencionar "WordPress", "wordpress", "WP" o "wp-" en el código fuente.
+- Este es un sistema **propio e independiente**.
+- **Reemplazos**:
+ - "WordPress" -> "GKACHELE™" o "Sistema Modular".
+ - "wp-admin" -> "/dashboard".
+ - "wp_options" -> "tabla settings".
+
+### 2. Metodología GKACHELE™
+- **Prioridad 1: Funcionalidad**. Hacer que el código funcione y sea verificado en producción (Raspberry).
+- **Prioridad 2: Limpieza**. Refactorizar y optimizar SOLO después de que la funcionalidad sea confirmada.
+- **Flujo**: `Funcionalidad -> Probar -> Funciona -> Limpieza -> Documentar`.
+
+## 🛠️ Skills & Infraestructura
+
+### Gestión de Repositorios (Gitea)
+- Uso de `gitea_connector.py` para automatizar la creación de organizaciones y repositorios por cliente.
+- Workflows de auto-commit y auto-deploy tras cambios en el customizer.
+
+### Infraestructura (Raspberry Pi & Docker)
+- Despliegue mediante `docker-compose`.
+- Scripts de sincronización: `sync-to-raspberry.sh`, `update-code-pi.sh`.
+- Dominios gestionados via DuckDNS.
+
+## 📝 Guías de Trabajo para el Agente
+- **Análisis antes de actuar**: Siempre revisar la carpeta `memoria/` antes de realizar cambios estructurales.
+- **Verificación de reglas**: Antes de cada commit, realizar un grep para asegurar que no se colaron referencias prohibidas.
+- **Persistencia**: Actualizar `task.md` y la memoria del proyecto tras completar hitos importantes.
+
+---
+
+## 🔄 Flujo Principal de la Aplicación GKACHELE™
+
+Este es el proceso completo, desde un nuevo visitante hasta un sitio web publicado:
+
+1. **Visita a la Landing Page (`/`):**
+ * Un cliente potencial visita la página principal del servicio.
+
+2. **Solicitud de Plan y Configuración Inicial:**
+ * Desde la Landing Page, el cliente selecciona un plan (Base, Pro, Premium).
+ * Rellena un formulario emergente (menú desplegable) con información inicial (nombre, email, rubro, etc.).
+ * Este proceso redirige al usuario a la página de registro con los datos pre-cargados.
+
+3. **Registro de Cliente (`/register`):**
+ * El cliente finaliza su registro en la plataforma.
+
+4. **Creación del Sitio en Borrador (`/customizer`):**
+ * Una vez registrado y/o logueado, el cliente es dirigido al "Customizer". Aquí puede diseñar y personalizar su sitio web, que permanece en estado de borrador y no es público.
+
+5. **Envío de Solicitud de Publicación:**
+ * Cuando el cliente considera que su sitio en borrador está listo, lo envía para tu revisión y aprobación.
+
+6. **Revisión en el Dashboard del Administrador (`/dashboard`):**
+ * Tú, como administrador del sistema, recibes esta solicitud en tu panel principal. Aquí puedes ver todos los sitios pendientes de aprobación.
+
+7. **Aprobación o Rechazo Manual:**
+ * Revisas el sitio del cliente. Desde tu dashboard, decides si **apruebas** o **rechazas** la solicitud de publicación.
+
+8. **Panel de Cliente (`/admin`) y Publicación del Sitio:**
+ * Si la solicitud es **aprobada**, el sitio web del cliente se **publica**, y se habilita un panel de administración específico para ese cliente (`/admin`), desde donde puede gestionar su sitio en línea.
+
+9. **Visualización Pública del Sitio:**
+ * El sitio web aprobado y publicado es ahora accesible para cualquier usuario de internet en su dominio asignado.
+
+---
+**© 2025 GKACHELE™. Todos los derechos reservados.**
\ No newline at end of file
diff --git a/demo/Dockerfile b/demo/Dockerfile
index d7199b6..d0590de 100644
--- a/demo/Dockerfile
+++ b/demo/Dockerfile
@@ -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"]
diff --git a/demo/__pycache__/config.cpython-314.pyc b/demo/__pycache__/config.cpython-314.pyc
new file mode 100644
index 0000000..9975899
Binary files /dev/null and b/demo/__pycache__/config.cpython-314.pyc differ
diff --git a/demo/__pycache__/database.cpython-314.pyc b/demo/__pycache__/database.cpython-314.pyc
new file mode 100644
index 0000000..ac5e78b
Binary files /dev/null and b/demo/__pycache__/database.cpython-314.pyc differ
diff --git a/demo/customizer.html b/demo/customizer.html
new file mode 100644
index 0000000..c0f557a
--- /dev/null
+++ b/demo/customizer.html
@@ -0,0 +1,188 @@
+
+
+
+
+
+ GKACHELE Customizer
+
+
+
+
+
+
+
+
+
diff --git a/demo/database.py b/demo/database.py
index 757aee1..59929f1 100644
--- a/demo/database.py
+++ b/demo/database.py
@@ -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}")
diff --git a/demo/database/main.db b/demo/database/main.db
index e09825c..1676475 100644
Binary files a/demo/database/main.db and b/demo/database/main.db differ
diff --git a/demo/requirements.txt b/demo/requirements.txt
index 2f2fa62..5df4741 100644
--- a/demo/requirements.txt
+++ b/demo/requirements.txt
@@ -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
diff --git a/demo/routes/__pycache__/__init__.cpython-314.pyc b/demo/routes/__pycache__/__init__.cpython-314.pyc
new file mode 100644
index 0000000..335fcff
Binary files /dev/null and b/demo/routes/__pycache__/__init__.cpython-314.pyc differ
diff --git a/demo/routes/__pycache__/admin.cpython-314.pyc b/demo/routes/__pycache__/admin.cpython-314.pyc
new file mode 100644
index 0000000..173b39b
Binary files /dev/null and b/demo/routes/__pycache__/admin.cpython-314.pyc differ
diff --git a/demo/routes/__pycache__/auth.cpython-314.pyc b/demo/routes/__pycache__/auth.cpython-314.pyc
new file mode 100644
index 0000000..b48af22
Binary files /dev/null and b/demo/routes/__pycache__/auth.cpython-314.pyc differ
diff --git a/demo/routes/__pycache__/customizer.cpython-314.pyc b/demo/routes/__pycache__/customizer.cpython-314.pyc
new file mode 100644
index 0000000..cfa1636
Binary files /dev/null and b/demo/routes/__pycache__/customizer.cpython-314.pyc differ
diff --git a/demo/routes/__pycache__/dashboard.cpython-314.pyc b/demo/routes/__pycache__/dashboard.cpython-314.pyc
new file mode 100644
index 0000000..80fc80d
Binary files /dev/null and b/demo/routes/__pycache__/dashboard.cpython-314.pyc differ
diff --git a/demo/routes/__pycache__/public.cpython-314.pyc b/demo/routes/__pycache__/public.cpython-314.pyc
new file mode 100644
index 0000000..8bfd8b7
Binary files /dev/null and b/demo/routes/__pycache__/public.cpython-314.pyc differ
diff --git a/demo/routes/customizer.py b/demo/routes/customizer.py
index fcc0319..1f19ee3 100644
--- a/demo/routes/customizer.py
+++ b/demo/routes/customizer.py
@@ -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/', 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/', 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'] = []
diff --git a/demo/routes/get_blocks_fix.py b/demo/routes/get_blocks_fix.py
new file mode 100644
index 0000000..4c180e6
--- /dev/null
+++ b/demo/routes/get_blocks_fix.py
@@ -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/', 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([])
diff --git a/demo/static/test.html b/demo/static/test.html
new file mode 100644
index 0000000..2e6a7c1
--- /dev/null
+++ b/demo/static/test.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Test Customizer
+
+
+
+ Abre la Consola (F12) y verás los errores del Customizer
+
+
+
+
\ No newline at end of file
diff --git a/demo/templates/admin.html b/demo/templates/admin.html
index 7c0992d..029d3fd 100644
--- a/demo/templates/admin.html
+++ b/demo/templates/admin.html
@@ -1,36 +1,48 @@
+
Admin - Demo
+
-
+
Solicitudes Pendientes
@@ -67,10 +80,12 @@
{% endfor %}
{% if not requests %}
- | No hay solicitudes pendientes |
+
+ | No hay solicitudes pendientes |
+
{% endif %}
-
+
👥 Usuarios Registrados
@@ -104,10 +119,12 @@
{% endfor %}
{% if not users %}
- | No hay usuarios registrados |
+
+ | No hay usuarios registrados |
+
{% endif %}
-
+
🌐 Todos los Sitios
@@ -131,48 +148,50 @@
{% endfor %}
-
+
-
+
-
+
+