Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JuanSebasSV/healtyhelp/llms.txt

Use this file to discover all available pages before exploring further.

Personalized Nutrition Recommendations

HealtyHelp includes a server-side recommendation engine (motorRecomendaciones.js) that analyses each user’s health profile, body metrics, and the last 30 days of consumption history to produce real-time, personalised nutritional and exercise guidance.

What the Engine Uses

The engine reads only two fields from healthProfile:
FieldDescription
healthProfile.condicionesActive health conditions / dietary patterns
healthProfile.categoriasActive meal-time categories (desayuno, almuerzo, etc.)
healthProfile.alergias and healthProfile.preferencias are not read by the recommendation engine. Those fields are only used by NutriBot’s recipe filtering. Allergy information never influences the recommendation score.
Body metrics used:
User fieldDescription
weightWeight in kg
heightHeight in cm
birthDateDate of birth (for age and BMR calculation)

TDEE Calculation (Mifflin-St Jeor)

The engine calculates a gender-neutral Total Daily Energy Expenditure using a modified Mifflin-St Jeor formula with a gender-neutral BMR offset:
// Offset -78 is the mean of the male (+5) and female (-161) constants
const bmr = 10 * weight + 6.25 * height - 5 * age - 78;

Age Correction for Sarcopenia

After age 40, the BMR drops approximately 2% per decade due to muscle mass loss:
const factorEdad = age > 60 ? 0.93 : age > 40 ? 0.97 : 1.0;

Activity Factor from BMI

Because HealtyHelp does not store a user-reported activity level, the engine infers one from the BMI category as a statistical proxy for sedentary behaviour:
const FACTOR_ACTIVIDAD_IMC = {
  bajo_peso:  1.45,
  normal:     1.55,
  sobrepeso:  1.48,
  obesidad:   1.375,
};
Final TDEE:
const tdee = Math.round(bmr * activityFactor * ageFactor);

BMI Categories

function calcularIMC(weight, height) {
  const m   = height / 100;
  const val = weight / (m * m);
  // bajo_peso < 18.5 | normal < 25 | sobrepeso < 30 | obesidad >= 30
}
The BMI categoria string drives the activity factor, hydration adjustments, exercise recommendations, and several alert messages.

Health Condition Limits (LIMITES_CONDICION)

The engine enforces per-day nutritional ceilings for each health condition. When today’s accumulated intake exceeds a limit, the engine adds a real-time alert to alertasHoy.
ConditionNutrientDaily Limit
diabetesazucar≤ 25 g
diabetescarb≤ 130 g
hipertensionsodio≤ 1 500 mg
bajo-sodiosodio≤ 1 500 mg
enfermedad-renalsodio≤ 1 500 mg
enfermedad-renalpotasio≤ 2 000 mg
enfermedad-renalfosforo≤ 800 mg
colesterol-altograsSat≤ 15 g
colesterol-altocolesterol≤ 200 mg
ketocarbNetos≤ 50 g
bajo-carbohidratoscarb≤ 100 g
sin-azucarazucar≤ 10 g
bajo-grasagras≤ 50 g
The sodium limit is also dynamically adjusted based on age even without a specific condition:
  • Conditions hipertension, enfermedad-renal, or bajo-sodio1 500 mg
  • Age ≥ 50 (no cardiovascular condition) → 1 800 mg
  • All other adults → 2 300 mg (WHO general guideline)

Macro Objectives by Condition (OBJETIVOS_MACRO)

These percentage targets are used to evaluate the macro split in alertasHoy when the user has consumed more than 30 % of their TDEE:
ConditionProtein %Carb %Fat %
default20 %50 %30 %
keto20 %5 %75 %
bajo-carbohidratos30 %20 %50 %
bajo-grasa30 %55 %15 %
paleo30 %25 %45 %
vegano18 %55 %27 %
vegetariano18 %52 %30 %
colesterol-alto22 %50 %28 %
diabetes20 %45 %35 %

30-Day Consumption History

