Sobre la Aplicación

WHeat-Jobs | Fenotipo

1. Identificación de la obra y declaración de autoría

La presente memoria técnica describe la arquitectura, organización funcional y modelo operativo del sistema software WHeat-Jobs | Fenotipo, desarrollado como aplicación web para la integración, estructuración y explotación funcional de datos biométricos y clínicos.

Se hace constar que Ricardo Manuel Trigo Calonge es autor del diseño funcional del sistema, de su arquitectura de software, de la organización de módulos, de la lógica algorítmica y del código fuente principal incorporado a la presente obra.

Estado registral: obra software inscrita en el Registro Territorial de la Propiedad Intelectual de Madrid, asiento registral 16/2026/8798, fecha de efecto 07/09/2026.

2. Objeto y finalidad funcional del sistema

El sistema WHeat-Jobs | Fenotipo está orientado a la integración y análisis funcional de datos biométricos diarios, analíticas clínicas y variables personales, con el objetivo de generar indicadores compuestos y evaluar tendencias temporales.

El sistema no se limita al almacenamiento de datos, sino que incorpora mecanismos propios de formación de contexto fisiológico, cálculo técnico, persistencia histórica y evaluación funcional.

3. Arquitectura general del sistema

Patrón arquitectónico Modelo Vista Controlador (MVC)
Lenguaje principal C# · .NET 10 · ASP.NET Core MVC
Persistencia SQL Server mediante Entity Framework Core
Autenticación ASP.NET Core Identity
Motores de cálculo Motor hardcoded, motor declarativo y modo dual comparativo
Asistencia IA Ollama Cloud (por defecto) / Claude API (alternativo) · Modelo: gemma4:31b · Procesamiento asíncrono en background con cola de canal interno
Importación masiva Subida ZIP fragmentada por bloques, recomposición en servidor, extracción de export.xml y generación de reducidos semanales
Importación analítica clínica Lectura automática de informes de laboratorio en PDF mediante IA nativa (AnthropicClient / documento directo, sin OCR previo): extracción de hasta 60 biomarcadores, conversión automática de unidades según laboratorio, revisión con semáforo de rangos de referencia y persistencia en AnaliticaClinica

4. Modelo de datos

PerfilPersonal

Contiene los datos estructurales del usuario, incluyendo edad, altura, sexo y otros elementos necesarios para la interpretación funcional.

BiodataDiaria

Registro diario estructurado que contiene información biométrica agregada y normalizada procedente de fuentes internas o externas.

AnaliticaClinica

Almacena biomarcadores clínicos utilizados como soporte del cálculo funcional. Dispone de CRUD completo e importación automática desde PDF: la IA extrae hasta 60 parámetros (hematología, lípidos, enzimas, tiroides, orina…) con conversión automática de unidades y revisión asistida antes de persistir.

HealthScoreHistory

Persistencia técnica del resultado de cálculo con su contexto temporal.

EnergyDailyBalance

Persistencia diaria del balance energético: ingesta, gasto basal y activo ajustados, macronutrientes, alcohol, proteína/kg y clasificación oficial del estado energético.

HealthEvolutionSnapshot

Persistencia del resultado heurístico vigente: objetivo, alineación de pauta, decisión operativa y justificación funcional.

IntakePool / IntakePoolItem / IntakePoolFoodItem

Gestión semanal de ingesta alimentaria por usuario: 7 slots diarios con sus alimentos, gramos confirmados y JSON nutricional calculado por IA u OpenFoodFacts. Multiusuario — cada usuario dispone de su propio pool aislado.

UserEnergyCalibration

Factor de corrección de ingesta calórica derivado empíricamente por regresión MCO para cada usuario. Incluye factores de corrección de gasto basal y activo importado de HealthKit. El factor de ingesta se aplica solo sobre macros; el alcohol usa fórmula determinista y queda excluido de la corrección.

MetabolicAdaptationSnapshot

Snapshot de adaptación metabólica calculada sobre una ventana temporal configurable (14–90 días). Persiste el gap entre TDEE teórico por el modelo energético y TDEE real estimado por balance de masa, con nivel de adaptación (Sin / Leve / Moderada / Severa), delta de peso, R² de regresión de peso, cobertura proteica media y alertas de reactivación metabólica y proteína. Soporta serie histórica de 12 ventanas de 28 días. Generación automática nocturna mediante MetabolicSnapshotWorker, que cada noche recalcula D-1 y D-2 para absorber datos del wearable completados o corregidos al día siguiente.

5. Secuencia funcional del algoritmo

  1. Importación masiva — recepción del ZIP, recomposición por bloques, extracción del export.xml y persistencia interna.
  2. Importación de analítica clínica desde PDF — subida del informe de laboratorio; la IA extrae hasta 60 biomarcadores con conversión automática de unidades, el usuario revisa en pantalla con semáforo de rangos y confirma la persistencia en AnaliticaClinica.
  3. Reducción semanal y agregación diaria — generación de XML reducidos por semana y transformación a registros normalizados de BiodataDiaria.
  4. Cálculo energético diario — a partir de BiodataDiaria, generación y persistencia de EnergyDailyBalance con balance, macronutrientes y estado energético.
  5. Construcción de la Colección de Datos Funcionales — selección de analítica válida y ventana biométrica; formación del HealthDashDataBundle.
  6. Ejecución del motor activo — cálculo de Score SNC, Metabólico y Bioenergético mediante motor seleccionado.
  7. Persistencia del resultado — registro en HealthScoreHistory con contexto temporal y técnico completo.
  8. Evaluación heurística — contraste con objetivo, dirección, alineación de pauta y decisión operativa. Persistencia en HealthEvolutionSnapshot.

6. Integración de asistencia inteligente mediante modelos IA

El sistema incorpora módulos de asistencia funcional basados en modelos de lenguaje. El proveedor por defecto es Ollama Cloud, con Claude API como alternativa conmutable desde la consola de administración. El modelo activo se configura mediante OllamaCloud:Model (actualmente: gemma4:31b).

