295 lines
6.5 KiB
Markdown
295 lines
6.5 KiB
Markdown
# Sistema de Agendamento com API de Slots
|
|
|
|
## Implementação Concluída ✅
|
|
|
|
### Fluxo de Agendamento
|
|
|
|
1. **Usuário seleciona médico** → Mostra calendário
|
|
2. **Usuário seleciona data** → Chama API de slots disponíveis
|
|
3. **API calcula horários** → Considera:
|
|
- Disponibilidade do médico (agenda configurada)
|
|
- Exceções (bloqueios e horários extras)
|
|
- Antecedência mínima para agendamento
|
|
- Consultas já agendadas
|
|
4. **Usuário seleciona horário** e preenche motivo
|
|
5. **Sistema cria agendamento** → Salva no banco
|
|
|
|
---
|
|
|
|
## APIs Implementadas
|
|
|
|
### 1. Calcular Slots Disponíveis
|
|
|
|
**Endpoint**: `POST /functions/v1/get-available-slots`
|
|
|
|
**Request**:
|
|
|
|
```json
|
|
{
|
|
"doctor_id": "uuid-do-medico",
|
|
"date": "2025-10-30"
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
|
|
```json
|
|
{
|
|
"slots": [
|
|
{
|
|
"time": "09:00",
|
|
"available": true
|
|
},
|
|
{
|
|
"time": "09:30",
|
|
"available": false
|
|
},
|
|
{
|
|
"time": "10:00",
|
|
"available": true
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Código Implementado**:
|
|
|
|
```typescript
|
|
// src/services/appointments/appointmentService.ts
|
|
async getAvailableSlots(data: GetAvailableSlotsInput): Promise<GetAvailableSlotsResponse> {
|
|
const response = await apiClient.post<GetAvailableSlotsResponse>(
|
|
"/functions/v1/get-available-slots",
|
|
data
|
|
);
|
|
return response.data;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Criar Agendamento
|
|
|
|
**Endpoint**: `POST /rest/v1/appointments`
|
|
|
|
**Request**:
|
|
|
|
```json
|
|
{
|
|
"doctor_id": "uuid-do-medico",
|
|
"patient_id": "uuid-do-paciente",
|
|
"scheduled_at": "2025-10-30T09:00:00Z",
|
|
"duration_minutes": 30,
|
|
"appointment_type": "presencial",
|
|
"chief_complaint": "Consulta de rotina",
|
|
"created_by": "uuid-do-usuario"
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
|
|
```json
|
|
{
|
|
"id": "uuid-do-agendamento",
|
|
"order_number": "APT-2025-0001",
|
|
"status": "requested",
|
|
...
|
|
}
|
|
```
|
|
|
|
**Código Implementado**:
|
|
|
|
```typescript
|
|
// src/services/appointments/appointmentService.ts
|
|
async create(data: CreateAppointmentInput): Promise<Appointment> {
|
|
const payload = {
|
|
...data,
|
|
duration_minutes: data.duration_minutes || 30,
|
|
appointment_type: data.appointment_type || "presencial",
|
|
status: "requested",
|
|
};
|
|
|
|
const response = await apiClient.post<Appointment[]>(
|
|
"/rest/v1/appointments",
|
|
payload,
|
|
{
|
|
headers: {
|
|
Prefer: "return=representation",
|
|
},
|
|
}
|
|
);
|
|
|
|
return response.data[0];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Componente AgendamentoConsulta
|
|
|
|
### Principais Melhorias
|
|
|
|
#### Antes ❌
|
|
|
|
- Calculava slots manualmente no frontend
|
|
- Precisava carregar disponibilidade + exceções separadamente
|
|
- Lógica complexa de validação no cliente
|
|
- Não considerava antecedência mínima
|
|
- Não verificava consultas já agendadas
|
|
|
|
#### Depois ✅
|
|
|
|
- Usa Edge Function para calcular slots
|
|
- API retorna apenas horários realmente disponíveis
|
|
- Validações centralizadas no backend
|
|
- Considera todas as regras de negócio
|
|
- Performance melhorada (menos requisições)
|
|
|
|
### Código Simplificado
|
|
|
|
```typescript
|
|
// src/components/AgendamentoConsulta.tsx
|
|
|
|
const calculateAvailableSlots = useCallback(async () => {
|
|
if (!selectedDate || !selectedMedico) {
|
|
setAvailableSlots([]);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const dateStr = format(selectedDate, "yyyy-MM-dd");
|
|
|
|
// Chama a Edge Function
|
|
const response = await appointmentService.getAvailableSlots({
|
|
doctor_id: selectedMedico.id,
|
|
date: dateStr,
|
|
});
|
|
|
|
if (response && response.slots) {
|
|
// Filtra apenas slots disponíveis
|
|
const available = response.slots
|
|
.filter((slot) => slot.available)
|
|
.map((slot) => slot.time);
|
|
setAvailableSlots(available);
|
|
} else {
|
|
setAvailableSlots([]);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar slots:", error);
|
|
setAvailableSlots([]);
|
|
}
|
|
}, [selectedDate, selectedMedico]);
|
|
|
|
const confirmAppointment = async () => {
|
|
if (!selectedMedico || !selectedDate || !selectedTime || !user) return;
|
|
|
|
try {
|
|
const scheduledAt =
|
|
format(selectedDate, "yyyy-MM-dd") + "T" + selectedTime + ":00Z";
|
|
|
|
// Cria o agendamento
|
|
const appointment = await appointmentService.create({
|
|
patient_id: user.id,
|
|
doctor_id: selectedMedico.id,
|
|
scheduled_at: scheduledAt,
|
|
duration_minutes: 30,
|
|
appointment_type:
|
|
appointmentType === "online" ? "telemedicina" : "presencial",
|
|
chief_complaint: motivo,
|
|
});
|
|
|
|
console.log("Consulta criada:", appointment);
|
|
setBookingSuccess(true);
|
|
} catch (error) {
|
|
setBookingError(error.message);
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Tipos TypeScript
|
|
|
|
```typescript
|
|
// src/services/appointments/types.ts
|
|
|
|
export interface GetAvailableSlotsInput {
|
|
doctor_id: string;
|
|
date: string; // YYYY-MM-DD
|
|
}
|
|
|
|
export interface TimeSlot {
|
|
time: string; // HH:MM (ex: "09:00")
|
|
available: boolean;
|
|
}
|
|
|
|
export interface GetAvailableSlotsResponse {
|
|
slots: TimeSlot[];
|
|
}
|
|
|
|
export interface CreateAppointmentInput {
|
|
patient_id: string;
|
|
doctor_id: string;
|
|
scheduled_at: string; // ISO 8601
|
|
duration_minutes?: number;
|
|
appointment_type?: "presencial" | "telemedicina";
|
|
chief_complaint?: string;
|
|
patient_notes?: string;
|
|
insurance_provider?: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Benefícios da Implementação
|
|
|
|
✅ **Performance**: Menos requisições ao backend
|
|
✅ **Confiabilidade**: Validações centralizadas
|
|
✅ **Manutenibilidade**: Lógica de negócio no servidor
|
|
✅ **Escalabilidade**: Edge Functions são otimizadas
|
|
✅ **UX**: Interface mais responsiva e clara
|
|
✅ **Segurança**: Validações no backend não podem ser burladas
|
|
|
|
---
|
|
|
|
## Próximos Passos (Opcional)
|
|
|
|
- [ ] Adicionar loading states mais detalhados
|
|
- [ ] Implementar cache de slots (evitar chamadas repetidas)
|
|
- [ ] Adicionar retry automático em caso de falha
|
|
- [ ] Mostrar motivo quando slot não está disponível
|
|
- [ ] Implementar notificações (SMS/Email) após agendamento
|
|
|
|
---
|
|
|
|
## Testando
|
|
|
|
### 1. Selecione um médico
|
|
|
|
### 2. Selecione uma data futura
|
|
|
|
### 3. Verifique os slots disponíveis
|
|
|
|
### 4. Selecione um horário
|
|
|
|
### 5. Preencha o motivo
|
|
|
|
### 6. Confirme o agendamento
|
|
|
|
**Logs no Console**:
|
|
|
|
```
|
|
[AppointmentService] Buscando slots para: {doctor_id, date}
|
|
[AppointmentService] Slots recebidos: 12 slots
|
|
[AppointmentService] Criando agendamento...
|
|
[AppointmentService] Consulta criada: {id, order_number, ...}
|
|
```
|
|
|
|
---
|
|
|
|
## Data de Implementação
|
|
|
|
**30 de Outubro de 2025**
|
|
|
|
Implementado por: GitHub Copilot
|
|
Revisado por: Equipe RiseUp Squad 18
|