Files
gkachele-saas/backups/backup-20260114-085602/local/demo/app_simple.py
2026-01-17 11:40:17 +01:00

595 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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>&copy; 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)