Jose Salazar
Correccion de bugs del pipeline de IA, incorporo openrouter con deepseek para fallback, rate limit de login diferenciado entre prod y dev, cambios varios en UI
8a4b117 | /** | |
| * Modulo de visualizacion de graficos usando Chart.js. | |
| * | |
| * Responsabilidades: | |
| * - renderDetailChart(canvasId, currentPrice) → grafico de linea 7d con sparklines. | |
| * - renderSparkline(containerId, price, side) → barras verticales animadas (YES/NO). | |
| * | |
| * Datos: | |
| * - Los puntos se generan sinteticamente con ruido aleatorio alrededor del precio actual. | |
| * - Color verde si precio > 50¢, rojo si < 40¢, naranja en medio. | |
| * | |
| * Consumido por: | |
| * - app.js → al renderizar el panel de detalle de un mercado. | |
| */ | |
| import { Chart } from 'chart.js/auto' | |
| let detailChartInstance = null | |
| export function renderDetailChart(canvasId, currentPrice, history = []) { | |
| const ctx = document.getElementById(canvasId) | |
| if (!ctx) return | |
| if (detailChartInstance) detailChartInstance.destroy() | |
| const container = ctx.parentElement | |
| if (container) { | |
| const labelEl = container.querySelector('.chart-label') | |
| const labelH = labelEl ? labelEl.offsetHeight + 6 : 24 | |
| ctx.style.height = `${container.clientHeight - labelH - 28}px` | |
| } | |
| let pts, labels | |
| if (history.length >= 2) { | |
| pts = history.map((h) => +(h.p * 100).toFixed(2)) | |
| labels = history.map((h) => { | |
| const d = new Date(h.t * 1000) | |
| return `${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}h` | |
| }) | |
| } else { | |
| const base = currentPrice * 100 | |
| pts = Array.from({ length: 8 }, (_, i) => { | |
| const noise = (Math.random() - 0.5) * 8 | |
| return Math.max(5, Math.min(95, base - 12 + (i / 7) * 12 + noise)) | |
| }) | |
| pts[pts.length - 1] = base | |
| labels = ['7d', '6d', '5d', '4d', '3d', '2d', '1d', 'ahora'] | |
| } | |
| const last = pts[pts.length - 1] | |
| const col = last > 50 ? '#22d37a' : last < 40 ? '#f04040' : '#f0a020' | |
| detailChartInstance = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels, | |
| datasets: [ | |
| { | |
| data: pts, | |
| borderColor: col, | |
| borderWidth: 1.5, | |
| pointRadius: 0, | |
| fill: false, | |
| tension: 0.4, | |
| }, | |
| ], | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { display: false }, tooltip: { enabled: false } }, | |
| scales: { x: { display: false }, y: { display: false } }, | |
| animation: { duration: 600 }, | |
| }, | |
| }) | |
| } | |
| export function renderSparkline(containerId, price, side) { | |
| const el = document.getElementById(containerId) | |
| if (!el) return | |
| el.innerHTML = '' | |
| el.className = 'sparkline' | |
| const base = price * 100 | |
| for (let i = 0; i < 12; i++) { | |
| const h = Math.max(4, Math.min(24, base / 4 + (Math.random() - 0.5) * 8)) | |
| const d = document.createElement('div') | |
| d.className = 'spark-bar' | |
| d.style.height = h + 'px' | |
| d.style.background = side === 'yes' ? '#0d6e3a' : '#7a1a1a' | |
| el.appendChild(d) | |
| } | |
| const last = document.createElement('div') | |
| last.className = 'spark-bar' | |
| last.style.height = Math.min(28, base / 3.5) + 'px' | |
| last.style.background = side === 'yes' ? '#22d37a' : '#f04040' | |
| el.appendChild(last) | |
| } | |