Agenda, Agendador, Configurações

This commit is contained in:
Leonardo
2026-03-12 08:58:36 -03:00
parent f733db8436
commit f4b185ae17
197 changed files with 33405 additions and 6507 deletions

232
TESTES.md Normal file
View File

@@ -0,0 +1,232 @@
# Guia de Testes — AgenciaPsi
## Testes Automatizados
### Pré-requisito
Vitest já instalado (`npm install` resolve). Não precisa de banco, Supabase ou variáveis de ambiente.
### Comandos
| Comando | Descrição |
|---|---|
| `npm test` | Roda todos os testes uma vez e exibe resultado |
| `npm run test:watch` | Modo watch — re-roda ao salvar arquivos |
| `npm run test:ui` | Abre UI visual no browser (`http://localhost:51204`) |
### Arquivos de teste
| Arquivo | O que cobre |
|---|---|
| `src/features/agenda/composables/__tests__/useRecurrence.spec.js` | Geração de datas por tipo de regra, max_occurrences global, exceções, remarcação cross-range |
| `src/features/agenda/services/__tests__/agendaMappers.spec.js` | Mapeamento para FullCalendar, ícones de status, cores, buildNextSessions, minutesToDuration, buildWeeklyBreakBackgroundEvents |
### Quando rodar
- Antes de commitar qualquer mudança em `useRecurrence.js` ou `agendaMappers.js`
- Ao adicionar novo tipo de frequência (mensal, quinzenal, etc.)
- Ao mexer em exceções de recorrência
- Em CI/CD antes do deploy
---
## Testes Manuais
### Preparação
1. Limpar dados de teste no banco:
```sql
TRUNCATE TABLE recurrence_exceptions CASCADE;
TRUNCATE TABLE recurrence_rules CASCADE;
TRUNCATE TABLE agenda_eventos CASCADE;
TRUNCATE TABLE agendador_solicitacoes CASCADE;
```
2. Fazer login com seu usuário real
3. Selecionar a clínica/tenant correto
---
### 1. Evento Avulso
| Passo | Esperado |
|---|---|
| Clicar em um horário livre na agenda | Dialog de criação abre |
| Preencher paciente, horário, modalidade → Salvar | Evento aparece no calendário |
| Clicar no evento → Editar horário → Salvar | Horário atualiza |
| Clicar no evento → Marcar como "Faltou" | Cor muda para vermelho, ícone ✗ |
| Clicar no evento → Marcar como "Realizado" | Cor muda para cinza, ícone ✓ |
| Clicar no evento → Cancelar sessão | Cor muda para laranja, ícone ∅ |
| Clicar no evento → Excluir | Evento some do calendário |
---
### 2. Recorrência Semanal
| Passo | Esperado |
|---|---|
| Criar evento com frequência "Semanal" | Ocorrências aparecem em todas as semanas seguintes com ícone ↻ |
| Navegar para a semana seguinte | Ocorrências continuam aparecendo |
| Navegar para além do end_date | Não aparecem ocorrências após a data final |
| Criar série com "4 sessões" (max_occurrences) | Exatamente 4 ocorrências visíveis no calendário |
---
### 3. Recorrência Quinzenal e Dias Específicos
| Passo | Esperado |
|---|---|
| Criar série "Quinzenal" | Ocorrências aparecem a cada 2 semanas |
| Criar série "Dias específicos" (ex: seg + qua) | Ambos os dias aparecem toda semana |
| Navegar para semanas futuras | Padrão se mantém |
---
### 4. Edição de Série
| Passo | Esperado |
|---|---|
| Clicar em ocorrência → Editar → "Somente este" → mudar horário | Só aquela data muda; as outras continuam iguais |
| Clicar em ocorrência → Cancelar → "Somente este" | Só aquela data some (ou aparece cancelada) |
| Clicar em ocorrência → Cancelar → "Este e os seguintes" | A partir daquela data, sem mais ocorrências |
| Clicar em ocorrência → Cancelar → "Todos" | Série inteira some |
---
### 5. Remarcação Cross-Range ⭐
Este é o caso mais importante a testar.
| Passo | Esperado |
|---|---|
| Criar série semanal (ex: toda segunda) | Ocorrências nas segundas |
| Clicar na sessão da **semana 1** → Remarcar para **terça da semana 2** | — |
| Navegar para a **semana 1** | Segunda da semana 1 aparece vazia ou como "remarcado" |
| Navegar para a **semana 2** | Terça aparece com ícone ↺ e status "remarcado" |
---
### 6. Bloqueio de Agenda
| Passo | Esperado |
|---|---|
| Criar bloqueio de horário | Aparece no calendário com visual diferente (ícone ⊘) |
| Tentar agendar no horário bloqueado | Aviso de conflito |
---
### 7. Agendamento Online (Agendador Público)
| Passo | Esperado |
|---|---|
| Acessar URL pública do agendador | Página pública abre sem login |
| Selecionar data/horário disponível → Enviar solicitação | Confirmação exibida |
| No painel do terapeuta → "Agendamentos Recebidos" | Solicitação aparece na lista |
| Clicar em "Confirmar" | Evento criado na agenda |
| Clicar em "Recusar" | Solicitação removida, sem evento na agenda |
---
### 8. Suporte Técnico SaaS
| Passo | Esperado |
|---|---|
| Logar como `saas_admin` → Menu "Suporte Técnico" | Página de suporte abre |
| Selecionar um tenant → "Criar Sessão de Suporte" | URL com token é gerada |
| Copiar URL e abrir em outra aba | Agenda do terapeuta abre com banner de debug no rodapé |
| No banner → filtrar logs por categoria | Logs filtram corretamente |
| No banner → "Desativar suporte" | Banner some |
| No painel SaaS → "Revogar" na sessão ativa | Token invalidado |
---
### 9. Multi-Tenancy (se você tem 2 clínicas cadastradas)
| Passo | Esperado |
|---|---|
| Criar evento na clínica A | Evento aparece na agenda da clínica A |
| Trocar para clínica B | Evento da clínica A **não aparece** |
| Criar evento na clínica B | Aparece apenas na clínica B |
---
## Pedindo ao Claude para Executar os Testes
### Como usar o Claude Code para rodar e corrigir testes
O Claude Code (este agente) consegue rodar os testes, ler os erros e corrigir os problemas automaticamente. Basta iniciar a conversa com o contexto certo.
### Prompt de retomada recomendado
Cole isso no início de uma nova sessão com o Claude:
---
> Estou desenvolvendo o AgenciaPsi. Temos testes automatizados com Vitest.
>
> **Arquivos de teste:**
> - `src/features/agenda/composables/__tests__/useRecurrence.spec.js` — testa `generateDates`, `expandRules`, `mergeWithStoredSessions`
> - `src/features/agenda/services/__tests__/agendaMappers.spec.js` — testa mapeamento para FullCalendar
>
> **Rodar os testes:** `npm test`
>
> Por favor, rode os testes agora e me informe o resultado. Se houver falhas, analise a causa e corrija.
---
### O que o Claude consegue fazer automaticamente
| Pedido | O Claude faz |
|---|---|
| "Rode os testes" | Executa `npm test` e exibe o resultado |
| "Tem algum teste falhando?" | Roda e diagnóstica a causa raiz |
| "Corrija os testes que falham" | Analisa erro, ajusta o código ou o teste e re-roda |
| "Adicionei a funcionalidade X, crie testes para ela" | Lê o código e escreve novos casos no spec |
| "O teste Y está errado, o comportamento correto é Z" | Atualiza a asserção e confirma que passa |
### Boas práticas ao pedir testes ao Claude
- **Forneça o `AUDITORIA.md`** no início da sessão — dá contexto sobre a arquitetura e decisões já tomadas
- **Descreva o comportamento esperado** em português, não o código — o Claude escreve o código do teste
- **Se um teste falhar e você achar que o código está certo**, diga isso explicitamente: *"o teste está errado, não o código"* — o Claude vai ajustar a asserção
- **Se um teste falhar e você achar que o código está errado**, diga: *"o comportamento esperado é X"* — o Claude vai corrigir a implementação
### Exemplo de sessão típica
```
Você: Rodei npm test e 2 testes falharam. Analise e corrija.
Claude: [roda npm test, lê os erros, corrige o código ou as asserções, re-roda até 63/63 passarem]
```
---
## Adicionando Novos Testes
### Para `useRecurrence.spec.js`
```js
import { generateDates, expandRules, mergeWithStoredSessions } from '../useRecurrence.js'
it('meu novo caso', () => {
const r = {
id: 'rule-1', type: 'weekly', weekdays: [1], interval: 1,
start_date: '2026-03-02', end_date: null, max_occurrences: null,
status: 'ativo', start_time: '09:00', end_time: '10:00',
// ... outros campos necessários
}
const dates = generateDates(r, new Date(2026, 2, 1), new Date(2026, 2, 31))
expect(dates.length).toBe(/* esperado */)
})
```
### Para `agendaMappers.spec.js`
```js
import { mapAgendaEventosToCalendarEvents } from '../agendaMappers.js'
it('meu novo caso de mapeamento', () => {
const [ev] = mapAgendaEventosToCalendarEvents([{
id: 'ev-1', titulo: 'Teste', tipo: 'sessao', status: 'agendado',
inicio_em: '2026-03-10T09:00:00', fim_em: '2026-03-10T10:00:00',
owner_id: 'owner-1',
}])
expect(ev.extendedProps./* campo */).toBe(/* esperado */)
})
```