03_Modelo

📦 DESARROLLO DEL MODELO (IotModel.java)

📋 PASO 1: MODELO DE DATOS

🎯 Responsabilidad Única del Modelo

  • Almacenar:
    • Información fija del sensor (nombre, modelo, ubicación).
    • Valores actuales de temperatura, humedad y presión.
    • Valores máximos permitidos para cada magnitud.
  • Mantener históricos de valores para cada magnitud.
  • Simular nuevas lecturas del sensor:
    • Generar valores aleatorios realistas.
    • Actualizar valores actuales.
    • Guardar el histórico.
  • Proporcionar los datos necesarios para el gráfico.
  • Detectar cuándo un valor supera su umbral máximo.

🧱 ESTRUCTURA COMPLETA DE LA CLASE

1.1. Constantes del Sensor

    // =========================
    // CONSTANTES DEL SENSOR
    // =========================
    private final String nombre = "Sonda Ambiental";
    private final String modelo = "DHT-Pro + BMP";
    private final String ubicacion = "Aula 2.1 (ventana)";

Propósito: Información descriptiva del sensor físico simulado.

Diseño: Variables final porque son características fijas del dispositivo.


1.2. Umbrales de Alerta

    // =========================
    // UMBRALES (máximos)
    // =========================
    private static final double TEMP_MAX = 35.0;     // °C
    private static final double HUM_MAX  = 80.0;     // %
    private static final double PRES_MAX = 1035.0;   // hPa

Propósito: Valores máximos permitidos. Si se superan → generar alerta.

Criterios de diseño:

  • static final: Constantes compartidas por todas las instancias
  • Valores realistas basados en condiciones ambientales típicas
  • Fácilmente modificables para diferentes contextos

1.3. Estado Actual del Sensor

    // =========================
    // VALORES ACTUALES DEL SENSOR
    // =========================
    private double tempActual = 22.0;
    private double humActual  = 45.0;
    private double presActual = 1015.0;

Propósito: Almacenar la última lectura generada.

Valores iniciales: Condiciones ambientales normales típicas de interior:

  • 22°C: Temperatura confortable
  • 45%: Humedad moderada
  • 1015 hPa: Presión atmosférica media a nivel del mar

1.4. Control de Tiempo

Variables explicadas:

Variable Tipo Propósito
tiempoSimulacion double Tiempo transcurrido desde inicio (eje X gráfico)
ultimaMedida LocalDateTime Timestamp real de última lectura
formatoFecha DateTimeFormatter Formato "02/02/2026 15:30:45"

1.5. Configuración de Visualización

Propósito:

Constante Valor Función
MAX_PUNTOS_POR_SERIE 200 Solo se almacenarán 200 valores en cada serie
VENTANA_TIEMPO 30.0s Segundos visibles en gráfico simultáneamente (eje X)

1.6. Constructor

    /**
     * Constructor: inicializa las series con sus nombres.
     * Los nombres aparecerán en la leyenda del gráfico.
     */

🔧 MÉTODOS PRINCIPALES - DIAGRAMAS DE FLUJO

2.1. Método generarLectura()

Propósito: Simular una nueva lectura del sensor con variaciones realistas.

