馃搳 Obsidian: Nutrici贸n

Oscar L贸pez | Mar 28, 2024

A pesar de que no soy una persona fit, este a帽o me he propuesto mejorar mi salud. Esto me ha llevado a prestar mucha m谩s atenci贸n a la informaci贸n nutricional de mis comidas y tener un mayor control sobre mi ingesta diaria. Sin embargo, como todo desarrollador, no quiero pasar demasiado tiempo realizando estas tareas de forma constante. Por ello, he desarrollado un sistema que me permite registrar las comidas a lo largo del d铆a en combinaci贸n con mi journal.

馃摀 Journal

En posts anteriores he mencionado que mantengo un journal donde registro mucha informaci贸n acerca de mi d铆a, incluyendo datos que me ayudan a realizar un seguimiento de ciertos aspectos de mi vida, como mis proyectos o conocimientos adquiridos. Adem谩s de estos datos, tambi茅n registro las comidas que consumo a lo largo del d铆a. Al principio, lo hac铆a para diversificar mi alimentaci贸n al vivir solo y prepararme siempre lo mismo, pero ahora lo hago con el objetivo de tener un mayor control sobre mis h谩bitos alimenticios.

Para comprender mejor lo que he logrado, voy a mostrar el resultado final de mi sistema. Mi journal est谩 en Obsidian, y esta ser铆a la parte donde visualizo la informaci贸n sobre mis comidas:

Renderizado mi lista de comidas

Como se puede observar, he dividido mi d铆a en 4 secciones, correspondientes a mis 3 comidas principales y los snacks o comidas intermedias. Dentro de cada secci贸n, enlazo a la nota de la comida correspondiente.

馃嵔 Estructura de la comida

La estructura de las notas de comida sigue una plantilla definida, utilizando el motor de plantillas nativo de Obsidian. La secci贸n de informaci贸n nutricional es de especial inter茅s, adem谩s de los tags que utilizo para identificar las notas de tipo receta:

---
tag: receta
vegetariana: true
pescado: true
tipo_comida: 
- desayuno
dificultad_elaboracion: 5
---

# 馃カ {{title}} 
## 馃搳 Informaci贸n nutrimental 
[Calorias:: 0 kcal] 
[Proteinas:: 0 g] 
[Grasas:: 0 g] 
[Carbohidratos:: 0 g] 
[Fibra:: 0 g] 
[Azucares:: 0 g]
## 馃搵 Ingredientes 
- 

## 馃鈥嶐煃 Receta 
1. 

Cabe destacar que para mi caso de uso, no requiero much铆sima presici贸n, entonces los valores los obtengo con IA, le describo la receta y porciones a ChatGPT y me da una muy buena estimaci贸n de la informaci贸n nutrimental de la comida.

馃嵔 Comidas de restaurantes

En ocasiones, consumo comidas de restaurantes. Estas notas se diferencian de las recetas caseras, pero tambi茅n incluyen una tabla de informaci贸n nutricional.

---
calificacion: 5
tags:
  - platillo
---

# 馃惀 Nugget de [[McDonalds]]

## 馃搳 Informaci贸n nutrimental 
[Calorias:: 50 kcal] 
[Proteinas:: 2 g] 
[Grasas:: 2 g] 
[Carbohidratos:: 3 g] 
[Fibra:: 0 g] 
[Azucares:: 1 g]

De nuevo, el tag me permite diferenciar que la nota es de una comida, aunque en esta ocasi贸n una comida de restaurante, aunque para el sistema para calcular la informaci贸n nutrimental no importa mucho, tambi茅n tiene una propiedad de calificaci贸n, esa propiedad la utilizo para registrar las comidas que ped铆 un restaurantes, al volver a ir al restaurante puedo ver qu茅 me pareci贸 la vez pasada esa comida, para saber si deber铆a volver a pedirla o no, eso no viene al tema de hoy, pero comentenme si les gustar铆a que comentara la forma en la que recopilo mis recomendaciones de las comidas de cada restaurante.

馃搳 Reporte de calor铆as

Al final del d铆a, utilizo Dataview, un plugin de Obsidian, que me permite realizar consultas a mis notas de forma similar a SQL para visualizar la informaci贸n recopilada:

Renderizado del reporte de calor铆as y otras macros

La tabla muestra cada comida del d铆a y su informaci贸n nutricional, as铆 como una tabla de totales. Es importante destacar que la informaci贸n en la tabla refleja la cantidad de comida consumida, es decir, la cantidad de cada unidad multiplicada por el n煤mero de unidades.

Para mi, fue un poco complejo lograr la l贸gica que quer铆a utilizando DQL (el lenguaje de consulta de Dataview) entonces aprovech茅 que el plugin nos permite hacer consultas y tableros utilizando Javascript.

const todayMeals = dv.pages('outgoing([[#]]) and (#receta or #platillo)');
const exercises = dv.pages('outgoing([[#]]) and #tipo_ejercicio');

const numExercises = dv.current().file.lists.text.array().findLastIndex(text => text.includes('Serie_'))

// Si la nota contiene links de ejercicios, entonces comenzamos desde donde terminen los ejercicios, que se registran antes de la comida
const start = numExercises === -1 ? 0 : numExercises;

