Pular para o conteúdo principal

Resolução de Problemas

Este guia cataloga os problemas reais observados no projeto, organizados por categoria. Para cada problema: Sintoma, Causa e Solução objetiva.


1. Banco de Dados

1.1 Servidor MySQL inacessível

Sintoma:

Error: Can't reach database server at `localhost:3306`
PrismaClientInitializationError: P1001

Causa: O servidor MySQL não está em execução, ou DATABASE_URL aponta para um host/porta incorretos.

Solução:

# Subir o MySQL via Docker Compose
npm run db:up

# Verificar se o container está rodando
docker ps | grep mysql

# Verificar a DATABASE_URL no .env
cat .env | grep DATABASE_URL
# Formato esperado: mysql://usuario:senha@localhost:3306/nome_banco

1.2 Permissão negada ao usuário MySQL

Sintoma:

Error: P1010 - User `usuario` was denied access on the database `ljvemasearomas`

Causa: O usuário MySQL não tem as permissões necessárias para acessar o banco de dados.

Solução:

-- Conectar ao MySQL como root
mysql -u root -p

-- Conceder permissões completas
GRANT ALL PRIVILEGES ON ljvemasearomas.* TO 'usuario'@'localhost';
GRANT ALL PRIVILEGES ON ljvemasearomas.* TO 'usuario'@'%';
FLUSH PRIVILEGES;

1.3 Variável DATABASE_URL não encontrada

Sintoma:

Error: Environment variable not found: DATABASE_URL

Causa: O arquivo .env não existe na raiz do projeto.

Solução:

# Criar o .env a partir do template
cp .env.example .env

# Editar com os valores corretos
# DATABASE_URL=mysql://root:root@localhost:3306/ljvemasearomas

Após criar o .env, reiniciar o servidor de desenvolvimento (npm run dev).


1.4 Prisma Client não gerado

Sintoma:

Error: Module not found: Can't resolve '@/lib/db'
Error: @prisma/client did not initialize yet

Causa: O Prisma Client não foi gerado. Isso acontece após um npm ci limpo, em novos clones ou após alterações no schema.prisma.

Solução:

npx prisma generate

Este comando deve ser executado antes de qualquer next build, next dev ou execução de testes. Ele não requer conexão com o banco de dados.


1.5 Falha de constraint única no seed

Sintoma:

Error: Unique constraint failed on the fields: (`email`)
PrismaClientKnownRequestError: P2002

Causa: O banco já foi populado anteriormente (usuário admin ou dados de seed já existem). Rodar db:seed duas vezes causa conflito.

Solução:

# Resetar o banco (apaga tudo e reaplicar migrations + seed)
npm run db:reset

Atenção: db:reset apaga todos os dados do banco de desenvolvimento. Nunca executar em produção.


2. Ambiente

2.1 Porta 3000 em uso

Sintoma:

Error: listen EADDRINUSE: address already in use :::3000

Causa: Outra instância do servidor (ou outro processo) já está ocupando a porta 3000.

Solução:

No Windows:

netstat -ano | findstr :3000
# Anota o PID na última coluna
taskkill /F /PID <PID>

No Linux/macOS:

lsof -i :3000
# Anota o PID
kill -9 <PID>

Alternativamente, iniciar o servidor em outra porta:

PORT=3001 npm run dev

2.2 Permissão negada na pasta de uploads

Sintoma:

Error: EACCES: permission denied, mkdir './public/uploads'

Causa: O processo Node.js não tem permissão de escrita no diretório public/uploads (comum em servidores Linux após deploy).

Solução:

# No servidor de produção (Linux)
chmod 755 public/uploads

# Se a pasta não existir
mkdir -p public/uploads
chmod 755 public/uploads

# Verificar o usuário do processo PM2
pm2 info ljvemasearomas | grep "exec user"
# Garantir que o usuário tem acesso à pasta
chown -R <usuario-pm2>:<grupo> public/uploads

