Los pasos, la distancia, las calorías, la frecuencia cardíaca y el sueño deben extraerse de la plataforma del usuario (Android Health Connect en Android, Apple HealthKit en iOS) mediante una aplicación de salud y nutrición, y presentarse como un perfil diario único, limpio y deduplicado. Existen muy pocas similitudes entre las dos APIs: mecanismos de lectura separados, diferentes esquemas de autorización y diferentes estructuras de datos. Construimos la capa de sincronización móvil que las hace parecer una sola.
El Desafío
- Dos APIs nativas que no concuerdan en nada. Apple HealthKit devuelve agregados diarios a través de callbacks; Health Connect devuelve muestras brutas y paginadas con metadatos de origen enriquecidos. Mismos conceptos, formas completamente diferentes, y la aplicación tuvo que normalizar ambos en un solo esquema.
- El doble conteo es el predeterminado. Un teléfono, un smartwatch y una aplicación de terceros pueden reportar los mismos 8,000 pasos. Sumarlos ingenuamente infla cada métrica. La aplicación necesitaba reconocer fuentes superpuestas y contar cada actividad del mundo real solo una vez.
- La batería y la red no pueden pagar la frescura. Los datos de salud cambian todo el día, pero leer el historial completo en cada sincronización agotaría la batería y saturaría las conexiones celulares. La cadencia de sincronización tenía que ser ligera sin dejar de sentirse en vivo.
Los permisos pueden ser confusos. Decenas de tipos de datos, dos modelos de permisos, restricciones de lectura en segundo plano y usuarios que otorgan algunos alcances pero no otros, todo lo cual la aplicación debe manejar sin bloquear el flujo.
Nuestra Solución
Construimos una sincronización de tres capas: una capa nativa delgada lee la API de cada plataforma, una capa de normalización en el dispositivo deduplica y consolida los datos en resúmenes diarios, y el backend los almacena y concilia en un único libro mayor confiable. El cliente nunca envía muestras brutas, sino resúmenes diarios limpios y correctos según la zona horaria.

Arquitectura
- iOS lee Apple HealthKit a través de react-native-health; Android lee Health Connect a través de react-native-health-connect.
- La normalización en el dispositivo transforma la salida de ambas plataformas en un solo esquema y deduplica antes de que algo salga del teléfono.
- Disparadores de sincronización — 30 días de historial en el primer lanzamiento, luego lecturas incrementales ligeras de 1 día en un intervalo de 10 minutos y en cada reanudación de la aplicación.
- Transporte — los resúmenes diarios normalizados se envían mediante POST a /user-health/system-activity del servidor principal, que los reenvía (con ID de usuario + zona horaria) al microservicio de salud.
- Persistencia — cada fuente/día se almacena de forma idempotente como un HealthSystemAggregate; un MongoDB change stream luego lo concilia en un HealthLedger por día.
Flujo de trabajo gestionado de Expo / React Native con módulos nativos de HealthKit y Health Connect.
Características Clave
- Un contrato de normalización para dos APIs. Los lectores específicos de la plataforma alimentan un único paso de formatHealthData que emite la misma forma independientemente de la fuente, por lo que el backend y la UI nunca se ramifican entre iOS y Android.
2. Deduplicación basada en el máximo en el dispositivo. Dentro de cada fuente, se suman las lecturas de un día; luego, el valor diario es el Math.max() entre fuentes (calorías claveadas por source|deviceType), de modo que múltiples lecturas de un dispositivo se acumulan mientras que un reloj y un teléfono que reportan el mismo día no se duplican:
// Sum readings within each source, then take the max ACROSS sources dateSourceMap[date][source] += entry.count; const totalSteps = Math.ceil(Math.max(...Object.values(dateSourceMap[date]))); |
3.La estrategia de lectura adecuada por plataforma. Las lecturas de Health Connect están paginadas con un bucle de pageToken (1,000 registros/página); los agregados diarios de HealthKit se recorren día a día. Cada peculiaridad está contenida en su propio lector, invisible para el resto de la aplicación.
4. Una cadencia de sincronización que respeta la batería. Un relleno completo de 30 días se ejecuta solo una vez (protegido por una bandera de primera llamada); después de eso, cada sincronización extrae solo un día: rápido en celular, económico en energía.
5. Agrupación diaria correcta según la zona horaria. Las muestras se agrupan en claves YYYY-MM-DD en la propia zona horaria del usuario, de modo que un entrenamiento a las 11 PM cae en el día correcto, sin importar dónde se encuentre el servidor.
6. Escrituras idempotentes en el backend. Cada resumen realiza un upsert en (userId, deviceId, source, date) — reenviar el mismo día es una operación nula, lo que hace que los reintentos y las sincronizaciones superpuestas sean seguras.
7. Degradación honesta. Los permisos faltantes, las lecturas vacías y los errores de límite de tasa de Health Connect se detectan y se presentan de forma limpia: un día vacío nunca bloquea la sincronización, y el usuario recibe un mensaje claro en lugar de un fallo silencioso.
Resultados
- Un perfil de salud único y consistente en iOS y Android: la diferencia de plataforma es invisible para el resto de la aplicación.
- Las fuentes de dispositivos/aplicaciones superpuestas se deduplican, de modo que los pasos y las calorías reflejan la realidad en lugar de sumas infladas.
- Las sincronizaciones incrementales ligeras mantienen los datos actualizados sin agotar la batería ni consumir datos móviles.
Las escrituras idempotentes y correctas según la zona horaria significan que cada métrica se registra en el día correcto, y los reintentos nunca corrompen el registro.
Pila Tecnológica
React Native (Expo) · react-native-health (HealthKit) · react-native-health-connect · NestJS · MongoDB · MongoDB Change Streams · moment-timezone

