← Índice del curso

Internet de las Cosas · Tema 45

Gestión y monitoreo de flotas

Una placa responde en el banco. Cientos de unidades instaladas necesitan algo más: saber cuáles existen, dónde están, cuándo informaron, qué versión ejecutan y qué evidencia hay de su salud. Vamos a organizar esa información sin confundir silencio con falla ni una versión anunciada con una actualización aceptada.

01 · Mantener evidencia de muchas unidades

De una unidad a una flota

Una flota reúne dispositivos administrados bajo una operación común. Pueden estar repartidos por clientes, sitios, redes y versiones de hardware. Gestionarla requiere vincular cada identidad con su inventario y conservar información suficiente para decidir qué hacer con ella.

Los temas 42 a 44 aportaron alta, credenciales, actualización y aceptación de firmware. Ahora agregamos una vista de conjunto: qué equipos tienen evidencia reciente, cuáles están pendientes, qué fallos se repiten y cuándo es razonable continuar una campaña.

Preguntas de operación y evidencia necesaria.
PreguntaInformación necesaria
¿Cuántos equipos deberían existir?Inventario y ciclo de vida, incluso de los que nunca reportaron
¿Cuáles tuvieron contacto reciente?Última recepción válida y expectativa de comunicación
¿Cuáles funcionan correctamente?Diagnóstico y métricas con antigüedad conocida
¿Cuáles aceptaron el firmware objetivo?Build ejecutado y confirmación de aceptación
¿A quiénes se puede actualizar ahora?Compatibilidad, autorización, salud y condiciones de la campaña

La plataforma observa reportes; no ve directamente el estado físico completo. El panel debe conservar esa distinción. Un equipo silencioso puede estar sin red, apagado, durmiendo, rechazado por credenciales o funcionando localmente sin poder informar.

El laboratorio administra siete equipos ficticios en memoria. No se conecta a una nube ni envía órdenes. Sus umbrales y reglas son ejemplos didácticos que el producto debe adaptar.

02 · Lo que existe, lo que se pide y lo que se observa

Inventario y estados

El inventario conserva la identidad estable, modelo, revisión, pertenencia, sitio y ciclo de vida. No se crea únicamente a partir de la última telemetría: de lo contrario desaparecen de la vista las unidades que todavía no se conectaron.

Capas de información que conviene distinguir.
CapaEjemploAutoridad
Inventarioequipo03 pertenece a plantaA y usa placaAProceso autorizado de alta y gestión
Estado deseadoObjetivo de firmware build 2Política y operador autorizados
Estado reportadoEl equipo informa build 1 confirmadaAgente autenticado, con límites de confianza
Estado derivadoReporte vencido o candidato a un loteReglas del backend y momento de evaluación
HistorialReversiones, fallos y cambios de pertenenciaEventos y auditoría con retención definida

Deseado no significa aplicado

El operador puede pedir una configuración y el dispositivo reportar la que efectivamente aplicó. La diferencia es información útil: puede indicar una acción pendiente, un rechazo o una unidad sin contacto. Escribir el objetivo no cambia por sí solo el estado físico.

Como ejemplo de servicio, Device Shadow de AWS IoT distingue estados deseado y reportado y notifica diferencias. Aquí usamos un modelo propio, sin implementar ese servicio ni sus tópicos.

Propiedad y permisos

El sitio o cliente de un equipo se toma del inventario autorizado, no de un campo libre en un mensaje. La consulta de un panel, la exportación de registros y la ejecución de trabajos remotos también tienen permisos por organización y recurso.

Una unidad retirada conserva la trazabilidad que corresponda, pero no reaparece como activa al publicar un mensaje. El retiro del inventario debe acompañar la política de credenciales y permisos del tema 42.

03 · Cuándo deja de ser reciente una observación

Contacto reciente y evidencia vencida

Un heartbeat informa presencia o estado con una cadencia prevista. El backend registra la recepción con su reloj. Una regla simple compara la edad de la última recepción válida con un múltiplo del intervalo esperado:

edad = ahora del servidor − última recepción válida
contacto reciente si 0 ≤ edad ≤ factor × intervalo esperado

En el laboratorio cada equipo espera reportar cada 30 segundos. Con factor 3, el límite es 90 segundos inclusive. A los 91 se clasifica como vencido. Es un umbral de observación: no demuestra el instante de una desconexión ni una falla física.

Estados con significados explícitos.
EstadoSignificado
RecienteSe aceptó una observación dentro de la ventana prevista
VencidoLa observación anterior ya no satisface esa ventana
Sin reporteEl inventario no tiene todavía evidencia aceptada
RetiradoEl ciclo de vida lo excluye de la operación activa

Conectividad no equivale a salud

Un agente puede informar correctamente que falló un sensor. Está en contacto y degradado al mismo tiempo. También puede conservarse un último diagnóstico correcto de hace horas: se muestra como dato anterior, sin promoverlo a una garantía de salud actual.

