Skip to content

State Machines (XState) em Fluxos Médicos

Para fluxos de negócio que possuem múltiplos estados, transições complexas e regras de decisão baseadas em eventos, utilizamos o XState para criar Máquinas de Estado. No contexto de saúde, a precisão do fluxo é uma questão de segurança do paciente.

A Necessidade de Máquinas de Estado na Saúde

Enquanto o Redux gerencia os dados (o que é o prontuário), o XState gerencia o comportamento (o que pode ser feito com o prontuário agora).

Por que XState e não apenas Booleanos?

Em um atendimento de telemedicina, usar múltiplos if (isLoading && isStarted && !isFinished) leva ao "Código Espaguete" e a estados impossíveis. O XState resolve isso ao:

  • Prevenir Erros de Fluxo: Um médico não pode "Finalizar" uma consulta que ainda não foi "Iniciada".
  • Garantir Protocolos: Podemos forçar que um "Termo de Consentimento" seja assinado antes que a câmera seja ligada.
  • Visualização Clínica: O fluxo definido no código é exatamente o mesmo que o protocolo médico desenhado pelos analistas de negócio.

Caso de Uso: Ciclo de Vida da Teleconsulta

O módulo de consultas (packages/modules/consultation) utiliza o XState para gerenciar o progresso rigoroso de um atendimento.

Diagrama de Estados da Consulta

text
[*] -> AGENDADA
AGENDADA -> SALA_DE_ESPERA : PACIENTE_CONECTOU
SALA_DE_ESPERA -> EM_ATENDIMENTO : MEDICO_INICIOU
EM_ATENDIMENTO -> FINALIZADA : MEDICO_CONCLUIU [TERMO_ASSINADO]
EM_ATENDIMENTO -> CANCELADA : ERRO_TECNICO_GRAVE
AGENDADA -> CANCELADA : PACIENTE_DESISTIU

Exemplo de Protocolo: Termo de Consentimento

Um dos usos mais poderosos de Guards em máquinas de estado na saúde é a imposição de protocolos legais. Por exemplo, a câmera do médico ou do paciente não deve ser ativada antes que o Termo de Consentimento de Teleconsulta seja assinado eletronicamente.

Implementação do Guard de Protocolo

Abaixo, detalhamos como a máquina de estado bloqueia a transição para o estado de vídeo se o contrato (contexto) não indicar que o termo foi aceito.

typescript
// packages/modules/consultation/machines/sessionMachine.ts

const teleconsultationMachine = createMachine({
  id: 'teleconsultation',
  initial: 'aguardandoTermo',
  context: {
    isTermOfUseAccepted: false, // Inicia como falso
  },
  states: {
    aguardandoTermo: {
      on: {
        // Evento disparado quando o usuário clica em "Aceito os Termos"
        ACCEPT_TERMS: {
          actions: assign({ isTermOfUseAccepted: true }),
          target: 'preparandoEquipamento'
        }
      }
    },
    preparandoEquipamento: {
      initial: 'verificandoCamera',
      states: {
        verificandoCamera: {
          on: {
            START_VIDEO: {
              target: 'cameraAtiva',
              // GUARD CRÍTICO: Impede a ativação se o termo não estiver assinado
              cond: 'hasAcceptedTerms'
            }
          }
        },
        cameraAtiva: {
          // Lógica de stream de vídeo
        }
      }
    }
  }
}, {
  guards: {
    // Implementação da lógica do Guard
    hasAcceptedTerms: (context) => context.isTermOfUseAccepted === true
  }
});

Por que esta abordagem é superior à lógica tradicional?

  1. Impossibilidade de Bypass: Como a transição START_VIDEO exige o guard hasAcceptedTerms, não existe nenhum caminho no grafo da aplicação que permita chegar ao estado cameraAtiva sem passar pelo evento ACCEPT_TERMS.
  2. Auditabilidade: O estado da máquina pode ser persistido, servindo como prova técnica de que o software seguiu o protocolo legal antes de abrir o stream de vídeo.
  3. Interface Reativa: A UI pode usar o seletor state.can('START_VIDEO') para desabilitar visualmente o botão de câmera, fornecendo feedback instantâneo ao usuário sobre a pendência do termo.

Máquinas de Estado Paralelas: O Desafio da Telemedicina

Em uma teleconsulta, múltiplos sistemas precisam operar de forma independente, mas coordenada. O médico precisa enviar mensagens no chat enquanto o vídeo está em fase de reconexão. O XState gerencia isso através de Estados Paralelos (Orthogonal States).

Caso de Uso: Sessão Ativa (Vídeo + Chat + Prontuário)

