Plataforma inteligente de comparação de preços de GLP com foco em Economia Real
Compare preços de gás de cozinha (GLP 13kg) em sua cidade e descubra quanto você pode economizar. Dados oficiais da ANP, atualizados semanalmente.
Não mostramos apenas preços - mostramos quanto você pode economizar:
- 💰 Economia Potencial: R$ 29,50 por botijão = R$ 354,00/ano
- 📊 Contexto Percentual: Economize até 24% escolhendo o posto mais barato
- 📈 Distribuição de Preços: Visualização de quartis para entender o mercado
- 🎯 Benchmark Inteligente: Mediana como referência de "preço justo"
- Server Components: Renderização instantânea no servidor
- Cache Inteligente: 30 minutos de TTL para dados da ANP
- Error Boundaries: Nunca mostra tela branca ao usuário
- SEO Otimizado: Metadata dinâmica por cidade + JSON-LD
- 100% Responsivo: 8 breakpoints customizados (mobile → ultra-wide)
11 métricas calculadas automaticamente:
- Preço mínimo, máximo, médio e mediano
- Economia potencial (absoluta e percentual)
- Volatilidade do mercado
- Distribuição estatística (quartis Q1, Q2, Q3)
- Desvio padrão e confiança dos dados
https://gasmaisbarato.com
┌─────────────────────────────────────────┐
│ 🔥 Gás Mais Barato │
│ ┌───────────────────────────────────┐ │
│ │ 🔍 Buscar cidade... │ │
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ 💰 Economia Potencial: R$ 29,50 │
│ R$ 354,00/ano (24% de economia) │
├─────────────────────────────────────────┤
│ 📊 Distribuição de Preços │
│ [Verde════Mediana════Vermelho] │
│ R$ 95,50 R$ 107,00 R$ 125,00 │
├─────────────────────────────────────────┤
│ 💡 Dica: Busque postos abaixo da │
│ mediana (R$ 107,00) para garantir │
│ bom custo-benefício │
└─────────────────────────────────────────┘
- Framework: Next.js 14.2 (App Router)
- Language: TypeScript 5.4 (Strict Mode)
- Styling: Tailwind CSS 3.4
- Icons: Lucide React
- Data Processing: csv-parse
- Testing: Vitest + Testing Library
webapp_gas_mais_barato/
├── app/ # Next.js App Router
│ ├── layout.tsx # Layout raiz + SEO metadata
│ ├── page.tsx # Página principal (Server Component)
│ ├── error.tsx # Error boundary global
│ ├── not-found.tsx # Página 404 customizada
│ ├── opengraph-image.tsx # Open Graph image generator
│ ├── [city]/ # Rotas dinâmicas por cidade
│ │ └── page.tsx # /sao-paulo, /rio-de-janeiro, etc.
│ └── globals.css # Estilos globais (Tailwind)
│
├── components/ # Componentes React
│ ├── kpi-card.tsx # Card de KPI reutilizável
│ ├── search-bar.tsx # Busca com autocomplete (Client)
│ ├── stations-list.tsx # Lista de postos (responsivo)
│ ├── price-distribution.tsx # Visualização de quartis
│ └── error-boundary.tsx # Componentes de erro
│
├── lib/ # Lógica de negócio
│ ├── analytics.ts # 🧠 Funções puras de BI
│ ├── metadata.ts # Geração de metadata SEO
│ ├── utils.ts # Utilitários (cn, etc.)
│ └── data/
│ └── csv-processor.ts # Pipeline de dados ANP
│
├── types/ # Type definitions
│ └── gas.ts # Interfaces TypeScript
│
├── data/ # Dados ANP
│ └── ultimas-4-semanas-glp.csv # CSV oficial (fallback)
│
├── public/ # Assets estáticos
│ ├── favicon.ico
│ └── apple-touch-icon.png
│
├── package.json # Dependências
├── tsconfig.json # TypeScript config (strict mode)
├── next.config.js # Next.js config + headers
├── tailwind.config.ts # Tailwind config
└── .eslintrc.json # ESLint rules (no-any: error)
- Node.js >= 18.17.0
- npm >= 9.0.0
# 1. Clone o repositório
git clone https://github.com/seu-usuario/webapp_gas_mais_barato.git
cd webapp_gas_mais_barato
# 2. Instale as dependências
npm install
# 3. Execute em desenvolvimento
npm run dev
# 4. Abra no navegador
http://localhost:3000# Validar TypeScript
npm run type-check
# Lint do código
npm run lint
# Build para produção
npm run build
# Executar em produção
npm start- Autocomplete: Sugestões de cidades enquanto digita
- Navegação por Teclado: ↑↓ para navegar, Enter para selecionar
- Case-Insensitive: Busca flexível (são paulo = São Paulo)
- URL-Friendly: Compartilhe links
/sao-paulodiretamente
-
Economia Potencial (verde)
- Valor absoluto: R$ 29,50
- Projeção anual: R$ 354,00/ano
- Percentual: 24% de economia
-
Menor Preço
- Melhor oferta encontrada
- Posto com melhor custo-benefício
-
Potencial de Economia
- Percentual de economia: 24%
- Comparação: mais barato vs. mais caro
-
Preço Típico (Mediana): R$ 107,00
- 50% dos postos cobram menos
- Mais robusto que média
-
Volatilidade do Mercado: Variação Significativa
- Baseado em price range percentage
- Indicador de competitividade
-
Confiança dos Dados: Alta
- Baseado em número de postos (47)
- Score: Baixa / Moderada / Média / Alta / Muito Alta
Gráfico de Quartis:
[Verde════════Amarelo════════Vermelho]
↑ ↑ ↑
Q1(25%) Mediana Q3(75%)
R$ 100,00 R$ 107,00 R$ 115,00
Interpretação:
- Verde: 25% mais baratos (até R$ 100,00)
- Amarelo: Preço típico/mediana (R$ 107,00)
- Vermelho: 25% mais caros (acima de R$ 115,00)
Desktop: Tabela completa com colunas
- Posto | Preço | Bandeira | Bairro | Endereço | vs. Mediana | Ações
Tablet: Tabela responsiva
- Layout adaptativo com
table-auto→lg:table-fixed - Espaçamento otimizado para telas médias
Mobile: Cards responsivos
- Layout otimizado para telas pequenas
- Todas informações preservadas
- Touch targets ≥ 44px (WCAG AAA)
Features:
- Ordenação automática por preço
- Badge "🏆 Melhor Preço" no mais barato
- Indicador visual vs. Mediana (↑/↓)
- Formatação brasileira (R$, DD/MM/YYYY)
Error Boundaries:
app/error.tsx: Captura erros globaisapp/not-found.tsx: Página 404 customizadaErrorFallback: Componente reutilizável
Tipos de Erro:
- Fetch Error: Problema ao buscar dados ANP
- Processing Error: Erro ao processar CSV
- Not Found: Cidade não encontrada
- Unknown: Erro genérico
UX de Erro:
- Visual amigável (não mostra stack traces)
- Botão "Tentar Novamente" funcional
- Link para suporte
- Detalhes técnicos apenas em desenvolvimento
Metadata Dinâmica por Cidade:
// Para: /?city=São Paulo
title: "Gás a partir de R$ 95,50 em São Paulo | Gás Mais Barato"
description: "Preços de GLP em São Paulo variam de R$ 95,50 a R$ 125,00.
Economize até 24% (R$ 29,50) escolhendo o posto mais barato."Structured Data (JSON-LD):
- WebSite com SearchAction
- AggregateOffer com preços min/max
- Breadcrumbs por cidade
Open Graph:
- Imagem gerada dinamicamente (1200x630px)
- Título e descrição otimizados
- Twitter Cards
Funções Puras (lib/analytics.ts)
Todas funções são puras (sem side effects):
// Função principal: calcula todas as métricas
calculateCityMetrics(
stations: GasStation[],
city: string
): CityMetrics | null
// Funções auxiliares
findCheapestStation(stations, city): GasStation | null
findMostExpensiveStation(stations, city): GasStation | null
getTopCheapestStations(stations, city, limit): GasStation[]
calculatePriceVariance(price, cityMetrics): number
calculateAnnualSavings(currentPrice, cityMetrics): number
calculateBrandAverages(stations, city?): Map<string, number>Pipeline de Dados (lib/data/csv-processor.ts)
┌─────────────────┐
│ ANP Live URL │ (dados semanais)
└────────┬────────┘
│ fetch()
↓
┌─────────────────┐
│ CSV Parser │ (csv-parse)
│ - UTF-8 │
│ - Delimiter ; │
└────────┬────────┘
│
↓
┌─────────────────┐
│ Validation & │
│ Transformation │
│ - Type guards │
│ - Price parse │
│ - Date parse │
└────────┬────────┘
│
↓
┌─────────────────┐
│ Deduplication │ (CNPJ)
└────────┬────────┘
│
↓
┌─────────────────┐
│ In-Memory │ (30 min TTL)
│ Cache │
└─────────────────┘
Tratamento de Erros:
- Try-catch em todas operações de I/O
- Fallback para arquivo local se API ANP falhar
- Validação linha a linha (skip inválidos)
- Logs estruturados de estatísticas
# Testes unitários
npm test
# Testes com UI
npm run test:ui
# Coverage
npm run test:coverageEstrutura de Testes (a implementar):
tests/
├── unit/
│ ├── analytics.test.ts # Testes de funções puras
│ ├── csv-processor.test.ts # Testes de parsing
│ └── metadata.test.ts # Testes de SEO
├── integration/
│ └── api.test.ts # Testes de integração
└── e2e/
└── user-flow.spec.ts # Testes E2E (Playwright)
| Aspecto | Status | Detalhes |
|---|---|---|
| TypeScript | ✅ 100% | Strict mode, sem any |
| Type Safety | ✅ Completo | Type guards + validação runtime |
| Error Handling | ✅ Robusto | Boundaries + fallbacks |
| Performance | ✅ Otimizado | Server Components + cache |
| SEO Score | ✅ ~95 | Metadata dinâmica + JSON-LD |
| Accessibility | ✅ WCAG AAA | ARIA + 44px touch targets |
| Responsividade | ✅ 8 Breakpoints | Mobile-first (320px → 3840px) |
| Segurança | ✅ Headers | CSP, X-Frame-Options |
O projeto implementa 8 breakpoints customizados para garantir experiência otimizada em todos os dispositivos:
| Breakpoint | Width | Dispositivos | Layout |
|---|---|---|---|
xs |
375px | iPhone SE, smartphones pequenos | 1 coluna, cards mobile |
sm |
640px | Smartphones grandes, phablets | 2 colunas, tabela simplificada |
md |
768px | Tablets portrait | Tabela completa inicia |
lg |
1024px | Tablets landscape, laptops | 3 colunas, table-fixed |
xl |
1280px | Desktops padrão | Layout completo |
2xl |
1536px | Desktops grandes | Container expandido |
3xl |
1920px | Full HD, monitores padrão | Max-width 1600px |
4xl |
2560px | Ultra-wide, 2K/4K | Max-width 2000px |
Todos os componentes seguem a abordagem mobile-first:
// Exemplo: KPI Card responsivo
className="
p-4 sm:p-5 md:p-6 // Padding progressivo
text-2xl sm:text-3xl // Texto escalável
grid-cols-1 sm:grid-cols-2 // Grid adaptativo
lg:grid-cols-3 // Desktop full
"- SearchBar: Botão mobile (< sm), keyboard hints (≥ sm), max-width expandida (3xl)
- KPI Cards: Padding/texto escalonados, ícones responsivos
- Price Distribution: Altura variável, labels ocultas em mobile
- Stations List: Cards mobile (< md) → Tabela desktop (≥ md)
- Containers: Max-width progressiva (7xl → 3xl → 4xl)
- Touch Targets: Mínimo 44x44px em mobile (WCAG AAA)
- Fontes: Mínimo 14px (mobile) → 16px (desktop)
- Contraste: AAA (7:1) em todos breakpoints
- Navegação: Tab order mantido em todas resoluções
Ver RESPONSIVE_DESIGN.md para:
- Guia detalhado de breakpoints
- Padrões de classes responsivas
- Testes de responsividade
- Troubleshooting comum
# Instalar CLI
npm install -g vercel
# Deploy
vercel
# Deploy para produção
vercel --prodFROM node:18-alpine AS base
# Dependências
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Produção
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]Crie .env.local:
# URL do CSV ANP (opcional - usa URL padrão se não definido)
ANP_CSV_URL=https://www.gov.br/anp/pt-br/centrais-de-conteudo/dados-abertos/arquivos/shpc/qus/ultimas-4-semanas-glp.csv
# Site URL (para canonical links e OG images)
NEXT_PUBLIC_SITE_URL=https://gasmaisbarato.com
# Analytics (opcional)
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX// ✅ Bom
interface GasStation {
id: string;
price: number;
}
// ❌ Ruim
const station: any = { ... }// ✅ Bom - Pure function
function calculateAverage(prices: number[]): number {
return prices.reduce((a, b) => a + b, 0) / prices.length;
}
// ❌ Ruim - Side effect
let total = 0;
function addPrice(price: number) {
total += price; // Mutação externa
}// ✅ Bom
try {
const data = await fetchData();
return processData(data);
} catch (error) {
if (error instanceof TypeError) {
// Tratamento específico
}
throw new Error('Processing failed', { cause: error });
}
// ❌ Ruim
const data = await fetchData(); // Pode quebrar sem try-catch// ✅ Bom - Server Component (default)
export default async function Page() {
const data = await fetchData(); // Executa no servidor
return <div>{data}</div>;
}
// ❌ Ruim - Client Component desnecessário
'use client';
export default function Page() { ... }Contribuições são bem-vindas! Siga estas diretrizes:
# 1. Fork o repositório
# 2. Clone seu fork
git clone https://github.com/SEU-USUARIO/webapp_gas_mais_barato.git
# 3. Crie uma branch
git checkout -b feature/minha-feature
# 4. Faça suas alterações
# 5. Execute testes
npm run type-check
npm run lint
npm test
# 6. Commit (use Conventional Commits)
git commit -m "feat: adiciona filtro por bandeira"
# 7. Push
git push origin feature/minha-feature
# 8. Abra um Pull Requestfeat: nova funcionalidade
fix: correção de bug
docs: documentação
style: formatação
refactor: refatoração
test: testes
chore: tarefas gerais
- TypeScript strict mode OK
- Testes passando
- ESLint sem warnings
- Funções puras quando possível
- Error handling adequado
- Componentes responsivos
- Acessibilidade (ARIA)
- Documentação atualizada
- Filtro por bandeira (Ultragaz, Liquigás, etc.)
- Comparação lado a lado de 2 cidades
- Exportação de dados (CSV, PDF)
- Modo escuro (dark mode)
- Histórico de preços (séries temporais)
- Gráficos de tendência (Chart.js)
- Alertas de preço (push notifications)
- Progressive Web App (PWA)
- Área do usuário (login)
- Favoritos/watchlist de postos
- API pública REST
- App mobile (React Native)
Este projeto está sob a licença MIT. Veja LICENSE para mais detalhes.
- ANP - Agência Nacional do Petróleo, pelos dados oficiais abertos
- Vercel - Plataforma de deploy Next.js
- Comunidade Next.js - Framework incrível
- Tailwind CSS - Framework CSS utilitário
- Todos os contribuidores - Obrigado! ❤️
- 🐛 Bugs: Abrir issue
- 💡 Features: Discussões
- 📧 Email: suporte@gasmaisbarato.com