|
EN / PT
← Voltar aos projetos

Kal AI | Contador de calorias

Kal AI

Criar um Contador de Calorias Inteligente: Visão por IA, Nutrição Verificada e um Pipeline no Homelab

Cansei-me de apps de contagem de calorias que nos obrigam a procurar em bases de dados infinitas, adivinhar porções ou pagar subscrições mensais por funcionalidades básicas. Por isso, construí a minha própria — Kal AI, uma PWA que te permite tirar uma foto à comida e obter dados nutricionais verificados em segundos. Corre no meu homelab, suporta Português e Inglês, e parece uma app nativa do iPhone.

Este artigo explica como a construí, o pipeline de IA por trás da análise, e as lições aprendidas ao lançá-la para utilizadores reais.


1. O Problema

A maioria das apps de contagem de calorias cai numa de duas categorias:

  • Apps de pesquisa manual — escreves "peito de frango" e escolhes entre 40 resultados com valores completamente diferentes. Ninguém tem tempo para isso.
  • Apps só com IA — estimam calorias a partir de uma foto, mas os dados não são fiáveis. Alucinações da IA em dados nutricionais podem significar um erro de 300 kcal numa única refeição.

Eu queria algo no meio: a IA identifica a comida, e depois bases de dados verificadas confirmam os números. O melhor dos dois mundos.


2. A Tech Stack

Camada Tecnologia
Framework Next.js 16 (App Router)
Styling Tailwind CSS v4
Base de Dados & Auth Supabase (PostgreSQL + Auth)
Visão por IA Google Gemini 2.5 Flash
Dados Nutricionais FatSecret API + OpenFoodFacts
Pesquisa de Receitas Spoonacular API
Gráficos Recharts
PWA next-pwa
Deployment Docker + GitHub Actions → GHCR → Homelab
Linguagem TypeScript

A app inteira é uma PWA com Next.js 16 usando o App Router. O styling é Tailwind CSS v4 com um design system inspirado no iOS — font stack SF Pro, paleta de cores do Apple Health (--ios-blue, --ios-green, --ios-red), barra de navegação com efeito frosted-glass, e suporte para safe-area dos notches do iPhone.

A autenticação funciona através do Supabase Auth com email/password e Google OAuth. A base de dados é PostgreSQL no Supabase com Row Level Security em todas as tabelas — os utilizadores só conseguem ler e escrever os seus próprios dados.


3. O Pipeline de IA: Da Foto à Nutrição Verificada

Este é o core da app. Quando registas uma refeição, ela passa por um pipeline de 3 passos:

📸 Passo 1 — O Gemini Vision Identifica a Comida

A foto (ou lista de ingredientes) é enviada para o Google Gemini 2.5 Flash. A IA atua como um "Nutricionista Digital" — identifica cada alimento individualmente, estima o tamanho das porções com base nas proporções do prato e talheres, considera métodos de cozinha (brilho de óleo = frito = mais calorias), e devolve JSON estruturado com o nome, peso e macros estimados de cada item.

Utilizador tira foto do prato de almoço
         │
         ▼
┌─────────────────────────┐
│   Gemini 2.5 Flash      │
│   "Vejo:                │
│   - 200g arroz branco   │
│   - 150g peito de frango│
│   - 80g brócolos"       │
└─────────────────────────┘

🔍 Passo 2 — Verificação em Base de Dados

As estimativas do Gemini são boas, mas não suficientemente fiáveis sozinhas. Cada item identificado é cruzado com bases de dados nutricionais reais:

  1. FatSecret API — a fonte principal, com dados verificados por 100g
  2. OpenFoodFacts — fallback gratuito se o FatSecret não tiver resultado
  3. Estimativa Gemini — último recurso, só usado quando ambas as bases de dados falham

Cada item no resultado final mostra um badge de fonte para que o utilizador saiba exatamente de onde vieram os dados. Se múltiplas fontes foram consultadas, todos os valores são mostrados para total transparência.

✅ Passo 3 — Pontuação de Confiança

O sistema calcula uma pontuação de confiança com base em quantos itens foram verificados por bases de dados vs. estimativas só de IA. Uma refeição onde 3/3 itens coincidiram com o FatSecret obtém 90%+ de confiança. Uma refeição onde tudo caiu para o Gemini fica com ~65%.

O utilizador vê esta pontuação junto com os resultados — total transparência sobre a qualidade dos dados.


4. Funcionalidades em Detalhe

🥩 Construtor de Ingredientes

Nem toda a refeição precisa de foto. A app tem um construtor de ingredientes categorizado com 9 categorias: Cereais e Hidratos, Carnes e Aves, Peixe e Marisco, Legumes e Vegetais, Leguminosas, Laticínios e Ovos, Frutas, Gorduras e Óleos, e Molhos e Extras.