Diagrama de Flujo Detallado:

        ┌──────────────────────────────┐
        │ generarLectura()             │
        └──────────┬───────────────────┘
                   │
                   ↓
    ╔══════════════════════════════════════════╗
    ║ FASE 1: VARIACIONES NORMALES             ║
    ╚══════════════╦═══════════════════════════╝
                   │
                   ↓
        ┌──────────────────────────────────────┐
        │ tempActual = limitarRango(           │
        │   tempActual + rnd(-0.8, 0.8),       │
        │   10, 40)                             │
        │                                       │
        │ Explicación:                          │
        │ - Suma variación: -0.8°C a +0.8°C    │
        │ - Limita resultado entre 10°C y 40°C │
        └──────────┬───────────────────────────┘
                   │
                   ↓
        ┌──────────────────────────────────────┐
        │ humActual = limitarRango(            │
        │   humActual + rnd(-1.6, 1.6),        │
        │   20, 90)                             │
        │                                       │
        │ Explicación:                          │
        │ - Suma variación: -1.6% a +1.6%      │
        │ - Limita resultado entre 20% y 90%   │
        └──────────┬───────────────────────────┘
                   │
                   ↓
        ┌──────────────────────────────────────┐
        │ presActual = limitarRango(           │
        │   presActual + rnd(-1.1, 1.1),       │
        │   980, 1040)                          │
        │                                       │
        │ Explicación:                          │
        │ - Suma variación: -1.1 a +1.1 hPa    │
        │ - Limita entre 980 y 1040 hPa        │
        └──────────┬───────────────────────────┘
                   │
                   ↓
    ╔══════════════════════════════════════════╗
    ║ FASE 2: PICOS ALEATORIOS (eventos)      ║
    ╚══════════════╦═══════════════════════════╝
                   │
                   ↓
        ┌──────────────────────────────────────┐
        │ ¿Random < 0.05? (5% probabilidad)    │
        └──────────┬───────────────────────────┘
              SÍ ←─┤      │ NO
              │           ↓
              ↓     ┌──────────────┐
     ┌────────────────────┐       │
     │ tempActual +=      │       │
     │ rnd(4, 8)          │       │
     │                    │       │
     │ (Pico: +4°C a +8°C)│       │
     │ Simula: ventana    │       │
     │ abierta, sol       │       │
     └────────┬───────────┘       │
              │                    │
              └────→ ┌ ← ← ← ← ← ←┘
                     │
                     ↓
        ┌──────────────────────────────────┐
        │ ¿Random < 0.05? (5% prob.)       │
        └──────────┬─────────────────────────┘
              SÍ ←─┤      │ NO
              │           ↓
              ↓     ┌──────────────┐
     ┌────────────────────┐       │
     │ humActual +=       │       │
     │ rnd(8, 15)         │       │
     │                    │       │
     │ (Pico: +8% a +15%) │       │
     │ Simula: lluvia,    │       │
     │ vapor de agua      │       │
     └────────┬───────────┘       │
              │                    │
              └────→ ┌ ← ← ← ← ← ←┘
                     │
                     ↓
        ┌──────────────────────────────────┐
        │ ¿Random < 0.05? (4% prob. aprox) │
        └──────────┬─────────────────────────┘
              SÍ ←─┤      │ NO
              │           ↓
              ↓     ┌──────────────┐
     ┌────────────────────────┐   │
     │ presActual +=          │   │
     │ rnd(8, 18)             │   │
     │                        │   │
     │ (Pico: +8 a +18 hPa)   │   │
     │ Simula: cambio         │   │
     │ meteorológico          │   │
     └────────┬───────────────┘   │
              │                    │
              └────→ ┌ ← ← ← ← ← ←┘
                     │
                     ↓
    ╔══════════════════════════════════════════╗
    ║ FASE 3: ACTUALIZAR TIMESTAMP             ║
    ╚══════════════╦═══════════════════════════╝
                   │
                   ↓
        ┌──────────────────────────────────────┐
        │ ultimaMedida = LocalDateTime.now()   │
        │                                       │
        │ Captura fecha/hora actual del sistema│
        └──────────────────────────────────────┘
                   │
                   ↓
                 (FIN)

Código Completo:

    /**
     * Genera una nueva lectura del sensor con variaciones aleatorias.
     * 
     * Algoritmo:
     * 1. Aplica pequeñas variaciones a cada magnitud
     * 2. Limita los valores entre mínimos y máximos realistas
     * 3. Aleatoriamente genera "picos" para simular eventos
     * 4. Actualiza la marca de tiempo
     */
    public void generarLectura() {
        // FASE 1: Variaciones normales (pequeños cambios graduales)
        tempActual = limitarRango(tempActual + rnd(-0.8, 0.8), 10, 40);
        humActual  = limitarRango(humActual  + rnd(-1.6, 1.6), 20, 90);
        presActual = limitarRango(presActual + rnd(-1.1, 1.1), 980, 1040);

        // FASE 2: Picos aleatorios (simulan eventos repentinos)
        if (ThreadLocalRandom.current().nextDouble() < 0.05) {
            tempActual += rnd(4, 8);  // Pico de temperatura
        }
        if (ThreadLocalRandom.current().nextDouble() < 0.05) {
            humActual += rnd(8, 15);  // Pico de humedad
        }
        if (ThreadLocalRandom.current().nextDouble() < 0.05) {
            presActual += rnd(8, 18); // Pico de presión
        }

        // FASE 3: Actualizar timestamp
        ultimaMedida = LocalDateTime.now();
    }

