Files
nutriscan/README.md
T

110 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div align="center">
<img src="assets/readme/hero.svg" width="100%" alt="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)
</div>
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 `[email protected]`).
```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/).
<div align="center">
<sub>Feito por <a href="https://evandro.dev.br">Evandro Fonseca Junior</a> · <a href="https://git.evandro.dev.br/evandro">git.evandro.dev.br</a></sub>
</div>