MQTT dispone de mecanismos de presencia, como Keep Alive y Will, descritos en la especificación MQTT 5.0. Son evidencias del transporte bajo sus condiciones, no una prueba de funcionamiento del producto. Un mensaje retenido tampoco adquiere actualidad por entregarse a un suscriptor nuevo.

Los equipos que duermen necesitan otra expectativa de contacto. No se impone el mismo intervalo a una unidad a batería que despierta cada hora y a un controlador alimentado continuamente. El tema 46 desarrolla esa relación con el consumo.

Se usan relojes y fuentes de tiempo apropiados en el backend. El reloj del dispositivo puede servir para fechar mediciones bajo su política, pero no reemplaza sin validación el tiempo de recepción del servidor.

04 · No sobrescribir el presente con un reporte viejo

Reportes, sesiones y secuencias

El receptor vincula la conexión autenticada con el equipo antes de aceptar un reporte. Valida esquema, tipos, rangos y tamaño; luego actualiza el estado de forma coherente. Un JSON que declara id=equipo03 no demuestra que provenga de esa identidad.

{
  "id": "equipo03",
  "secuencia": 11,
  "build": 1,
  "salud": true,
  "confirmada": true,
  "boot_id": "arranque-ficticio-8",
  "uptime_s": 840
}

Es un snapshot ilustrativo, no un contrato universal. Un producto suele incluir versión de esquema, configuración aplicada, motivo de reinicio y evidencias de diagnóstico. La marca de recepción y el principal autenticado se agregan del lado servidor.

Orden dentro de una sesión

El modelo conserva una sesión admitida por equipo y el último número de secuencia aceptado. Rechaza secuencias iguales o menores y sesiones anteriores. La sesión representa contexto establecido por el backend después de autenticar: no es un número que el cliente pueda elegir para obtener más permisos.

Un contador puede reiniciarse al arrancar. Por eso no se comparan sin contexto el 8 de una sesión nueva y el 900 de una anterior. Un boot_id opaco ayuda a correlacionar, pero ordenarlo alfabéticamente no prueba cuál arranque es posterior.

En producción se define cómo se abre una sesión nueva, cómo se invalida la anterior y cómo se resuelven carreras entre receptores. El ejemplo mantiene sesión 1 fija y no implementa reconexión ni un registro distribuido. La comparación y actualización del cursor necesitan atomicidad en el backend real.

Recepción reciente y contenido antiguo

Un lote de mediciones guardadas durante horas puede llegar ahora sin describir el estado actual. Se separan historia, instante de medición y snapshot operativo. El laboratorio supone snapshots actuales del agente, no una cola histórica; aun así, «contacto reciente» solo indica evidencia recibida dentro de su política.

Un rechazo no refresca la presencia ni sobrescribe la versión. Esto impide que un duplicado o un mensaje de otra sesión mantenga artificialmente vigente una observación.

05 · Elegir señales que ayuden a decidir

Métricas, registros y alertas

Las métricas resumen magnitudes y tendencias; los registros describen eventos con contexto; las trazas pueden relacionar etapas de una operación entre servicios. La introducción a observabilidad de OpenTelemetry presenta estas señales. Una placa pequeña no necesita ejecutar toda una plataforma de observabilidad para producir información útil.

Señales de operación de una flota.
SeñalEjemploUso
ConectividadEdad de recepción, reconexiones y reintentosLocalizar problemas de enlace o servicio
RecursosMemoria libre, máximos de cola y duración de tareasDetectar presión antes de fallos repetidos
ReiniciosContador, motivo y contexto de arranqueDistinguir corte, watchdog y otras causas
AplicaciónLecturas inválidas, errores y plazos incumplidosEvaluar la función que presta el equipo
MantenimientoBuild, aceptación, reversión y credenciales por vencerPlanificar campañas y recuperación

Un registro estructurado

{
  "nivel": "warning",
  "evento": "ota_reversion",
  "equipo": "equipo03",
  "boot_id": "arranque-ficticio-8",
  "campana": "campana-build2",
  "trabajo": "trabajo-104",
  "build_ejecutado": 1,
  "build_candidato": 2,
  "motivo": "diagnostico_local"
}

Se registran referencias y códigos estables en lugar de depender solo de frases libres. No se incluyen claves privadas, tokens, contraseñas ni URLs con secretos. Las exportaciones y el acceso de soporte también se limitan por permisos y retención.

Alertar con contexto

Una alerta necesita condición, ventana, severidad, propietario y una acción posible. Se pueden exigir varios intervalos de degradación, usar márgenes distintos para activar y cerrar, y agrupar eventos de un sitio para no generar cientos de avisos por un mismo enlace caído.

Los contadores acumulados se interpretan dentro de su arranque o época: un descenso tras reinicio no significa una tasa negativa de errores. Los indicadores agregados conservan dimensiones útiles como modelo y build. Poner un ID distinto en cada combinación de etiquetas puede aumentar mucho la cardinalidad; se elige dónde guardar el detalle por equipo.