const listOfMeals = dv.current().file.lists.text.array().slice(start, numExercises + todayMeals.length + 1);

const macrosNames = [
'Calorias',
'Proteinas',
'Grasas',
'Carbohidratos',
'Fibra',
'Azucares'
];

const rows = todayMeals.map(page => {
const mealName = page.file.name;
const mealInList = listOfMeals
聽 聽 .find(_mealName => _mealName.includes(mealName)) ?? 'No encontrado';
const times = getTimes(mealInList);
const values = macrosNames.map(macroName => removeUnit(page[macroName]) * times);
return [mealName, times, ...values]
})

dv.table(
聽 [
聽 聽 'Comida',
聽 聽 'Cantidad en el d铆a',
聽 聽 'Calorias (Kcal)',
聽 聽 'Proteinas(g)',
聽 聽 'Grasas (g)',
聽 聽 'Carbohidratos (g)',
聽 聽 'Fibra (g)',
聽 聽 'Azucares (g)'
聽 ],
rows
)

dv.header(3, "馃き Totales");

const totals = rows.array().reduce((acc, val) => {
const CALORIES_INDEX = 0;
const PROTEINS_INDEX = 1;
const FATS_INDEX = 2;
const CARBS_INDEX = 3;
const FIBER_INDEX = 4;
const SUGAR_INDEX = 5;

// El 2 que se suma en el segundo indice es porque en el arreglo original viene el nombre de la comida
// y las cantidades por d铆a, entonces hay que adelantar 2 posiciones para acceder al valor que nos interesa
const totalCalories = acc[CALORIES_INDEX] + val[CALORIES_INDEX + 2];
const totalProteins = acc[PROTEINS_INDEX] + val[PROTEINS_INDEX + 2];
const totalFats = acc[FATS_INDEX] + val[FATS_INDEX + 2];
const totalCarbs = acc[CARBS_INDEX] + val[CARBS_INDEX + 2];
const totalFiber = acc[FIBER_INDEX] + val[FIBER_INDEX + 2];
const totalSugar = acc[SUGAR_INDEX] + val[SUGAR_INDEX + 2];

return [
聽 聽 totalCalories,
聽 聽 totalProteins,
聽 聽 totalFats,
聽 聽 totalCarbs,
聽 聽 totalFiber,
聽 聽 totalSugar
聽 ];

}, [0, 0, 0, 0, 0, 0] /*Los totales inician en 0 */);

const totalsWithName = totals.map((value, index) => [
macrosNames[index],
`${value} ${index === 0 ? 'Kcal' : 'g'}`
]);

dv.table(
聽 ['Macro', 'Total'],
totalsWithName
)

function removeUnit(text) {
if(!text) return 0;
const [quantity, unit] = text.split(' ');
return Number(quantity);
}

function getTimes(text) {
const [timesText] = text.split(' ');
const times = Number(timesText);
if(!times) return 1;
return times;
}

Lo primero es obtener las comidas del d铆a en todayMeals, entonces utilizamos la “selecci贸n”: outgoing[[#]] and (#receta o #platillo) . Esto significa que queremos obtener todos los links (las notas a las que enlaza la nota, en este caso mi journal del d铆a), el and actuar铆a como un join, con las notas que tengan el tag receta o el tag platillo, de esa forma obtendr铆amos las comidas del d铆a.

Para obtener las cantidades de la comida, por ejemplo, el n煤mero de panes tostados, tengo que obtener la lista de journal, ya que esa informaci贸n no puede estar en la nota, porque es distinto cada vez, el problema es que en mi journal tambi茅n tengo una lista de mis ejercicios, as铆 que para evitar que esa lista confunda las cantidades que quiero obtener, tomo todos los bullets de lista del journal y como los ejercicios est谩n antes que la comida, quito esa cantidad de elementos, de forma que donde comience, ser谩 en la lista de comidas y no en la lista de ejercicios, esto lo hago con el slice que le digo que comience donde terminan los ejercicios y solo tome la cantidad de comidas que haya.

De esta forma obtengo los textos que hay en la lista de comidas, esto para poder obtener la cantidad comida, el resto es que a trav茅s de un map a las comidas del d铆a obtengo cada fila de la primera tabla, haciendo los calculos de la informaci贸n nutrimental por la cantidad.

Y utilizando un reduce, obtengo los totales.

馃悰 Problemas y mejoras

Este sistema tiene sus fallos, como los mostrados a continuaci贸n. Estoy abierto a sugerencias y mejoras para seguir desarrollando este sistema.

Cuando se come los mismo en 2 comidas

Tiene problemas cuando se registra una misma receta en 2 comidas diferentes, es decir, si yo desayuno y ceno lo mismo, tengo que registrar la cantidad en un solo punto, porque al hacer el join no me toma las 2 apariciones.

Los puntos decimales

Este no es un problema como tal, pero tengo que limitar los decimales a 2.

馃 Conclusi贸n

En conclusi贸n, este sistema me ha permitido tener un mayor control sobre mi ingesta diaria, ayud谩ndome a mejorar mis h谩bitos alimenticios y mi salud en general. Estoy emocionado por seguir desarrollando y mejorando esta herramienta en el futuro.

comments powered by Disqus