WEBSOCKETappointments.serving

Agendamento Em Atendimento

Evento WebSocket para notificar em tempo real quando um agendamento entra em atendimento. Este evento é emitido automaticamente pelo servidor quando um agendamento muda de status para EM_ATENDIMENTO, propagando a notificação para todos os clientes conectados da mesma conta. O servidor faz broadcast do evento para todos os clientes da mesma accountId, permitindo que todos removam o agendamento da fila de espera simultaneamente. Este evento não é iniciado pelo frontend; é um evento unidirecional (servidor → clientes) usado exclusivamente para sincronização de UI em tempo real. Antes de emitir o evento, o servidor atualiza o médico como ocupado (isBusy: true) e garante o vínculo do appointment com o médico dentro de uma transação atômica.

Access Token

Para realizar requisições GET, POST, PUT, DELETE e PATCH nos endpoints da API você precisa de uma chave de autorização. Chamamos essa chave de accessToken.

Para ter acesso ao accessToken, é necessário que o usuário master da licença efetue a liberação deste pela interface do ImageMais Clinic. O accessToken tem validade de 1 hora.

Headers

AuthorizationOBRIGATÓRIO
Tipo:string
Token de autenticação no formato Bearer {accessToken}. O token deve ser enviado durante o handshake de conexão WebSocket.

Parâmetros

Este método não possui parâmetros.

Fluxo passo a passo: appointments.serving

1

Listener escuta mudança de status para EM_ATENDIMENTO

O InServiceAppointmentListener escuta o evento interno 'appointments.status.change' e processa quando o status muda para EM_ATENDIMENTO:

// in-service-appointment.ts
@OnEvent('appointments.status.change')
async handleAppointmentsStatusChange(event: AppointmentStatusChangeEvent) {
  // Só processa se action === EM_ATENDIMENTO
  if (event.action !== AppointmentStatusEnum.EM_ATENDIMENTO) return;

  try {
    // Processa atualizações em transação atômica
    // ... continua no próximo passo
  } catch (error) {
    // Trata erros
  }
}
Quem escuta: InServiceAppointmentListener
Evento interno: 'appointments.status.change' (EventEmitter)
Validação: só processa se action === EM_ATENDIMENTO
Quando é chamado: quando status muda para 'Em atendimento' (geralmente quando médico inicia atendimento)
2

Listener atualiza médico e appointment em transação

O listener atualiza o médico como ocupado e garante o vínculo do appointment com o médico dentro de uma transação atômica:

// in-service-appointment.ts - Continuação
try {
  await this.prisma.$transaction(async (tx) => {
    // Marca médico como ocupado
    const doctor = await tx.doctors.update({
      where: {
        id: event.doctorId ?? event.appointment.doctorId,
      },
      data: { isBusy: true },
    });

    // Garante vínculo do appointment com o médico
    await tx.appointments.update({
      where: {
        id: event.appointment.id,
      },
      data: {
        doctorId: doctor.id,
      },
    });
  });

  // Emite evento interno para propagação via WebSocket
  this.eventEmitter.emit('appointments.serving', {
    appointment: event.appointment,
    payload: event.payload,
    doctorId: event.doctorId ?? event.appointment.doctorId,
  });
} catch (error) {
  // Trata erros
}
Transação atômica: médico e appointment atualizados juntos (garantia de consistência)
Atualização 1: doctors.update({ isBusy: true }) marca médico como ocupado
Atualização 2: appointments.update({ doctorId }) garante vínculo do appointment com o médico
Emite evento: eventEmitter.emit('appointments.serving', { appointment, payload, doctorId })
Dados do evento: appointment completo, payload (token), e doctorId
3

Listener propaga evento via WebSocket

O appointments.listener.ts escuta o evento interno e propaga via WebSocket para todos os clientes da conta:

// appointments.listener.ts
@OnEvent('appointments.serving')
async handleAppointmentServing(data: {
  appointment: AppointmentResponseDto;
  payload: PayloadTokenDto;
  doctorId: number;
}) {
  try {
    // Propaga via WebSocket para todos os clientes da conta
    await this.gateway.notifyOnEvent(
      'appointments.serving',
      data.appointment,
      data.payload,
    );
    
    this.logger.debug(
      `Agendamento ${data.appointment.id} propagado via WebSocket para o medico atribuido ${data.doctorId}`,
    );
  } catch (error) {
    this.logger.error(
      `Erro ao propagar evento appointments.serving:`,
      error,
    );
  }
}
O que faz: escuta evento interno e propaga via WebSocket
Broadcast: notifica todos os clientes da mesma conta
Dados enviados: objeto AppointmentResponseDto completo (sem payload e doctorId extras)
Log: registra ID do appointment e do médico
Tratamento de erro: loga erros sem quebrar o fluxo
4

Gateway faz broadcast para todos da conta

O método `notifyOnEvent` envia o evento 'appointments.serving' para todos os sockets da mesma accountId:

// accounts.gateways.ts
async notifyOnEvent(
  event: string,
  data: any,
  payload: PayloadTokenDto,
): Promise<any> {
  if (this._events.includes(event)) {
    // Envia para todos os clientes da mesma conta
    this.server
      .to(`${this.room}${payload.accountId}`)
      .emit(event, data);
  }
}

// this.room = 'orders.account.'
// Todos os clientes conectados da mesma accountId recebem o evento 'appointments.serving'
Evento emitido: 'appointments.serving' (está na lista de eventos permitidos)
Dados enviados: objeto AppointmentResponseDto completo
Quem recebe: todos os clientes conectados da mesma accountId
Sincronização: todos os clientes recebem a notificação simultaneamente
5

Cliente recebe evento e remove da lista

Todos os clientes conectados da mesma conta recebem o evento 'appointments.serving' e removem o agendamento da fila de espera:

// List.tsx
socket.on('appointments.serving', (appointment: Appointment) => {
  // Remove o agendamento da fila de espera
  setListAppointments((prev) =>
    prev.filter((apt) => apt.id !== appointment.id)
  );
});

// Remove listener ao desmontar componente
useEffect(() => {
  return () => {
    socket.off('doctors.busy');
    socket.off('appointments.checkin');
    socket.off('appointments.serving');
    socket.off('appointmnents.finished');
  };
}, []);
Quem recebe: todos os clientes conectados da mesma conta
O que faz: remove agendamento da fila de espera (filtra por ID)
Ação: appointment sai da lista de aguardando atendimento
Sincronização: todos os clientes veem a remoção simultaneamente
Atualização de UI: fila é atualizada automaticamente em tempo real
Cleanup: remove listener ao desmontar para prevenir memory leaks

Request URL

ws://api-dev.imagemais.com
{
  "message": "Este evento é emitido automaticamente pelo servidor (não requer request body)"
}
{
  "message": "Este evento é emitido automaticamente pelo servidor (não requer request body)"
}

Respostas

{
  "id": 123,
  "date": "2025-01-23T00:00:00.000Z",
  ...
{
  "id": 123,
  "date": "2025-01-23T00:00:00.000Z",
  "startTime": "09:00",
  "endTime": "09:15",
  "notes": "Observações sobre o agendamento",
  "accountId": 1,
  "createdAt": "2025-01-22T10:00:00.000Z",
  "updatedAt": "2025-01-23T09:30:00.000Z",
  "deletedAt": null,
  "doctor": {
    "id": 1,
    "name": "Dr. Victor"
  },
  "patient": {
    "id": 456,
    "name": "João Silva",
    "contact": {
      "phone": "(81) 99999-9999"
    }
  },
  "status": {
    "id": 3,
    "name": "Em atendimento",
    "color": "#007bff"
  },
  "appointmentProcedures": {
    "procedure": {
      "id": 3699,
      "name": "Ultrassom: Tireoide",
      "price": "100"
    }
  }
}