06 · Denominadores, filtros y antigüedad

Un panel que explica sus números

«El 95 % está actualizado» es incompleto si no se sabe qué equipos entraron en la cuenta. El inventario incluye unidades sin reporte; el filtro puede limitarse a un sitio; y una publicación puede corresponder solo a cierto hardware.

Cobertura de contacto = activos con evidencia reciente / activos del filtro
Objetivo confirmado = compatibles activos con evidencia reciente,
salud correcta y build objetivo aceptada / compatibles activos del filtro

La segunda medida es una definición conservadora del ejercicio. No trata a un equipo vencido como actualmente confirmado solo porque su último reporte decía build 2. En una plataforma real puede mostrarse además la última versión conocida, con su antigüedad y una categoría de estado desconocido.

Flota inicial del laboratorio, con reloj en 600 segundos y factor 3.
EquipoSituaciónConsecuencia
equipo01Reciente, build 2 aceptada y salud correctaAporta al objetivo confirmado
equipo02Reciente, build 1 aceptada y salud correctaPuede proponerse para el siguiente lote
equipo03Última recepción en 400: edad 200 segundosEvidencia vencida; no se propone
equipo04Reciente, build 2 sin aceptar y salud degradadaRequiere diagnóstico
equipo05Reciente, pero es placaBNo corresponde a esta publicación para placaA
equipo06Activo en inventario, sin reportesSigue contando en la cobertura
equipo07RetiradoVisible como referencia, fuera del denominador activo

Inicialmente hay 4 contactos recientes entre 6 activos. Para el objetivo, hay 1 confirmado con evidencia reciente entre 5 compatibles activos. Son medidas diferentes; la placaB cuenta para contacto, pero no para la campaña de placaA.

El panel también declara cuándo se calculó y con qué política de vencimiento. No usa verde para «sin datos». Una vista histórica y una vista de estado actual tienen funciones distintas y pueden necesitar almacenes y retenciones diferentes.

07 · Intervenir con permisos y límites

Diagnóstico y trabajos remotos

El diagnóstico empieza con la evidencia disponible: inventario, contacto, versión, arranque y registros. Se acota la causa antes de enviar una intervención. Reiniciar toda una flota para resolver un problema localizado puede borrar evidencia y ampliar la interrupción.

  1. Delimitá el alcance: equipo, sitio, modelo, build y momento de inicio.
  2. Separá capas: recepción del backend, red, identidad, recursos y función local.
  3. Correlacioná eventos: cambios de versión, reconexiones, reinicios y errores.
  4. Pedí evidencia acotada: snapshot o registros con límite de tamaño y duración.
  5. Intervení bajo autorización: trabajo específico, plazo y resultado verificable.

Los trabajos remotos organizan acciones como consultar diagnóstico o preparar una actualización. Como ejemplo, AWS IoT Jobs describe operaciones dirigidas a uno o varios equipos. El diseño de esta lección es independiente del proveedor.

Contrato mínimo de una operación remota.
Campo o condiciónPropósito
ID de trabajo y campañaCorrelacionar recepción, ejecución y resultado
Destino y permisosLimitar quién ordena qué sobre qué unidad
Vigencia y condiciones localesNo ejecutar una orden vencida o insegura
Idempotencia y estado persistidoResolver reintentos sin repetir efectos indebidamente
Resultado y evidenciaDistinguir recibido, iniciado, aceptado, fallido o desconocido

La entrega de un mensaje no demuestra ejecución física ni éxito de firmware. Un timeout puede dejar el resultado desconocido: no se convierte automáticamente en éxito ni justifica repetir una acción sin consultar su estado.

La gestión remota no debe ofrecer una consola de comandos arbitrarios como atajo. Las operaciones tienen un contrato acotado, auditoría y condiciones locales. Los interbloqueos del dispositivo siguen actuando aunque el operador tenga permiso.

08 · Aprender antes de ampliar el alcance

Campañas y grupos piloto

Una campaña identifica publicación, población objetivo, permisos, límites y criterios de avance. Un grupo piloto pequeño permite observar la instalación antes de ampliarla. Se eligen unidades que representen hardware y condiciones relevantes, no solo las que tienen mejor conectividad.

Avance de una campaña por evidenciaInventario y estado reciente permiten formar candidatos compatibles. Los resultados del piloto deciden si se espera, se pausa o se propone un siguiente lote. Cada equipo mantiene la verificación local de la actualización.Inventario y observacionescompatibilidad y frescuraEvaluar el pilotomuestra, fallos y pendientesProponer siguiente lotecon un límite de equiposEsperar o pausarcuando falta evidencia o falla

Regla de avance del ejercicio

