Guia de Contribuição
Obrigado pelo interesse em contribuir com o projeto LJ Velas & Aromas! Este guia foi elaborado para tornar o processo de contribuição claro, consistente e eficiente, independentemente do seu nível de experiência.
Sumário
- Bem-vindo
- Pré-requisitos
- Fluxo de trabalho Git
- Convenções de commits
- Estrutura de arquivos e convenções de código
- Padrões por camada
- Escrevendo testes
- Checklist de PR
- Atualizando o schema do banco
- Adicionando uma integração de terceiros
- Code review
- Dúvidas e suporte
1. Bem-vindo
Este é o repositório da LJ Velas & Aromas — uma loja de velas artesanais construída com Next.js 15 e App Router. O projeto abrange vitrine da loja, painel administrativo, integração com meios de pagamento, cálculo de frete e emissão de NF-e.
Filosofia do projeto
- Qualidade acima de velocidade. Código correto e testado vale mais do que código rápido e frágil.
- Testes são obrigatórios. Toda feature nova e todo bug corrigido devem ter cobertura de testes.
- Consistência é respeito. Seguir as convenções existentes facilita a vida de todos que mantêm o projeto.
- Segurança não é opcional. Preços, estoque e autenticação são sempre validados no servidor.
- Sem surpresas no CI. O pipeline deve sempre passar antes de um PR ser mergeado.
Novo no projeto? Comece por
docs/SETUP.mdpara configurar o ambiente de desenvolvimento local, depois volte aqui.
2. Pré-requisitos
Certifique-se de ter as ferramentas abaixo instaladas antes de começar. Para instruções de instalação detalhadas, consulte docs/SETUP.md.
| Ferramenta | Versão mínima | Observação |
|---|---|---|
| Node.js | 20.x LTS | Versão recomendada para o projeto |
| npm | 10.x | Incluído com Node.js 20 |
| Git | 2.x | Necessário para versionamento |
| Docker + Compose | 24.x / 2.x | Recomendado para o banco de dados MySQL |
| MySQL | 8.0+ | Alternativa ao Docker para ambiente local |
Verifique suas versões antes de começar:
node -v # → v20.x.x ou superior
npm -v # → 10.x.x ou superior
git --version # → git version 2.x.x
docker -v # → Docker version 24.x.x (se estiver usando)
Dica: O Docker é o caminho mais rápido para subir o banco de dados em desenvolvimento. Um único
npm run db:upinicia o MySQL 8.0 e o phpMyAdmin sem nenhuma configuração adicional.
3. Fluxo de trabalho Git
3.1 Clone o repositório
# Clone via HTTPS
git clone https://github.com/seu-usuario/ljvemasearomas.git
cd ljvemasearomas
# Instale as dependências (o postinstall já roda prisma generate)
npm install
3.2 Configure o ambiente
# Copie o arquivo de variáveis de ambiente
cp .env.example .env
# Edite o .env com suas configurações locais
# Consulte docs/SETUP.md para detalhes de cada variável
3.3 Crie uma branch a partir de main
A branch padrão do projeto é main. Nunca commite diretamente em main.
Use a seguinte nomenclatura para nomear sua branch:
| Tipo | Prefixo | Exemplo |
|---|---|---|
| Nova feature | feat/ | feat/filtro-por-faixa-de-preco |
| Correção de bug | fix/ | fix/500-checkout-estoque-zero |
| Documentação | docs/ | docs/api-endpoints-v1 |
| Testes | test/ | test/e2e-variantes-produto |
| Refatoração | refactor/ | refactor/checkout-extrair-steps |
| Manutenção | chore/ | chore/atualizar-dependencias |
# Garanta que você está em main e atualizado
git checkout main
git pull origin main
# Crie e acesse sua branch
git checkout -b feat/nome-da-feature
Use nomes de branch em kebab-case, descritivos e em português ou inglês. Evite nomes genéricos como
fix/bugoufeat/mudancas.
3.4 Desenvolva com commits frequentes
Commite com frequência à medida que avança. Commits pequenos e atômicos são mais fáceis de revisar e reverter. Siga as convenções de commits descritas na seção seguinte.
git add .
git commit -m "feat(loja): adiciona filtro por faixa de preço no sidebar"
3.5 Garanta qualidade antes do push
Execute os checks localmente antes de enviar seu código. O CI vai rodar os mesmos checks e falhará se eles não passarem.
# 1. Lint — zero warnings tolerados
npm run lint
# 2. Type check — sem erros de tipo
npx tsc --noEmit
# 3. Testes unitários e de integração
npm test
# 4. (Opcional) Testes E2E — necessário app rodando
npm run test:e2e
3.6 Abra um Pull Request para main
git push origin feat/nome-da-feature
Em seguida, abra um Pull Request no GitHub apontando para main. Use um título claro e uma descrição que explique o quê foi feito e por quê. Consulte o Checklist de PR antes de submeter.
O pipeline de CI (.github/workflows/ci.yml e .github/workflows/tests.yml) será acionado automaticamente. Os testes Playwright E2E rodam apenas em PRs para main, após os testes Jest passarem.
4. Convenções de commits
O projeto adota Conventional Commits com prefixos que descrevem o tipo e a área da mudança. Mensagens de commit claras tornam o histórico legível e facilitam a geração de changelogs.
Formato
<tipo>(<área>): <descrição imperativa em minúsculas>
[corpo opcional — mais detalhes sobre a mudança]
[rodapé opcional — refs de issues, breaking changes]
Tabela de prefixos
| Prefixo | Quando usar | Exemplo |
|---|---|---|
feat(área) | Nova funcionalidade | feat(loja): adiciona filtro por faixa de preço |
fix(área) | Correção de bug | fix(api): corrige 500 em POST /api/orders quando estoque é 0 |
test(área) | Adição ou correção de testes | test(e2e): adiciona spec de variantes de produto |
docs(área) | Documentação | docs(api): documenta endpoints da API v1 |
refactor(área) | Refatoração sem mudança de comportamento | refactor(checkout): extrai lógica de cálculo para hook |
chore | Manutenção, dependências, CI | chore: atualiza dependências de desenvolvimento |
style(área) | Ajustes de formatação sem lógica | style(header): ajusta espaçamento do menu mobile |
perf(área) | Melhoria de performance | perf(produto): adiciona cache no endpoint de produtos em destaque |
sprint(15.x) | Commits de sprint (padrão interno) | sprint(15.3): implementa fluxo de rastreamento de pedidos |
Boas práticas para mensagens de commit
- Use o imperativo presente: "adiciona" e não "adicionado" ou "adicionando"
- A primeira linha deve ter no máximo 72 caracteres
- A área deve ser o módulo afetado:
loja,admin,api,checkout,auth,frete,email,db,e2e, etc. - Se o commit resolve um issue, adicione
Closes #123no rodapé
# Exemplo com corpo e rodapé
git commit -m "fix(api): corrige 500 em POST /api/orders quando estoque é 0
Ao tentar criar um pedido com produto sem estoque, o servidor lançava
uma exceção não tratada. Agora retorna 422 com mensagem adequada.
Closes #47"
5. Estrutura de arquivos e convenções de código
5.1 Nomes de arquivo: kebab-case obrigatório
Todos os arquivos TypeScript/TSX do projeto usam kebab-case. Nunca use PascalCase ou camelCase para nomes de arquivo.
✅ product-card.tsx
✅ cart-drawer.tsx
✅ add-to-cart-button.tsx
✅ use-debounce.ts
✅ shipping-calculator.ts
❌ ProductCard.tsx
❌ cartDrawer.tsx
❌ AddToCartButton.tsx
5.2 Onde cada tipo de arquivo vive
| Tipo | Diretório | Exemplo de arquivo |
|---|---|---|
| Componentes da loja | components/store/ | product-card.tsx |
| Componentes do admin | components/admin/ | orders-table.tsx |
| Design system / UI | components/ui/ | button.tsx, dialog.tsx |
| Páginas da loja | app/(store)/ | app/(store)/page.tsx |
| Páginas do admin | app/(admin)/ | app/(admin)/dashboard/page.tsx |
| Route Handlers | app/api/ | app/api/products/route.ts |
| Utilitários e serviços | lib/ | lib/shipping.ts, lib/email.ts |
| Stores Zustand | store/ | store/cart.ts |
| Tipos globais | types/ | types/index.ts |
| Testes unitários / lib | __tests__/lib/ | __tests__/lib/shipping.test.ts |
| Testes de API | __tests__/api/ | __tests__/api/orders/ |
| Testes de componente | __tests__/components/ | __tests__/components/store/ |
| Testes E2E | e2e/ | e2e/purchase.spec.ts |
5.3 Componentes
- Um componente por arquivo — nunca exporte múltiplos componentes de um mesmo arquivo
- Export default para o componente principal do arquivo
- Named exports para tipos, helpers e constantes relacionados ao componente
// ✅ Padrão correto
export interface ProductCardProps {
product: ProductSummary
priority?: boolean
}
export default function ProductCard({ product, priority = false }: ProductCardProps) {
// ...
}
5.4 Ícones: sempre Lucide React
O projeto usa exclusivamente Lucide React para ícones. Nunca use emojis no JSX como substitutos de ícones funcionais.
// ✅ Correto
import { ShoppingCart, Heart, Search } from 'lucide-react'
function CartButton() {
return <ShoppingCart className="h-5 w-5" />
}
// ❌ Errado — nunca use emojis no JSX
function CartButton() {
return <span>🛒</span>
}
5.5 Comentários: em português
Os comentários de código seguem o português como padrão do projeto, alinhado com a documentação e as mensagens de commit.
// ✅ Correto — comentários em português
// Chave única por item — combina produto + variante quando houver
function cartKey(p: Pick<CartProduct, 'id' | 'variantId'>): string {
return p.variantId ? `${p.id}::${p.variantId}` : p.id
}
5.6 TypeScript: tipagem explícita
- Interfaces e props sempre com tipos explícitos — nunca
any - Retornos de função devem ter tipo declarado quando não for trivialmente inferível
- Use
unknownem vez deanyquando o tipo for realmente desconhecido - Prefira
interfaceatypepara objetos (usetypepara unions, intersections e utilitários)
// ✅ Correto
export interface ProductFilters {
categorySlug?: string
minPrice?: number
maxPrice?: number
inStock?: boolean
}
async function fetchProducts(filters: ProductFilters): Promise<Product[]> {
// ...
}
// ❌ Errado — nunca use `any`
async function fetchProducts(filters: any): Promise<any> {
// ...
}
6. Padrões por camada
6.1 Route Handlers (app/api/)
Todo Route Handler deve seguir o padrão de autenticação, formato de resposta e tratamento de erros adotado no projeto:
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { requireAuth } from '@/lib/api-auth'
import { z } from 'zod'
// Schema Zod para validação do body
const createOrderSchema = z.object({
items: z.array(z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive(),
})).min(1),
})
export async function POST(req: NextRequest) {
// 1. Autenticação
const session = await requireAuth(req)
if (!session) {
return NextResponse.json({ error: 'Não autorizado' }, { status: 401 })
}
// 2. Validação com Zod
const body = await req.json()
const parsed = createOrderSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json(
{ error: 'Dados inválidos', details: parsed.error.flatten() },
{ status: 422 },
)
}
// 3. Lógica de negócio com tratamento de erro
try {
const order = await prisma.order.create({ data: { /* ... */ } })
return NextResponse.json(order, { status: 201 })
} catch (error) {
console.error('[POST /api/orders]', error)
return NextResponse.json({ error: 'Erro interno' }, { status: 500 })
}
}
Regras para Route Handlers:
- Sempre validar o body com Zod antes de processar
- Sempre re-validar preços e disponibilidade de estoque no servidor — nunca confiar nos dados do cliente
- Retornar mensagens de erro sem vazar detalhes internos para o cliente
- Logar erros com
console.errorincluindo o endpoint no prefixo:[POST /api/orders]
6.2 Componentes RSC vs RCC
O App Router permite dois tipos de componentes: React Server Components (RSC) e React Client Components (RCC). Prefira sempre RSC e adicione "use client" apenas quando estritamente necessário.
Use RSC (padrão) quando:
- O componente apenas renderiza dados (busca no banco, lê arquivos)
- Não há interação do usuário (eventos de clique, hover, formulários)
- Não precisa de estado local (
useState) ou efeitos (useEffect) - Não usa hooks de browser (
useRouter,useSearchParams, etc.)
Use RCC ("use client") quando:
- O componente precisa de estado local interativo
- Usa event listeners (
onClick,onChange) - Depende de APIs do browser (
localStorage,window,document) - Usa hooks como
useState,useEffect,useContext - Usa stores Zustand (
useCartStore,useWishlistStore)
// ✅ RSC — busca dados diretamente, sem "use client"
// app/(store)/produtos/page.tsx
async function ProdutosPage() {
const products = await prisma.product.findMany({ where: { active: true } })
return <ProductGrid products={products} />
}
// ✅ RCC — necessário porque usa estado e eventos
// components/store/add-to-cart-button.tsx
'use client'
export default function AddToCartButton({ product }: Props) {
const addItem = useCartStore((s) => s.addItem)
return (
<button onClick={() => addItem(product)}>
<ShoppingCart className="h-4 w-4" /> Adicionar ao carrinho
</button>
)
}
Atenção: Marcar um componente com
"use client"também torna todos os seus filhos componentes cliente. Por isso, mantenha os componentes cliente o mais "folha" possível na árvore.
6.3 Prisma: sempre via singleton
Nunca instancie PrismaClient diretamente. Use sempre o singleton exportado de @/lib/prisma:
// ✅ Correto — sempre importe do singleton
import { prisma } from '@/lib/prisma'
const products = await prisma.product.findMany({
include: { category: true, variants: true },
})
// ❌ Errado — nunca instancie diretamente
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient() // causa múltiplas conexões em dev!
Boas práticas com Prisma:
- Use
includepara carregar relações em uma única query — evite N+1 queries - Use
selectpara retornar apenas os campos necessários - Em transações, use
prisma.$transaction([...]) - Após alterar
prisma/schema.prisma, rodenpx prisma generatepara atualizar o client
6.4 Zustand: stores tipados com partialize
As stores Zustand ficam em store/ e devem ter tipagem explícita. Use persist com partialize para nunca salvar estado volátil (como se um drawer está aberto) no localStorage:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
// ✅ Interface explícita para o estado e as ações
interface ExampleStore {
// Estado persistido
items: Item[]
// Estado volátil (não deve ser salvo)
isLoading: boolean
// Ações
addItem: (item: Item) => void
setLoading: (loading: boolean) => void
}
export const useExampleStore = create<ExampleStore>()(
persist(
(set) => ({
items: [],
isLoading: false,
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
setLoading: (loading) => set({ isLoading: loading }),
}),
{
name: 'ljvemasearomas-example',
// ✅ partialize exclui estado volátil da persistência
partialize: (state) => ({ items: state.items }),
},
),
)
6.5 Validação: Zod e React Hook Form
- API Routes: sempre use Zod para validar o corpo das requisições (
.safeParse()) - Formulários: sempre use
react-hook-formcomzodResolverpara validação client-side
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
// ✅ Schema Zod compartilhado entre frontend e backend
const addressSchema = z.object({
cep: z.string().regex(/^\d{8}$/, 'CEP inválido'),
street: z.string().min(3, 'Rua é obrigatória'),
number: z.string().min(1, 'Número é obrigatório'),
city: z.string().min(2, 'Cidade é obrigatória'),
state: z.string().length(2, 'UF deve ter 2 caracteres'),
})
type AddressFormData = z.infer<typeof addressSchema>
function AddressForm() {
const { register, handleSubmit, formState: { errors } } = useForm<AddressFormData>({
resolver: zodResolver(addressSchema),
})
const onSubmit = (data: AddressFormData) => { /* ... */ }
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('cep')} placeholder="CEP" />
{errors.cep && <span>{errors.cep.message}</span>}
{/* ... */}
</form>
)
}
7. Escrevendo testes
O projeto usa Jest + React Testing Library para testes unitários e de componentes, e Playwright para testes E2E. A cobertura de código é medida sobre lib/** e components/** com um threshold mínimo de 60% em branches, funções, linhas e statements.
7.1 Onde criar cada tipo de teste
| Tipo | Diretório | Sufixo | Exemplo |
|---|---|---|---|
| Utilitários / lib | __tests__/lib/ | .test.ts | __tests__/lib/shipping.test.ts |
| Route Handlers (integração) | __tests__/api/ | .test.ts | __tests__/api/orders/create.test.ts |
| Componentes RTL | __tests__/components/ | .test.tsx | __tests__/components/store/product-card.test.tsx |
| Fluxos E2E | e2e/ | .spec.ts | e2e/purchase.spec.ts |
7.2 Scripts de teste
# Roda todos os testes Jest uma vez
npm test
# Modo watch — ideal durante o desenvolvimento
npm run test:watch
# Modo CI — com coverage e forceExit (usado no pipeline)
npm run test:ci
# Testes E2E com Playwright (requer app rodando)
npm run test:e2e
# Playwright com interface visual (debug interativo)
npm run test:e2e:ui
# Abre o relatório HTML do último run E2E
npm run test:e2e:report
7.3 Teste unitário de utilitário
import { formatPrice, calculateDiscount } from '@/lib/utils'
describe('formatPrice', () => {
it('formata valores em reais com duas casas decimais', () => {
expect(formatPrice(1990)).toBe('R$ 19,90')
expect(formatPrice(100000)).toBe('R$ 1.000,00')
})
it('retorna R$ 0,00 para valores zero ou negativos', () => {
expect(formatPrice(0)).toBe('R$ 0,00')
expect(formatPrice(-50)).toBe('R$ 0,00')
})
})
describe('calculateDiscount', () => {
it('aplica desconto percentual corretamente', () => {
expect(calculateDiscount(10000, 10)).toBe(9000)
})
it('não permite desconto maior que 100%', () => {
expect(calculateDiscount(10000, 150)).toBe(0)
})
})
7.4 Teste de Route Handler (integração com SQLite)
Os testes de API usam SQLite em memória (DATABASE_URL=file:./test.db) para isolar do banco de produção:
import { POST } from '@/app/api/products/route'
import { prisma } from '@/lib/prisma'
// Limpa o banco antes de cada teste para garantir isolamento
beforeEach(async () => {
await prisma.product.deleteMany()
})
afterAll(async () => {
await prisma.$disconnect()
})
describe('POST /api/products', () => {
it('cria produto com dados válidos e retorna 201', async () => {
const req = new Request('http://localhost/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Vela Lavanda', price: 3990, stock: 10 }),
})
const res = await POST(req as any)
const data = await res.json()
expect(res.status).toBe(201)
expect(data.name).toBe('Vela Lavanda')
expect(data.price).toBe(3990)
})
it('retorna 422 quando campos obrigatórios estão ausentes', async () => {
const req = new Request('http://localhost/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Produto sem preço' }),
})
const res = await POST(req as any)
expect(res.status).toBe(422)
})
})
7.5 Teste de componente (React Testing Library)
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ProductCard from '@/components/store/product-card'
const mockProduct = {
id: '1',
name: 'Vela Soja Lavanda',
slug: 'vela-soja-lavanda',
price: 3990,
image: '/products/lavanda.jpg',
}
describe('ProductCard', () => {
it('renderiza o nome e preço do produto', () => {
render(<ProductCard product={mockProduct} />)
expect(screen.getByText('Vela Soja Lavanda')).toBeInTheDocument()
expect(screen.getByText('R$ 39,90')).toBeInTheDocument()
})
it('exibe imagem com alt text acessível', () => {
render(<ProductCard product={mockProduct} />)
const img = screen.getByRole('img', { name: /vela soja lavanda/i })
expect(img).toBeInTheDocument()
})
it('navega para a página do produto ao clicar', async () => {
render(<ProductCard product={mockProduct} />)
const link = screen.getByRole('link')
expect(link).toHaveAttribute('href', '/produtos/vela-soja-lavanda')
})
})
7.6 Teste E2E (Playwright)
import { test, expect } from '@playwright/test'
test.describe('Fluxo de compra', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/produtos/vela-soja-lavanda')
})
test('adiciona produto ao carrinho e verifica contador', async ({ page }) => {
await page.getByRole('button', { name: /adicionar ao carrinho/i }).click()
const cartBadge = page.getByTestId('cart-badge')
await expect(cartBadge).toHaveText('1')
})
test('aplica cupom de desconto no checkout', async ({ page }) => {
await page.getByRole('button', { name: /adicionar ao carrinho/i }).click()
await page.goto('/checkout')
await page.getByPlaceholder(/código do cupom/i).fill('PRIMEIRAVELA10')
await page.getByRole('button', { name: /aplicar/i }).click()
await expect(page.getByText(/desconto aplicado/i)).toBeVisible()
})
})
7.7 Regras de cobertura de testes
- Toda feature nova deve ter ao menos um teste — unitário, de integração ou E2E
- Todo bug corrigido deve ter um teste de regressão — garanta que o bug não volte
- Testes de comportamento são mais valiosos do que testes de implementação — teste o que o usuário vê e faz, não os detalhes internos
8. Checklist de PR
Antes de submeter seu Pull Request, revise cada item abaixo:
Qualidade de código
-
npm run lintpassa sem erros (o script local enforça--max-warnings=0) -
npx tsc --noEmitpassa sem erros de tipo - Nenhum
anyintroduzido sem justificativa documentada em comentário - Nenhum
console.log,console.debugou código de debug commitado
Testes
-
npm testpassa com todos os testes verdes - Testes novos ou atualizados para cobrir a mudança
- Se corrigiu um bug: existe um teste de regressão que falha antes da correção e passa depois
Segurança e dados
- Nenhum arquivo
.env,.env.localou segredo commitado - Nenhuma chave de API ou token hardcoded no código
- Preços e estoque re-validados no servidor (nunca confiar no payload do cliente)
- Inputs do usuário validados com Zod antes de qualquer operação de banco
Banco de dados (se aplicável)
- Alterações no
prisma/schema.prismaacompanhadas de arquivo de migração (npx prisma migrate dev) -
prisma/seed.jsatualizado se novos dados de seed forem necessários -
docs/DATABASE.mdatualizado com as mudanças de schema
Descrição do PR
- Título do PR segue as convenções de commit (
feat(área): descrição) - Descrição explica o quê foi feito e por quê
- Issues relacionadas referenciadas no corpo (
Closes #123,Refs #456) - Screenshots ou GIFs incluídos para mudanças visuais
Template de descrição de PR
## O que foi feito
Descreva brevemente as mudanças introduzidas neste PR.
## Por que foi feito
Contexto e motivação para a mudança.
## Como testar
Passo a passo para verificar o comportamento esperado.
## Screenshots (se aplicável)
Inclua capturas de tela para mudanças de UI.
## Issues relacionadas
Closes #
9. Atualizando o schema do banco
O banco de dados é gerenciado pelo Prisma ORM. Siga este fluxo ao fazer alterações no schema:
9.1 Fluxo em desenvolvimento
# 1. Edite o schema
# Arquivo: prisma/schema.prisma
# 2. Sincronize o banco local (sem gerar arquivo de migração)
npm run db:push
# 3. Regenere o Prisma Client
npx prisma generate
# 4. Teste sua mudança
npm test
db:pushé ideal para exploração rápida em desenvolvimento. Não gera arquivos de migração rastreáveis — use apenas localmente.
9.2 Fluxo para mudanças que vão para produção
# 1. Edite prisma/schema.prisma
# 2. Gere um arquivo de migração versionado
npx prisma migrate dev --name descricao-da-mudanca
# Exemplo: npx prisma migrate dev --name add-product-weight-field
# Isso irá:
# - Criar um arquivo em prisma/migrations/TIMESTAMP_descricao-da-mudanca/migration.sql
# - Aplicar a migração ao banco local
# - Regenerar o Prisma Client automaticamente
# 3. Revise o SQL gerado antes de commitar
cat prisma/migrations/*/migration.sql
Nunca altere um arquivo de migração já commitado. Crie sempre uma nova migração para corrigir uma migração anterior.
9.3 Checklist de mudança de schema
-
prisma/schema.prismaeditado com as novas definições - Arquivo de migração gerado com
npx prisma migrate dev --name <descricao> -
prisma/seed.jsatualizado se a mudança introduz novos campos obrigatórios -
docs/DATABASE.mdatualizado com a documentação das novas tabelas/campos - Testes passando após a mudança (
npm test)
9.4 Comandos Prisma de referência
| Comando | Descrição |
|---|---|
npm run db:push | Sincroniza schema com o banco (sem migração — apenas dev) |
npm run db:seed | Popula o banco com dados de teste |
npm run db:reset | Apaga tudo e recria com seed (destrói dados!) |
npm run db:studio | Abre o Prisma Studio em http://localhost:5555 |
npx prisma migrate dev --name <nome> | Cria e aplica migração versionada |
npx prisma generate | Regenera o Prisma Client após alterar o schema |
npx prisma migrate status | Mostra status das migrações pendentes |
10. Adicionando uma integração de terceiros
Ao integrar um novo serviço externo (gateway de pagamento, transportadora, serviço de e-mail, etc.), siga este processo:
10.1 Estrutura e localização
# Crie o wrapper/client em lib/
lib/nome-do-servico.ts
# Exemplos existentes no projeto:
lib/mercadopago.ts # Cliente do Mercado Pago
lib/email.ts # Nodemailer / Resend
lib/shipping.ts # Cálculo de frete (múltiplos provedores)
lib/nfe.ts # Serviço de NF-e
10.2 Variáveis de ambiente
Toda credencial de integração deve passar por variável de ambiente. Nunca hardcode tokens ou chaves no código.
# 1. Adicione ao .env.example com comentário explicativo
# Serviço XYZ — obtenha em https://servico.com/developers
SERVICO_XYZ_API_KEY="sua-chave-aqui"
SERVICO_XYZ_WEBHOOK_SECRET="seu-secret-aqui"
# 2. Adicione ao seu .env local com os valores reais
# (o .env nunca é commitado — está no .gitignore)
10.3 Documentação obrigatória
- Documente as novas variáveis em
docs/SETUP.md(seção de configuração do.env) - Se a integração afeta o deploy, documente em
docs/DEPLOYMENT.md - Adicione um comentário JSDoc no wrapper explicando o propósito e limitações do serviço
10.4 Padrão de implementação
/**
* Cliente para o Serviço XYZ.
* Documentação: https://docs.servico.xyz/api
*
* Variáveis necessárias:
* - SERVICO_XYZ_API_KEY: chave de API obtida no painel do serviço
*/
const API_KEY = process.env.SERVICO_XYZ_API_KEY
const BASE_URL = 'https://api.servico.xyz/v1'
if (!API_KEY && process.env.NODE_ENV === 'production') {
throw new Error('SERVICO_XYZ_API_KEY não configurada')
}
export async function fetchFromServico<T>(endpoint: string): Promise<T> {
const res = await fetch(`${BASE_URL}${endpoint}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
})
// ✅ Fallback gracioso — nunca deixe uma integração travar o fluxo principal
if (!res.ok) {
console.error(`[servico-xyz] Erro ${res.status} em ${endpoint}`)
throw new Error(`Serviço indisponível: ${res.status}`)
}
return res.json() as Promise<T>
}
10.5 Testes com mock
Toda integração de terceiros deve ter testes usando mock para não depender do serviço externo em CI:
// Use jest.mock ou MSW para interceptar as chamadas HTTP
jest.mock('@/lib/nome-do-servico', () => ({
fetchFromServico: jest.fn().mockResolvedValue({ status: 'ok' }),
}))
import { fetchFromServico } from '@/lib/nome-do-servico'
describe('Integração ServicoXYZ', () => {
it('processa resposta de sucesso corretamente', async () => {
const result = await fetchFromServico('/endpoint')
expect(result).toEqual({ status: 'ok' })
})
it('lança erro quando serviço retorna falha', async () => {
(fetchFromServico as jest.Mock).mockRejectedValueOnce(
new Error('Serviço indisponível: 503')
)
await expect(fetchFromServico('/endpoint')).rejects.toThrow('503')
})
})
10.6 Checklist de nova integração
- Wrapper criado em
lib/nome-do-servico.ts - Variáveis de ambiente adicionadas em
.env.examplecom comentários - Variáveis documentadas em
docs/SETUP.md - Deploy documentado em
docs/DEPLOYMENT.md(se aplicável) - Fallback ou tratamento de erro gracioso implementado
- Testes com mock cobrindo cenários de sucesso e falha
11. Code review
O que os revisores verificam
O processo de code review tem como objetivo garantir qualidade, segurança e consistência. Os revisores verificarão:
Segurança
| Ponto | O que verificar |
|---|---|
| Segredos expostos | Nenhum token, senha ou chave hardcoded |
| Validação server-side | Preços, estoque e permissões re-validados na API |
| Inputs sanitizados | Todos os inputs de usuário validados com Zod |
| Autenticação correta | Rotas protegidas usam requireAuth ou requireAdminAuth |
| Dados sensíveis em log | Nenhum dado pessoal ou financeiro logado |
Performance
| Ponto | O que verificar |
|---|---|
| N+1 queries | Usar include do Prisma em vez de queries dentro de loops |
use client desnecessário | Componentes que não precisam de interatividade são RSC |
| Imagens otimizadas | Uso de <Image> do Next.js com width, height e alt |
| Imports pesados | Imports de bibliotecas grandes feitos de forma seletiva |
Acessibilidade
| Ponto | O que verificar |
|---|---|
| Labels em inputs | Todo <input> tem <label> ou aria-label associado |
| Alt em imagens | Toda <Image> tem alt descritivo (vazio apenas para imagens decorativas) |
| Contraste adequado | Texto sobre fundo tem contraste mínimo de 4.5:1 (WCAG AA) |
| Elementos interativos | Botões e links são focáveis via teclado |
| Roles semânticos | Elementos usam as tags HTML semanticamente corretas |
Consistência
| Ponto | O que verificar |
|---|---|
| Convenções de arquivo | kebab-case, diretório correto, export default |
| Padrões de componente | Segue a estrutura dos componentes existentes |
| Tratamento de estado | Usa Zustand corretamente, sem prop drilling excessivo |
| Mensagens ao usuário | Usa react-hot-toast para feedback de ações |
Como responder a uma revisão
- Endereçe cada comentário com uma ação: corrija, explique por que não concorda, ou pergunte se não entendeu
- Commits de fixup após a revisão devem seguir o mesmo padrão:
fix(área): endereça review — descrição - Uma vez que todos os comentários estejam resolvidos e os checks do CI estejam verdes, o PR está pronto para merge
12. Dúvidas e suporte
Onde pedir ajuda
- Dúvidas sobre o projeto: Abra uma Discussion no GitHub
- Bug encontrado: Abra um Issue seguindo o template abaixo
- Dúvidas sobre setup: Consulte primeiro
docs/SETUP.md— a maioria dos problemas comuns está documentada lá
Como reportar um bug
Ao abrir um issue de bug, inclua as seguintes informações para facilitar a reprodução e correção:
**Versão do Node.js:**
node -v → v20.x.x
**Descrição do bug:**
Descrição clara e concisa do comportamento incorreto.
**Passos para reproduzir:**
1. Acesse a página '...'
2. Clique em '...'
3. Preencha o campo '...' com '...'
4. Veja o erro
**Comportamento esperado:**
O que deveria acontecer.
**Comportamento atual:**
O que está acontecendo.
**Screenshots / Logs:**
Se aplicável, adicione capturas de tela ou a mensagem de erro do console.
**Ambiente:**
- OS: Windows / macOS / Linux
- Browser: Chrome 120, Firefox 121, etc.
- Versão do projeto: (hash do último commit)
Como sugerir uma melhoria
Para sugestões de nova funcionalidade ou melhoria, abra um issue com o prefixo [Feature Request] no título e descreva:
- Motivação: qual problema essa melhoria resolve?
- Proposta: como você imagina a solução?
- Alternativas consideradas: quais outras abordagens foram descartadas e por quê?
Feito com 🕯️ pela equipe LJ Velas & Aromas.
Sua contribuição faz o projeto crescer — obrigado por dedicar seu tempo a torná-lo melhor!