Cada alimento tem nome em Inglês e Português. Alguns itens usam input por unidades em vez de gramas — os ovos, por exemplo, permitem escrever "2" em vez de "100g", e a app calcula o peso automaticamente (2 × 50g = 100g).

Existe também uma categoria "Outro / Personalizado" onde os utilizadores podem escrever qualquer nome de alimento livremente — esta funcionalidade foi adicionada com base em feedback de utilizadores que pediam alimentos que ainda não estavam na base de dados.

A base de dados de alimentos inclui comidas portuguesas e brasileiras como panados, feijoada, farofa e beijinho — todos adicionados com base em pedidos reais de utilizadores através do sistema de feedback integrado na app.

🍪 Snacks Comuns

Snacks embalados são um caso especial. Uma barra de proteína ou um pacote de bolachas tem dados nutricionais exatos impressos no rótulo — não é preciso estimativa da IA.

O fluxo:

  1. Tirar uma foto do rótulo nutricional da embalagem
  2. O Gemini lê o rótulo e extrai os macros por porção (não por 100g, não por embalagem — a porção individual real)
  3. Guarda uma vez — a partir daí, basta tocar no snack, escolher quantos comeste, e pronto

Podes opcionalmente adicionar contexto como "pacote de 4 bolachas" para ajudar a IA a identificar o que é realmente uma porção.

🍳 Receitas da Despensa

"Tenho frango, arroz e brócolos — o que posso fazer?" A funcionalidade de despensa responde a isto usando a Spoonacular API.

Os utilizadores escrevem os ingredientes que têm em casa. A app suporta input em Português com tradução automática — escreve "frango" e é traduzido para "chicken" antes de chamar a API. Isto é possível graças a um dicionário bilingue integrado com mais de 40 mapeamentos de ingredientes.

Os resultados das receitas mostram:

  • Nutrição por porção (calorias, proteína, hidratos, gorduras)
  • Quais ingredientes já tens vs. o que ainda precisas
  • Instruções de preparação passo a passo
  • Um link para a receita completa

📊 Dashboard e Gráficos

A vista diária mostra um anel circular de calorias (SVG), barras de progresso de macros, e uma lista das refeições de hoje com ícones de tipo de refeição e horários.

A vista semanal é um gráfico de área que mostra as tendências calóricas ao longo de 7 dias. Toca em qualquer dia para ver as refeições desse dia em detalhe.

A vista mensal é um gráfico de barras que agrega calorias por semana.

Todos os gráficos são construídos com Recharts e são totalmente responsivos.

🌍 Suporte Bilingue

A app inteira está disponível em Inglês e Português. Isto não são só labels de UI — as respostas da IA, nomes de alimentos, categorias de ingredientes, mensagens de erro, e até o sistema de feedback respeitam todos o idioma selecionado.

O sistema de tradução é uma implementação i18n leve e personalizada (sem necessidade de bibliotecas pesadas) com um único ficheiro i18n.ts que contém todas as chaves de tradução.


5. O Pipeline de Deployment

A app é self-hosted no meu homelab com um pipeline CI/CD totalmente automatizado:

git push para master
       │
       ▼
┌──────────────────────┐
│  GitHub Actions       │
│  - Build imagem Docker│
│  - Injetar secrets    │
│    como build-args    │
│  - Push para GHCR     │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│  GitHub Container     │
│  Registry (ghcr.io)   │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│  Servidor Homelab     │
│  - Watchtower faz     │
│    auto-pull de novas │
│    imagens            │
│  - Docker Compose     │
│    reinicia container │
└──────────────────────┘

O Dockerfile usa um multi-stage build de 3 estágios:

  1. deps — instala os node_modules
  2. builder — faz build da app Next.js com todas as env vars injetadas como build-args
  3. runner — imagem Alpine mínima apenas com o output standalone

Uma lição importante aprendida: as variáveis de ambiente definidas no estágio builder não transitam para o estágio runner em multi-stage Docker builds. As API keys server-side (Gemini, FatSecret, Spoonacular) tiveram de ser re-declaradas como ARG + ENV no estágio runner, caso contrário as API routes falhavam silenciosamente em runtime sem chaves disponíveis.

O workflow do GitHub Actions injeta todos os secrets como --build-args durante o build do Docker. No homelab, o Watchtower monitoriza o GHCR e faz pull automático de novas imagens quando são publicadas — zero deployment manual.


6. O Modo Debug de Admin

Quando algo parte em produção, precisas de visibilidade. A app tem um modo debug oculto protegido pelo email de admin e uma flag no localStorage.

