TEMPO

Estándares de Calidad del Aire y AQI

El Índice de Calidad del Aire (AQI - Air Quality Index) es una herramienta estandarizada que convierte concentraciones de contaminantes en un número fácil de entender.

¿Qué es el AQI?

El AQI es un índice que va de 0 a 500. Cuanto más alto el valor, mayor el nivel de contaminación y mayor el riesgo para la salud.

Escala AQI (EPA - EE.UU.)

Tabla Completa de Categorías

AQICategoríaColorSignificadoPoblación afectada
0-50Bueno🟢 VerdeAire de calidad satisfactoriaNinguna
51-100Moderado🟡 AmarilloCalidad aceptablePocos inusualmente sensibles
101-150Insalubre para grupos sensibles🟠 NaranjaEfectos en grupos sensiblesNiños, ancianos, asmáticos
151-200Insalubre🔴 RojoEfectos en población generalTodos pueden experimentar efectos
201-300Muy insalubre🟣 PúrpuraAlerta de saludEfectos serios más probables
301-500Peligroso🟤 MarrónEmergencia de saludToda la población afectada

Cálculo del AQI

El AQI se calcula usando una fórmula de interpolación lineal entre puntos de quiebre (breakpoints).

Fórmula General

AQI=IhiIloChiClo×(CClo)+IloAQI = \frac{I_{hi} - I_{lo}}{C_{hi} - C_{lo}} \times (C - C_{lo}) + I_{lo}

Donde:

  • C = concentración del contaminante medida
  • C_lo = breakpoint de concentración ≤ C (límite inferior)
  • C_hi = breakpoint de concentración ≥ C (límite superior)
  • I_lo = valor AQI correspondiente a C_lo
  • I_hi = valor AQI correspondiente a C_hi

Implementación para Desarrolladores

La EPA actualizó los breakpoints de PM2.5 en Mayo 2024, reduciendo el límite "Bueno" de 12.0 a 9.0 μg/m³. El código usa estos valores actualizados.

Checkpoint: ¿Necesitas este código?

Si tu aplicación solo muestra datos de AQI de APIs existentes (como AirNow), puedes saltarte esta sección.

Si planeas calcular AQI tú mismo desde concentraciones (ej. de TEMPO o sensores), este código es esencial.

