Sistema web simples, focado em mobile, para registrar abastecimentos e manutenções de veículos e acompanhar consumo (km/l) e gastos. Acesse em https://pitstop.morenadoaco.com.br.
- Multi-usuário: cada conta só enxerga e mexe nos próprios veículos/registros
- Cadastro aberto com confirmação de e-mail (código de 6 dígitos, válido por 15 min, com limite de tentativas e reenvio), rate limit de 5 cadastros por hora por IP; também dá pra entrar por convite de quem já usa o app (link com token de uso único e validade de 7 dias, e-mail considerado confirmado automaticamente nesse caso)
- Painel Administrativo (conta com papel "admin"): visão agregada de todas as contas — veículos, registros e total gasto por conta — sem acessar o detalhe de nenhum registro individual
- Funciona sem internet: Service Worker + fila offline (IndexedDB) guardam o que você registra sem sinal e sincronizam sozinhos assim que a conexão volta (inclusive em segundo plano, via Background Sync); aviso de "o que mudou" nas primeiras aberturas depois de uma atualização
- Login com bloqueio temporário após tentativas falhas
- Cadastro, edição e exclusão de veículos (nome, tipo, cor, placa) com busca inteligente de modelo (ex.: "Bros 160 2025") que autopreenche capacidade do tanque e peso a partir de um catálogo de modelos comuns no Brasil — campos continuam editáveis à mão se o modelo não estiver no catálogo
- Registro, edição e exclusão de abastecimentos (km, litros, valor pago, combustível: Gasolina Comum/Aditivada, Etanol, Diesel, GNV ou Outro), manutenções (km, valor, descrição) e despesas (Seguro, IPVA, Estacionamento, Pedágio, Multa, Lavagem ou Outro)
- Lembretes de manutenção/documentos por km ou por data (ex.: troca de óleo aos 40.000km, seguro vencendo em uma data), com status Vencido/Próximo/Em dia e alerta no painel principal
- Cálculo automático da última média de consumo (km/l), do preço por litro e do gasto do mês
- Relatórios com gráficos (Chart.js): gasto por mês, km rodado por mês e evolução do consumo, cards de gasto total, gasto médio por dia e preço médio por litro, filtro por veículo e por período (data início/fim), exportação em CSV ou PDF (impressão do navegador), e comparação do consumo real com o de fábrica (cidade/estrada) quando o veículo tem modelo do catálogo vinculado
- Filtro de registros e relatórios por veículo
- Conformidade com a LGPD: política de privacidade, aceite de consentimento obrigatório no cadastro/convite e exclusão definitiva da própria conta e dados (direito ao esquecimento)
- Identidade visual própria (paleta laranja + teal, logo, favicons), com medidor (gauge) de consumo, selos de ícone coloridos nas estatísticas, transições e micro-interações em toda a interface, e manifest PWA (instalável na tela inicial)
- App Android nativo (TWA assinado) pra instalar via APK, além do PWA — página
/instalar.phpcom instruções pras duas formas - Interface mobile-only por design (Bootstrap 5 + Bootstrap Icons) com navegação inferior fixa e botão de novo registro embutido na própria barra (elevado, ao centro) — mesmo layout em qualquer tamanho de tela, só centralizado numa largura de celular a partir de 560px (sem layout separado de desktop/notebook)
- Estados vazios com ícone, texto e chamada para ação em vez de mensagens soltas
- Frontend: HTML5 + Bootstrap 5 (CDN) + Bootstrap Icons + Chart.js (CDN) + identidade visual própria (CSS, com animações) + manifest PWA + Service Worker/IndexedDB (modo offline)
- Backend: PHP 8.2 puro (sem framework), Apache
- Banco: MySQL 8.0, acesso exclusivo via PDO (prepared statements)
- E-mail: cliente SMTP próprio em PHP puro (sem dependências), usado pro envio de convites
- App Android: TWA (Trusted Web Activity) gerado com Bubblewrap, assinado com keystore próprio
- Infra: Docker Compose (build próprio da imagem PHP+Apache hardenizada)
pitstop-br/
├── docker-compose.yml
├── .env # credenciais (gitignored, gerado localmente)
├── db/
│ └── init.sql # schema + seed inicial
├── docker/php/
│ ├── Dockerfile # imagem PHP+Apache hardenizada
│ ├── php.ini # hardening PHP (expose_php off, sessão segura, sem upload...)
│ └── security.conf # hardening Apache (headers, sem listagem de diretório)
└── src/
├── api/
│ ├── registro.php # POST idempotente (client_uuid) usado pela fila offline
│ ├── lembrete.php # POST idempotente (client_uuid) usado pela fila offline
│ └── versao.php # versão + changelog em JSON (aviso de atualização)
├── assets/
│ ├── css/brand.css # identidade visual (paleta, header, bottom-nav, telas de auth)
│ ├── img/ # logo, favicons e ícones PWA
│ └── js/
│ ├── offline.js # registra o SW, intercepta formulários sem sinal, aviso de atualização
│ └── idb-outbox.js # fila offline (IndexedDB), compartilhada com o Service Worker
├── config/
│ ├── bootstrap.php # sessão segura (30 dias, PWA) + carrega conexão/CSRF/auth/funções/versão
│ ├── conexao.php # PDO (lê credenciais do ambiente)
│ ├── csrf.php # geração/validação de token CSRF
│ ├── auth.php # login/registro/logout/guard, papel admin, verificação de e-mail, lockout
│ ├── versao.php # versão do app + changelog (rodapé e aviso de atualização)
│ └── mailer.php # cliente SMTP mínimo (sem dependências) pro envio de convites/códigos
├── includes/
│ ├── functions.php # helpers (escape, flash, cálculo de consumo, validação de registro/lembrete)
│ ├── header.php
│ └── footer.php
├── manifest.json # manifest PWA (instalável na tela inicial)
├── sw.php # Service Worker (cache com versionamento, fallback offline, Background Sync)
├── login.php / cadastro.php / verificar_email.php / logout.php # autenticação + confirmação de e-mail
├── convidar.php / convite.php # envio e aceite de convite (registro por convite)
├── gerenciador.php # painel administrativo (dados agregados por conta; só para papel admin)
├── conta.php / privacidade.php # minha conta (exclusão de dados) e política LGPD
├── index.php # dashboard (última média, gastos do mês, alerta de lembretes, registros)
├── relatorios.php # gráficos de gasto, km rodado e consumo; filtro por período; export CSV/PDF
├── adicionar.php / registro_editar.php / excluir.php # CRUD de registros (abastecimento/manutenção/despesa)
├── lembretes.php / lembrete_concluir.php / lembrete_excluir.php # lembretes de manutenção (km ou data)
└── veiculos.php / veiculo_editar.php / veiculo_excluir.php # CRUD de veículos
- 100% PDO com prepared statements (sem SQL cru/concatenado) — zero SQLi
- Proteção CSRF (token por sessão,
hash_equals) em todo formulário POST - Output sempre escapado (
htmlspecialchars) — zero XSS refletido - Validação estrita (whitelist) de tipo de registro, tipo de veículo, datas e números
- Autenticação: senha com
password_hash/password_verify, bloqueio de 15 min após 5 tentativas falhas, mensagem de erro genérica no login (sem enumeração de e-mail),session_regenerate_idapós login/cadastro - Isolamento multi-usuário: toda consulta/gravação de veículo e registro é restrita por
usuario_id(via FK +JOIN/WHERE), prevenindo IDOR entre contas - Confirmação de e-mail: código de 6 dígitos, armazenado só como hash SHA-256 (nunca em texto plano), validade de 15 min, limite de tentativas e rate limit de reenvio; sem essa confirmação a conta não consegue logar
- Rate limit de cadastro (5 por hora por IP, hash do IP no banco) contra automação de contas em massa
- Papel admin: página administrativa responde 404 (não 403) pra quem não é admin, evitando revelar que a rota existe; painel mostra só dados agregados por conta, nunca o detalhe de um registro
- API offline (
api/registro.php,api/lembrete.php) reusa a mesma validação e o mesmo escopo porusuario_iddos formulários clássicos; inserções são idempotentes porclient_uuid(UNIQUE), então reenviar o mesmo item da fila offline nunca duplica dados - Convites: token de 32 bytes aleatórios, armazenado só como hash SHA-256 no banco (nunca em
texto plano), expira em 7 dias, uso único garantido por lock transacional (
SELECT ... FOR UPDATE) - Exclusão de conta exige reautenticação por senha antes de apagar os dados definitivamente
- Sessão: cookie
HttpOnly,SameSite=Strict,Secure(atrás de proxy HTTPS) - Apache:
ServerTokens Prod, sem listagem de diretório, headersCSP/X-Frame-Options/X-Content-Type-Options/Referrer-Policy, bloqueio de arquivos sensíveis (.env,.sql, etc.) - PHP:
expose_php=Off,display_errors=Off, uploads desabilitados, limites de memória/execução - Container do app:
read_onlyfilesystem,cap_drop: ALL(com apenas as 3 capabilities mínimas necessárias),no-new-privileges, sem privilégio de root persistente - MySQL sem porta exposta ao host — acessível apenas pela rede Docker interna
- Senhas geradas aleatoriamente (
openssl rand), armazenadas só em.env(gitignored,chmod 600) - Limites de CPU/memória por container (
deploy.resources.limits)
Pra enviar convites por e-mail, defina no .env (raiz do projeto, gitignored):
SMTP_HOST=smtp.exemplo.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=noreply@pitstop.morenadoaco.com.br
SMTP_PASS=...
SMTP_FROM=PitStop BR <noreply@pitstop.morenadoaco.com.br>
Sem essas variáveis, o convite continua sendo gerado no banco normalmente, mas o e-mail não é
enviado (fica registrado em log). O cliente SMTP é caseiro (sem dependências externas), suporta
TLS implícito (porta 465) ou STARTTLS (porta 587) com AUTH LOGIN.
cd pitstop-br
docker compose up -d --buildApp disponível em http://127.0.0.1:8033 (atrás de proxy reverso Nginx + TLS em produção).
| Versão | Data | Descrição |
|---|---|---|
| 1.7.0 | 2026-07-01 | Cadastro inteligente de veículo: novos campos cor/placa, e busca de modelo (api/buscar_modelo.php, separa texto de ano automaticamente — ex. "Bros 160 2025") que autopreenche tanque/peso a partir de um novo catálogo (modelos_veiculos, ~20 modelos comuns no Brasil). Relatórios ganham card de comparação do consumo real com o de fábrica (cidade/estrada) quando o veículo tem modelo vinculado. Testado com Playwright: busca retornando o modelo certo, autopreenchimento, salvamento no banco e card de comparação renderizando com os números certos |
| 1.6.9 | 2026-07-01 | Removido o layout separado de desktop/notebook (sidebar, colunas largas) — o app é mobile-only de propósito agora, mesma disposição em qualquer tamanho de tela, só centralizada numa largura de celular a partir de 560px. assets/js/viewport.js removido (órfão, só existia pra decidir esse layout) |
| 1.6.8 | 2026-07-01 | Duas correções: (1) offline logo após instalar o app do zero — o cache de páginas autenticadas só era preenchido no install (que roda antes do primeiro login) ou visitando cada tela manualmente; agora offline.js avisa o Service Worker via postMessage assim que confirma sessão ativa, recarregando o cache sem precisar navegar por tudo primeiro; (2) espaço vazio grande sobrando embaixo da tela em páginas com pouco conteúdo (ex.: nenhum registro ainda) — body/.app-shell/main.container viram uma coluna flex de altura real (100dvh), e o bloco de lista cresce pra preencher o espaço, centralizando só o estado vazio (listas com dados continuam alinhadas no topo). Ambas testadas com Playwright (instalação do zero + bloqueio de rede real, e inspeção do layout renderizado) |
| 1.6.7 | 2026-07-01 | Duas correções sérias do modo offline: (1) o install do Service Worker agora busca cada página autenticada com a sessão atual e só cacheia se vier direto (sem redirect pra login) — antes, cada atualização de versão apagava o cache antigo e deixava tudo vazio até o usuário visitar cada página manualmente; (2) offline.js não confia mais só em navigator.onLine (que reporta "online" mesmo com wifi/dados sem internet de verdade) — testa uma busca real (HEAD /manifest.json, 2.5s) antes de deixar o formulário submeter nativamente, evitando o app travar no aviso de "confirmar reenvio do formulário" do navegador. Fallback final do SW também trocado por uma resposta com a marca do app em vez da tela genérica do navegador. Testado com Playwright com bloqueio de rede real (route.abort) |
| 1.6.6 | 2026-07-01 | Correção de texto/elementos renderizando maiores dentro do app instalado (TWA) do que numa aba comum do navegador: faltava text-size-adjust: 100% no CSS, e sem isso o WebView do Android aplica sozinho um recurso de "aumentar a fonte pra ficar legível" (pensado pra sites antigos não responsivos) — trava em 100% pra sempre respeitar o tamanho definido no CSS |
| 1.6.5 | 2026-07-01 | Correção definitiva do layout "de PC" no celular (sidebar/colunas largas): a tentativa anterior (1.6.2) travava por @media (pointer: fine), mas essa media feature dá falso positivo em aparelhos com caneta (ex.: S Pen em celulares Samsung) mesmo em uso 100% por toque, porque ela vale "verdadeiro" se QUALQUER mecanismo de entrada disponível for de precisão, não só o principal. Novo assets/js/viewport.js usa navigator.maxTouchPoints (a única checagem confiável de tela de toque) pra decidir, via JS, se marca a classe is-desktop-real no <html> — o CSS do layout desktop agora exige essa classe em vez de confiar só na media query. Testado com Playwright simulando os dois cenários (toque+pointer:fine falso-positivo vs. desktop real sem toque) |
| 1.6.4 | 2026-07-01 | Correção crítica do modo offline: o Service Worker registrava (e pré-cacheava páginas autenticadas) já na tela de login, antes do usuário logar — como index.php/adicionar.php/etc. redirecionam pra login.php sem sessão, o fetch() seguia o redirect e guardava a tela de login no cache com a chave da página original, fazendo o app parecer "quebrado" offline. Páginas autenticadas saíram da pré-carga (só é cacheada sob demanda, quando visitada online já logado) e o handler de navegação passou a ignorar explicitamente qualquer resposta que veio de um redirect pra login.php |
| 1.6.3 | 2026-07-01 | Auto-reload quando o Service Worker troca de versão (controllerchange): antes, uma aba já aberta continuava presa na versão antiga até fechar tudo manualmente — agora correções futuras chegam sozinhas, sem intervenção do usuário |
| 1.6.2 | 2026-07-01 | Correção do layout "de PC" surgindo em celulares/PWA/APK: o breakpoint de sidebar (≥992px) passou a exigir pointer: fine (mouse), já que alguns WebViews reportam a largura de viewport errada pro CSS num toque; o .container também trava em 100% de largura fora do modo desktop-com-mouse como segunda camada de proteção |
| 1.6.1 | 2026-07-01 | Correção de bug crítico do modo offline: faltava connect-src no CSP, então o fetch() do Service Worker pro CDN (Bootstrap/ícones) era bloqueado depois do 1º login, derrubando a formatação de todo o app; grandfather de contas anteriores à confirmação de e-mail e ajuste do padding-bottom da bottom-nav vazando nas telas de login/cadastro |
| 1.6.0 | 2026-07-01 | Modo offline completo (Service Worker + fila IndexedDB com sincronização automática/Background Sync, API idempotente por client_uuid), reabertura do cadastro público com confirmação de e-mail por código de 6 dígitos (rate limit por IP), Painel Administrativo com dados agregados por conta (papel admin) e aviso de atualização com changelog simplificado nas primeiras aberturas após uma nova versão |
| 1.5.0 | 2026-07-01 | Categoria "Despesa" no registro (Seguro, IPVA, Estacionamento, Pedágio, Multa, Lavagem, Outro), lembretes de manutenção/documentos por km ou por data com alerta no painel principal, filtro de relatórios por período (data início/fim) e exportação em CSV ou PDF (impressão do navegador) |
| 1.4.0 | 2026-07-01 | Redesign visual com base em pesquisa de apps reais da categoria (Drivvo): correção definitiva do bug do botão de novo registro sobrepondo valores da lista (agora embutido na barra de navegação, não mais flutuante), paleta com duas cores (laranja + teal) e selos de ícone nas estatísticas, estados vazios com ícone/texto/CTA, sidebar de navegação compacta e grade de 2 colunas nos gráficos para telas ≥992px, barras dos gráficos com largura proporcional e emojis trocados por ícones Bootstrap Icons na página de instalação |
| 1.3.0 | 2026-06-30 | Redesign visual (cantos suaves, sombras com tom da marca, transições de toque/hover, entrada animada de página/listas, medidor (gauge) SVG animado com contagem progressiva do km/l, respeitando prefers-reduced-motion), app Android nativo (TWA assinado via Bubblewrap) com APK para download, Digital Asset Links (abre em tela cheia sem barra de URL) e página pública /instalar.php com instruções APK/PWA |
| 1.2.0 | 2026-06-30 | Registro por convite (token único por e-mail, SMTP próprio sem dependências), conformidade LGPD (política de privacidade, consentimento, exclusão de conta), combustível no abastecimento (Gasolina Comum/Aditivada, Etanol, Diesel, GNV, Outro), preço por litro calculado, página de Relatórios com gráficos (gasto por mês, km rodado, evolução do consumo) e reorganização da navegação (dropdown de conta + bottom-nav com Relatórios) |
| 1.1.0 | 2026-06-30 | Multi-usuário: cadastro/login/logout com lockout de tentativas, isolamento de dados por conta (correção de IDOR em exclusão/edição), edição de veículo e registro, identidade visual própria (logo, paleta, favicons) e manifest PWA |
| 1.0.0 | 2026-06-30 | Versão inicial: CRUD de veículos/registros, cálculo de km/l, hardening completo, deploy em produção com Nginx + Let's Encrypt em pitstop.morenadoaco.com.br |