2.3 Módulo não encontrado com path alias @/

Sintoma:

Module not found: Can't resolve '@/components/ui/button'

Causa: Path aliases TypeScript (@/) não estão configurados no ambiente de execução, ou o Prisma Client não foi gerado.

Solução:

  1. Verificar se tsconfig.json contém "paths": { "@/*": ["./src/*"] } ou "./*"
  2. Verificar se next.config.ts não está sobrescrevendo os aliases
  3. Rodar npx prisma generate se o import for de @/lib/db
  4. Parar e reiniciar o servidor de desenvolvimento completamente

3. Testes

3.1 Seed falha com timeout no CI

Sintoma:

Error: P1001 - Can't reach database server at `localhost:3306`
(no seed step in CI)

Causa: O container MySQL ainda não está pronto para aceitar conexões quando o Prisma tenta conectar, ou DATABASE_URL está incorreta no step do workflow.

Solução:

# No workflow .github/workflows/tests.yml
- name: Aguardar MySQL estar pronto
run: |
for i in $(seq 1 30); do
mysqladmin ping -h 127.0.0.1 -u root -proot --silent && break
echo "Aguardando MySQL... tentativa $i"
sleep 2
done

- name: Executar seed
run: npm run db:seed
env:
DATABASE_URL: mysql://root:root@127.0.0.1:3306/ljvemasearomas_test

3.2 Testes E2E sem browsers instalados

Sintoma:

Error: browserType.launch: Executable doesn't exist at
/root/.cache/ms-playwright/chromium-1097/chrome-linux/chrome

Causa: O Playwright foi instalado via npm ci, mas os binários dos browsers não foram baixados. Isso ocorre em toda máquina nova ou container CI limpo.

Solução:

# Instalar apenas o Chromium (usado nos testes deste projeto)
npx playwright install chromium

# Se houver dependências de sistema faltando (Linux)
npx playwright install-deps chromium

No GitHub Actions, adicionar este step antes do step de execução dos testes.


3.3 Login helper falha nos testes E2E

Sintoma:

Error: expect(page).toHaveURL('/admin/dashboard')
Received URL: '/login'

Os testes E2E passam localmente mas falham no CI.

Causa: O banco de dados de testes não foi populado antes dos testes. O helper de login (e2e/helpers/auth.ts) tenta autenticar com credenciais do seed (ex.: admin@ljvemas.com.br), mas o usuário não existe.

Solução:

# Antes de rodar os testes E2E, sempre executar o seed
npm run db:seed

# Ordem correta no CI:
# 1. prisma migrate deploy
# 2. npm run db:seed ← obrigatório
# 3. npm run test:e2e

3.4 Hydration mismatch no carrinho

Sintoma:

Warning: Hydration failed because the initial UI does not match
what was rendered on the server.

Aparece relacionado ao componente de carrinho ou ao estado isOpen.

Causa: O campo isOpen estava sendo persistido no localStorage via Zustand partialize, causando divergência entre o estado do servidor (sempre false) e o estado hidratado no cliente.

Status: Já corrigido — isOpen foi removido do partialize da store do carrinho. Se o erro reaparecer, verificar se algum campo de UI (modais, drawers, estados de abertura) foi adicionado acidentalmente ao partialize.

// store/cart-store.ts — configuração correta
partialize: (state) => ({
items: state.items,
// isOpen NÃO deve ser incluído aqui
}),

4. E-mail

4.1 Gmail bloqueando envio SMTP

Sintoma:

Error: Invalid login: 535-5.7.8 Username and Password not accepted

Causa: O Gmail bloqueia autenticação com senha comum por conta da política de segurança "Acesso a apps menos seguros". O uso de senha da conta Google no SMTP não é mais permitido.

