{
setStore({...store, guess1:guess});
setSubmitted(true);
}}>Confirmar mi predicción →
Una vez confirmes, te muestro lo que pasó realmente
) : (
)}
);
}
function Reveal({guess, real, score, onNext}){
return (
Tu margen de error
± {Math.abs(guess-real).toFixed(0)} μg/m³
Adivinar el aire es difícil: la diferencia entre respirar aire limpio y respirar aire contaminado son apenas 15 μg. Por eso los pronósticos no se hacen a ojo.
Veamos cómo lo hace la ciencia →
);
}
function MiniReadout({label, value, muted, highlight}){
return (
{label}
{value}
μg/m³
{pmLabel(value)}
);
}
// ───────────────────────────────────────────────────────────────
// ROUND 2 — INGREDIENTS (toggle data sources, watch uncertainty shrink)
// ───────────────────────────────────────────────────────────────
const INGREDIENTS = [
{
id:'sat', icon:'🛰️',
name:'Satélites globales',
sys:'CAMS · Copernicus',
desc:'Cada 12 h, satélites europeos miden polvo, humo y gases sobre todo el planeta. Nos dicen, por ejemplo, si llega humo del Amazonas o polvo del Sahara.',
reduces: 18, // μg/m³ uncertainty removed
color:'#A78BFA',
},
{
id:'wrf', icon:'🌬️',
name:'Modelo meteorológico local',
sys:'WRF · 1 km Valle de Aburrá',
desc:'Una simulación meteorológica del valle: viento, lluvia, inversión térmica. Nos dice si el material particulado se va a quedar atrapado entre las montañas o se va a dispersar.',
reduces: 14,
color:'#0DCCC0',
},
{
id:'sens', icon:'🌡️',
name:'Sensores en tierra',
sys:'20 estaciones SIATA',
desc:'Mediciones reales, minuto a minuto, en estaciones del Valle. Corrigen al modelo cuando se equivoca: lo aterrizan al aire que de verdad estamos respirando.',
reduces: 8,
color:'#84BD45',
},
];
function RoundIngredients({onNext, store, setStore}){
const [active, setActive] = React.useState(store.active || {});
const baseUncertainty = 42;
const reduced = INGREDIENTS.reduce((s,i)=> active[i.id] ? s + i.reduces : s, 0);
const uncertainty = Math.max(2, baseUncertainty - reduced);
const prediction = 38;
const accuracy = Math.round( ((baseUncertainty - uncertainty) / baseUncertainty) * 100 );
const allOn = INGREDIENTS.every(i => active[i.id]);
const toggle = (id)=>{
const next = {...active, [id]: !active[id]};
setActive(next);
setStore({...store, active: next});
};
return (
);
}
// ───────────────────────────────────────────────────────────────
// ROUND 3 — DECISIONS (apply the 72h forecast to 3 people)
// ───────────────────────────────────────────────────────────────
const HOURS = Array.from({length:24}, (_,i) => i);
// 3-day fake forecast, but plausible: bad morning, ok afternoon, peak Sunday
const FORECAST = [
// Day 1 — Saturday
{label:'Sábado', values: [22,28,34,38,42,46,48,44,38,32,28,24,22,20,18,17,18,22,28,34,38,42,40,36]},
// Day 2 — Sunday (asado peak evening)
{label:'Domingo', values: [30,32,36,40,44,48,52,50,44,36,30,26,24,22,22,24,28,36,46,56,62,58,50,42]},
// Day 3 — Monday
{label:'Lunes', values: [34,30,26,24,22,20,22,28,32,30,26,22,20,18,16,16,18,22,26,30,32,30,28,26]},
];
const PEOPLE = [
{
id:'sofia', emoji:'🏃♀️', name:'Sofía, 12 años',
plan:'Quiere correr en el parque mañana sábado a las 7 a.m.',
when:{day:0, hour:7},
options:[
{id:'go', label:'Sí, que corra'},
{id:'late', label:'Esperar a la tarde'},
{id:'no', label:'Mejor que descanse'},
],
best:'late',
feedback:{
go:{ok:false, text:'A las 7 a.m. el aire está en su peor momento (~48 μg/m³). Sofía termina con tos y dolor de cabeza.'},
late:{ok:true, text:'¡Perfecto! Hacia las 3 p.m. el viento baja el PM2.5 a ~17 μg/m³ — aire aceptable para correr.'},
no:{ok:false, text:'No hace falta cancelar. El pronóstico muestra una ventana limpia por la tarde — solo había que esperar.'},
},
},
{
id:'carlos', emoji:'🔥', name:'Don Carlos, 58 años',
plan:'Quiere hacer un asado con leña el domingo a las 7 p.m.',
when:{day:1, hour:19},
options:[
{id:'go', label:'Encender la leña'},
{id:'gas', label:'Cambiar a parrilla a gas'},
{id:'move', label:'Aplazar al lunes'},
],
best:'move',
feedback:{
go:{ok:false, text:'Domingo a las 7 p.m. el valle está en ~56 μg/m³ y atrapando humo bajo inversión térmica. Sumar leña empeora todo el barrio.'},
gas:{ok:false, text:'Mejor, pero el aire afuera ya está dañino — los invitados respirarán mal igual.'},
move:{ok:true, text:'Lunes 7 p.m. el pronóstico baja a ~30 μg/m³. Asado feliz, vecinos felices.'},
},
},
{
id:'rosa', emoji:'🚶♀️', name:'Doña Rosa, 71 años, asmática',
plan:'Acostumbra caminar 30 minutos cada mañana. ¿Y el lunes?',
when:{day:2, hour:8},
options:[
{id:'go', label:'Caminar como siempre'},
{id:'mask', label:'Caminar con tapabocas'},
{id:'in', label:'Caminar adentro'},
],
best:'go',
feedback:{
go:{ok:true, text:'Lunes 8 a.m. el aire estará en ~28 μg/m³ — aceptable para caminar. ¡Que disfrute!'},
mask:{ok:false, text:'No es necesario. El pronóstico muestra aire aceptable — el tapabocas es para los días dañinos.'},
in:{ok:false, text:'Encerrarla cuando no hace falta le quita salud. El pronóstico es para usarlo, no para tenerle miedo.'},
},
},
];
function RoundDecide({onNext, store, setStore}){
const [choices, setChoices] = React.useState(store.choices || {});
const allDone = PEOPLE.every(p => choices[p.id]);
const pickFor = (pid, oid) => {
const next = {...choices, [pid]:oid};
setChoices(next);
setStore({...store, choices: next});
};
return (
{PEOPLE.map(p => (
pickFor(p.id, o)} />
))}
{allDone ? 'Ver mi resultado →' : 'Decide por las 3 personas para continuar'}
);
}
function ForecastChart({forecast, highlights, choices}){
// 72 bars total
const max = 70;
return (
);
}
// ───────────────────────────────────────────────────────────────
// ROUND 4 — WHY IT MATTERS (recap + impact)
// ───────────────────────────────────────────────────────────────
function RoundOutro({onRestart, store}){
const correct = PEOPLE.filter(p => store.choices?.[p.id] === p.best).length;
const verdict = correct === 3
? {title:'¡Pronosticador del SIATA!', sub:'Las 3 decisiones acertadas. Sabes leer el aire del valle.'}
: correct === 2
? {title:'Vas por buen camino', sub:'2 de 3. Mira la gráfica de nuevo — el aire cambia mucho según la hora.'}
: correct === 1
? {title:'Aprendizaje en curso', sub:'1 de 3. El pronóstico solo sirve si lo lees con calma — la peor hora no siempre es la noche.'}
: {title:'Vuelve a intentarlo', sub:'Las decisiones rápidas no son aliadas del aire. Reinicia y observa las horas pico.'};
return (
Lo que aprendiste · resumen
🫁
Por eso pronosticamos
El pronóstico no es un juego. Le dice a colegios cuándo cancelar deporte al aire libre, a hospitales cuándo preparar más broncodilatadores, a alcaldías cuándo declarar pico-y-placa ambiental. Tu cuerpo lo agradece sin enterarse.
Conviértete en pronosticador del SIATA. 5 niveles para entender, paso a paso, cómo predecir el aire del valle — desde las fuentes que usamos hasta las decisiones que cambian vidas.
{[
{n:'01', t:'Las fuentes', d:'Conoce a CAMS, WRF y los sensores', c:'#A78BFA'},
{n:'02', t:'Significados', d:'Qué ve y qué no ve cada uno', c:'#0DCCC0'},
{n:'03', t:'Matemáticas', d:'Las 3 ecuaciones que mueven todo', c:'#FFB300'},
{n:'04', t:'Comprensión', d:'Test de 5 preguntas', c:'#FF8A3D'},
{n:'05', t:'A pronosticar', d:'4 rondas de decisiones reales', c:'#84BD45'},
].map((s, i) => (
Nivel {s.n}
{s.t}
{s.d}
))}
Empezar Nivel 1 →
∼ 10 minutos · niños desde 10 años · curiosos de toda edad