Jose Salazar commited on
Commit
71925de
Β·
unverified Β·
2 Parent(s): 2c0899cb51e0a6

Merge pull request #32 from josesalazar2025/jose

Browse files

Cambios en UI para incorporar las posiciones actuales del usuario

frontend/index.html CHANGED
@@ -183,225 +183,238 @@
183
 
184
  <!-- Main content area -->
185
  <main class="main" id="main">
 
186
 
187
- <!-- DASHBOARD VIEW -->
188
- <section class="view active" id="view-dashboard">
189
- <div class="dashboard-grid">
190
 
191
- <!-- Map Panel -->
192
- <div class="panel map-panel" id="panel-map">
193
- <div class="panel-header" data-panel="map">
194
- <div class="panel-title">
195
- <span>β—ˆ</span>
196
- Mapa global
 
 
 
 
 
 
 
 
197
  </div>
198
- <div class="map-legend">
199
- <div class="legend-item"><div class="legend-dot green"></div>alcista</div>
200
- <div class="legend-item"><div class="legend-dot red"></div>bajista</div>
201
- <div class="legend-item"><div class="legend-dot gray"></div>neutral</div>
202
  </div>
203
- <span class="panel-toggle">β–Ό</span>
204
  </div>
205
- <div class="panel-body">
206
- <div id="map-container"></div>
207
- </div>
208
- </div>
209
 
210
- <!-- Signals Panel -->
211
- <div class="panel signals-panel" id="panel-signals">
212
- <div class="panel-header" data-panel="signals">
213
- <div class="panel-title">
214
- <span>β—ˆ</span>
215
- SeΓ±ales IA β€” mercados top
216
  </div>
217
- <span class="panel-toggle">β–Ό</span>
218
- </div>
219
- <div class="panel-body">
220
- <div class="signals-list" id="signals-list"></div>
221
- <div class="positions-separator">
222
- <div class="panel-title mb-sm">Mis posiciones</div>
223
- <div id="mini-positions"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  </div>
225
  </div>
226
- </div>
227
 
228
- <!-- Detail Panel -->
229
- <div class="panel detail-panel" id="panel-detail">
230
- <div class="panel-header" data-panel="detail">
231
- <div class="panel-title">
232
- <span>β—ˆ</span>
233
- Detalle del mercado
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  </div>
235
- <span class="panel-toggle">β–Ό</span>
236
  </div>
237
- <div class="panel-body" id="detail-body">
238
- <!-- Dynamic content -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  </div>
240
- </div>
241
 
242
- </div>
243
- </section>
 
 
 
 
 
 
244
 
245
- <!-- POSITIONS VIEW -->
246
- <section class="view" id="view-positions">
247
- <div class="panel full-height">
248
- <div class="panel-header">
249
- <div class="panel-title"><span>β—«</span> Simulador β€” Posiciones abiertas</div>
250
- </div>
251
- <div class="panel-body">
252
- <div class="table-wrap">
253
- <table id="positions-table">
254
- <thead>
255
- <tr>
256
- <th>Mercado</th>
257
- <th>Resultado</th>
258
- <th>Cantidad</th>
259
- <th>Entrada</th>
260
- <th>Actual</th>
261
- <th>G&amp;P</th>
262
- <th>Kelly</th>
263
- <th>Abierta</th>
264
- <th></th>
265
- </tr>
266
- </thead>
267
- <tbody></tbody>
268
- </table>
269
- </div>
270
- <div class="empty-state hidden" id="positions-empty">No hay posiciones abiertas. Ve al Panel para simular una operaciΓ³n.</div>
271
- </div>
272
- </div>
273
- </section>
274
 
275
- <!-- WATCHLIST VIEW -->
276
- <section class="view" id="view-watchlist">
277
- <div class="panel full-height">
278
- <div class="panel-header">
279
- <div class="panel-title"><span>β˜†</span> Lista de seguimiento</div>
280
- </div>
281
- <div class="panel-body">
282
- <div class="table-wrap">
283
- <table id="watchlist-table">
284
- <thead>
285
- <tr>
286
- <th>Mercado</th>
287
- <th>CategorΓ­a</th>
288
- <th>SΓ­</th>
289
- <th>No</th>
290
- <th>SeΓ±al</th>
291
- <th>Volumen</th>
292
- <th>Umbral de alerta</th>
293
- <th></th>
294
- </tr>
295
- </thead>
296
- <tbody></tbody>
297
- </table>
298
- </div>
299
- <div class="empty-state hidden" id="watchlist-empty">Tu lista de seguimiento estΓ‘ vacΓ­a. AΓ±ade mercados desde el Panel.</div>
300
- </div>
301
- </div>
302
- </section>
303
 
