Tu Nombre commited on
Commit
74e333c
·
1 Parent(s): 3fad336

Actualización: Cambio a modelo Llama 3.1 8B

Browse files
Files changed (1) hide show
  1. main.py +422 -0
main.py ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, re, time, asyncio, threading, httpx
2
+ from fastapi import FastAPI, Request, BackgroundTasks
3
+ from fastapi.responses import HTMLResponse, JSONResponse
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+
6
+ app = FastAPI()
7
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
8
+
9
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
10
+ HF_USER = os.environ.get("HF_USER", "Cristobal299")
11
+ GROQ_KEY = os.environ.get("GROQ_KEY", "")
12
+ PEXELS_KEY = os.environ.get("PEXELS_KEY", "")
13
+ STRIPE_KEY = os.environ.get("STRIPE_KEY", "")
14
+
15
+ CLIENTES = {}
16
+
17
+ # ══════════════════════════════════════════
18
+ # CREAR SPACE EN HUGGING FACE
19
+ # ══════════════════════════════════════════
20
+ async def crear_space(nicho: str, email: str) -> str:
21
+ space_id = f"tb-{re.sub(r'[^a-z0-9]','', nicho.lower()[:12])}-{int(time.time())%9999}"
22
+ repo_id = f"{HF_USER}/{space_id}"
23
+ headers = {"Authorization": f"Bearer {HF_TOKEN}", "Content-Type": "application/json"}
24
+
25
+ main_code = f'''
26
+ import os, asyncio, time, random, requests, threading
27
+ import gradio as gr
28
+ from groq import Groq
29
+ import edge_tts
30
+ from moviepy.editor import VideoFileClip, AudioFileClip, TextClip, CompositeVideoClip
31
+
32
+ NICHO = "{nicho}"
33
+ GROQ_KEY = "{GROQ_KEY}"
34
+ PEXELS_KEY = "{PEXELS_KEY}"
35
+ LOGS = []
36
+ STATUS = "Iniciando..."
37
+
38
+ def log(msg):
39
+ t = time.strftime("%H:%M:%S")
40
+ LOGS.append(f"[{{t}}] {{msg}}")
41
+ if len(LOGS) > 40: LOGS.pop(0)
42
+ print(msg, flush=True)
43
+
44
+ def generar_guion():
45
+ client = Groq(api_key=GROQ_KEY)
46
+ res = client.chat.completions.create(
47
+ messages=[{{"role":"user","content":f"Crea un guion de video de 60 segundos sobre {{NICHO}}. Directo, sin simbolos ni hashtags."}}],
48
+ model="llama-3.3-70b-versatile", temperature=0.82
49
+ )
50
+ return res.choices[0].message.content.strip()
51
+
52
+ async def pipeline():
53
+ global STATUS
54
+ while True:
55
+ try:
56
+ STATUS = "Generando guion..."
57
+ log(f"🎬 Nuevo video: {{NICHO}}")
58
+ guion = generar_guion()
59
+ log("✅ Guion listo")
60
+
61
+ STATUS = "Generando voz..."
62
+ await edge_tts.Communicate(guion, "es-ES-AlvaroNeural").save("voz.mp3")
63
+ log("✅ Voz lista")
64
+
65
+ STATUS = "Descargando fondo..."
66
+ h = {{"Authorization": PEXELS_KEY}}
67
+ r = requests.get(f"https://api.pexels.com/videos/search?query={{NICHO}}&per_page=10", headers=h)
68
+ vids = r.json().get("videos", [])
69
+ if vids:
70
+ url = random.choice(vids)["video_files"][0]["link"]
71
+ with open("fondo.mp4","wb") as f: f.write(requests.get(url).content)
72
+ log("✅ Fondo listo")
73
+
74
+ STATUS = "Renderizando video..."
75
+ from moviepy.editor import AudioFileClip as AC, VideoFileClip as VC
76
+ voz = AC("voz.mp3")
77
+ clip = VC("fondo.mp4").resize(width=1920).loop(duration=voz.duration)
78
+ palabras = guion.split()
79
+ frases = [" ".join(palabras[i:i+6]) for i in range(0,len(palabras),6)]
80
+ t_f = voz.duration / max(len(frases),1)
81
+ txts = [TextClip(t.upper(), fontsize=48, color="#D4AF37", font="DejaVu-Sans-Bold",
82
+ stroke_color="black", stroke_width=2, method="caption",
83
+ size=(clip.w*0.8,None)).set_start(i*t_f).set_duration(t_f).set_pos(("center",880))
84
+ for i,t in enumerate(frases)]
85
+ final = CompositeVideoClip([clip]+txts).set_audio(voz)
86
+ final.write_videofile("out.mp4", fps=20, codec="libx264", preset="ultrafast", logger=None)
87
+ log("✅ Video renderizado")
88
+
89
+ STATUS = "Esperando siguiente ciclo..."
90
+ log("⏳ Esperando 4 horas...")
91
+ await asyncio.sleep(14400)
92
+
93
+ except Exception as e:
94
+ log(f"❌ Error: {{e}}")
95
+ STATUS = f"Error: {{str(e)[:40]}}"
96
+ await asyncio.sleep(600)
97
+
98
+ def start():
99
+ loop = asyncio.new_event_loop()
100
+ asyncio.set_event_loop(loop)
101
+ loop.run_until_complete(pipeline())
102
+
103
+ threading.Thread(target=start, daemon=True).start()
104
+
105
+ with gr.Blocks(css="body{{background:#0a0a0a!important;}} .gradio-container{{background:#0a0a0a!important;color:#D4AF37!important;}}") as demo:
106
+ gr.Markdown(f"# 🤖 TubeBot — {{NICHO.upper()}}")
107
+ gr.Markdown("Tu canal está generando videos automáticamente 24/7")
108
+ gr.Textbox(label="📜 Logs en tiempo real", lines=18, every=4,
109
+ value=lambda: "\\n".join(LOGS[-18:]) if LOGS else "Iniciando sistema...")
110
+ gr.Textbox(label="⚡ Estado actual", every=3, value=lambda: STATUS)
111
+
112
+ demo.launch()
113
+ '''
114
+
115
+ req = "gradio\ngroq\nedge-tts\nmoviepy\nrequests\nhttpx"
116
+
117
+ async with httpx.AsyncClient(timeout=30) as client:
118
+ # Crear repo
119
+ r = await client.post("https://huggingface.co/api/repos/create",
120
+ headers=headers,
121
+ json={"name": space_id, "type": "space", "sdk": "gradio", "private": False})
122
+ if r.status_code not in [200, 201]:
123
+ print(f"Error repo: {r.text}")
124
+ return None
125
+
126
+ # Subir main.py
127
+ await client.put(
128
+ f"https://huggingface.co/api/repos/{repo_id}/upload/main/main.py",
129
+ headers={"Authorization": f"Bearer {HF_TOKEN}"},
130
+ content=main_code.encode())
131
+
132
+ # Subir requirements.txt
133
+ await client.put(
134
+ f"https://huggingface.co/api/repos/{repo_id}/upload/main/requirements.txt",
135
+ headers={"Authorization": f"Bearer {HF_TOKEN}"},
136
+ content=req.encode())
137
+
138
+ url = f"https://huggingface.co/spaces/{repo_id}"
139
+ CLIENTES[email] = {"nicho": nicho, "url": url}
140
+ return url
141
+
142
+ # ══════════════════════════════════════════
143
+ # PÁGINA PRINCIPAL — VENTAS
144
+ # ══════════════════════════════════════════
145
+ VENTAS_HTML = """<!DOCTYPE html>
146
+ <html lang="es">
147
+ <head>
148
+ <meta charset="UTF-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
150
+ <title>TubeBot — Canal de YouTube Automático</title>
151
+ <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Manrope:wght@300;400;700;800&display=swap" rel="stylesheet">
152
+ <style>
153
+ :root{--r:#ff2d2d;--bg:#080808;--card:#111;--b:#1e1e1e;--t:#f0f0f0;--m:#666;}
154
+ *{box-sizing:border-box;margin:0;padding:0;}
155
+ html{scroll-behavior:smooth;}
156
+ body{background:var(--bg);color:var(--t);font-family:'Manrope',sans-serif;overflow-x:hidden;}
157
+ .hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:40px 20px;background:radial-gradient(ellipse 80% 50% at 50% 0%,#2a0000,transparent 70%);}
158
+ .badge{display:inline-block;background:#1a0000;border:1px solid var(--r);color:var(--r);font-size:.7rem;letter-spacing:3px;text-transform:uppercase;padding:6px 16px;border-radius:100px;margin-bottom:28px;animation:fd .6s ease both;}
159
+ h1{font-family:'Bebas Neue',sans-serif;font-size:clamp(3.5rem,12vw,8rem);line-height:.9;animation:fd .6s .1s ease both;opacity:0;}
160
+ h1 span{color:var(--r);}
161
+ .sub{font-size:clamp(1rem,2.5vw,1.3rem);color:#aaa;font-weight:300;max-width:560px;margin:24px auto 0;line-height:1.6;animation:fd .6s .2s ease both;opacity:0;}
162
+ .hero-cta{margin-top:40px;animation:fd .6s .3s ease both;opacity:0;}
163
+ .btn{display:inline-block;background:var(--r);color:#fff;font-family:'Manrope',sans-serif;font-weight:800;font-size:1.1rem;padding:18px 44px;border-radius:6px;text-decoration:none;cursor:pointer;border:none;transition:all .2s;}
164
+ .btn:hover{background:#cc0000;transform:translateY(-2px);box-shadow:0 8px 30px rgba(255,45,45,.4);}
165
+ .price-note{display:block;margin-top:14px;color:var(--m);font-size:.85rem;}
166
+ .price-note strong{color:var(--t);}
167
+ .stats{display:flex;justify-content:center;gap:clamp(20px,5vw,60px);flex-wrap:wrap;padding:60px 20px;border-top:1px solid var(--b);border-bottom:1px solid var(--b);background:#0d0d0d;}
168
+ .stat-n{font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,6vw,4rem);color:var(--r);line-height:1;}
169
+ .stat-l{font-size:.8rem;color:var(--m);letter-spacing:2px;text-transform:uppercase;margin-top:4px;}
170
+ .sec{padding:80px 20px;max-width:900px;margin:0 auto;}
171
+ .sec-label{font-size:.72rem;letter-spacing:4px;text-transform:uppercase;color:var(--r);margin-bottom:16px;font-weight:700;}
172
+ .sec h2{font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,6vw,4.5rem);line-height:1;margin-bottom:48px;}
173
+ .steps{display:flex;flex-direction:column;gap:16px;}
174
+ .step{display:flex;align-items:flex-start;gap:20px;background:var(--card);border:1px solid var(--b);border-radius:12px;padding:24px;}
175
+ .step-n{font-family:'Bebas Neue',sans-serif;font-size:2.5rem;color:var(--r);line-height:1;min-width:48px;}
176
+ .step-title{font-weight:700;margin-bottom:6px;}
177
+ .step-desc{color:#888;font-size:.9rem;line-height:1.6;}
178
+ .feats{padding:80px 20px;background:#0d0d0d;border-top:1px solid var(--b);border-bottom:1px solid var(--b);}
179
+ .feats-inner{max-width:900px;margin:0 auto;}
180
+ .feat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px;margin-top:48px;}
181
+ .feat{background:var(--bg);border:1px solid var(--b);border-radius:12px;padding:24px;}
182
+ .feat-icon{font-size:1.8rem;margin-bottom:12px;}
183
+ .feat-title{font-weight:700;margin-bottom:6px;}
184
+ .feat-desc{color:#777;font-size:.88rem;line-height:1.5;}
185
+ .price-sec{padding:80px 20px;max-width:480px;margin:0 auto;text-align:center;}
186
+ .price-card{background:var(--card);border:2px solid var(--r);border-radius:16px;padding:40px 32px;margin-top:48px;position:relative;overflow:hidden;}
187
+ .price-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--r),#ff6b6b);}
188
+ .price-amount{font-family:'Bebas Neue',sans-serif;font-size:5rem;color:var(--r);line-height:1;}
189
+ .price-period{color:var(--m);font-size:.9rem;margin-bottom:28px;}
190
+ .price-feats{list-style:none;text-align:left;margin-bottom:32px;}
191
+ .price-feats li{padding:10px 0;border-bottom:1px solid var(--b);font-size:.9rem;display:flex;align-items:center;gap:10px;}
192
+ .price-feats li:last-child{border-bottom:none;}
193
+ .chk{color:var(--r);}
194
+ .faq-sec{padding:80px 20px;max-width:700px;margin:0 auto;}
195
+ .faq-item{border-bottom:1px solid var(--b);padding:20px 0;cursor:pointer;}
196
+ .faq-q{font-weight:700;display:flex;justify-content:space-between;font-size:.95rem;}
197
+ .faq-a{color:#888;font-size:.88rem;line-height:1.6;margin-top:12px;display:none;}
198
+ .faq-item.open .faq-a{display:block;}
199
+ .faq-arr{transition:transform .2s;color:var(--r);}
200
+ .faq-item.open .faq-arr{transform:rotate(180deg);}
201
+ .fcta{padding:100px 20px;text-align:center;background:radial-gradient(ellipse 80% 60% at 50% 100%,#2a0000,transparent 70%);}
202
+ .fcta h2{font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,7vw,5rem);line-height:1;margin-bottom:24px;}
203
+ .fcta h2 span{color:var(--r);}
204
+ footer{text-align:center;padding:24px;color:var(--m);font-size:.8rem;border-top:1px solid var(--b);}
205
+ @keyframes fd{from{opacity:0;transform:translateY(-20px);}to{opacity:1;transform:translateY(0);}}
206
+ </style>
207
+ </head>
208
+ <body>
209
+
210
+ <section class="hero">
211
+ <div class="badge">🤖 100% Automático — Sin grabar</div>
212
+ <h1>TUBE<span>BOT</span></h1>
213
+ <p class="sub">Tu canal de YouTube publica solo cada día mientras tú duermes. Sin grabar. Sin editar. Sin tocar nada.</p>
214
+ <div class="hero-cta">
215
+ <a href="#precio" class="btn">🚀 QUIERO MI CANAL AUTOMÁTICO</a>
216
+ <span class="price-note">Pago único de <strong>$49</strong> — Acceso de por vida</span>
217
+ </div>
218
+ </section>
219
+
220
+ <div class="stats">
221
+ <div class="stat"><div class="stat-n">24/7</div><div class="stat-l">Publica solo</div></div>
222
+ <div class="stat"><div class="stat-n">0h</div><div class="stat-l">Sin grabar</div></div>
223
+ <div class="stat"><div class="stat-n">∞</div><div class="stat-l">Videos infinitos</div></div>
224
+ <div class="stat"><div class="stat-n">5min</div><div class="stat-l">Para activarlo</div></div>
225
+ </div>
226
+
227
+ <div class="sec">
228
+ <div class="sec-label">Cómo funciona</div>
229
+ <h2>EN 3 PASOS Y LISTO</h2>
230
+ <div class="steps">
231
+ <div class="step"><div class="step-n">01</div><div><div class="step-title">Pagas una sola vez</div><div class="step-desc">Pago seguro de $49. Sin mensualidades ni sorpresas.</div></div></div>
232
+ <div class="step"><div class="step-n">02</div><div><div class="step-title">Eliges tu nicho</div><div class="step-desc">Fitness, crypto, mascotas, finanzas... Tú decides el tema y la IA hace el resto.</div></div></div>
233
+ <div class="step"><div class="step-n">03</div><div><div class="step-title">Tu canal publica solo</div><div class="step-desc">El sistema crea videos completos con IA y los sube a YouTube automáticamente cada día.</div></div></div>
234
+ </div>
235
+ </div>
236
+
237
+ <div class="feats">
238
+ <div class="feats-inner">
239
+ <div class="sec-label">Qué incluye</div>
240
+ <h2 style="font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,6vw,4.5rem);line-height:1;margin-bottom:48px;">TODO INCLUIDO</h2>
241
+ <div class="feat-grid">
242
+ <div class="feat"><div class="feat-icon">🧠</div><div class="feat-title">Guión con IA</div><div class="feat-desc">Guión nuevo cada día adaptado a tu nicho automáticamente.</div></div>
243
+ <div class="feat"><div class="feat-icon">🎙️</div><div class="feat-title">Voz humana</div><div class="feat-desc">Narración en español con voz natural de alta calidad.</div></div>
244
+ <div class="feat"><div class="feat-icon">🎬</div><div class="feat-title">Vídeo cinematográfico</div><div class="feat-desc">Fondos HD seleccionados automáticamente según el tema.</div></div>
245
+ <div class="feat"><div class="feat-icon">✍️</div><div class="feat-title">Subtítulos automáticos</div><div class="feat-desc">Subtítulos sincronizados en cada vídeo.</div></div>
246
+ <div class="feat"><div class="feat-icon">📺</div><div class="feat-title">Subida automática</div><div class="feat-desc">Publica en tu canal con título y descripción optimizados.</div></div>
247
+ <div class="feat"><div class="feat-icon">♾️</div><div class="feat-title">Sin límite</div><div class="feat-desc">Publica todos los días para siempre sin coste adicional.</div></div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+
252
+ <div class="price-sec" id="precio">
253
+ <div class="sec-label" style="text-align:center;">Precio</div>
254
+ <h2 style="font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,6vw,4.5rem);line-height:1;text-align:center;">UN SOLO PAGO.<br>PARA SIEMPRE.</h2>
255
+ <div class="price-card">
256
+ <div class="price-amount">$49</div>
257
+ <div class="price-period">Pago único — Sin mensualidades</div>
258
+ <ul class="price-feats">
259
+ <li><span class="chk">▶</span> Canal de YouTube 100% automático</li>
260
+ <li><span class="chk">▶</span> Vídeos diarios con IA</li>
261
+ <li><span class="chk">▶</span> Voz + subtítulos + vídeo incluidos</li>
262
+ <li><span class="chk">▶</span> Activación en minutos</li>
263
+ <li><span class="chk">▶</span> Soporte por WhatsApp</li>
264
+ <li><span class="chk">▶</span> Acceso de por vida</li>
265
+ </ul>
266
+ <a href="https://buy.stripe.com/test_6oUdR82014SPg4Z7UKdQQ01?success_url=https://YOUR_APP_URL/activar-canal&cancel_url=https://YOUR_APP_URL" class="btn" style="display:block;text-align:center;">💳 COMPRAR AHORA — $49</a>
267
+ <p style="color:var(--m);font-size:.78rem;margin-top:12px;">Pago seguro · Activación inmediata</p>
268
+ </div>
269
+ </div>
270
+
271
+ <div class="faq-sec">
272
+ <div class="sec-label" style="text-align:center;">FAQ</div>
273
+ <h2 style="font-family:'Bebas Neue',sans-serif;font-size:clamp(2.5rem,6vw,4.5rem);line-height:1;text-align:center;margin-bottom:32px;">PREGUNTAS</h2>
274
+ <div class="faq-item" onclick="this.classList.toggle('open')"><div class="faq-q">¿Necesito saber programar? <span class="faq-arr">▼</span></div><div class="faq-a">No. Solo eliges tu nicho y listo. Todo lo técnico es automático.</div></div>
275
+ <div class="faq-item" onclick="this.classList.toggle('open')"><div class="faq-q">¿Cuántos vídeos publica? <span class="faq-arr">▼</span></div><div class="faq-a">Un video cada 4 horas automáticamente, todos los días.</div></div>
276
+ <div class="faq-item" onclick="this.classList.toggle('open')"><div class="faq-q">¿En qué idioma publica? <span class="faq-arr">▼</span></div><div class="faq-a">En español por defecto. Configurable a cualquier idioma.</div></div>
277
+ <div class="faq-item" onclick="this.classList.toggle('open')"><div class="faq-q">¿Cuándo se activa? <span class="faq-arr">▼</span></div><div class="faq-a">Inmediatamente después del pago. En minutos tienes tu sistema funcionando.</div></div>
278
+ </div>
279
+
280
+ <div class="fcta">
281
+ <h2>TU CANAL PUBLICA<br><span>MIENTRAS DUERMES</span></h2>
282
+ <a href="#precio" class="btn">🚀 ACTIVAR TUBEBOT — $49</a>
283
+ </div>
284
+
285
+ <footer>© 2026 TubeBot · Todos los derechos reservados</footer>
286
+ </body>
287
+ </html>"""
288
+ # ══════════════════════════════════════════
289
+ # PÁGINA DE ACTIVACIÓN — DESPUÉS DEL PAGO
290
+ # ══════════════════════════════════════════
291
+ ACTIVAR_HTML = """<!DOCTYPE html>
292
+ <html lang="es">
293
+ <head>
294
+ <meta charset="UTF-8">
295
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
296
+ <title>TubeBot — Activa tu canal</title>
297
+ <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Manrope:wght@400;700;800&display=swap" rel="stylesheet">
298
+ <style>
299
+ *{box-sizing:border-box;margin:0;padding:0;}
300
+ body{background:#080808;color:#f0f0f0;font-family:'Manrope',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;}
301
+ .card{background:#111;border:1px solid #222;border-radius:20px;padding:40px 32px;max-width:480px;width:100%;text-align:center;}
302
+ .check-icon{font-size:3rem;margin-bottom:16px;}
303
+ h1{font-family:'Bebas Neue',sans-serif;font-size:2.5rem;color:#ff2d2d;margin-bottom:8px;}
304
+ .sub{color:#888;font-size:.95rem;margin-bottom:32px;line-height:1.6;}
305
+ label{display:block;text-align:left;font-size:.75rem;letter-spacing:2px;text-transform:uppercase;color:#ff2d2d;margin-bottom:6px;margin-top:16px;}
306
+ input,select{width:100%;background:#0a0a0a;border:1px solid #222;color:#f0f0f0;padding:14px 16px;border-radius:10px;font-family:'Manrope',sans-serif;font-size:.95rem;outline:none;}
307
+ input:focus,select:focus{border-color:#ff2d2d;}
308
+ .btn{width:100%;background:#ff2d2d;color:#fff;border:none;padding:16px;border-radius:10px;font-family:'Manrope',sans-serif;font-size:1rem;font-weight:800;cursor:pointer;margin-top:24px;transition:all .2s;}
309
+ .btn:hover{background:#cc0000;}
310
+ .btn:disabled{opacity:.5;cursor:not-allowed;}
311
+ .loading{display:none;margin-top:20px;color:#ff2d2d;font-size:.9rem;line-height:1.6;}
312
+ .result{display:none;margin-top:24px;background:#0a0a0a;border:1px solid #ff2d2d;border-radius:12px;padding:24px;text-align:left;}
313
+ .result h3{color:#ff2d2d;margin-bottom:12px;font-size:1.1rem;}
314
+ .result p{color:#888;font-size:.85rem;margin-bottom:8px;line-height:1.5;}
315
+ .url-box{background:#111;border:1px solid #333;border-radius:8px;padding:12px;word-break:break-all;font-size:.85rem;margin:12px 0;}
316
+ .url-box a{color:#fff;text-decoration:none;}
317
+ .copy-btn{background:transparent;border:1px solid #ff2d2d;color:#ff2d2d;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:.8rem;font-family:'Manrope',sans-serif;font-weight:700;width:100%;margin-top:8px;transition:all .2s;}
318
+ .copy-btn:hover{background:#ff2d2d;color:#fff;}
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <div class="card">
323
+ <div class="check-icon">✅</div>
324
+ <h1>¡PAGO CONFIRMADO!</h1>
325
+ <p class="sub">Ahora configura tu canal. Solo tienes que elegir tu nicho y en minutos tendrás tu sistema funcionando.</p>
326
+
327
+ <label>Tu email</label>
328
+ <input type="email" id="email" placeholder="tu@email.com"/>
329
+
330
+ <label>Elige el nicho de tu canal</label>
331
+ <select id="nicho">
332
+ <option value="fitness y salud">💪 Fitness y Salud</option>
333
+ <option value="finanzas personales">💰 Finanzas Personales</option>
334
+ <option value="criptomonedas">₿ Criptomonedas</option>
335
+ <option value="tecnologia e inteligencia artificial">🤖 Tecnología e IA</option>
336
+ <option value="motivacion y exito personal">🔥 Motivación y Éxito</option>
337
+ <option value="mascotas">🐾 Mascotas</option>
338
+ <option value="recetas de cocina">🍳 Cocina y Recetas</option>
339
+ <option value="viajes y aventura">✈️ Viajes</option>
340
+ <option value="emprendimiento y negocios">🚀 Emprendimiento</option>
341
+ </select>
342
+
343
+ <button class="btn" id="btn" onclick="activar()">⚡ CREAR MI CANAL AUTOMÁTICO</button>
344
+
345
+ <div class="loading" id="loading">
346
+ ⚡ Creando tu sistema en Hugging Face...<br>
347
+ <small style="color:#666;">Esto puede tardar 1-2 minutos</small>
348
+ </div>
349
+
350
+ <div class="result" id="result">
351
+ <h3>🎉 ¡Tu canal está listo!</h3>
352
+ <p>Accede aquí para ver tu sistema funcionando en tiempo real:</p>
353
+ <div class="url-box"><a id="url-link" href="#" target="_blank">Cargando...</a></div>
354
+ <button class="copy-btn" onclick="copiar()">📋 Copiar enlace</button>
355
+ <p style="margin-top:16px;color:#666;font-size:.8rem;">El sistema puede tardar unos minutos en arrancar la primera vez. ¡Es normal!</p>
356
+ </div>
357
+ </div>
358
+
359
+ <script>
360
+ async function activar(){
361
+ const email = document.getElementById('email').value.trim();
362
+ const nicho = document.getElementById('nicho').value;
363
+ if(!email){alert('Por favor pon tu email');return;}
364
+ document.getElementById('btn').disabled = true;
365
+ document.getElementById('loading').style.display = 'block';
366
+ try{
367
+ const r = await fetch('/crear', {
368
+ method:'POST',
369
+ headers:{'Content-Type':'application/json'},
370
+ body: JSON.stringify({email, nicho})
371
+ });
372
+ const d = await r.json();
373
+ if(d.url){
374
+ document.getElementById('result').style.display = 'block';
375
+ document.getElementById('url-link').href = d.url;
376
+ document.getElementById('url-link').textContent = d.url;
377
+ window._url = d.url;
378
+ } else {
379
+ alert('Error: ' + (d.error||'Inténtalo de nuevo'));
380
+ document.getElementById('btn').disabled = false;
381
+ }
382
+ }catch(e){
383
+ alert('Error de conexión');
384
+ document.getElementById('btn').disabled = false;
385
+ }
386
+ document.getElementById('loading').style.display = 'none';
387
+ }
388
+ function copiar(){
389
+ navigator.clipboard.writeText(window._url||'').then(()=>alert('¡Copiado!'));
390
+ }
391
+ </script>
392
+ </body>
393
+ </html>"""
394
+
395
+ # ══════════════════════════════════════════
396
+ # RUTAS
397
+ # ══════════════════════════════════════════
398
+ @app.get("/", response_class=HTMLResponse)
399
+ async def home():
400
+ return VENTAS_HTML
401
+
402
+ @app.get("/activar-canal", response_class=HTMLResponse)
403
+ async def activar_canal():
404
+ return ACTIVAR_HTML
405
+
406
+ @app.post("/crear")
407
+ async def crear(request: Request):
408
+ data = await request.json()
409
+ email = data.get("email","")
410
+ nicho = data.get("nicho","tecnologia")
411
+ url = await crear_space(nicho, email)
412
+ if url:
413
+ return {"success": True, "url": url}
414
+ return JSONResponse({"error": "No se pudo crear el Space"}, status_code=500)
415
+
416
+ @app.get("/clientes")
417
+ async def ver_clientes():
418
+ return CLIENTES
419
+
420
+ if __name__ == "__main__":
421
+ import uvicorn
422
+ uvicorn.run(app, host="0.0.0.0", port=7860)