1interface AQIBreakpoint {
2  cLo: number;
3  cHi: number;
4  iLo: number;
5  iHi: number;
6  category: string;
7  color: string;
8}
9
10class AQICalculator {
11  // Breakpoints para PM2.5 (μg/m³, promedio 24h)
12  // ACTUALIZADOS según EPA Mayo 2024
13  private static PM25_BREAKPOINTS: AQIBreakpoint[] = [
14    { cLo: 0.0, cHi: 9.0, iLo: 0, iHi: 50, category: 'Bueno', color: '#00e400' },
15    { cLo: 9.1, cHi: 35.4, iLo: 51, iHi: 100, category: 'Moderado', color: '#ffff00' },
16    { cLo: 35.5, cHi: 55.4, iLo: 101, iHi: 150, category: 'Insalubre sensibles', color: '#ff7e00' },
17    { cLo: 55.5, cHi: 125.4, iLo: 151, iHi: 200, category: 'Insalubre', color: '#ff0000' },
18    { cLo: 125.5, cHi: 225.4, iLo: 201, iHi: 300, category: 'Muy insalubre', color: '#8f3f97' },
19    { cLo: 225.5, cHi: 325.4, iLo: 301, iHi: 400, category: 'Peligroso', color: '#7e0023' },
20    { cLo: 325.5, cHi: 500.4, iLo: 401, iHi: 500, category: 'Peligroso', color: '#7e0023' },
21  ];
22
23  // Breakpoints para O₃ (ppm, 8h promedio)
24  private static O3_BREAKPOINTS: AQIBreakpoint[] = [
25    { cLo: 0.000, cHi: 0.054, iLo: 0, iHi: 50, category: 'Bueno', color: '#00e400' },
26    { cLo: 0.055, cHi: 0.070, iLo: 51, iHi: 100, category: 'Moderado', color: '#ffff00' },
27    { cLo: 0.071, cHi: 0.085, iLo: 101, iHi: 150, category: 'Insalubre sensibles', color: '#ff7e00' },
28    { cLo: 0.086, cHi: 0.105, iLo: 151, iHi: 200, category: 'Insalubre', color: '#ff0000' },
29    { cLo: 0.106, cHi: 0.200, iLo: 201, iHi: 300, category: 'Muy insalubre', color: '#8f3f97' },
30  ];
31
32  // Breakpoints para NO₂ (ppb, 1h promedio)
33  private static NO2_BREAKPOINTS: AQIBreakpoint[] = [
34    { cLo: 0, cHi: 53, iLo: 0, iHi: 50, category: 'Bueno', color: '#00e400' },
35    { cLo: 54, cHi: 100, iLo: 51, iHi: 100, category: 'Moderado', color: '#ffff00' },
36    { cLo: 101, cHi: 360, iLo: 101, iHi: 150, category: 'Insalubre sensibles', color: '#ff7e00' },
37    { cLo: 361, cHi: 649, iLo: 151, iHi: 200, category: 'Insalubre', color: '#ff0000' },
38    { cLo: 650, cHi: 1249, iLo: 201, iHi: 300, category: 'Muy insalubre', color: '#8f3f97' },
39    { cLo: 1250, cHi: 2049, iLo: 301, iHi: 500, category: 'Peligroso', color: '#7e0023' },
40  ];
41
42  static calculate(concentration: number, breakpoints: AQIBreakpoint[]): {
43    aqi: number;
44    category: string;
45    color: string;
46  } | null {
47    // Encuentra el rango de breakpoint apropiado
48    for (const bp of breakpoints) {
49      if (concentration >= bp.cLo && concentration <= bp.cHi) {
50        // Aplica fórmula de interpolación lineal
51        const aqi = Math.round(
52          ((bp.iHi - bp.iLo) / (bp.cHi - bp.cLo)) *
53          (concentration - bp.cLo) +
54          bp.iLo
55        );
56
57        return {
58          aqi,
59          category: bp.category,
60          color: bp.color
61        };
62      }
63    }
64
65    // Si está fuera de rango
66    if (concentration > breakpoints[breakpoints.length - 1].cHi) {
67      return {
68        aqi: 500,
69        category: 'Más allá de escala AQI',
70        color: '#7e0023'
71      };
72    }
73
74    return null;
75  }
76
77  static calculatePM25(concentration: number) {
78    return this.calculate(concentration, this.PM25_BREAKPOINTS);
79  }
80
81  static calculateO3(concentration: number) {
82    return this.calculate(concentration, this.O3_BREAKPOINTS);
83  }
84
85  static calculateNO2(concentration: number) {
86    return this.calculate(concentration, this.NO2_BREAKPOINTS);
87  }
88}
89
90// Ejemplos
91const pm25_result = AQICalculator.calculatePM25(45);  // μg/m³
92const o3_result = AQICalculator.calculateO3(0.075);   // ppm
93const no2_result = AQICalculator.calculateNO2(120);   // ppb
94
95console.log(`PM2.5 (45 μg/m³): AQI = ${pm25_result?.aqi} (${pm25_result?.category})`);
96console.log(`O₃ (0.075 ppm): AQI = ${o3_result?.aqi} (${o3_result?.category})`);
97console.log(`NO₂ (120 ppb): AQI = ${no2_result?.aqi} (${no2_result?.category})`);
98

AQI Compuesto

Cuando hay múltiples contaminantes, el AQI reportado es el máximo de todos.

AQI Compuesto en la Práctica

Cuando tienes mediciones de múltiples contaminantes, el AQI que reportas es el valor más alto. Esto asegura que se comunique el riesgo más significativo.

1def composite_aqi(pollutant_aqis: dict) -> dict:
2    """
3    Calcula el AQI compuesto tomando el máximo
4    """
5    if not pollutant_aqis:
6        return {'aqi': 0, 'dominant_pollutant': None}
7
8    # Encuentra el máximo
9    dominant = max(pollutant_aqis.items(), key=lambda x: x[1])
10
11    return {
12        'aqi': dominant[1],
13        'dominant_pollutant': dominant[0],
14        'all_pollutants': pollutant_aqis
15    }
16
17# Ejemplo: múltiples contaminantes
18measurements = {
19    'PM2.5': 120,   # AQI = 120 (Insalubre para sensibles)
20    'O₃': 145,      # AQI = 145 (Insalubre para sensibles)
21    'NO₂': 85,      # AQI = 85 (Moderado)
22    'PM10': 95,     # AQI = 95 (Moderado)
23}
24
25result = composite_aqi(measurements)
26print(f"AQI compuesto: {result['aqi']}")
27print(f"Contaminante dominante: {result['dominant_pollutant']}")
28# Salida: AQI = 145, dominante = O₃
29

Estándares Internacionales

Diferentes países usan diferentes estándares.

Comparación de Límites PM2.5

Organización/PaísLímite Anual (μg/m³)Límite 24h (μg/m³)
OMS (2021)515
EPA (EE.UU.)1235
Unión Europea25-
China3575
India4060
Disparidad de Estándares