Solução: Criar uma Senha de App no Google:

  1. Acessar myaccount.google.com
  2. Segurança → Verificação em duas etapas (deve estar ativada)
  3. Segurança → Senhas de app
  4. Selecionar "Outro (nome personalizado)" → ljvemasearomas
  5. Copiar a senha gerada (16 caracteres)
  6. No .env, usar a senha de app:
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=seu@gmail.com
EMAIL_PASS=abcd efgh ijkl mnop # senha de app (sem espaços na prática)

4.2 Testando envio com Mailtrap

Para ambiente de desenvolvimento e staging, usar o Mailtrap em vez de um servidor SMTP real. O Mailtrap captura todos os e-mails sem entregá-los, permitindo inspecionar o conteúdo.

# .env (desenvolvimento)
EMAIL_HOST=sandbox.smtp.mailtrap.io
EMAIL_PORT=2525
EMAIL_USER=<usuario-mailtrap>
EMAIL_PASS=<senha-mailtrap>

Credenciais disponíveis em mailtrap.io → Inbox → SMTP Settings.


4.3 Testando o envio de e-mail manualmente

npm run test:smtp

Se o script não existir, testar via Node.js diretamente:

// scripts/test-email.ts
import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: Number(process.env.EMAIL_PORT),
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

await transporter.sendMail({
from: process.env.EMAIL_USER,
to: "teste@exemplo.com",
subject: "Teste de envio",
text: "E-mail de teste do sistema ljvemasearomas",
});

console.log("E-mail enviado com sucesso");
npx tsx scripts/test-email.ts

5. Frete

5.1 Provider retornando erro (token expirado)

Sintoma:

FreightError: 401 Unauthorized — token de acesso expirado
FreightError: Melhor Envio: invalid_client

Causa: O token de acesso da API do provider de frete (Melhor Envio, Correios, etc.) expirou ou foi revogado.

Solução:

  1. Acessar o painel do provider e gerar um novo token de acesso
  2. Atualizar a variável de ambiente correspondente no servidor:
MELHOR_ENVIO_TOKEN=novo_token_aqui
  1. Reiniciar o servidor: pm2 reload ljvemasearomas

5.2 Timeout na consulta de frete

Sintoma:

FreightError: Request timeout after 5000ms
FreightError: ECONNREFUSED — API de frete inacessível

Causa: A API do provider de frete está fora do ar, lenta ou bloqueada por firewall do servidor.

Solução: O sistema possui fallback para tarifas estáticas. Verificar se o fallback está ativado no código:

// lib/freight/index.ts
try {
return await calcularFreteOnline(cep, itens);
} catch (err) {
console.error("Frete online indisponível, usando tarifas estáticas:", err);
return calcularFreteFallback(cep, itens); // tabela de preços estática
}

Se o fallback não estiver implementado, a página de checkout exibirá erro. Adicionar tratamento de erro e exibir mensagem amigável ao usuário enquanto o provider estiver indisponível.


6. Deploy

6.1 PM2 não inicia a aplicação

Sintoma:

[PM2] App [ljvemasearomas] with id [0] and pid [...] exited with code [1] ...

pm2 status mostra errored ou stopped.

Causa: Erro durante a inicialização — pode ser variável de ambiente faltando, build ausente, ou erro de sintaxe no código.

Solução:

# Ver os logs de erro do PM2
pm2 logs ljvemasearomas --lines 50 --nostream

# Verificar se o build existe
ls -la .next/

# Se o build não existir
npm run build && pm2 restart ljvemasearomas

# Verificar variáveis de ambiente
pm2 env 0 # substitua 0 pelo ID do processo

6.2 Nginx retornando 502 Bad Gateway

Sintoma: O site exibe "502 Bad Gateway" no navegador.

Causa: O Nginx não consegue se comunicar com a aplicação Next.js. Geralmente significa que o processo PM2 não está rodando ou está na porta errada.

Solução:

# Verificar se o processo está rodando
pm2 status