304
- <!-- ALERTS VIEW -->
305
- <section class="view" id="view-alerts">
306
- <div class="panel full-height">
307
- <div class="panel-header">
308
- <div class="panel-title"><span>⚑</span> Historial de alertas</div>
309
- </div>
310
- <div class="panel-body">
311
- <div class="table-wrap">
312
- <table id="alerts-table">
313
- <thead>
314
- <tr>
315
- <th>Hora</th>
316
- <th>Mercado</th>
317
- <th>Tipo</th>
318
- <th>Mensaje</th>
319
- </tr>
320
- </thead>
321
- <tbody></tbody>
322
- </table>
323
- </div>
324
- <div class="empty-state hidden" id="alerts-empty">AΓΊn no se han enviado alertas.</div>
325
- </div>
326
- </div>
327
- </section>
328
 
329
- <!-- PREFERENCES VIEW (desktop only β€” mobile uses modal) -->
330
- <section class="view" id="view-preferences">
331
- <div class="panel full-height">
332
- <div class="panel-header">
333
- <div class="panel-title"><span>βš™</span> Modelo de IA</div>
334
- </div>
335
- <div class="panel-body">
336
- <div class="prefs-view-body">
 
 
 
337
 
338
- <div class="prefs-modes" id="view-prefs-modes">
339
- <button class="prefs-mode-btn active" data-mode="auto">AutomΓ‘tico</button>
340
- <button class="prefs-mode-btn" data-mode="external">Proveedor externo</button>
341
- <button class="prefs-mode-btn" data-mode="local">Modelo local</button>
342
- <button class="prefs-mode-btn" data-mode="custom">Personalizado</button>
343
- </div>
 
 
 
 
 
 
 
 
 
344
 
345
- <div class="prefs-section active" id="view-prefs-auto">
346
- <p class="prefs-description">
347
- El servidor usa su cadena de respaldo configurada por variables de entorno:<br>
348
- <strong>DeepSeek β†’ OpenRouter β†’ HuggingFace β†’ Ollama local β†’ regla de precio.</strong>
349
- </p>
350
- </div>
351
 
352
- <div class="prefs-section" id="view-prefs-external">
353
- <div class="form-group">
354
- <label for="view-prefs-provider">Proveedor</label>
355
- <select class="filter-select prefs-select" id="view-prefs-provider">
356
- <option value="deepseek">DeepSeek API</option>
357
- <option value="openrouter">OpenRouter</option>
358
- <option value="huggingface">HuggingFace Inference</option>
359
- </select>
360
- </div>
361
- <div class="form-group">
362
- <label for="view-prefs-api-key">Clave API</label>
363
- <input type="password" id="view-prefs-api-key" placeholder="sk-..." autocomplete="off" />
364
  </div>
365
  </div>
 
 
366
 
367
- <div class="prefs-section" id="view-prefs-local">
368
- <p class="prefs-description">Compatible con Ollama. Usa <code>/api/chat</code> en la URL base.</p>
369
- <div class="form-group">
370
- <label for="view-prefs-local-url">URL del servidor</label>
371
- <input type="text" id="view-prefs-local-url" placeholder="http://localhost:11434" />
372
- </div>
373
- <div class="form-group">
374
- <label for="view-prefs-local-model">Nombre del modelo</label>
375
- <input type="text" id="view-prefs-local-model" placeholder="qwen3:8b" />
376
- </div>
377
- </div>
378
 
379
- <div class="prefs-section" id="view-prefs-custom">
380
- <p class="prefs-description">Cualquier endpoint compatible con OpenAI Chat Completions.</p>
381
- <div class="form-group">
382
- <label for="view-prefs-custom-url">Endpoint URL</label>
383
- <input type="text" id="view-prefs-custom-url" placeholder="https://api.example.com/v1/chat/completions" />
384
- </div>
385
- <div class="form-group">
386
- <label for="view-prefs-custom-key">Clave API (opcional)</label>
387
- <input type="password" id="view-prefs-custom-key" placeholder="sk-..." autocomplete="off" />
388
- </div>
389
- <div class="form-group">
390
- <label for="view-prefs-custom-model">Nombre del modelo</label>
391
- <input type="text" id="view-prefs-custom-model" placeholder="gpt-4o-mini" />
392
- </div>
393
- </div>
394
 
