Qué vamos a construir
Un contador de calorías que permite a los usuarios buscar alimentos, registrar comidas y ver sus totales nutricionales diarios. Usaremos JavaScript puro y la API de FoodBase para que todo sea simple y no dependa de ningún framework.
Al final de este tutorial tendrás una app funcional que:
- Busca en nuestra base de datos de 6,1 M de productos alimentarios
- Muestra la información nutricional por porción
- Lleva el total diario de calorías y macros
Requisitos previos
- Una cuenta de FoodBase (regístrate gratis)
- Una clave de API del panel
- Conocimientos básicos de JavaScript
Paso 1: configura el cliente de la API
Crea una función reutilizable para llamar a la API:
const API_KEY = "food_your_key_here";
const BASE_URL = "https://foodbase.dev/v1";
async function foodApi(path) {
const res = await fetch(`${BASE_URL}${path}`, {
headers: { "X-API-Key": API_KEY },
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || `HTTP ${res.status}`);
}
return res.json();
}
Paso 2: busca alimentos
Deja que los usuarios busquen por nombre. La API devuelve los resultados ordenados por relevancia:
async function searchFoods(query, limit = 10) {
const params = new URLSearchParams({ q: query, limit });
const data = await foodApi(`/foods/search?${params}`);
return data.data; // array of food items
}
// Usage
const results = await searchFoods("chicken breast");
console.log(`Found ${results.length} results`);
results.forEach(food => {
console.log(`${food.name_default} (${food.brand || "Generic"})`);
});
Paso 3: obtén el detalle nutricional
Cuando el usuario elige un alimento, pide sus detalles completos, incluido el resumen nutricional:
async function getFoodWithNutrition(foodId) {
return foodApi(`/foods/${foodId}`);
}
const food = await getFoodWithNutrition("abc-123");
console.log(`${food.name_default}: ${food.nutrition_summary.energy_kcal} kcal/100g`);
El nutrition_summary incluye los 6 macros más comunes: energy_kcal, proteins_g, carbs_g, fat_g, sugars_g y fiber_g.
Paso 4: calcula por porción
La API devuelve todos los valores por 100 g. Para calcular por porción, multiplica por el peso de la porción:
function calculateServing(nutritionPer100g, servingGrams) {
if (!nutritionPer100g) return null;
const factor = servingGrams / 100;
return {
calories: Math.round((nutritionPer100g.energy_kcal || 0) * factor),
protein: round1((nutritionPer100g.proteins_g || 0) * factor),
carbs: round1((nutritionPer100g.carbs_g || 0) * factor),
fat: round1((nutritionPer100g.fat_g || 0) * factor),
fiber: round1((nutritionPer100g.fiber_g || 0) * factor),
};
}
function round1(n) {
return Math.round(n * 10) / 10;
}
// Example: 100g chicken breast
const serving = calculateServing(food.nutrition_summary, 150);
// → { calories: 248, protein: 46.5, carbs: 0, fat: 5.3, fiber: 0 }
Paso 5: registra las comidas del día
Guarda las comidas registradas y calcula los totales acumulados:
const meals = [];
function addMeal(food, servingGrams) {
const nutrition = calculateServing(food.nutrition_summary, servingGrams);
meals.push({
name: food.name_default,
brand: food.brand,
grams: servingGrams,
...nutrition,
addedAt: new Date(),
});
return nutrition;
}
function getDailyTotals() {
return meals.reduce(
(totals, meal) => ({
calories: totals.calories + (meal.calories || 0),
protein: round1(totals.protein + (meal.protein || 0)),
carbs: round1(totals.carbs + (meal.carbs || 0)),
fat: round1(totals.fat + (meal.fat || 0)),
fiber: round1(totals.fiber + (meal.fiber || 0)),
}),
{ calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0 },
);
}
// Log a meal
addMeal(food, 150);
console.log(getDailyTotals());
// → { calories: 248, protein: 46.5, carbs: 0, fat: 5.3, fiber: 0 }
Paso 6: añade la lectura de códigos de barras
Si tu app tiene acceso a la cámara, puedes escanear códigos de barras y consultar productos al instante:
async function lookupBarcode(barcode) {
try {
return await foodApi(`/foods/barcode/${barcode}`);
} catch (err) {
if (err.message.includes("Not found")) {
return null; // Product not in database
}
throw err;
}
}
// After scanning a barcode with a camera library
const product = await lookupBarcode("5449000000996");
if (product) {
console.log(`Found: ${product.name_default} by ${product.brand}`);
// → "Found: Original Taste by Coca-Cola"
}
Paso 7: guarda los datos
En una app real querrás conservar el historial de comidas. Esta es una forma sencilla con localStorage:
function saveMeals() {
localStorage.setItem("meals", JSON.stringify(meals));
localStorage.setItem("mealsDate", new Date().toISOString().slice(0, 10));
}
function loadMeals() {
const today = new Date().toISOString().slice(0, 10);
const savedDate = localStorage.getItem("mealsDate");
if (savedDate === today) {
const saved = localStorage.getItem("meals");
return saved ? JSON.parse(saved) : [];
}
// New day — reset
return [];
}
Consejos para apps en producción
- Guarda en caché los resultados de búsqueda en el cliente para reducir las llamadas a la API cuando los usuarios repiten las mismas búsquedas
- Usa el endpoint bulk al cargar el historial de comidas — obtén todos los detalles de los alimentos en una sola petición en lugar de N llamadas individuales
- Define objetivos diarios y muestra barras de progreso (objetivos habituales: 2000 kcal, 50 g de proteína, 25 g de fibra)
- Gestiona bien el modo sin conexión — guarda en caché los alimentos vistos recientemente para que la app funcione sin red
- Controla tu consumo en el panel para no pasarte de los límites de tu plan
Extra: registra comidas a partir de una foto
Si tienes créditos de IA para gastar (todos los planes incluyen algunos), el endpoint de comidas convierte la foto de un plato en elementos registrados con estimaciones de porción — una forma mucho más cómoda de registrar que buscar a mano:
async function analyzeMealPhoto(file) {
const form = new FormData();
form.append("image", file);
const res = await fetch("https://foodbase.dev/v1/vision/meal", {
method: "POST",
headers: { "X-API-Key": API_KEY },
body: form,
});
if (!res.ok) throw new Error(`Vision error: ${res.status}`);
const meal = await res.json();
// meal.items: [{ dish, portion_g_est, match: { estimated_kcal, ... } }]
// meal.estimated_total_kcal: number | null
return meal;
}
Los tamaños de porción son estimaciones de la IA a partir de una sola foto, así que trata los números como un punto de partida que el usuario pueda ajustar — la respuesta incluye una cadena disclaimer que debes mostrar. Pruébalo primero en el playground de fotos.
Próximos pasos
- Añade una función de planificación de comidas con alimentos guardados y macros objetivo
- Integra la API de un rastreador de actividad para tener en cuenta el ejercicio
- Crea una calculadora de recetas que sume los nutrientes de varios ingredientes
- Usa el endpoint de nutrientes detallados para el seguimiento de micronutrientes (vitaminas, minerales)
Sigue leyendo
Constrúyelo con FoodBase
6,1 M de productos, más de 40 nutrientes y análisis de alimentos con IA — gratis para empezar, sin tarjeta de crédito.