# Se não estiver rodando
pm2 start ecosystem.config.js

# Verificar a porta em que o Next.js está escutando
pm2 logs ljvemasearomas | grep "started server"
# Deve mostrar: "started server on 0.0.0.0:3000"

# Verificar configuração do Nginx
cat /etc/nginx/sites-enabled/ljvemasearomas
# A linha proxy_pass deve apontar para http://localhost:3000

6.3 Certificado .pfx da NF-e com erro

Sintoma:

Error: PKCS12 certificate parse error
Error: mac verify failure

Causa: Senha do certificado .pfx incorreta, ou arquivo corrompido durante o upload.

Solução:

# Testar o certificado localmente
openssl pkcs12 -in certificado.pfx -noout
# Será solicitada a senha — deve completar sem erros

# Verificar integridade do arquivo
md5sum certificado.pfx # comparar com o hash do original

Atualizar o certificado no painel admin e garantir que a senha está correta na variável de ambiente correspondente.


6.4 Uploads sem permissão após deploy

Sintoma: Upload de imagens falha com EACCES: permission denied.

Causa: O deploy via git pull ou SSH não preserva as permissões corretas na pasta public/uploads.

Solução:

# No servidor, após o deploy
chmod -R 755 public/uploads
chown -R www-data:www-data public/uploads # ajustar para o usuário correto

# Verificar o usuário do processo PM2
whoami # quando SSH conectado
pm2 info ljvemasearomas | grep "username"

7. Performance

7.1 Queries N+1 com Prisma

Sintoma: Página carrega lentamente; HyperDX mostra dezenas de queries SQL para um único request.

Causa: Loop que executa uma query por iteração em vez de buscar todos os dados em uma única query com include.

Exemplo do problema:

// ❌ N+1: 1 query para pedidos + N queries para cliente de cada pedido
const pedidos = await prisma.order.findMany();
for (const pedido of pedidos) {
const cliente = await prisma.user.findUnique({ where: { id: pedido.userId } });
}

Solução:

// ✅ 1 única query com JOIN via include
const pedidos = await prisma.order.findMany({
include: {
user: { select: { name: true, email: true } },
items: { include: { product: true } },
},
});

7.2 Imagens lentas carregando

Sintoma: Imagens de produto com carregamento lento, LCP (Largest Contentful Paint) alto.

Causa: Imagens servidas em formato JPEG/PNG sem otimização, tamanho original muito grande, ou uso de <img> padrão em vez de next/image.

Solução:

// ❌ Imagem sem otimização
<img src="/uploads/produto.jpg" />

// ✅ Com next/image (converte para WebP automaticamente, lazy loading, tamanhos responsivos)
import Image from "next/image";

<Image
src="/uploads/produto.jpg"
alt="Nome do produto"
width={400}
height={400}
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>

Configurar domínios externos no next.config.ts se as imagens vierem de CDN:

images: {
remotePatterns: [{ protocol: "https", hostname: "cdn.exemplo.com" }],
},

7.3 Bundle JavaScript grande

Sintoma: Relatório do next build mostra chunks acima de 500KB. Carregamento inicial lento (FCP alto).

Causa: Importações de bibliotecas pesadas sem code splitting, ou importação de bibliotecas inteiras quando apenas uma função é necessária.

Solução:

# Analisar o bundle
ANALYZE=true npm run build

Estratégias de redução:

// ❌ Importa a biblioteca inteira (lodash: ~72KB gzipped)
import _ from "lodash";
_.debounce(fn, 300);

// ✅ Importa apenas a função necessária (tree-shaking)
import debounce from "lodash/debounce";

// ❌ Componente pesado carregado sempre
import { HeavyChart } from "@/components/charts/heavy-chart";

// ✅ Carregado apenas quando necessário (lazy loading)
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/charts/heavy-chart"), {
loading: () => <p>Carregando gráfico...</p>,
ssr: false,
});