Skip to content

Guards e Controle de Fluxo

Em sistemas críticos de saúde, a integridade do processo é tão importante quanto a integridade dos dados. Nossa arquitetura utiliza o conceito de Guards (Guardas) em múltiplas camadas para garantir que uma ação só seja executada se todos os pré-requisitos técnicos e clínicos forem atendidos.

Diferente de uma simples validação de formulário, os Guards atuam como "portões" que impedem o sistema de entrar em estados inválidos ou perigosos.

Camadas de Guards na Arquitetura

Dividimos a responsabilidade de proteção em três níveis complementares:

  1. Guards de Roteamento (Nível de Acesso)
  2. Guards de Máquina de Estado (Lógica de Negócio)
  3. Guards de UI (Experiência do Usuário)
  4. Guards de Efeito Colateral (Saga/Infraestrutura)

1. Guards de Roteamento (Segurança RBAC)

Localizados na camada de Apps, esses guards impedem que o usuário baixe o código de um domínio para o qual não tem permissão. Utilizamos o RequireRoleLayout para orquestrar essa proteção.

Exemplo Clínico:

Um médico pode acessar /medical/records, mas um paciente tentando acessar a mesma URL será bloqueado antes mesmo do componente de prontuário ser montado (Lazy Loading não é disparado).

tsx
// Proteção de rota via Guard de Role
<Route element={<RequireRoleLayout role="DOCTOR" />}>
  <Route path="/medical/prescriptions" element={<PrescriptionPage />} />
</Route>

2. Guards de Máquina de Estado (XState)

Estes são os guards mais críticos, localizados dentro da camada de Modules. Eles garantem que a transição entre estados de um protocolo médico siga regras estritas.

Exemplo: Validação de Teleconsulta

Um médico não pode clicar em "Finalizar Atendimento" se o diagnóstico obrigatório não tiver sido preenchido ou se a prescrição estiver em estado de "Salvando...".

typescript
// Exemplo de Guard no XState
{
  on: {
    FINISH_CONSULTATION: {
      target: 'finished',
      // O Guard impede a transição se a condição for falsa
      cond: (context) => context.hasDiagnosis && context.isPrescriptionSaved
    }
  }
}

3. Guards de UI (Component Level)

Na camada de Shared ou Domains/UI, utilizamos guards para controlar a interatividade. Eles geralmente refletem o estado da máquina de estados ou permissões de contexto.

  • Guards de Habilitação: Botões que permanecem desabilitados (disabled) enquanto um processo assíncrono crítico ocorre.
  • Guards de Visibilidade: Elementos que só aparecem se o usuário possuir um "Consentimento" assinado.
tsx
const FinishButton = () => {
  const { canFinish } = useConsultationGuard(); // Hook que consulta a State Machine
  
  return (
    <Button 
      disabled={!canFinish}
      tooltip={!canFinish ? "Preencha o diagnóstico antes de finalizar" : ""}
    >
      Finalizar Atendimento
    </Button>
  );
};

4. Guards de Efeito Colateral (Sagas)

Na camada de Modules/Store, os Sagas atuam como o último guard antes de uma chamada de API ou de um evento de domínio ser disparado. Eles validam se o estado global da aplicação é consistente para aquela operação.

Por que usar guards nos Sagas?

Para evitar "Race Conditions" (Condições de Corrida). Se um usuário clicar duas vezes rapidamente em "Salvar", o Saga utiliza guards (como takeLatest ou verificações de loading) para garantir que apenas uma operação seja processada.

typescript
// packages/modules/medical-record/store/sagas.ts
import { select, call, put } from 'redux-saga/effects';
import { selectIsSaving } from './selectors';
import { actions } from './slice';

function* handleSaveMedicalRecord(action) {
  // Guard de Reentrada
  const isSaving = yield select(selectIsSaving);
  if (isSaving) {
    console.warn('Operação de salvamento já em curso. Ignorando...');
    return;
  }

  // Guard de Validação de Dados Críticos
  if (!action.payload.diagnosisCode) {
    yield put(actions.setError('O código CID é obrigatório para salvar o prontuário.'));
    return;
  }

  try {
    yield put(actions.setSaving(true));
    yield call(api.save, action.payload);
    yield put(actions.saveSuccess());
  } catch (err) {
    yield put(actions.saveFailure(err.message));
  } finally {
    yield put(actions.setSaving(false));
  }
}

Implementação de Guard de Roteamento (RequireRoleLayout)

O guard de roteamento é um componente de alta ordem que protege galhos inteiros da árvore de rotas.

tsx
// infra/auth/RequireRoleLayout.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './hooks';

interface Props {
  role: 'DOCTOR' | 'PATIENT' | 'ADMIN';
}

export const RequireRoleLayout = ({ role }: Props) => {
  const { user, isAuthenticated, loading } = useAuth();

  if (loading) return <LoadingSpinner />;

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  if (user.role !== role) {
    return <Navigate to="/unauthorized" replace />;
  }

  // Se passou em todos os guards, renderiza as rotas filhas
  return <Outlet />;
};

Matriz de Responsabilidade de Guards

Tipo de GuardLocalizaçãoObjetivo PrincipalQuando falha...
RoteamentoAppsSegurança e Autorização (RBAC)Redireciona para Login/403
State MachineModulesIntegridade do Processo ClínicoImpede a mudança de tela/estado
UIShared/UIFeedback ao UsuárioDesabilita botões/inputs
SagaModules/StoreConsistência de Dados e APICancela a requisição/log de erro

Conclusão

O uso sistemático de Guards em nossa arquitetura garante que a aplicação seja "resiliente a erros de operação". Em um contexto médico, onde a agilidade é necessária, os guards protegem o profissional de saúde de cometer erros acidentais no fluxo do sistema, garantindo que o software siga rigorosamente os protocolos estabelecidos.