395
- <div class="form-status" id="view-prefs-status"></div>
396
- <div class="form-actions">
397
- <button type="button" class="modal-submit" id="btn-view-save-prefs">Guardar</button>
 
 
 
 
398
  </div>
 
 
 
 
 
 
399
 
 
 
 
 
 
 
 
 
 
 
400
  </div>
401
  </div>
402
  </div>
403
- </section>
404
 
 
405
  </main>
406
  </div>
407
 
 
183
 
184
  <!-- Main content area -->
185
  <main class="main" id="main">
186
+ <div class="dashboard-grid view-dashboard">
187
 
188
+ <!-- Left: switchable views (map area) -->
189
+ <div class="main-content-area">
 
190
 
191
+ <!-- DASHBOARD VIEW -->
192
+ <section class="view active" id="view-dashboard">
193
+ <div class="panel map-panel" id="panel-map">
194
+ <div class="panel-header" data-panel="map">
195
+ <div class="panel-title">
196
+ <span>β—ˆ</span>
197
+ Mapa global
198
+ </div>
199
+ <div class="map-legend">
200
+ <div class="legend-item"><div class="legend-dot green"></div>alcista</div>
201
+ <div class="legend-item"><div class="legend-dot red"></div>bajista</div>
202
+ <div class="legend-item"><div class="legend-dot gray"></div>neutral</div>
203
+ </div>
204
+ <span class="panel-toggle">β–Ό</span>
205
  </div>
206
+ <div class="panel-body">
207
+ <div id="map-container"></div>
 
 
208
  </div>
 
209
  </div>
210
+ </section>
 
 
 
211
 
212
+ <!-- POSITIONS VIEW -->
213
+ <section class="view" id="view-positions">
214
+ <div class="panel full-height">
215
+ <div class="panel-header">
216
+ <div class="panel-title"><span>β—«</span> Simulador β€” Posiciones abiertas</div>
 
217
  </div>
218
+ <div class="panel-body">
219
+ <div class="table-wrap">
220
+ <table id="positions-table">
221
+ <thead>
222
+ <tr>
223
+ <th>Mercado</th>
224
+ <th>Resultado</th>
225
+ <th>Cantidad</th>
226
+ <th>Entrada</th>
227
+ <th>Actual</th>
228
+ <th>G&amp;P</th>
229
+ <th>Kelly</th>
230
+ <th>Abierta</th>
231
+ <th></th>
232
+ </tr>
233
+ </thead>
234
+ <tbody></tbody>
235
+ </table>
236
+ </div>
237
+ <div class="empty-state hidden" id="positions-empty">No hay posiciones abiertas. Ve al Panel para simular una operaciΓ³n.</div>
238
  </div>
239
  </div>
240
+ </section>
241
 
242
+ <!-- WATCHLIST VIEW -->
243
+ <section class="view" id="view-watchlist">
244
+ <div class="panel full-height">
245
+ <div class="panel-header">
246
+ <div class="panel-title"><span>β˜†</span> Lista de seguimiento</div>
247
+ </div>
248
+ <div class="panel-body">
249
+ <div class="table-wrap">
250
+ <table id="watchlist-table">
251
+ <thead>
252
+ <tr>
253
+ <th>Mercado</th>
254
+ <th>CategorΓ­a</th>
255
+ <th>SΓ­</th>
256
+ <th>No</th>
257
+ <th>SeΓ±al</th>
258
+ <th>Volumen</th>
259
+ <th>Umbral de alerta</th>
260
+ <th></th>
261
+ </tr>
262
+ </thead>
263
+ <tbody></tbody>
264
+ </table>
265
+ </div>
266
+ <div class="empty-state hidden" id="watchlist-empty">Tu lista de seguimiento estΓ‘ vacΓ­a. AΓ±ade mercados desde el Panel.</div>
267
  </div>
 
268
  </div>