Durante o estado EM_ATENDIMENTO, a aplicação entra em uma sub-máquina paralela onde cada "região" mantém seu próprio status.

Diagrama de Estados Paralelos

text
SESSAO_ATIVA (Estado Paralelo)
├── REGIAO VÍDEO (WebRTC) ────────────────────────┐
│ [*] -> AGUARDANDO_PERMISSAO                     │
│ AGUARDANDO_PERMISSAO -> CONECTANDO [HAS_CAM]    │
│ CONECTANDO -> EM_CHAMADA                        │
│ EM_CHAMADA -> RECONECTANDO (se cair o sinal)   │
└─────────────────────────────────────────────────┘
├── REGIAO CHAT (WebSocket) ──────────────────────┐
│ [*] -> SINCRONIZANDO_HISTORICO                  │
│ SINCRONIZANDO_HISTORICO -> PRONTO               │
│ PRONTO -> ENVIANDO_MENSAGEM                     │
└─────────────────────────────────────────────────┘
├── REGIAO PRONTUARIO (Auto-save) ────────────────┐
│ [*] -> OCIOSO                                   │
│ OCIOSO -> EDITANDO                              │
│ EDITANDO -> SALVANDO (Debounce)                 │
└─────────────────────────────────────────────────┘

Implementação com Guards e Contexto

Os Guards garantem que transições só ocorram se critérios clínicos ou técnicos forem atendidos.

typescript
import { createMachine } from 'xstate';

export const sessionMachine = createMachine({
  id: 'session',
  type: 'parallel',
  context: {
    hasCameraPermission: false,
    isTermOfUseAccepted: false
  },
  states: {
    video: {
      initial: 'aguardandoPermissao',
      states: {
        aguardandoPermissao: {
          on: {
            GRANT_PERMISSION: {
              target: 'conectando',
              // Transição condicional (Guard)
              cond: (context) => context.isTermOfUseAccepted 
            }
          }
        },
        conectando: { /* ... */ },
        emChamada: { /* ... */ }
      }
    },
    chat: {
      initial: 'sincronizando',
      states: {
        sincronizando: { on: { SYNC_SUCCESS: 'pronto' } },
        pronto: { on: { SEND_MESSAGE: 'enviando' } },
        enviando: { on: { SENT: 'pronto' } }
      }
    }
  }
});

Implementação Detalhada: Máquina de Sessão Ativa

Uma implementação de máquina de estado em XState deve ser robusta para lidar com os desafios técnicos de uma teleconsulta.

typescript
import { createMachine, assign } from 'xstate';

interface SessionContext {
  hasCameraPermission: boolean;
  isTermOfUseAccepted: boolean;
  retryCount: number;
}

export const sessionMachine = createMachine({
  id: 'session',
  type: 'parallel',
  context: {
    hasCameraPermission: false,
    isTermOfUseAccepted: false,
    retryCount: 0
  } as SessionContext,
  states: {
    video: {
      initial: 'aguardandoPermissao',
      states: {
        aguardandoPermissao: {
          on: {
            GRANT_PERMISSION: {
              target: 'conectando',
              // Guard: Só avança se o termo foi aceito
              cond: (context) => context.isTermOfUseAccepted 
            },
            REJECT_PERMISSION: 'semAcesso'
          }
        },
        conectando: {
          invoke: {
            src: 'connectToWebRTC',
            onDone: 'emChamada',
            onError: {
              target: 'reconectando',
              actions: assign({ retryCount: (ctx) => ctx.retryCount + 1 })
            }
          }
        },
        emChamada: {
          on: { DISCONNECT: 'finalizado' }
        },
        reconectando: {
          after: {
            3000: { target: 'conectando' } // Tenta reconectar após 3 segundos
          }
        },
        semAcesso: {},
        finalizado: { type: 'final' }
      }
    },
    chat: {
      initial: 'sincronizando',
      states: {
        sincronizando: {
          on: { SYNC_SUCCESS: 'pronto' }
        },
        pronto: {
          on: { SEND_MESSAGE: 'enviando' }
        },
        enviando: {
          on: { SENT: 'pronto', ERROR: 'pronto' }
        }
      }
    }
  }
});

Integração com a Arquitetura Global

As máquinas de estado não substituem o Redux; elas atuam como o "Cérebro" da lógica.

  1. Redux: Armazena os dados brutos (nome do médico, especialidade, texto da prescrição).
  2. XState: Decide qual botão deve estar habilitado ou qual tela deve aparecer.
  3. Sagas: Escutam as transições da máquina para disparar efeitos colaterais (ex: iniciar o sinal de vídeo ao entrar no estado conectando).