Los módulos IA activos son:

  • Análisis nutricional de ingesta — dos modos: IA (estimación de macros y energía por modelo de lenguaje) y modo híbrido OFf+IA (datos reales de etiqueta desde OpenFoodFacts para productos envasados de marca; IA como fallback para genéricos y platos caseros; bebidas alcohólicas siempre a IA por cálculo determinista). Antes de llamar a cualquiera de los dos motores, cada alimento se contrasta contra una biblioteca personal por usuario (nombre normalizado, composición por 100g/ml); si ya está guardado, se reutiliza sin volver a estimar — elimina la variabilidad de recalcular el mismo alimento cada vez. Los alimentos que OpenFoodFacts no encuentra se avisan por su nombre y pueden añadirse a mano a la biblioteca (composición por 100g/ml), tras lo que se recalcula el menú.
  • Aviso de alérgenos en la estimación nutricional — capa independiente del cálculo de macros. Combina un diccionario determinista de los 14 alérgenos de declaración obligatoria de la UE, con normalización de texto y detección de negaciones ("sin gluten", "deslactosado"...) para evitar falsos positivos por coincidencia de subcadena, con una capa complementaria de IA cuyo resultado se filtra por el mismo diccionario antes de fusionarse: el diccionario actúa como veto final, ya que la respuesta de la IA no es determinista. Se aplica igual en ambos motores de cálculo de ingesta.
  • Automatización de Ingesta — generación sintética de una semana de registros de ingesta a partir del historial del usuario (requiere ≥ 30 días previos). Calcula media (μ) y desviación típica (σ) por macro y día de semana; genera valores mediante muestreo normal con σ efectiva ≤ 25 % de μ. Se recomienda su uso solo como último recurso ante ausencia de datos reales, ya que introduce inexactitud estadística en los registros de ingesta. Mejor que no tener datos, pero nunca equivalente a la declaración real.
  • Asistente IA conversacional — chatbot contextual accesible desde cualquier página mediante botón flotante. Recibe los últimos 60 días (Ollama) o 180 días (Claude API) de BiodataDiaria (energía basal, activa, ingesta, balance, macros, sueño, FC, HRV…), el perfil personal y el mapa de menús visibles para el usuario según su rol (generado desde NavigationData) como contexto. Compatible con Claude API y Ollama. Historial en cliente (10 turnos), sin tablas propias.
  • Resumen interpretativo de evolución de BiodataDiaria.
  • Análisis integrado de HealthDash — síntesis en lenguaje natural del estado fisiológico global, sin carácter diagnóstico.
  • Interpretación de dinámica metabólica.
  • Extracción automática de analítica clínica desde PDF — el informe de laboratorio se procesa directamente por la IA como documento nativo (sin OCR previo), mapeando hasta 60 biomarcadores al modelo AnaliticaClinica con corrección automática de escalas (notación científica ×10ⁿ, PCR mg/dL→mg/L, Urea mmol/L→mg/dL, T4 pmol/L→ng/dL, Calcio/Fósforo mmol/L→mg/dL).
  • Revisión histórica parcial con resumen IA — informe longitudinal para un período seleccionado por el usuario, que integra perfil fenotípico, evolución de scores SNC/MET/BIOE, balance energético (ingesta/basal/NEAT/total/balance), macronutrientes medios y analítica clínica con verificación sistemática de rangos de referencia sobre ~55 parámetros (BuildParametros); genera un resumen interpretativo asíncrono de cinco párrafos estructurados mediante plantilla versionada en BD (HistoriaPacientePromptSeeder), con informe imprimible como PDF desde el navegador.
  • Evolución longitudinal de parámetros clínico-analíticos — módulo de análisis de series temporales de analíticas clínicas en el intervalo que el usuario seleccione (presets de 6 meses, 1, 2 y 5 años o rango libre). Muestra ocho gráficas por categoría fisiológica (lipídico, glucémico, renal, hepático, tiroides, inflamación, micronutrientes, hematología). Una matriz comparativa interactiva presenta todos los biomarcadores disponibles en una tabla única con las fechas como columnas y el delta (Δ) entre primera y última analítica con color semántico por parámetro. Un análisis IA asíncrono — generado mediante plantilla versionada ANALITICA_EVOLUCION — interpreta las tendencias sostenidas, identifica cambios clínicamente relevantes y agrupa los hallazgos por sistema, tomando siempre al propio paciente como referencia en el tiempo, sin baremos poblacionales externos. Un informe imprimible reúne las ocho gráficas en formato compacto, la matriz comparativa completa y, cuando ya se ha generado, el análisis IA con su aviso de no constituir diagnóstico médico.
  • Inspector de asuntos internos IA — registro de diagnóstico de cada llamada a Ollama/Claude (proveedor, origen, usuario, modelo, motivo de parada, tokens, duración), persistido fuera del árbol público de la aplicación y accesible solo para el rol Admin, para diagnosticar lentitud o fallos intermitentes con datos objetivos en vez de suposición.
  • Análisis de impacto etílico — modelo MCO que cuantifica el efecto del alcohol sobre el peso mediante un único coeficiente, la retención de la mañana siguiente D+1 (β_alc), con su error estándar y p-valor. Separado de la regresión, calcula el equivalente calórico teórico del alcohol (7 kcal/g) y lo contrasta con una predicción de balance energético independiente (sin usar el peso observado) para detectar, sin circularidad, desviaciones que en series largas puedan indicar compromiso hepático.
  • Generación de documentos RGPD y contratos — producción asistida por IA de consentimientos informados, políticas de privacidad y contratos profesional-paciente adaptados al perfil del profesional y del paciente. La evaluación de impacto (EIPD) vive como documento interno editable solo por el administrador, con historial de versiones (Plantillas RGPD).
  • Seguridad y protección de datos — contraseñas de 12 o más caracteres, bloqueo de cuenta tras 5 intentos fallidos (15 min), verificación en dos pasos (TOTP) obligatoria para el administrador y voluntaria para el resto, tokens de Oura cifrados con Data Protection, tokens de importación guardados solo como hash, comprobación de la vigencia de la relación profesional-paciente en cada petición, registro de accesos de profesionales a pacientes (quién, a qué paciente, cuándo y qué petición, en ficheros mensuales sin contenido de datos) y, antes de enviar nada a la IA, filtro de identificadores, edad en lugar de fecha de nacimiento y extracción local del texto de las analíticas (o imagen tapada por el usuario en escaneos).
  • Todas las llamadas a IA se procesan en segundo plano mediante AiBackgroundWorker y IBackgroundTaskQueue, desacoplando la solicitud del usuario de la espera bloqueante y mostrando estimación de tiempo dinámica en la interfaz.

    7. Subsistema de importación de Biodata desde wearables

    El sistema dispone de dos vías de importación masiva de BiodataDiaria desde dispositivos wearable, accesibles desde el menú Adquisición de datos → Apple / Android (ZIP) mediante un selector de plataforma iOS/Android:

    7.1 Apple HealthKit (iOS)

    Procesa la exportación estándar de la app Salud de iPhone (export.zip), transformando el export.xml en registros diarios normalizados.

    1. Recepción del export.zip en bloques (chunked upload).
    2. Recomposición y extracción del export.xml interno.
    3. Generación de XML reducido semanal con filtro temporal inteligente.
    4. Agregación diaria por tipo de registro (pasos, FC, HRV, sueño, peso…).
    5. Previsualización por semana y persistencia selectiva en BiodataDiaria.
    7.2 Android / Google Health Connect vía heredada

    Procesa el ZIP de Google Takeout generado con Google Fit y/o Health Connect. Compatible con cualquier reloj integrado en Health Connect (Samsung Galaxy Watch, Pixel Watch, Fitbit, Garmin…). Marcada como obsoleta en la interfaz en favor del webhook automatizado (7.4): el HRV que produce es un valor ya agregado por fila del export, sin el filtrado por ventana de sueño real que sí aplican las demás vías.

    1. Recepción del ZIP de Takeout en bloques (misma mecánica chunked que iOS).
    2. Parseo en memoria de dos formatos: CSVs de Daily activity metrics (Google Fit, un fichero por día) y CSVs por tipo de métrica en carpetas Health Connect.
    3. Acumulación por fecha con upsert diferido: el primer valor encontrado por campo tiene prioridad; los formatos se complementan sin sobreescribirse.
    4. Previsualización con tabla de días (verde = nuevo, amarillo = actualiza existente) antes de confirmar.
    5. Confirmación: upsert real en BiodataDiaria. HRV mapeado como HrvSource.HealthKitSDNN (algoritmo SDNN, propio de los ficheros HeartRateVariabilitySdnn de Health Connect); FC media como FcMediaSource.HealthKitDiaria.
    6. Limpieza automática del ZIP temporal en servidor tras la importación.
    7.3 Apple HealthKit — automatización REST desatendida (JSON)

    Canal alternativo al XML (7.1), basado en el esquema JSON de la app de terceros Health Auto Export, mediante un endpoint REST (POST /api/healthautoexport, con el token en la cabecera Authorization: Bearer; la forma antigua con el token en la ruta sigue aceptándose mientras se reconfiguran los dispositivos) que recibe datos directamente desde una automatización del dispositivo, sin sesión interactiva.

    1. Autenticación por token opaco de 256 bits generado/regenerado por el usuario: en PerfilPersonal solo se guarda su hash SHA-256 y el valor en claro se muestra una única vez; viaja en la cabecera Authorization: Bearer (o X-Import-Token), no en la URL.
    2. Mismo motor de agregación Suma/Promedio/Último que el pipeline XML, vía acumulador por (fecha, campo).
    3. Deduplicación por preferencia de fuente (reloj sobre teléfono) en pasos, distancia, pisos y energía, evitando doble cómputo cuando ambos dispositivos registran la misma actividad.
    4. Reconstrucción del valor bruto de ejercicio cuando métricas y entrenamientos llegan en llamadas REST separadas, para que el resultado converja sin importar el orden de llegada.
    5. Marca de trazabilidad (FechaUltimaActualizacion / OrigenUltimaActualizacion) en cada fila tocada, visible en el listado de Biodata y en el panel de adherencia del inicio.
    6. Cintura medida a mano protegida frente al arrastre automático: como HealthKit no reporta cintura, el sistema la rellena por fallback con el último valor real conocido cuando el día no trae dato. El campo CinturaMedidaEnUtc (mismo patrón que PesoMedidoEnUtc) se rellena solo con mediciones reales introducidas en Biodata; mientras esté informado, ningún pipeline vuelve a tocar la cintura de ese día, aunque lleguen varias automatizaciones solapadas con la semana completa.
    7. VO2 máx como último valor medido: el campo Vo2Max de BiodataDiaria se alimenta desde los canales de importación de Apple y Google; al ser una medida esporádica, una lectura nueva sustituye al valor y los días sin lectura conservan el último conocido, sin promediar ni anularlo por ausencia de dato.
    7.4 Android — Webhook desatendido ("Health Connect Webhook")

    Canal equivalente a 7.3 pero para Android: un endpoint REST (POST /api/healthconnectwebhook, mismo esquema de token en cabecera) que recibe datos directamente desde la app de terceros "Health Connect Webhook" (proyecto "HC Webhook", código abierto), instalada en el propio dispositivo, sin sesión interactiva ni exportación manual.

    1. Reutiliza el mismo token opaco que iOS (PerfilPersonal.HealthAutoExportToken, guardado como hash) — identifica al usuario, no la plataforma.
    2. La app lee Health Connect vía su API nativa y publica un JSON plano con un array por tipo de dato; el sueño llega ya como sesiones con desglose de fases (sin reconstrucción por huecos, a diferencia del XML de Apple); si varias sesiones terminan el mismo día, se conserva la de más sueño real como episodio principal.
    3. Reutiliza sin cambios el mismo motor de persistencia que el JSON de iOS (7.3): mismo DTO por día, mismas reglas de Suma/Promedio/Último.
    4. El HRV se recoge como muestras crudas por instante y se filtra por la ventana de sueño real de esa noche (mismo criterio que 7.1/7.3), marcado con HrvSource.HealthConnectRMSSD (RMSSD, como Oura, pero origen distinto).

    7.5 Motor estructural de agregación diaria

    El sistema incorpora un motor estructural capaz de procesar documentos XML complejos mediante lectura secuencial en streaming.

    Este motor permite procesar estructuras jerárquicas sin necesidad de cargar el documento completo en memoria, permitiendo el tratamiento eficiente de archivos de gran tamaño.

    Se soportan estructuras complejas como:

    • Record
    • Workout
    • WorkoutStatistics
    • ActivitySummary
    • Correlation

    El sistema incorpora mecanismos para identificar correctamente intervalos temporales que cruzan medianoche.

    8. Elementos de originalidad técnica

    1. Construcción de la Colección de Datos Funcionales (HealthDashDataBundle).
    2. Motores de cálculo intercambiables: hardcoded, declarativo y modo dual comparativo.
    3. Persistencia histórica completa con contexto temporal, analítica y motor utilizado.
    4. Importación masiva estructural mediante subida ZIP fragmentada por bloques.
    5. Motor de agregación diaria estructural con lectura XML en streaming.
    6. Modelo diario de balance energético y disponibilidad funcional (EnergyDailyBalance).
    7. Análisis longitudinal de tendencias sobre variables directas de BiodataDiaria.
    8. Arquitectura de procesamiento IA asíncrono con cola de canal, almacenamiento de resultado y estimación dinámica de tiempos.
    9. Sistema dual de proveedor IA (Ollama / Claude) con consola de administración y conmutación en tiempo de ejecución.
    10. Sistema declarativo de gestión de prompts IA: plantillas versionadas (IaPromptTemplate), bloques de contenido componibles (IaPromptBlock) y motor de construcción con interpolación de variables de contexto (IaPromptBuilderService), administrable sin modificación de código fuente.
    11. Subsistema de importación automática de analíticas clínicas desde PDF mediante IA nativa: envío del documento directamente como base64 a AnthropicClient (sin OCR previo), extracción de hasta 60 biomarcadores, conversión automática de unidades por laboratorio, revisión interactiva con semáforo de rangos de referencia y persistencia estructurada en AnaliticaClinica.
    12. Módulo de revisión histórica parcial: servicio propio (HistoriaPacienteService) que compila un informe longitudinal del período seleccionado integrando perfil, evolución de scores, balance energético, macros y analítica clínica con verificación de rangos de referencia sobre ~55 parámetros; emite un resumen IA asíncrono de cinco párrafos estructurados (estado funcional, balance energético, nutrición/sueño/composición, analítica clínica y recomendaciones) mediante plantilla versionada (HistoriaPacientePromptSeeder), con informe imprimible como PDF desde navegador.
    13. Módulo de evolución longitudinal de parámetros clínico-analíticos (AnaliticaEvolucionAiService): procesa la serie temporal completa de objetos AnaliticaClinica en el intervalo seleccionado; construye ocho gráficas Chart.js por sistema fisiológico y una matriz comparativa interactiva (biomarcadores × fechas) con delta Δ codificado por semántica clínica del parámetro; encola un análisis IA asíncrono (IBackgroundTaskQueue, tipo ANALITICA_EVOLUCION) que interpreta tendencias sostenidas agrupadas por sistema, tomando al propio paciente como referencia, sin baremos poblacionales. Plantilla de prompt versionada en BD y editable desde la consola de prompts sin redesplegar. Generación de informe imprimible (hoja apaisada) en cliente: las ocho gráficas se redibujan en tamaño reducido, se incorpora la matriz comparativa y, si está disponible, el resultado IA.
    14. Modo híbrido OFf+IA para estimación nutricional: consulta OpenFoodFacts para productos envasados de marca (datos de etiqueta real) con fallback automático a IA para alimentos no encontrados; bebidas alcohólicas siempre calculadas por IA mediante fórmula determinista.
    15. Detección de alérgenos independiente del cálculo de macros: capa determinista (diccionario de los 14 alérgenos de declaración obligatoria de la UE, con detección de negaciones textuales del tipo "sin gluten") combinada con una capa complementaria de IA cuyo resultado se filtra por el mismo diccionario antes de fusionarse, de modo que la variabilidad de la IA no introduce falsos positivos.
    16. Modelo de impacto etílico (AlcoholCalibrationService): regresión MCO con F fijo que estima el coeficiente β_alc (retención la mañana siguiente, D+1), con su error estándar y p-valor (inversión de (XᵀX)⁻¹ vía Gauss-Jordan). La calidad del análisis se basa en la significancia de β_alc, no en el R² (bajo de forma estructural dado el ruido del peso diario, incluso con una señal real). El equivalente calórico teórico del alcohol (7 kcal/g) se calcula aparte de la regresión y se contrasta con una predicción de balance energético independiente —construida solo desde ingesta, gasto y alcohol, sin usar el peso observado— para validar la hipótesis sin caer en tautología; en series largas, sus desviaciones persistentes son la métrica de seguimiento de un posible compromiso hepático.
    17. Factor de corrección de ingesta aplicado exclusivamente sobre macros (HC×4 + Prot×4 + Grasa×9); el alcohol queda excluido de la corrección por usar fórmula determinista (ml × graduación × 0,789 × 7).
    18. Sistema de alerta automática de recalibración: en el primer ApplyToBiodata del día y mediante botón manual, comprueba si el balance acumulado implica variación de peso superior al umbral individual (0,5 % del peso actual, rango 200-800 g) y han transcurrido ≥ 28 días desde la última calibración.
    19. Arquitectura multiusuario con tres perfiles: profesional (crea y gestiona pacientes, accede a su propio expediente), paciente (creado por el profesional con credenciales propias) y usuario autónomo. El profesional alterna entre su expediente y el del paciente mediante contexto de sesión con validación de vinculación activa. Módulo de mensajería asíncrona profesional-paciente con tipos Indicación y SolicitudPermiso (aceptable/rechazable por el paciente). Generación asistida por IA de documentos RGPD y contratos profesional-paciente.
    20. Módulo de detección y medición de adaptación metabólica (MetabolicAdaptationService): cuantifica el gap entre TDEE teórico por el modelo energético (EnergyDailyBalance.EnergiaGastoTotalKcal) y TDEE real estimado por balance de masa (TDEE_real = (ΣIngestaCorregida − ΔPeso_kg × 7 700) / n_días), con regresión OLS sobre la serie de peso (umbral R² 0,25) para aislar variación de masa de ruido hídrico. Clasifica la adaptación en cuatro niveles internos (SinAdaptacion / Leve / Moderada / Severa; en pantalla, etiqueta corta — Sin adaptación (o "Muy adaptable" si el residual es inferior al −5 % del TDEE teórico) / Leve / Moderada / Severa — con la interpretación completa en el tooltip del badge, unificada entre AdaptacionMetabolica e HistoricoAdaptacion vía NivelAdaptacionDisplay, sin alterar el umbral que decide cada nivel), emite alertas de reactivación metabólica (adaptación > 250 kcal) y de cobertura proteica insuficiente (< 80 %), y ofrece serie histórica de 12 ventanas de 28 días con representación Chart.js. Los snapshots se persisten en MetabolicAdaptationSnapshots. Indicadores complementarios sobre la misma ventana: TEF individualizado por macronutriente (proteína 25 % / hidratos 8 % / grasa 2 % / alcohol 15 %, puramente informativo, no recalcula TDEE_real), Ratio TDEE real/teórico ("Índice de Rendimiento Metabólico"), ΔAdaptación a 30 días (velocidad de cambio contra el snapshot histórico más cercano) y variabilidad de peso residual (dispersión de los pesajes alrededor de la misma recta Theil-Sen que ya calcula ΔPeso). Ambas pantallas marcan en ámbar las ventanas con R² < 0,50 (mismo umbral en las dos) para señalar cuando el ΔPeso de esa ventana está dominado por ruido ajeno a grasa/masa magra (rachas de alcohol de varios días, sal, ciclo hormonal) — la cifra no cambia, solo su fiabilidad. Distinto de ese caso: IPesoRachaService detecta cuándo Peso lleva varios días con el mismo valor exacto (fallback de "último valor conocido" cuando el paciente no se pesa, no una meseta real) comparando la racha activa contra el máximo histórico de rachas de ese mismo paciente (umbral personal, no fijo). Cuando se dispara, inhibe la lectura de ΔPeso/composición/Nivel — y las reglas de interpretación automática que dependen de peso (Adaptación Metabólica, Calibración, Evolución BioData, Historia Paciente) — en vez de solo avisar visualmente. Cobertura de pesaje real y "No concluyente": MetabolicAdaptationService calcula el % de días con PesoMedidoEnUtc real de cada ventana; por debajo del 50 %, el badge de Nivel se sustituye por completo por "No concluyente" (se omite la gradación entera) en ambas pantallas y en el prompt HISTORICO_ADAPTACION, y no se dispara la recomendación de reactivación metabólica. Cubre las ventanas reconstruidas en su mayoría con valores heredados aunque no presenten racha idéntica ni R² bajo. El R² se calcula con una recta OLS independiente (siempre en [0,1]), mientras que ΔPeso usa la pendiente robusta Theil-Sen. Ventanas de ~90 días en la interpretación por IA: el prompt HISTORICO_ADAPTACION recibe, junto a la tabla de snapshots (ventanas de ~28-30 días), una segunda tabla con una serie semanal de ventanas de 90 días calculada al vuelo por MetabolicAdaptationService.BuildAsync (sin persistir), que incluye solo las ventanas con al menos 75 días con datos. La conclusión sobre presencia o ausencia de adaptación se apoya en esas ventanas; las de ~30 días son orientativas. Si no hay ninguna ventana de 90 días válida, la interpretación se limita a describir la evolución reciente y señala la falta de histórico suficiente. Las filas de ambas tablas se solapan, por lo que no se tratan como mediciones independientes.
    21. Índice de Eficiencia Locomotora (IEL), puramente diagnóstico (nunca calibrador): cociente entre la energía declarada por el wearable y la estimada por un modelo mecánico de la marcha (E = distancia × masa × 0,75 kcal/(kg·km) + pisos × masa × 0,15 kcal/(kg·piso)). Solo importa su tendencia en el tiempo: una deriva decreciente sostenida es la tercera línea de evidencia de adaptación metabólica activa (junto a la brecha TDEE_teórico/TDEE_real y la regresión OLS del peso) — el coste real de caminar baja tras pérdida de peso sostenida (Rosenbaum et al., 2008: −20–25% tras perder el 10% del peso). Se muestra como tarjeta diagnóstica en Adaptación Metabólica; ver el bullet siguiente para por qué ninguna vía indirecta (incluida esta) se usa para fijar el factor de Activa.
    22. Detección de eventos de readaptación — Refeed/Diet break (RefeedDietBreakDetectionService): escaneo retrospectivo y automático (cada noche, vía MetabolicSnapshotWorker) del balance energético diario, clasificando cada día en Déficit / Mantenimiento / Superávit agresivo según una banda ±10 % del TDEE teórico diario. Una Readaptación ligera (Refeed) es una racha de 1-2 días en banda de mantenimiento precedida de ≥5 días consecutivos en déficit; una Readaptación prolongada (Diet break) es una racha de 7-14 días precedida de ≥14 días consecutivos en déficit. Rachas de 3-6 días quedan deliberadamente sin clasificar. Cada evento persistido (RefeedDietBreakEvents) incluye una confirmación informativa (no excluyente) de si el paciente volvió a déficit después, y una marca ImpulsadoPorAlcohol: compara cuánto del salto real de balance (déficit previo → ventana del evento) explican el alcohol y los hidratos por separado, y advierte (sin excluir el evento) cuando el alcohol explica la mayoría del salto y los hidratos no — un refeed real busca reponer glucógeno/leptina vía hidratos, algo que el alcohol no hace. Es 100% retrospectivo: el sistema no distingue si el patrón fue planeado o casual, el efecto medido es el mismo en ambos casos.
    23. Conservación de la Flexibilidad Metabólica (FlexibilidadMetabolicaService): módulo complementario y conceptualmente separado de la Adaptación Metabólica — Adaptación mide si el cuerpo está ahorrando energía (TDEE teórico vs. real); Flexibilidad mide la probabilidad de conservar la capacidad de alternar entre sustratos energéticos, a partir de señales conductuales (no mide el cambio de sustrato directamente, requeriría calorimetría indirecta). Aplica el modelo jerárquico de referencia de Fenotipo: fuerza, cardio (BiodataDiaria.MinutosEjercicioGym/MinutosEjercicioExterior) y variabilidad de peso se comparan contra la propia media del paciente en los 90 días anteriores a la ventana, no contra recomendaciones poblacionales (OMS/ACSM) ni umbrales fijos; si no hay al menos 14 días de histórico previo, el criterio (o la etiqueta, en el caso de variabilidad de peso) se omite en vez de sustituirse por un valor poblacional por defecto. El criterio de déficit prolongado usa la adaptación ya medida por MetabolicAdaptationService (Nivel/residual), no un simple recuento de días — un paciente puede llevar semanas en déficit sin que su TDEE_real se resienta. Un criterio adicional ("Recuperación tras tu última readaptación") compara la adaptación medida antes/después del evento de readaptación más reciente ya concluido con ≥7 días de margen, verificando el efecto real en vez de asumir que el evento ya basta. Presenta un checklist de criterios binarios sin ponderar (enfoque tipo Apgar, puntuación X/Y variable, 3-6 según disponibilidad de datos), un panel de recomendaciones no prescriptivas con prioridad cualitativa (Alto/Medio, apoyada en la evidencia ya citada en Adaptación Metabólica) y una interpretación en lenguaje natural generada por reglas deterministas (sin llamada a IA) a partir de los mismos datos.
    24. Factor de actividad neutro: ActiveEnergyCorrectionFactor vale 1,00 y solo se ajusta con evidencia directa (calorimetría indirecta real). No se deriva de modelos indirectos (factores bibliográficos, estimación por frecuencia cardíaca o modelos mecánicos de la marcha), porque comparan la Energía Activa contra una referencia idealizada y no contra el dato objetivo, el peso real, y porque el sesgo del sensor no tiene una dirección universal. El Índice de Eficiencia Locomotora (IEL) se usa únicamente como señal diagnóstica en Adaptación Metabólica.
    25. Metodología canónica de calibración — 1:1:1 y prueba-error contra peso real: calibrar los tres factores (ingesta, basal, activa) a la vez es circular, porque la ingesta se deriva del gasto total, que ya incluye basal y activa con sus factores aplicados — cambiar cualquiera de los otros dos desplaza mecánicamente la ingesta recomendada sin evidencia nueva sobre ella. El procedimiento: fijar los tres factores en 1,00 (neutro), propagarlo con "Recalcular rango", calibrar solo la ingesta por MCO, aplicar y recalcular de nuevo, y validar el resultado comparando el Δ peso que implica el balance acumulado contra el Δ peso real, idealmente sobre días no usados en la propia calibración para evitar una coincidencia tautológica. Antes de aceptar Basal=1,00 como neutro se contrasta el basal del wearable con Mifflin-St Jeor y Katch-McArdle (con la grasa corporal medida por bioimpedancia): si ambas fórmulas coinciden entre sí y difieren claramente del basal del wearable, se ajusta el factor Basal de ese usuario. La desviación es individual y no se traslada a otros usuarios. El paso de validación está automatizado en la app (EnergyWeightValidationService, tarjeta "Validación: peso calculado vs. peso real" en el panel de Calibración).
    26. Sueño — duración, regularidad y despertar precoz: análisis al vuelo sobre el episodio principal de cada noche de BiodataDiaria (SleepAnalysisService), con las horas convertidas a la zona horaria del perfil y referenciadas a un punto de corte alejado de la medianoche para promediarlas sin saltos. La referencia principal es la basal a largo plazo del propio usuario (hasta 365 días previos al periodo y no antes del 1 de abril de 2026, mínimo 28 noches); las cifras poblacionales solo inicializan o contextualizan. La hora de despertar es el final del último tramo dormido registrado, no la de salir de la cama. Incluye duración media y porcentaje de noches con 7 h o más (AASM/SRS), desviación típica de la hora de acostarse, de despertar y de la duración (bandas del estudio MESA, mínimo 10 noches) y despertar precoz: noches que terminan al menos 90 min antes de la hora habitual de despertar, definida como el percentil 75 de las 28 noches anteriores. Para el profesional, cada noche precoz se compara con las noches normales a ±14 días en alcohol, kcal y ejercicio del día anterior y hora de acostarse, con mínimos de 5 pares y 3 noches cercanas por par, presentado como asociación y no como causa. Interpretación IA bajo demanda (prompt SUENO_ANALISIS) restringida a hallazgos que superan criterios explícitos frente a la basal propia, con un máximo de dos sugerencias de una lista cerrada, sin nombrar entidades clínicas ni recomendar productos.
    27. Dieta y Evolución — grupos de alimentos frente a la evolución de un parámetro: análisis exploratorio (DietaEvolucionService), calculado al abrir la pantalla y sin guardar nada, que contrasta por semanas lo que se come (desglose de platos de AlimentosDiaJson, asignado a 22 grupos mediante un diccionario común AlimentoGrupo sin datos personales) con el cambio, 1 a 4 semanas después, del peso, la grasa corporal, el HRV, la FC en reposo, el sueño o la recuperación. Usa la correlación parcial de Spearman descontando la tendencia del tiempo, corrige por comparaciones múltiples y marca como no evaluable lo que varía poco o tiene pocas semanas. Muestra coincidencias, no causas. Una pestaña de interpretación IA, bajo demanda, explica en lenguaje llano lo que destaca en la tabla mediante una plantilla versionada en BD (DIETA_EVOLUCION_ANALISIS), con aviso de contenido generado por IA.
    28. Copias de seguridad de la ingesta y del diccionario de alimentos: copia automática cada noche (IntakeBackupWorker, 03:30, a día vencido) de los datos que el usuario introduce a mano y no pueden releerse de exportaciones: el diccionario (IntakeFoodLibraryItem) y las columnas de ingesta de BiodataDiaria. Se guarda un JSON comprimido por usuario en un directorio del servidor fuera de la parte pública, con una cabecera y los factores de calibración con los que se calculó cada día. La pantalla avisa de los cambios en la base de datos desde la última copia y permite restaurar, con simulación previa, copia del estado anterior, periodo elegible y recálculo del balance energético con la calibración de origen. Sin «imagen exacta» la restauración nunca degrada: un dato vacío de la copia (macros a 0, sodio nulo o desglose de platos vacío) no borra uno que hoy existe. Retención de 30 días más la última de cada mes, y supresión al causar baja.
    29. Evolución del Peso — línea de fondo, ritmo y rango normal del día: cálculo al vuelo (WeightTrendService y WeightTrendAnalyzer) sobre las pesadas de los últimos 180 días de BiodataDiaria con hora de medición real (PesoMedidoEnUtc); los pesos arrastrados o estimados no cuentan. Un modelo de estado de tres componentes (nivel de fondo, pendiente y oscilación persistente de un día para otro) se ajusta a cada usuario con un filtro de Kalman (estimador recursivo que revisa su cálculo con cada pesada) y un suavizado posterior, de modo que los parámetros salen de las pesadas del propio usuario y no de tablas poblacionales. Resultado: peso de fondo, ritmo en kg por semana con su intervalo del 90 %, y el rango en que cae el peso de hoy el 90 % de los días. El estado es Bajando o Subiendo solo cuando la probabilidad de que el ritmo sea negativo (o positivo) alcanza el 90 %, Estable cuando todo el intervalo cabe en ±0,15 kg por semana con al menos tres semanas de datos, y sin conclusión en el resto. Un valor muy fuera de lo previsto se marca como inusual y no mueve la línea de fondo. Los textos de la pantalla están redactados en lenguaje llano; no hay cifras de proyección y nada se guarda en base de datos. Los textos animan a pesarse cada mañana y explican para qué sirve (separar el agua del cambio real, conocer cuánto varía el peso propio y detectar lecturas raras), sin reprochar los días sin pesada. Con pesadas espaciadas (menos de 14 días con pesada en 60, pero al menos 8 en 180 días repartidas en 6 semanas o más) se ajusta una regresión lineal simple (una recta peso-fecha) y se informa del cambio total y del ritmo medio con su intervalo del 90 %, sin estado ni rango del día. El punto de partida elegido por el usuario debe existir en BiodataDiaria (ese día, el anterior o el posterior) y solo se conserva en la sesión. Con más de 730 días de historia desde el inicio, el cálculo usa los últimos 730 días diarios y lo anterior se resume en medias semanales (semanas que empiezan en lunes) solo para el gráfico, donde la vista «desde el inicio» pasa a mostrar todo por semanas.
    30. Oxigenación nocturna — SDS/SAP como cribado de apnea del sueño: dos índices deliberadamente desacoplados (SleepOxygenationService). El SDS (Índice de Desaturación del Sueño) es puramente objetivo: z-score de la SpO₂ media nocturna (ventana de sueño real de cada día, con franja de respaldo 22:00-07:00) contra el baseline personal, con la dirección invertida respecto al resto de scores del sistema —aquí más alto es peor—, mínimo de 5 lecturas por noche y 7 noches de baseline. El SAP (Probabilidad de Apnea del Sueño) combina 50% SDS con 50% de una puntuación STOP-BANG simplificada de 7 factores (IMC, edad, ronquidos, apnea observada, hipertensión, somnolencia diurna, alcohol), sin pesos diferenciados por factor y con renormalización cuando faltan datos, marcando explícitamente como "cribado incompleto" cuando hay menos de 4 de los 7 disponibles. Las muestras de SpO₂ del reloj se leen de SaturacionOxigenoRaw (alimentada por la exportación automática y por la importación de Apple Health) y el cálculo se rehace en cada visita; un SDS "importante" exige un episodio sostenido de desaturación esa noche. Ninguna lectura se descarta: cada lectura baja se contrasta con la FC y la frecuencia respiratoria de ±15 min (FrecuenciaVitalRaw) frente a lo habitual del propio usuario en ese punto de la noche y la nota de la noche avisa de las que no responden o no pueden verificarse; en las noches poco fiables el SDS pesa un 20 % del SAP en vez de un 50 %. Presentado siempre como cribado orientativo, nunca como diagnóstico.
    31. Generador de Informes — exploración dinámica sin persistir diseño ni resultado: herramienta de auto-servicio (InformeDinamicoService) que une BiodataDiaria y EnergyDailyBalance por fecha, permitiendo elegir libremente entre ~40 campos agrupados en 6 temas clínicos (peso, actividad, cardio, sueño, nutrición, energía/balance) y una ventana temporal, sin necesidad de SQL ni desarrollo a medida por cada pregunta puntual. Ni la selección de campos ni el resultado se guardan en base de datos — la propia URL con querystring es la única "persistencia", reproducible o compartible reenviando el enlace. El orden por cualquier campo se resuelve por reflexión sobre un único catálogo, sin switch manual por variable, y el gráfico asigna cada serie a un eje izquierdo o derecho según su magnitud real en los datos del usuario, para que un campo de escala grande (p. ej. pasos) no aplaste visualmente a uno de escala pequeña (p. ej. IMC). El traslado a IA se resuelve copiando el informe en formato tabla al portapapeles en vez de construir una tubería de IA nueva, reutilizando el asistente conversacional ya existente.
    32. Correlación Termogénesis-Recuperación — convergencia entre dos baselines personales: el indicador de Adaptación metabólica arrastra un sesgo estructural (el basal teórico nunca acierta al milímetro el metabolismo real de cada individuo), pero ese sesgo es aproximadamente constante en el tiempo — el metabolismo basal es fisiológicamente estable y cambia despacio, como confirma el seguimiento a largo plazo de "The Biggest Loser" — así que el valor absoluto de Adaptación no es del todo fiable, pero sí su tendencia. CorrelacionTermogenesisService cruza el histórico ya persistido de Adaptación (ventanas de 28 días rodantes) con el de Recovery (diario, HRV/FC reposo/temperatura/sueño), promediando Recovery sobre la misma ventana de cada snapshot de Adaptación — ambos sistemas ya usan el mismo criterio de baseline personal, comparables por construcción sin normalización especial. No calcula ningún índice compuesto: marca convergencia negativa cuando Adaptación y Recovery empeoran a la vez respecto a la ventana anterior, con justificación fisiológica directa (la restricción calórica crónica se asocia a caída de HRV y subida de FC reposo en la literatura de RED-S/infra-alimentación) — dos señales independientes moviéndose juntas es una convergencia real, no una coincidencia estadística de métricas sin relación mecanística entre sí.
    33. Fiabilidad del HRV nocturno en el Panel de Recuperación: cada registro guarda también HrvMuestrasNocturnas (nº de muestras crudas promediadas esa noche dentro de la ventana de sueño), no solo el valor final, porque con Apple Watch lo habitual son muy pocas muestras por noche. El criterio es asimétrico y no descarta ningún valor: con pocas muestras (menos de 3) y un valor anormalmente bajo (por debajo de -1,5σ de la basal propia), si esa noche hubo vigilia nocturna ≥30 min el valor se sustituye por la mediana de la basal personal (HrvSustituido, marcado con asterisco en la vista) solo para este cálculo; el valor original de BiodataDiaria.HrvMs no se modifica nunca, así que ningún otro módulo (Biodatos diarios, exports...) hereda la sustitución. Si no hubo vigilia que lo explique, el valor se mantiene tal cual, porque un HRV bajo sin vigilia de por medio no es ruido de medición sino señal fisiológica (estrés, sobreentrenamiento...). Un valor anormalmente alto con pocas muestras se mantiene siempre, porque los artefactos de medición típicos del HRV deprimen el valor, no lo inflan. El modal del asterisco permite además comprobar la noche contra el export.xml de Apple Health del servidor: se lee en ese momento, se determina qué lecturas de HRV se tomaron dentro de un tramo de vigilia y el veredicto (HrvVerificacionNocturna) se aplica al recalcular el score — sin lecturas en vigilia el valor medido se usa tal cual; con al menos una, esas lecturas se descartan y el HRV de la noche es la media de las restantes (tras el filtro de valores implausibles frente al baseline propio), sin exigir que el valor original fuera anómalo.
    34. Variabilidad del HRV entre noches y fase de sueño: el valor de HRV depende de la fase de sueño en el instante de cada lectura (más alto en REM que en sueño profundo o en vigilia), de modo que con solo 1-2 muestras por noche el resultado depende de qué fase capturó el reloj. El JSON del pipeline automático (Health Auto Export) solo aporta totales de la noche por fase, no la fase activa en cada instante, por lo que el sistema no filtra por fase. Por la misma razón, la vigilia de la noche nunca se usa como criterio aislado: solo explica un valor que ya es estadísticamente anómalo por sí mismo (bullet anterior).
    35. Motor de propuesta de entrenamiento de fuerza — progresión con rotación anti-estancamiento y personalización sin tabla adicional: el motor semanal (PropuestaEntrenamientoService) no se limita a repetir el mismo ejercicio hasta que el usuario complete dos ejecuciones limpias seguidas (sin molestia, estado Completo) para subir de nivel en su cadena de progresión — si la última ejecución no fue limpia, rota automáticamente a otro ejercicio del mismo patrón de movimiento y nivel igual o inferior, priorizando el que lleve más tiempo sin proponerse, para que una molestia puntual o una sesión a medias no deje al usuario atascado semana tras semana en el mismo movimiento. Además, las series/repeticiones/descanso propuestos no copian siempre el default del catálogo de sistema: usan la última ejecución real de ese usuario para ese ejercicio concreto (ventana de 14 días) como propuesta, y el default del catálogo actúa solo de semilla la primera vez — la personalización se consigue leyendo el histórico ya persistido (EntrenamientoEjecucion), sin necesidad de una tabla de preferencias por usuario/ejercicio.
    36. Gimnasio real detectado por el reloj fuera del flujo guiado — Fallida vs. CompletadaExterna: una sesión del plan que vence sin hacerse en Entrenar se marca como Fallida, salvo que BiodataDiaria.MinutosEjercicioGym muestre un entrenamiento real ese día (p. ej. registrado con la app Entreno del Apple Watch). En ese caso queda como CompletadaExterna: cuenta como adherencia real (InspectorService) y sigue siendo recuperable con RetomarHoy igual que Fallida/Rotada. En ProgresoFuerzaService se reporta aparte (SesionesCompletadasExternamente, sin sumarse a SesionesCompletadas) pero entra en SesionesTotales y en DiasEntrenamiento (días con sesión completada, de reloj o con minutos de gimnasio), que alimentan la pantalla y la valoración IA como entrenamiento real. Las series, los niveles por patrón y los ejercicios solo salen de sesiones con EntrenamientoEjecucion real; el prompt ENTRENAMIENTO_VALORACION_FUERZA (v1.1.0) indica que su ausencia no implica inactividad. Como el wearable puede sincronizar tarde, las sesiones Fallida de la semana en curso se revisan de nuevo en cada llamada a MarcarPendientesVencidasComoFallidasAsync, que también se ejecuta al entrar en Progreso.
    37. Reconciliación de ejercicio al terminar la sesión: ReconciliacionEjercicioHealthKitService se ejecuta tras cada importación del wearable y también al final de EntrenamientoController.FinalizarSesion, de modo que BiodataDiaria.MinutosEjercicioGym refleja el entrenamiento guiado de hoy sin esperar al siguiente sync del reloj, y cualquier vista que lea ese campo en caliente (HistoriaPacienteService.MinGymHoy) lo ve de inmediato.
    38. Split 2-de-3: cada una de las 3 sesiones semanales cubre 2 de las 3 grandes zonas (empuje/tracción/pierna-cadera), rotando cuál se omite cada día, de modo que cada zona llega a las 2 veces/semana que exige la guía ACSM 2026 y queda algún día libre a 48 h de las sesiones que trabajan cada zona, lo que permite añadir sesiones extra. Como clonar una sesión reproduce su contenido exacto con independencia del calendario, RepetirSesion comprueba en el momento de la copia si el día elegido comparte PatronMovimiento con otra sesión a menos de 48 h y la bloquea si es así.
    39. Catálogo ampliable por JSON — exportación e importación con vista previa, todo o nada: el administrador exporta el catálogo completo (CatalogoEjercicioJsonService) y lo importa de vuelta en dos modos: «Solo textos» (explicación y alertas de ejercicios existentes) y «Completo» (además crea ejercicios nuevos y actualiza el resto de campos). La clave es el nombre del ejercicio, no el identificador, porque este difiere entre entornos; la variante base también se indica por nombre. Un campo ausente deja el valor actual. La misma lógica calcula la vista previa (antes y después por campo, avisos y errores) y la aplicación, de modo que lo previsto es lo que se guarda, y la escritura es una sola transacción: con algún error no se guarda nada. La explicación y las alertas admiten varias líneas, que se muestran como apartados con la etiqueta anterior a los dos puntos en negrita.
    40. Figuras SVG del movimiento — sembrado idempotente por ejercicio, sin tabla de assets binarios: cada ejercicio del catálogo puede tener un par de posturas SVG (inicio/fin) guardadas como texto en CatalogoEjercicioFigura, animadas por alternancia CSS pura de opacidad cada 700ms — sin JavaScript de temporización ni ficheros de imagen. El seeder de contenido (CatalogoEjercicioFiguraSeeder) es idempotente por ejercicio: solo rellena huecos donde aún no existe figura, nunca sobrescribe una ya asignada, lo que permite corregir una postura mal diseñada desde el CRUD (selector "reutilizar postura existente" que rellena los dos SVG desde el mismo catálogo de posturas del seeder) sin miedo a que un reseed posterior la revierta.
    41. Exclusión personal de ejercicios — genérica, no ligada a metadatos descriptivos: cada usuario puede marcar ejercicios del catálogo como excluidos de su propio plan semanal (UsuarioEjercicioExcluido: UsuarioId + CatalogoEjercicioId), sin afectar a otros usuarios, por cualquier motivo (falta de equipamiento, preferencia...). El campo descriptivo RequiereSuelo es un metadato visible en el CRUD, no un filtro del motor. El filtro se aplica en los tres puntos donde ElegirEjercicioParaPatron elige un ejercicio nuevo (primera vez, progresión, rotación anti-estancamiento); si excluir deja un patrón de movimiento sin ningún candidato, el motor usa la lista completa para no dejar un hueco esa semana, en vez de fallar o saltarse el patrón.
    42. Rutina fija de cuerpo completo — alternativa independiente con rutina personal por usuario: junto al motor adaptativo (PropuestaEntrenamientoService) existe una segunda opción, completamente aparte, para quien quiere una sesión a la medida del tiempo que tiene: RutinaFijaController + RutinaFijaService. No crea EntrenamientoSesion ni EntrenamientoEjecucion: es una guía en pantalla, sin cuenta atrás, que el usuario avanza a su ritmo con un clic; el entrenamiento real queda registrado por el reloj, igual que cualquier sesión "de reloj" (CompletadaExterna) del motor adaptativo. Cada usuario tiene su propia tabla de ejercicios (UsuarioRutinaFijaEjercicio), copiada la primera vez del banco inicial (RutinaFijaCuerpoCompleto) y editable: puede quitar ejercicios y añadir cualquiera del catálogo general. La fase de cada paso la marca el patrón de movimiento del catálogo (Calentamiento, Movilidad, Estiramiento); el resto son de fuerza y se reparten en cuatro bloques corporales. El usuario pide los minutos y la sesión se sortea por presupuesto de tiempo (series × trabajo + descanso de circuito de 20 s), con al menos un ejercicio de cada bloque y sin ejercicios excluidos en su perfil; una vista previa muestra la lista antes de empezar. Los ejercicios propios de esta rutina y sus pasos de calentamiento, movilidad y estiramiento forman parte del catálogo de sistema mediante RutinaFijaCatalogoAdicionalSeeder, idempotente por nombre, con sus figuras en CatalogoEjercicioFiguraSeeder.
    43. FC en reposo — mínimo real de la ventana de sueño: la FC en reposo de BiodataDiaria es el mínimo de las muestras crudas de pulso (HeartRate.Min) dentro de la ventana de sueño real de cada noche, la misma que se usa para el HRV nocturno, en los dos pipelines de Apple (ZIP y JSON). No se usa RestingHeartRate de Apple porque es una estimación que Apple recalcula a lo largo del día según acumula muestras de inactividad, de modo que el mismo día puede dar valores distintos según la hora de la consulta y no refleja la recuperación nocturna. El canal de Google Health Connect conserva el valor que entrega la plataforma.
    44. Captura cruda de SpO2, calibración y análisis por reposo/actividad: a diferencia del cribado de apnea (que lee export.xml bajo demanda sin persistir nada), la tabla SaturacionOxigenoRaw guarda cada muestra individual con su hora exacta, alimentada de forma independiente desde los dos pipelines de HealthKit (automatización de pago Health Auto Export y exportación nativa export.xml, esta última la vía más probable para la mayoría de usuarios reales al no exigir ninguna app adicional en iOS). Spo2Controller permite registrar pares (reloj, pulsioxímetro de referencia) y decidir cuándo validar viendo cuatro estadísticos de las diferencias (media/offset, mediana, desviación típica, MAE) más un gráfico de Bland-Altman (sesgo y límites de acuerdo al 95%) — sin asumir que el pulsioxímetro doméstico de referencia sea un patrón absoluto. "Validar y activar" crea un SpO2FactorCorreccion con su propia ventana de vigencia (se cierra la del anterior al validar uno nuevo, sin huecos ni solapes entre calibraciones sucesivas), que aplica un offset constante. La corrección sí llega a BiodataDiaria.SaturacionOxigeno: de forma automática en cada importación nueva (usa siempre el factor activo) y de forma manual para el histórico ya guardado (recalculado desde SaturacionOxigenoRaw con el factor vigente cada día concreto, nunca se dispara solo, idempotente). Spo2AnalysisService clasifica cada muestra como reposo o actividad reutilizando la ventana de sueño real ya persistida (mismo criterio que el HRV/FC nocturnos), calcula mediana e IQR por separado para cada contexto, y sitúa el día actual en un percentil 0-100 contra el propio historial de reposo del paciente. La vista de evolución no persiste ningún resultado: todo —incluidos el badge de última sincronización y una tercera serie de contraste con el valor guardado en BiodataDiaria— se recalcula al vuelo en cada carga, con descarga opcional del detalle diario en JSON generada en el propio navegador. Una pestaña de análisis IA, bajo demanda, interpreta la carga de desaturación nocturna (lecturas bajas, rachas seguidas y momento de la noche) frente a la basal propia del paciente y a su perfil, con las cifras calculadas antes por Spo2AnalysisService y sin nombrar enfermedades ni causas. La zona de SpO2 mostrada en los informes (SpO2Classifier) usa el corte de "Límite" ajustado por edad (≥65) e IMC (≥30), y solo la mantiene si el z-score personal frente al historial propio indica un descenso real.
    45. Objetivo Nutricional — kcal/macros personalizados: pantalla con NutritionTargetService, que calcula el kcal objetivo diario (TDEE calibrado ± ritmo de cambio de peso, campo PerfilPersonal.RitmoObjetivoKgSemana en kg/semana) y reparte los macros tomando la proporción real proteína/grasa/hidratos que el usuario registró en la última semana (gramos de tabla) y aplicándola al kcal objetivo. La referencia de proteína es ESPEN, la misma que en el resto de la app. IngestaCorreccionService contrasta la media de los últimos 5 días completos en dos ejes: cantidad total (contra el objetivo de kcal) y reparto de proteína (contra la referencia ESPEN) — siempre separando el alcohol de las kcal de comida (no es un macronutriente y puede enmascarar un déficit real de comida), y añadiendo una nota de compensación en hidratos para los días con alcohol registrado.
    46. Objetivo Nutricional — progreso del día y sugerencias: la pantalla se organiza en pestañas (Objetivo / Seguimiento / Alimentos / Estado Corporal / Evolución). IntakeDiaProgresoService lee en vivo los slots de ingesta de HOY (deserializa IntakePoolItem.NormalizedText, el mismo JSON que usa IntakeExecutionService al aplicar un slot, para los que ya tengan cálculo de IA aunque no estén "Aplicado" todavía) y muestra barras de progreso kcal/macros del día en curso junto al objetivo. SugerenciaAlimentosService cubre los TRES macros con hueco, usando la biblioteca personal (IntakeFoodLibraryItem) ordenada por densidad del macro buscado sobre sodio, y propone siempre una ración de referencia fija de 100 g por alimento, pensada como opciones combinables, nunca la cantidad exacta que cerraría el hueco con un solo alimento.
    47. Detalle energético — factores visibles: EnergyManager/Detail se organiza en pestañas (Macronutrientes / Calidad del día) y etiqueta el objetivo de proteína como ESPEN 2022, la misma referencia que usa Objetivo Nutricional. Ahí y en Index/Report, cada valor de Ingerida/Basal/Activa muestra el factor de calibración congelado ese día (EnergyDailyBalance.FactorIngesta/FactorBasal/FactorActivo) como superíndice entre corchetes ([factor]).
    48. Recálculo del balance y factor histórico: consultar el Panel Energético o el Informe no recalcula nada; los balances de días pasados, con su factor congelado, solo cambian mediante "Recalcular rango", una acción explícita que pide confirmar el rango y el factor, y que permite tratar por separado tramos con factores distintos. Adaptación y Flexibilidad Metabólica suman directamente EnergyDailyBalance.EnergiaIngeridaKcal de la ventana, calculada con el factor vigente cada día, de modo que una ventana que abarque un cambio de calibración respeta el factor de cada tramo.
    49. Estado Corporal — cuarta pestaña en Objetivo Nutricional: BodyStatusService clasifica la adiposidad con la grasa corporal por impedancia más reciente frente a una tabla de referencia por edad y sexo (equivalente a los cortes de IMC 18,5/25/30, proporcionada directamente para esta función), con el IMC estándar OMS como alternativa si falta la medición o el sexo no tiene banda propia. Muestra siempre por separado tres ejes de contexto — cardiovascular (tensión arterial media de 7 días y capacidad aeróbica —último VO₂ máx estimado por el reloj— frente a la tabla por edad y sexo de la app Salud, que se muestra completa —mujeres y hombres— en el detalle del cálculo), metabólico-analítico (última analítica clínica, descartada si tiene más de 6 meses) y recuperación (HRV medio de 7 días frente a la línea base personal del usuario) — y combina la adiposidad con el nº de ejes alterados en una categoría final, incluyendo dos nombres propios de Fenotipo para combinaciones con respaldo clínico real ("Obesidad metabólicamente sana", "Normopeso con riesgo metabólico asociado"), siempre etiquetados como modelo propio, no clasificación oficial. Cuando la adiposidad es Obesidad se añade el grado I/II/III de la OMS (IMC 30-34,9/35-39,9/≥40), calculado siempre sobre el IMC aunque la categoría venga de la grasa medida, ya que la tabla de bandas por edad/sexo no distingue gravedad dentro de "Obesidad". Distinto en propósito y criterio de la "lectura corporal" del Panel de Salud (HealthDash) (basada en IMC, no en grasa medida). Una quinta pestaña, Evolución, reutiliza el mismo cálculo día a día (todos los datos del rango cargados una sola vez, sin repetir consultas por día) para un periodo Desde/Hasta elegido por el usuario (3 meses por defecto), agrupando los días consecutivos con la misma categoría (incluido el grado de obesidad y los ejes concretos alterados) en una línea de tiempo de segmentos coloreados.
    50. Capacidad Física — segunda categorización en Estado Corporal: ICapacidadFisicaService, independiente de la adiposidad. Compara los minutos de gimnasio/exterior de los últimos 14 días contra la guía de actividad física OMS 2020 (fuerza ≥2 días/semana de ≥20min, aeróbico ≥150 min/semana) para dar "Activo"/"Parcialmente activo"/"Sedentario", y añade tres ejes de contexto —energía basal calibrada (reflejo de NEAT), minutos de ejercicio totales y capacidad cardiorrespiratoria (último VO₂ máx, estimado por el reloj, con su nivel frente a la población de la misma edad y sexo como información añadida)— comparados con la propia línea base de 90 días del usuario vía PersonalBaselineZScore, resaltando cuando la tendencia personal contradice la etiqueta OMS (p. ej. "Sedentario según la guía OMS, aunque por encima de tu propia media reciente"). Los minutos de gimnasio se toman sin distinguir si vienen del entrenamiento guiado o de la rutina fija libre. La pestaña Evolución incluye una segunda línea de tiempo independiente para esta categorización, con el mismo mecanismo de carga única de datos.

    9. Autoría y titularidad

    Autor: Ricardo Manuel Trigo Calonge

    La presente obra incluye código fuente, arquitectura de software, modelo funcional y documentación técnica asociada.