269
+ </section>
270
+
271
+ <!-- ALERTS VIEW -->
272
+ <section class="view" id="view-alerts">
273
+ <div class="panel full-height">
274
+ <div class="panel-header">
275
+ <div class="panel-title"><span>⚑</span> Historial de alertas</div>
276
+ </div>
277
+ <div class="panel-body">
278
+ <div class="table-wrap">
279
+ <table id="alerts-table">
280
+ <thead>
281
+ <tr>
282
+ <th>Hora</th>
283
+ <th>Mercado</th>
284
+ <th>Tipo</th>
285
+ <th>Mensaje</th>
286
+ </tr>
287
+ </thead>
288
+ <tbody></tbody>
289
+ </table>
290
+ </div>
291
+ <div class="empty-state hidden" id="alerts-empty">AΓΊn no se han enviado alertas.</div>
292
+ </div>
293
  </div>
294
+ </section>
295
 
296
+ <!-- PREFERENCES VIEW (desktop only β€” mobile uses modal) -->
297
+ <section class="view" id="view-preferences">
298
+ <div class="panel full-height">
299
+ <div class="panel-header">
300
+ <div class="panel-title"><span>βš™</span> Modelo de IA</div>
301
+ </div>
302
+ <div class="panel-body">
303
+ <div class="prefs-view-body">
304
 
305
+ <div class="prefs-modes" id="view-prefs-modes">
306
+ <button class="prefs-mode-btn active" data-mode="auto">AutomΓ‘tico</button>
307
+ <button class="prefs-mode-btn" data-mode="external">Proveedor externo</button>
308
+ <button class="prefs-mode-btn" data-mode="local">Modelo local</button>
309
+ <button class="prefs-mode-btn" data-mode="custom">Personalizado</button>
310
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
+ <div class="prefs-section active" id="view-prefs-auto">
313
+ <p class="prefs-description">
314
+ El servidor usa su cadena de respaldo configurada por variables de entorno:<br>
315
+ <strong>DeepSeek β†’ OpenRouter β†’ HuggingFace β†’ Ollama local β†’ regla de precio.</strong>
316
+ </p>
317
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
+ <div class="prefs-section" id="view-prefs-external">
320
+ <div class="form-group">
321
+ <label for="view-prefs-provider">Proveedor</label>
322
+ <select class="filter-select prefs-select" id="view-prefs-provider">
323
+ <option value="deepseek">DeepSeek API</option>
324
+ <option value="openrouter">OpenRouter</option>
325
+ <option value="huggingface">HuggingFace Inference</option>
326
+ </select>
327
+ </div>
328
+ <div class="form-group">
329
+ <label for="view-prefs-api-key">Clave API</label>
330
+ <input type="password" id="view-prefs-api-key" placeholder="sk-..." autocomplete="off" />
331
+ </div>
332
+ </div>
 
 
 
 
 
 
 
 
 
 
333
 
334
+ <div class="prefs-section" id="view-prefs-local">
335
+ <p class="prefs-description">Compatible con Ollama. Usa <code>/api/chat</code> en la URL base.</p>
336
+ <div class="form-group">
337
+ <label for="view-prefs-local-url">URL del servidor</label>
338
+ <input type="text" id="view-prefs-local-url" placeholder="http://localhost:11434" />
339
+ </div>
340
+ <div class="form-group">
341
+ <label for="view-prefs-local-model">Nombre del modelo</label>
342
+ <input type="text" id="view-prefs-local-model" placeholder="qwen3:8b" />
343
+ </div>
344
+ </div>
345
 
346
+ <div class="prefs-section" id="view-prefs-custom">
347
+ <p class="prefs-description">Cualquier endpoint compatible con OpenAI Chat Completions.</p>
348
+ <div class="form-group">
349
+ <label for="view-prefs-custom-url">Endpoint URL</label>
350
+ <input type="text" id="view-prefs-custom-url" placeholder="https://api.example.com/v1/chat/completions" />
351
+ </div>
352
+ <div class="form-group">
353
+ <label for="view-prefs-custom-key">Clave API (opcional)</label>
354
+ <input type="password" id="view-prefs-custom-key" placeholder="sk-..." autocomplete="off" />
355
+ </div>
356
+ <div class="form-group">
357
+ <label for="view-prefs-custom-model">Nombre del modelo</label>
358
+ <input type="text" id="view-prefs-custom-model" placeholder="gpt-4o-mini" />
359
+ </div>
360
+ </div>
361
 
362
+ <div class="form-status" id="view-prefs-status"></div>
363
+ <div class="form-actions form-actions--end">
364
+ <button type="button" class="btn-ghost" id="btn-view-save-prefs">Guardar</button>
365
+ </div>
 
 
366
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  </div>
368
  </div>
369
+ </div>
370
+ </section>
371
 
372
+ </div>
 
 
 
 
 
 
 
 
 
 
373
 
