riseup-squad18/AGENDAMENTO-SLOTS-API.md
2025-10-30 17:20:28 -03:00

6.5 KiB

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:

{
  "doctor_id": "uuid-do-medico",
  "date": "2025-10-30"
}

Response:

{
  "slots": [
    {
      "time": "09:00",
      "available": true
    },
    {
      "time": "09:30",
      "available": false
    },
    {
      "time": "10:00",
      "available": true
    }
  ]
}

Código Implementado:

// 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:

{
  "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:

{
  "id": "uuid-do-agendamento",
  "order_number": "APT-2025-0001",
  "status": "requested",
  ...
}

Código Implementado:

// 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

// 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

// 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