resueltos = éxitos + fallos
tasa de fallos = fallos / resueltos × 100, si resueltos > 0
  1. Si la tasa observada supera el 20 %, se pausa, incluso con una muestra pequeña.
  2. Si hay resultados pendientes, se espera.
  3. Si no hay al menos tres resultados resueltos, se espera.
  4. Con esas condiciones satisfechas, se propone hasta el tamaño de lote indicado entre los equipos elegibles del filtro.

El límite es estricto: 1 fallo entre 5 resueltos da 20 % y no activa por sí solo la pausa; 1 entre 4 da 25 % y sí. Cero resultados no significa 0 % de fallos: significa ausencia de evidencia.

Estos números son una política didáctica, no una recomendación de tamaño o tolerancia para cualquier producto. Tres éxitos no prueban seguridad estadística. La operación real considera severidad, ventana de observación, representatividad, disponibilidad y fallos tardíos.

Elegibilidad y avance son controles distintos

El piloto puede permitir avanzar y aun así no haber candidatos en un sitio. El modelo exige equipo activo, placaA, snapshot reciente, salud correcta, firmware actual confirmado y build menor que el objetivo. Ordena por ID para que el resultado sea reproducible; no optimiza redundancia ni riesgo físico.

Los resultados introducidos en el laboratorio representan un piloto fijo de la misma campaña y son entradas independientes: no se calculan a partir de estas siete filas. Filtrar la tabla cambia los candidatos, no reescribe esos resultados. Una plataforma real los obtiene de eventos relacionados con trabajo, campaña, publicación y población, evitando mezclar éxitos de otras versiones.

Una pausa detiene nuevas asignaciones según su política; no cancela mágicamente trabajos en ejecución ni revierte equipos ya aceptados. El firmware conserva los controles de firma, aceptación y seguridad de los temas anteriores.

09 · Medir el costo de observar

Volumen, recursos y operación

La frecuencia que parece pequeña en una unidad puede ser importante en miles. Con 10.000 equipos y un reporte por minuto, el promedio es de unos 166,7 reportes por segundo. Si cada payload mide 512 bytes, son aproximadamente 85,3 kB/s de payload, sin incluir protocolo, TLS, reintentos ni almacenamiento adicional.

El promedio no describe el pico de reconexión tras volver la energía de un edificio. Se usan esperas, variación temporal y límites para evitar que todos los agentes publiquen o descarguen al mismo instante. Se mide también el comportamiento de las colas y la recuperación del backend.

Decisiones de recursos que afectan al monitoreo.
DecisiónCompromiso
Cadencia de reportesDetección más rápida frente a consumo y tráfico
Buffer localConservar evidencia frente a RAM, flash y desgaste
Nivel de registrosDetalle de diagnóstico frente a volumen y exposición
RetenciónHistoria útil frente a costo y necesidad de protección
AgregaciónVisión de conjunto frente a pérdida de detalle por unidad

Los buffers son acotados y tienen una política cuando se llenan: prioridad de eventos esenciales, descarte contado y recuperación de envío. No se permite que un problema de nube consuma toda la memoria del control local.

El nivel de diagnóstico aumentado puede tener una duración limitada y volver al normal automáticamente. También se monitorea el sistema de monitoreo: retraso de ingesta, mensajes rechazados, colas, errores de consulta y disponibilidad de la vista.

El laboratorio usa segundos relativos y una lista pequeña. No implementa broker, almacenamiento histórico, autenticación real, alarmas persistentes, trabajos ni ejecución OTA. Sus funciones ayudan a razonar sobre la evidencia y se pueden probar sin depender de una cuenta externa.

10 · Reportes y decisiones reproducibles

Modelo ejecutable en Python

El registro conserva siete equipos, una sesión fija por unidad y el tiempo relativo del servidor. recibir valida el vínculo y el orden; resumir calcula una vista; campana propone un lote. Ninguna de esas funciones envía órdenes.

Los parámetros de filtros y campaña pertenecen al conjunto cerrado del laboratorio: factores positivos, lotes de 1 a 3 y contadores enteros no negativos. La función de recepción sí comprueba los campos del snapshot que utiliza. Un servicio real valida además formato, límites, autenticación, transacciones y concurrencia.

Python en el navegador. El ejemplo usa solo biblioteca estándar y funciona con Pyodide. Los reportes son actuales dentro de la simulación; no representan mediciones históricas encoladas.

from copy import deepcopy


def nuevo():
    filas = [
        ("equipo01", "plantaA", "placaA", 590, 2, True, True, "activo"),
        ("equipo02", "plantaA", "placaA", 570, 1, True, True, "activo"),
        ("equipo03", "plantaA", "placaA", 400, 1, True, True, "activo"),
        ("equipo04", "plantaB", "placaA", 580, 2, False, False, "activo"),
        ("equipo05", "plantaB", "placaB", 590, 1, True, True, "activo"),
        ("equipo06", "plantaB", "placaA", None, None, None, False, "activo"),
        ("equipo07", "plantaA", "placaA", 595, 1, True, True, "retirado")
    ]
    flota = [dict(id=id, sitio=sitio, modelo=modelo, intervalo=30,
                  recibido=recibido, build=build, salud=salud,
                  confirmada=confirmada, ciclo=ciclo, sesion=1,
                  secuencia=0 if recibido is None else 10)
             for id, sitio, modelo, recibido, build, salud, confirmada, ciclo in filas]
    return dict(ahora=600, objetivo=2, flota=flota)