374
+ <!-- Right: always visible -->
375
+ <div class="panel signals-panel" id="panel-signals">
376
+ <div class="panel-header" data-panel="signals">
377
+ <div class="panel-title">
378
+ <span>β—ˆ</span>
379
+ SeΓ±ales IA β€” mercados top
380
+ </div>
381
+ <span class="panel-toggle">β–Ό</span>
382
+ </div>
383
+ <div class="panel-body">
384
+ <div class="signals-list" id="signals-list"></div>
385
+ </div>
386
+ </div>
 
 
387
 
388
+ <!-- Bottom-left: positions + detail side by side -->
389
+ <div class="bottom-panels">
390
+ <div class="panel positions-mini-panel" id="panel-positions-mini">
391
+ <div class="panel-header" data-panel="positions-mini">
392
+ <div class="panel-title">
393
+ <span>β—«</span>
394
+ Mis posiciones
395
  </div>
396
+ <span class="panel-toggle">β–Ό</span>
397
+ </div>
398
+ <div class="panel-body">
399
+ <div id="mini-positions"></div>
400
+ </div>
401
+ </div>
402
 
403
+ <div class="panel detail-panel" id="panel-detail">
404
+ <div class="panel-header" data-panel="detail">
405
+ <div class="panel-title">
406
+ <span>β—ˆ</span>
407
+ Detalle del mercado
408
+ </div>
409
+ <span class="panel-toggle">β–Ό</span>
410
+ </div>
411
+ <div class="panel-body" id="detail-body">
412
+ <!-- Dynamic content -->
413
  </div>
414
  </div>
415
  </div>
 
416
 
417
+ </div>
418
  </main>
419
  </div>
420
 
frontend/src/app.js CHANGED
@@ -620,6 +620,7 @@ function switchView(viewName) {
620
  state.view = viewName
621
  document.querySelectorAll('.view').forEach((v) => v.classList.toggle('active', v.id === `view-${viewName}`))
622
  document.querySelectorAll('.nav-item').forEach((v) => v.classList.toggle('active', v.dataset.view === viewName))
 
623
  if (viewName === 'positions') renderPositions()
624
  if (viewName === 'watchlist') renderWatchlist()
625
  if (viewName === 'alerts') renderAlerts()
@@ -913,12 +914,13 @@ function renderMiniPositions() {
913
  const cls = (p.pnl || 0) >= 0 ? 'green' : 'red'
914
  const sign = (p.pnl || 0) >= 0 ? '+' : ''
915
 
916
- const row = el('div', 'flex-between mb-6')
917
  const label = el('span', 'text-sm text-neutral font-mono')
918
  label.textContent = `${(m.question || p.marketId).substring(0, 32)}${(m.question || p.marketId).length > 32 ? '…' : ''} ${translateOutcome(p.outcome)}`
919
  const val = el('span', `text-base font-semibold text-${cls} font-mono`)
920
  val.textContent = `${sign}€${(p.pnl || 0).toFixed(2)}`
921
  row.append(label, val)
 
922
  container.appendChild(row)
923
  })
924
 