The engine fetches all Consumo documents for the authenticated user where fechaBogota >= today - 30 days. From this history it computes:
  • Daily averages for cal, prot, sodio, fiber, carb, gras, azucar (via promedioNutri)
  • Skipped meal detection — if a meal type (desayuno, almuerzo, cena) appears in fewer than 50 % of logged days over the period, it is flagged as a comidasSaltadas entry
function detectarComidasSaltadas(consumos) {
  const fechas = [...new Set(consumos.map(c => c.fechaBogota))];
  if (fechas.length < 3) return [];   // requires at least 3 days of data
  return ['desayuno', 'almuerzo', 'cena'].reduce((acc, tipo) => {
    const pct = fechas.filter(f =>
      consumos.some(c => c.fechaBogota === f && c.tipo === tipo)
    ).length / fechas.length;
    if (pct < 0.5) acc.push({ tipo, porcentaje: Math.round(pct * 100) });
    return acc;
  }, []);
}

Timezone Handling

Like the consumption controller, the recommendations route uses Bogotá UTC−5 for all date comparisons:
const offsetMs   = -5 * 60 * 60 * 1000;
const bogota     = new Date(ahora.getTime() + offsetMs);
const horaActual = bogota.getUTCHours();  // 0–23, drives contextual meal tips
const fechaHoy   = bogota.toISOString().split('T')[0];
const hace30Dias = new Date(bogota.getTime() - 30 * 24 * 60 * 60 * 1000);
horaActual is passed into generarRecomendaciones to produce time-sensitive contextoHorario messages (e.g. late-night eating warnings, skipped breakfast alerts).

API Endpoints

Get Recommendations

GET /api/recomendaciones
Authorization: Bearer <token>
The server fetches the user’s body metrics and healthProfile.condiciones / healthProfile.categorias, queries the last 30 days of Consumo records and today’s records in parallel, then calls generarRecomendaciones. Response structure:
{
  "ok": true,
  "nombre": "María",
  "recomendaciones": {
    "imc": { "valor": 24.1, "categoria": "normal" },
    "caloriasObjetivo": 1890,
    "caloriasHoy": 1420,
    "caloriasRestantes": 470,
    "progresoHoy": 75,
    "alertas": [...],
    "alertasHoy": [...],
    "contextoHorario": [...],
    "alimentacion": [...],
    "ejercicio": [...],
    "ejercicioHoy": [...],
    "hidratacion": { "litros": 2.1, "vasos": 8, "nota": "..." },
    "comidasSaltadas": [...],
    "condicionesDetectadas": ["diabetes"],
    "categoriasActivas": ["desayuno"],
    "nutriPromedio": {
      "calPromedio": 1750, "protPromedio": 62,
      "carbPromedio": 210, "grasaPromedio": 58,
      "sodioPromedio": 1800, "fibraPromedio": 18
    },
    "macrosHoy": { "carbPct": 48, "protPct": 22, "grasPct": 30 }
  }
}

Get Recommendation Filters

GET /api/recomendaciones/filtros
Authorization: Bearer <token>
Returns only condiciones and categorias — not allergies or preferences, which are irrelevant to the recommendation engine:
{
  "condiciones": ["diabetes", "hipertension"],
  "categorias":  ["desayuno"]
}

The PanelRecomendaciones Component

The front-end panel (PanelRecomendaciones.jsx) is embedded in the tracking view and re-fetches from /api/recomendaciones whenever its versionFiltros prop increments. This prop is bumped by the parent component whenever the user changes their health condition filters on the home page — ensuring the panel stays in sync without polling. The panel renders these sections:
SectionSource field
IMC badge + calorie targetimc, caloriasObjetivo
Caloric progress bar todaycaloriasHoy, progresoHoy
Macro split bars todaymacrosHoy
Real-time contextual tipscontextoHorario
General profile alertsalertas
Today’s micronutrient alertsalertasHoy
Suggested activity for todayejercicioHoy
Hydration goalhidratacion
Skipped meal warningscomidasSaltadas
Nutrition tipsalimentacion
General exercise guidanceejercicio
All recommendation text is generated purely server-side. The client panel is a display layer only — it never applies local filtering logic to the recommendation data.

Build docs developers (and LLMs) love