def entero_seguro(n):
    # Igual rango entero exacto que admite JavaScript; no acepta booleanos.
    return type(n) in (int, float) and -9007199254740991 <= n <= 9007199254740991 and n % 1 == 0


def recibir(estado, id="equipo03", principal="equipo03", sesion=1,
            secuencia=11, build=1, salud=True, confirmada=True, identidad=True):
    e = deepcopy(estado)
    def fin(ok, motivo): return dict(estado=e, ok=ok, motivo=motivo)
    d = next((x for x in e["flota"] if x["id"] == id), None)
    if not identidad or principal != id:
        return fin(False, "El principal autenticado no corresponde al equipo declarado")
    if d is None: return fin(False, "Equipo fuera del inventario")
    if d["ciclo"] != "activo": return fin(False, "Equipo retirado: no reactiva su identidad mediante un reporte")
    if not entero_seguro(sesion) or sesion != d["sesion"]: return fin(False, "Reporte de otra sesión; un contador mayor no lo habilita")
    if not entero_seguro(secuencia) or secuencia <= d["secuencia"]:
        return fin(False, "Secuencia duplicada, atrasada o inválida")
    if not entero_seguro(build) or build < 1 or type(salud) is not bool or type(confirmada) is not bool:
        return fin(False, "Snapshot con campos inválidos")
    d.update(recibido=e["ahora"], secuencia=int(secuencia), build=int(build), salud=salud, confirmada=confirmada)
    return fin(True, "Snapshot aceptado con tiempo de recepción del servidor")


def avanzar(estado, segundos):
    e = deepcopy(estado)
    if not entero_seguro(segundos) or segundos <= 0 or segundos > 3600:
        return dict(estado=e, ok=False, motivo="Avance de tiempo inválido")
    segundos = int(segundos)
    e["ahora"] += segundos
    return dict(estado=e, ok=True, motivo=f"Reloj del servidor avanzado {segundos} segundos")


def resumir(e, sitio="todos", factor=3):
    filas = []
    for d in e["flota"]:
        if sitio != "todos" and d["sitio"] != sitio: continue
        edad = None if d["recibido"] is None else e["ahora"] - d["recibido"]
        reciente = edad is not None and 0 <= edad <= factor * d["intervalo"]
        if d["ciclo"] != "activo": motivo = "Retirado"
        elif d["recibido"] is None: motivo = "Sin reporte"
        elif d["modelo"] != "placaA": motivo = "Otro hardware"
        elif not reciente: motivo = "Evidencia vencida"
        elif not d["salud"]: motivo = "Salud degradada"
        elif not d["confirmada"]: motivo = "Firmware sin confirmar"
        elif d["build"] >= e["objetivo"]: motivo = "No requiere el objetivo"
        else: motivo = "Candidato al lote"
        contacto = ("retirado" if d["ciclo"] != "activo" else "sin reporte" if edad is None
                    else "reciente" if reciente else "vencido")
        objetivo = (d["ciclo"] == "activo" and d["modelo"] == "placaA" and reciente
                    and d["salud"] is True and d["confirmada"] and d["build"] == e["objetivo"])
        filas.append(dict(d, edad=edad, contacto=contacto, elegible=motivo == "Candidato al lote",
                          motivo=motivo, objetivoConfirmado=objetivo))
    activos = [d for d in filas if d["ciclo"] == "activo"]
    return dict(filas=filas, activos=len(activos),
                recientes=sum(d["contacto"] == "reciente" for d in activos),
                vencidos=sum(d["contacto"] == "vencido" for d in activos),
                sinReporte=sum(d["contacto"] == "sin reporte" for d in activos),
                compatibles=sum(d["modelo"] == "placaA" for d in activos),
                objetivoConfirmado=sum(d["objetivoConfirmado"] for d in filas))


def campana(resumen, exitos=3, fallos=0, pendientes=0, lote=2):
    resueltos = exitos + fallos
    tasa = fallos * 100 / resueltos if resueltos else None
    base = dict(resueltos=resueltos, tasa=tasa, decision="", motivo="", seleccion=[])
    if resueltos > 0 and fallos * 100 > 20 * resueltos:
        return dict(base, decision="pausar", motivo="Fallas observadas por encima del 20 %")
    if pendientes > 0:
        return dict(base, decision="esperar", motivo="El piloto todavía tiene resultados pendientes")
    if resueltos < 3:
        return dict(base, decision="esperar", motivo="Faltan al menos tres resultados resueltos del piloto")
    seleccion = sorted(d["id"] for d in resumen["filas"] if d["elegible"])[:lote]
    return dict(base, seleccion=seleccion, decision="avanzar" if seleccion else "sin candidatos",
                motivo="Piloto admite evaluar el siguiente lote compatible" if seleccion
                else "No hay equipos elegibles en el filtro actual")


