¿Quién va a estudiar hoy?
Cada alumno tiene su propio progreso, guardado en este dispositivo
Crear nuevo perfil
🔐
Panel Docente
Ingresá la contraseña de configuración
Contraseña incorrecta
Contraseña por defecto: docente2024
Cambiala desde el panel una vez adentro.
Modo Socrático
Definí un tema en el panel izquierdo
Hola, soy Eterneura
Tu compañero de aprendizaje. Elegí un modo, definí tu tema y empezá. Podés subir tus propios apuntes como base de conocimiento.
📋 Historial de sesiones
// Usa Open-Meteo (sin key, CORS libre) para datos numéricos, // y Geocoding API de Open-Meteo para convertir nombre → lat/lon. // Las imágenes satelitales son URLs públicas del SMN y GOES-16. let wpWeatherData = null; // datos actuales cacheados para el análisis IA let wpLocationName = ''; let wpLat = null, wpLon = null; const WP_WEATHER_CODES = { 0:'Despejado',1:'Mayormente despejado',2:'Parcialmente nublado',3:'Nublado', 45:'Niebla',48:'Niebla con escarcha',51:'Llovizna ligera',53:'Llovizna moderada',55:'Llovizna intensa', 61:'Lluvia ligera',63:'Lluvia moderada',65:'Lluvia intensa', 71:'Nevada ligera',73:'Nevada moderada',75:'Nevada intensa', 80:'Chaparrones ligeros',81:'Chaparrones moderados',82:'Chaparrones intensos', 95:'Tormenta',96:'Tormenta con granizo ligero',99:'Tormenta con granizo intenso' }; const WP_WEATHER_ICON = { 0:'☀️',1:'🌤',2:'⛅',3:'☁️',45:'🌫',48:'🌫', 51:'🌦',53:'🌦',55:'🌧',61:'🌧',63:'🌧',65:'🌧', 71:'🌨',73:'❄️',75:'❄️',80:'🌦',81:'🌧',82:'⛈', 95:'⛈',96:'⛈',99:'⛈' }; const WP_SAT_SOURCES = [ { label: 'Satélite GOES-16 — Sudamérica visible', url: 'https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/sa/GEOCOLOR/latest.jpg', ts: 'NOAA / GOES-16 · Actualización automática' }, { label: 'Satélite GOES-16 — Infrarrojo (IR)', url: 'https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/sa/13/latest.jpg', ts: 'NOAA / GOES-16 Canal 13 (IR) · Actualización automática' }, { label: 'Radar Nacional — SMN Argentina', url: 'https://www.smn.gob.ar/sites/default/files/imagenes/radar/radar-nacional.gif', ts: 'SMN Argentina · Se actualiza cada ~10 minutos' }, { label: 'Satélite IR — SMN Argentina', url: 'https://www.smn.gob.ar/sites/default/files/imagenes/satelite/satelitecanal4-SudAmerica.jpg', ts: 'SMN Argentina · Canal 4 IR · Se actualiza cada ~30 minutos' } ]; function openWeatherPanel() { document.getElementById('weather-overlay').classList.add('open'); renderSatImages(); } function closeWeatherPanel() { document.getElementById('weather-overlay').classList.remove('open'); } function switchWpTab(btn) { document.querySelectorAll('.wp-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.wp-section').forEach(s => s.classList.remove('active')); btn.classList.add('active'); document.getElementById(btn.getAttribute('data-tab')).classList.add('active'); if (btn.getAttribute('data-tab') === 'wp-satellite-sec') renderSatImages(); } function wpShowError(msg) { const el = document.getElementById('wp-error'); el.textContent = msg; el.style.display = 'block'; } function wpHideError() { document.getElementById('wp-error').style.display = 'none'; } async function fetchWeather() { const locInput = document.getElementById('wp-loc-input').value.trim(); if (!locInput) { wpShowError('Escribí una ciudad'); return; } wpHideError(); const btn = document.getElementById('wp-fetch-btn'); btn.disabled = true; btn.textContent = '⏳'; try { // 1. Geocoding: nombre → lat/lon const geoRes = await fetch( `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(locInput)}&count=1&language=es&format=json` ); const geoData = await geoRes.json(); if (!geoData.results?.length) { wpShowError('No se encontró esa ciudad. Probá con otro nombre.'); return; } const loc = geoData.results[0]; wpLat = loc.latitude; wpLon = loc.longitude; wpLocationName = `${loc.name}${loc.admin1 ? ', '+loc.admin1 : ''}, ${loc.country}`; await loadWeatherData(); } catch(err) { wpShowError('Error de conexión: ' + err.message); } finally { btn.disabled = false; btn.textContent = 'Buscar'; } } async function fetchWeatherGeo() { wpHideError(); if (!navigator.geolocation) { wpShowError('Tu navegador no soporta geolocalización'); return; } navigator.geolocation.getCurrentPosition( async pos => { wpLat = pos.coords.latitude; wpLon = pos.coords.longitude; // Reverse geocoding via Open-Meteo try { const r = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=&latitude=${wpLat}&longitude=${wpLon}&count=1&language=es&format=json`); const d = await r.json(); wpLocationName = d.results?.[0]?.name || `${wpLat.toFixed(2)}, ${wpLon.toFixed(2)}`; } catch(_) { wpLocationName = `${wpLat.toFixed(2)}, ${wpLon.toFixed(2)}`; } await loadWeatherData(); }, err => wpShowError('No se pudo obtener tu ubicación: ' + err.message) ); } async function loadWeatherData() { const url = `https://api.open-meteo.com/v1/forecast?latitude=${wpLat}&longitude=${wpLon}` + `¤t=temperature_2m,apparent_temperature,relative_humidity_2m,precipitation,weather_code,` + `wind_speed_10m,wind_direction_10m,surface_pressure,uv_index` + `&hourly=precipitation_probability` + `&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max` + `&timezone=auto&forecast_days=7`; const res = await fetch(url); if (!res.ok) { wpShowError('Error al obtener datos meteorológicos'); return; } const data = await res.json(); wpWeatherData = { ...data, locationName: wpLocationName, lat: wpLat, lon: wpLon }; renderCurrentWeather(data); renderForecast(data); document.getElementById('wp-tabs').style.display = 'flex'; document.getElementById('wp-content').style.display = 'block'; } function windDir(deg) { const dirs = ['N','NE','E','SE','S','SO','O','NO']; return dirs[Math.round(deg / 45) % 8]; } function renderCurrentWeather(data) { const c = data.current; const code = c.weather_code; document.getElementById('wp-loc-label').textContent = wpLocationName; document.getElementById('wp-temp').innerHTML = `${Math.round(c.temperature_2m)}°C`; document.getElementById('wp-desc').textContent = `${WP_WEATHER_ICON[code] || '🌡'} ${WP_WEATHER_CODES[code] || 'Condición desconocida'}`; document.getElementById('wp-feel').textContent = `${Math.round(c.apparent_temperature)}°C`; document.getElementById('wp-hum').textContent = `${c.relative_humidity_2m}%`; document.getElementById('wp-wind').textContent = `${Math.round(c.wind_speed_10m)} km/h ${windDir(c.wind_direction_10m)}`; document.getElementById('wp-rain1h').textContent = `${(c.precipitation || 0).toFixed(1)} mm`; document.getElementById('wp-press').textContent = `${Math.round(c.surface_pressure || 0)} hPa`; document.getElementById('wp-uv').textContent = c.uv_index != null ? c.uv_index.toFixed(1) : '—'; } function renderForecast(data) { const grid = document.getElementById('wp-forecast-grid'); grid.innerHTML = ''; const days = ['Dom','Lun','Mar','Mié','Jue','Vie','Sáb']; (data.daily.time || []).forEach((dateStr, i) => { const d = new Date(dateStr + 'T12:00:00'); const code = data.daily.weather_code[i]; const el = document.createElement('div'); el.className = 'wp-day'; el.innerHTML = `
${days[d.getDay()]}
${WP_WEATHER_ICON[code] || '🌡'}
${Math.round(data.daily.temperature_2m_max[i])}°
${Math.round(data.daily.temperature_2m_min[i])}°
💧 ${Math.round(data.daily.precipitation_probability_max[i] || 0)}%
`; grid.appendChild(el); }); } function renderSatImages() { const grid = document.getElementById('wp-sat-grid'); grid.innerHTML = ''; const ts = new Date().toLocaleTimeString('es-AR', {hour:'2-digit',minute:'2-digit'}); WP_SAT_SOURCES.forEach(src => { const card = document.createElement('div'); card.className = 'wp-img-card'; const cacheBust = `?cb=${Date.now()}`; card.innerHTML = `
${src.label}
${src.ts}
${src.label} `; grid.appendChild(card); }); } function refreshSatImage(btn, baseUrl) { const img = btn.previousElementSibling; if (img && img.tagName === 'IMG') { img.src = baseUrl + '?cb=' + Date.now(); btn.textContent = '⏳'; img.onload = () => { btn.textContent = '↻ Refrescar'; }; img.onerror = () => { btn.textContent = '↻ Refrescar'; }; } } // ── Análisis IA: datos numéricos ────────────────────────── async function analyzeWeatherText() { if (!wpWeatherData) { wpShowError('Primero buscá una ciudad'); return; } switchWpTab(document.querySelector('[data-tab="wp-ai-sec"]')); const box = document.getElementById('wp-ai-box'); box.className = 'wp-ai-box loading'; box.innerHTML = 'Analizando condiciones meteorológicas…'; const c = wpWeatherData.current; const daily = wpWeatherData.daily; const code = c.weather_code; const forecastLines = (daily.time || []).map((d,i) => ` ${d}: ${WP_WEATHER_CODES[daily.weather_code[i]]||'?'}, máx ${Math.round(daily.temperature_2m_max[i])}°C / mín ${Math.round(daily.temperature_2m_min[i])}°C, prob lluvia ${daily.precipitation_probability_max[i]||0}%` ).join('\n'); const prompt = `Sos un meteorólogo experto. Analizá los siguientes datos meteorológicos actuales y el pronóstico de los próximos 7 días para ${wpWeatherData.locationName} (lat ${wpWeatherData.lat?.toFixed(2)}, lon ${wpWeatherData.lon?.toFixed(2)}) y elaborá un análisis en español rioplatense, claro y didáctico. Incluí: condición actual, interpretación de los parámetros relevantes (sensación térmica, viento, presión, UV), tendencia general de la semana, y recomendaciones prácticas. DATOS ACTUALES: - Temperatura: ${Math.round(c.temperature_2m)}°C (sensación: ${Math.round(c.apparent_temperature)}°C) - Condición: ${WP_WEATHER_CODES[code] || code} - Humedad: ${c.relative_humidity_2m}% - Viento: ${Math.round(c.wind_speed_10m)} km/h (${windDir(c.wind_direction_10m)}) - Lluvia última hora: ${(c.precipitation||0).toFixed(1)} mm - Presión: ${Math.round(c.surface_pressure||0)} hPa - Índice UV: ${c.uv_index ?? '—'} PRONÓSTICO 7 DÍAS: ${forecastLines} Respondé en español rioplatense. Sé concreto y útil, no genérico.`; try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [{ role: 'user', content: prompt }] }) }); const data = await res.json(); box.className = 'wp-ai-box'; if (!res.ok || data.error) { box.innerHTML = `⚠️ Error: ${escapeHtml(data.error || 'Sin respuesta del servidor.')}`; } else { box.innerHTML = renderMarkdownBody(data.reply); } } catch(err) { box.className = 'wp-ai-box'; box.innerHTML = '⚠️ No se pudo conectar al servidor.'; } } // ── Análisis IA: imágenes satelitales ──────────────────── async function analyzeWeatherImages() { switchWpTab(document.querySelector('[data-tab="wp-ai-sec"]')); const box = document.getElementById('wp-ai-box'); box.className = 'wp-ai-box loading'; box.innerHTML = 'Descargando imágenes y analizando…'; // Convertimos las 2 primeras imágenes a base64 para mandárselas al modelo de visión async function imgToBase64(url) { try { // Las imágenes del SMN/NOAA no tienen CORS, así que las pedimos via proxy canvas const proxyUrl = `https://corsproxy.io/?${encodeURIComponent(url + '?cb=' + Date.now())}`; const res = await fetch(proxyUrl); if (!res.ok) throw new Error('fetch failed'); const blob = await res.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } catch(_) { return null; } } const sources = WP_SAT_SOURCES.slice(0, 4); const images = (await Promise.all(sources.map(s => imgToBase64(s.url)))).filter(Boolean); if (!images.length) { box.className = 'wp-ai-box'; box.innerHTML = '⚠️ No se pudieron obtener las imágenes satelitales. Las fuentes pueden estar temporalmente no disponibles. Usá el botón "Analizar condiciones actuales" en su lugar.'; return; } const locCtx = wpWeatherData ? ` La ubicación de referencia es ${wpWeatherData.locationName} (lat ${wpWeatherData.lat?.toFixed(2)}, lon ${wpWeatherData.lon?.toFixed(2)}).` : ''; const content = [ ...images.map((dataUrl, i) => ({ type: 'image_url', image_url: { url: dataUrl } })), { type: 'text', text: `Estas son imágenes meteorológicas satelitales e IR de Sudamérica y radar nacional de Argentina.${locCtx} Analizalas en español rioplatense: describí los sistemas de nubosidad visibles, frentes, zonas de lluvia o convección, y elaborá un pronóstico a corto plazo (próximas 6–12 horas) para la región. Sé técnico pero claro.` } ]; try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [{ role: 'user', content }], hasImages: true }) }); const data = await res.json(); box.className = 'wp-ai-box'; if (!res.ok || data.error) { box.innerHTML = `⚠️ Error: ${escapeHtml(data.error || 'Sin respuesta.')}`; } else { box.innerHTML = renderMarkdownBody(data.reply); } } catch(err) { box.className = 'wp-ai-box'; box.innerHTML = '⚠️ No se pudo conectar al servidor.'; } } // ══ /Panel Clima ══════════════════════════════════════════