Pular para o conteúdo principal

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​

  1. Bem-vindo
  2. Pré-requisitos
  3. Fluxo de trabalho Git
  4. Convenções de commits
  5. Estrutura de arquivos e convenções de código
  6. Padrões por camada
  7. Escrevendo testes
  8. Checklist de PR
  9. Atualizando o schema do banco
  10. Adicionando uma integração de terceiros
  11. Code review
  12. 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.md para 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.

FerramentaVersão mínimaObservação
Node.js20.x LTSVersão recomendada para o projeto
npm10.xIncluído com Node.js 20
Git2.xNecessário para versionamento
Docker + Compose24.x / 2.xRecomendado para o banco de dados MySQL
MySQL8.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:up inicia 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:

TipoPrefixoExemplo
Nova featurefeat/feat/filtro-por-faixa-de-preco
Correção de bugfix/fix/500-checkout-estoque-zero
Documentaçãodocs/docs/api-endpoints-v1
Testestest/test/e2e-variantes-produto
Refatoraçãorefactor/refactor/checkout-extrair-steps
Manutençãochore/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/bug ou feat/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​

PrefixoQuando usarExemplo
feat(área)Nova funcionalidadefeat(loja): adiciona filtro por faixa de preço
fix(área)Correção de bugfix(api): corrige 500 em POST /api/orders quando estoque é 0
test(área)Adição ou correção de testestest(e2e): adiciona spec de variantes de produto
docs(área)Documentaçãodocs(api): documenta endpoints da API v1
refactor(área)Refatoração sem mudança de comportamentorefactor(checkout): extrai lógica de cálculo para hook
choreManutenção, dependências, CIchore: atualiza dependências de desenvolvimento
style(área)Ajustes de formatação sem lógicastyle(header): ajusta espaçamento do menu mobile
perf(área)Melhoria de performanceperf(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 #123 no 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​

TipoDiretórioExemplo de arquivo
Componentes da lojacomponents/store/product-card.tsx
Componentes do admincomponents/admin/orders-table.tsx
Design system / UIcomponents/ui/button.tsx, dialog.tsx
Páginas da lojaapp/(store)/app/(store)/page.tsx
Páginas do adminapp/(admin)/app/(admin)/dashboard/page.tsx
Route Handlersapp/api/app/api/products/route.ts
Utilitários e serviçoslib/lib/shipping.ts, lib/email.ts
Stores Zustandstore/store/cart.ts
Tipos globaistypes/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 E2Ee2e/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 unknown em vez de any quando o tipo for realmente desconhecido
  • Prefira interface a type para objetos (use type para 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.error incluindo 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 include para carregar relações em uma única query — evite N+1 queries
  • Use select para retornar apenas os campos necessários
  • Em transações, use prisma.$transaction([...])
  • Após alterar prisma/schema.prisma, rode npx prisma generate para 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-form com zodResolver para 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​

TipoDiretórioSufixoExemplo
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 E2Ee2e/.spec.tse2e/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 lint passa sem erros (o script local enforça --max-warnings=0)
  • npx tsc --noEmit passa sem erros de tipo
  • Nenhum any introduzido sem justificativa documentada em comentário
  • Nenhum console.log, console.debug ou código de debug commitado

Testes​

  • npm test passa 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.local ou 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.prisma acompanhadas de arquivo de migração (npx prisma migrate dev)
  • prisma/seed.js atualizado se novos dados de seed forem necessários
  • docs/DATABASE.md atualizado 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.prisma editado com as novas definições
  • Arquivo de migração gerado com npx prisma migrate dev --name <descricao>
  • prisma/seed.js atualizado se a mudança introduz novos campos obrigatórios
  • docs/DATABASE.md atualizado com a documentação das novas tabelas/campos
  • Testes passando após a mudança (npm test)

9.4 Comandos Prisma de referência​

ComandoDescrição
npm run db:pushSincroniza schema com o banco (sem migração — apenas dev)
npm run db:seedPopula o banco com dados de teste
npm run db:resetApaga tudo e recria com seed (destrói dados!)
npm run db:studioAbre o Prisma Studio em http://localhost:5555
npx prisma migrate dev --name <nome>Cria e aplica migração versionada
npx prisma generateRegenera o Prisma Client após alterar o schema
npx prisma migrate statusMostra 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.example com 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​

PontoO que verificar
Segredos expostosNenhum token, senha ou chave hardcoded
Validação server-sidePreços, estoque e permissões re-validados na API
Inputs sanitizadosTodos os inputs de usuário validados com Zod
Autenticação corretaRotas protegidas usam requireAuth ou requireAdminAuth
Dados sensíveis em logNenhum dado pessoal ou financeiro logado

Performance​

PontoO que verificar
N+1 queriesUsar include do Prisma em vez de queries dentro de loops
use client desnecessárioComponentes que não precisam de interatividade são RSC
Imagens otimizadasUso de <Image> do Next.js com width, height e alt
Imports pesadosImports de bibliotecas grandes feitos de forma seletiva

Acessibilidade​

PontoO que verificar
Labels em inputsTodo <input> tem <label> ou aria-label associado
Alt em imagensToda <Image> tem alt descritivo (vazio apenas para imagens decorativas)
Contraste adequadoTexto sobre fundo tem contraste mínimo de 4.5:1 (WCAG AA)
Elementos interativosBotões e links são focáveis via teclado
Roles semânticosElementos usam as tags HTML semanticamente corretas

Consistência​

PontoO que verificar
Convenções de arquivokebab-case, diretório correto, export default
Padrões de componenteSegue a estrutura dos componentes existentes
Tratamento de estadoUsa Zustand corretamente, sem prop drilling excessivo
Mensagens ao usuárioUsa 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!