comprobaciones = 0


def comprobar(condicion, nombre):
    global comprobaciones
    assert condicion, nombre
    comprobaciones += 1
    print("OK:", nombre)


def rechazar(e, nombre, **reporte):
    antes = deepcopy(e)
    r = recibir(e, **reporte)
    comprobar(not r["ok"] and r["estado"] == antes and e == antes, nombre)


estado = nuevo()
vista = resumir(estado)
comprobar(vista["activos"] == 6, "Retirado fuera del denominador activo")
comprobar(vista["recientes"] == 4, "Contacto inicial: 4 de 6")
comprobar(vista["vencidos"] == 1 and vista["sinReporte"] == 1, "Vencido y nunca reportado son distintos")
comprobar(vista["objetivoConfirmado"] == 1 and vista["compatibles"] == 5, "Objetivo: 1 de 5 compatibles")
comprobar(campana(vista)["seleccion"] == ["equipo02"], "Solo propone el candidato reciente")
comprobar(campana(vista, exitos=0)["tasa"] is None, "Sin muestra no inventa tasa cero")
comprobar(campana(vista, exitos=2)["decision"] == "esperar", "Muestra insuficiente")
comprobar(campana(vista, pendientes=1)["decision"] == "esperar", "Pendientes impiden avanzar")
comprobar(campana(vista, exitos=3, fallos=1)["decision"] == "pausar", "25 % pausa")
comprobar(campana(vista, exitos=4, fallos=1)["decision"] == "avanzar", "20 % exacto no supera el límite")
comprobar(campana(vista, exitos=0, fallos=1)["decision"] == "pausar", "Fallo temprano también pausa")
comprobar(campana(resumir(estado, sitio="plantaB"))["decision"] == "sin candidatos", "Filtro sin candidatos")
comprobar(resumir(estado, sitio="plantaA")["activos"] == 3, "Filtro conserva denominador de sitio")
limite = resumir(avanzar(estado, 60)["estado"])
comprobar(next(d for d in limite["filas"] if d["id"] == "equipo02")["contacto"] == "reciente", "90 segundos inclusive")
limite = resumir(avanzar(estado, 61)["estado"])
comprobar(next(d for d in limite["filas"] if d["id"] == "equipo02")["contacto"] == "vencido", "91 segundos vencido")
antes = deepcopy(estado)
r = recibir(estado)
comprobar(r["ok"] and estado == antes, "Recepción no modifica el argumento")
estado = r["estado"]
comprobar(campana(resumir(estado))["seleccion"] == ["equipo02", "equipo03"], "Nuevo snapshot habilita otro candidato")
rechazar(estado, "Duplicado no refresca recepción")
rechazar(estado, "Secuencia atrasada", secuencia=9)
rechazar(estado, "Sesión anterior con contador mayor", sesion=0, secuencia=99)
rechazar(estado, "Principal de otro equipo", principal="equipo01")
rechazar(estado, "Identidad no verificada", identidad=False)
rechazar(estado, "Fuera de inventario", id="equipo99", principal="equipo99")
rechazar(estado, "Retirado no se reactiva", id="equipo07", principal="equipo07")
rechazar(estado, "Secuencia fraccionaria", secuencia=11.5)
rechazar(estado, "Salud con tipo inválido", secuencia=12, salud=1)
r = recibir(estado, id="equipo02", principal="equipo02", build=2, confirmada=False)
estado = r["estado"]
fila = next(d for d in resumir(estado)["filas"] if d["id"] == "equipo02")
comprobar(r["ok"] and fila["motivo"] == "Firmware sin confirmar", "Build objetivo en prueba no equivale a aceptación")
r = recibir(estado, id="equipo02", principal="equipo02", secuencia=12, build=2)
estado = r["estado"]
comprobar(r["ok"] and resumir(estado)["objetivoConfirmado"] == 2, "Aceptación reportada actualiza cobertura")
estado = recibir(estado, id="equipo01", principal="equipo01", build=2, salud=False)["estado"]
comprobar(resumir(estado)["objetivoConfirmado"] == 1, "Degradación reduce evidencia de objetivo sano")
estado = avanzar(estado, 120)["estado"]
comprobar(resumir(estado)["recientes"] == 0, "El paso del tiempo vence los reportes")
comprobar(campana(resumir(estado))["seleccion"] == [], "No propone unidades con evidencia vencida")
r = avanzar(estado, -1)
comprobar(not r["ok"] and r["estado"] == estado, "Reloj de prueba no retrocede")
estado = recibir(estado, secuencia=12)["estado"]
comprobar(campana(resumir(estado))["seleccion"] == ["equipo03"], "Snapshot nuevo recupera elegibilidad")
comprobar(len(campana(resumir(nuevo()), lote=1)["seleccion"]) <= 1, "Respeta el límite de lote")
print(f"\n{comprobaciones} comprobaciones superadas; no se enviaron trabajos.")

