NutriScan: busca de alimentos no Open Food Facts por código de barras ou nome, mostrando um produto com Nutri-Score D e NOVA grupo 4 ![Next.js 15.5](https://img.shields.io/badge/Next.js-15.5-317257?style=flat-square) ![React 19](https://img.shields.io/badge/React-19-317257?style=flat-square) ![Prisma 6 + SQLite](https://img.shields.io/badge/Prisma_6-SQLite-317257?style=flat-square) ![Licença: uso pessoal](https://img.shields.io/badge/licen%C3%A7a-uso_pessoal-317257?style=flat-square) [Notas da API Open Food Facts](docs/openFoodFacts.md) · [Issues](https://git.evandro.dev.br/evandro/nutriscan/issues)
O cache JSON do servidor vem no repositório (`data/products-cache.json`) com produtos reais do Open Food Facts. Contado por Nutri-Score, num clone novo e sem instalar nada: ```console $ node -e "const p = Object.values(require('./data/products-cache.json').products); const g = {}; for (const x of p) g[x.nutrition_grades] = (g[x.nutrition_grades] || 0) + 1; console.log(p.length, g)" 232 { e: 26, unknown: 167, d: 24, c: 9, a: 2, b: 4 } ``` ## O que é NutriScan é um app web em Next.js para buscar produtos alimentícios no [Open Food Facts](https://world.openfoodfacts.org/) e montar listas de compras. A entrada é detectada sozinha: 8 a 13 dígitos viram busca por código de barras; o resto, busca por nome ou marca. Páginas: `/` (landing), `/resultados`, `/produto/[barcode]`, `/lista/[id]` e `/sobre`. | Recurso | O que faz | |---|---| | **Filtros** | Nutri-Score A–E, grupos NOVA 1–4, categorias, marcas, alérgenos e aditivos (incluir/excluir, ou "sem aditivos"), faixas por 100 g de energia, gordura, carboidratos, proteínas, açúcares, fibras e sódio, e países | | **12 critérios de ordenação** | Relevância, Nutri-Score, nome, marca, energia, gordura, açúcares, completude, com imagem, com dados nutricionais, Eco-Score e grupo NOVA, em ordem crescente ou decrescente | | **País por IP** | A rota `/api/location` descobre o país do visitante e a busca prioriza produtos dele; o resultado fica 7 dias no `localStorage`, com Brasil como fallback | | **Listas de compras** | Várias listas acessíveis por ID em `/lista/[id]`; cada item tem nome, quantidade, unidade e status de comprado, persistidos com Prisma + SQLite | | **Interface** | Tema claro/escuro, componentes shadcn/ui sobre Radix UI, animações com Framer Motion, skeletons, paginação e manifest de PWA | ## Por que usar - **Menos chamadas ao Open Food Facts.** A busca passa por dois caches antes da API: `localStorage` no navegador (TTL padrão de 1 hora) e o cache JSON no servidor. Tudo que vem da API é gravado no cache JSON. - **Funciona sem a API.** No modo cache-only o servidor responde só com o cache JSON, sem consultar o Open Food Facts. - **Filtros combinados**: Nutri-Score, NOVA, alérgenos, aditivos e faixas de nutrientes ao mesmo tempo, com 12 ordenações. Quando não usar: se você precisa publicar o app ou usá-lo comercialmente (a licença só permite uso pessoal em localhost), ler código de barras pela câmera (a entrada é digitada ou colada) ou esperar Nutri-Score em cada produto (no cache acima, 167 de 232 produtos vieram sem nota). ## Como funciona 1. O navegador procura a busca no `localStorage` (`lib/cacheManager.ts`, `hooks/useSearchCache.ts`). 2. Se não achar, chama as rotas Next.js `/api/search` e `/api/products/[barcode]`. 3. As rotas consultam o cache JSON do servidor (`lib/jsonCacheManager.ts`, `data/products-cache.json`). 4. Fora do modo cache-only, o que falta vem do Open Food Facts (`lib/openFoodFactsApi.ts`: timeout de 8 s, 200 ms entre páginas, até 1000 produtos por página) e é gravado no cache JSON. `POST /api/cache/sync` também grava produtos enviados pelo cliente. 5. As listas de compras usam Server Actions (`app/actions.ts`) sobre Prisma + SQLite: modelos `Lista` e `Produtos` em `prisma/schema.prisma`. ## Início rápido Requisitos: Node.js 18+ (o deploy usa `nodejs_18`) e pnpm (o deploy fixa `pnpm@10.34.5`). ```bash git clone https://git.evandro.dev.br/evandro/nutriscan.git cd nutriscan pnpm install # o postinstall já roda `prisma generate` # .env na raiz (o Prisma lê .env, não .env.local) echo 'DATABASE_URL="file:./db/database.sqlite"' > .env pnpm db:push # cria o banco SQLite a partir do schema pnpm dev # http://localhost:3000 ``` > [!WARNING] > **Não existe `.env.example`** e **`pnpm db:migrate` não funciona num clone novo**: ele roda `prisma migrate deploy`, mas `/prisma/migrations` está no `.gitignore`. Crie o banco com `pnpm db:push`. O `DATABASE_URL` é resolvido a partir da pasta `prisma/`, então `file:./db/database.sqlite` vira `prisma/db/database.sqlite` (pasta também ignorada pelo git). Outros scripts: `pnpm build` (`prisma generate && next build`), `pnpm start`, `pnpm lint` (`next lint`), `pnpm db:generate`. ## Configuração | Variável | Efeito | |---|---| | `DATABASE_URL` | Banco SQLite das listas de compras (obrigatória) | | `NEXT_PUBLIC_CACHE_ONLY_MODE=true` | As rotas de busca e de produto respondem só com o cache JSON | | `NEXT_PUBLIC_RYBBIT_HOST` | Destino do proxy `/analytics/*` (padrão `https://analytics.evandro.dev.br/api`) | O switch de cache-only na interface só funciona em produção (`NODE_ENV=production`); as rotas `/api/search` e `/api/products/[barcode]` aceitam `?cache_only=true` em qualquer ambiente. O que sai do seu servidor: - **Open Food Facts**: dados e imagens de produtos (`next.config.mjs` libera `static.openfoodfacts.org` e `images.openfoodfacts.org`). - **ip-api.com**: o IP do visitante, enviado pelo servidor por HTTP, para descobrir o país. - **Rybbit Analytics** (self-hosted, via proxy `/analytics/`): com session replay e rastreamento de erros. - O app também exibe um card de doação via [Buy Me a Coffee](https://buymeacoffee.com/evandrotruuta). ## Estado do projeto Projeto pessoal, sem suíte de testes; o único check é `pnpm lint`. O último trabalho de código foi o redesign completo e os ajustes no deploy com Prisma/SQLite, em 2026-07-16. O repositório traz [`dokploy.yaml`](dokploy.yaml) e [`nixpacks.toml`](nixpacks.toml) para o deploy do autor, sem workflow de CI. A instância pública do autor em `nutriscan.evandro.dev.br` não respondia com o app em 2026-10-24. Limites conhecidos: - Toda busca nova depende do Open Food Facts; além do cache, não há fila nem fallback. - A localização é por IP, não pela API de geolocalização do navegador. - O `sitemap.xml` lista só a página inicial. ## Licença Licença de Uso Pessoal, em [`LICENSE`](LICENSE) (Copyright (c) 2024 Evandro): uso pessoal, não comercial e só em localhost. É proibido fazer deploy público, redistribuir, usar comercialmente ou criar obras derivadas para distribuição. Dúvidas sobre a licença: evandro@evandro.dev.br. Créditos: [Open Food Facts](https://world.openfoodfacts.org/), [Next.js](https://nextjs.org/), [Prisma](https://www.prisma.io/), [shadcn/ui](https://ui.shadcn.com/) e [Radix UI](https://www.radix-ui.com/).
Feito por Evandro Fonseca Junior · git.evandro.dev.br