first commit
This commit is contained in:
58
docs/01-visao-geral.md
Normal file
58
docs/01-visao-geral.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 1. Visão Geral do Projecto
|
||||
|
||||
## 1.1 Identificação do Problema
|
||||
|
||||
Em Portugal, estima-se que existam mais de 300 mil animais em canis e associações de protecção animal, enfrentando condições precárias e longos períodos de espera por adopção. A falta de uma plataforma centralizada, acessível e moderna dificulta tanto o processo de adopção como a coordenação de doações de bens essenciais como ração, brinquedos e apoio financeiro.
|
||||
|
||||
Os canis dependem maioritariamente de redes sociais e contacto telefónico para gerir adopções e receber doações, o que resulta em processos ineficientes, informação desactualizada e barreiras significativas para potenciais adoptantes e doadores.
|
||||
|
||||
---
|
||||
|
||||
## 1.2 A Solução — PawLink
|
||||
|
||||
**PawLink** é uma plataforma web que centraliza a adopção de animais e a gestão de doações para canis em todo o território português. A plataforma conecta potenciais adoptantes, doadores e canis numa interface intuitiva, segura e moderna.
|
||||
|
||||
**Missão:** Reduzir o número de animais em canis portugueses através da tecnologia, tornando a adopção responsável e a doação de bens o caminho mais fácil e natural.
|
||||
|
||||
---
|
||||
|
||||
## 1.3 Público-Alvo
|
||||
|
||||
| Segmento | Perfil | Necessidade Principal |
|
||||
|---|---|---|
|
||||
| Adoptante | Adulto(a) +18 anos, residente em Portugal | Encontrar animal compatível próximo da sua localidade |
|
||||
| Doador | Pessoa física ou empresa | Contribuir financeiramente ou com bens para canis |
|
||||
| Canil / Associação | Instituição de protecção animal | Gerir animais, reservas e doações recebidas |
|
||||
| Administrador | Equipa PawLink | Supervisionar toda a plataforma e garantir qualidade |
|
||||
|
||||
---
|
||||
|
||||
## 1.4 Proposta de Valor
|
||||
|
||||
- **Para adoptantes:** descoberta fácil de animais por localidade, espécie, raça e características, com reserva online e confirmação por email
|
||||
- **Para doadores:** fluxo simplificado de doação monetária, ração ou brinquedos, com opção de entrega em casa ou no canil
|
||||
- **Para canis:** painel de gestão de animais, reservas e doações, com visibilidade das necessidades actuais
|
||||
- **Para a sociedade:** redução do número de animais em canis e aumento das adopções responsáveis
|
||||
|
||||
---
|
||||
|
||||
## 1.5 Modelo de Negócio
|
||||
|
||||
A plataforma opera como serviço público sem fins lucrativos na fase inicial, sustentado por:
|
||||
|
||||
1. Percentagem simbólica (1–2%) sobre doações monetárias processadas para manutenção da plataforma
|
||||
2. Parcerias com marcas de alimentação e acessórios para animais (patrocínios éticos)
|
||||
3. Candidatura a fundos europeus de bem-estar animal e digitalização do terceiro sector
|
||||
|
||||
---
|
||||
|
||||
## 1.6 Funcionalidades Principais (Resumo)
|
||||
|
||||
| Área | Funcionalidade |
|
||||
|---|---|
|
||||
| Conta | Registo, login, recuperação de palavra-passe, verificação de idade +18 |
|
||||
| Adopção | Listagem de animais, filtros, ficha do animal, reserva com email de confirmação |
|
||||
| Doação | Monetária (Stripe/MBWay), ração e brinquedos com opção de recolha em casa |
|
||||
| Canis | Perfil público com horários, painel de gestão privado |
|
||||
| IA | Match inteligente, chatbot de suporte, recomendações personalizadas |
|
||||
| UI | Modo claro/escuro, menu lateral, design mobile-first |
|
||||
73
docs/02-requisitos.md
Normal file
73
docs/02-requisitos.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 2. Requisitos do Sistema
|
||||
|
||||
## 2.1 Requisitos Funcionais
|
||||
|
||||
### 2.1.1 Autenticação e Conta
|
||||
|
||||
- O sistema deve permitir o registo de novos utilizadores com nome, email, palavra-passe, data de nascimento e localidade (distrito)
|
||||
- O sistema deve impedir o registo de utilizadores com menos de 18 anos, verificando a data de nascimento no servidor
|
||||
- O sistema deve permitir login por email e palavra-passe
|
||||
- O sistema deve disponibilizar recuperação de palavra-passe por email
|
||||
- O utilizador deve poder alterar os seus dados, palavra-passe e preferências de tema (modo claro/escuro)
|
||||
- O utilizador deve poder consultar o seu histórico de adopções e doações
|
||||
- O utilizador deve poder eliminar a sua conta (direito ao apagamento — RGPD)
|
||||
|
||||
### 2.1.2 Módulo de Adopção
|
||||
|
||||
- A página inicial deve apresentar todos os animais disponíveis para adopção
|
||||
- O utilizador deve poder filtrar animais por: distrito, espécie (cão, gato, outro), raça, sexo (macho/fêmea), estado de esterilização e faixa etária
|
||||
- Cada animal deve ter ficha detalhada com: nome, espécie, raça (cães e gatos), idade, sexo, estado de esterilização, fotografias e canil onde se encontra
|
||||
- Ao clicar no canil na ficha do animal, deve aparecer informação com horário de funcionamento, morada e contacto
|
||||
- Para iniciar uma adopção, o utilizador deve ter sessão activa ou ser redirecionado para login/registo
|
||||
- O utilizador deve poder seleccionar uma data disponível para levantar o animal no canil
|
||||
- Após confirmação da reserva, o animal fica com estado **"Reservado"** e o utilizador recebe email de confirmação com data e detalhes do canil
|
||||
- Animais com urgência de adopção devem ter destaque visual na listagem
|
||||
|
||||
### 2.1.3 Módulo de Doação
|
||||
|
||||
- Para efectuar qualquer doação, o sistema deve confirmar que o utilizador tem sessão activa e +18 anos
|
||||
- O utilizador deve poder escolher entre três tipos de doação: **Monetária**, **Ração** e **Brinquedos**
|
||||
- **Doação Monetária:** selecção de método de pagamento (Cartão de Crédito, MBWay), escolha de canil (da localidade do utilizador ou de outra localidade) e montante
|
||||
- **Doação de Ração:** listagem de canis que aceitam ração, tipos disponíveis (seca/húmida, para cão/gato, adulto/filhote), opção de entrega em casa ou levar pessoalmente ao canil
|
||||
- **Doação de Brinquedos:** listagem de canis que aceitam brinquedos, categorias disponíveis, opção de entrega em casa ou levar pessoalmente ao canil
|
||||
- O utilizador deve receber recibo de doação por email após confirmação
|
||||
|
||||
### 2.1.4 Gestão de Canis (Painel Privado)
|
||||
|
||||
- Canis registados devem ter acesso a painel próprio para gerir animais, reservas e necessidades actuais
|
||||
- Canis devem poder actualizar horários de funcionamento, morada e informações de contacto
|
||||
- Canis devem receber notificação de novas reservas e doações
|
||||
- Canis devem poder indicar que necessidades têm actualmente (ex: ração seca para cão adulto, mantas)
|
||||
- Canis devem poder marcar animais como adoptados após levantamento
|
||||
|
||||
### 2.1.5 Painel de Administração
|
||||
|
||||
- O administrador deve poder visualizar estatísticas gerais (adopções, doações, utilizadores activos)
|
||||
- O administrador deve poder moderar canis e utilizadores
|
||||
- O administrador deve ter acesso a logs de actividade relevantes
|
||||
|
||||
---
|
||||
|
||||
## 2.2 Requisitos Não-Funcionais
|
||||
|
||||
| Categoria | Requisito | Critério de Aceitação |
|
||||
|---|---|---|
|
||||
| Desempenho | Tempo de carregamento da página inicial | < 2 segundos em ligação 4G |
|
||||
| Segurança | Autenticação e protecção de dados | HTTPS obrigatório, hash bcrypt (custo 12), tokens JWT assinados |
|
||||
| Acessibilidade | Conformidade com directrizes de acessibilidade | WCAG 2.1 Nível AA mínimo |
|
||||
| Responsividade | Compatibilidade mobile e desktop | Funcional em viewports a partir de 320px de largura |
|
||||
| Disponibilidade | Uptime da plataforma | ≥ 99,5% mensal (acordo de nível de serviço) |
|
||||
| RGPD | Protecção de dados pessoais | Consentimento explícito, direito ao apagamento, exportação de dados |
|
||||
| Escalabilidade | Crescimento de utilizadores | Suporte a 10.000 utilizadores activos simultâneos sem degradação |
|
||||
| Email | Entrega de emails transaccionais | Taxa de entrega superior a 98% |
|
||||
| Internacionalização | Idioma da interface | Português de Portugal (pt-PT) como idioma principal |
|
||||
| SEO | Indexação por motores de busca | Páginas públicas com SSR e metadados correctos |
|
||||
|
||||
---
|
||||
|
||||
## 2.3 Restrições
|
||||
|
||||
- A plataforma deve estar em conformidade com o **RGPD** (Regulamento Geral sobre a Protecção de Dados) e legislação portuguesa
|
||||
- Os pagamentos devem ser processados por um fornecedor certificado **PCI DSS** (Stripe)
|
||||
- O registo é **exclusivo para maiores de 18 anos** — validação obrigatória no servidor
|
||||
- Apenas canis registados e verificados pela equipa PawLink podem listar animais para adopção
|
||||
139
docs/03-arquitectura.md
Normal file
139
docs/03-arquitectura.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 3. Arquitectura do Sistema
|
||||
|
||||
## 3.1 Visão Geral
|
||||
|
||||
A arquitectura da PawLink segue o padrão de aplicação web moderna com separação clara entre frontend, backend e serviços externos. Adoptamos uma abordagem **server-first** com Next.js, que permite renderização no servidor (SSR) para melhor SEO e desempenho inicial, combinada com componentes interactivos no cliente onde necessário.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CLIENTE (Browser) │
|
||||
│ React 18 · Tailwind CSS · TanStack Query │
|
||||
└───────────────────────┬─────────────────────────────────────┘
|
||||
│ HTTPS
|
||||
┌───────────────────────▼─────────────────────────────────────┐
|
||||
│ VERCEL (Edge Network) │
|
||||
│ Next.js 14 — SSR · API Routes · Middleware │
|
||||
├──────────────┬──────────────┬───────────────────────────────┤
|
||||
│ Auth Layer │ API Routes │ Static Assets (CDN) │
|
||||
│ NextAuth.js │ Prisma ORM │ Cloudflare │
|
||||
└──────┬───────┴──────┬───────┴───────────────────────────────┘
|
||||
│ │
|
||||
┌──────▼──────┐ ┌─────▼──────────────────────────────────────┐
|
||||
│ Supabase │ │ Serviços Externos │
|
||||
│ PostgreSQL │ │ Stripe · Resend · Anthropic · Upstash │
|
||||
│ + Storage │ │ │
|
||||
└─────────────┘ └──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Camadas da Arquitectura
|
||||
|
||||
### Camada de Apresentação (Frontend)
|
||||
|
||||
| Tecnologia | Função |
|
||||
|---|---|
|
||||
| **Next.js 14+ (App Router)** | Framework principal — SSR, routing, optimização de imagens |
|
||||
| **TypeScript** | Tipagem estática para reduzir bugs em desenvolvimento |
|
||||
| **Tailwind CSS** | Estilos utilitários responsivos e consistentes |
|
||||
| **shadcn/ui** | Componentes acessíveis (ARIA) e personalizáveis |
|
||||
| **TanStack Query** | Cache de dados do servidor, loading/error states |
|
||||
| **Zustand** | Estado global da aplicação (sessão, preferências UI) |
|
||||
| **React Hook Form + Zod** | Formulários com validação type-safe |
|
||||
|
||||
### Camada de Lógica de Negócio (Backend)
|
||||
|
||||
| Tecnologia | Função |
|
||||
|---|---|
|
||||
| **Next.js API Routes** | Endpoints serverless co-localizados com o frontend |
|
||||
| **NextAuth.js** | Autenticação completa com middleware de protecção de rotas |
|
||||
| **Prisma ORM** | Acesso type-safe à base de dados com migrações automáticas |
|
||||
| **Zod** | Validação rigorosa de todos os dados de entrada |
|
||||
| **bcryptjs** | Hash seguro de palavras-passe |
|
||||
|
||||
### Camada de Dados
|
||||
|
||||
| Tecnologia | Função |
|
||||
|---|---|
|
||||
| **PostgreSQL via Supabase** | Base de dados relacional principal |
|
||||
| **Supabase Storage** | Armazenamento de imagens de animais e documentos |
|
||||
| **Upstash Redis** | Cache de sessões e rate limiting |
|
||||
|
||||
### Serviços Externos
|
||||
|
||||
| Serviço | Função |
|
||||
|---|---|
|
||||
| **Stripe** | Processamento de pagamentos (Cartão, MBWay via Payment Element) |
|
||||
| **Resend + react-email** | Envio de emails transaccionais com templates profissionais |
|
||||
| **Anthropic Claude API** | IA para match inteligente e chatbot de suporte |
|
||||
| **Cloudflare** | CDN global, protecção DDoS, cache de assets |
|
||||
| **Sentry** | Monitorização de erros em produção |
|
||||
| **Vercel Analytics** | Core Web Vitals e métricas de performance |
|
||||
|
||||
---
|
||||
|
||||
## 3.3 Fluxo de Dados — Adopção
|
||||
|
||||
```
|
||||
Utilizador
|
||||
│
|
||||
▼
|
||||
[1] GET / → Next.js SSR → Prisma query → PostgreSQL
|
||||
│ Renderiza lista de animais no servidor
|
||||
│
|
||||
▼
|
||||
[2] Aplica filtros → TanStack Query → GET /api/animals?district=Lisboa&species=DOG
|
||||
│
|
||||
▼
|
||||
[3] API Route valida params (Zod) → Prisma query com WHERE clause
|
||||
│
|
||||
▼
|
||||
[4] Resposta JSON → React actualiza componentes AnimalGrid
|
||||
│
|
||||
▼
|
||||
[5] Clica "Adoptar" → Middleware Next.js verifica sessão JWT
|
||||
│ (sem sessão → redirect /login)
|
||||
│
|
||||
▼
|
||||
[6] Selecciona data → POST /api/reservations { animalId, date }
|
||||
│
|
||||
▼
|
||||
[7] API Route valida (Zod) → Prisma: UPDATE animal SET status='RESERVED'
|
||||
→ Prisma: CREATE reservation
|
||||
│
|
||||
▼
|
||||
[8] Resend envia email de confirmação com template react-email
|
||||
│
|
||||
▼
|
||||
[9] Resposta 201 → TanStack Query invalida cache → UI actualiza estado do animal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Fluxo de Dados — Doação Monetária
|
||||
|
||||
```
|
||||
[1] Utilizador selecciona canil e montante
|
||||
[2] POST /api/donations/intent → Stripe cria PaymentIntent
|
||||
[3] Frontend renderiza Stripe Payment Element (dados do cartão tratados pelo Stripe)
|
||||
[4] Stripe processa pagamento → webhook POST /api/payments/webhook
|
||||
[5] API Route verifica assinatura HMAC do webhook
|
||||
[6] Prisma: UPDATE donation SET status='COMPLETED'
|
||||
[7] Resend envia recibo de doação
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3.5 Decisões de Arquitectura
|
||||
|
||||
### Porque Next.js e não SPA pura (Vite/React)?
|
||||
|
||||
O SEO é crítico para uma plataforma de adopção — os animais devem aparecer nos resultados de pesquisa do Google. Com SSR, a página `/animals/bobi-labrador-lisboa` renderiza HTML completo no servidor, indexável pelos motores de busca. Uma SPA devolveria apenas HTML vazio.
|
||||
|
||||
### Porque Supabase e não base de dados gerida manualmente?
|
||||
|
||||
O Supabase oferece PostgreSQL gerido, storage de ficheiros, autenticação e realtime numa única plataforma com free tier generoso. Reduz a complexidade operacional para uma equipa pequena sem comprometer escalabilidade.
|
||||
|
||||
### Porque Stripe e não Paypal?
|
||||
|
||||
O Stripe tem suporte nativo a MBWay (método de pagamento dominante em Portugal), SEPA Direct Debit e MB Multibanco através do Stripe Payment Element. Tem a melhor integração com o ecossistema Node.js/TypeScript e é PCI DSS Level 1.
|
||||
322
docs/04-base-de-dados.md
Normal file
322
docs/04-base-de-dados.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# 4. Modelação da Base de Dados
|
||||
|
||||
## 4.1 Diagrama de Entidade-Relacionamento (ERD)
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
||||
│ User │ │ Animal │ │ Shelter │
|
||||
├──────────────┤ ├──────────────────┤ ├──────────────┤
|
||||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ email │ │ shelterId (FK) ──┼──────►│ name │
|
||||
│ password │ │ name │ │ district │
|
||||
│ name │ │ species │ │ address │
|
||||
│ birthdate │ │ breed │ │ latitude │
|
||||
│ district │ │ age │ │ longitude │
|
||||
│ role │ │ sex │ │ phone │
|
||||
│ darkMode │ │ sterilized │ │ email │
|
||||
│ createdAt │ │ status │ │ openHours │
|
||||
└──────┬───────┘ │ urgent │ │ passwordHash │
|
||||
│ │ description │ │ createdAt │
|
||||
│ │ createdAt │ └──────┬───────┘
|
||||
│ └────────┬─────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ ┌────────▼────────┐
|
||||
│ │ AnimalPhoto │ │ ShelterNeed │
|
||||
│ ├─────────────────┤ ├─────────────────┤
|
||||
│ │ id (PK) │ │ id (PK) │
|
||||
│ │ animalId (FK) │ │ shelterId (FK) │
|
||||
│ │ url │ │ type │
|
||||
│ │ isPrimary │ │ description │
|
||||
│ └─────────────────┘ │ urgent │
|
||||
│ │ active │
|
||||
│ └─────────────────┘
|
||||
│
|
||||
├──────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
┌──────▼───────────────┐ ┌──────────▼──────┐
|
||||
│ Reservation │ │ Donation │
|
||||
├──────────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │ │ id (PK) │
|
||||
│ userId (FK) ─────────┘ │ userId (FK) │
|
||||
│ animalId (FK) │ │ shelterId (FK) │
|
||||
│ date │ │ type │
|
||||
│ status │ │ details (JSON) │
|
||||
│ emailSent │ │ status │
|
||||
│ createdAt │ │ createdAt │
|
||||
└──────────────────────┘ └────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Payment │
|
||||
├─────────────────┤
|
||||
│ id (PK) │
|
||||
│ donationId (FK) │
|
||||
│ stripeId │
|
||||
│ amount │
|
||||
│ currency │
|
||||
│ status │
|
||||
│ createdAt │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4.2 Descrição das Entidades
|
||||
|
||||
| Entidade | Descrição | Relações |
|
||||
|---|---|---|
|
||||
| `User` | Utilizador registado na plataforma | Tem muitas `Reservation` e `Donation` |
|
||||
| `Shelter` | Canil ou associação de protecção animal | Tem muitos `Animal`, recebe `Donation`, tem `ShelterNeed` |
|
||||
| `Animal` | Animal disponível para adopção | Pertence a um `Shelter`, tem `AnimalPhoto` e `Reservation` |
|
||||
| `Reservation` | Reserva de adopção de um animal | Pertence a `User` e `Animal` |
|
||||
| `Donation` | Doação (monetária, ração ou brinquedos) | Pertence a `User` e `Shelter`, tem `Payment` opcional |
|
||||
| `Payment` | Registo do pagamento processado pelo Stripe | Associado a `Donation` do tipo monetário |
|
||||
| `AnimalPhoto` | Fotografia de um animal | Pertence a `Animal` |
|
||||
| `ShelterNeed` | Necessidade actual do canil | Pertence a `Shelter` |
|
||||
|
||||
---
|
||||
|
||||
## 4.3 Esquema Prisma Completo
|
||||
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ─── Enums ──────────────────────────────────────────────────────
|
||||
|
||||
enum UserRole {
|
||||
USER
|
||||
SHELTER_ADMIN
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum Species {
|
||||
DOG
|
||||
CAT
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum Sex {
|
||||
MALE
|
||||
FEMALE
|
||||
}
|
||||
|
||||
enum AnimalStatus {
|
||||
AVAILABLE
|
||||
RESERVED
|
||||
ADOPTED
|
||||
}
|
||||
|
||||
enum DonationType {
|
||||
MONETARY
|
||||
FOOD
|
||||
TOYS
|
||||
}
|
||||
|
||||
enum ReservationStatus {
|
||||
PENDING
|
||||
CONFIRMED
|
||||
CANCELLED
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
enum DonationStatus {
|
||||
PENDING
|
||||
CONFIRMED
|
||||
DELIVERED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
// ─── Models ─────────────────────────────────────────────────────
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
password String // bcrypt hash — nunca em texto simples
|
||||
name String
|
||||
birthdate DateTime
|
||||
district String
|
||||
role UserRole @default(USER)
|
||||
darkMode Boolean @default(false)
|
||||
emailVerified Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
reservations Reservation[]
|
||||
donations Donation[]
|
||||
|
||||
@@index([email])
|
||||
@@index([district])
|
||||
}
|
||||
|
||||
model Shelter {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
district String
|
||||
address String
|
||||
latitude Float
|
||||
longitude Float
|
||||
phone String
|
||||
email String @unique
|
||||
passwordHash String
|
||||
description String?
|
||||
website String?
|
||||
// JSON: { mon: "09:00-18:00", tue: "09:00-18:00", ... , sun: null }
|
||||
openHours Json
|
||||
verified Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
animals Animal[]
|
||||
donations Donation[]
|
||||
needs ShelterNeed[]
|
||||
|
||||
@@index([district])
|
||||
}
|
||||
|
||||
model Animal {
|
||||
id String @id @default(cuid())
|
||||
shelterId String
|
||||
shelter Shelter @relation(fields: [shelterId], references: [id])
|
||||
name String
|
||||
species Species
|
||||
breed String? // relevante para DOG e CAT
|
||||
ageMonths Int // idade em meses para precisão
|
||||
sex Sex
|
||||
sterilized Boolean
|
||||
status AnimalStatus @default(AVAILABLE)
|
||||
urgent Boolean @default(false)
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
photos AnimalPhoto[]
|
||||
reservations Reservation[]
|
||||
|
||||
@@index([shelterId])
|
||||
@@index([species])
|
||||
@@index([status])
|
||||
@@index([district: false]) // via shelter — para queries por localidade
|
||||
}
|
||||
|
||||
model AnimalPhoto {
|
||||
id String @id @default(cuid())
|
||||
animalId String
|
||||
animal Animal @relation(fields: [animalId], references: [id], onDelete: Cascade)
|
||||
url String
|
||||
isPrimary Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([animalId])
|
||||
}
|
||||
|
||||
model Reservation {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
animalId String
|
||||
animal Animal @relation(fields: [animalId], references: [id])
|
||||
date DateTime
|
||||
status ReservationStatus @default(PENDING)
|
||||
emailSent Boolean @default(false)
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([animalId])
|
||||
@@index([date])
|
||||
}
|
||||
|
||||
model Donation {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
shelterId String
|
||||
shelter Shelter @relation(fields: [shelterId], references: [id])
|
||||
type DonationType
|
||||
// Para FOOD: { foodType: "dry|wet", animalType: "dog|cat", ageGroup: "adult|puppy" }
|
||||
// Para TOYS: { category: "chew|plush|interactive" }
|
||||
// Para MONETARY: { amount: 25.00, currency: "EUR" }
|
||||
// Partilhado: { deliveryMethod: "pickup|home_delivery", address?: "..." }
|
||||
details Json
|
||||
status DonationStatus @default(PENDING)
|
||||
emailSent Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
payment Payment?
|
||||
|
||||
@@index([userId])
|
||||
@@index([shelterId])
|
||||
@@index([type])
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(cuid())
|
||||
donationId String @unique
|
||||
donation Donation @relation(fields: [donationId], references: [id])
|
||||
stripePaymentId String @unique
|
||||
amount Int // em cêntimos (ex: 2500 = 25,00€)
|
||||
currency String @default("eur")
|
||||
status String // stripe payment status
|
||||
receiptUrl String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model ShelterNeed {
|
||||
id String @id @default(cuid())
|
||||
shelterId String
|
||||
shelter Shelter @relation(fields: [shelterId], references: [id])
|
||||
type String // "food_dry_dog", "toys", "blankets", etc.
|
||||
description String?
|
||||
urgent Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([shelterId])
|
||||
@@index([active])
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4.4 Estratégia de Indexação
|
||||
|
||||
Os índices foram definidos com base nos filtros mais comuns da aplicação:
|
||||
|
||||
| Índice | Tabela | Motivo |
|
||||
|---|---|---|
|
||||
| `email` | User | Login — lookup por email frequente |
|
||||
| `district` | User, Shelter | Filtros por localidade |
|
||||
| `shelterId` | Animal | Listar animais de um canil |
|
||||
| `species`, `status` | Animal | Filtros principais da listagem |
|
||||
| `userId` | Reservation, Donation | Histórico do utilizador |
|
||||
| `animalId` | Reservation | Verificar disponibilidade do animal |
|
||||
| `type`, `active` | ShelterNeed | Listar necessidades activas por tipo |
|
||||
|
||||
---
|
||||
|
||||
## 4.5 Migrações
|
||||
|
||||
As migrações são geridas automaticamente pelo Prisma:
|
||||
|
||||
```bash
|
||||
# Criar nova migração após alterar schema.prisma
|
||||
npx prisma migrate dev --name descricao_da_alteracao
|
||||
|
||||
# Aplicar migrações em produção
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Visualizar base de dados no browser (Prisma Studio)
|
||||
npx prisma studio
|
||||
```
|
||||
218
docs/05-fluxos-utilizador.md
Normal file
218
docs/05-fluxos-utilizador.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# 5. Fluxos de Utilizador
|
||||
|
||||
## 5.1 User Stories
|
||||
|
||||
| ID | Como... | Quero... | Para... |
|
||||
|---|---|---|---|
|
||||
| US-01 | Visitante | Ver animais disponíveis sem criar conta | Descobrir se há animais que me interessem antes de me registar |
|
||||
| US-02 | Visitante | Filtrar animais por distrito e espécie | Encontrar animais próximos de mim rapidamente |
|
||||
| US-03 | Visitante | Ver o perfil de um canil com horários | Saber quando posso visitar antes de reservar |
|
||||
| US-04 | Utilizador | Criar conta com os meus dados | Poder adoptar animais e fazer doações |
|
||||
| US-05 | Utilizador | Reservar um animal para adopção | Garantir o animal antes de me deslocar ao canil |
|
||||
| US-06 | Utilizador | Receber email de confirmação da reserva | Ter prova escrita e recordar a data marcada |
|
||||
| US-07 | Utilizador | Fazer uma doação monetária a um canil | Apoiar financeiramente um canil próximo de mim |
|
||||
| US-08 | Utilizador | Doar ração ou brinquedos | Contribuir com bens essenciais sem precisar de dinheiro |
|
||||
| US-09 | Utilizador | Escolher se entrego ou peço recolha | Ter flexibilidade na logística da doação |
|
||||
| US-10 | Utilizador | Ver o meu histórico de adopções e doações | Acompanhar o meu impacto ao longo do tempo |
|
||||
| US-11 | Utilizador | Activar o modo escuro | Reduzir cansaço visual ao usar a plataforma |
|
||||
| US-12 | Utilizador | Alterar a minha palavra-passe | Manter a minha conta segura |
|
||||
| US-13 | Canil | Gerir os meus animais registados | Ter controlo sobre quais animais estão disponíveis |
|
||||
| US-14 | Canil | Ver e confirmar reservas recebidas | Preparar o animal para o dia do levantamento |
|
||||
| US-15 | Canil | Indicar as minhas necessidades actuais | Receber doações específicas do que me faz falta |
|
||||
| US-16 | Admin | Supervisionar canis e utilizadores | Garantir qualidade e segurança da plataforma |
|
||||
|
||||
---
|
||||
|
||||
## 5.2 Fluxo de Registo
|
||||
|
||||
```
|
||||
Visitante clica "Criar Conta" (menu — 3 traços, canto superior direito)
|
||||
│
|
||||
▼
|
||||
Preenche formulário:
|
||||
• Nome completo
|
||||
• Email
|
||||
• Palavra-passe (mínimo 8 caracteres, 1 maiúscula, 1 número)
|
||||
• Confirmação de palavra-passe
|
||||
• Data de nascimento
|
||||
• Distrito
|
||||
• Checkbox: Concordo com os Termos e Política de Privacidade
|
||||
│
|
||||
▼
|
||||
[Servidor] Valida se idade ≥ 18 anos
|
||||
├── Não → Mostra erro: "É necessário ter 18 ou mais anos para criar conta"
|
||||
└── Sim → Continua
|
||||
│
|
||||
▼
|
||||
[Servidor] Verifica se email já existe
|
||||
├── Sim → Mostra erro: "Este email já está registado"
|
||||
└── Não → Cria conta com palavra-passe em bcrypt hash
|
||||
│
|
||||
▼
|
||||
Envia email de verificação (Resend)
|
||||
│
|
||||
▼
|
||||
Utilizador confirma email → Redirecionado para página inicial com sessão activa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Fluxo de Login
|
||||
|
||||
```
|
||||
Utilizador clica "Iniciar Sessão" no menu
|
||||
│
|
||||
▼
|
||||
Preenche email e palavra-passe
|
||||
│
|
||||
▼
|
||||
[Servidor] NextAuth verifica credenciais
|
||||
├── Inválidas → Mostra erro genérico (não especificar qual campo falhou — segurança)
|
||||
└── Válidas → Cria sessão JWT
|
||||
│
|
||||
▼
|
||||
Redirecionado para a página onde estava (ou página inicial)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.4 Fluxo Completo de Adopção
|
||||
|
||||
```
|
||||
1. DESCOBERTA
|
||||
├── Página inicial → lista todos os animais disponíveis (SSR)
|
||||
├── Filtros disponíveis: distrito, espécie, raça, sexo, esterilizado, faixa etária
|
||||
└── Animais urgentes têm badge "Urgente" destacado
|
||||
|
||||
2. DETALHES DO ANIMAL
|
||||
├── Clica no animal → abre ficha completa
|
||||
├── Galeria de fotos
|
||||
├── Informações: nome, espécie, raça, idade, sexo, esterilizado, descrição
|
||||
└── Secção do canil → nome, distrito
|
||||
└── Clica no canil → modal/página com:
|
||||
• Morada completa
|
||||
• Horário de funcionamento (dia a dia)
|
||||
• Telefone e email
|
||||
|
||||
3. RESERVA
|
||||
├── Clica "Adoptar"
|
||||
├── [Sem sessão] → Redirect para login/registo com mensagem explicativa
|
||||
├── [Com sessão] → Abre calendário de reservas
|
||||
├── Dias disponíveis: dias em que o canil está aberto e sem reservas em excesso
|
||||
├── Selecciona data → formulário de confirmação
|
||||
│ • Nome do utilizador (pré-preenchido)
|
||||
│ • Data seleccionada
|
||||
│ • Nome do animal e canil
|
||||
│ • Nota opcional para o canil
|
||||
└── Confirma
|
||||
|
||||
4. PÓS-RESERVA
|
||||
├── Animal muda para status RESERVED
|
||||
├── Email de confirmação enviado (Resend):
|
||||
│ • Assunto: "Reserva confirmada — [Nome do Animal] no [Nome do Canil]"
|
||||
│ • Data e hora de levantamento
|
||||
│ • Morada do canil
|
||||
│ • Horário de funcionamento
|
||||
│ • Instruções para cancelamento
|
||||
└── Reserva visível em "Conta → As Minhas Adopções"
|
||||
|
||||
5. DIA DO LEVANTAMENTO
|
||||
├── Utilizador dirige-se ao canil na data marcada
|
||||
├── Canil confirma levantamento → status muda para COMPLETED
|
||||
└── Animal muda para status ADOPTED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.5 Fluxo de Doação — Monetária
|
||||
|
||||
```
|
||||
Clica "Fazer Doação"
|
||||
│
|
||||
▼
|
||||
[Sem sessão] → Redirect para login com mensagem: "Para doar é necessário ter conta"
|
||||
[Com sessão, <18 anos] → Confirmação extra de data de nascimento
|
||||
│
|
||||
▼
|
||||
Escolhe tipo: MONETÁRIA
|
||||
│
|
||||
▼
|
||||
Escolhe canil:
|
||||
┌── "Da minha localidade" → lista canis no distrito do utilizador
|
||||
└── "Outra localidade" → pesquisa por distrito/nome do canil
|
||||
│
|
||||
▼
|
||||
Insere montante (mínimo: 1€)
|
||||
│
|
||||
▼
|
||||
Escolhe método de pagamento:
|
||||
┌── Cartão de Crédito/Débito
|
||||
└── MBWay (introduz número de telemóvel)
|
||||
│
|
||||
▼
|
||||
Stripe processa pagamento (dados nunca passam nos nossos servidores)
|
||||
│
|
||||
▼
|
||||
Confirmação:
|
||||
• Página de sucesso com animação
|
||||
• Email com recibo de doação
|
||||
• Visível em "Conta → Histórico de Doações"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.6 Fluxo de Doação — Ração / Brinquedos
|
||||
|
||||
```
|
||||
Escolhe tipo: RAÇÃO ou BRINQUEDOS
|
||||
│
|
||||
▼
|
||||
Lista de canis que necessitam e aceitam este tipo de donativo
|
||||
(baseada nas ShelterNeeds activas)
|
||||
│
|
||||
▼
|
||||
Selecciona canil
|
||||
│
|
||||
▼
|
||||
[RAÇÃO] Especifica:
|
||||
• Tipo: Seca / Húmida
|
||||
• Para: Cão / Gato
|
||||
• Faixa etária: Adulto / Filhote / Sénior
|
||||
|
||||
[BRINQUEDOS] Especifica:
|
||||
• Categoria: Mordedor / Pelúcia / Interactivo / Corda
|
||||
│
|
||||
▼
|
||||
Escolhe modalidade de entrega:
|
||||
┌── "Levo eu ao canil"
|
||||
│ └── Mostra horários e morada do canil seleccionado
|
||||
└── "Quero recolha em casa"
|
||||
├── Confirma/edita morada de recolha
|
||||
└── Selecciona data e período preferencial (manhã/tarde)
|
||||
│
|
||||
▼
|
||||
Confirmação:
|
||||
• Canil recebe notificação com todos os detalhes
|
||||
• Utilizador recebe email de confirmação
|
||||
• Visível em "Conta → Histórico de Doações"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.7 Navegação e Menu
|
||||
|
||||
O menu é acedido pelos **3 traços no canto superior direito** e contém:
|
||||
|
||||
**Sem sessão:**
|
||||
- Iniciar Sessão
|
||||
- Criar Conta
|
||||
|
||||
**Com sessão:**
|
||||
- A Minha Conta (nome do utilizador)
|
||||
- As Minhas Adopções
|
||||
- Histórico de Doações
|
||||
- Definições
|
||||
- Dados da Conta
|
||||
- Alterar Palavra-passe
|
||||
- Modo Escuro / Claro (toggle)
|
||||
- Terminar Sessão
|
||||
117
docs/06-stack-tecnologica.md
Normal file
117
docs/06-stack-tecnologica.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 6. Stack Tecnológica
|
||||
|
||||
## 6.1 Frontend
|
||||
|
||||
| Tecnologia | Versão | Função | Justificação |
|
||||
|---|---|---|---|
|
||||
| **Next.js** | 14+ | Framework React principal | SSR/SSG integrado, App Router, optimizações automáticas de imagem e font, middleware nativo |
|
||||
| **TypeScript** | 5+ | Linguagem de programação | Tipagem estática reduz bugs, melhora DX e facilita manutenção a longo prazo |
|
||||
| **Tailwind CSS** | 3+ | Framework de estilos | Produtividade elevada, classes utilitárias, sistema de design consistente e responsivo por defeito |
|
||||
| **shadcn/ui** | latest | Componentes base | Acessíveis por defeito (ARIA), totalmente personalizáveis, sem overhead de bundle desnecessário |
|
||||
| **TanStack Query** | 5+ | Gestão de dados do servidor | Cache automático, invalidação, loading/error states, optimistic updates sem boilerplate |
|
||||
| **Zustand** | 4+ | Estado global cliente | Minimalista, sem boilerplate, ideal para preferências UI e dados de sessão no cliente |
|
||||
| **React Hook Form** | 7+ | Gestão de formulários | Performance superior (sem re-renders a cada tecla), integração nativa com Zod |
|
||||
| **Zod** | 3+ | Validação de esquemas | Type-safe, partilhado entre frontend e backend, mensagens de erro em português |
|
||||
| **react-email** | latest | Templates de email | Componentes React para emails transaccionais com preview em browser |
|
||||
| **next/image** | built-in | Optimização de imagens | WebP automático, lazy loading, placeholder blur, responsive sizes |
|
||||
|
||||
---
|
||||
|
||||
## 6.2 Backend
|
||||
|
||||
| Tecnologia | Versão | Função | Justificação |
|
||||
|---|---|---|---|
|
||||
| **Next.js API Routes** | 14+ | Endpoints da API | Co-localizados com o frontend, serverless por defeito, sem configuração de servidor separado |
|
||||
| **Prisma ORM** | 5+ | Acesso à base de dados | Type-safe, migrações declarativas, excelente DX, Prisma Studio para explorar dados |
|
||||
| **NextAuth.js** | 4+ | Autenticação | Suporte a múltiplos providers, sessões seguras (JWT/database), middleware de protecção de rotas |
|
||||
| **bcryptjs** | 2+ | Hash de palavras-passe | Standard seguro para hashing de credenciais, factor de custo configurável |
|
||||
| **Resend** | 2+ | Envio de emails | API moderna com alta taxa de entrega, integração directa com react-email, webhook de eventos |
|
||||
| **Stripe SDK** | 14+ | Pagamentos | PCI DSS Level 1, suporte MBWay/SEPA/MB, webhooks robustos, excelente documentação |
|
||||
|
||||
---
|
||||
|
||||
## 6.3 Infra-estrutura e Serviços
|
||||
|
||||
| Serviço | Plano Inicial | Função | Custo Estimado |
|
||||
|---|---|---|---|
|
||||
| **Supabase** | Free → Pro | PostgreSQL + Storage + Auth | Gratuito até 500MB DB; $25/mês no Pro |
|
||||
| **Vercel** | Hobby → Pro | Hosting + CDN + CI/CD automático | Gratuito na fase inicial; $20/mês no Pro |
|
||||
| **Upstash Redis** | Free | Cache e rate limiting | Gratuito até 10.000 comandos/dia |
|
||||
| **Resend** | Free | Email transaccional | Gratuito até 3.000 emails/mês |
|
||||
| **Stripe** | Pay-as-you-go | Pagamentos online | 1,4% + 0,25€ por transacção (cartões UE) |
|
||||
| **Cloudflare** | Free | CDN e protecção DDoS | Gratuito para sites |
|
||||
| **Sentry** | Free | Monitorização de erros | Gratuito até 5.000 eventos/mês |
|
||||
| **Anthropic Claude API** | Pay-as-you-go | IA — match e chatbot | ~$3/1M tokens input (Sonnet 4) |
|
||||
|
||||
**Custo total estimado na fase MVP: ~0–25€/mês**
|
||||
|
||||
---
|
||||
|
||||
## 6.4 Ferramentas de Desenvolvimento
|
||||
|
||||
| Ferramenta | Função |
|
||||
|---|---|
|
||||
| **pnpm** | Gestor de pacotes (mais rápido e eficiente que npm) |
|
||||
| **ESLint + Prettier** | Linting e formatação automática de código |
|
||||
| **Husky + lint-staged** | Git hooks para validar código antes de cada commit |
|
||||
| **Vitest** | Framework de testes unitários e de integração |
|
||||
| **Playwright** | Testes end-to-end e de acessibilidade |
|
||||
| **Prisma Studio** | Interface visual para explorar e editar a base de dados |
|
||||
| **@next/bundle-analyzer** | Análise do tamanho do bundle JavaScript |
|
||||
| **Storybook** | Documentação e desenvolvimento isolado de componentes UI |
|
||||
|
||||
---
|
||||
|
||||
## 6.5 Variáveis de Ambiente
|
||||
|
||||
```bash
|
||||
# .env.local (nunca commitar — usar .env.example como template)
|
||||
|
||||
# Base de Dados
|
||||
DATABASE_URL="postgresql://user:password@host:5432/pawlink"
|
||||
|
||||
# Autenticação
|
||||
NEXTAUTH_SECRET="gerar com: openssl rand -hex 32"
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
|
||||
# Stripe
|
||||
STRIPE_SECRET_KEY="sk_test_..."
|
||||
STRIPE_WEBHOOK_SECRET="whsec_..."
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
|
||||
|
||||
# Email
|
||||
RESEND_API_KEY="re_..."
|
||||
RESEND_FROM_EMAIL="noreply@pawlink.pt"
|
||||
|
||||
# Supabase Storage
|
||||
NEXT_PUBLIC_SUPABASE_URL="https://xxx.supabase.co"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY="..."
|
||||
SUPABASE_SERVICE_ROLE_KEY="..."
|
||||
|
||||
# Anthropic (IA)
|
||||
ANTHROPIC_API_KEY="sk-ant-..."
|
||||
|
||||
# Redis (Rate Limiting)
|
||||
UPSTASH_REDIS_REST_URL="https://..."
|
||||
UPSTASH_REDIS_REST_TOKEN="..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.6 Justificação das Escolhas Tecnológicas
|
||||
|
||||
### Porque Next.js em vez de Remix ou Astro?
|
||||
|
||||
Next.js tem o ecossistema mais maduro para aplicações React complexas com SSR. O App Router (Next.js 14) oferece Server Components que permitem buscar dados directamente no servidor sem APIs intermediárias para o conteúdo estático. A integração com Vercel é nativa e o deploy é trivial.
|
||||
|
||||
### Porque PostgreSQL em vez de MongoDB?
|
||||
|
||||
Os dados da PawLink são fortemente relacionais (utilizador → reserva → animal → canil). Uma base de dados relacional como PostgreSQL garante integridade referencial por defeito, suporta transacções ACID (críticas para reservas e pagamentos) e tem melhor suporte a queries complexas com joins.
|
||||
|
||||
### Porque Tailwind CSS em vez de CSS Modules ou Styled Components?
|
||||
|
||||
Tailwind elimina a necessidade de alternar entre ficheiros CSS e JSX. As classes utilitárias são co-localizadas com o markup, tornando o código mais fácil de ler e manter. O modo JIT garante que apenas as classes usadas chegam ao bundle final.
|
||||
|
||||
### Porque Stripe em vez de SIBS/Multibanco directo?
|
||||
|
||||
O Stripe suporta MBWay, MB Multibanco e SEPA através da sua abstracção unificada (Payment Element), é PCI DSS Level 1 (o mais alto), tem webhooks fiáveis para confirmações assíncronas, e tem documentação e SDK TypeScript excelentes. A integração directa com SIBS requer certificações e processos burocráticos inadequados para a fase inicial.
|
||||
208
docs/07-seguranca.md
Normal file
208
docs/07-seguranca.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 7. Segurança e Conformidade
|
||||
|
||||
## 7.1 Autenticação e Autorização
|
||||
|
||||
### Palavras-passe
|
||||
|
||||
As palavras-passe são **nunca armazenadas em texto simples**. Utiliza-se o algoritmo bcrypt com factor de custo 12, que torna ataques de força bruta computacionalmente inviáveis mesmo com hardware moderno.
|
||||
|
||||
```typescript
|
||||
// lib/auth/password.ts
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const SALT_ROUNDS = 12;
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
```
|
||||
|
||||
### Sessões e JWT
|
||||
|
||||
As sessões são geridas pelo NextAuth.js com tokens JWT assinados com um segredo de 256 bits armazenado em variável de ambiente. O token expira ao fim de 30 dias. O middleware Next.js intercepta todas as rotas protegidas antes de qualquer lógica de negócio.
|
||||
|
||||
```typescript
|
||||
// middleware.ts
|
||||
import { withAuth } from 'next-auth/middleware';
|
||||
|
||||
export default withAuth({
|
||||
pages: { signIn: '/login' }
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/account/:path*',
|
||||
'/donate/:path*',
|
||||
'/dashboard/:path*',
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### Verificação de Idade
|
||||
|
||||
A verificação de +18 anos é **sempre feita no servidor**, nunca confiando em dados do cliente:
|
||||
|
||||
```typescript
|
||||
// lib/auth/age-validation.ts
|
||||
export function isAdult(birthdate: Date): boolean {
|
||||
const today = new Date();
|
||||
const age = today.getFullYear() - birthdate.getFullYear();
|
||||
const monthDiff = today.getMonth() - birthdate.getMonth();
|
||||
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthdate.getDate())) {
|
||||
return age - 1 >= 18;
|
||||
}
|
||||
return age >= 18;
|
||||
}
|
||||
```
|
||||
|
||||
### Controlo de Acesso por Roles
|
||||
|
||||
| Role | Permissões |
|
||||
|---|---|
|
||||
| `USER` | Adoptar animais, fazer doações, gerir a própria conta |
|
||||
| `SHELTER_ADMIN` | Tudo de USER + gerir animais/reservas/necessidades do seu canil |
|
||||
| `ADMIN` | Acesso total à plataforma, moderação, estatísticas |
|
||||
|
||||
---
|
||||
|
||||
## 7.2 Segurança da Aplicação
|
||||
|
||||
### Headers de Segurança HTTP
|
||||
|
||||
```typescript
|
||||
// next.config.ts
|
||||
const securityHeaders = [
|
||||
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
|
||||
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
|
||||
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
|
||||
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
||||
{ key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' https://js.stripe.com",
|
||||
"frame-src https://js.stripe.com",
|
||||
"img-src 'self' data: https://*.supabase.co",
|
||||
"connect-src 'self' https://api.anthropic.com https://api.stripe.com",
|
||||
].join('; ')
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Validação de Inputs
|
||||
|
||||
Todos os dados recebidos nas API Routes são validados com Zod antes de qualquer processamento:
|
||||
|
||||
```typescript
|
||||
// app/api/reservations/route.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
const ReservationSchema = z.object({
|
||||
animalId: z.string().cuid(),
|
||||
date: z.string().datetime().refine(
|
||||
date => new Date(date) > new Date(),
|
||||
{ message: 'A data deve ser no futuro' }
|
||||
),
|
||||
notes: z.string().max(500).optional(),
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = await req.json();
|
||||
const parsed = ReservationSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return Response.json({ error: parsed.error.issues }, { status: 400 });
|
||||
}
|
||||
// ... lógica de negócio com parsed.data (type-safe)
|
||||
}
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```typescript
|
||||
// lib/rate-limit.ts — protege endpoints de autenticação
|
||||
import { Ratelimit } from '@upstash/ratelimit';
|
||||
import { Redis } from '@upstash/redis';
|
||||
|
||||
export const authRateLimit = new Ratelimit({
|
||||
redis: Redis.fromEnv(),
|
||||
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 tentativas por minuto por IP
|
||||
});
|
||||
```
|
||||
|
||||
### Uploads de Imagens
|
||||
|
||||
Todos os uploads são validados antes de armazenar no Supabase Storage:
|
||||
- Tipo MIME verificado no servidor (não confia no Content-Type do cliente)
|
||||
- Tamanho máximo: 5MB por ficheiro
|
||||
- Extensões permitidas: `.jpg`, `.jpeg`, `.png`, `.webp`
|
||||
- Nome do ficheiro gerado pelo servidor (nunca usa o nome original do utilizador)
|
||||
|
||||
---
|
||||
|
||||
## 7.3 Conformidade com o RGPD
|
||||
|
||||
| Princípio RGPD | Implementação na PawLink |
|
||||
|---|---|
|
||||
| **Consentimento explícito** | Checkbox obrigatório no registo com link para Política de Privacidade. Consentimento separado para emails de marketing (opcional). |
|
||||
| **Finalidade limitada** | Dados pessoais usados apenas para adopção e doação — não partilhados com terceiros excepto processadores necessários (Stripe, Resend). |
|
||||
| **Minimização de dados** | Apenas nome, email, distrito, data de nascimento e palavra-passe são recolhidos no registo. |
|
||||
| **Exactidão** | Utilizador pode actualizar todos os seus dados nas Definições a qualquer momento. |
|
||||
| **Limitação de armazenamento** | Dados de contas inactivas (sem login há 24+ meses) são anonimizados automaticamente. |
|
||||
| **Direito ao apagamento** | Botão "Eliminar Conta" nas Definições — inicia processo de apagamento de dados pessoais em 30 dias. |
|
||||
| **Portabilidade** | Exportação dos dados do utilizador em formato JSON nas Definições. |
|
||||
| **Notificação de violações** | Plano de resposta a incidentes com notificação à CNPD em 72 horas conforme legislação. |
|
||||
| **Processadores de dados** | Contratos de processamento de dados (DPA) celebrados com Stripe, Resend, Supabase e Vercel. |
|
||||
|
||||
### Política de Cookies
|
||||
|
||||
| Cookie | Tipo | Finalidade | Duração |
|
||||
|---|---|---|---|
|
||||
| `next-auth.session-token` | Necessário | Sessão de autenticação | 30 dias |
|
||||
| `next-auth.csrf-token` | Necessário | Protecção CSRF | Sessão |
|
||||
| `theme` | Preferências | Modo claro/escuro | 1 ano |
|
||||
| Analytics Vercel | Analítico | Métricas de performance anonimizadas | Sessão |
|
||||
|
||||
Não são usados cookies de rastreamento ou publicidade.
|
||||
|
||||
---
|
||||
|
||||
## 7.4 Segurança dos Pagamentos
|
||||
|
||||
- Dados de cartão **nunca passam pelos servidores da PawLink** — tratados directamente pelo Stripe via Payment Element
|
||||
- Stripe é certificado **PCI DSS Level 1** — o mais elevado nível de conformidade
|
||||
- Webhooks Stripe verificados com **assinatura HMAC-SHA256** — previne falsificação de eventos de pagamento
|
||||
- Montantes validados no servidor antes de criar PaymentIntent — cliente não pode alterar o valor
|
||||
- Ambiente de teste (chaves `sk_test_`) separado do ambiente de produção (chaves `sk_live_`)
|
||||
|
||||
```typescript
|
||||
// app/api/payments/webhook/route.ts
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = await req.text();
|
||||
const signature = req.headers.get('stripe-signature')!;
|
||||
|
||||
let event: Stripe.Event;
|
||||
try {
|
||||
// Verifica assinatura HMAC — rejeitado se não for do Stripe
|
||||
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
||||
} catch {
|
||||
return new Response('Webhook signature verification failed', { status: 400 });
|
||||
}
|
||||
|
||||
// Processar evento verificado
|
||||
if (event.type === 'payment_intent.succeeded') {
|
||||
// Actualizar donation status na base de dados
|
||||
// Enviar email de recibo
|
||||
}
|
||||
}
|
||||
```
|
||||
257
docs/08-ia.md
Normal file
257
docs/08-ia.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# 8. Integração de Inteligência Artificial
|
||||
|
||||
## 8.1 Casos de Uso de IA
|
||||
|
||||
| Funcionalidade | Descrição | Modelo |
|
||||
|---|---|---|
|
||||
| **Match Inteligente** | Sugere os 3 animais mais compatíveis com o perfil do utilizador | Claude Sonnet (API) |
|
||||
| **Assistente de Suporte** | Responde a dúvidas sobre adopção e doação em chat em tempo real | Claude Sonnet (streaming) |
|
||||
| **Geração de Descrições** | Gera descrições cativantes para animais sem texto fornecido pelo canil | Claude Haiku (API) |
|
||||
| **Análise de Necessidades** | Identifica padrões nas necessidades dos canis e sugere campanhas | Claude Sonnet (API) |
|
||||
| **Moderação de Conteúdo** | Verifica fotos e textos submetidos pelos canis antes de publicar | Claude Sonnet Vision |
|
||||
|
||||
---
|
||||
|
||||
## 8.2 Arquitectura do Agente de Match
|
||||
|
||||
O sistema de match inteligente funciona em dois passos:
|
||||
|
||||
1. **Recolha de contexto:** combina o perfil do utilizador com o inventário actualizado de animais disponíveis
|
||||
2. **Inferência:** passa o contexto ao Claude com um system prompt especializado e recebe sugestões fundamentadas
|
||||
|
||||
### Estrutura do Contexto
|
||||
|
||||
```typescript
|
||||
// lib/ai/match.ts
|
||||
|
||||
interface MatchContext {
|
||||
user: {
|
||||
district: string;
|
||||
age: number;
|
||||
preferences: {
|
||||
species?: 'dog' | 'cat' | 'other';
|
||||
preferredSize?: 'small' | 'medium' | 'large';
|
||||
experience: 'first_time' | 'experienced' | 'expert';
|
||||
livingSpace: 'apartment' | 'house_no_garden' | 'house_with_garden';
|
||||
activityLevel: 'low' | 'moderate' | 'high';
|
||||
hasChildren?: boolean;
|
||||
hasOtherPets?: boolean;
|
||||
};
|
||||
history: {
|
||||
viewedAnimalIds: string[];
|
||||
pastAdoptions: number;
|
||||
};
|
||||
};
|
||||
availableAnimals: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
species: string;
|
||||
breed: string | null;
|
||||
ageMonths: number;
|
||||
sex: string;
|
||||
sterilized: boolean;
|
||||
district: string;
|
||||
shelterName: string;
|
||||
urgent: boolean;
|
||||
description: string | null;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### System Prompt do Agente de Match
|
||||
|
||||
```typescript
|
||||
// lib/ai/prompts.ts
|
||||
|
||||
export const MATCH_SYSTEM_PROMPT = `
|
||||
És um especialista em adopção responsável de animais da plataforma PawLink,
|
||||
em Portugal. O teu papel é ajudar utilizadores a encontrar o animal de estimação
|
||||
mais compatível com o seu estilo de vida.
|
||||
|
||||
INSTRUÇÕES:
|
||||
- Analisa o perfil do utilizador e a lista de animais disponíveis
|
||||
- Sugere exactamente 3 animais, ordenados por compatibilidade (do mais ao menos compatível)
|
||||
- Para cada animal, explica em 2-3 frases porque é uma boa correspondência
|
||||
- Considera sempre: espaço disponível, nível de actividade, experiência, localidade
|
||||
- Animais com flag "urgent: true" devem receber peso extra na pontuação
|
||||
- Não inventes características — usa apenas os dados fornecidos
|
||||
- Se não houver animais adequados, diz-o honestamente e explica porquê
|
||||
|
||||
FORMATO DA RESPOSTA (JSON):
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"animalId": "string",
|
||||
"compatibilityScore": 95,
|
||||
"reason": "string — 2-3 frases em português"
|
||||
}
|
||||
],
|
||||
"generalAdvice": "string — conselho geral opcional (máximo 1 frase)"
|
||||
}
|
||||
|
||||
Responde APENAS com JSON válido, sem texto adicional.
|
||||
`;
|
||||
```
|
||||
|
||||
### Implementação da Chamada à API
|
||||
|
||||
```typescript
|
||||
// lib/ai/match.ts
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
||||
|
||||
export async function getAnimalMatches(context: MatchContext) {
|
||||
const response = await anthropic.messages.create({
|
||||
model: 'claude-sonnet-4-20250514',
|
||||
max_tokens: 1024,
|
||||
system: MATCH_SYSTEM_PROMPT,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `Perfil do utilizador e animais disponíveis:\n${JSON.stringify(context, null, 2)}`
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
||||
|
||||
try {
|
||||
return JSON.parse(text) as { matches: MatchResult[]; generalAdvice?: string };
|
||||
} catch {
|
||||
console.error('Erro ao fazer parse da resposta de match:', text);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8.3 Assistente de Suporte (Chatbot)
|
||||
|
||||
O assistente responde em tempo real usando **streaming** para melhor experiência do utilizador. É implementado com o Vercel AI SDK.
|
||||
|
||||
### System Prompt do Suporte
|
||||
|
||||
```typescript
|
||||
export const SUPPORT_SYSTEM_PROMPT = `
|
||||
És o assistente virtual da PawLink, uma plataforma portuguesa de adopção de
|
||||
animais e doação a canis. Chamas-te Paws.
|
||||
|
||||
PAPEL:
|
||||
- Ajudar utilizadores a navegar na plataforma e responder a dúvidas sobre
|
||||
adopção e doação de animais
|
||||
- Fornecer informação geral sobre cuidados com animais de estimação
|
||||
- Orientar utilizadores que estejam a considerar adoptar pela primeira vez
|
||||
|
||||
LIMITAÇÕES:
|
||||
- Não tens acesso a informação em tempo real sobre animais específicos ou
|
||||
disponibilidade actual (sugere usar os filtros da plataforma)
|
||||
- Não podes processar pagamentos ou fazer reservas directamente
|
||||
- Para questões técnicas graves, sugere contacto via email: suporte@pawlink.pt
|
||||
|
||||
TOM:
|
||||
- Caloroso, encorajador e amigável
|
||||
- Profissional mas acessível
|
||||
- Nunca condescendente
|
||||
|
||||
LÍNGUA: Responde sempre em português de Portugal (pt-PT).
|
||||
`;
|
||||
```
|
||||
|
||||
### Endpoint de Streaming
|
||||
|
||||
```typescript
|
||||
// app/api/chat/route.ts
|
||||
import { streamText } from 'ai';
|
||||
import { anthropic } from '@ai-sdk/anthropic';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json();
|
||||
|
||||
const result = await streamText({
|
||||
model: anthropic('claude-sonnet-4-20250514'),
|
||||
system: SUPPORT_SYSTEM_PROMPT,
|
||||
messages,
|
||||
maxTokens: 500, // respostas concisas no chat
|
||||
});
|
||||
|
||||
return result.toDataStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
### Componente de Chat no Frontend
|
||||
|
||||
```typescript
|
||||
// components/chat/SupportChat.tsx
|
||||
'use client';
|
||||
import { useChat } from 'ai/react';
|
||||
|
||||
export function SupportChat() {
|
||||
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
|
||||
api: '/api/chat',
|
||||
initialMessages: [{
|
||||
id: 'welcome',
|
||||
role: 'assistant',
|
||||
content: 'Olá! Sou o Paws, o assistente da PawLink. Como posso ajudar-te hoje? 🐾'
|
||||
}]
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="chat-container">
|
||||
{messages.map(msg => (
|
||||
<div key={msg.id} className={`message ${msg.role}`}>
|
||||
{msg.content}
|
||||
</div>
|
||||
))}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input value={input} onChange={handleInputChange} placeholder="Escreve a tua mensagem..." />
|
||||
<button type="submit" disabled={isLoading}>Enviar</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8.4 Geração de Descrições de Animais
|
||||
|
||||
Quando um canil regista um animal sem fornecer descrição, o sistema gera automaticamente uma descrição cativante:
|
||||
|
||||
```typescript
|
||||
export async function generateAnimalDescription(animal: {
|
||||
name: string;
|
||||
species: string;
|
||||
breed: string | null;
|
||||
ageMonths: number;
|
||||
sex: string;
|
||||
sterilized: boolean;
|
||||
}) {
|
||||
const response = await anthropic.messages.create({
|
||||
model: 'claude-haiku-4-5', // Haiku é suficiente e mais económico para esta tarefa
|
||||
max_tokens: 200,
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: `Escreve uma descrição curta e cativante (máximo 3 frases) para este animal
|
||||
disponível para adopção numa plataforma portuguesa.
|
||||
Dados: ${JSON.stringify(animal)}
|
||||
Escreve em português de Portugal, com tom caloroso e encorajador.
|
||||
Não inventes factos específicos, usa apenas os dados fornecidos.`
|
||||
}]
|
||||
});
|
||||
|
||||
return response.content[0].type === 'text' ? response.content[0].text : null;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8.5 Boas Práticas e Limites
|
||||
|
||||
- **Rate limiting:** chamadas à IA limitadas a 20 por utilizador por hora para controlo de custos
|
||||
- **Cache de resultados:** respostas de match são cacheadas 30 minutos no Redis (o inventário não muda ao segundo)
|
||||
- **Fallback gracioso:** se a API da Anthropic falhar, a plataforma funciona normalmente sem funcionalidades de IA
|
||||
- **Transparência:** o utilizador é informado quando está a interagir com IA
|
||||
- **Sem decisões críticas:** a IA sugere mas nunca decide automaticamente — o utilizador tem sempre controlo
|
||||
- **Monitorização de custos:** dashboard no Anthropic Console para acompanhar utilização e custos
|
||||
255
docs/09-estrutura-projecto.md
Normal file
255
docs/09-estrutura-projecto.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# 9. Estrutura do Projecto
|
||||
|
||||
## 9.1 Organização de Directórios
|
||||
|
||||
```
|
||||
pawlink/
|
||||
│
|
||||
├── app/ # Next.js App Router
|
||||
│ │
|
||||
│ ├── (auth)/ # Grupo: sem layout principal
|
||||
│ │ ├── login/
|
||||
│ │ │ └── page.tsx # Página de login
|
||||
│ │ ├── register/
|
||||
│ │ │ └── page.tsx # Página de registo
|
||||
│ │ └── forgot-password/
|
||||
│ │ └── page.tsx # Recuperação de palavra-passe
|
||||
│ │
|
||||
│ ├── (main)/ # Grupo: com header/footer
|
||||
│ │ ├── layout.tsx # Layout principal — Header, SideMenu, Footer
|
||||
│ │ ├── page.tsx # Homepage — listagem de todos os animais (SSR)
|
||||
│ │ │
|
||||
│ │ ├── animals/
|
||||
│ │ │ ├── page.tsx # Listagem completa com filtros
|
||||
│ │ │ └── [id]/
|
||||
│ │ │ └── page.tsx # Ficha detalhada do animal
|
||||
│ │ │
|
||||
│ │ ├── shelters/
|
||||
│ │ │ ├── page.tsx # Lista de canis
|
||||
│ │ │ └── [id]/
|
||||
│ │ │ └── page.tsx # Perfil do canil + horários + animais
|
||||
│ │ │
|
||||
│ │ ├── donate/
|
||||
│ │ │ ├── page.tsx # Escolha do tipo de doação
|
||||
│ │ │ ├── monetary/
|
||||
│ │ │ │ └── page.tsx # Fluxo doação monetária
|
||||
│ │ │ ├── food/
|
||||
│ │ │ │ └── page.tsx # Fluxo doação de ração
|
||||
│ │ │ └── toys/
|
||||
│ │ │ └── page.tsx # Fluxo doação de brinquedos
|
||||
│ │ │
|
||||
│ │ └── account/
|
||||
│ │ ├── layout.tsx # Layout da área de conta (sidebar)
|
||||
│ │ ├── settings/
|
||||
│ │ │ └── page.tsx # Dados da conta, palavra-passe, tema
|
||||
│ │ ├── adoptions/
|
||||
│ │ │ └── page.tsx # Histórico e estado das adopções
|
||||
│ │ └── donations/
|
||||
│ │ └── page.tsx # Histórico de doações
|
||||
│ │
|
||||
│ ├── (shelter)/ # Grupo: painel dos canis
|
||||
│ │ ├── layout.tsx # Layout do dashboard
|
||||
│ │ └── dashboard/
|
||||
│ │ ├── page.tsx # Resumo + estatísticas
|
||||
│ │ ├── animals/
|
||||
│ │ │ ├── page.tsx # Listar e gerir animais
|
||||
│ │ │ ├── new/page.tsx # Adicionar novo animal
|
||||
│ │ │ └── [id]/edit/page.tsx # Editar animal
|
||||
│ │ ├── reservations/
|
||||
│ │ │ └── page.tsx # Ver e confirmar reservas
|
||||
│ │ ├── donations/
|
||||
│ │ │ └── page.tsx # Ver doações recebidas
|
||||
│ │ └── needs/
|
||||
│ │ └── page.tsx # Gerir necessidades actuais
|
||||
│ │
|
||||
│ └── api/ # API Routes (endpoints REST)
|
||||
│ ├── auth/
|
||||
│ │ └── [...nextauth]/
|
||||
│ │ └── route.ts # NextAuth handler
|
||||
│ │
|
||||
│ ├── animals/
|
||||
│ │ ├── route.ts # GET (listar com filtros) + POST (criar)
|
||||
│ │ └── [id]/
|
||||
│ │ └── route.ts # GET (detalhe) + PATCH (actualizar) + DELETE
|
||||
│ │
|
||||
│ ├── shelters/
|
||||
│ │ ├── route.ts # GET (listar) + POST (registar canil)
|
||||
│ │ └── [id]/
|
||||
│ │ └── route.ts # GET + PATCH
|
||||
│ │
|
||||
│ ├── reservations/
|
||||
│ │ ├── route.ts # GET (minhas reservas) + POST (criar)
|
||||
│ │ └── [id]/
|
||||
│ │ └── route.ts # PATCH (confirmar/cancelar)
|
||||
│ │
|
||||
│ ├── donations/
|
||||
│ │ ├── route.ts # GET + POST
|
||||
│ │ └── intent/
|
||||
│ │ └── route.ts # POST — cria Stripe PaymentIntent
|
||||
│ │
|
||||
│ ├── payments/
|
||||
│ │ └── webhook/
|
||||
│ │ └── route.ts # POST — Stripe webhook
|
||||
│ │
|
||||
│ ├── users/
|
||||
│ │ └── me/
|
||||
│ │ └── route.ts # GET (perfil) + PATCH (actualizar) + DELETE
|
||||
│ │
|
||||
│ └── chat/
|
||||
│ └── route.ts # POST — chatbot IA (streaming)
|
||||
│
|
||||
├── components/ # Componentes React reutilizáveis
|
||||
│ │
|
||||
│ ├── ui/ # shadcn/ui base components
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── card.tsx
|
||||
│ │ ├── dialog.tsx
|
||||
│ │ ├── input.tsx
|
||||
│ │ ├── select.tsx
|
||||
│ │ ├── badge.tsx
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── animals/
|
||||
│ │ ├── AnimalCard.tsx # Card da listagem
|
||||
│ │ ├── AnimalGrid.tsx # Grid responsivo de cards
|
||||
│ │ ├── AnimalFilters.tsx # Painel de filtros
|
||||
│ │ ├── AnimalProfile.tsx # Ficha completa do animal
|
||||
│ │ ├── AnimalGallery.tsx # Galeria de fotos
|
||||
│ │ ├── AnimalStatusBadge.tsx # Badge: Disponível/Reservado/Adoptado
|
||||
│ │ └── UrgentBadge.tsx # Destaque para animais urgentes
|
||||
│ │
|
||||
│ ├── shelters/
|
||||
│ │ ├── ShelterCard.tsx
|
||||
│ │ ├── ShelterProfile.tsx
|
||||
│ │ └── ShelterHours.tsx # Tabela de horários formatada
|
||||
│ │
|
||||
│ ├── donations/
|
||||
│ │ ├── DonationTypeSelector.tsx # Escolha Monetária/Ração/Brinquedos
|
||||
│ │ ├── ShelterSelector.tsx # Escolha do canil destinatário
|
||||
│ │ ├── PaymentForm.tsx # Stripe Payment Element wrapper
|
||||
│ │ ├── FoodDonationForm.tsx # Formulário ração
|
||||
│ │ ├── ToysDonationForm.tsx # Formulário brinquedos
|
||||
│ │ └── DeliveryMethodSelector.tsx # Levar / Recolha em casa
|
||||
│ │
|
||||
│ ├── reservations/
|
||||
│ │ ├── ReservationCalendar.tsx # Calendário de datas disponíveis
|
||||
│ │ ├── ReservationSummary.tsx # Resumo antes de confirmar
|
||||
│ │ └── ReservationCard.tsx # Card no histórico
|
||||
│ │
|
||||
│ ├── ai/
|
||||
│ │ ├── AnimalMatchWidget.tsx # Widget de match inteligente
|
||||
│ │ └── SupportChat.tsx # Chatbot de suporte
|
||||
│ │
|
||||
│ └── layout/
|
||||
│ ├── Header.tsx # Cabeçalho com logo e navegação
|
||||
│ ├── SideMenu.tsx # Menu lateral (3 traços)
|
||||
│ ├── Footer.tsx
|
||||
│ └── ThemeToggle.tsx # Toggle modo claro/escuro
|
||||
│
|
||||
├── lib/ # Lógica partilhada e utilitários
|
||||
│ │
|
||||
│ ├── db/
|
||||
│ │ ├── prisma.ts # Singleton do Prisma Client
|
||||
│ │ ├── animals.ts # Queries reutilizáveis de animais
|
||||
│ │ ├── shelters.ts # Queries de canis
|
||||
│ │ ├── reservations.ts # Queries de reservas
|
||||
│ │ └── donations.ts # Queries de doações
|
||||
│ │
|
||||
│ ├── auth/
|
||||
│ │ ├── config.ts # Configuração NextAuth
|
||||
│ │ ├── age-validation.ts # Validação de +18 anos
|
||||
│ │ └── password.ts # bcrypt helpers
|
||||
│ │
|
||||
│ ├── email/
|
||||
│ │ ├── send.ts # Wrapper Resend
|
||||
│ │ └── templates/
|
||||
│ │ ├── ReservationConfirmation.tsx # Template email de reserva
|
||||
│ │ ├── DonationReceipt.tsx # Template recibo de doação
|
||||
│ │ └── PasswordReset.tsx # Template recuperação de palavra-passe
|
||||
│ │
|
||||
│ ├── payments/
|
||||
│ │ └── stripe.ts # Stripe client + helpers
|
||||
│ │
|
||||
│ ├── ai/
|
||||
│ │ ├── match.ts # Lógica de match inteligente
|
||||
│ │ ├── descriptions.ts # Geração de descrições
|
||||
│ │ └── prompts.ts # System prompts centralizados
|
||||
│ │
|
||||
│ ├── utils/
|
||||
│ │ ├── format.ts # Formatação de datas, moeda, idade
|
||||
│ │ ├── districts.ts # Lista de distritos de Portugal
|
||||
│ │ └── cn.ts # Utility para merge de classes Tailwind
|
||||
│ │
|
||||
│ └── validations/
|
||||
│ ├── auth.ts # Schemas Zod para auth
|
||||
│ ├── animals.ts # Schemas para animais
|
||||
│ ├── donations.ts # Schemas para doações
|
||||
│ └── reservations.ts # Schemas para reservas
|
||||
│
|
||||
├── prisma/
|
||||
│ ├── schema.prisma # Esquema da base de dados
|
||||
│ ├── seed.ts # Dados iniciais para desenvolvimento
|
||||
│ └── migrations/ # Histórico de migrações (gerado automaticamente)
|
||||
│
|
||||
├── public/
|
||||
│ ├── images/
|
||||
│ │ ├── logo.svg
|
||||
│ │ ├── logo-dark.svg
|
||||
│ │ └── placeholders/ # Imagens placeholder para animais
|
||||
│ └── icons/
|
||||
│ └── favicon.ico
|
||||
│
|
||||
├── docs/ # Esta documentação
|
||||
│ ├── 01-visao-geral.md
|
||||
│ ├── 02-requisitos.md
|
||||
│ ├── 03-arquitectura.md
|
||||
│ ├── 04-base-de-dados.md
|
||||
│ ├── 05-fluxos-utilizador.md
|
||||
│ ├── 06-stack-tecnologica.md
|
||||
│ ├── 07-seguranca.md
|
||||
│ ├── 08-ia.md
|
||||
│ ├── 09-estrutura-projecto.md
|
||||
│ ├── 10-orientacoes-desenvolvimento.md
|
||||
│ ├── 11-roadmap.md
|
||||
│ └── 12-glossario.md
|
||||
│
|
||||
├── .env.local # Variáveis de ambiente (não commitado)
|
||||
├── .env.example # Template de variáveis (commitado)
|
||||
├── .gitignore
|
||||
├── .eslintrc.json
|
||||
├── .prettierrc
|
||||
├── middleware.ts # Next.js middleware (auth + rate limiting)
|
||||
├── next.config.ts
|
||||
├── tailwind.config.ts
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9.2 Convenções de Nomenclatura de Ficheiros
|
||||
|
||||
| Tipo | Convenção | Exemplo |
|
||||
|---|---|---|
|
||||
| Componentes React | PascalCase | `AnimalCard.tsx`, `ShelterProfile.tsx` |
|
||||
| Hooks personalizados | camelCase com prefixo `use` | `useAnimals.ts`, `useDonation.ts` |
|
||||
| Utilitários e helpers | camelCase | `format.ts`, `age-validation.ts` |
|
||||
| Constantes | kebab-case | `districts.ts`, `animal-species.ts` |
|
||||
| Páginas Next.js | lowercase (`page.tsx`) | `page.tsx`, `layout.tsx`, `loading.tsx` |
|
||||
| API Routes | lowercase (`route.ts`) | `route.ts` |
|
||||
|
||||
---
|
||||
|
||||
## 9.3 Padrão de Exportação de Componentes
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECTO — export default para componentes de página e principais
|
||||
export default function AnimalCard({ animal }: AnimalCardProps) { ... }
|
||||
|
||||
// ✅ CORRECTO — named exports para utilitários e múltiplos exports
|
||||
export function formatAge(ageMonths: number): string { ... }
|
||||
export function formatPrice(cents: number): string { ... }
|
||||
|
||||
// ✅ CORRECTO — tipos exportados separadamente
|
||||
export type { AnimalCardProps };
|
||||
```
|
||||
302
docs/10-orientacoes-desenvolvimento.md
Normal file
302
docs/10-orientacoes-desenvolvimento.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 10. Orientações de Desenvolvimento
|
||||
|
||||
## 10.1 Configuração do Ambiente Local
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
- **Node.js 20+** — instalar via [nvm](https://github.com/nvm-sh/nvm) (recomendado)
|
||||
- **pnpm 8+** — `npm install -g pnpm`
|
||||
- **Git 2.40+**
|
||||
- Conta **Supabase** (base de dados + storage)
|
||||
- Conta **Stripe** (modo teste disponível gratuitamente)
|
||||
- Conta **Resend** (emails)
|
||||
- Chave API **Anthropic** (IA)
|
||||
|
||||
### Setup Inicial
|
||||
|
||||
```bash
|
||||
# 1. Clonar o repositório
|
||||
git clone https://github.com/pawlink/pawlink.git
|
||||
cd pawlink
|
||||
|
||||
# 2. Instalar dependências
|
||||
pnpm install
|
||||
|
||||
# 3. Copiar template de variáveis de ambiente
|
||||
cp .env.example .env.local
|
||||
# Preencher .env.local com as tuas chaves
|
||||
|
||||
# 4. Aplicar migrações da base de dados
|
||||
npx prisma migrate dev
|
||||
|
||||
# 5. Popular a base de dados com dados de teste
|
||||
npx prisma db seed
|
||||
|
||||
# 6. Iniciar o servidor de desenvolvimento
|
||||
pnpm dev
|
||||
# Aplicação disponível em http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.2 Convenções de Código
|
||||
|
||||
### TypeScript
|
||||
|
||||
```typescript
|
||||
// ✅ Usar tipos explícitos em funções públicas
|
||||
export async function getAnimalById(id: string): Promise<Animal | null> { ... }
|
||||
|
||||
// ✅ Interfaces para props de componentes
|
||||
interface AnimalCardProps {
|
||||
animal: Animal;
|
||||
onReserve?: (id: string) => void;
|
||||
}
|
||||
|
||||
// ✅ Zod para validação — nunca "as" para fazer cast de dados externos
|
||||
const schema = z.object({ id: z.string().cuid() });
|
||||
const data = schema.parse(body); // lança erro se inválido
|
||||
|
||||
// ❌ Evitar
|
||||
const data = body as { id: string }; // não valida nada
|
||||
```
|
||||
|
||||
### Componentes React
|
||||
|
||||
```typescript
|
||||
// ✅ Server Components por defeito (sem 'use client')
|
||||
// Apenas adicionar 'use client' quando necessário (hooks, eventos, browser APIs)
|
||||
|
||||
// ✅ Async Server Components para fetch de dados
|
||||
async function AnimalPage({ params }: { params: { id: string } }) {
|
||||
const animal = await getAnimalById(params.id); // fetch directo no servidor
|
||||
if (!animal) notFound();
|
||||
return <AnimalProfile animal={animal} />;
|
||||
}
|
||||
|
||||
// ✅ Separar lógica de UI — componente de apresentação vs. componente de dados
|
||||
// AnimalProfile.tsx — recebe dados como props, sem fetch
|
||||
// app/(main)/animals/[id]/page.tsx — faz fetch, passa para AnimalProfile
|
||||
```
|
||||
|
||||
### API Routes
|
||||
|
||||
```typescript
|
||||
// ✅ Padrão de API Route
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
// 1. Autenticação (se necessário)
|
||||
const session = await getServerSession(authConfig);
|
||||
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
// 2. Validação de inputs
|
||||
const { searchParams } = new URL(req.url);
|
||||
const params = FiltersSchema.safeParse(Object.fromEntries(searchParams));
|
||||
if (!params.success) return Response.json({ error: params.error.issues }, { status: 400 });
|
||||
|
||||
// 3. Lógica de negócio
|
||||
const data = await getAnimals(params.data);
|
||||
|
||||
// 4. Resposta
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
console.error('[GET /api/animals]', error);
|
||||
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.3 Gestão de Estado
|
||||
|
||||
| Tipo de Estado | Solução | Quando usar |
|
||||
|---|---|---|
|
||||
| Dados do servidor | TanStack Query | Listas de animais, detalhes, reservas — qualquer dado que vem da API |
|
||||
| Estado global do cliente | Zustand | Sessão do utilizador, preferências UI (tema), estado do menu |
|
||||
| Estado de formulários | React Hook Form | Todos os formulários — registo, doação, reserva |
|
||||
| Estado de UI local | `useState` | Toggles, modais, estados temporários dentro de um componente |
|
||||
|
||||
```typescript
|
||||
// ✅ Exemplo: store Zustand para preferências do utilizador
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface UIStore {
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
export const useUIStore = create<UIStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
darkMode: false,
|
||||
toggleDarkMode: () => set(state => ({ darkMode: !state.darkMode })),
|
||||
}),
|
||||
{ name: 'ui-preferences' }
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.4 Fluxo de Trabalho Git
|
||||
|
||||
### Branches
|
||||
|
||||
```
|
||||
main → código estável em produção
|
||||
develop → integração contínua de funcionalidades
|
||||
feature/* → novas funcionalidades (ex: feature/donation-flow)
|
||||
fix/* → correcção de bugs (ex: fix/age-validation)
|
||||
docs/* → alterações de documentação
|
||||
```
|
||||
|
||||
### Processo de Desenvolvimento
|
||||
|
||||
```bash
|
||||
# 1. Criar branch a partir de develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b feature/nome-da-funcionalidade
|
||||
|
||||
# 2. Desenvolver + commits frequentes
|
||||
git add .
|
||||
git commit -m "feat: add animal filter by district"
|
||||
|
||||
# 3. Push e Pull Request para develop
|
||||
git push origin feature/nome-da-funcionalidade
|
||||
# Abrir PR no GitHub com descrição do que foi feito
|
||||
|
||||
# 4. Após review e testes → merge para develop
|
||||
# 5. Periodicamente → release: merge develop para main
|
||||
```
|
||||
|
||||
### Mensagens de Commit (Conventional Commits)
|
||||
|
||||
| Prefixo | Uso |
|
||||
|---|---|
|
||||
| `feat:` | Nova funcionalidade |
|
||||
| `fix:` | Correcção de bug |
|
||||
| `docs:` | Alteração de documentação |
|
||||
| `style:` | Formatação, espaços (sem alteração de lógica) |
|
||||
| `refactor:` | Refactorização sem nova funcionalidade |
|
||||
| `test:` | Adicionar ou corrigir testes |
|
||||
| `chore:` | Tarefas de manutenção (deps, config) |
|
||||
|
||||
```bash
|
||||
# ✅ Exemplos de boas mensagens
|
||||
feat: add reservation calendar with available dates
|
||||
fix: correct age validation for February 29 birthdays
|
||||
docs: update API routes documentation
|
||||
refactor: extract donation logic to separate service
|
||||
test: add E2E tests for adoption flow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.5 Testes
|
||||
|
||||
### Estrutura de Testes
|
||||
|
||||
```
|
||||
pawlink/
|
||||
├── __tests__/
|
||||
│ ├── unit/
|
||||
│ │ ├── age-validation.test.ts
|
||||
│ │ ├── format.test.ts
|
||||
│ │ └── schemas.test.ts
|
||||
│ ├── integration/
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── animals.test.ts
|
||||
│ │ │ └── reservations.test.ts
|
||||
│ └── components/
|
||||
│ ├── AnimalCard.test.tsx
|
||||
│ └── DonationFlow.test.tsx
|
||||
└── e2e/
|
||||
├── auth.spec.ts
|
||||
├── adoption.spec.ts
|
||||
└── donation.spec.ts
|
||||
```
|
||||
|
||||
### Exemplos de Testes
|
||||
|
||||
```typescript
|
||||
// __tests__/unit/age-validation.test.ts
|
||||
import { isAdult } from '@/lib/auth/age-validation';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('isAdult', () => {
|
||||
it('deve retornar true para utilizador com exactamente 18 anos hoje', () => {
|
||||
const today = new Date();
|
||||
const birthdate = new Date(today.getFullYear() - 18, today.getMonth(), today.getDate());
|
||||
expect(isAdult(birthdate)).toBe(true);
|
||||
});
|
||||
|
||||
it('deve retornar false para utilizador com 17 anos', () => {
|
||||
const today = new Date();
|
||||
const birthdate = new Date(today.getFullYear() - 17, today.getMonth(), today.getDate());
|
||||
expect(isAdult(birthdate)).toBe(false);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// e2e/adoption.spec.ts (Playwright)
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('fluxo completo de adopção', async ({ page }) => {
|
||||
// Login
|
||||
await page.goto('/login');
|
||||
await page.fill('[name="email"]', 'teste@pawlink.pt');
|
||||
await page.fill('[name="password"]', 'Password123!');
|
||||
await page.click('[type="submit"]');
|
||||
|
||||
// Navegar para animal
|
||||
await page.goto('/animals');
|
||||
await page.click('[data-testid="animal-card"]:first-child');
|
||||
|
||||
// Reservar
|
||||
await page.click('[data-testid="adopt-button"]');
|
||||
await page.click('[data-testid="calendar-day-available"]:first-child');
|
||||
await page.click('[data-testid="confirm-reservation"]');
|
||||
|
||||
// Verificar confirmação
|
||||
await expect(page.locator('[data-testid="reservation-success"]')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.6 Performance
|
||||
|
||||
- **Imagens:** usar sempre `next/image` com `sizes` adequado — geração automática WebP e lazy loading
|
||||
- **Paginação:** API de animais usa cursor-based pagination — nunca carregar mais de 20 por pedido
|
||||
- **Cache TanStack Query:** `staleTime: 5 * 60 * 1000` (5 minutos) para listas de animais
|
||||
- **Connection Pooling:** Supabase Pooler activado para PostgreSQL em produção
|
||||
- **Bundle Analysis:** executar `pnpm analyze` antes de cada release para identificar regressões
|
||||
|
||||
```typescript
|
||||
// Exemplo: query paginada com cursor
|
||||
export async function getAnimals(params: AnimalFilters & { cursor?: string }) {
|
||||
return prisma.animal.findMany({
|
||||
where: buildWhereClause(params),
|
||||
take: 20,
|
||||
skip: params.cursor ? 1 : 0,
|
||||
cursor: params.cursor ? { id: params.cursor } : undefined,
|
||||
orderBy: [{ urgent: 'desc' }, { createdAt: 'desc' }],
|
||||
include: { photos: { where: { isPrimary: true }, take: 1 }, shelter: true }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.7 Acessibilidade
|
||||
|
||||
- **Imagens:** `alt` descritivo obrigatório em todas as imagens (ex: `"Bobi — Labrador macho de 2 anos no Canil de Lisboa"`)
|
||||
- **Formulários:** labels associados a todos os inputs, mensagens de erro ligadas via `aria-describedby`
|
||||
- **Foco:** ordem de foco lógica, indicadores de foco visíveis, skip links no topo
|
||||
- **Contraste:** mínimo 4.5:1 para texto normal, 3:1 para texto grande (testar com Lighthouse)
|
||||
- **Teclado:** todos os elementos interactivos acessíveis por teclado (Tab, Enter, Espaço, Esc)
|
||||
- **Screen readers:** testar com NVDA (Windows) ou VoiceOver (Mac/iOS) nas páginas principais
|
||||
117
docs/11-roadmap.md
Normal file
117
docs/11-roadmap.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 11. Roadmap de Desenvolvimento
|
||||
|
||||
## Visão Geral das Fases
|
||||
|
||||
```
|
||||
Fase 1 — MVP Fase 2 — Doações Fase 3 — IA Fase 4 — Escala
|
||||
[Meses 1–3] [Meses 4–5] [Meses 6–7] [Mês 8+]
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
Adopção completa ──► Doações completas ──► Funcionalidades ──► App móvel
|
||||
Auth + Reservas Stripe + Ração de IA + comunidade Escalabilidade
|
||||
Canis base Brinquedos Match inteligente Internacionalização
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fase 1 — MVP (Meses 1–3)
|
||||
|
||||
**Objectivo:** plataforma funcional de adopção de ponta a ponta
|
||||
|
||||
| Funcionalidade | Prioridade | Estimativa | Dependências |
|
||||
|---|---|---|---|
|
||||
| Configuração do projecto (Next.js, Prisma, Supabase, CI/CD) | Crítica | 2 dias | — |
|
||||
| Esquema da base de dados + migrações iniciais | Crítica | 3 dias | Config |
|
||||
| Autenticação: registo, login, recuperação de palavra-passe | Crítica | 1 semana | DB |
|
||||
| Validação de idade +18 (servidor) e verificação de email | Crítica | 2 dias | Auth |
|
||||
| CRUD de canis (painel de registo de canis) | Alta | 3 dias | DB, Auth |
|
||||
| Upload e gestão de imagens de animais (Supabase Storage) | Alta | 2 dias | DB |
|
||||
| CRUD de animais pelos canis | Crítica | 1 semana | DB, Auth |
|
||||
| Página inicial com listagem de animais (SSR) | Crítica | 1 semana | DB |
|
||||
| Sistema de filtros (distrito, espécie, raça, sexo, esterilizado) | Alta | 1 semana | Listagem |
|
||||
| Ficha detalhada do animal com galeria | Alta | 3 dias | Listagem |
|
||||
| Perfil do canil com horários de funcionamento | Alta | 3 dias | CRUD Canis |
|
||||
| Sistema de reserva com calendário | Crítica | 1,5 semanas | Auth, Animals |
|
||||
| Email de confirmação de reserva (react-email + Resend) | Crítica | 3 dias | Reservas |
|
||||
| Área de conta: histórico de adopções e definições | Média | 4 dias | Auth |
|
||||
| Menu lateral + modo escuro/claro | Média | 3 dias | — |
|
||||
| Deploy inicial na Vercel + domínio pawlink.pt | Alta | 1 dia | Tudo acima |
|
||||
| **Total Fase 1** | | **~10 semanas** | |
|
||||
|
||||
---
|
||||
|
||||
## Fase 2 — Doações (Meses 4–5)
|
||||
|
||||
**Objectivo:** fluxos de doação completos com pagamentos reais
|
||||
|
||||
| Funcionalidade | Prioridade | Estimativa | Dependências |
|
||||
|---|---|---|---|
|
||||
| Integração Stripe (PaymentIntent, Payment Element) | Crítica | 1,5 semanas | Fase 1 |
|
||||
| Suporte MBWay via Stripe Payment Element | Alta | 3 dias | Stripe |
|
||||
| Fluxo de doação monetária (escolha de canil + pagamento) | Crítica | 1 semana | Stripe |
|
||||
| Webhook Stripe (confirmação e recibo) | Crítica | 2 dias | Stripe |
|
||||
| Fluxo de doação de ração (selector + logística) | Alta | 1 semana | DB |
|
||||
| Fluxo de doação de brinquedos | Alta | 3 dias | Ração |
|
||||
| Sistema de necessidades actuais dos canis | Alta | 3 dias | Canis |
|
||||
| Email de recibo de doação | Alta | 2 dias | Resend |
|
||||
| Histórico de doações na área de conta | Média | 2 dias | DB |
|
||||
| Painel dos canis: ver doações recebidas | Média | 3 dias | DB |
|
||||
| **Total Fase 2** | | **~7 semanas** | |
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 — IA e Comunidade (Meses 6–7)
|
||||
|
||||
**Objectivo:** diferenciação com IA e funcionalidades de comunidade
|
||||
|
||||
| Funcionalidade | Prioridade | Estimativa | Dependências |
|
||||
|---|---|---|---|
|
||||
| Match inteligente com Claude API | Alta | 1,5 semanas | Fase 2 |
|
||||
| Chatbot de suporte Paws (streaming) | Média | 1 semana | Anthropic API |
|
||||
| Geração automática de descrições de animais | Média | 3 dias | Anthropic API |
|
||||
| Perfis pós-adopção (utilizadores partilham actualizações) | Média | 1 semana | Fase 1 |
|
||||
| Destaque de animais urgentes na homepage | Alta | 2 dias | DB |
|
||||
| Sistema de notificações por email (novos animais, urgências) | Média | 1 semana | Resend |
|
||||
| Registo de voluntários para canis | Baixa | 4 dias | Auth |
|
||||
| Avaliações de canis por utilizadores (após adopção) | Baixa | 1 semana | Reservas |
|
||||
| **Total Fase 3** | | **~7 semanas** | |
|
||||
|
||||
---
|
||||
|
||||
## Fase 4 — Escala (Mês 8+)
|
||||
|
||||
**Objectivo:** crescimento, optimização e expansão
|
||||
|
||||
| Funcionalidade | Prioridade |
|
||||
|---|---|
|
||||
| App móvel com React Native (partilha de lógica com a web) | Alta |
|
||||
| Relatórios fiscais automáticos para doações dedutíveis em IRS | Média |
|
||||
| Dashboard analítico para administração (métricas, funis, tendências) | Alta |
|
||||
| API pública para integração com associações e municípios | Média |
|
||||
| Suporte multilingue (espanhol e inglês) | Média |
|
||||
| Campanha de adopção sazonal (Natal, Verão) com destaque especial | Média |
|
||||
| Programa de parceiros (pet shops, veterinários) | Baixa |
|
||||
| Expansão a Espanha e mercados ibéricos | Baixa |
|
||||
|
||||
---
|
||||
|
||||
## Critérios de Sucesso por Fase
|
||||
|
||||
| Fase | KPI Principal | Meta |
|
||||
|---|---|---|
|
||||
| Fase 1 — MVP | Reservas de adopção efectuadas | ≥ 50 no primeiro mês após lançamento |
|
||||
| Fase 2 — Doações | Volume de doações processadas | ≥ 500€/mês aos 2 meses |
|
||||
| Fase 3 — IA | Taxa de adopção via match inteligente | ≥ 30% das adopções com match activado |
|
||||
| Fase 4 — Escala | Animais adoptados acumulados | ≥ 1.000 adopções nos primeiros 12 meses |
|
||||
|
||||
---
|
||||
|
||||
## Riscos e Mitigações
|
||||
|
||||
| Risco | Probabilidade | Impacto | Mitigação |
|
||||
|---|---|---|---|
|
||||
| Baixa adesão de canis ao registo na plataforma | Média | Alto | Onboarding simplificado + contacto directo com associações parceiras |
|
||||
| Problemas de pagamento com Stripe/MBWay | Baixa | Alto | Ambiente de teste extenso antes do lançamento; fallback para transferência bancária |
|
||||
| Custos da API Anthropic superiores ao previsto | Média | Médio | Rate limiting, cache de resultados, funcionalidades de IA como opt-in |
|
||||
| Violação de dados (segurança) | Baixa | Alto | Auditorias de segurança periódicas, seguir OWASP Top 10, responsible disclosure program |
|
||||
| Não conformidade com RGPD | Baixa | Alto | Revisão jurídica antes do lançamento, DPA com todos os processadores |
|
||||
66
docs/12-glossario.md
Normal file
66
docs/12-glossario.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 12. Glossário
|
||||
|
||||
| Termo | Definição |
|
||||
|---|---|
|
||||
| **API Route** | Endpoint de servidor criado dentro do Next.js que responde a pedidos HTTP sem necessidade de servidor separado. Corre como função serverless na Vercel. |
|
||||
| **App Router** | Sistema de routing do Next.js 13+ baseado na estrutura de directórios, com suporte nativo a Server Components e layouts aninhados. |
|
||||
| **bcrypt** | Algoritmo de hashing criptográfico usado para armazenar palavras-passe de forma segura e irreversível. O factor de custo (12) determina a lentidão intencional do hash. |
|
||||
| **CDN** | Content Delivery Network — rede global de servidores que entrega assets (imagens, CSS, JS) a partir do ponto geograficamente mais próximo do utilizador. |
|
||||
| **CNPD** | Comissão Nacional de Protecção de Dados — entidade reguladora portuguesa responsável pela supervisão do cumprimento do RGPD. |
|
||||
| **CSR** | Client-Side Rendering — renderização de HTML feita no browser do utilizador usando JavaScript. Resulta em HTML vazio inicial (mau para SEO). |
|
||||
| **Cursor-based Pagination** | Técnica de paginação que usa o ID do último item como referência para a próxima página, mais eficiente que offset para grandes datasets. |
|
||||
| **DPA** | Data Processing Agreement — contrato celebrado com processadores de dados (Stripe, Resend, etc.) para garantir conformidade com o RGPD. |
|
||||
| **DX** | Developer Experience — qualidade da experiência de desenvolvimento, incluindo ferramentas, mensagens de erro claras e produtividade. |
|
||||
| **Edge Network** | Rede de servidores distribuída globalmente (como a Vercel) que executa código próximo dos utilizadores finais para reduzir latência. |
|
||||
| **Embedding** | Representação vectorial de texto ou dados que permite medir similaridade semântica — usado no match inteligente de animais. |
|
||||
| **JWT** | JSON Web Token — standard para transmissão segura de informação entre partes como token compacto, URL-safe e assinado digitalmente. |
|
||||
| **KPI** | Key Performance Indicator — métrica usada para avaliar o sucesso de um objectivo (ex: número de adopções por mês). |
|
||||
| **Middleware** | Código que é executado entre o pedido HTTP e a resposta. No Next.js, intercepta pedidos antes de chegarem às páginas para verificar autenticação. |
|
||||
| **ORM** | Object-Relational Mapping — camada de abstracção que permite interagir com a base de dados usando código TypeScript em vez de SQL directo. |
|
||||
| **PCI DSS** | Payment Card Industry Data Security Standard — norma de segurança para sistemas que processam dados de cartões de pagamento. Stripe é Level 1 (o mais elevado). |
|
||||
| **Prisma** | ORM moderno para Node.js e TypeScript com esquema declarativo, migrações automáticas e cliente type-safe gerado automaticamente. |
|
||||
| **Rate Limiting** | Mecanismo que limita o número de pedidos que um utilizador pode fazer num determinado período de tempo, prevenindo abusos. |
|
||||
| **React Hook Form** | Biblioteca para gestão de formulários em React com performance optimizada (evita re-renders desnecessários) e integração com Zod. |
|
||||
| **RGPD** | Regulamento Geral sobre a Protecção de Dados — legislação europeia que regula o tratamento de dados pessoais, em vigor desde Maio de 2018. |
|
||||
| **Server Component** | Componente React renderizado exclusivamente no servidor, sem JavaScript enviado para o cliente. Pode aceder directamente à base de dados. |
|
||||
| **Serverless** | Modelo de execução onde o código corre em funções geridas pelo fornecedor cloud (Vercel), escalando automaticamente sem gestão de servidor. |
|
||||
| **shadcn/ui** | Colecção de componentes React acessíveis e personalizáveis, construídos sobre Radix UI e Tailwind CSS. Copiados directamente para o projecto (não é uma dependência de pacote). |
|
||||
| **SSG** | Static Site Generation — geração de HTML no momento do build, servido directamente via CDN. Ideal para páginas que não mudam frequentemente. |
|
||||
| **SSR** | Server-Side Rendering — renderização de HTML feita no servidor a cada pedido, antes de ser enviada para o browser. Óptimo para SEO e dados dinâmicos. |
|
||||
| **Stripe** | Plataforma de pagamentos online que permite aceitar cartões de crédito, MBWay, SEPA e outras formas de pagamento. Certificado PCI DSS Level 1. |
|
||||
| **Stripe Payment Element** | Componente UI do Stripe que renderiza um formulário de pagamento seguro directamente no browser, suportando múltiplos métodos de pagamento. |
|
||||
| **TanStack Query** | Biblioteca para gestão de estado assíncrono e cache de dados do servidor em React. Anteriormente conhecida como React Query. |
|
||||
| **Type-safe** | Código onde erros de tipo são detectados em tempo de compilação (TypeScript), antes de a aplicação correr, eliminando uma classe inteira de bugs. |
|
||||
| **WCAG** | Web Content Accessibility Guidelines — conjunto de directrizes do W3C para tornar conteúdo web acessível a pessoas com deficiência. O Nível AA é o standard mínimo recomendado. |
|
||||
| **Webhook** | Notificação HTTP enviada automaticamente por um serviço externo (ex: Stripe) quando um evento específico ocorre (ex: pagamento confirmado). |
|
||||
| **Zod** | Biblioteca TypeScript-first para declaração e validação de esquemas de dados, com inferência automática de tipos. |
|
||||
| **Zustand** | Biblioteca minimalista de gestão de estado global para React, sem boilerplate, baseada em hooks. |
|
||||
|
||||
---
|
||||
|
||||
## Acrónimos
|
||||
|
||||
| Acrónimo | Significado |
|
||||
|---|---|
|
||||
| API | Application Programming Interface |
|
||||
| CDN | Content Delivery Network |
|
||||
| CNPD | Comissão Nacional de Protecção de Dados |
|
||||
| CSP | Content Security Policy |
|
||||
| CSR | Client-Side Rendering |
|
||||
| DPA | Data Processing Agreement |
|
||||
| DX | Developer Experience |
|
||||
| E2E | End-to-End (testes) |
|
||||
| HMAC | Hash-based Message Authentication Code |
|
||||
| HSTS | HTTP Strict Transport Security |
|
||||
| JWT | JSON Web Token |
|
||||
| KPI | Key Performance Indicator |
|
||||
| ORM | Object-Relational Mapping |
|
||||
| PAP | Prova de Aptidão Profissional |
|
||||
| PCI DSS | Payment Card Industry Data Security Standard |
|
||||
| RGPD | Regulamento Geral sobre a Protecção de Dados |
|
||||
| SSG | Static Site Generation |
|
||||
| SSR | Server-Side Rendering |
|
||||
| TLS | Transport Layer Security |
|
||||
| UI | User Interface |
|
||||
| UX | User Experience |
|
||||
| WCAG | Web Content Accessibility Guidelines |
|
||||
154
docs/PROGRESS.md
Normal file
154
docs/PROGRESS.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# PawLink — Registo de Progresso
|
||||
|
||||
## Estado Geral
|
||||
- **Fase actual:** Fase 1 — MVP
|
||||
- **Última actualização:** 2026-05-04 09:37
|
||||
- **Sessão #:** 1
|
||||
|
||||
---
|
||||
|
||||
## Legenda de Estados
|
||||
- ✅ Concluído
|
||||
- 🔄 Em progresso
|
||||
- ⏳ Por fazer
|
||||
- ❌ Bloqueado (indicar motivo)
|
||||
- ⚠️ Atenção necessária
|
||||
|
||||
---
|
||||
|
||||
## Fase 1 — MVP
|
||||
|
||||
### Infra-estrutura e Configuração
|
||||
- ⏳ Setup Next.js 14 + TypeScript + Tailwind + shadcn/ui
|
||||
- ⏳ Configuração ESLint + Prettier + Husky
|
||||
- ⏳ Esquema Prisma + ligação Supabase
|
||||
- ⏳ Migrações iniciais da base de dados
|
||||
- ⏳ Seed de dados de desenvolvimento
|
||||
- ⏳ Configuração variáveis de ambiente (.env.example)
|
||||
- ⏳ Deploy inicial Vercel + domínio
|
||||
|
||||
### Autenticação
|
||||
- ⏳ Configuração NextAuth.js
|
||||
- ⏳ Registo de utilizador (nome, email, password, birthdate, distrito)
|
||||
- ⏳ Validação de +18 anos no servidor
|
||||
- ⏳ Verificação de email (Resend)
|
||||
- ⏳ Login por email + password
|
||||
- ⏳ Recuperação de palavra-passe
|
||||
- ⏳ Middleware de protecção de rotas
|
||||
- ⏳ Controlo de acesso por roles (USER, SHELTER_ADMIN, ADMIN)
|
||||
|
||||
### Canis
|
||||
- ⏳ Registo de canis (painel admin)
|
||||
- ⏳ Perfil público do canil (nome, morada, horários, contacto)
|
||||
- ⏳ Painel privado do canil (dashboard base)
|
||||
|
||||
### Animais
|
||||
- ⏳ CRUD de animais (painel do canil)
|
||||
- ⏳ Upload de fotos (Supabase Storage)
|
||||
- ⏳ Listagem pública com SSR (página inicial)
|
||||
- ⏳ Sistema de filtros (distrito, espécie, raça, sexo, esterilizado)
|
||||
- ⏳ Ficha detalhada do animal
|
||||
- ⏳ Galeria de fotos
|
||||
- ⏳ Badge de animal urgente
|
||||
- ⏳ Estado do animal (Disponível / Reservado / Adoptado)
|
||||
|
||||
### Reservas
|
||||
- ⏳ Calendário de datas disponíveis
|
||||
- ⏳ Criação de reserva (animal → Reservado)
|
||||
- ⏳ Email de confirmação (react-email + Resend)
|
||||
- ⏳ Histórico de reservas na área de conta
|
||||
- ⏳ Confirmação/cancelamento pelo canil
|
||||
|
||||
### UI / UX
|
||||
- ⏳ Layout principal (Header, Footer)
|
||||
- ⏳ Menu lateral (3 traços, canto superior direito)
|
||||
- ⏳ Modo escuro / claro (toggle + persistência)
|
||||
- ⏳ Área de conta (definições, palavra-passe, dados)
|
||||
- ⏳ Design responsivo (mobile-first)
|
||||
|
||||
---
|
||||
|
||||
## Fase 2 — Doações (Por iniciar)
|
||||
|
||||
- ⏳ Integração Stripe (PaymentIntent + Payment Element)
|
||||
- ⏳ Suporte MBWay
|
||||
- ⏳ Fluxo doação monetária
|
||||
- ⏳ Webhook Stripe
|
||||
- ⏳ Fluxo doação de ração
|
||||
- ⏳ Fluxo doação de brinquedos
|
||||
- ⏳ Sistema de necessidades dos canis (ShelterNeed)
|
||||
- ⏳ Email recibo de doação
|
||||
- ⏳ Histórico de doações na conta
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 — IA e Comunidade (Por iniciar)
|
||||
|
||||
- ⏳ Match inteligente (Claude API)
|
||||
- ⏳ Chatbot de suporte Paws (streaming)
|
||||
- ⏳ Geração automática de descrições de animais
|
||||
- ⏳ Perfis pós-adopção
|
||||
- ⏳ Sistema de notificações por email
|
||||
- ⏳ Destaque de animais urgentes
|
||||
- ⏳ Registo de voluntários
|
||||
- ⏳ Avaliações de canis
|
||||
|
||||
---
|
||||
|
||||
## Fase 4 — Escala (Por iniciar)
|
||||
|
||||
- ⏳ App móvel React Native
|
||||
- ⏳ Relatórios fiscais (IRS)
|
||||
- ⏳ Dashboard analítico
|
||||
- ⏳ API pública
|
||||
- ⏳ Suporte multilingue
|
||||
|
||||
---
|
||||
|
||||
## Decisões Técnicas Tomadas
|
||||
|
||||
| Data | Decisão | Motivo |
|
||||
|------|---------|--------|
|
||||
| — | — | — |
|
||||
|
||||
---
|
||||
|
||||
## Problemas Conhecidos / Bloqueios
|
||||
|
||||
| # | Descrição | Estado | Sessão detectada |
|
||||
|---|-----------|--------|-----------------|
|
||||
| — | — | — | — |
|
||||
|
||||
---
|
||||
|
||||
## Dependências Externas Configuradas
|
||||
|
||||
| Serviço | Estado | Notas |
|
||||
|---------|--------|-------|
|
||||
| Supabase (PostgreSQL) | ⏳ Por configurar | — |
|
||||
| Supabase Storage | ⏳ Por configurar | — |
|
||||
| Vercel | ⏳ Por configurar | — |
|
||||
| Stripe | ⏳ Por configurar | — |
|
||||
| Resend | ⏳ Por configurar | — |
|
||||
| Anthropic Claude API | ⏳ Por configurar | — |
|
||||
| Upstash Redis | ⏳ Por configurar | — |
|
||||
| Cloudflare | ⏳ Por configurar | — |
|
||||
|
||||
---
|
||||
|
||||
## Histórico de Sessões
|
||||
|
||||
### Sessão #1 — 2026-05-04
|
||||
**Duração:** —
|
||||
**Trabalho realizado:**
|
||||
- Leitura de toda a documentação
|
||||
- Criação de docs/PROGRESS.md
|
||||
|
||||
**Ficheiros criados/modificados:**
|
||||
- docs/PROGRESS.md
|
||||
|
||||
**Próximos passos para a sessão seguinte:**
|
||||
- Iniciar a Fase 1 — Infra-estrutura e Configuração (Setup Next.js, instalar dependências, prisma init, shadcn init)
|
||||
|
||||
**Notas:**
|
||||
- Nenhuma.
|
||||
43
docs/README.md
Normal file
43
docs/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 🐾 PawLink — Documentação do Projecto
|
||||
|
||||
**Plataforma de Adopção e Doação Animal**
|
||||
Prova de Aptidão Profissional (PAP) — Ano Lectivo 2024/2025
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
| # | Documento | Conteúdo |
|
||||
|---|---|---|
|
||||
| 01 | [Visão Geral](./01-visao-geral.md) | Problema, solução, público-alvo, proposta de valor, modelo de negócio |
|
||||
| 02 | [Requisitos](./02-requisitos.md) | Requisitos funcionais e não-funcionais, restrições |
|
||||
| 03 | [Arquitectura](./03-arquitectura.md) | Diagrama de arquitectura, camadas, fluxos de dados, decisões técnicas |
|
||||
| 04 | [Base de Dados](./04-base-de-dados.md) | ERD, descrição das entidades, esquema Prisma completo, estratégia de indexação |
|
||||
| 05 | [Fluxos de Utilizador](./05-fluxos-utilizador.md) | User stories, fluxos detalhados de adopção, doação e navegação |
|
||||
| 06 | [Stack Tecnológica](./06-stack-tecnologica.md) | Todas as tecnologias com versões, funções e justificações |
|
||||
| 07 | [Segurança](./07-seguranca.md) | Autenticação, autorização, RGPD, conformidade de pagamentos |
|
||||
| 08 | [Inteligência Artificial](./08-ia.md) | Casos de uso, agente de match, chatbot, system prompts, mecanismos de contexto |
|
||||
| 09 | [Estrutura do Projecto](./09-estrutura-projecto.md) | Árvore de directórios completa, convenções de nomenclatura |
|
||||
| 10 | [Orientações de Desenvolvimento](./10-orientacoes-desenvolvimento.md) | Setup, convenções de código, Git, testes, performance, acessibilidade |
|
||||
| 11 | [Roadmap](./11-roadmap.md) | 4 fases de desenvolvimento com estimativas, KPIs e gestão de riscos |
|
||||
| 12 | [Glossário](./12-glossario.md) | Definição de todos os termos técnicos e acrónimos usados |
|
||||
|
||||
---
|
||||
|
||||
## Resumo Técnico
|
||||
|
||||
| Aspecto | Escolha |
|
||||
|---|---|
|
||||
| **Framework** | Next.js 14+ (App Router, SSR) |
|
||||
| **Linguagem** | TypeScript 5+ |
|
||||
| **Base de Dados** | PostgreSQL via Supabase + Prisma ORM |
|
||||
| **Autenticação** | NextAuth.js com verificação de +18 anos |
|
||||
| **Pagamentos** | Stripe (Cartão + MBWay) |
|
||||
| **Email** | Resend + react-email |
|
||||
| **IA** | Anthropic Claude API (match + chatbot) |
|
||||
| **Hosting** | Vercel + Cloudflare CDN |
|
||||
| **Estilos** | Tailwind CSS + shadcn/ui |
|
||||
|
||||
---
|
||||
|
||||
*Documentação gerada para a PAP 2024/2025*
|
||||
Reference in New Issue
Block a user