Skip to content

Exemplo Prático: Jornada do Paciente — Solicitação de Atendimento Online

Este guia descreve, passo a passo, a jornada completa de um paciente solicitando um atendimento online. O fluxo cobre da autenticação à entrada na sala de atendimento, incluindo questionário clínico, upload de exames, gestão de fila com atualização em tempo real e a sessão com chat e vídeo.


Visão Geral do Fluxo

text
[Login] -> [Home do Paciente] -> [Solicitar Atendimento]
        -> [Questionário + Upload de Exames]
        -> [Fila de Espera (posição dinâmica)]
        -> [Match com Médico]
        -> [Sala de Atendimento: Chat + Video]

Cada etapa é suportada por camadas específicas da arquitetura:

  • Apps: Roteamento, RBAC e composição de domínios
  • Domains: Consultation, Users, Chat, Medical-Record (quando aplicável)
  • Core: WebSocket, API, Domain Events, VideoCall Adapter
  • Shared: UI, Hooks, Utils

Passo 1: Autenticação (RBAC)

  1. Ação: o paciente realiza login.
  2. Camada: Apps/AppPatient + Shared/Auth.
  3. Mecanismo: Rotas do paciente protegidas com RequireRoleLayout role="PATIENT" e lazy-loading das páginas.
tsx
// Roteamento do App do Paciente
<Route element={<RequireRoleLayout role="PATIENT" />}> 
  <Route path="/patient" element={<PatientHome />} />
  <Route path="/patient/request" element={<RequestCareRoute />} />
  <Route path="/patient/queue" element={<QueueRoute />} />
  <Route path="/patient/room/:roomId" element={<PatientRoomRoute />} />
</Route>

Passo 2: Página Principal (Home do Paciente)

  1. Ação: visualizar opções e iniciar “Solicitar Atendimento”.
  2. Camada: Apps/AppPatient (UI orquestradora) + Domains/Consultation (quando a jornada iniciar).
  3. Decisão: Apenas após o usuário acionar a jornada, os recursos do domínio são injetados.
tsx
// Ao entrar na rota de solicitação, injetamos o domínio necessário
const RequestCareRoute = () => {
  useInjectReducer({ key: 'consultation', reducer: consultationReducer });
  useInjectSaga({ key: 'consultation', saga: consultationSaga });
  return <RequestCarePage />;
};

Passo 3: Solicitar Atendimento (Questionário Clínico)

  1. Ação: o paciente preenche um questionário (sintomas, duração, alergias) e confirma a solicitação.
  2. Camada: Domains/Consultation/UI + Store.
  3. Mecanismo: Saga com guards valida campos obrigatórios e idempotência no envio.
typescript
// packages/domains/consultation/store/sagas.ts (trecho)
import { call, put, select, takeLatest } from 'redux-saga/effects';
import { actions as consultationActions } from './slice';
import { selectIsSubmittingRequest } from './selectors';
import { consultationApi } from '../api';

function* handleRequestOnlineCare(action: ReturnType<typeof consultationActions.requestCare>) {
  // Guard de Reentrada (impede múltiplos envios)
  const inFlight = yield select(selectIsSubmittingRequest);
  if (inFlight) return;

  // Guard de Dados Clínicos mínimos
  const { symptoms, duration } = action.payload;
  if (!symptoms || !duration) {
    yield put(consultationActions.formError('Sintomas e duração são obrigatórios.'));
    return;
  }

  try {
    yield put(consultationActions.setSubmitting(true));
    const ticket = yield call(consultationApi.requestCare, action.payload);
    yield put(consultationActions.requestCareSuccess(ticket));
  } catch (e: any) {
    yield put(consultationActions.requestCareFailure(e.message));
  } finally {
    yield put(consultationActions.setSubmitting(false));
  }
}

export function* consultationSaga() {
  yield takeLatest(consultationActions.requestCare.type, handleRequestOnlineCare);
}

Passo 4: Upload de Arquivos de Exames (Pré-Atendimento)

  1. Ação: anexar imagens/PDFs de exames.
  2. Camada: Domains/Consultation (ou Domains/Medical-Record, dependendo da divisão de responsabilidade).
  3. Mecanismo: Saga com validações de tamanho e tipo; upload com feedback de progresso.
typescript
// Guard de upload
function* handleUploadExam(action: ReturnType<typeof consultationActions.uploadExam>) {
  const { file } = action.payload;
  if (file.size > 20 * 1024 * 1024) {
    yield put(consultationActions.formError('Arquivo acima de 20MB.')); return;
  }
  if (!/(pdf|png|jpg|jpeg)$/i.test(file.type)) {
    yield put(consultationActions.formError('Formato inválido.')); return;
  }
  // Chamada para API de upload
  yield call(consultationApi.uploadExam, file);
  yield put(consultationActions.uploadExamSuccess());
}

UI recomendada (Shared Design System):

  • Componente FileDropzone com estados: vazio, arrastando, carregando e concluído.
  • Preview e opção de remover arquivo antes de enviar.