El recorrido comprueba denominadores, límites de frescura, piloto, identidad, orden de reportes, aceptación de firmware y vencimiento. Los rechazos conservan el estado y no refrescan la presencia.

11 · Observar y proponer sin ejecutar

Laboratorio de flota y campaña

Los filtros recalculan la vista de inmediato. Los reportes son eventos predefinidos con secuencia fija: repetir uno aceptado produce un duplicado. El reloj avanza solo cuando lo pedís; la pestaña no recibe eventos reales.

Flota de dos instalaciones

Reloj relativo del servidor—
Contacto reciente / activos del filtro—
Vencidos / sin reporte—
Objetivo sano confirmado / compatibles activos—
Decisión de campaña—
Equipos propuestos, sin asignación real—

Preparando propuesta.

Vista de equipos

Versión, salud y aceptación corresponden al último snapshot; la columna de contacto indica su antigüedad.
Equipo / sitio / hardwareContacto y edadÚltima versiónÚltima saludAceptación del firmwareSiguiente loteMotivo de elegibilidad

Simular recepción o paso del tiempo

Flota inicial lista. No se contactó ningún dispositivo.

Historial de operaciones

Últimas 30 operaciones sobre el registro ficticio.
PasoOperaciónResultadoMotivo

Propuesta de campaña


        

Recorrido guiado

  1. Con todos los sitios y factor 3, observá 4/6 en contacto y 1/5 en objetivo sano confirmado. El lote propone solo equipo02.
  2. Filtrá plantaB. No hay candidatos: equipo04 está degradado, equipo05 usa otro hardware y equipo06 no reportó. Restaurá todos los sitios.
  3. Simulá el snapshot nuevo de equipo03. Pasa a reciente y se suma al lote. Cambiá el máximo a 1: la propuesta respeta el límite. Volvé a 2.
  4. Repetí el mismo snapshot. Se rechaza como duplicado. Probá también secuencia atrasada, sesión anterior y principal incorrecto: no refrescan los datos.
  5. Enviá build 2 sin confirmar de equipo02. Sale de los candidatos, pero no cuenta todavía como objetivo aceptado. Enviá después la confirmada: aumenta la cobertura.
  6. Enviá salud degradada de equipo01. Sigue con contacto reciente, pero deja de aportar al indicador de objetivo sano.
  7. Avanzá 120 segundos con factor 3. La evidencia de todos los equipos activos queda vencida o ausente. Los números cambian aunque no llegue ningún evento nuevo.
  8. Probá el reporte de equipo retirado y el desconocido. Ninguno modifica el inventario. Un reporte rechazado tampoco se convierte en una señal de presencia válida.
  9. Creá un escenario nuevo. Con 3 éxitos y 1 fallo el piloto pausa por 25 %. Con 4 éxitos y 1 fallo queda en 20 %; con pendientes todavía espera. Con cero éxitos y cero fallos la tasa dice «sin datos».
  10. Restaurá 3 éxitos, cero fallos y cero pendientes. La propuesta vuelve a los candidatos del filtro. No se asigna ningún trabajo ni se actualiza un firmware por cambiar estos controles.

El botón de escenario conserva filtros y parámetros. Para repetir las cifras iniciales, usá todos los sitios, factor 3, lote 2, 3 éxitos y cero fallos o pendientes. Recargar la página reinicia toda la simulación.

¿Por qué un equipo puede estar reciente y no ser candidato?

La recepción es una condición. También se exigen ciclo activo, hardware compatible, salud, firmware aceptado y necesidad de instalar el objetivo. Esas razones se muestran por separado de la presencia.

12 · De los datos a una intervención trazable

Método de implementación

  1. Establecé el inventario: identidad, pertenencia, hardware, sitio y ciclo de vida.
  2. Definí expectativas: cadencia, antigüedad admisible y política de unidades dormidas.
  3. Definí contratos: snapshots, historia, sesión, secuencia y esquema.
  4. Autenticá la ingesta: principal y permisos antes de actualizar la vista.
  5. Preservá orden y consistencia: rechazos sin refresco, cursores y concurrencia.
  6. Diseñá indicadores: filtros, denominadores, desconocidos y momento de cálculo.
  7. Diseñá diagnóstico: registros acotados, correlación y acceso autorizado.
  8. Diseñá campañas: población, piloto, criterio de pausa y tamaño de lote.
  9. Probá fallos del sistema: enlace caído, reinicios, backlog, retraso de ingesta y recuperación del backend.
«El último estado guardado sigue vigente hasta que llegue otro»