@@ -1452,6 +1454,12 @@ async function initAppData() {
1452
  /* ─── InicializaciΓ³n ─── */
1453
  export async function init() {
1454
  document.getElementById('sidebar-toggle')?.addEventListener('click', toggleSidebar)
 
 
 
 
 
 
1455
 
1456
  document.querySelectorAll('.nav-item').forEach((item) => {
1457
  item.addEventListener('click', () => switchView(item.dataset.view))
 
620
  state.view = viewName
621
  document.querySelectorAll('.view').forEach((v) => v.classList.toggle('active', v.id === `view-${viewName}`))
622
  document.querySelectorAll('.nav-item').forEach((v) => v.classList.toggle('active', v.dataset.view === viewName))
623
+ document.querySelector('.dashboard-grid')?.classList.toggle('view-dashboard', viewName === 'dashboard')
624
  if (viewName === 'positions') renderPositions()
625
  if (viewName === 'watchlist') renderWatchlist()
626
  if (viewName === 'alerts') renderAlerts()
 
914
  const cls = (p.pnl || 0) >= 0 ? 'green' : 'red'
915
  const sign = (p.pnl || 0) >= 0 ? '+' : ''
916
 
917
+ const row = el('div', 'flex-between mb-6 pos-row')
918
  const label = el('span', 'text-sm text-neutral font-mono')
919
  label.textContent = `${(m.question || p.marketId).substring(0, 32)}${(m.question || p.marketId).length > 32 ? '…' : ''} ${translateOutcome(p.outcome)}`
920
  const val = el('span', `text-base font-semibold text-${cls} font-mono`)
921
  val.textContent = `${sign}€${(p.pnl || 0).toFixed(2)}`
922
  row.append(label, val)
923
+ row.addEventListener('click', () => selectMarket(p.marketId))
924
  container.appendChild(row)
925
  })
926
 
 
1454
  /* ─── InicializaciΓ³n ─── */
1455
  export async function init() {
1456
  document.getElementById('sidebar-toggle')?.addEventListener('click', toggleSidebar)
1457
+ document.querySelector('.topbar-logo')?.addEventListener('click', () => {
1458
+ if (window.matchMedia('(max-width: 640px)').matches) {
1459
+ switchView('dashboard')
1460
+ document.getElementById('main')?.scrollTo({ top: 0, behavior: 'smooth' })
1461
+ }
1462
+ })
1463
 
1464
  document.querySelectorAll('.nav-item').forEach((item) => {
1465
  item.addEventListener('click', () => switchView(item.dataset.view))
frontend/src/map.js CHANGED
@@ -204,14 +204,16 @@ export function init(containerId, markets, signals, onSelect) {
204
  attributionControl: false,
205
  minZoom: 2,
206
  maxZoom: 6,
207
- worldCopyJump: true,
208
  }).setView([25, 10], 2)
209
 
210
  // Capa de tiles oscura (CartoDB Dark Matter)
211
- L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
212
  attribution: '&copy;OpenStreetMap &copy;CartoDB',
213
  subdomains: 'abcd',
214
  maxZoom: 19,
 
 
215
  }).addTo(mapInstance)
216
 
217
  mapInstance.on('zoomend', updateDiagonalLines)
 
204
  attributionControl: false,
205
  minZoom: 2,
206
  maxZoom: 6,
207
+ maxBounds: [[-90, -180], [90, 180]],
208
  }).setView([25, 10], 2)
209
 
210
  // Capa de tiles oscura (CartoDB Dark Matter)
211
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png', {
212
  attribution: '&copy;OpenStreetMap &copy;CartoDB',
213
  subdomains: 'abcd',
214
  maxZoom: 19,
215
+ noWrap: true,
216
+ bounds: [[-90, -180], [90, 180]],
217
  }).addTo(mapInstance)
218
 
219
  mapInstance.on('zoomend', updateDiagonalLines)
frontend/src/style.css CHANGED
@@ -405,6 +405,27 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
405
  .view { display: none; }
406
  .view.active { display: block; height: 100%; }
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  /* ─── Dashboard layout ─── */
409
  .dashboard-grid {
410
  display: grid;
@@ -472,12 +493,6 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
472
  color: var(--text3);
473
  }
474
 
475
- .positions-separator {
476
- margin-top: 10px;
477
- padding-top: 10px;
478
- border-top: 0.5px solid var(--border);
479
- }
480
-
481
  .panel-title.mb-sm {
482
  margin-bottom: 8px;
483
  }
@@ -526,7 +541,7 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
526
  width: 100%;
527
  height: 100%;
528
  min-height: 300px;
529
- background: var(--bg3);
530
  border-radius: var(--radius-sm);
531
  overflow: hidden;
532
  }
@@ -799,13 +814,41 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
799
  background: rgba(240,64,64,0.05);
800
  }
801
 
802
- /* Panel detalle */
803
- .detail-panel {
 
 
804
  grid-row: 2 / 3;
805
  grid-column: 1 / 2;
 
806
  overflow: hidden;
807
  }
808
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
809
  .detail-panel .panel-body {
810
  overflow: hidden;
811
  }
@@ -920,7 +963,7 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
920
  .outcome-card.no .outcome-price { color: var(--red); }
921
 
922
  .chart-container {
923
- flex: 2;
924
  background: var(--bg3);
925
  border: 0.5px solid var(--border);
926
  border-radius: var(--radius-sm);
@@ -977,7 +1020,7 @@ h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
977
  }
978
 
