| <!DOCTYPE html> |
| <html lang="es"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>PolySignal — Dashboard de Inteligencia de Mercados</title> |
| <link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
| <script type="module" crossorigin src="/assets/index-Dk2_c6vF.js"></script> |
| <link rel="stylesheet" crossorigin href="/assets/index-B5AdzxgP.css"> |
| </head> |
| <body> |
| |
| <div id="global-loader" class="global-loader" role="status" aria-live="polite" aria-busy="true"> |
| <div class="global-loader__spinner"></div> |
| <span class="global-loader__label" id="global-loader-label">Cargando…</span> |
| </div> |
|
|
| |
| <div id="view-auth" class="auth-view hidden"> |
| <div class="auth-card"> |
| <div class="auth-header"> |
| <div class="auth-logo"> |
| <div class="logo-dot"></div> |
| <span class="logo-text">PolySignal</span> |
| </div> |
| <p class="auth-claim">Inteligencia en tiempo real para mercados de predicción de Polymarket — señales IA, mapa global y simulador.</p> |
| </div> |
| <div class="auth-body"> |
| <div class="modal-tabs"> |
| <button class="modal-tab active" data-tab="login">Iniciar sesión</button> |
| <button class="modal-tab" data-tab="register">Registrarse</button> |
| </div> |
| |
| <form class="modal-form active" id="form-login" novalidate> |
| <div class="form-group"> |
| <label for="login-email">Correo electrónico</label> |
| <input type="text" id="login-email" placeholder="usuario@ejemplo.com" autocomplete="email" /> |
| <small class="field-error" id="login-email-error"></small> |
| </div> |
| <div class="form-group"> |
| <label for="login-password">Contraseña</label> |
| <input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password" /> |
| <small class="field-error" id="login-password-error"></small> |
| </div> |
| <div class="form-error" id="login-error"></div> |
| <button type="submit" class="modal-submit">Entrar</button> |
| </form> |
| |
| <form class="modal-form" id="form-register" novalidate> |
| <div class="form-group"> |
| <label for="register-email">Correo electrónico</label> |
| <input type="text" id="register-email" placeholder="usuario@ejemplo.com" autocomplete="email" /> |
| <small class="field-error" id="register-email-error"></small> |
| </div> |
| <div class="form-group"> |
| <label for="register-password">Contraseña</label> |
| <input type="password" id="register-password" placeholder="Mínimo 8 caracteres" autocomplete="new-password" /> |
| <small class="field-error" id="register-password-error"></small> |
| </div> |
| <div class="form-group"> |
| <label for="register-password-confirm">Confirmar contraseña</label> |
| <input type="password" id="register-password-confirm" placeholder="Repite la contraseña" autocomplete="new-password" /> |
| <small class="field-error" id="register-password-confirm-error"></small> |
| </div> |
| <div class="form-error" id="register-error"></div> |
| <button type="submit" class="modal-submit">Crear cuenta</button> |
| </form> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="app" class="layout hidden"> |
|
|
| |
| <aside class="sidebar" id="sidebar"> |
| <div class="sidebar-toggle" id="sidebar-toggle" title="Colapsar sidebar">◀</div> |
| <nav class="sidebar-nav"> |
| <div class="nav-item active" data-view="dashboard"> |
| <span class="nav-icon">◈</span> |
| <span class="nav-label">Panel</span> |
| </div> |
| <div class="nav-item" data-view="positions"> |
| <span class="nav-icon">◫</span> |
| <span class="nav-label">Posiciones</span> |
| </div> |
| <div class="nav-item" data-view="watchlist"> |
| <span class="nav-icon">☆</span> |
| <span class="nav-label">Seguimiento</span> |
| </div> |
| <div class="nav-item" data-view="alerts"> |
| <span class="nav-icon">⚡</span> |
| <span class="nav-label">Alertas</span> |
| </div> |
| <div class="nav-item" data-view="preferences"> |
| <span class="nav-icon">⚙</span> |
| <span class="nav-label">Modelo IA</span> |
| </div> |
| </nav> |
| <div class="sidebar-footer"> |
| v0.1.0 · HF Spaces |
| </div> |
| </aside> |
|
|
| |
| <header class="topbar" id="topbar"> |
| <div class="topbar-logo"> |
| <div class="logo-dot"></div> |
| <span class="logo-text">PolySignal</span> |
| </div> |
| <div class="topbar-stats"> |
| <div class="live-badge"> |
| <div class="live-dot"></div> |
| EN VIVO |
| </div> |
| <div class="stats-track"> |
| <div class="stat"> |
| <span class="stat-label">Mercados</span> |
| <span class="stat-val" id="stat-markets">2.847</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Volumen 24h</span> |
| <span class="stat-val" id="stat-volume">€4,2M</span> |
| <span class="stat-delta up" id="stat-volume-delta">+12,4%</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Señales IA</span> |
| <span class="stat-val" id="stat-signals">183</span> |
| <span class="stat-delta up" id="stat-signals-delta">alcista</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Alertas enviadas</span> |
| <span class="stat-val" id="stat-alerts">47</span> |
| <span class="stat-delta neutral">hoy</span> |
| </div> |
| <div class="legend end"> |
| <div class="legend-item"><div class="legend-dot green"></div>alcista</div> |
| <div class="legend-item"><div class="legend-dot red"></div>bajista</div> |
| <div class="legend-item"><div class="legend-dot gray"></div>neutral</div> |
| </div> |
| |
| <div class="stat"> |
| <span class="stat-label">Mercados</span> |
| <span class="stat-val" id="stat-markets-dup">2.847</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Volumen 24h</span> |
| <span class="stat-val" id="stat-volume-dup">€4,2M</span> |
| <span class="stat-delta up">+12,4%</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Señales IA</span> |
| <span class="stat-val" id="stat-signals-dup">183</span> |
| <span class="stat-delta up">alcista</span> |
| </div> |
| <div class="stat"> |
| <span class="stat-label">Alertas enviadas</span> |
| <span class="stat-val" id="stat-alerts-dup">47</span> |
| <span class="stat-delta neutral">hoy</span> |
| </div> |
| <div class="legend"> |
| <div class="legend-item"><div class="legend-dot green"></div>alcista</div> |
| <div class="legend-item"><div class="legend-dot red"></div>bajista</div> |
| <div class="legend-item"><div class="legend-dot gray"></div>neutral</div> |
| </div> |
| </div> |
| </div> |
| <div class="topbar-filters desktop-only"> |
| <select class="filter-select" id="filter-trend" title="Tendencias"> |
| <option value="">Todos los mercados</option> |
| <option value="hot">Más activos</option> |
| <option value="bullish-trend">Tendencia alcista</option> |
| <option value="bearish-trend">Tendencia bajista</option> |
| <option value="high-volume">Alto volumen</option> |
| <option value="open-only">Mercados Abiertos</option> |
| </select> |
| <select class="filter-select" id="filter-category" title="Categoría"> |
| <option value="">Todas las categorías</option> |
| </select> |
|
|
| </div> |
| <div class="topbar-actions"> |
| <button class="btn-ghost desktop-only" id="btn-telegram">Alertas Telegram</button> |
| <button class="icon-btn mobile-only" id="btn-telegram-mobile" title="Alertas Telegram">✈</button> |
| <button class="icon-btn mobile-only" id="btn-watchlist-mobile" title="Seguimiento">☆</button> |
| <button class="icon-btn mobile-only" id="btn-alerts-mobile" title="Alertas">⚡</button> |
| <button class="icon-btn mobile-only" id="btn-prefs" title="Modelo IA">⚙</button> |
| |
| <button class="btn-ghost desktop-only" id="btn-auth">Entrar</button> |
| |
| <div class="user-menu" id="user-menu" hidden aria-haspopup="true"> |
| <button class="user-menu-trigger" id="user-menu-trigger" aria-expanded="false" aria-controls="user-menu-panel"> |
| <span class="user-avatar" id="user-avatar" aria-hidden="true">?</span> |
| <span class="user-email-short" id="user-email-short"></span> |
| <span class="user-chevron" aria-hidden="true">▾</span> |
| </button> |
| <div class="user-menu-panel" id="user-menu-panel" role="menu" hidden> |
| <div class="user-menu-info"> |
| <span class="user-menu-label">Sesión iniciada como</span> |
| <span class="user-menu-email" id="user-menu-email"></span> |
| </div> |
| <hr class="user-menu-divider" /> |
| <button class="user-menu-item" id="btn-logout" role="menuitem"> |
| Cerrar sesión |
| </button> |
| </div> |
| </div> |
| |
| <button class="icon-btn auth-indicator mobile-only" id="btn-auth-mobile" title="Entrar"></button> |
| </div> |
| </header> |
|
|
| |
| <main class="main" id="main"> |
| <div class="dashboard-grid view-dashboard"> |
|
|
| |
| <div class="main-content-area"> |
|
|
| |
| <section class="view active" id="view-dashboard"> |
| <div class="panel map-panel" id="panel-map"> |
| <div class="panel-header" data-panel="map"> |
| <div class="panel-title"> |
| <span>◈</span> |
| Mapa global |
| </div> |
| <div class="map-legend"> |
| <div class="legend-item"><div class="legend-dot green"></div>alcista</div> |
| <div class="legend-item"><div class="legend-dot red"></div>bajista</div> |
| <div class="legend-item"><div class="legend-dot gray"></div>neutral</div> |
| </div> |
| <span class="panel-toggle">▼</span> |
| </div> |
| <div class="panel-body"> |
| <div id="map-container"></div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="view" id="view-positions"> |
| <div class="panel full-height"> |
| <div class="panel-header"> |
| <div class="panel-title"><span>◫</span> Simulador — Posiciones abiertas</div> |
| </div> |
| <div class="panel-body"> |
| <div class="table-wrap"> |
| <table id="positions-table"> |
| <thead> |
| <tr> |
| <th>Mercado</th> |
| <th>Resultado</th> |
| <th>Cantidad</th> |
| <th>Entrada</th> |
| <th>Actual</th> |
| <th>G&P</th> |
| <th>Kelly</th> |
| <th>Abierta</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody></tbody> |
| </table> |
| </div> |
| <div class="empty-state hidden" id="positions-empty">No hay posiciones abiertas. Ve al Panel para simular una operación.</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="view" id="view-watchlist"> |
| <div class="panel full-height"> |
| <div class="panel-header"> |
| <div class="panel-title"><span>☆</span> Lista de seguimiento</div> |
| </div> |
| <div class="panel-body"> |
| <div class="table-wrap"> |
| <table id="watchlist-table"> |
| <thead> |
| <tr> |
| <th>Mercado</th> |
| <th>Categoría</th> |
| <th>Sí</th> |
| <th>No</th> |
| <th>Señal</th> |
| <th>Volumen</th> |
| <th>Umbral de alerta</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody></tbody> |
| </table> |
| </div> |
| <div class="empty-state hidden" id="watchlist-empty">Tu lista de seguimiento está vacía. Añade mercados desde el Panel.</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="view" id="view-alerts"> |
| <div class="panel full-height"> |
| <div class="panel-header"> |
| <div class="panel-title"><span>⚡</span> Historial de alertas</div> |
| </div> |
| <div class="panel-body"> |
| <div class="table-wrap"> |
| <table id="alerts-table"> |
| <thead> |
| <tr> |
| <th>Hora</th> |
| <th>Mercado</th> |
| <th>Tipo</th> |
| <th>Mensaje</th> |
| </tr> |
| </thead> |
| <tbody></tbody> |
| </table> |
| </div> |
| <div class="empty-state hidden" id="alerts-empty">Aún no se han enviado alertas.</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="view" id="view-preferences"> |
| <div class="panel full-height"> |
| <div class="panel-header"> |
| <div class="panel-title"><span>⚙</span> Modelo de IA</div> |
| </div> |
| <div class="panel-body"> |
| <div class="prefs-view-body"> |
|
|
| <div class="prefs-modes" id="view-prefs-modes"> |
| <button class="prefs-mode-btn active" data-mode="auto">Automático</button> |
| <button class="prefs-mode-btn" data-mode="external">Proveedor externo</button> |
| <button class="prefs-mode-btn" data-mode="local">Modelo local</button> |
| <button class="prefs-mode-btn" data-mode="custom">Personalizado</button> |
| </div> |
|
|
| <div class="prefs-section active" id="view-prefs-auto"> |
| <p class="prefs-description"> |
| El servidor usa su cadena de respaldo configurada por variables de entorno:<br> |
| <strong>DeepSeek → OpenRouter → HuggingFace → Ollama local → regla de precio.</strong> |
| </p> |
| </div> |
|
|
| <div class="prefs-section" id="view-prefs-external"> |
| <div class="form-group"> |
| <label for="view-prefs-provider">Proveedor</label> |
| <select class="filter-select prefs-select" id="view-prefs-provider"> |
| <option value="deepseek">DeepSeek API</option> |
| <option value="openrouter">OpenRouter</option> |
| <option value="huggingface">HuggingFace Inference</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label for="view-prefs-api-key">Clave API</label> |
| <input type="password" id="view-prefs-api-key" placeholder="sk-..." autocomplete="off" /> |
| </div> |
| </div> |
|
|
| <div class="prefs-section" id="view-prefs-local"> |
| <p class="prefs-description">Compatible con Ollama. Usa <code>/api/chat</code> en la URL base.</p> |
| <div class="form-group"> |
| <label for="view-prefs-local-url">URL del servidor</label> |
| <input type="text" id="view-prefs-local-url" placeholder="http://localhost:11434" /> |
| </div> |
| <div class="form-group"> |
| <label for="view-prefs-local-model">Nombre del modelo</label> |
| <input type="text" id="view-prefs-local-model" placeholder="qwen3:8b" /> |
| </div> |
| </div> |
|
|
| <div class="prefs-section" id="view-prefs-custom"> |
| <p class="prefs-description">Cualquier endpoint compatible con OpenAI Chat Completions.</p> |
| <div class="form-group"> |
| <label for="view-prefs-custom-url">Endpoint URL</label> |
| <input type="text" id="view-prefs-custom-url" placeholder="https://api.example.com/v1/chat/completions" /> |
| </div> |
| <div class="form-group"> |
| <label for="view-prefs-custom-key">Clave API (opcional)</label> |
| <input type="password" id="view-prefs-custom-key" placeholder="sk-..." autocomplete="off" /> |
| </div> |
| <div class="form-group"> |
| <label for="view-prefs-custom-model">Nombre del modelo</label> |
| <input type="text" id="view-prefs-custom-model" placeholder="gpt-4o-mini" /> |
| </div> |
| </div> |
|
|
| <div class="form-status" id="view-prefs-status"></div> |
| <div class="form-actions form-actions--end"> |
| <button type="button" class="btn-ghost" id="btn-view-save-prefs">Guardar</button> |
| </div> |
|
|
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| </div> |
|
|
| |
| <div class="panel signals-panel" id="panel-signals"> |
| <div class="panel-header" data-panel="signals"> |
| <div class="panel-title"> |
| <span>◈</span> |
| Señales IA — mercados top |
| </div> |
| <span class="panel-toggle">▼</span> |
| </div> |
| <div class="panel-body"> |
| <div class="signals-list" id="signals-list"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="bottom-panels"> |
| <div class="panel positions-mini-panel" id="panel-positions-mini"> |
| <div class="panel-header" data-panel="positions-mini"> |
| <div class="panel-title"> |
| <span>◫</span> |
| Mis posiciones |
| </div> |
| <span class="panel-toggle">▼</span> |
| </div> |
| <div class="panel-body"> |
| <div id="mini-positions"></div> |
| </div> |
| </div> |
|
|
| <div class="panel detail-panel" id="panel-detail"> |
| <div class="panel-header" data-panel="detail"> |
| <div class="panel-title"> |
| <span>◈</span> |
| Detalle del mercado |
| </div> |
| <span class="panel-toggle">▼</span> |
| </div> |
| <div class="panel-body" id="detail-body"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
| </main> |
| </div> |
|
|
| |
| <div class="modal-overlay hidden" id="telegram-modal"> |
| <div class="modal"> |
| <div class="modal-header"> |
| <div class="modal-title"> |
| <span class="modal-title-icon">✈</span> |
| <span>Alertas Telegram</span> |
| </div> |
| <button class="modal-close" id="telegram-modal-close" title="Cerrar">✕</button> |
| </div> |
| <div class="modal-body"> |
| |
| <div class="telegram-instructions"> |
| <div class="instruction-step"> |
| <span class="step-num">1</span> |
| <div class="step-body"> |
| <strong>Crea tu bot</strong> |
| <p>Abre <a href="https://t.me/BotFather" target="_blank" rel="noopener">@BotFather</a> en Telegram, pulsa <em>/newbot</em> y sigue los pasos. Copia el token que te devuelve (parece <code>123456789:ABC...</code>).</p> |
| </div> |
| </div> |
| <div class="instruction-step"> |
| <span class="step-num">2</span> |
| <div class="step-body"> |
| <strong>Obtén tu Chat ID</strong> |
| <p>Escríbele a <a href="https://t.me/userinfobot" target="_blank" rel="noopener">@userinfobot</a> y te responderá con tu ID. Si quieres enviarlo a un grupo, añade el bot al grupo y usa <a href="https://t.me/getidsbot" target="_blank" rel="noopener">@getidsbot</a>.</p> |
| </div> |
| </div> |
| <div class="instruction-step"> |
| <span class="step-num">3</span> |
| <div class="step-body"> |
| <strong>Inicia el bot</strong> |
| <p>En Telegram, abre una conversación con tu bot y pulsa <em>/start</em>. Si usas un grupo, envía <em>/start</em> en el grupo también.</p> |
| </div> |
| </div> |
| </div> |
|
|
| <form class="modal-form active" id="form-telegram"> |
| <div class="form-group"> |
| <label for="telegram-bot-token">Token del bot</label> |
| <input type="password" id="telegram-bot-token" placeholder="123456789:ABCdefGHIjklMNOpqrsTUVwxyz" /> |
| </div> |
| <div class="form-group"> |
| <label for="telegram-chat-id">Chat ID</label> |
| <input type="text" id="telegram-chat-id" placeholder="-1001234567890" /> |
| </div> |
| <div class="form-group"> |
| <div class="toggle-row"> |
| <label for="telegram-enabled">Activar alertas</label> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="telegram-enabled" /> |
| <span class="toggle-slider"></span> |
| </label> |
| </div> |
| </div> |
| <div class="form-status" id="telegram-status"></div> |
| <div class="form-actions"> |
| <button type="button" class="modal-submit modal-submit--secondary" id="btn-test-telegram">Probar conexión</button> |
| <button type="submit" class="modal-submit">Guardar configuración</button> |
| </div> |
| </form> |
| </div> |
| </div> |
| </div> |
|
|
|
|
| </body> |
| </html> |
|
|