Métodos Auxiliares:

    /**
     * Genera un número aleatorio en el rango [min, max).
     * Usa ThreadLocalRandom para mejor rendimiento en entornos concurrentes.
     * 
     * @param min Valor mínimo (inclusivo)
     * @param max Valor máximo (exclusivo)
     * @return Número aleatorio en el rango
     */
    private double rnd(double min, double max) {
        return ThreadLocalRandom.current().nextDouble(min, max);
    }

    /**
     * Limita un valor entre un mínimo y un máximo (clamp).
     * 
     * @param v Valor a limitar
     * @param min Límite inferior
     * @param max Límite superior
     * @return Valor limitado
     */
    private double limitarRango(double v, double min, double max) {
        return Math.max(min, Math.min(max, v));
    }

Explicación del algoritmo clamp:

Si v < min  → retorna min
Si v > max  → retorna max
Si min ≤ v ≤ max → retorna v

2.2. Método agregarPuntosASeries()

Propósito: Añadir los valores actuales a las series del gráfico, si los checkbox de la serie están seleccionados.

Diagrama de Flujo:

        ┌────────────────────────────────────┐
        │ agregarPuntosASeries(              │
        │   tempHabilitada,                  │
        │   humHabilitada,                   │
        │   presHabilitada,                  │
        │   incrementoTiempo)                │
        └─────────┬──────────────────────────┘
                  │
                  ↓
        ┌─────────────────────────────────────┐
        │ actualizamos el tiempo de la        |
        | simulación con el nuevo incremento  |
        | de timepo                           |
        │ (Avanzar tiempo del eje X)          │
        └─────────┬───────────────────────────┘
                  │
                  ↓
        ┌─────────────────────────────────────┐
        │ si el checkbox de la serie          |
        | correspondiente está marcado        │
        └───────┬─────────────────────────────┘
             SÍ ┤        │ NO
                │        |
                ↓        └───────┐
    ┌──────────────────────────┐ │
    │ Añade punto a la serie:  │ │
    │ (tiempo, temperatura)    │ │
    └──────────┬───────────────┘ │
               │                 │
               ↓                 │
    ┌────────────────────────┐   │
    │ Elimina puntos         │   │
    │ antiguos si tamaño>200 |   │ 
    └──────────┬─────────────┘   │
               │                 │
               └────→ ┌ ← ← ← ←  ┘
                      │
                      ↓
        ┌─────────────────────────────────┐
        │ repetir para todos lo sensores  │
        └────┬────────────────────────────┘
           SÍ┤           │ NO
             │           ↓
      [Mismo proceso]  [Continuar]
             |           │
             │           │
             └────→ ┌ ← ←┘
                    │
                    ↓
                  (FIN)

2.3. Método recortarSerie()

Propósito: Evitar crecimiento infinito de memoria eliminando puntos antiguos.

Diagrama de Flujo:

        ┌───────────────────────────────┐
        │ recortarSerie(serie)          │
        └──────────┬────────────────────┘
                   │
                   ↓
        ┌────────────────────────────────────────┐
        │ tamaño de serie > MAX_PUNTOS_POR_SERIE |
        └──────────┬─────────────────────────────┘
              SÍ ←─┤      │ NO
              │           ↓
              ↓     ┌──────────┐
   ┌────────────────────────────┐  │ (FIN)
   │ Si size=250 puntos:        │  │
   │   eliminar = 250 - 200 = 50│  │
   │   Elimina primeros 50    │  │
   └────────┬───────────────────┘  │
            │                      │
            └─────→ ┌ ← ← ← ← ← ← ┘
                    │
                    ↓
                  (FIN)

