riseup-squad18/README.md
2025-10-30 17:20:28 -03:00

1011 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MediConnect - Sistema de Agendamento Médico
Sistema completo de gestão médica com agendamento inteligente, prontuários eletrônicos e gerenciamento de pacientes.
**Stack:** React + TypeScript + Vite + TailwindCSS + Supabase
**Deploy:** Cloudflare Pages
---
## 🚀 Acesso ao Sistema
- **URL Principal:** https://mediconnectbrasil.app/
- **URL Cloudflare:** https://mediconnect-5oz.pages.dev/
### Credenciais de Teste
**Médico:**
- Email: medico@teste.com
- Senha: senha123
**Paciente:**
- Email: paciente@teste.com
- Senha: senha123
**Secretária:**
- Email: secretaria@teste.com
- Senha: senha123
---
## 🏗️ Arquitetura
```
Frontend (React/Vite)
Supabase Backend
├── Auth (JWT + Magic Link)
├── PostgreSQL (PostgREST)
├── Edge Functions (Slots, Criação de Usuários)
└── Storage (Avatares, Documentos)
Cloudflare Pages (Deploy)
```
---
## <20> Instalação e Execução
### Pré-requisitos
- Node.js 18+
- pnpm (recomendado) ou npm
### Instalação
```bash
# Instalar dependências
pnpm install
# Iniciar desenvolvimento
pnpm dev
# Acessar em http://localhost:5173
```
### Build e Deploy
```bash
# Build de produção
pnpm build
# Deploy para Cloudflare Pages
pnpm wrangler pages deploy dist --project-name=mediconnect --branch=production
```
---
## ✨ Funcionalidades Principais
### 🏥 Para Médicos
- ✅ Agenda personalizada com disponibilidade configurável
- ✅ Gerenciamento de exceções (bloqueios e horários extras)
- ✅ Prontuário eletrônico completo
- ✅ Histórico de consultas do paciente
- ✅ Dashboard com métricas e estatísticas
- ✅ Teleconsulta e presencial
### 👥 Para Pacientes
- ✅ Agendamento inteligente com slots disponíveis em tempo real
- ✅ Histórico completo de consultas
- ✅ Visualização e download de relatórios médicos (PDF)
- ✅ Perfil com avatar e dados pessoais
- ✅ Filtros por médico, especialidade e data
### 🏢 Para Secretárias
- ✅ Gerenciamento completo de médicos, pacientes e consultas
- ✅ Cadastro com validação de CPF e CRM
- ✅ Configuração de agenda médica (horários e exceções)
- ✅ Busca e filtros avançados
- ✅ Confirmação profissional para exclusões
### 🔐 Sistema de Autenticação
- ✅ Login com email/senha
- ✅ Magic Link (login sem senha)
- ✅ Recuperação de senha
- ✅ Tokens JWT com refresh automático
- ✅ Controle de acesso por role (médico/paciente/secretária)
---
## 🔧 Tecnologias
### Frontend
- **React 18** - Interface moderna e reativa
- **TypeScript** - Tipagem estática
- **Vite** - Build ultra-rápido
- **TailwindCSS** - Estilização utilitária
- **React Router** - Navegação SPA
- **Axios** - Cliente HTTP
- **date-fns** - Manipulação de datas
- **jsPDF** - Geração de PDFs
- **Lucide Icons** - Ícones modernos
### Backend (Supabase)
- **PostgreSQL** - Banco de dados relacional
- **PostgREST** - API REST automática
- **Edge Functions** - Funções serverless (Deno)
- **Storage** - Armazenamento de arquivos
- **Auth** - Autenticação e autorização
### Deploy
- **Cloudflare Pages** - Hospedagem global com CDN
---
## 📁 Estrutura do Projeto
```
MEDICONNECT 2/
├── src/
│ ├── components/ # Componentes React
│ │ ├── auth/ # Login, cadastro, recuperação
│ │ ├── secretaria/ # Painéis da secretária
│ │ ├── agenda/ # Sistema de agendamento
│ │ ├── consultas/ # Gerenciamento de consultas
│ │ └── ui/ # Componentes reutilizáveis
│ ├── pages/ # Páginas da aplicação
│ │ ├── Home.tsx
│ │ ├── PainelMedico.tsx
│ │ ├── PainelSecretaria.tsx
│ │ └── AgendamentoPaciente.tsx
│ ├── services/ # Camada de API
│ │ ├── api/ # Cliente HTTP
│ │ ├── auth/ # Autenticação
│ │ ├── appointments/ # Agendamentos
│ │ ├── doctors/ # Médicos
│ │ ├── patients/ # Pacientes
│ │ ├── availability/ # Disponibilidade
│ │ └── avatars/ # Avatares
│ ├── context/ # Context API
│ ├── hooks/ # Custom hooks
│ ├── types/ # TypeScript types
│ └── utils/ # Funções utilitárias
├── public/ # Arquivos estáticos
├── scripts/ # Scripts de utilidade
└── dist/ # Build de produção
```
---
## 🔑 APIs e Serviços
### Principais Endpoints
#### Agendamentos
```typescript
// Buscar slots disponíveis (Edge Function)
POST /functions/v1/get-available-slots
{
"doctor_id": "uuid",
"date": "2025-10-30"
}
// Criar agendamento
POST /rest/v1/appointments
{
"doctor_id": "uuid",
"patient_id": "uuid",
"scheduled_at": "2025-10-30T09:00:00Z",
"duration_minutes": 30,
"appointment_type": "presencial"
}
```
#### Disponibilidade
```typescript
// Listar disponibilidade do médico
GET /rest/v1/doctor_availability?doctor_id=eq.{uuid}
// Criar horário de atendimento
POST /rest/v1/doctor_availability
// Atualizar disponibilidade
PATCH /rest/v1/doctor_availability?id=eq.{uuid}
```
#### Usuários
```typescript
// Criar médico (Edge Function com validações)
POST /functions/v1/create-doctor
// Criar paciente
POST /rest/v1/patients
// Listar médicos
GET /rest/v1/doctors?select=*
// Atualizar perfil
PATCH /rest/v1/doctors?id=eq.{uuid}
```
**Documentação completa:** Ver [AGENDAMENTO-SLOTS-API.md](./AGENDAMENTO-SLOTS-API.md)
---
## 🔒 Autenticação e Permissões
### Sistema de Autenticação
- **JWT Tokens** com refresh automático
- **Magic Link** - Login sem senha via email
- **Recuperação de senha** com email
- **Interceptors** adicionam token automaticamente
- **Renovação automática** quando token expira
### Roles e Permissões (RLS)
**Admin/Gestor:**
- Acesso completo a todos os recursos
- Criar/editar/deletar usuários
- Visualizar todos os dados
**Médicos:**
- Gerenciar agenda e disponibilidade
- Visualizar todos os pacientes
- Criar e editar prontuários
- Ver apenas próprios agendamentos
**Pacientes:**
- Agendar consultas
- Visualizar histórico próprio
- Editar perfil pessoal
- Download de relatórios médicos
**Secretárias:**
- Cadastrar médicos e pacientes
- Gerenciar agendamentos
- Configurar agendas médicas
- Busca e filtros avançados
---
## 🎨 Recursos de Acessibilidade
- ✅ Modo de alto contraste
- ✅ Ajuste de tamanho de fonte
- ✅ Navegação por teclado
- ✅ Leitores de tela compatíveis
- ✅ Menu de acessibilidade flutuante
---
## 📊 Dashboards e Relatórios
### Médico
- Total de pacientes atendidos
- Consultas do dia/semana/mês
- Próximas consultas
- Histórico de atendimentos
### Paciente
- Histórico de consultas
- Relatórios médicos com download PDF
- Próximos agendamentos
- Acompanhamento médico
### Secretária
- Visão geral de agendamentos
- Filtros por médico, data e status
- Busca de pacientes e médicos
- Estatísticas gerais
---
## 🚀 Melhorias Recentes (Outubro 2025)
### Sistema de Agendamento
- ✅ API de slots disponíveis (Edge Function)
- ✅ Cálculo automático de horários
- ✅ Validação de antecedência mínima
- ✅ Verificação de conflitos
- ✅ Interface otimizada
### Formatação de Dados
- ✅ Limpeza automática de telefone/CPF
- ✅ Formatação de nomes de médicos ("Dr.")
- ✅ Validação de campos obrigatórios
- ✅ Máscaras de entrada
### UX/UI
- ✅ Diálogos de confirmação profissionais
- ✅ Filtros de busca em todas as listas
- ✅ Feedback visual melhorado
- ✅ Loading states consistentes
- ✅ Mensagens de erro claras
### Performance
- ✅ Build otimizado (~424KB)
- ✅ Code splitting
- ✅ Lazy loading de rotas
- ✅ Cache de assets
---
## 📝 Convenções de Código
### TypeScript
- Interfaces para todas as entidades
- Tipos explícitos em funções
- Evitar `any` (usar `unknown` quando necessário)
### Componentes React
- Functional components com hooks
- Props tipadas com interfaces
- Estado local com useState/useContext
- Effects para side effects
### Serviços
- Um serviço por entidade (doctorService, patientService)
- Métodos assíncronos com try/catch
- Logs de debug no console
- Tratamento de erros consistente
### Nomenclatura
- **Componentes:** PascalCase (ex: `AgendamentoConsulta`)
- **Arquivos:** kebab-case ou PascalCase conforme tipo
- **Variáveis:** camelCase (ex: `selectedDate`)
- **Constantes:** UPPER_SNAKE_CASE (ex: `API_CONFIG`)
---
## 🐛 Troubleshooting
### Erro 401 (Unauthorized)
- Verificar se token está no localStorage
- Tentar logout e login novamente
- Verificar permissões RLS no Supabase
### Slots não aparecem
- Verificar se médico tem disponibilidade configurada
- Verificar se data é futura
- Verificar logs da Edge Function
### Upload de avatar falha
- Verificar tamanho do arquivo (max 2MB)
- Verificar formato (jpg, png)
- Verificar permissões do Storage no Supabase
### Build falha
- Limpar cache: `rm -rf node_modules dist`
- Reinstalar: `pnpm install`
- Verificar versão do Node (18+)
---
## 👥 Equipe
**RiseUp Squad 18**
- Desenvolvimento: GitHub Copilot + Equipe
- Data: Outubro 2025
---
## 📄 Licença
Este projeto é privado e desenvolvido para fins educacionais.
---
## <20> Links Úteis
- [Supabase Docs](https://supabase.com/docs)
- [React Docs](https://react.dev/)
- [Vite Docs](https://vitejs.dev/)
- [TailwindCSS Docs](https://tailwindcss.com/)
- [Cloudflare Pages](https://pages.cloudflare.com/)
---
**Última atualização:** 30 de Outubro de 2025
- ✅ Veem apenas **seus próprios dados**
- ✅ Veem apenas **seus próprios laudos** (filtro: `patient_id = paciente`)
- ✅ Veem apenas **seus próprios agendamentos**
- ✅ Podem agendar consultas
#### 👩‍💼 Secretárias:
- ✅ Veem **todos os pacientes**
- ✅ Veem **todos os agendamentos**
- ✅ Veem **todos os laudos**
- ✅ Podem criar/editar agendamentos
---
## 📡 API e Serviços
### Estrutura de Services
O projeto usa uma arquitetura de **services** que encapsulam toda comunicação com o Supabase:
```
src/services/
├── api/
│ ├── client.ts # Cliente HTTP (Axios configurado)
│ └── config.ts # Configurações da API
├── auth/
│ ├── authService.ts # Login, signup, recuperação de senha
│ └── types.ts
├── users/
│ └── userService.ts # getUserInfo, createUser, deleteUser
├── patients/
│ └── patientService.ts # CRUD de pacientes
├── doctors/
│ └── doctorService.ts # CRUD de médicos
├── appointments/
│ └── appointmentService.ts # Agendamentos
└── availability/
└── availabilityService.ts # Disponibilidade médica
```
### Principais Endpoints
#### 🔐 Autenticação (authService)
```typescript
// Login com Email e Senha
await authService.login({ email, password });
// Retorna: { access_token, refresh_token, user }
// Magic Link (Login sem senha)
await authService.sendMagicLink("email@example.com");
// Envia email com link de autenticação
// Usuário clica no link e é automaticamente autenticado
// Recuperação de senha
await authService.requestPasswordReset("email@example.com");
// Envia email com link de reset
// Atualizar senha
await authService.updatePassword(accessToken, newPassword);
// Usado na página /reset-password
// Refresh token
await authService.refreshToken(refreshToken);
```
**Fluxo Magic Link:**
1. Usuário solicita magic link na tela de login
2. `localStorage.setItem("magic_link_redirect", "/painel-medico")` salva contexto
3. Supabase envia email com link único
4. Usuário clica no link
5. `Home.tsx` detecta hash params e redireciona para `/auth/callback`
6. `AuthCallback.tsx` processa tokens, salva no localStorage
7. `window.location.href` redireciona para painel salvo
8. Página recarrega com `AuthContext` atualizado
9. Usuário autenticado no painel correto ✅
#### 👤 Usuários (userService)
```typescript
// Buscar informações do usuário autenticado (com roles)
const userInfo = await userService.getUserInfo();
// Retorna: { id, email, full_name, roles: ['medico', 'admin'] }
// Criar usuário com role
await userService.createUser({
email: "user@example.com",
full_name: "Nome Completo",
role: "medico", // ou 'admin', 'paciente', 'secretaria'
});
// Deletar usuário
await userService.deleteUser(userId);
```
#### 🏥 Pacientes (patientService)
```typescript
// Listar pacientes
const patients = await patientService.list();
// Buscar por ID
const patient = await patientService.getById(id);
// Criar paciente
await patientService.create({
email: "paciente@example.com",
full_name: "Nome Paciente",
cpf: "12345678900",
phone_mobile: "11999999999",
});
// Atualizar paciente
await patientService.update(id, { phone_mobile: "11888888888" });
// Deletar paciente
await patientService.delete(id);
```
#### 👨‍⚕️ Médicos (doctorService)
```typescript
// Listar médicos
const doctors = await doctorService.list();
// Buscar por ID
const doctor = await doctorService.getById(id);
// Buscar disponibilidade
const slots = await doctorService.getAvailableSlots(doctorId, date);
```
#### 📅 Agendamentos (appointmentService)
```typescript
// Listar agendamentos (filtrado por role automaticamente)
const appointments = await appointmentService.list();
// Criar agendamento
await appointmentService.create({
patient_id: "uuid-paciente",
doctor_id: "uuid-medico",
scheduled_at: "2025-10-25T10:00:00",
reason: "Consulta de rotina",
});
// Atualizar status
await appointmentService.updateStatus(id, "confirmed");
// Status: requested, confirmed, completed, cancelled
// Cancelar
await appointmentService.cancel(id, "Motivo do cancelamento");
```
#### 📸 Avatares (avatarService)
```typescript
// Upload de avatar (usa FormData com x-upsert: true)
const file = event.target.files[0]; // File do input
const result = await avatarService.upload({
userId: user.id,
file: file,
});
// Retorna: { Key: "url-publica-do-avatar" }
// Obter URL pública do avatar
const url = avatarService.getPublicUrl({
userId: user.id,
ext: "png", // ou 'jpg', 'webp'
});
// Retorna: https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/{userId}/avatar.png
// Auto-load de avatar (testa múltiplas extensões)
const extensions = ["png", "jpg", "webp"];
for (const ext of extensions) {
const url = avatarService.getPublicUrl({ userId: user.id, ext });
const response = await fetch(url, { method: "HEAD" });
if (response.ok) {
setAvatarUrl(url);
break;
}
}
```
**Detalhes importantes:**
- Upload usa **multipart/form-data** via FormData
- Header `x-upsert: true` permite sobrescrever avatares existentes
- Suporta formatos: PNG, JPG, WEBP (máx 2MB)
- URLs públicas não requerem autenticação
- Avatar é carregado automaticamente nos painéis
---
## 🔧 Configuração da API
```typescript
// src/services/api/config.ts
export const API_CONFIG = {
SUPABASE_URL: "https://yuanqfswhberkoevtmfr.supabase.co",
SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
AUTH_URL: `${SUPABASE_URL}/auth/v1`,
REST_URL: `${SUPABASE_URL}/rest/v1`,
APP_URL: "https://mediconnectbrasil.app",
TIMEOUT: 30000,
STORAGE_KEYS: {
ACCESS_TOKEN: "mediconnect_access_token",
REFRESH_TOKEN: "mediconnect_refresh_token",
USER: "mediconnect_user",
},
};
```
---
## 🚀 Deploy no Cloudflare Pages
### Via Wrangler CLI
```powershell
# Build
pnpm build
# Deploy para production
npx wrangler pages deploy dist --project-name=mediconnect --branch=production
# Deploy com mudanças não commitadas
npx wrangler pages deploy dist --project-name=mediconnect --commit-dirty=true
```
### Configuração do Projeto
- **Production Branch:** `production`
- **Build Command:** `pnpm build`
- **Build Output:** `dist`
- **Custom Domain:** `mediconnectbrasil.app`
### URLs
- **Production:** https://mediconnectbrasil.app/
- **Preview:** https://mediconnect-5oz.pages.dev/
- **Branch Preview:** https://[branch].mediconnect-5oz.pages.dev/
---
## 1. Variáveis de Ambiente (`.env` / `.env.local`)
| Variável | Obrigatória | Descrição |
| ------------------------ | ----------- | --------------------------------------------------------------- |
| `VITE_SUPABASE_URL` | Sim | URL base do projeto Supabase (`https://<ref>.supabase.co`) |
| `VITE_SUPABASE_ANON_KEY` | Sim | Chave pública (anon) usada para Auth password grant e PostgREST |
| `VITE_APP_ENV` | Não | Identifica ambiente (ex: `dev`, `staging`, `prod`) |
**Nota:** As variáveis já estão configuradas no código em `src/services/api/config.ts`. Não é necessário criar arquivo `.env` para desenvolvimento local.
Boas práticas:
- Nunca exponha Service Role Key no frontend.
- Não comitar `.env` usar `.env.example` como referência (se houver).
---
## 2. Fluxo de Login e Validação de Roles
### Exemplo: Login de Médico
```typescript
// pages/LoginMedico.tsx
const handleLogin = async (e: FormEvent) => {
e.preventDefault();
try {
// 1. Autenticar com Supabase
const loginResponse = await authService.login({ email, password });
// 2. Salvar tokens
localStorage.setItem("access_token", loginResponse.access_token);
localStorage.setItem("refresh_token", loginResponse.refresh_token);
// 3. Buscar informações do usuário com roles
const userInfo = await userService.getUserInfo();
const roles = userInfo.roles || [];
// 4. Validar permissões
const isAdmin = roles.includes("admin");
const isGestor = roles.includes("gestor");
const isMedico = roles.includes("medico");
if (!isAdmin && !isGestor && !isMedico) {
toast.error("Você não tem permissão para acessar esta área");
await authService.logout();
return;
}
// 5. Redirecionar para o painel
navigate("/painel-medico");
} catch (error) {
toast.error("Email ou senha incorretos");
}
};
```
---
## 3. Recuperação de Senha
### Fluxo Completo
1. **Usuário clica em "Esqueceu a senha?"**
2. **Sistema envia email com link de recuperação**
3. **Usuário clica no link → redireciona para `/reset-password`**
4. **Sistema extrai token do URL (#access_token=...)**
5. **Usuário define nova senha**
6. **Sistema atualiza senha e redireciona para login**
### Implementação
```typescript
// Solicitar recuperação (páginas de login)
const handlePasswordReset = async () => {
try {
await authService.requestPasswordReset(email);
toast.success("Link de recuperação enviado para seu email");
} catch (error) {
toast.error("Erro ao enviar email de recuperação");
}
};
// Página de reset (ResetPassword.tsx)
useEffect(() => {
// Extrair token do URL
const hash = window.location.hash;
const params = new URLSearchParams(hash.substring(1));
const token = params.get("access_token");
if (token) {
setAccessToken(token);
setIsLoading(false);
}
}, []);
const handleSubmit = async (e: FormEvent) => {
try {
await authService.updatePassword(accessToken, newPassword);
toast.success("Senha atualizada com sucesso!");
navigate("/login-paciente");
} catch (error) {
toast.error("Erro ao atualizar senha");
}
};
```
---
## 5. Estrutura do Banco de Dados
### Tabelas Principais
#### `profiles`
```sql
- id (uuid, PK)
- email (text, unique)
- full_name (text)
- phone (text)
- created_at (timestamp)
- updated_at (timestamp)
```
#### `patients`
```sql
- id (uuid, PK, FK -> profiles)
- email (text, unique)
- full_name (text)
- cpf (text, unique)
- phone_mobile (text)
- birth_date (date)
- address (text)
- created_at (timestamp)
```
#### `doctors`
```sql
- id (uuid, PK, FK -> profiles)
- email (text, unique)
- full_name (text)
- specialty (text)
- crm (text, unique)
- phone (text)
- created_at (timestamp)
```
#### `appointments`
```sql
- id (uuid, PK)
- patient_id (uuid, FK -> patients)
- doctor_id (uuid, FK -> doctors)
- scheduled_at (timestamp)
- status (enum: requested, confirmed, completed, cancelled)
- reason (text)
- notes (text)
- created_at (timestamp)
```
#### `user_roles`
```sql
- user_id (uuid, FK -> profiles)
- role (text: admin, gestor, medico, secretaria, paciente)
- created_at (timestamp)
```
---
## 6. Armazenamento de Tokens
O sistema usa uma estratégia híbrida para máxima segurança:
| Tipo | Local | Expiração Natural |
| ------------- | ------------ | --------------------------------- |
| Access Token | localStorage | 1 hora (renovado automaticamente) |
| Refresh Token | localStorage | 30 dias (ou revogação backend) |
| User Snapshot | localStorage | Limpo em logout |
**Segurança:**
- Tokens são limpos automaticamente no logout
- Refresh automático quando access_token expira (401)
- Interceptors garantem tokens válidos em todas as requisições
Riscos remanescentes:
- XSS ainda pode ler refresh token dentro da mesma aba.
- Ataques supply-chain podem capturar tokens em runtime.
Mitigações planejadas:
1. CSP + bloqueio de inline script não autorizado.
2. Auditoria de dependências e lockfile imutável.
3. (Opcional) Migrar refresh para cookie httpOnly + rotacionamento curto (exige backend/proxy).
Fallback / Migração:
- Em primeira utilização o `tokenStore` migra chaves legacy (`authToken`, `refreshToken`, `authUser`) e remove do `localStorage`.
Operações:
- `tokenStore.setTokens(access, refresh?)` atualiza memória e session.
- `tokenStore.clear()` remove tudo (usado em logout e erro crítico de refresh).
Fluxo de Refresh:
1. Requisição falha com 401.
2. Wrapper (`http.ts`) obtém refresh do `tokenStore`.
---
## 7. Scripts Utilitários
### Gerenciamento de Usuários
```bash
# Listar todos os usuários
node scripts/manage-users.js list
# Criar usuário
node scripts/manage-users.js create email@example.com "Nome Completo"
# Deletar usuário
node scripts/manage-users.js delete user-id
# Limpar usuários de teste
node scripts/cleanup-users.js
```
### Testes de API
```bash
# Testar recuperação de senha
node test-password-recovery.js
# Criar usuário Fernando (exemplo)
node create-fernando.cjs
# Buscar usuário Fernando
node search-fernando.cjs
```
---
## 8. Padrões de Código
### Nomenclatura
- **Componentes:** PascalCase (`LoginPaciente.tsx`)
- **Serviços:** camelCase (`authService.ts`)
- **Hooks:** camelCase com prefixo `use` (`useAuth.ts`)
- **Tipos:** PascalCase (`LoginInput`, `AuthUser`)
### Estrutura de Componentes
```typescript
// Imports
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { serviceImport } from "../services";
// Types
interface Props {
// ...
}
// Component
const ComponentName: React.FC<Props> = ({ ...props }) => {
// Hooks
const navigate = useNavigate();
const [state, setState] = useState();
// Effects
useEffect(() => {
// ...
}, []);
// Handlers
const handleAction = async () => {
// ...
};
// Render
return <div>{/* JSX */}</div>;
};
export default ComponentName;
```
---
## 9. Tecnologias Utilizadas
### Frontend
- **React** 18.3.1 - Biblioteca UI
- **TypeScript** 5.9.3 - Tipagem estática
- **Vite** 7.1.10 - Build tool
- **React Router** 6.30.1 - Roteamento
- **Tailwind CSS** 3.4.17 - Estilização
- **Axios** 1.12.2 - Cliente HTTP
- **React Hot Toast** 2.4.1 - Notificações
- **date-fns** 4.1.0 - Manipulação de datas
### Backend
- **Supabase** - Backend as a Service
- **PostgreSQL** - Banco de dados relacional
- **Supabase Auth** - Autenticação JWT
### Deploy
- **Cloudflare Pages** - Hospedagem frontend
- **Wrangler** 4.44.0 - CLI Cloudflare
---
## 10. Suporte e Contato
- **Equipe:** Squad 18 - Rise Up
- **Repositório:** https://git.popcode.com.br/RiseUP/riseup-squad18.git
- **Trello:** [Squad 18 - Idealização/Planejamento](https://trello.com/b/CCl3Azxk/squad-18-idealizacao-planejamento)
---
**Desenvolvido com ❤️ pela Squad 18**