Modularización de GKACHELE SaaS
This commit is contained in:
74
backups/backup-20260114-085602/local/demo/COMO_USAR.md
Normal file
74
backups/backup-20260114-085602/local/demo/COMO_USAR.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 🎯 Cómo Usar el Demo
|
||||
|
||||
## ⚠️ IMPORTANTE: Python en WSL
|
||||
|
||||
Tu proyecto está en WSL, así que necesitas ejecutar desde ahí.
|
||||
|
||||
## 🚀 Opción 1: Desde WSL (Recomendado)
|
||||
|
||||
```bash
|
||||
# En WSL
|
||||
cd /mnt/c/word/demo
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
O directamente:
|
||||
```bash
|
||||
cd /mnt/c/word/demo
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
## 🚀 Opción 2: Copiar a WSL
|
||||
|
||||
```bash
|
||||
# En WSL
|
||||
cp -r /mnt/c/word/demo ~/demo-saas
|
||||
cd ~/demo-saas
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
## 📋 Pasos para Probar
|
||||
|
||||
1. **Abrir navegador**: http://localhost:5001
|
||||
|
||||
2. **Registrarse**:
|
||||
- Email: test@test.com
|
||||
- Password: test123
|
||||
- Plan: Base
|
||||
- Rubro: Gimnasio
|
||||
|
||||
3. **Crear sitio**:
|
||||
- Nombre: Mi Gimnasio
|
||||
- Slug: mi-gimnasio
|
||||
- Título: Bienvenido
|
||||
- Descripción: Tu gimnasio de confianza
|
||||
|
||||
4. **Personalizar** (Customizer):
|
||||
- Cambia colores
|
||||
- Cambia texto
|
||||
- Preview se actualiza en tiempo real
|
||||
- Guarda cambios
|
||||
|
||||
5. **Enviar para aprobación**
|
||||
|
||||
6. **Admin** (necesitas ser user_id = 1):
|
||||
- Ve a `/admin`
|
||||
- Aprueba el sitio
|
||||
|
||||
7. **Ver publicado**: `/site/mi-gimnasio`
|
||||
|
||||
## 🔧 Si el puerto 5001 no funciona
|
||||
|
||||
Cambia en `app.py` línea 400:
|
||||
```python
|
||||
port = int(os.environ.get('PORT', 8000)) # Cambia 5001 por 8000
|
||||
```
|
||||
|
||||
## ✅ Verificar que funciona
|
||||
|
||||
```bash
|
||||
# En WSL
|
||||
curl http://localhost:5001
|
||||
# Debe mostrar HTML de la landing
|
||||
```
|
||||
57
backups/backup-20260114-085602/local/demo/EJECUTAR_AHORA.md
Normal file
57
backups/backup-20260114-085602/local/demo/EJECUTAR_AHORA.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# ✅ DEMO FUNCIONANDO - EJECUTAR AHORA
|
||||
|
||||
## 🚀 Comando Simple
|
||||
|
||||
**Abre WSL y ejecuta:**
|
||||
|
||||
```bash
|
||||
cd /mnt/c/word/demo
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
## 📍 Luego abre en tu navegador:
|
||||
|
||||
```
|
||||
http://localhost:5001
|
||||
```
|
||||
|
||||
## ✅ Verificación
|
||||
|
||||
El test ya pasó:
|
||||
- ✅ App importada correctamente
|
||||
- ✅ Flask funcionando
|
||||
- ✅ Templates encontrados
|
||||
- ✅ Base de datos inicializada
|
||||
|
||||
## 🎯 Flujo de Prueba
|
||||
|
||||
1. **Landing** → http://localhost:5001
|
||||
2. **Registrarse** → Click "Registrarse"
|
||||
- Email: test@test.com
|
||||
- Password: test123
|
||||
3. **Crear sitio** → Click "Crear Sitio"
|
||||
- Nombre: Mi Gimnasio
|
||||
- Slug: mi-gimnasio
|
||||
4. **Customizer** → Se abre automáticamente
|
||||
- Cambia colores, texto
|
||||
- Preview se actualiza en tiempo real
|
||||
5. **Enviar** → Click "Enviar para Aprobación"
|
||||
6. **Admin** → http://localhost:5001/admin
|
||||
- (Necesitas ser user_id = 1, el primer usuario registrado)
|
||||
7. **Ver publicado** → http://localhost:5001/site/mi-gimnasio
|
||||
|
||||
## 🔧 Si el puerto 5001 está ocupado
|
||||
|
||||
Edita `app.py` línea 399:
|
||||
```python
|
||||
port = int(os.environ.get('PORT', 8000)) # Cambia a 8000
|
||||
```
|
||||
|
||||
## ✨ Características Funcionando
|
||||
|
||||
- ✅ Multi-tenant (DB por cliente)
|
||||
- ✅ Customizer (sidebar + preview)
|
||||
- ✅ Actualización en tiempo real
|
||||
- ✅ Sistema de solicitudes
|
||||
- ✅ Admin panel
|
||||
- ✅ Sitios públicos
|
||||
69
backups/backup-20260114-085602/local/demo/INICIO_WINDOWS.md
Normal file
69
backups/backup-20260114-085602/local/demo/INICIO_WINDOWS.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 🚀 Iniciar Demo en Windows
|
||||
|
||||
## Opción 1: Usar Python desde WSL
|
||||
|
||||
Si tienes Python en WSL (que es donde tienes tu proyecto):
|
||||
|
||||
```bash
|
||||
# En WSL
|
||||
cd ~/mi-landing/pagebuilder-saas-prod
|
||||
# O copia la carpeta demo a WSL
|
||||
cd /mnt/c/word/demo
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
## Opción 2: Usar Python de Windows
|
||||
|
||||
1. **Verificar Python instalado:**
|
||||
```powershell
|
||||
py --version
|
||||
# o
|
||||
python --version
|
||||
```
|
||||
|
||||
2. **Instalar dependencias:**
|
||||
```powershell
|
||||
cd demo
|
||||
py -m pip install Flask Werkzeug
|
||||
```
|
||||
|
||||
3. **Ejecutar:**
|
||||
```powershell
|
||||
py app.py
|
||||
```
|
||||
|
||||
## Opción 3: Usar el script start.bat
|
||||
|
||||
Doble click en `start.bat` (instala dependencias y ejecuta)
|
||||
|
||||
## 🔧 Si no funciona
|
||||
|
||||
### Verificar puerto
|
||||
```powershell
|
||||
netstat -ano | findstr :5001
|
||||
```
|
||||
|
||||
### Cambiar puerto
|
||||
Edita `app.py` línea 400, cambia:
|
||||
```python
|
||||
port = int(os.environ.get('PORT', 5001))
|
||||
```
|
||||
Por:
|
||||
```python
|
||||
port = int(os.environ.get('PORT', 8000))
|
||||
```
|
||||
|
||||
### Ver errores
|
||||
Ejecuta directamente:
|
||||
```powershell
|
||||
cd demo
|
||||
py app.py
|
||||
```
|
||||
|
||||
Y verás los errores en la consola.
|
||||
|
||||
## 📍 URLs
|
||||
|
||||
- Landing: http://localhost:5001
|
||||
- Admin: http://localhost:5001/admin
|
||||
- Dashboard: http://localhost:5001/dashboard (requiere login)
|
||||
90
backups/backup-20260114-085602/local/demo/INSTRUCCIONES.md
Normal file
90
backups/backup-20260114-085602/local/demo/INSTRUCCIONES.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 🚀 Demo Funcional - Instrucciones
|
||||
|
||||
## ✅ Lo que tienes
|
||||
|
||||
1. **Backend Flask completo** con:
|
||||
- Multi-tenant (DB por cliente)
|
||||
- Customizer (sidebar + preview)
|
||||
- Sistema de solicitudes
|
||||
- Admin panel
|
||||
|
||||
2. **Flujo completo**:
|
||||
- Registro → Dashboard → Crear sitio → Customizer → Enviar → Admin aprueba → Publicado
|
||||
|
||||
## 🎯 Cómo probarlo
|
||||
|
||||
### 1. Instalar dependencias
|
||||
```bash
|
||||
cd demo
|
||||
pip install Flask Werkzeug
|
||||
```
|
||||
|
||||
### 2. Ejecutar
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
### 3. Abrir navegador
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
## 📋 Pasos para probar
|
||||
|
||||
1. **Registrarse** (`/register`)
|
||||
- Email: test@test.com
|
||||
- Password: test123
|
||||
- Plan: Base
|
||||
- Rubro: Gimnasio
|
||||
|
||||
2. **Crear sitio** (`/dashboard/create`)
|
||||
- Nombre: Mi Gimnasio
|
||||
- Slug: mi-gimnasio
|
||||
- Título: Bienvenido
|
||||
- Descripción: Tu gimnasio de confianza
|
||||
|
||||
3. **Personalizar** (`/customizer/{site_id}`)
|
||||
- Cambia colores, texto, tipografía
|
||||
- Preview se actualiza en tiempo real
|
||||
- Guarda cambios
|
||||
|
||||
4. **Enviar para aprobación**
|
||||
- Click en "Enviar para Aprobación"
|
||||
- Status cambia a "pending"
|
||||
|
||||
5. **Admin** (`/admin`)
|
||||
- Registra otro usuario (será user_id = 2)
|
||||
- O modifica la DB para que el primer usuario sea admin (user_id = 1)
|
||||
- Ve solicitudes pendientes
|
||||
- Aprueba sitio
|
||||
|
||||
6. **Ver sitio publicado** (`/site/{slug}`)
|
||||
- Una vez aprobado, el sitio está público
|
||||
|
||||
## 🔧 Para hacer admin
|
||||
|
||||
En `app.py` línea 200, cambia:
|
||||
```python
|
||||
if 'user_id' not in session or session['user_id'] != 1:
|
||||
```
|
||||
Por:
|
||||
```python
|
||||
if 'user_id' not in session:
|
||||
```
|
||||
Así cualquier usuario puede ser admin (solo para demo).
|
||||
|
||||
## ✨ Características del Demo
|
||||
|
||||
- ✅ Multi-tenant: Cada cliente tiene su DB
|
||||
- ✅ Customizer: Sidebar izquierdo + Preview derecha
|
||||
- ✅ Actualización en tiempo real
|
||||
- ✅ Sistema de solicitudes
|
||||
- ✅ Admin panel
|
||||
- ✅ Sitios públicos
|
||||
|
||||
## 🐛 Si no funciona
|
||||
|
||||
1. Verifica que Flask esté instalado: `pip list | findstr Flask`
|
||||
2. Verifica puerto 5000 libre: `netstat -ano | findstr :5000`
|
||||
3. Revisa logs en consola
|
||||
4. Asegúrate de estar en la carpeta `demo/`
|
||||
47
backups/backup-20260114-085602/local/demo/LEEME_PRIMERO.txt
Normal file
47
backups/backup-20260114-085602/local/demo/LEEME_PRIMERO.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
═══════════════════════════════════════════════════════════
|
||||
✅ DEMO FUNCIONANDO - VERSIÓN SIMPLE
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
🚀 EJECUTAR:
|
||||
|
||||
Desde WSL:
|
||||
cd /mnt/c/word/demo
|
||||
python3 app_simple.py
|
||||
|
||||
Luego abre en navegador:
|
||||
http://localhost:5001
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
✨ CARACTERÍSTICAS:
|
||||
|
||||
✅ Landing page
|
||||
✅ Registro/Login
|
||||
✅ Dashboard cliente
|
||||
✅ Crear sitio
|
||||
✅ Customizer (sidebar + preview en tiempo real)
|
||||
✅ Enviar para aprobación
|
||||
✅ Admin panel
|
||||
✅ Sitios públicos
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
📋 FLUJO:
|
||||
|
||||
1. Registrarse → /register
|
||||
2. Crear sitio → /create
|
||||
3. Personalizar → /customizer/{id}
|
||||
4. Enviar → Click "Enviar"
|
||||
5. Admin → /admin (aprobar)
|
||||
6. Ver → /site/{slug}
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
🔧 SI NO FUNCIONA:
|
||||
|
||||
1. Verifica Python: python3 --version
|
||||
2. Instala Flask: pip3 install Flask Werkzeug
|
||||
3. Cambia puerto en app_simple.py línea final:
|
||||
port=8000 (en lugar de 5001)
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
38
backups/backup-20260114-085602/local/demo/README.md
Normal file
38
backups/backup-20260114-085602/local/demo/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 🚀 Demo SaaS PageBuilder
|
||||
|
||||
## Inicio Rápido
|
||||
|
||||
```bash
|
||||
cd demo
|
||||
pip install -r requirements.txt
|
||||
python app.py
|
||||
```
|
||||
|
||||
Abre: http://localhost:5000
|
||||
|
||||
## Flujo Demo
|
||||
|
||||
1. **Registrarse** → `/register`
|
||||
2. **Crear sitio** → `/dashboard/create`
|
||||
3. **Personalizar** → `/customizer/{site_id}` (sidebar + preview)
|
||||
4. **Enviar** → Cliente envía para aprobación
|
||||
5. **Admin** → `/admin` (aprobar sitios)
|
||||
6. **Publicado** → `/site/{slug}`
|
||||
|
||||
## Credenciales Admin
|
||||
|
||||
- User ID: 1 (primer usuario registrado)
|
||||
- Accede a `/admin` con user_id = 1
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
demo/
|
||||
├── app.py # Backend Flask
|
||||
├── database/ # SQLite DBs
|
||||
│ ├── main.db # DB principal
|
||||
│ └── sites/ # DB por cliente
|
||||
├── sites/ # Sitios compilados
|
||||
├── themes/ # Templates
|
||||
└── templates/ # HTML templates
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
1038
backups/backup-20260114-085602/local/demo/app.py
Normal file
1038
backups/backup-20260114-085602/local/demo/app.py
Normal file
File diff suppressed because it is too large
Load Diff
594
backups/backup-20260114-085602/local/demo/app_simple.py
Normal file
594
backups/backup-20260114-085602/local/demo/app_simple.py
Normal file
@@ -0,0 +1,594 @@
|
||||
"""
|
||||
Demo SaaS - Versión SIMPLE que FUNCIONA
|
||||
Sistema profesional automatizado - GKACHELE™
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, render_template_string, request, jsonify, session, redirect
|
||||
import sqlite3
|
||||
import os
|
||||
import json
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
app = Flask(__name__, template_folder='templates', static_folder='static')
|
||||
app.secret_key = 'demo-2025'
|
||||
|
||||
# DB simple
|
||||
DB_PATH = 'demo.db'
|
||||
|
||||
def init_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY, email TEXT UNIQUE, password TEXT, plan TEXT, rubro TEXT
|
||||
)''')
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS sites (
|
||||
id INTEGER PRIMARY KEY, user_id INTEGER, slug TEXT UNIQUE,
|
||||
content TEXT, status TEXT DEFAULT 'draft'
|
||||
)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
init_db()
|
||||
|
||||
# Landing profesional (inspirada en tu landing real)
|
||||
LANDING_HTML = '''
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PageBuilder SaaS | Crea tu sitio web automatizado</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', sans-serif; color: #333; }
|
||||
|
||||
/* Nav */
|
||||
.nav-sticky { position: fixed; top: 0; width: 100%; background: rgba(255,255,255,0.95);
|
||||
backdrop-filter: blur(10px); z-index: 1000; padding: 15px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.nav-container { max-width: 1200px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; padding: 0 20px; }
|
||||
.nav-logo a { font-size: 1.5rem; font-weight: 700; color: #667eea; text-decoration: none; }
|
||||
.nav-menu { display: flex; gap: 30px; }
|
||||
.nav-link { color: #333; text-decoration: none; font-weight: 500; transition: color 0.3s; }
|
||||
.nav-link:hover { color: #667eea; }
|
||||
.nav-social { display: flex; gap: 15px; }
|
||||
.nav-social a { color: #667eea; font-size: 1.2rem; }
|
||||
|
||||
/* Hero */
|
||||
.hero-section { min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex; align-items: center; justify-content: center; text-align: center; color: white;
|
||||
position: relative; padding-top: 80px; }
|
||||
.hero-content { max-width: 800px; padding: 40px 20px; }
|
||||
.hero-title { font-size: 4rem; font-weight: 700; margin-bottom: 20px; }
|
||||
.hero-subtitle { font-size: 1.5rem; margin-bottom: 15px; opacity: 0.9; }
|
||||
.hero-description { font-size: 1.1rem; margin-bottom: 40px; opacity: 0.8; }
|
||||
.hero-buttons { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; }
|
||||
.btn-hero { padding: 15px 40px; background: white; color: #667eea; text-decoration: none;
|
||||
border-radius: 50px; font-weight: 600; transition: transform 0.3s, box-shadow 0.3s; }
|
||||
.btn-hero:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
|
||||
.btn-hero.secondary { background: transparent; border: 2px solid white; color: white; }
|
||||
|
||||
/* Plans */
|
||||
.plans-section { padding: 100px 20px; background: #f8f9fa; }
|
||||
.container-large { max-width: 1200px; margin: 0 auto; }
|
||||
.section-title { text-align: center; font-size: 2.5rem; margin-bottom: 60px; color: #333; }
|
||||
.plans-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; }
|
||||
.plan-card { background: white; padding: 40px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s; }
|
||||
.plan-card:hover { transform: translateY(-5px); }
|
||||
.plan-card h3 { font-size: 2rem; margin-bottom: 10px; color: #667eea; }
|
||||
.plan-card .price { font-size: 3rem; font-weight: 700; color: #333; margin: 20px 0; }
|
||||
.plan-card ul { list-style: none; margin: 30px 0; }
|
||||
.plan-card li { padding: 10px 0; border-bottom: 1px solid #eee; }
|
||||
.plan-card li:before { content: "✅ "; margin-right: 10px; }
|
||||
.btn-plan { width: 100%; padding: 15px; background: #667eea; color: white; border: none;
|
||||
border-radius: 8px; font-size: 1.1rem; font-weight: 600; cursor: pointer; margin-top: 20px; }
|
||||
.btn-plan:hover { background: #5568d3; }
|
||||
|
||||
/* Contact */
|
||||
.contact-section { padding: 100px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
|
||||
.contact-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; margin-top: 50px; }
|
||||
.contact-card { background: rgba(255,255,255,0.1); padding: 30px; border-radius: 15px; text-align: center;
|
||||
backdrop-filter: blur(10px); }
|
||||
.contact-card i { font-size: 2.5rem; margin-bottom: 15px; }
|
||||
|
||||
/* WhatsApp Float */
|
||||
.whatsapp-float { position: fixed; bottom: 30px; right: 30px; background: #25D366; color: white;
|
||||
width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center;
|
||||
justify-content: center; font-size: 1.5rem; box-shadow: 0 5px 20px rgba(37,211,102,0.4);
|
||||
z-index: 1000; text-decoration: none; transition: transform 0.3s; }
|
||||
.whatsapp-float:hover { transform: scale(1.1); }
|
||||
|
||||
/* Footer */
|
||||
footer { background: #1a1a1a; color: white; padding: 40px 20px; text-align: center; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-title { font-size: 2.5rem; }
|
||||
.nav-menu { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Nav -->
|
||||
<nav class="nav-sticky">
|
||||
<div class="nav-container">
|
||||
<div class="nav-logo"><a href="/">PageBuilder SaaS</a></div>
|
||||
<div class="nav-menu">
|
||||
<a href="#planes" class="nav-link">Planes</a>
|
||||
<a href="#contacto" class="nav-link">Contacto</a>
|
||||
</div>
|
||||
<div class="nav-social">
|
||||
<a href="/login" class="nav-link">Login</a>
|
||||
<a href="/register" class="nav-link" style="background: #667eea; color: white; padding: 8px 20px; border-radius: 20px;">Registrarse</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">🎨 PageBuilder SaaS</h1>
|
||||
<p class="hero-subtitle">Crea tu sitio web automatizado</p>
|
||||
<p class="hero-description">Sistema profesional y automatizado. Cada cliente con su admin, templates y base de datos.</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="/register" class="btn-hero">Comenzar Gratis</a>
|
||||
<a href="#planes" class="btn-hero secondary">Ver Planes</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Plans -->
|
||||
<section id="planes" class="plans-section">
|
||||
<div class="container-large">
|
||||
<h2 class="section-title">Nuestros Planes</h2>
|
||||
<div class="plans-grid">
|
||||
<div class="plan-card">
|
||||
<h3>Base</h3>
|
||||
<div class="price">$50</div>
|
||||
<ul>
|
||||
<li>Sitio web de una página</li>
|
||||
<li>Diseño profesional</li>
|
||||
<li>Responsive móvil</li>
|
||||
<li>Panel de administración</li>
|
||||
<li>Base de datos propia</li>
|
||||
</ul>
|
||||
<button class="btn-plan" onclick="window.location.href='/register?plan=base'">Elegir Base</button>
|
||||
</div>
|
||||
<div class="plan-card">
|
||||
<h3>Pro</h3>
|
||||
<div class="price">$100</div>
|
||||
<ul>
|
||||
<li>Sitio multipágina</li>
|
||||
<li>Animaciones y efectos</li>
|
||||
<li>Galería de imágenes</li>
|
||||
<li>Formularios avanzados</li>
|
||||
<li>Customizer completo</li>
|
||||
</ul>
|
||||
<button class="btn-plan" onclick="window.location.href='/register?plan=pro'">Elegir Pro</button>
|
||||
</div>
|
||||
<div class="plan-card">
|
||||
<h3>Premium</h3>
|
||||
<div class="price">$200</div>
|
||||
<ul>
|
||||
<li>Sitio completo escalable</li>
|
||||
<li>SEO optimizado</li>
|
||||
<li>Panel admin avanzado</li>
|
||||
<li>Integraciones</li>
|
||||
<li>Soporte prioritario</li>
|
||||
</ul>
|
||||
<button class="btn-plan" onclick="window.location.href='/register?plan=premium'">Elegir Premium</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact -->
|
||||
<section id="contacto" class="contact-section">
|
||||
<div class="container-large">
|
||||
<h2 class="section-title" style="color: white;">Contacto</h2>
|
||||
<div class="contact-grid">
|
||||
<div class="contact-card">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<h3>Email</h3>
|
||||
<p>contacto@pagebuilder.com</p>
|
||||
</div>
|
||||
<div class="contact-card">
|
||||
<i class="fas fa-phone"></i>
|
||||
<h3>Teléfono</h3>
|
||||
<p>+54 9 11 2345 6789</p>
|
||||
</div>
|
||||
<div class="contact-card">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
<h3>WhatsApp</h3>
|
||||
<p>Chatea con nosotros</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- WhatsApp Float -->
|
||||
<a href="https://wa.me/5491123456789" target="_blank" class="whatsapp-float">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
</a>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<p>© 2025 PageBuilder SaaS. Todos los derechos reservados.</p>
|
||||
<p style="margin-top: 10px; opacity: 0.7; font-size: 0.9rem;">Sistema profesional y automatizado - GKACHELE™</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Smooth scroll
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
# Usar tu landing real EXACTA
|
||||
return render_template('landing_real.html')
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute('INSERT INTO users (email, password, plan, rubro) VALUES (?, ?, ?, ?)',
|
||||
(data['email'], generate_password_hash(data['password']),
|
||||
data.get('plan', 'base'), data.get('rubro', 'gimnasio')))
|
||||
user_id = c.lastrowid
|
||||
conn.commit()
|
||||
session['user_id'] = user_id
|
||||
return jsonify({'success': True, 'user_id': user_id})
|
||||
except:
|
||||
return jsonify({'error': 'Email existe'}), 400
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return render_template_string('''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Registro</title>
|
||||
<style>body { font-family: Arial; padding: 40px; max-width: 400px; margin: 0 auto; }
|
||||
input { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #ddd; border-radius: 5px; }
|
||||
button { width: 100%; padding: 12px; background: #667eea; color: white; border: none; border-radius: 5px; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Registro</h1>
|
||||
<form id="form">
|
||||
<input type="email" id="email" placeholder="Email" required>
|
||||
<input type="password" id="password" placeholder="Password" required>
|
||||
<button type="submit">Registrarse</button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('form').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch('/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({email: document.getElementById('email').value,
|
||||
password: document.getElementById('password').value})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) window.location.href = '/dashboard';
|
||||
else alert(data.error);
|
||||
};
|
||||
</script>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT id, password FROM users WHERE email = ?', (data['email'],))
|
||||
user = c.fetchone()
|
||||
conn.close()
|
||||
if user and check_password_hash(user[1], data['password']):
|
||||
session['user_id'] = user[0]
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'error': 'Credenciales inválidas'}), 401
|
||||
|
||||
return render_template_string('''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Login</title>
|
||||
<style>body { font-family: Arial; padding: 40px; max-width: 400px; margin: 0 auto; }
|
||||
input { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #ddd; border-radius: 5px; }
|
||||
button { width: 100%; padding: 12px; background: #667eea; color: white; border: none; border-radius: 5px; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Login</h1>
|
||||
<form id="form">
|
||||
<input type="email" id="email" placeholder="Email" required>
|
||||
<input type="password" id="password" placeholder="Password" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('form').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({email: document.getElementById('email').value,
|
||||
password: document.getElementById('password').value})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) window.location.href = '/dashboard';
|
||||
else alert(data.error);
|
||||
};
|
||||
</script>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/dashboard')
|
||||
def dashboard():
|
||||
if 'user_id' not in session:
|
||||
return redirect('/login')
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT id, slug, status FROM sites WHERE user_id = ?', (session['user_id'],))
|
||||
sites = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
sites_html = ''.join([f'<div style="padding: 15px; background: #f5f5f5; margin: 10px 0; border-radius: 5px;"><h3>{s[1]}</h3><p>Status: {s[2]}</p><a href="/customizer/{s[0]}" style="padding: 8px 15px; background: #667eea; color: white; text-decoration: none; border-radius: 3px;">Editar</a></div>' for s in sites])
|
||||
|
||||
return render_template_string(f'''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Dashboard</title>
|
||||
<style>body {{ font-family: Arial; padding: 20px; max-width: 800px; margin: 0 auto; }}
|
||||
.header {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }}
|
||||
.btn {{ padding: 10px 20px; background: #667eea; color: white; text-decoration: none; border-radius: 5px; }}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>📊 Dashboard</h1>
|
||||
<a href="/create" class="btn">➕ Crear Sitio</a>
|
||||
</div>
|
||||
{sites_html if sites else '<p>No tienes sitios. <a href="/create">Crear uno</a></p>'}
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/create', methods=['GET', 'POST'])
|
||||
def create():
|
||||
if 'user_id' not in session:
|
||||
return redirect('/login')
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
content = json.dumps({
|
||||
'site_name': data.get('site_name', 'Mi Sitio'),
|
||||
'hero_title': data.get('hero_title', 'Bienvenido'),
|
||||
'colors': {'primary': '#ff4d4d', 'secondary': '#1a1a1a'}
|
||||
})
|
||||
c.execute('INSERT INTO sites (user_id, slug, content) VALUES (?, ?, ?)',
|
||||
(session['user_id'], data['slug'], content))
|
||||
site_id = c.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True, 'site_id': site_id})
|
||||
|
||||
return render_template_string('''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Crear Sitio</title>
|
||||
<style>body { font-family: Arial; padding: 40px; max-width: 600px; margin: 0 auto; }
|
||||
input, textarea { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #ddd; border-radius: 5px; }
|
||||
button { width: 100%; padding: 12px; background: #667eea; color: white; border: none; border-radius: 5px; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>➕ Crear Sitio</h1>
|
||||
<form id="form">
|
||||
<input type="text" id="site_name" placeholder="Nombre" required>
|
||||
<input type="text" id="slug" placeholder="Slug (URL)" required>
|
||||
<input type="text" id="hero_title" placeholder="Título" required>
|
||||
<button type="submit">Crear</button>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('form').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch('/create', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
site_name: document.getElementById('site_name').value,
|
||||
slug: document.getElementById('slug').value,
|
||||
hero_title: document.getElementById('hero_title').value
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) window.location.href = `/customizer/${data.site_id}`;
|
||||
};
|
||||
</script>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/customizer/<int:site_id>')
|
||||
def customizer(site_id):
|
||||
if 'user_id' not in session:
|
||||
return redirect('/login')
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT content FROM sites WHERE id = ? AND user_id = ?', (site_id, session['user_id']))
|
||||
site = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not site:
|
||||
return "No encontrado", 404
|
||||
|
||||
content = json.loads(site[0])
|
||||
|
||||
return render_template_string(f'''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Customizer</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: Arial; display: flex; height: 100vh; }}
|
||||
.sidebar {{ width: 300px; background: #f5f5f5; padding: 20px; overflow-y: auto; }}
|
||||
.preview {{ flex: 1; padding: 40px; background: white; overflow-y: auto; }}
|
||||
input, select {{ width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 3px; }}
|
||||
button {{ padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<h2>⚙️ Personalizar</h2>
|
||||
<input type="text" id="hero_title" value="{content.get('hero_title', '')}" placeholder="Título">
|
||||
<input type="color" id="color_primary" value="{content.get('colors', {}).get('primary', '#ff4d4d')}">
|
||||
<button onclick="save()">💾 Guardar</button>
|
||||
<button onclick="submit()" style="background: #ff4d4d;">📤 Enviar</button>
|
||||
</div>
|
||||
<div class="preview">
|
||||
<div id="preview">
|
||||
<h1 id="preview_title" style="color: {content.get('colors', {}).get('primary', '#ff4d4d')};">
|
||||
{content.get('hero_title', 'Título')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const siteId = {site_id};
|
||||
document.getElementById('hero_title').addEventListener('input', update);
|
||||
document.getElementById('color_primary').addEventListener('change', update);
|
||||
function update() {{
|
||||
const title = document.getElementById('hero_title').value;
|
||||
const color = document.getElementById('color_primary').value;
|
||||
document.getElementById('preview_title').textContent = title;
|
||||
document.getElementById('preview_title').style.color = color;
|
||||
}}
|
||||
function save() {{
|
||||
const content = {{
|
||||
hero_title: document.getElementById('hero_title').value,
|
||||
colors: {{primary: document.getElementById('color_primary').value}}
|
||||
}};
|
||||
fetch('/api/save/' + siteId, {{
|
||||
method: 'POST',
|
||||
headers: {{'Content-Type': 'application/json'}},
|
||||
body: JSON.stringify({{content: content}})
|
||||
}}).then(() => alert('✅ Guardado'));
|
||||
}}
|
||||
function submit() {{
|
||||
fetch('/api/submit/' + siteId, {{method: 'POST'}})
|
||||
.then(() => {{alert('✅ Enviado'); window.location.href = '/dashboard';}});
|
||||
}}
|
||||
</script>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/api/save/<int:site_id>', methods=['POST'])
|
||||
def save(site_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'No autorizado'}), 401
|
||||
data = request.get_json()
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('UPDATE sites SET content = ? WHERE id = ? AND user_id = ?',
|
||||
(json.dumps(data['content']), site_id, session['user_id']))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
@app.route('/api/submit/<int:site_id>', methods=['POST'])
|
||||
def submit(site_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'No autorizado'}), 401
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('UPDATE sites SET status = ? WHERE id = ? AND user_id = ?',
|
||||
('pending', site_id, session['user_id']))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
@app.route('/admin')
|
||||
def admin():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT id, slug, status FROM sites WHERE status = ?', ('pending',))
|
||||
requests = c.fetchall()
|
||||
c.execute('SELECT id, slug, status FROM sites')
|
||||
sites = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
return render_template_string(f'''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Admin</title>
|
||||
<style>body {{ font-family: Arial; padding: 20px; }}
|
||||
table {{ width: 100%; border-collapse: collapse; }}
|
||||
th, td {{ padding: 12px; border: 1px solid #ddd; }}
|
||||
th {{ background: #667eea; color: white; }}
|
||||
button {{ padding: 5px 15px; background: #4caf50; color: white; border: none; border-radius: 3px; cursor: pointer; }}
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>🔧 Admin</h1>
|
||||
<h2>Solicitudes Pendientes</h2>
|
||||
<table>
|
||||
<tr><th>ID</th><th>Slug</th><th>Acción</th></tr>
|
||||
{''.join([f'<tr><td>{r[0]}</td><td>{r[1]}</td><td><button onclick="approve({r[0]})">✅ Aprobar</button></td></tr>' for r in requests])}
|
||||
</table>
|
||||
<h2>Todos los Sitios</h2>
|
||||
<table>
|
||||
<tr><th>ID</th><th>Slug</th><th>Status</th></tr>
|
||||
{''.join([f'<tr><td>{s[0]}</td><td>{s[1]}</td><td>{s[2]}</td></tr>' for s in sites])}
|
||||
</table>
|
||||
<script>
|
||||
function approve(id) {{
|
||||
fetch('/api/approve/' + id, {{method: 'POST'}})
|
||||
.then(() => {{alert('✅ Aprobado'); location.reload();}});
|
||||
}}
|
||||
</script>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
@app.route('/api/approve/<int:site_id>', methods=['POST'])
|
||||
def approve(site_id):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('UPDATE sites SET status = ? WHERE id = ?', ('published', site_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
@app.route('/site/<slug>')
|
||||
def site(slug):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT content FROM sites WHERE slug = ? AND status = ?', (slug, 'published'))
|
||||
site = c.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not site:
|
||||
return "Sitio no encontrado", 404
|
||||
|
||||
content = json.loads(site[0])
|
||||
return render_template_string(f'''
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>{content.get('site_name', 'Sitio')}</title>
|
||||
<style>body {{ font-family: Arial; padding: 40px; max-width: 800px; margin: 0 auto; }}
|
||||
h1 {{ color: {content.get('colors', {}).get('primary', '#ff4d4d')}; }}
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>{content.get('hero_title', 'Título')}</h1>
|
||||
<p>{content.get('site_name', '')}</p>
|
||||
</body></html>
|
||||
''')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🚀 Demo SaaS SIMPLE iniciado")
|
||||
print("📍 http://localhost:5001")
|
||||
app.run(debug=True, host='0.0.0.0', port=5001)
|
||||
BIN
backups/backup-20260114-085602/local/demo/database/main.db
Normal file
BIN
backups/backup-20260114-085602/local/demo/database/main.db
Normal file
Binary file not shown.
BIN
backups/backup-20260114-085602/local/demo/demo.db
Normal file
BIN
backups/backup-20260114-085602/local/demo/demo.db
Normal file
Binary file not shown.
66
backups/backup-20260114-085602/local/demo/limpiar_db.py
Normal file
66
backups/backup-20260114-085602/local/demo/limpiar_db.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
GKACHELE™ - Script para limpiar base de datos
|
||||
© 2025 GKACHELE™. Todos los derechos reservados.
|
||||
Uso: python limpiar_db.py
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
DATABASE_DIR = os.path.join(BASE_DIR, 'database')
|
||||
MAIN_DB = os.path.join(DATABASE_DIR, 'main.db')
|
||||
|
||||
def limpiar_db():
|
||||
"""Limpiar todas las tablas de la base de datos"""
|
||||
if not os.path.exists(MAIN_DB):
|
||||
print(f"❌ No se encontró la base de datos en: {MAIN_DB}")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(MAIN_DB)
|
||||
c = conn.cursor()
|
||||
|
||||
# Mostrar datos actuales
|
||||
c.execute('SELECT COUNT(*) FROM users')
|
||||
user_count = c.fetchone()[0]
|
||||
c.execute('SELECT COUNT(*) FROM sites')
|
||||
site_count = c.fetchone()[0]
|
||||
|
||||
print("=" * 80)
|
||||
print("📊 ESTADO ACTUAL DE LA BASE DE DATOS")
|
||||
print("=" * 80)
|
||||
print(f"Usuarios: {user_count}")
|
||||
print(f"Sitios: {site_count}")
|
||||
print("=" * 80)
|
||||
|
||||
respuesta = input("\n⚠️ ¿Estás seguro de que quieres limpiar TODA la base de datos? (escribe 'SI' para confirmar): ")
|
||||
|
||||
if respuesta.upper() != 'SI':
|
||||
print("❌ Operación cancelada")
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# Limpiar en orden (respetando foreign keys)
|
||||
print("\n🗑️ Limpiando base de datos...")
|
||||
c.execute('DELETE FROM widgets')
|
||||
c.execute('DELETE FROM menus')
|
||||
c.execute('DELETE FROM media')
|
||||
c.execute('DELETE FROM content')
|
||||
c.execute('DELETE FROM settings')
|
||||
c.execute('DELETE FROM requests')
|
||||
c.execute('DELETE FROM sites')
|
||||
c.execute('DELETE FROM users')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("✅ Base de datos limpiada exitosamente")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == '__main__':
|
||||
limpiar_db()
|
||||
@@ -0,0 +1,2 @@
|
||||
Flask==2.3.3
|
||||
Werkzeug==2.3.7
|
||||
5
backups/backup-20260114-085602/local/demo/run.sh
Normal file
5
backups/backup-20260114-085602/local/demo/run.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
echo "🚀 Iniciando Demo SaaS..."
|
||||
echo ""
|
||||
python3 app.py
|
||||
8
backups/backup-20260114-085602/local/demo/start.bat
Normal file
8
backups/backup-20260114-085602/local/demo/start.bat
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
echo Instalando dependencias...
|
||||
pip install Flask Werkzeug --quiet
|
||||
echo.
|
||||
echo Iniciando servidor en puerto 5001...
|
||||
echo.
|
||||
python app.py
|
||||
pause
|
||||
27
backups/backup-20260114-085602/local/demo/start.sh
Normal file
27
backups/backup-20260114-085602/local/demo/start.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Script para iniciar el demo desde WSL
|
||||
|
||||
echo "🚀 Iniciando Demo SaaS..."
|
||||
echo ""
|
||||
|
||||
# Verificar Python
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ Python3 no encontrado"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Instalar dependencias si no están
|
||||
if ! python3 -c "import flask" 2>/dev/null; then
|
||||
echo "📦 Instalando dependencias..."
|
||||
pip3 install Flask Werkzeug --quiet
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Todo listo"
|
||||
echo "📍 Servidor en: http://localhost:5001"
|
||||
echo ""
|
||||
echo "Presiona Ctrl+C para detener"
|
||||
echo ""
|
||||
|
||||
# Ejecutar
|
||||
python3 app.py
|
||||
2409
backups/backup-20260114-085602/local/demo/static/style.css
Normal file
2409
backups/backup-20260114-085602/local/demo/static/style.css
Normal file
File diff suppressed because it is too large
Load Diff
113
backups/backup-20260114-085602/local/demo/templates/admin.html
Normal file
113
backups/backup-20260114-085602/local/demo/templates/admin.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Demo</title>
|
||||
<style>
|
||||
* { 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 {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.btn {
|
||||
padding: 5px 15px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔧 Panel Admin</h1>
|
||||
</div>
|
||||
|
||||
<h2>Solicitudes Pendientes</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Slug</th>
|
||||
<th>Email Cliente</th>
|
||||
<th>Fecha</th>
|
||||
<th>Acción</th>
|
||||
</tr>
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td>{{ req.id }}</td>
|
||||
<td>{{ req.slug }}</td>
|
||||
<td>{{ req.email }}</td>
|
||||
<td>{{ req.created_at }}</td>
|
||||
<td>
|
||||
<button class="btn" onclick="approve({{ req.id }})">✅ Aprobar</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not requests %}
|
||||
<tr><td colspan="5">No hay solicitudes pendientes</td></tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<h2>Todos los Sitios</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Slug</th>
|
||||
<th>Tema</th>
|
||||
<th>Status</th>
|
||||
<th>Ver</th>
|
||||
</tr>
|
||||
{% for site in sites %}
|
||||
<tr>
|
||||
<td>{{ site.id }}</td>
|
||||
<td>{{ site.slug }}</td>
|
||||
<td>{{ site.theme }}</td>
|
||||
<td>{{ site.status }}</td>
|
||||
<td>
|
||||
{% if site.status == 'published' %}
|
||||
<a href="/site/{{ site.slug }}" target="_blank">Ver</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,311 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Cliente</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: #f0f0f1;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.header h1 {
|
||||
color: #1d2327;
|
||||
font-size: 24px;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-size: 14px;
|
||||
color: #50575e;
|
||||
}
|
||||
.tab.active {
|
||||
color: #2271b1;
|
||||
border-bottom-color: #2271b1;
|
||||
}
|
||||
.tab-content {
|
||||
display: none;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
background: #2271b1;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn:hover { background: #135e96; }
|
||||
.btn-danger { background: #d63638; }
|
||||
.btn-danger:hover { background: #b32d2e; }
|
||||
.btn-success { background: #00a32a; }
|
||||
.btn-success:hover { background: #008a20; }
|
||||
.upload-area {
|
||||
border: 2px dashed #c3c4c7;
|
||||
border-radius: 4px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
background: #f6f7f7;
|
||||
}
|
||||
.upload-area:hover {
|
||||
border-color: #2271b1;
|
||||
background: #f0f6fc;
|
||||
}
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.media-item {
|
||||
position: relative;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.media-item img {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.media-item .actions {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
background: #f6f7f7;
|
||||
}
|
||||
.media-item .actions button {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #8c8f94;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: #2271b1;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #2271b1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>⚙️ Panel de Administración</h1>
|
||||
<div>
|
||||
<a href="/dashboard" class="btn">← Volver al Dashboard</a>
|
||||
<a href="/logout" class="btn btn-danger">Salir</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="showTab('media')">📷 Media</button>
|
||||
<button class="tab" onclick="showTab('settings')">⚙️ Configuración</button>
|
||||
<button class="tab" onclick="showTab('sites')">🌐 Mis Sitios</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Media -->
|
||||
<div id="tab-media" class="tab-content active">
|
||||
<h2>Gestionar Imágenes</h2>
|
||||
<div class="upload-area" onclick="document.getElementById('fileInput').click()">
|
||||
<p style="font-size: 18px; color: #50575e; margin-bottom: 10px;">📤 Arrastra imágenes aquí o haz clic para subir</p>
|
||||
<p style="font-size: 12px; color: #8c8f94;">Formatos: JPG, PNG, GIF (máx. 5MB)</p>
|
||||
<input type="file" id="fileInput" multiple accept="image/*" onchange="uploadFiles(this.files)">
|
||||
</div>
|
||||
<div id="mediaGrid" class="media-grid">
|
||||
<!-- Las imágenes se cargarán aquí -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Configuración -->
|
||||
<div id="tab-settings" class="tab-content">
|
||||
<h2>Configuración de Cuenta</h2>
|
||||
<form id="settingsForm">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" id="user_email" value="{{ user_email }}" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Plan</label>
|
||||
<input type="text" id="user_plan" value="{{ user_plan }}" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cambiar Contraseña</label>
|
||||
<input type="password" id="new_password" placeholder="Nueva contraseña">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">💾 Guardar Cambios</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tab Sitios -->
|
||||
<div id="tab-sites" class="tab-content">
|
||||
<h2>Mis Sitios</h2>
|
||||
<div style="display: grid; gap: 15px; margin-top: 20px;">
|
||||
{% for site in sites %}
|
||||
<div style="background: #f6f7f7; padding: 15px; border-radius: 4px; border: 1px solid #c3c4c7;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<h3 style="margin-bottom: 5px;">{{ site.slug }}</h3>
|
||||
<p style="color: #50575e; font-size: 14px;">Tema: {{ site.theme }} | Estado: <span style="padding: 3px 8px; background: {% if site.status == 'published' %}#00a32a{% elif site.status == 'pending' %}#dba617{% else %}#d63638{% endif %}; color: white; border-radius: 3px; font-size: 12px;">{{ site.status }}</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/customizer/{{ site.id }}" class="btn">✏️ Editar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showTab(tabName) {
|
||||
// Ocultar todos los tabs
|
||||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
document.querySelectorAll('.tab').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
|
||||
// Mostrar el tab seleccionado
|
||||
document.getElementById('tab-' + tabName).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
function uploadFiles(files) {
|
||||
const formData = new FormData();
|
||||
for (let file of files) {
|
||||
formData.append('files', file);
|
||||
}
|
||||
|
||||
fetch('/api/admin/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Imágenes subidas correctamente');
|
||||
loadMedia();
|
||||
} else {
|
||||
alert('❌ Error: ' + data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadMedia() {
|
||||
fetch('/api/admin/media')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const grid = document.getElementById('mediaGrid');
|
||||
grid.innerHTML = '';
|
||||
data.media.forEach(item => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'media-item';
|
||||
div.innerHTML = `
|
||||
<img src="/uploads/${item.filename}" alt="${item.filename}">
|
||||
<div class="actions">
|
||||
<button class="btn" onclick="copyUrl('${item.filename}')">📋 URL</button>
|
||||
<button class="btn btn-danger" onclick="deleteMedia(${item.id})">🗑️</button>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(div);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyUrl(filename) {
|
||||
const url = window.location.origin + '/uploads/' + filename;
|
||||
navigator.clipboard.writeText(url);
|
||||
alert('✅ URL copiada: ' + url);
|
||||
}
|
||||
|
||||
function deleteMedia(id) {
|
||||
if (confirm('¿Eliminar esta imagen?')) {
|
||||
fetch(`/api/admin/media/${id}`, {method: 'DELETE'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadMedia();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cargar media al iniciar
|
||||
loadMedia();
|
||||
|
||||
// Guardar configuración
|
||||
document.getElementById('settingsForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const password = document.getElementById('new_password').value;
|
||||
if (password) {
|
||||
fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({password: password})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Contraseña actualizada');
|
||||
document.getElementById('new_password').value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Crear Sitio - Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
h1 { margin-bottom: 20px; }
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn {
|
||||
padding: 12px 30px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover { background: #5568d3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>➕ Crear Nuevo Sitio</h1>
|
||||
<form id="createForm">
|
||||
<input type="text" id="site_name" placeholder="Nombre del sitio" required>
|
||||
<input type="text" id="slug" placeholder="Slug (URL)" required>
|
||||
<input type="text" id="hero_title" placeholder="Título principal" required>
|
||||
<textarea id="hero_description" placeholder="Descripción" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 15px;"></textarea>
|
||||
<select id="theme">
|
||||
<option value="default">Tema Default</option>
|
||||
<option value="modern">Tema Moderno</option>
|
||||
</select>
|
||||
<button type="submit" class="btn">Crear Sitio</button>
|
||||
</form>
|
||||
<a href="/dashboard" style="display: inline-block; margin-top: 15px; color: #667eea;">← Volver</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('createForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
site_name: document.getElementById('site_name').value,
|
||||
slug: document.getElementById('slug').value,
|
||||
hero_title: document.getElementById('hero_title').value,
|
||||
hero_description: document.getElementById('hero_description').value,
|
||||
theme: document.getElementById('theme').value
|
||||
};
|
||||
|
||||
const res = await fetch('/dashboard/create', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
window.location.href = `/customizer/${result.site_id}`;
|
||||
} else {
|
||||
alert(result.error || 'Error al crear sitio');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,467 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Personalizar - {{ site_name or 'Sitio' }}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
/* Sidebar de personalización */
|
||||
.sidebar {
|
||||
width: 350px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #c3c4c7;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
background: #f6f7f7;
|
||||
}
|
||||
|
||||
.sidebar-header h2 {
|
||||
font-size: 20px;
|
||||
color: #1d2327;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.control-section {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.control-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.control-section h3 {
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
color: #50575e;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1d2327;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-group input[type="text"],
|
||||
.control-group textarea,
|
||||
.control-group select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #8c8f94;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.control-group input[type="text"]:focus,
|
||||
.control-group textarea:focus,
|
||||
.control-group select:focus {
|
||||
outline: none;
|
||||
border-color: #2271b1;
|
||||
box-shadow: 0 0 0 1px #2271b1;
|
||||
}
|
||||
|
||||
.control-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.color-picker-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.color-picker-group input[type="color"] {
|
||||
width: 50px;
|
||||
height: 40px;
|
||||
border: 1px solid #8c8f94;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.color-picker-group input[type="text"] {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
padding: 15px 20px;
|
||||
background: #f6f7f7;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-header h3 {
|
||||
font-size: 14px;
|
||||
color: #1d2327;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.preview-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #2271b1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #135e96;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #00a32a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #008a20;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #d63638;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #b32d2e;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #c3c4c7;
|
||||
background: #f6f7f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #50575e;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
background: #00a32a;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
display: block;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>⚙️ Personalizar Sitio</h2>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<!-- Contenido -->
|
||||
<div class="control-section">
|
||||
<h3>Contenido</h3>
|
||||
<div class="control-group">
|
||||
<label>Nombre del Sitio</label>
|
||||
<input type="text" id="site_name" placeholder="Mi Restaurante" value="{{ content.site_name or '' }}">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Título Principal</label>
|
||||
<input type="text" id="hero_title" placeholder="Bienvenido" value="{{ content.hero_title or '' }}">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Descripción</label>
|
||||
<textarea id="hero_description" placeholder="Descripción de tu restaurante...">{{ content.hero_description or '' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colores -->
|
||||
<div class="control-section">
|
||||
<h3>Colores</h3>
|
||||
<div class="control-group">
|
||||
<label>Color Primario</label>
|
||||
<div class="color-picker-group">
|
||||
<input type="color" id="color_primary" value="{{ content.colors.primary if content.colors else '#d32f2f' }}">
|
||||
<input type="text" id="color_primary_text" value="{{ content.colors.primary if content.colors else '#d32f2f' }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Color Secundario</label>
|
||||
<div class="color-picker-group">
|
||||
<input type="color" id="color_secondary" value="{{ content.colors.secondary if content.colors else '#ff6f00' }}">
|
||||
<input type="text" id="color_secondary_text" value="{{ content.colors.secondary if content.colors else '#ff6f00' }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% if theme == 'restaurante-moderno' or theme == 'restaurante-elegante' %}
|
||||
<div class="control-group">
|
||||
<label>Color Acento</label>
|
||||
<div class="color-picker-group">
|
||||
<input type="color" id="color_accent" value="{{ content.colors.accent if content.colors and content.colors.accent else '#ff8f00' }}">
|
||||
<input type="text" id="color_accent_text" value="{{ content.colors.accent if content.colors and content.colors.accent else '#ff8f00' }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="control-group">
|
||||
<label>Color de Texto</label>
|
||||
<div class="color-picker-group">
|
||||
<input type="color" id="color_text" value="{{ content.colors.text if content.colors else '#2c2c2c' }}">
|
||||
<input type="text" id="color_text_text" value="{{ content.colors.text if content.colors else '#2c2c2c' }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tipografía -->
|
||||
<div class="control-section">
|
||||
<h3>Tipografía</h3>
|
||||
<div class="control-group">
|
||||
<label>Familia de Fuente</label>
|
||||
<select id="font_family">
|
||||
<option value="Roboto" {% if content.typography and content.typography.font_family == 'Roboto' %}selected{% endif %}>Roboto</option>
|
||||
<option value="Georgia" {% if content.typography and content.typography.font_family == 'Georgia' %}selected{% endif %}>Georgia</option>
|
||||
<option value="Playfair Display" {% if content.typography and content.typography.font_family == 'Playfair Display' %}selected{% endif %}>Playfair Display</option>
|
||||
<option value="Cormorant Garamond" {% if content.typography and content.typography.font_family == 'Cormorant Garamond' %}selected{% endif %}>Cormorant Garamond</option>
|
||||
<option value="Arial" {% if content.typography and content.typography.font_family == 'Arial' %}selected{% endif %}>Arial</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menús -->
|
||||
<div class="control-section">
|
||||
<h3>Menús</h3>
|
||||
<div class="control-group">
|
||||
<label>Gestionar Menús</label>
|
||||
<button class="btn" style="background: #f0f0f1; color: #1d2327; width: 100%; margin-top: 5px;" onclick="gestionarMenus()">📋 Gestionar Menús</button>
|
||||
<p style="font-size: 11px; color: #50575e; margin-top: 5px;">Configura menús para header, footer y sidebar</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<button class="btn btn-primary btn-block" onclick="saveChanges()">💾 Guardar Cambios</button>
|
||||
<button class="btn btn-success btn-block" onclick="submitSite()">📤 Enviar para Aprobación</button>
|
||||
<a href="/dashboard" class="btn btn-block" style="background: #50575e; color: #fff; text-align: center;">← Volver al Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-container">
|
||||
<div class="preview-header">
|
||||
<h3>Vista Previa</h3>
|
||||
<div class="preview-actions">
|
||||
<button class="btn" style="background: #f0f0f1; color: #1d2327;" onclick="refreshPreview()">🔄 Actualizar</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview">
|
||||
<div class="loading" id="loading">Cargando vista previa...</div>
|
||||
<iframe id="preview-iframe" style="width: 100%; height: 100%; border: none;" src="/api/customizer/preview-frame/{{ site_id }}" onload="document.getElementById('loading').style.display='none'"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notification" id="notification">✅ Cambios guardados</div>
|
||||
|
||||
<script>
|
||||
const siteId = {{ site_id }};
|
||||
const theme = '{{ theme }}';
|
||||
|
||||
// Sincronizar color picker con input de texto
|
||||
function syncColorInputs() {
|
||||
document.querySelectorAll('input[type="color"]').forEach(colorInput => {
|
||||
const textInput = document.getElementById(colorInput.id + '_text');
|
||||
if (textInput) {
|
||||
colorInput.addEventListener('input', () => {
|
||||
textInput.value = colorInput.value;
|
||||
updatePreview();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncColorInputs();
|
||||
|
||||
// Actualizar preview cuando cambian los campos
|
||||
function updatePreview() {
|
||||
const content = {
|
||||
site_name: document.getElementById('site_name').value,
|
||||
hero_title: document.getElementById('hero_title').value,
|
||||
hero_description: document.getElementById('hero_description').value,
|
||||
colors: {
|
||||
primary: document.getElementById('color_primary').value,
|
||||
secondary: document.getElementById('color_secondary').value,
|
||||
text: document.getElementById('color_text').value
|
||||
},
|
||||
typography: {
|
||||
font_family: document.getElementById('font_family').value
|
||||
}
|
||||
};
|
||||
|
||||
// Agregar accent si existe
|
||||
const accentInput = document.getElementById('color_accent');
|
||||
if (accentInput) {
|
||||
content.colors.accent = accentInput.value;
|
||||
}
|
||||
|
||||
// Guardar cambios y recargar iframe
|
||||
fetch('/api/customizer/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({site_id: siteId, content: content})
|
||||
})
|
||||
.then(() => {
|
||||
refreshPreview();
|
||||
});
|
||||
}
|
||||
|
||||
function refreshPreview() {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('preview-iframe').src = '/api/customizer/preview-frame/' + siteId + '?t=' + Date.now();
|
||||
}
|
||||
|
||||
function gestionarMenus() {
|
||||
alert('📋 Gestión de menús próximamente disponible.\n\nPor ahora los menús se crean automáticamente al registrar.\nPuedes editarlos desde el código o esperar a la próxima actualización.');
|
||||
}
|
||||
|
||||
// Escuchar cambios en los campos
|
||||
document.querySelectorAll('#site_name, #hero_title, #hero_description, #color_primary, #color_secondary, #color_text, #font_family').forEach(el => {
|
||||
el.addEventListener('input', updatePreview);
|
||||
el.addEventListener('change', updatePreview);
|
||||
});
|
||||
|
||||
const accentInput = document.getElementById('color_accent');
|
||||
if (accentInput) {
|
||||
accentInput.addEventListener('input', updatePreview);
|
||||
accentInput.addEventListener('change', updatePreview);
|
||||
}
|
||||
|
||||
function saveChanges() {
|
||||
updatePreview();
|
||||
showNotification('✅ Cambios guardados');
|
||||
}
|
||||
|
||||
function submitSite() {
|
||||
if (confirm('¿Enviar sitio para aprobación? Una vez enviado, esperarás la aprobación del administrador.')) {
|
||||
fetch(`/dashboard/submit/${siteId}`, {method: 'POST'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('✅ Sitio enviado para aprobación');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showNotification(message) {
|
||||
const notif = document.getElementById('notification');
|
||||
notif.textContent = message;
|
||||
notif.classList.add('show');
|
||||
setTimeout(() => {
|
||||
notif.classList.remove('show');
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dashboard - Demo</title>
|
||||
<style>
|
||||
* { 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;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.sites {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.site-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.status.draft { background: #ffc107; }
|
||||
.status.pending { background: #ff9800; }
|
||||
.status.published { background: #4caf50; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>📊 Mi Panel de Control</h1>
|
||||
<div>
|
||||
<a href="/dashboard/admin" class="btn" style="background: #28a745;">⚙️ Admin</a>
|
||||
<a href="/dashboard/create" class="btn">➕ Crear Sitio</a>
|
||||
<a href="/logout" class="btn" style="background: #ff4d4d;">Salir</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sites">
|
||||
{% for site in sites %}
|
||||
<div class="site-card">
|
||||
<h3>{{ site.slug }}</h3>
|
||||
<p>Tema: {{ site.theme }}</p>
|
||||
<span class="status {{ site.status }}">{{ site.status }}</span>
|
||||
<div style="margin-top: 15px;">
|
||||
<a href="/customizer/{{ site.id }}" class="btn">✏️ Editar</a>
|
||||
{% if site.status == 'draft' %}
|
||||
<button onclick="submitSite({{ site.id }})" class="btn" style="background: #ff9800; border: none; cursor: pointer;">📤 Enviar</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if not sites %}
|
||||
<div class="site-card">
|
||||
<p>No tienes sitios aún. <a href="/dashboard/create">Crear uno</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function submitSite(siteId) {
|
||||
if (confirm('¿Enviar sitio para aprobación?')) {
|
||||
fetch(`/dashboard/submit/${siteId}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Sitio enviado para aprobación');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.error || 'Error al enviar'));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error:', err);
|
||||
alert('❌ Error de conexión');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>PageBuilder SaaS - Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
}
|
||||
h1 { color: #333; margin-bottom: 20px; }
|
||||
p { color: #666; margin-bottom: 30px; }
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
.btn:hover { background: #5568d3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎨 PageBuilder SaaS</h1>
|
||||
<p>Crea tu sitio web en minutos</p>
|
||||
<a href="/register" class="btn">Registrarse</a>
|
||||
<a href="/login" class="btn">Iniciar Sesión</a>
|
||||
<a href="/admin" class="btn" style="background: #ff4d4d;">Admin</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login - Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
h1 { margin-bottom: 20px; color: #333; }
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
.btn:hover { background: #5568d3; }
|
||||
a { color: #667eea; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔐 Iniciar Sesión</h1>
|
||||
<form id="loginForm">
|
||||
<input type="email" id="email" placeholder="Email" required>
|
||||
<input type="password" id="password" placeholder="Contraseña" required>
|
||||
<button type="submit" class="btn">Iniciar Sesión</button>
|
||||
</form>
|
||||
<p style="text-align: center; margin-top: 15px;">
|
||||
<a href="/register">¿No tienes cuenta? Regístrate</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
email: document.getElementById('email').value,
|
||||
password: document.getElementById('password').value
|
||||
};
|
||||
|
||||
const res = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await res.json();
|
||||
} catch (e) {
|
||||
const text = await res.text();
|
||||
console.error('❌ Servidor devolvió HTML:', text.substring(0, 200));
|
||||
alert('❌ Error del servidor. Revisa la consola.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
window.location.href = result.redirect || '/dashboard';
|
||||
} else {
|
||||
alert('❌ ' + (result.error || 'Error al iniciar sesión'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ content.site_name or 'Mi Sitio' }}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: {{ content.typography.font_family if content.typography else 'Arial' }};
|
||||
background: white;
|
||||
padding: 40px;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
color: {{ content.colors.primary if content.colors else '#ff4d4d' }};
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
p {
|
||||
color: {{ content.colors.text if content.colors else '#333' }};
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>{{ content.hero_title or 'Título' }}</h1>
|
||||
<p>{{ content.hero_description or 'Descripción' }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,132 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Registro - Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
h1 { margin-bottom: 20px; color: #333; }
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
.btn:hover { background: #5568d3; }
|
||||
a { color: #667eea; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📝 Registrarse</h1>
|
||||
<form id="registerForm">
|
||||
<input type="email" id="email" placeholder="Email" required>
|
||||
<input type="password" id="password" placeholder="Contraseña" required>
|
||||
<select id="plan">
|
||||
<option value="base" {% if plan == 'base' %}selected{% endif %}>Base</option>
|
||||
<option value="pro" {% if plan == 'pro' %}selected{% endif %}>Pro</option>
|
||||
<option value="premium" {% if plan == 'premium' %}selected{% endif %}>Premium</option>
|
||||
</select>
|
||||
<select id="rubro">
|
||||
<option value="gimnasio" {% if rubro == 'gimnasio' or rubro == 'gimnasios' %}selected{% endif %}>Gimnasio</option>
|
||||
<option value="restaurante" {% if rubro == 'restaurante' %}selected{% endif %}>Restaurante</option>
|
||||
<option value="danza" {% if rubro == 'danza' %}selected{% endif %}>Danza</option>
|
||||
<option value="cosmeticos" {% if rubro == 'cosmeticos' %}selected{% endif %}>Cosméticos</option>
|
||||
<option value="despachos" {% if rubro == 'despachos' %}selected{% endif %}>Despachos</option>
|
||||
<option value="educacion" {% if rubro == 'educacion' %}selected{% endif %}>Educación</option>
|
||||
<option value="tienda" {% if rubro == 'tienda' %}selected{% endif %}>Tienda</option>
|
||||
</select>
|
||||
<button type="submit" class="btn">Registrarse</button>
|
||||
</form>
|
||||
<p style="text-align: center; margin-top: 15px;">
|
||||
<a href="/login">¿Ya tienes cuenta? Inicia sesión</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('registerForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const plan = document.getElementById('plan').value;
|
||||
const rubro = document.getElementById('rubro').value;
|
||||
|
||||
if (!email || !password) {
|
||||
alert('❌ Por favor completa email y contraseña');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
email: email,
|
||||
password: password,
|
||||
plan: plan,
|
||||
rubro: rubro
|
||||
};
|
||||
|
||||
console.log('Enviando registro:', data);
|
||||
|
||||
try {
|
||||
const res = await fetch('/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
console.log('Respuesta del servidor:', res.status, res.statusText);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await res.json();
|
||||
} catch (e) {
|
||||
// Si no es JSON, el servidor devolvió HTML (error 500)
|
||||
const text = await res.text();
|
||||
console.error('❌ Servidor devolvió HTML en vez de JSON:', text.substring(0, 200));
|
||||
alert('❌ Error del servidor. Revisa la consola para más detalles.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Resultado:', result);
|
||||
|
||||
if (result.success) {
|
||||
// Registro exitoso - mostrar mensaje y redirigir al login
|
||||
alert(result.message || '✅ Registro exitoso. Por favor inicia sesión.');
|
||||
window.location.href = result.redirect || '/login';
|
||||
} else {
|
||||
alert('❌ Error: ' + (result.error || 'Error al registrarse'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error en registro:', error);
|
||||
alert('❌ Error de conexión. Verifica la consola para más detalles.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
22
backups/backup-20260114-085602/local/demo/test.py
Normal file
22
backups/backup-20260114-085602/local/demo/test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test rápido para verificar que todo funciona"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Agregar directorio actual al path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
try:
|
||||
from app import app
|
||||
print("✅ App importada correctamente")
|
||||
print("✅ Flask funcionando")
|
||||
print(f"✅ Templates en: {app.template_folder}")
|
||||
print("\n🚀 Para iniciar:")
|
||||
print(" python3 app.py")
|
||||
print("\n📍 URL: http://localhost:5001")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,22 @@
|
||||
</main>
|
||||
|
||||
<footer class="gk-footer" style="background: var(--text); color: var(--white); text-align: center; padding: 40px 20px; margin-top: 60px;">
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
{% if menus.footer %}
|
||||
<nav style="margin-bottom: 20px;">
|
||||
<ul style="display: flex; justify-content: center; list-style: none; gap: 20px; flex-wrap: wrap;">
|
||||
{% for menu_item in menus.footer %}
|
||||
<li><a href="{{ menu_item.url }}" style="color: var(--white); text-decoration: none;">{{ menu_item.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<p style="margin: 0; opacity: 0.8;">
|
||||
© 2025 GKACHELE™. Todos los derechos reservados.<br>
|
||||
Desarrollado desde noviembre 2025 por GKACHELE<br>
|
||||
Código propiedad de GKACHELE © 2025 - Prohibida su reproducción sin autorización
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
GKACHELE™ Template System
|
||||
© 2025 GKACHELE™. Todos los derechos reservados.
|
||||
Desarrollado desde noviembre 2025 por GKACHELE
|
||||
Código propiedad de GKACHELE © 2025 - Prohibida su reproducción sin autorización
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% if site_name %}{{ site_name }}{% else %}GKACHELE Site{% endif %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family={{ typography.font_family|replace(' ', '+') }}:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: {{ colors.primary or '#d32f2f' }};
|
||||
--secondary: {{ colors.secondary or '#ff6f00' }};
|
||||
--text: {{ colors.text or '#2c2c2c' }};
|
||||
--bg-light: #f9f9f9;
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: '{{ typography.font_family or 'Roboto' }}', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Header / Navigation */
|
||||
.gk-header {
|
||||
background: var(--white);
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.gk-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gk-logo {
|
||||
font-size: 28px;
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.gk-menu {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.gk-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.gk-menu a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.gk-menu .current-menu-item a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.gk-main {
|
||||
margin-top: 80px;
|
||||
min-height: calc(100vh - 160px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gk-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="gk-header">
|
||||
<nav class="gk-nav">
|
||||
<a href="/" class="gk-logo">{{ site_name or 'GKACHELE Site' }}</a>
|
||||
{% if menus %}
|
||||
<ul class="gk-menu">
|
||||
{% for menu_item in menus.header %}
|
||||
<li class="{% if menu_item.current %}current-menu-item{% endif %}">
|
||||
<a href="{{ menu_item.url }}">{{ menu_item.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
<main class="gk-main">
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
GKACHELE™ Sidebar Widget Area
|
||||
© 2025 GKACHELE™. Todos los derechos reservados.
|
||||
-->
|
||||
<aside class="gk-sidebar" style="width: 300px; padding: 20px; background: var(--bg-light);">
|
||||
{% if widgets %}
|
||||
{% for widget in widgets %}
|
||||
<div class="gk-widget" style="margin-bottom: 30px; padding: 20px; background: var(--white); border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.05);">
|
||||
{% if widget.title %}
|
||||
<h3 style="color: var(--primary); margin-bottom: 15px; font-size: 18px;">{{ widget.title }}</h3>
|
||||
{% endif %}
|
||||
<div class="gk-widget-content">
|
||||
{{ widget.content|safe }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if menus.sidebar %}
|
||||
<div class="gk-widget gk-menu-widget" style="margin-bottom: 30px; padding: 20px; background: var(--white); border-radius: 5px;">
|
||||
<h3 style="color: var(--primary); margin-bottom: 15px; font-size: 18px;">Menú</h3>
|
||||
<ul style="list-style: none; padding: 0;">
|
||||
{% for menu_item in menus.sidebar %}
|
||||
<li style="margin-bottom: 10px;">
|
||||
<a href="{{ menu_item.url }}" style="color: var(--text); text-decoration: none; transition: color 0.3s;">
|
||||
{{ menu_item.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</aside>
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Gimnasio Claro",
|
||||
"rubro": "gimnasios",
|
||||
"description": "Tema claro y moderno para gimnasios",
|
||||
"sections": [
|
||||
"hero",
|
||||
"planes_fitness",
|
||||
"horarios",
|
||||
"entrenadores",
|
||||
"instalaciones",
|
||||
"contacto"
|
||||
],
|
||||
"colors": {
|
||||
"primary": "#ff4d4d",
|
||||
"secondary": "#1a1a1a",
|
||||
"accent": "#ff6b6b"
|
||||
},
|
||||
"typography": {
|
||||
"font_family": "Roboto",
|
||||
"headings": "Roboto"
|
||||
},
|
||||
"features": {
|
||||
"planes_fitness": true,
|
||||
"horarios": true,
|
||||
"entrenadores": true,
|
||||
"instalaciones": true,
|
||||
"precios": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Restaurante Elegante",
|
||||
"rubro": "restaurante",
|
||||
"description": "Tema sofisticado y elegante para restaurantes de alta cocina",
|
||||
"sections": [
|
||||
"hero",
|
||||
"menu",
|
||||
"horarios",
|
||||
"reservas",
|
||||
"especialidad_culinaria",
|
||||
"galeria",
|
||||
"contacto"
|
||||
],
|
||||
"colors": {
|
||||
"primary": "#8b4513",
|
||||
"secondary": "#d4af37",
|
||||
"accent": "#f5deb3",
|
||||
"text": "#2c2c2c"
|
||||
},
|
||||
"typography": {
|
||||
"font_family": "Georgia",
|
||||
"headings": "Cormorant Garamond"
|
||||
},
|
||||
"features": {
|
||||
"menu_url": true,
|
||||
"horarios": true,
|
||||
"reservas": true,
|
||||
"capacidad": true,
|
||||
"especialidad_culinaria": true,
|
||||
"galeria": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name or 'Restaurante Elegante' }}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Georgia:wght@400;700&family=Cormorant+Garamond:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #8b4513;
|
||||
--secondary: #d4af37;
|
||||
--accent: #f5deb3;
|
||||
--text-dark: #2c2c2c;
|
||||
--text-light: #666;
|
||||
--bg-cream: #faf8f3;
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
color: var(--text-dark);
|
||||
line-height: 1.8;
|
||||
background: var(--bg-cream);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Cormorant Garamond', serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 100px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header Elegante */
|
||||
header {
|
||||
background: var(--white);
|
||||
box-shadow: 0 2px 20px rgba(0,0,0,0.08);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
border-bottom: 3px solid var(--secondary);
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25px 50px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: 'Cormorant Garamond', serif;
|
||||
font-size: 32px;
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.5px;
|
||||
transition: color 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-links a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--secondary);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.nav-links a:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Hero Elegante */
|
||||
.hero {
|
||||
background: linear-gradient(rgba(139, 69, 19, 0.85), rgba(139, 69, 19, 0.85)),
|
||||
url('https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=1920') center/cover;
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
padding: 200px 20px 150px;
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 72px;
|
||||
margin-bottom: 30px;
|
||||
color: var(--white);
|
||||
text-shadow: 2px 2px 10px rgba(0,0,0,0.3);
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 24px;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
max-width: 700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 18px 50px;
|
||||
background: var(--secondary);
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
border-radius: 0;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
transition: all 0.3s;
|
||||
border: 2px solid var(--secondary);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: transparent;
|
||||
color: var(--secondary);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 30px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
|
||||
/* Menu Elegante */
|
||||
.menu {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
color: var(--primary);
|
||||
margin-bottom: 80px;
|
||||
position: relative;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100px;
|
||||
height: 3px;
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 40px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
background: var(--bg-cream);
|
||||
padding: 40px;
|
||||
border: 1px solid #e8e5df;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 0;
|
||||
background: var(--secondary);
|
||||
transition: height 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:hover::before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
||||
border-color: var(--secondary);
|
||||
}
|
||||
|
||||
.menu-item h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 15px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.menu-item p {
|
||||
color: var(--text-light);
|
||||
margin-bottom: 20px;
|
||||
font-style: italic;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.menu-price {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--secondary);
|
||||
font-family: 'Cormorant Garamond', serif;
|
||||
}
|
||||
|
||||
/* Horarios Elegantes */
|
||||
.horarios {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.horarios .section-title {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.horarios .section-title::after {
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
.horarios-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.horario-item {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 40px;
|
||||
border: 1px solid rgba(212, 175, 55, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.horario-item h3 {
|
||||
color: var(--secondary);
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.horario-item p {
|
||||
font-size: 20px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Reservas Elegantes */
|
||||
.reservas {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.reservas-content {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reservas-form {
|
||||
display: grid;
|
||||
gap: 25px;
|
||||
margin-top: 50px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border: 2px solid #e8e5df;
|
||||
border-radius: 0;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
background: var(--bg-cream);
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: var(--secondary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
border: 2px solid var(--primary);
|
||||
padding: 18px 50px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: transparent;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Especialidad Elegante */
|
||||
.especialidad {
|
||||
background: var(--bg-cream);
|
||||
}
|
||||
|
||||
.especialidad-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.especialidad-text h2 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 30px;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.especialidad-text p {
|
||||
font-size: 18px;
|
||||
line-height: 2;
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.especialidad-image img {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||
border: 10px solid var(--white);
|
||||
}
|
||||
|
||||
/* Contacto Elegante */
|
||||
.contacto {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.contacto-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
.contacto-info h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 30px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.contacto-info p {
|
||||
color: var(--text-light);
|
||||
margin-bottom: 20px;
|
||||
font-size: 17px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.contacto-info strong {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.redes-sociales {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.redes-sociales a {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
color: var(--primary);
|
||||
font-size: 28px;
|
||||
transition: transform 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.redes-sociales a:hover {
|
||||
transform: scale(1.2);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* Footer Elegante */
|
||||
footer {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
padding: 50px 20px;
|
||||
border-top: 3px solid var(--secondary);
|
||||
}
|
||||
|
||||
footer p {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.hero h1 {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.especialidad-content,
|
||||
.contacto-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 60px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<nav>
|
||||
<div class="logo">{{ site_name or 'Restaurante Elegante' }}</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#inicio">Inicio</a></li>
|
||||
<li><a href="#menu">Menú</a></li>
|
||||
<li><a href="#horarios">Horarios</a></li>
|
||||
<li><a href="#reservas">Reservas</a></li>
|
||||
<li><a href="#contacto">Contacto</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section id="inicio" class="section hero">
|
||||
<div class="container">
|
||||
<h1>{{ hero_title or 'Experiencia Culinaria Excepcional' }}</h1>
|
||||
<p>{{ hero_description or 'Donde la tradición se encuentra con la innovación' }}</p>
|
||||
<a href="#reservas" class="btn">Reservar Mesa</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Menu Section -->
|
||||
<section id="menu" class="section menu">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Nuestro Menú Exquisito</h2>
|
||||
<div class="menu-grid">
|
||||
<div class="menu-item">
|
||||
<h3>Plato Especial del Chef</h3>
|
||||
<p>Una creación única que combina los mejores ingredientes con técnicas culinarias refinadas.</p>
|
||||
<div class="menu-price">€35.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Especialidad de la Casa</h3>
|
||||
<p>Nuestra receta tradicional, perfeccionada a lo largo de generaciones.</p>
|
||||
<div class="menu-price">€42.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Degustación Premium</h3>
|
||||
<p>Una experiencia gastronómica completa con los mejores sabores de nuestra cocina.</p>
|
||||
<div class="menu-price">€65.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Postre Artesanal</h3>
|
||||
<p>Dulces elaborados con ingredientes selectos y presentación impecable.</p>
|
||||
<div class="menu-price">€18.00</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if menu_url %}
|
||||
<div style="text-align: center; margin-top: 60px;">
|
||||
<a href="{{ menu_url }}" class="btn" target="_blank">Ver Menú Completo</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Horarios Section -->
|
||||
<section id="horarios" class="section horarios">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Horarios de Atención</h2>
|
||||
<div class="horarios-content">
|
||||
<div class="horario-item">
|
||||
<h3>Lunes - Viernes</h3>
|
||||
<p>{{ horarios.lunes_viernes or '13:00 - 23:00' }}</p>
|
||||
</div>
|
||||
<div class="horario-item">
|
||||
<h3>Sábados</h3>
|
||||
<p>{{ horarios.sabados or '13:00 - 00:00' }}</p>
|
||||
</div>
|
||||
<div class="horario-item">
|
||||
<h3>Domingos</h3>
|
||||
<p>{{ horarios.domingos or '13:00 - 22:00' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Reservas Section -->
|
||||
<section id="reservas" class="section reservas">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Reserva tu Mesa</h2>
|
||||
<div class="reservas-content">
|
||||
<p style="color: var(--text-light); margin-bottom: 40px; font-size: 18px; font-style: italic;">
|
||||
Complete el formulario y nos pondremos en contacto para confirmar su reserva.
|
||||
</p>
|
||||
<form class="reservas-form">
|
||||
<div class="form-group">
|
||||
<label>Nombre Completo</label>
|
||||
<input type="text" name="nombre" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Teléfono</label>
|
||||
<input type="tel" name="telefono" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Fecha</label>
|
||||
<input type="date" name="fecha" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hora</label>
|
||||
<input type="time" name="hora" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Número de Personas</label>
|
||||
<select name="personas" required>
|
||||
<option value="1">1 persona</option>
|
||||
<option value="2">2 personas</option>
|
||||
<option value="3">3 personas</option>
|
||||
<option value="4">4 personas</option>
|
||||
<option value="5+">5 o más personas</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Mensaje (opcional)</label>
|
||||
<textarea name="mensaje" rows="4"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Enviar Reserva</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Especialidad Culinaria Section -->
|
||||
<section class="section especialidad">
|
||||
<div class="container">
|
||||
<div class="especialidad-content">
|
||||
<div class="especialidad-text">
|
||||
<h2>{{ especialidad_culinaria.titulo or 'Nuestra Especialidad' }}</h2>
|
||||
<p>{{ especialidad_culinaria.descripcion or 'Cada plato es una obra de arte, preparado con ingredientes de la más alta calidad y técnicas culinarias refinadas que honran la tradición gastronómica.' }}</p>
|
||||
</div>
|
||||
<div class="especialidad-image">
|
||||
<img src="{{ especialidad_culinaria.imagen or 'https://via.placeholder.com/600x500?text=Especialidad' }}" alt="Especialidad Culinaria">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contacto Section -->
|
||||
<section id="contacto" class="section contacto">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Contacto</h2>
|
||||
<div class="contacto-content">
|
||||
<div class="contacto-info">
|
||||
<h3>Información de Contacto</h3>
|
||||
<p><strong>Dirección:</strong> {{ direccion or 'Avenida Principal 456, Ciudad' }}</p>
|
||||
<p><strong>Teléfono:</strong> {{ telefono or '+34 987 654 321' }}</p>
|
||||
<p><strong>Email:</strong> {{ email or 'contacto@restaurante.com' }}</p>
|
||||
{% if capacidad %}
|
||||
<p><strong>Capacidad:</strong> {{ capacidad }} personas</p>
|
||||
{% endif %}
|
||||
<div class="redes-sociales">
|
||||
{% if redes_sociales.facebook %}
|
||||
<a href="{{ redes_sociales.facebook }}" target="_blank">📘</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.instagram %}
|
||||
<a href="{{ redes_sociales.instagram }}" target="_blank">📷</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.twitter %}
|
||||
<a href="{{ redes_sociales.twitter }}" target="_blank">🐦</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.whatsapp %}
|
||||
<a href="https://wa.me/{{ redes_sociales.whatsapp }}" target="_blank">💬</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="contacto-info">
|
||||
<h3>Ubicación</h3>
|
||||
{% if mapa_url %}
|
||||
<iframe src="{{ mapa_url }}" width="100%" height="350" style="border:0; border: 2px solid #e8e5df;" allowfullscreen="" loading="lazy"></iframe>
|
||||
{% else %}
|
||||
<p style="color: var(--text-light); font-style: italic;">Mapa de ubicación disponible próximamente</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2025 {{ site_name or 'Restaurante Elegante' }}. Todos los derechos reservados.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Restaurante Moderno",
|
||||
"rubro": "restaurante",
|
||||
"description": "Tema elegante para restaurantes",
|
||||
"sections": [
|
||||
"hero",
|
||||
"menu",
|
||||
"horarios",
|
||||
"reservas",
|
||||
"especialidad_culinaria",
|
||||
"contacto"
|
||||
],
|
||||
"colors": {
|
||||
"primary": "#d32f2f",
|
||||
"secondary": "#ff6f00",
|
||||
"accent": "#ff8f00"
|
||||
},
|
||||
"typography": {
|
||||
"font_family": "Roboto",
|
||||
"headings": "Playfair Display"
|
||||
},
|
||||
"features": {
|
||||
"menu_url": true,
|
||||
"horarios": true,
|
||||
"reservas": true,
|
||||
"capacidad": true,
|
||||
"especialidad_culinaria": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Estilos adicionales para el tema Restaurante Moderno */
|
||||
/* Estos estilos complementan el template.html */
|
||||
|
||||
/* Animaciones suaves */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* Efectos hover mejorados */
|
||||
.menu-item,
|
||||
.horario-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Scroll suave */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Mejoras responsive */
|
||||
@media (max-width: 480px) {
|
||||
.hero h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name or 'Restaurante' }}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Playfair+Display:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #d32f2f;
|
||||
--secondary: #ff6f00;
|
||||
--accent: #ff8f00;
|
||||
--text-dark: #2c2c2c;
|
||||
--text-light: #666;
|
||||
--bg-light: #f9f9f9;
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: var(--text-dark);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Playfair Display', serif;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 80px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header / Navigation */
|
||||
header {
|
||||
background: var(--white);
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: 'Playfair Display', serif;
|
||||
font-size: 28px;
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
padding: 150px 20px 100px;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 20px;
|
||||
margin-bottom: 30px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 15px 40px;
|
||||
background: var(--accent);
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Menu Section */
|
||||
.menu {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
color: var(--primary);
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
background: var(--bg-light);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.menu-item h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 10px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.menu-item p {
|
||||
color: var(--text-light);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.menu-price {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* Horarios Section */
|
||||
.horarios {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.horarios-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.horario-item {
|
||||
background: var(--white);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.horario-item h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.horario-item p {
|
||||
color: var(--text-light);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Reservas Section */
|
||||
.reservas {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.reservas-content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reservas-form {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 15px 40px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
/* Especialidad Culinaria Section */
|
||||
.especialidad {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.especialidad-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 50px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.especialidad-text h2 {
|
||||
color: var(--white);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.especialidad-text p {
|
||||
font-size: 18px;
|
||||
opacity: 0.95;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.especialidad-image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.especialidad-image img {
|
||||
max-width: 100%;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Contacto Section */
|
||||
.contacto {
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.contacto-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
.contacto-info h3 {
|
||||
color: var(--primary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.contacto-info p {
|
||||
color: var(--text-light);
|
||||
margin-bottom: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.contacto-info strong {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.redes-sociales {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.redes-sociales a {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
color: var(--primary);
|
||||
font-size: 24px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.redes-sociales a:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background: var(--text-dark);
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.hero h1 {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.especialidad-content,
|
||||
.contacto-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 60px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<nav>
|
||||
<div class="logo">{{ site_name or 'Restaurante' }}</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#inicio">Inicio</a></li>
|
||||
<li><a href="#menu">Menú</a></li>
|
||||
<li><a href="#horarios">Horarios</a></li>
|
||||
<li><a href="#reservas">Reservas</a></li>
|
||||
<li><a href="#contacto">Contacto</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section id="inicio" class="section hero">
|
||||
<div class="container">
|
||||
<h1>{{ hero_title or 'Bienvenido a Nuestro Restaurante' }}</h1>
|
||||
<p>{{ hero_description or 'Sabores auténticos que despiertan tus sentidos' }}</p>
|
||||
<a href="#reservas" class="btn">Reservar Mesa</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Menu Section -->
|
||||
<section id="menu" class="section menu">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Nuestro Menú</h2>
|
||||
<div class="menu-grid">
|
||||
<div class="menu-item">
|
||||
<h3>Plato Especial 1</h3>
|
||||
<p>Descripción del plato con ingredientes frescos y sabores únicos.</p>
|
||||
<div class="menu-price">$25.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Plato Especial 2</h3>
|
||||
<p>Descripción del plato con ingredientes frescos y sabores únicos.</p>
|
||||
<div class="menu-price">$28.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Plato Especial 3</h3>
|
||||
<p>Descripción del plato con ingredientes frescos y sabores únicos.</p>
|
||||
<div class="menu-price">$30.00</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<h3>Plato Especial 4</h3>
|
||||
<p>Descripción del plato con ingredientes frescos y sabores únicos.</p>
|
||||
<div class="menu-price">$32.00</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if menu_url %}
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<a href="{{ menu_url }}" class="btn" target="_blank">Ver Menú Completo</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Horarios Section -->
|
||||
<section id="horarios" class="section horarios">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Horarios de Atención</h2>
|
||||
<div class="horarios-content">
|
||||
<div class="horario-item">
|
||||
<h3>Lunes - Viernes</h3>
|
||||
<p>{{ horarios.lunes_viernes or '12:00 PM - 10:00 PM' }}</p>
|
||||
</div>
|
||||
<div class="horario-item">
|
||||
<h3>Sábados</h3>
|
||||
<p>{{ horarios.sabados or '12:00 PM - 11:00 PM' }}</p>
|
||||
</div>
|
||||
<div class="horario-item">
|
||||
<h3>Domingos</h3>
|
||||
<p>{{ horarios.domingos or '12:00 PM - 9:00 PM' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Reservas Section -->
|
||||
<section id="reservas" class="section reservas">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Reserva tu Mesa</h2>
|
||||
<div class="reservas-content">
|
||||
<p style="color: var(--text-light); margin-bottom: 30px;">
|
||||
Completa el formulario y nos pondremos en contacto contigo para confirmar tu reserva.
|
||||
</p>
|
||||
<form class="reservas-form">
|
||||
<div class="form-group">
|
||||
<label>Nombre Completo</label>
|
||||
<input type="text" name="nombre" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Teléfono</label>
|
||||
<input type="tel" name="telefono" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Fecha</label>
|
||||
<input type="date" name="fecha" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hora</label>
|
||||
<input type="time" name="hora" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Número de Personas</label>
|
||||
<select name="personas" required>
|
||||
<option value="1">1 persona</option>
|
||||
<option value="2">2 personas</option>
|
||||
<option value="3">3 personas</option>
|
||||
<option value="4">4 personas</option>
|
||||
<option value="5+">5 o más personas</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Mensaje (opcional)</label>
|
||||
<textarea name="mensaje" rows="4"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Enviar Reserva</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Especialidad Culinaria Section -->
|
||||
<section class="section especialidad">
|
||||
<div class="container">
|
||||
<div class="especialidad-content">
|
||||
<div class="especialidad-text">
|
||||
<h2>{{ especialidad_culinaria.titulo or 'Nuestra Especialidad' }}</h2>
|
||||
<p>{{ especialidad_culinaria.descripcion or 'Cada plato es una obra de arte culinaria, preparado con ingredientes frescos y técnicas tradicionales que honran la autenticidad de nuestros sabores.' }}</p>
|
||||
</div>
|
||||
<div class="especialidad-image">
|
||||
<img src="{{ especialidad_culinaria.imagen or 'https://via.placeholder.com/500x400?text=Especialidad' }}" alt="Especialidad Culinaria">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contacto Section -->
|
||||
<section id="contacto" class="section contacto">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Contacto</h2>
|
||||
<div class="contacto-content">
|
||||
<div class="contacto-info">
|
||||
<h3>Información de Contacto</h3>
|
||||
<p><strong>Dirección:</strong> {{ direccion or 'Calle Principal 123, Ciudad' }}</p>
|
||||
<p><strong>Teléfono:</strong> {{ telefono or '+34 123 456 789' }}</p>
|
||||
<p><strong>Email:</strong> {{ email or 'contacto@restaurante.com' }}</p>
|
||||
{% if capacidad %}
|
||||
<p><strong>Capacidad:</strong> {{ capacidad }} personas</p>
|
||||
{% endif %}
|
||||
<div class="redes-sociales">
|
||||
{% if redes_sociales.facebook %}
|
||||
<a href="{{ redes_sociales.facebook }}" target="_blank">📘</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.instagram %}
|
||||
<a href="{{ redes_sociales.instagram }}" target="_blank">📷</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.twitter %}
|
||||
<a href="{{ redes_sociales.twitter }}" target="_blank">🐦</a>
|
||||
{% endif %}
|
||||
{% if redes_sociales.whatsapp %}
|
||||
<a href="https://wa.me/{{ redes_sociales.whatsapp }}" target="_blank">💬</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="contacto-info">
|
||||
<h3>Ubicación</h3>
|
||||
{% if mapa_url %}
|
||||
<iframe src="{{ mapa_url }}" width="100%" height="300" style="border:0; border-radius: 10px;" allowfullscreen="" loading="lazy"></iframe>
|
||||
{% else %}
|
||||
<p style="color: var(--text-light);">Mapa de ubicación disponible próximamente</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2025 {{ site_name or 'Restaurante' }}. Todos los derechos reservados.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
78
backups/backup-20260114-085602/local/demo/ver_usuarios.py
Normal file
78
backups/backup-20260114-085602/local/demo/ver_usuarios.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
GKACHELE™ - Script para ver usuarios registrados
|
||||
© 2025 GKACHELE™. Todos los derechos reservados.
|
||||
Uso: python ver_usuarios.py
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# Ruta de la base de datos
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
DATABASE_DIR = os.path.join(BASE_DIR, 'database')
|
||||
MAIN_DB = os.path.join(DATABASE_DIR, 'main.db')
|
||||
|
||||
def ver_usuarios():
|
||||
"""Ver todos los usuarios registrados"""
|
||||
if not os.path.exists(MAIN_DB):
|
||||
print(f"❌ No se encontró la base de datos en: {MAIN_DB}")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(MAIN_DB)
|
||||
c = conn.cursor()
|
||||
|
||||
# Obtener usuarios
|
||||
c.execute('SELECT id, email, plan, rubro, created_at FROM users ORDER BY id')
|
||||
users = c.fetchall()
|
||||
|
||||
if not users:
|
||||
print("📭 No hay usuarios registrados aún")
|
||||
return
|
||||
|
||||
print("=" * 80)
|
||||
print("👥 USUARIOS REGISTRADOS")
|
||||
print("=" * 80)
|
||||
print(f"{'ID':<5} {'Email':<35} {'Plan':<10} {'Rubro':<15} {'Fecha Registro':<20}")
|
||||
print("-" * 80)
|
||||
|
||||
for user in users:
|
||||
user_id, email, plan, rubro, created_at = user
|
||||
# Formatear fecha
|
||||
fecha = created_at if created_at else 'N/A'
|
||||
print(f"{user_id:<5} {email:<35} {plan:<10} {rubro:<15} {fecha:<20}")
|
||||
|
||||
print("-" * 80)
|
||||
print(f"Total: {len(users)} usuario(s)")
|
||||
|
||||
# Obtener sitios por usuario
|
||||
print("\n" + "=" * 80)
|
||||
print("🌐 SITIOS POR USUARIO")
|
||||
print("=" * 80)
|
||||
|
||||
for user in users:
|
||||
user_id, email, plan, rubro, created_at = user
|
||||
c.execute('SELECT id, slug, theme, status, created_at FROM sites WHERE user_id = ? ORDER BY id', (user_id,))
|
||||
sites = c.fetchall()
|
||||
|
||||
print(f"\n👤 Usuario ID {user_id} ({email}):")
|
||||
if sites:
|
||||
print(f" {'ID':<5} {'Slug':<25} {'Tema':<20} {'Estado':<12} {'Fecha':<20}")
|
||||
print(" " + "-" * 82)
|
||||
for site in sites:
|
||||
site_id, slug, theme, status, site_created = site
|
||||
fecha = site_created if site_created else 'N/A'
|
||||
print(f" {site_id:<5} {slug:<25} {theme:<20} {status:<12} {fecha:<20}")
|
||||
else:
|
||||
print(" ⚠️ No tiene sitios creados")
|
||||
|
||||
conn.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == '__main__':
|
||||
ver_usuarios()
|
||||
Reference in New Issue
Block a user