979
  .ai-box {
980
- flex: 1;
981
  background: var(--bg3);
982
  border: 0.5px solid var(--blue2);
983
  border-radius: var(--radius-sm);
@@ -1167,7 +1210,7 @@ td {
1167
 
1168
  /* ─── Leaflet overrides ─── */
1169
  #app .leaflet-container {
1170
- background: var(--bg3);
1171
  font-family: var(--font-mono);
1172
  }
1173
 
@@ -1597,6 +1640,10 @@ td {
1597
  margin-top: 4px;
1598
  }
1599
 
 
 
 
 
1600
  .modal-submit--secondary {
1601
  background: var(--bg3);
1602
  color: var(--text);
@@ -1884,21 +1931,44 @@ td {
1884
 
1885
  .dashboard-grid {
1886
  grid-template-columns: 1fr;
1887
- grid-template-rows: auto auto auto;
1888
  }
1889
- .signals-panel {
1890
  grid-row: auto;
1891
  grid-column: auto;
1892
- max-height: 400px;
 
1893
  }
1894
- .map-panel {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1895
  grid-row: auto;
1896
  grid-column: auto;
1897
- min-height: 300px;
1898
  }
1899
- .detail-panel {
 
 
 
 
 
 
1900
  grid-row: auto;
1901
  grid-column: auto;
 
1902
  }
1903
  }
1904
 
@@ -2017,6 +2087,7 @@ td {
2017
  height: auto;
2018
  order: 0;
2019
  flex: 1;
 
2020
  }
2021
 
2022
  .topbar-actions {
@@ -2084,17 +2155,64 @@ td {
2084
  /* Dashboard grid scrolls instead of stretching to fill viewport */
2085
  .dashboard-grid {
2086
  height: auto;
 
 
2087
  }
2088
 
2089
- /* Remove fixed heights on panels so collapsed state can fully shrink */
2090
- .map-panel {
2091
- min-height: 250px;
 
 
 
 
 
 
 
 
 
 
 
2092
  }
2093
 
2094
- .signals-panel {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2095
  max-height: none;
2096
  }
2097
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2098
  .panel.collapsed {
2099
  min-height: 0;
2100
  max-height: none;
 
405
  .view { display: none; }
406
  .view.active { display: block; height: 100%; }
407
 
408
+ /* Container for switchable views (left side) */
409
+ .main-content-area {
410
+ grid-row: 1 / 2;
411
+ grid-column: 1 / 2;
412
+ position: relative;
413
+ overflow: hidden;
414
+ min-height: 0;
415
+ }
416
+
417
+ .main-content-area > .view {
418
+ position: absolute;
419
+ top: 0;
420
+ left: 0;
421
+ width: 100%;
422
+ height: 100%;
423
+ }
424
+
425
+ .main-content-area .panel {
426
+ height: 100%;
427
+ }
428
+
429
  /* ─── Dashboard layout ─── */
430
  .dashboard-grid {
431
  display: grid;
 
493
  color: var(--text3);
494
  }
495
 
 
 
 
 
 
 
496
  .panel-title.mb-sm {
497
  margin-bottom: 8px;
498
  }
 
541
  width: 100%;
542
  height: 100%;
543
  min-height: 300px;
544
+ background: #262626;
545
  border-radius: var(--radius-sm);
546
  overflow: hidden;
547
  }
 
814
  background: rgba(240,64,64,0.05);
815
  }
816
 
817
+ /* Contenedor inferior: posiciones + detalle */
818
+ .bottom-panels {
819
+ display: flex;
820
+ gap: var(--panel-gap);
821
  grid-row: 2 / 3;
822
  grid-column: 1 / 2;
823
+ min-height: 0;
824
  overflow: hidden;
825
  }
826
 
827
+ .positions-mini-panel {
828
+ width: 420px;
829
+ flex-shrink: 0;
830
+ }
831
+
832
+ .pos-row {
833
+ cursor: pointer;
834
+ border-radius: var(--radius-sm);
835
+ padding: 4px 6px;
836
+ margin-left: -6px;
837
+ margin-right: -6px;
838
+ transition: background 0.12s;
839
+ }
840
+
841
+ .pos-row:hover {
842
+ background: rgba(255,255,255,0.05);
843
+ }
844
+
845
+ /* Panel detalle ocupa el espacio restante */
846
+ .detail-panel {
847
+ flex: 1;
848
+ overflow: hidden;
849
+ min-width: 0;
850
+ }
851
+
852
  .detail-panel .panel-body {
853
  overflow: hidden;
854
  }
 
963
  .outcome-card.no .outcome-price { color: var(--red); }
964
 
965
  .chart-container {
966
+ flex: 7;
967
  background: var(--bg3);
968
  border: 0.5px solid var(--border);
969
  border-radius: var(--radius-sm);
 
1020
  }
1021
 
1022
  .ai-box {
1023
+ flex: 8;
1024
  background: var(--bg3);
1025
  border: 0.5px solid var(--blue2);
1026
  border-radius: var(--radius-sm);
 
1210
 
1211
  /* ─── Leaflet overrides ─── */
1212
  #app .leaflet-container {
1213
+ background: #262626;
1214
  font-family: var(--font-mono);
1215
  }
1216
 
 
1640
  margin-top: 4px;
1641
  }
1642
 
1643
+ .form-actions--end {
1644
+ justify-content: flex-end;
1645
+ }
1646
+
1647
  .modal-submit--secondary {
1648
  background: var(--bg3);
1649
  color: var(--text);
 
1931
 
1932
  .dashboard-grid {
1933
  grid-template-columns: 1fr;
1934
+ grid-template-rows: auto;
1935
  }
1936
+ .main-content-area {
1937
  grid-row: auto;
1938
  grid-column: auto;
1939
+ min-height: 300px;
1940
+ position: relative;
1941
  }
1942
+ .main-content-area > .view {
1943
+ position: relative;
1944
+ top: auto;
1945
+ left: auto;
1946
+ width: 100%;
1947
+ height: auto;
1948
+ }
1949
+ .main-content-area .panel {
1950
+ height: auto;
1951
+ }
1952
+ .signals-panel,
1953
+ .bottom-panels {
1954
+ display: none;
1955
+ }
1956
+ .dashboard-grid.view-dashboard .signals-panel,
1957
+ .dashboard-grid.view-dashboard .bottom-panels {
1958
+ display: flex;
1959
  grid-row: auto;
1960
  grid-column: auto;
 
1961
  }
1962
+ .dashboard-grid.view-dashboard .signals-panel {
1963
+ max-height: 400px;
1964
+ }
1965
+ .positions-mini-panel {
1966
+ display: none;
1967
+ }
1968
+ .map-panel {
1969
  grid-row: auto;
1970
  grid-column: auto;
1971
+ min-height: 300px;
1972
  }
1973
  }
1974
 
 
2087
  height: auto;
2088
  order: 0;
2089
  flex: 1;
2090
+ cursor: pointer;
2091
  }
2092
 
2093
  .topbar-actions {
 
2155
  /* Dashboard grid scrolls instead of stretching to fill viewport */
2156
  .dashboard-grid {
2157
  height: auto;
2158
+ grid-template-columns: 1fr;
2159
+ grid-template-rows: auto;
2160
  }
2161
 
2162
+ /* Switchable views flow normally on mobile */
2163
+ .main-content-area {
2164
+ grid-row: auto;
2165
+ grid-column: auto;
2166
+ position: relative;
2167
+ min-height: auto;
2168
+ }
2169
+
2170
+ .main-content-area > .view {
2171
+ position: relative;
2172
+ top: auto;
2173
+ left: auto;
2174
+ width: 100%;
2175
+ height: auto;
2176
  }
2177
 
2178
+ .main-content-area .panel {
2179
+ height: auto;
2180
+ }
2181
+
2182
+ /* Hide persistent panels by default on mobile */
2183
+ .signals-panel,
2184
+ .bottom-panels {
2185
+ display: none;
2186
+ }
2187
+
2188
+ /* Only show panels when on dashboard view */
2189
+ .dashboard-grid.view-dashboard .signals-panel,
2190
+ .dashboard-grid.view-dashboard .bottom-panels {
2191
+ display: flex;
2192
+ grid-row: auto;
2193
+ grid-column: auto;
2194
+ }
2195
+
2196
+ .dashboard-grid.view-dashboard .signals-panel {
2197
  max-height: none;
2198
  }
2199
 
2200
+ .dashboard-grid.view-dashboard .bottom-panels {
2201
+ overflow: hidden;
2202
+ }
2203
+
2204
+ .positions-mini-panel {
2205
+ display: none;
2206
+ }
2207
+
2208
+ /* Remove fixed heights on panels so collapsed state can fully shrink */
2209
+ .main-content-area {
2210
+ min-height: 250px;
2211
+ }
2212
+ .map-panel {
2213
+ min-height: 250px;
2214
+ }
2215
+
2216
  .panel.collapsed {
2217
  min-height: 0;
2218
  max-height: none;