// MacroPlanner.js // Node.js script skeleton for meal planning + nutritional analysis pipeline using USDA FoodData Central const fs = require('fs'); const xlsx = require('xlsx'); const axios = require('axios'); require('dotenv').config(); // === CONFIG === const EXCEL_FILE = 'plantilla_plan_comidas.xlsx'; const SHEET_NAME = 'Semana 1'; const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const USDA_API_KEY = process.env.USDA_API_KEY; // === STEP 1: Load Excel and parse meals === function parseMealsFromExcel(filePath, sheetName) { const workbook = xlsx.readFile(filePath); const sheet = workbook.Sheets[sheetName]; const json = xlsx.utils.sheet_to_json(sheet, { defval: '' }); return json.map(row => ({ dia: row['Día'], desayuno: row['Desayuno'], comida: row['Comida'], cena: row['Cena'] })); } // === STEP 2: Call ChatGPT to extract ingredients === async function getIngredientsFromMeal(description) { const prompt = `Extrae los ingredientes clave con cantidades aproximadas de esta comida: "${description}". Devuélvelo como lista en JSON con nombre y cantidad.`; const response = await axios.post('https://api.openai.com/v1/chat/completions', { model: 'gpt-4', messages: [{ role: 'user', content: prompt }], temperature: 0.2 }, { headers: { 'Authorization': `Bearer ${OPENAI_API_KEY}` } }); const text = response.data.choices[0].message.content; return JSON.parse(text); } // === STEP 3: Use USDA API to get nutrient info === async function getNutritionFromIngredients(ingredients) { let totals = { protein: 0, carbs: 0, fat: 0 }; for (const item of ingredients) { try { const searchRes = await axios.get(`https://api.nal.usda.gov/fdc/v1/foods/search`, { params: { api_key: USDA_API_KEY, query: item.nombre, pageSize: 1 } }); const food = searchRes.data.foods[0]; if (!food || !food.fdcId) continue; const foodRes = await axios.get(`https://api.nal.usda.gov/fdc/v1/food/${food.fdcId}`, { params: { api_key: USDA_API_KEY } }); for (const nutrient of foodRes.data.foodNutrients) { if (nutrient.nutrientName === 'Protein') totals.protein += nutrient.value; if (nutrient.nutrientName === 'Carbohydrate, by difference') totals.carbs += nutrient.value; if (nutrient.nutrientName === 'Total lipid (fat)') totals.fat += nutrient.value; } } catch (error) { console.warn(`Error retrieving data for ingredient: ${item.nombre}`, error.message); } } return totals; } // === STEP 4: Combine and write back to Excel === async function processAndWriteBack() { const meals = parseMealsFromExcel(EXCEL_FILE, SHEET_NAME); const updatedMeals = []; for (const entry of meals) { const daySummary = { ...entry, protein: 0, carbs: 0, fat: 0 }; for (const meal of ['desayuno', 'comida', 'cena']) { if (!entry[meal]) continue; try { const ingredients = await getIngredientsFromMeal(entry[meal]); const macros = await getNutritionFromIngredients(ingredients); daySummary.protein += macros.protein; daySummary.carbs += macros.carbs; daySummary.fat += macros.fat; } catch (err) { console.error(`Error processing ${entry.dia} - ${meal}:`, err); } } updatedMeals.push({ 'Día': daySummary.dia, 'Desayuno': entry.desayuno, 'Comida': entry.comida, 'Cena': entry.cena, 'Proteínas (g)': daySummary.protein.toFixed(1), 'Carbohidratos (g)': daySummary.carbs.toFixed(1), 'Grasas (g)': daySummary.fat.toFixed(1) }); } const newSheet = xlsx.utils.json_to_sheet(updatedMeals); const newBook = xlsx.utils.book_new(); xlsx.utils.book_append_sheet(newBook, newSheet, SHEET_NAME); xlsx.writeFile(newBook, 'resultado_macros.xlsx'); console.log('Macros calculados y archivo actualizado: resultado_macros.xlsx'); } processAndWriteBack();