La antigüedad cambia con el tiempo. Una vista de estado actual debe poder vencer evidencia sin recibir un nuevo mensaje.

«Si está conectado, está sano»

El transporte puede funcionar mientras la aplicación informa una falla esencial. Se separan conectividad y salud.

«No recibí fallos, así que todos tuvieron éxito»

Puede faltar evidencia. Los resultados pendientes o desconocidos no se convierten en éxitos.

«Build 2 reportada equivale a build 2 aceptada»

El firmware puede estar en prueba o degradado. Se conserva la aceptación y su contexto.

«Una secuencia mayor siempre es más nueva»

Necesita contexto de sesión y reglas de orden. Un contador de un arranque anterior no reemplaza una sesión actual.

«Un porcentaje de flota no necesita explicar su población»

Retirados, incompatibles, nunca reportados y filtros cambian el denominador. Se muestra la definición del indicador.

«Pausar la campaña revierte todo automáticamente»

Se detienen nuevas asignaciones bajo la política definida. Los trabajos ya iniciados y las aplicaciones aceptadas requieren su propio tratamiento.

13 · Práctica guiada

Predecí antes de comprobar

1. Último reporte hace 91 segundos

El intervalo esperado es 30 segundos y el factor 3. ¿Cómo se clasifica una recepción de hace 91 segundos? ¿Demuestra que el equipo se apagó?

Ver solución

La evidencia está vencida. El límite es 90 inclusive. No demuestra una causa física concreta; indica ausencia de observación válida dentro de la ventana.

2. Degradado pero reciente

Una unidad informa ahora que falló su sensor esencial. ¿Se la marca sin contacto?

Ver solución

No. Hay contacto reciente y salud degradada. Confundir ambas capas oculta un diagnóstico útil.

3. Un duplicado después de un minuto

Llega de nuevo el mismo snapshot con igual sesión y secuencia. ¿Debe refrescar la última recepción operativa del modelo?

Ver solución

No. Se rechaza sin cambiar el registro. Su llegada puede registrarse como métrica de transporte o rechazo, sin convertir contenido repetido en evidencia actual.

4. Denominador incompleto

Hay seis activos y uno nunca reportó. ¿Se lo excluye para mostrar mejor cobertura de contacto?

Ver solución

No. Sigue en el inventario activo y cuenta en el denominador definido. Se muestra como sin reporte.

5. Una versión todavía en prueba

El dispositivo reporta build objetivo y salud correcta, pero aceptación falsa. ¿Cuenta como objetivo sano confirmado?

Ver solución

No. El primer arranque y la confirmación son etapas distintas, como vimos en el tema 44.

6. El límite de fallos

El piloto tiene 4 éxitos y 1 fallo, sin pendientes. ¿Supera el límite estricto del 20 %? ¿Y con 3 éxitos y 1 fallo?

Ver solución

1/5 es 20 % y no supera ese límite. 1/4 es 25 % y pausa. Se siguen aplicando las demás condiciones del ejercicio.

7. Resultados sin resolver

Hay 3 éxitos, cero fallos y 2 pendientes. ¿Avanza el piloto del modelo?

Ver solución

Espera. Los pendientes no se contabilizan como éxito. Un sistema real define su plazo y cómo clasifica resultados desconocidos.

8. Una caída de sitio

Cien equipos de una instalación dejan de reportar juntos. ¿Por dónde conviene comenzar el diagnóstico?

Ver solución

Por el alcance compartido: alimentación, enlace, gateway, permisos o ingesta de ese sitio, correlacionando tiempo y cambios. No se presupone que cien sensores independientes fallaron a la vez ni se reinicia todo sin evidencia.

14 · Síntesis

Ideas para recordar

  • El inventario incluye unidades sin reporte y conserva su pertenencia y ciclo de vida.
  • Estado deseado, reportado y derivado tienen autoridades y significados diferentes.
  • La evidencia vence con el tiempo; contacto reciente no garantiza salud física.
  • Principal, sesión y secuencia impiden que mensajes indebidos o atrasados sobrescriban la vista.
  • Las mediciones históricas no se confunden con snapshots actuales.
  • Los indicadores explican población, filtros, antigüedad y desconocidos.
  • Las campañas avanzan por resultados relacionados con su publicación, no por ausencia de errores recibidos.
  • Proponer un lote no equivale a ejecutar una actualización ni saltar los controles locales.
  • El monitoreo tiene costos de tráfico, energía, memoria, almacenamiento y operación.

¿Listo para el tema 46?

Antes de avanzar, definí qué reporta tu equipo, con qué cadencia y cuánto tiempo puede pasar sin que su evidencia siga considerándose reciente. Explicá también qué datos necesita un operador para distinguir una unidad dormida, un enlace caído y una aplicación degradada.

En el próximo tema, Bajo consumo en dispositivos conectados, estudiamos sueño profundo, radio apagada y autonomía con batería.