first commit

This commit is contained in:
2026-05-04 09:43:36 +01:00
commit dfae1b5335
37 changed files with 10343 additions and 0 deletions

58
docs/01-visao-geral.md Normal file
View 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 (12%) 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
View 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
View 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
View 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
```

View 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

View 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: ~025€/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
View 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
View 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

View 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 };
```

View 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
View 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 13] [Meses 45] [Meses 67] [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 13)
**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 45)
**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 67)
**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
View 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
View 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
View 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*