Quando ativado, o fluxo de análise regista cada passo em tempo real:

  • Se a API key está presente
  • O que o Gemini identificou e estimou
  • Quais fontes nutricionais foram consultadas para cada item
  • Onde as pesquisas falharam e qual foi o fallback
  • Totais finais de calorias e labels de fonte de dados

Isto foi fundamental para diagnosticar um bug em produção onde a API key do Spoonacular não estava a chegar ao runtime do Docker — o painel de debug mostrou imediatamente API key present: false, apontando diretamente para o problema das env vars no Dockerfile multi-stage.


7. Ciclo de Feedback dos Utilizadores

A app inclui um sistema de feedback integrado — os utilizadores podem submeter relatórios de bugs ou pedidos de funcionalidades com capturas de ecrã em anexo diretamente na app. Os relatórios são guardados no Supabase com tracking de estado (aberto, em progresso, fechado).

Isto gerou melhorias reais:

  • Um utilizador reportou que links de confirmação de email expirados mostravam um URL de erro bruto em vez de uma mensagem amigável → corrigido com tratamento de erros bilingue
  • Utilizadores pediram alimentos específicos (panados, feijoada, farofa) → adicionados à base de dados de ingredientes
  • Um utilizador queria escrever nomes de alimentos personalizados ao selecionar "Outro" → adicionado um campo de texto livre

Ter o ciclo de feedback integrado na própria app (em vez de depender de email ou GitHub issues) reduziu drasticamente a barreira para utilizadores não técnicos reportarem problemas.


8. Decisões de Design

Porquê PWA em vez de nativa?

O público-alvo usa iPhones. Construir uma app iOS nativa exigiria uma conta Apple Developer (99$/ano), revisão da App Store, e manter código Swift/SwiftUI. Uma PWA instalada pelo "Adicionar ao Ecrã Principal" do Safari dá 95% da experiência nativa — modo standalone, sem UI de browser, ícone no ecrã principal — com zero fricção da App Store.

Porquê não usar simplesmente o MyFitnessPal?

Três razões: a análise por foto com IA (tira foto e pronto), a verificação multi-fonte (não confiar numa única fonte), e o suporte em Português (a maioria das apps de nutrição são só em Inglês, o que é fricção para utilizadores lusófonos).

Porquê self-hosting?

A app lida com fotos de comida e dados pessoais de saúde. Self-hosting no homelab significa que os dados ficam em infraestrutura que eu controlo. Além disso, correr em Docker com auto-updates do Watchtower torna o deployment trivialmente simples — faz push para o GitHub e está live em minutos.


9. FAQ

P: Quão precisa é a análise de fotos por IA?

R: A identificação por IA é surpreendentemente boa para refeições comuns — identifica corretamente arroz, frango, legumes, ovos, etc. a partir de fotos. A precisão dos dados nutricionais depende do passo de verificação: itens que coincidem com o FatSecret ou OpenFoodFacts são altamente precisos (dentro de 5-10%), enquanto estimativas só de IA podem errar 15-20%. Os badges de fonte tornam isto transparente.

P: Funciona offline?

R: O shell da PWA fica em cache para acesso offline, mas a análise de refeições requer ligação à internet (Gemini API + pesquisas nutricionais). Refeições previamente registadas são visíveis offline.

P: Posso adicionar os meus próprios alimentos?

R: Sim. A categoria "Outro / Personalizado" permite escrever qualquer nome de alimento, e a IA estima a sua nutrição. Para snacks embalados, podes fotografar o rótulo nutricional uma vez e registá-lo rapidamente para sempre.

P: E quanto à privacidade?

R: Todos os dados são guardados no Supabase com Row Level Security — os utilizadores só acedem aos seus próprios dados. As fotos de refeições são guardadas no Supabase Storage. A app é self-hosted, por isso não há analytics ou tracking de terceiros.


Conclusão

O Kal AI começou como um projeto pessoal para resolver uma irritação real — contagem de calorias que fosse rápida e precisa ao mesmo tempo. A ideia-chave foi combinar visão por IA (rápida) com verificação em base de dados (precisa) num único pipeline.

A parte mais gratificante foi ver utilizadores reais a submeter feedback e a moldar a app. Funcionalidades como a tradução de ingredientes em Português, o input personalizado "Outro", e o leitor de rótulos de snacks vieram todas de pedidos reais de utilizadores.

A stack — Next.js + Supabase + Gemini + Docker num homelab — acabou por ser o sweet spot para uma app de produção à pequena escala: rápida de desenvolver, barata de manter, e totalmente sob o meu controlo.

Código fonte: github.com/MonoBL/Kal-AI


Vibecoded and designed by Nuno