calcularLimiteInferiorX() y calcularLimiteSuperiorX()

Propósito: Crear efecto de "ventana deslizante" en el gráfico.

Diagrama de Flujo Combinado:

╔══════════════════════════════════════════════════════════╗
║            VENTANA DESLIZANTE - LÓGICA                   ║
╚══════════════════════════════════════════════════════════╝

FASE INICIAL (t ≤ 30s):
┌────────────────────────────────────────┐
│ Gráfico muestra: [0s ──────── 30s]     │
│ Ventana FIJA al principio              │
└────────────────────────────────────────┘

TRANSICIÓN (t = 35s):
┌────────────────────────────────────────┐
│ calcularLimiteInferiorX()              │
│ ┌────────────────────────────────────┐ │
│ │ ¿tiempoSimulacion > VENTANA_TIEMPO?│ │
│ │ ¿35 > 30?                          │ │
│ └────────┬───────────────────────────┘ │
│          │ SÍ                          │
│          ↓                             │
│ ┌────────────────────────────────────┐ │
│ │ return tiempoSimulacion -          │ │
│ │        VENTANA_TIEMPO              │ │
│ │ return 35 - 30 = 5s                │ │
│ └────────────────────────────────────┘ │
└────────────────────────────────────────┘

RESULTADO:
┌────────────────────────────────────────┐
│ Gráfico muestra: [5s ──────── 35s]     │
│ Ventana DESLIZANTE (se mueve)          │
└────────────────────────────────────────┘

FASE AVANZADA (t = 60s):
┌────────────────────────────────────────┐
│ LimiteInferior: 60 - 30 = 30s          │
│ LimiteSuperior: 60s                    │
│ Gráfico muestra: [30s ─────── 60s]     │
└────────────────────────────────────────┘

2.4. Validación de Umbrales

Métodos hayAlertaXXX()

Propósito: Detectar si alguna magnitud supera su umbral máximo.

Métodos getMensajeAlertaXXX()

Propósito: Generar mensajes descriptivos para mostrar en la UI con los sigueitne formatos:

  • "Temp = XX.X °C (máx YY.Y)".
  • "Hum = XX.X % (máx YY.Y)"
  • "Pres = XXXX.X hPa (máx YYYY.Y)"

Ejemplos de salida:

"Temp = 36.2 °C (máx 35.0)"
"Hum = 85.7 % (máx 80.0)"
"Pres = 1038.4 hPa (máx 1035.0)"

2.5. Formateo de Valores

Métodos getXXXFormateada()

Propósito: Convertir valores numéricos a String con formato específico para UI.

  • Números con 1 decimal
  • Fecha "dd/MM/yyyy HH:mm:ss"

2.6. Método resetearDatos()

Propósito: Reiniciar toda la simulación a su estado inicial.

Diagrama de Flujo:

┌────────────────────────┐
│ resetearDatos()        │
└──────────┬─────────────┘
           │
           ↓
┌──────────────────────────────┐
│ (Eliminar TODOS los puntos   │
│  de la serie temperatura)    │
└──────────┬───────────────────┘
           │
           ↓
┌──────────────────────────────┐
│ (Eliminar TODOS los puntos   │
│  de la serie humedad)        │
└──────────┬───────────────────┘
           │
           ↓
┌──────────────────────────────┐
│ (Eliminar TODOS los puntos   │
│  de la serie presión)        │
└──────────┬───────────────────┘
           │
           ↓
┌──────────────────────────────┐
│ (Resetear contador tiempo)   │
└──────────┬───────────────────┘
           │
           ↓
┌──────────────────────────────┐
│ (Borrar timestamp)           │
└──────────────────────────────┘
           │
           ↓
         (FIN)

2.7. Getters Básicos

Propósito: Proporcionar acceso de solo lectura a los atributos privados.