Los límites de la OMS (actualizados en 2021) son mucho más estrictos porque están basados en la evidencia científica más reciente sobre efectos en salud. Muchos países aún no han actualizado sus estándares nacionales para cumplir con las nuevas guías de la OMS.

Fuente: WHO Air Quality Guidelines 2021

Recomendaciones por Nivel AQI

Guía Detallada de Acciones por Nivel AQI

🟢 AQI 0-50 (Bueno)

Público general: ✅ Es un gran día para estar activo al aire libre Grupos sensibles: ✅ Sin precauciones necesarias

🟡 AQI 51-100 (Moderado)

Público general: ✅ Actividades al aire libre aceptables Grupos sensibles:

  • ⚠️ Considera reducir ejercicio intenso prolongado
  • ⚠️ Observa síntomas como tos o falta de aire

🟠 AQI 101-150 (Insalubre para grupos sensibles)

Público general:

  • ⚠️ Reduce ejercicio intenso prolongado
  • ⚠️ Toma descansos frecuentes

Grupos sensibles:

  • 🚫 Evita ejercicio prolongado o intenso
  • 🏠 Considera actividades en interiores
  • 😷 Considera usar mascarilla al aire libre

🔴 AQI 151-200 (Insalubre)

Público general:

  • 🚫 Evita ejercicio prolongado o intenso
  • ⚠️ Toma descansos frecuentes si estás afuera
  • 😷 Considera usar mascarilla

Grupos sensibles:

  • 🚫 Evita TODA actividad física exterior
  • 🏠 Permanece en interiores
  • 😷 Usa mascarilla si debes salir

🟣 AQI 201-300 (Muy insalubre)

Público general:

  • 🚫 Evita TODA actividad física exterior
  • 🏠 Permanece en interiores con ventanas cerradas
  • 😷 Usa mascarilla N95 si debes salir

Grupos sensibles:

  • 🏠 Permanece en interiores
  • 🪟 Cierra ventanas
  • 🌬️ Usa purificador de aire si es posible

🟤 AQI 301-500 (Peligroso)

Público general:

  • 🚨 EMERGENCIA DE SALUD
  • 🏠 Permanece en interiores
  • 😷 Usa mascarilla N95 o P100 si debes salir
  • 🌬️ Usa purificador de aire

Grupos sensibles:

  • 🚨 Condición de emergencia
  • 🏥 Considera evacuación si es posible
  • 📞 Ten medicamentos de emergencia listos

NowCast: AQI en Tiempo Real

El NowCast es el algoritmo oficial de la EPA para calcular AQI "actual" (versus AQI de 24h). Da más peso a las horas recientes, ideal para eventos agudos como incendios.

¿Lo necesitas?: Probablemente no. La mayoría de APIs (AirNow, OpenAQ) ya proporcionan NowCast calculado. Solo implementa esto si trabajas con datos crudos de sensores.

1def nowcast_pm25(hourly_values):
2    """
3    Calcula NowCast para PM2.5
4    hourly_values: lista de últimas 12 horas de concentración
5    """
6    if len(hourly_values) < 3:
7        return None
8
9    # Usar últimas 12 horas máximo
10    values = hourly_values[-12:]
11
12    # Calcular factor de peso
13    min_val = min(values)
14    max_val = max(values)
15
16    if max_val == 0:
17        return 0
18
19    weight_factor = min_val / max_val
20    weight_factor = max(0.5, min(1.0, weight_factor))
21
22    # Calcular NowCast
23    weighted_sum = 0
24    weight_sum = 0
25
26    for i, value in enumerate(reversed(values)):
27        weight = weight_factor ** i
28        weighted_sum += value * weight
29        weight_sum += weight
30
31    nowcast = weighted_sum / weight_sum if weight_sum > 0 else 0
32
33    return round(nowcast, 1)
34
35# Ejemplo: concentraciones subiendo (incendio cerca)
36hourly_pm25 = [15, 18, 22, 28, 35, 45, 65, 85, 95, 110, 125, 140]
37nc = nowcast_pm25(hourly_pm25)
38print(f"NowCast PM2.5: {nc} μg/m³")  # Refleja aumento reciente
39

Recursos y Monitoreo en Tiempo Real

Monitorea la Calidad del Aire

Apps y sitios oficiales recomendados:

  • 🌍 AirNow.gov - Oficial EPA (EE.UU., México, Canadá)
  • 🌍 IQAir - Cobertura global con pronósticos
  • 🌍 PurpleAir - Red comunitaria en tiempo real
  • 🌍 OpenAQ - Datos abiertos globales con API

