feat: Add Dockerfile and initial Docker setup files

This commit is contained in:
komkida91
2026-01-31 16:04:55 +01:00
parent 70c533e755
commit 59812e547e
31 changed files with 7720 additions and 1776 deletions

View File

@@ -1,36 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Admin - Demo</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.header {
background: white;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
table {
width: 100%;
background: white;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #667eea;
color: white;
}
.btn {
padding: 5px 15px;
background: #4caf50;
@@ -41,11 +53,12 @@
}
</style>
</head>
<body>
<div class="header">
<h1>🔧 Panel Admin</h1>
</div>
<h2>Solicitudes Pendientes</h2>
<table>
<tr>
@@ -67,10 +80,12 @@
</tr>
{% endfor %}
{% if not requests %}
<tr><td colspan="5">No hay solicitudes pendientes</td></tr>
<tr>
<td colspan="5">No hay solicitudes pendientes</td>
</tr>
{% endif %}
</table>
<h2>👥 Usuarios Registrados</h2>
<table>
<tr>
@@ -104,10 +119,12 @@
</tr>
{% endfor %}
{% if not users %}
<tr><td colspan="9">No hay usuarios registrados</td></tr>
<tr>
<td colspan="9">No hay usuarios registrados</td>
</tr>
{% endif %}
</table>
<h2>🌐 Todos los Sitios</h2>
<table>
<tr>
@@ -131,48 +148,50 @@
</tr>
{% endfor %}
</table>
<style>
.btn-danger {
background: #d63638;
color: white;
}
.btn-danger:hover {
background: #b32d2e;
}
</style>
<script>
function approve(requestId) {
if (confirm('¿Aprobar este sitio?')) {
fetch(`/admin/approve/${requestId}`, {method: 'POST'})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('✅ Sitio aprobado');
location.reload();
}
});
fetch(`/admin/approve/${requestId}`, { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.success) {
alert('✅ Sitio aprobado');
location.reload();
}
});
}
}
function deleteUser(userId, email) {
if (confirm(`⚠️ ¿Eliminar usuario ${userId} (${email})?\n\nEsto eliminará TODOS sus datos:\n- Sitios\n- Menús\n- Widgets\n- Media\n- Solicitudes\n\nEsta acción NO se puede deshacer.`)) {
fetch(`/admin/users/delete/${userId}`, {method: 'POST'})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('✅ Usuario eliminado exitosamente');
location.reload();
} else {
alert('❌ Error: ' + (data.error || 'Error al eliminar'));
}
})
.catch(err => {
alert('❌ Error: ' + err);
});
fetch(`/admin/users/delete/${userId}`, { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.success) {
alert('✅ Usuario eliminado exitosamente');
location.reload();
} else {
alert('❌ Error: ' + (data.error || 'Error al eliminar'));
}
})
.catch(err => {
alert('❌ Error: ' + err);
});
}
}
</script>
</body>
</html>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,597 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GKACHELE Builder - Premium V3</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--bg-sidebar: #ffffff;
--bg-canvas: #f3f4f6;
--border: #e5e7eb;
--text-main: #111827;
--text-muted: #6b7280;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-canvas);
height: 100vh;
overflow: hidden;
display: flex;
}
/* SIDEBAR */
.sidebar {
width: 380px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.04);
z-index: 20;
}
.sidebar-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
font-weight: 800;
font-size: 18px;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #2563eb, #7c3aed);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 8px;
}
.logo i {
-webkit-text-fill-color: #2563eb;
}
.back-link {
color: var(--text-muted);
text-decoration: none;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 5px;
transition: 0.2s;
}
.back-link:hover {
color: var(--text-main);
}
.nav-sections {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.section-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-bottom: 8px;
background: #fff;
border: 1px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.section-item:hover {
border-color: var(--primary);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.08);
transform: translateY(-1px);
}
.section-info {
display: flex;
align-items: center;
gap: 12px;
}
.section-icon {
width: 36px;
height: 36px;
background: #f0f9ff;
color: #0369a1;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.section-title h3 {
font-size: 14px;
font-weight: 600;
color: var(--text-main);
margin-bottom: 2px;
}
.section-title p {
font-size: 12px;
color: var(--text-muted);
}
/* ACTIVE PANEL OVERLAY */
.panel-drawer {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 380px;
background: #fff;
transform: translateX(-100%);
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 30;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
box-shadow: 10px 0 40px rgba(0, 0, 0, 0.1);
}
.panel-drawer.active {
transform: translateX(0);
}
.drawer-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 15px;
}
.drawer-close {
border: none;
background: #f3f4f6;
width: 32px;
height: 32px;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #4b5563;
transition: 0.2s;
}
.drawer-close:hover {
background: #e5e7eb;
color: #111;
}
.drawer-title {
font-weight: 700;
font-size: 16px;
}
.drawer-content {
padding: 24px;
overflow-y: auto;
flex: 1;
}
/* FORM CONTROLS */
.form-group {
margin-bottom: 24px;
}
.label {
display: block;
font-size: 13px;
font-weight: 600;
color: #374151;
margin-bottom: 8px;
}
.input,
.textarea,
.select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
font-family: inherit;
font-size: 14px;
transition: 0.2s;
background: #fff;
}
.input:focus,
.textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
outline: none;
}
/* MENU CARD DESIGN */
.dish-card {
background: #fff;
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
display: flex;
gap: 15px;
transition: 0.2s;
}
.dish-card:hover {
border-color: #cbd5e1;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
.dish-img {
width: 60px;
height: 60px;
background: #eee;
border-radius: 8px;
object-fit: cover;
}
.dish-info {
flex: 1;
}
.dish-header {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.dish-name {
font-weight: 600;
font-size: 14px;
}
.dish-price {
font-weight: 700;
color: var(--primary);
font-size: 14px;
}
.dish-desc {
font-size: 12px;
color: #6b7280;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.btn-add {
width: 100%;
padding: 12px;
background: #f0f9ff;
color: #0369a1;
border: 1px dashed #bae6fd;
border-radius: 12px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
margin-bottom: 20px;
transition: 0.2s;
}
.btn-add:hover {
background: #e0f2fe;
border-color: #7dd3fc;
}
/* PREVIEW CANVAS */
.canvas-area {
flex: 1;
background-color: #e5e5e5;
background-image: radial-gradient(#d4d4d4 1px, transparent 1px);
background-size: 20px 20px;
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.device-bar {
background: #fff;
padding: 6px;
border-radius: 50px;
display: flex;
gap: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.device-btn {
width: 40px;
height: 40px;
border: none;
background: transparent;
border-radius: 50%;
color: #6b7280;
cursor: pointer;
transition: 0.2s;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.device-btn.active {
background: var(--bg-canvas);
color: var(--text-main);
}
.device-btn:hover {
color: var(--primary);
}
.preview-frame-wrapper {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
width: 100%;
height: 100%;
max-width: 1200px;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
</style>
</head>
<body>
<!-- SIDEBAR NAVIGATION -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo"><i class="fa-solid fa-cube"></i> GKACHELE™</div>
<a href="#" class="back-link"><i class="fa-solid fa-arrow-left"></i> Salir</a>
</div>
<div class="nav-sections">
<div class="section-item" onclick="openDrawer('identity')">
<div class="section-info">
<div class="section-icon"><i class="fa-solid fa-store"></i></div>
<div class="section-title">
<h3>Identidad del Sitio</h3>
<p>Logo, Nombre, Slogan</p>
</div>
</div>
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
</div>
<div class="section-item" onclick="openDrawer('menus')">
<div class="section-info">
<div class="section-icon" style="background:#fff7ed; color:#c2410c;"><i
class="fa-solid fa-utensils"></i></div>
<div class="section-title">
<h3>Menú & Platos</h3>
<p>Gestionar carta digital</p>
</div>
</div>
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
</div>
<div class="section-item" onclick="openDrawer('style')">
<div class="section-info">
<div class="section-icon" style="background:#fdf4ff; color:#a21caf;"><i
class="fa-solid fa-palette"></i></div>
<div class="section-title">
<h3>Estilo & Marca</h3>
<p>Colores, Tipografía</p>
</div>
</div>
<i class="fa-solid fa-chevron-right" style="color:#d1d5db; font-size: 12px;"></i>
</div>
</div>
<div style="padding: 24px; border-top: 1px solid var(--border);">
<button
style="width:100%; padding: 14px; background: var(--text-main); color: #fff; border:none; border-radius: 10px; font-weight: 600; cursor: pointer;">Publicar
Cambios</button>
</div>
</div>
<!-- DRAWERS (HIDDEN PANELS) -->
<!-- MENU DRAWER -->
<div id="menus" class="panel-drawer">
<div class="drawer-header">
<button class="drawer-close" onclick="closeDrawers()"><i class="fa-solid fa-arrow-left"></i></button>
<span class="drawer-title">Gestionar Menú</span>
</div>
<div class="drawer-content">
<button class="btn-add"><i class="fa-solid fa-plus"></i> Añadir Nuevo Plato</button>
<div class="dish-card">
<img src="https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=200&h=200"
class="dish-img">
<div class="dish-info">
<div class="dish-header">
<span class="dish-name">Bowl Saludable</span>
<span class="dish-price">$12.50</span>
</div>
<p class="dish-desc">Quinoa, aguacate, tomate cherry, huevo pouche y aderezo de sésamo.</p>
</div>
</div>
<div class="dish-card">
<img src="https://images.unsplash.com/photo-1482049016688-2d3e1b311543?auto=format&fit=crop&w=200&h=200"
class="dish-img">
<div class="dish-info">
<div class="dish-header">
<span class="dish-name">Tostada de Aguacate</span>
<span class="dish-price">$8.00</span>
</div>
<p class="dish-desc">Pan de masa madre, aguacate triturado, semillas de girasol.</p>
</div>
</div>
</div>
</div>
<!-- IDENTITY DRAWER -->
<div id="identity" class="panel-drawer">
<div class="drawer-header">
<button class="drawer-close" onclick="closeDrawers()"><i class="fa-solid fa-arrow-left"></i></button>
<span class="drawer-title">Identidad</span>
</div>
<div class="drawer-content">
<div class="form-group">
<label class="label">Nombre del Negocio</label>
<input type="text" class="input" id="inputName" value="Green Bowl Madrid"
oninput="updatePreviewText('.logo', this.value)">
</div>
<div class="form-group">
<label class="label">Descripción Corta</label>
<textarea class="textarea" rows="3" id="inputDesc"
oninput="updatePreviewText('.hero p', this.value)">Comida saludable y fresca en el corazón de la ciudad.</textarea>
</div>
<div class="form-group">
<label class="label">Título Hero</label>
<input type="text" class="input" id="inputHero" value="Sabor Natural"
oninput="updatePreviewText('.hero h1', this.value)">
</div>
</div>
</div>
<!-- PREVIEW AREA -->
<div class="canvas-area">
<div class="device-bar">
<button class="device-btn active" onclick="setDevice('100%')"><i class="fa-solid fa-desktop"></i></button>
<button class="device-btn" onclick="setDevice('768px')"><i
class="fa-solid fa-tablet-screen-button"></i></button>
<button class="device-btn" onclick="setDevice('390px')"><i
class="fa-solid fa-mobile-screen-button"></i></button>
</div>
<div class="preview-frame-wrapper" style="max-width: 100%">
<!-- MOCK FRAME CONTENT FOR DEMO -->
<iframe id="previewFrame" srcdoc='
<html>
<head>
<style>
body { margin: 0; font-family: "Helvetica Neue", sans-serif; }
header { padding: 20px 40px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; }
.logo { font-weight: bold; font-size: 20px; }
.hero { padding: 80px 40px; text-align: center; background: #f9f9f9; }
h1 { font-size: 48px; margin: 0 0 20px 0; }
p { font-size: 18px; color: #666; max-width: 600px; margin: 0 auto; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 30px; padding: 40px; max-width: 1200px; margin: 0 auto; }
.card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
.card img { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 20px; }
.price { float: right; font-weight: bold; color: #2563eb; }
</style>
</head>
<body>
<header>
<div class="logo">Green Bowl Madrid</div>
<nav>
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Menú</a>
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Reservas</a>
<a href="#" style="margin-left: 20px; text-decoration: none; color: #333;">Contacto</a>
</nav>
</header>
<div class="hero">
<h1>Sabor Natural</h1>
<p>Los mejores ingredientes orgánicos seleccionados para ti cada mañana.</p>
</div>
<div class="grid">
<div class="card">
<img src="https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=500&q=60">
<div class="card-body">
<span class="price">$12.50</span>
<h3>Bowl Saludable</h3>
<p style="font-size: 14px">Quinoa, aguacate, tomate cherry...</p>
</div>
</div>
<div class="card">
<img src="https://images.unsplash.com/photo-1482049016688-2d3e1b311543?auto=format&fit=crop&w=500&q=60">
<div class="card-body">
<span class="price">$8.00</span>
<h3>Tostada de Aguacate</h3>
<p style="font-size: 14px">Pan de masa madre tostado...</p>
</div>
</div>
<div class="card">
<img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=500&q=60">
<div class="card-body">
<span class="price">$10.00</span>
<h3>Ensalada César</h3>
<p style="font-size: 14px">Lechuga romana, crutones, parmesano...</p>
</div>
</div>
</div>
</body>
</html>
'></iframe>
</div>
</div>
<script>
function openDrawer(id) {
document.querySelectorAll(".panel-drawer").forEach(d => d.classList.remove("active"));
const target = document.getElementById(id);
if (target) target.classList.add("active");
}
function closeDrawers() {
document.querySelectorAll(".panel-drawer").forEach(d => d.classList.remove("active"));
}
function setDevice(size) {
const wrapper = document.querySelector(".preview-frame-wrapper");
wrapper.style.maxWidth = size;
document.querySelectorAll(".device-btn").forEach(b => b.classList.remove("active"));
event.currentTarget.classList.add("active");
}
// REAL-TIME PREVIEW LOGIC
function updatePreviewText(selector, value) {
const iframe = document.getElementById('previewFrame');
const doc = iframe.contentDocument || iframe.contentWindow.document;
const el = doc.querySelector(selector);
if (el) el.innerText = value;
}
// Initialize preview editability after load
document.getElementById('previewFrame').onload = function () {
// Optional: Add click-to-edit logic inside iframe if needed later
};
</script>
</body>
</html>