Passo 5: Entrada na Fila (XState — queueMachine)

  1. Ação: ao concluir o questionário e anexos, o paciente é colocado na fila virtual.
  2. Camada: Domains/Consultation/Machines.
  3. Mecanismo: Máquina de estados gerencia o status da fila e expõe posição atual via contexto.
typescript
// packages/domains/consultation/machines/queueMachine.ts
import { createMachine, assign } from 'xstate';

type Ctx = { ticketId: string; position: number; estimatedWaitMin: number };

export const queueMachine = createMachine<Ctx>({
  id: 'queue',
  initial: 'emFila',
  context: { ticketId: '', position: 0, estimatedWaitMin: 0 },
  states: {
    emFila: {
      entry: 'subscribeQueueUpdates',
      on: {
        QUEUE_UPDATED: {
          actions: assign({ position: (_ctx, e: any) => e.position, estimatedWaitMin: (_ctx, e: any) => e.eta })
        },
        MATCH_FOUND: { target: 'pareado' }
      },
      exit: 'unsubscribeQueueUpdates'
    },
    pareado: { type: 'final' }
  }
});

Atualização de posição em tempo real:

  • Assinatura via Core/WebSocket ou via Domain Events retransmitidos ao front-end.
  • A UI do paciente exibe “Você é o 3º na fila (≈ 6 min)”, atualizando a cada evento QUEUE_UPDATED.

Passo 6: Match e Redirecionamento Automático

  1. Ação: quando um médico fica disponível, o backend emite CONSULTATION_MATCHED.
  2. Camada: Core/DomainEvents + Domains/Consultation/Store.
  3. Mecanismo: Saga escuta eventos do barramento e redireciona para a sala virtual.
typescript
// Redirecionamento ao receber MATCH
import { take, put } from 'redux-saga/effects';
import { domainEventsBus } from 'core/domain-events';
import { DomainEventNames } from 'contracts/events';
import { push } from 'core/router';

export function* watchMatchEvent() {
  const channel = domainEventsBus.createChannel();
  while (true) {
    const event = yield take(channel);
    if (event.name === DomainEventNames.CONSULTATION_MATCHED) {
      yield put(push(`/patient/room/${event.payload.roomId}`));
    }
  }
}

Passo 7: Sala de Atendimento (Estados Paralelos: Vídeo + Chat)

  1. Ação: o paciente entra na sala e interage com o médico.
  2. Camada: Domains/Consultation/Machines (sessionMachine) + Domains/Chat + Core/VideoCall.
  3. Mecanismo: Máquina paralela orquestra vídeo e chat simultaneamente; guards asseguram consentimento antes de ligar a câmera.
typescript
// sessionMachine (trecho) com guard de consentimento
const sessionMachine = createMachine({
  id: 'session',
  type: 'parallel',
  context: { isTermOfUseAccepted: false },
  states: {
    video: {
      initial: 'aguardandoTermo',
      states: {
        aguardandoTermo: {
          on: { ACCEPT_TERMS: { target: 'conectando', actions: assign({ isTermOfUseAccepted: (_c) => true }) } }
        },
        conectando: {
          on: { START_VIDEO: { target: 'emChamada', cond: (ctx) => ctx.isTermOfUseAccepted } }
        },
        emChamada: { /* ... */ }
      }
    },
    chat: { /* sincronização e envio de mensagens */ }
  }
});

UI recomendada para a Sala:

  • Layout responsivo com vídeo principal e painel lateral de chat.
  • Indicadores de estado (conectando, reconectando, mutado).
  • Botão de “Aceitar Termos” bloqueando START_VIDEO até aceite (via state.can).

Diagrama ASCII do Fluxo Completo

text
LOGIN -> HOME(PATIENT) --clicar--> SOLICITAR ATENDIMENTO
  -> QUESTIONÁRIO + UPLOAD
    -> [Saga valida + envia]
      -> ENTRA NA FILA (queueMachine)
        -> [QUEUE_UPDATED]* (posição: n..3..2..1)
        -> MATCH_FOUND
          -> REDIRECT /patient/room/:roomId
            -> SESSÃO (XState paralela)
               ├─ Vídeo (guard: termo aceito) -> conectando -> em chamada
               └─ Chat  (ws) -> sincronizando -> pronto

Integração com a Arquitetura

  • Injeção Dinâmica: apenas ao iniciar a jornada do atendimento, o estado e sagas de consultation são carregados.
  • Event-Driven: o match é comunicado por CONSULTATION_MATCHED; o domínio de Chat reage independentemente para iniciar a sala.
  • RBAC: rotas do paciente são protegidas, evitando acesso não autorizado a outras jornadas.
  • Observabilidade: transições das máquinas podem ser logadas (ver página "Lifecycle e Observabilidade").

Considerações de Segurança e Conformidade

  • Consentimento: a câmera só ativa após aceite explícito do termo (guard na state machine).
  • Dados Sensíveis: uploads limitados por tipo e tamanho; metadados clínicos tratados via contracts para tipagem consistente.
  • Resiliência: fila e sessão toleram reconexões; chat permanece funcional mesmo durante instabilidades de vídeo.