Apps móviles:

  • EPA AIRNow (iOS/Android) - Con notificaciones push de alertas

Conexión con el Challenge de NASA

El AQI es el lenguaje común para comunicar calidad del aire en tu aplicación:

Implementación Recomendada

  1. Usa APIs existentes cuando sea posible:

    • AirNow API (EE.UU., México, Canadá) - ya proporciona AQI calculado
    • OpenAQ API - datos globales con AQI
  2. Calcula AQI tú mismo solo si:

    • Trabajas con datos crudos de TEMPO (columna vertical → superficie)
    • Integras sensores IoT propios
    • Necesitas umbrales personalizados
  3. Diseño de UI efectivo:

    • Colores consistentes: Usa los colores EPA estándar (verde, amarillo, naranja, rojo, púrpura)
    • Número + categoría: "145 - Insalubre para grupos sensibles"
    • Recomendaciones contextuales: Basadas en perfil de usuario
    • Gráficos de tendencia: NowCast muestra cambios hora a hora

Ejemplo de Integración

1// Obtener AQI de API (recomendado)
2const aqiData = await fetch('https://api.airnow.gov/...').then(r => r.json());
3
4// O calcular desde concentración de TEMPO
5const no2_concentration = 85; // ppb desde TEMPO
6const aqi = AQICalculator.calculateNO2(no2_concentration);
7
8// Mostrar con contexto
9if (aqi.aqi > 100) {
10  notifyUser(`AQI ${aqi.aqi} (${aqi.category}) - Evita ejercicio intenso si eres sensible`);
11}
12

Resumen del Módulo 1

Has completado los fundamentos de calidad del aire:

Lección 1: Contaminantes principales y AQI básico ✅ Lección 2: NO₂, O₃, HCHO, PM2.5 en detalle ✅ Lección 3: Fuentes de contaminación (tráfico, industria, incendios) ✅ Lección 4: Efectos en salud y grupos vulnerables ✅ Lección 5: Cálculo e interpretación de AQI

Ahora puedes: Entender datos de calidad del aire, interpretar valores de contaminantes, calcular AQI, y comunicar riesgos de forma efectiva.

Resumen del Módulo 1 - Fundamentos

Has completado los fundamentos de calidad del aire:

Lo que aprendiste:

Lección 1 - Introducción y Contaminantes:

  • Qué es calidad del aire y por qué importa
  • Los 4 contaminantes principales: NO₂, O₃, HCHO, PM2.5
  • Qué mide TEMPO (gases) y qué no (PM2.5)
  • Fuentes básicas: antropogénicas vs naturales

Lección 2 - Fuentes y Efectos en Salud:

  • Cómo se producen los contaminantes (combustión, fotoquímica, incendios)
  • Efectos específicos en salud de cada contaminante
  • Grupos vulnerables y factores de riesgo
  • Casos de éxito en reducción de contaminación

Lección 3 - AQI y Estándares:

  • Cómo funciona el AQI (0-500)
  • Cálculo de AQI desde concentraciones
  • Recomendaciones por nivel
  • NowCast para tiempo real

Habilidades adquiridas:

Ahora puedes:

  • Interpretar datos de calidad del aire correctamente
  • Calcular AQI desde concentraciones de contaminantes
  • Diseñar alertas basadas en salud y grupos vulnerables
  • Comunicar riesgos de forma clara y accionable
  • Entender limitaciones de diferentes fuentes de datos

Para tu aplicación del Challenge:

Stack recomendado:

  • 🛰️ TEMPO: NO₂, O₃, HCHO (gases desde el espacio)
  • 🌐 OpenAQ: PM2.5 (estaciones terrestres)
  • 🌤️ Weather API: Temperatura, viento, humedad
  • 🔥 FIRMS: Detección de incendios

Arquitectura básica:

1// Tu app combinará múltiples fuentes
2const airQuality = {
3  gases: await fetchTEMPO(lat, lon),      // NO2, O3, HCHO
4  particles: await fetchOpenAQ(lat, lon), // PM2.5
5  weather: await fetchWeather(lat, lon),  // Contexto
6  fires: await fetchFIRMS(lat, lon)       // Eventos
7};
8
9// Calcular AQI compuesto
10const aqi = calculateCompositeAQI(airQuality);
11
12// Generar alertas personalizadas
13const alert = generateAlert(aqi, userProfile);
14

Próximos Pasos

En el Módulo 2, profundizaremos en NASA TEMPO: cómo funciona, qué productos están disponibles, cómo acceder a los datos, y cómo integrarlos en tu aplicación.