Pular para conteúdo

Fluxo: Cadastro de paciente

Subfluxo do agendamento completo. Isolado aqui porque tem armadilhas específicas.

Regra de ouro

Abstract

Sempre buscar por CPF antes de cadastrar. Duplicar paciente é pior do que falhar.

Fluxograma

flowchart TD
    A[cpf + nome + data_nascimento + telefone] --> B[_only_digits em cpf e telefone]
    B --> C[POST /cadastros/pacientes/cpf/CPF]
    C --> D{data é dict com cpf?}
    D -->|sim| E[return data como paciente]
    D -->|não| F{data == CPF nao encontrado?}
    F -->|sim| G[POST /paciente/multiplos<br/>com array de 1 paciente]
    F -->|não| FAIL1[FALHA: resposta inesperada]
    G --> H[POST /cadastros/pacientes/cpf/CPF de novo]
    H --> I{Agora achou?}
    I -->|sim| J[return data]
    I -->|não| FAIL2[FALHA: cadastro nao confirmou]

Funções auxiliares

# comum aos dois projetos
def _only_digits(s: str) -> str:
    return "".join(c for c in (s or "") if c.isdigit())
# busca (não lança exceção em 'não encontrado')
def _buscar_paciente_cpf(cpf: str) -> dict | None:
    resp = requests.post(
        f"{OTIMUS_BASE_URL}/cadastros/pacientes/cpf/{_only_digits(cpf)}",
        headers=_otimus_headers(),
        timeout=30,
    )
    resp.raise_for_status()
    result = resp.json()
    data = result.get("data")
    if isinstance(data, dict) and data.get("cpf"):
        return data
    if data == "O CPF informado não foi encontrado.":
        return None
    return None
# cadastro
def _cadastrar_paciente(nome: str, cpf: str, data_nascimento: str, telefone: str) -> dict | None:
    payload = {
        "pacientes": [
            {
                "id": 123,  # TODO: investigar se pode ser 0/omitido — hoje é hardcoded
                "nome": nome,
                "dataNascimento": data_nascimento,       # YYYY-MM-DD
                "cpf": _only_digits(cpf),
                "celular": _only_digits(telefone),
            }
        ]
    }
    resp = requests.post(
        f"{OTIMUS_BASE_URL}/paciente/multiplos",
        headers=_otimus_headers(),
        json=payload,
        timeout=30,
    )
    resp.raise_for_status()
    # nao confiamos na resposta do cadastro; re-buscamos
    return _buscar_paciente_cpf(cpf)

Formatos

Campo Request (cadastro) Response (busca)
cpf apenas dígitos apenas dígitos
celular apenas dígitos (DDI+DDD+número) apenas dígitos
dataNascimento YYYY-MM-DD DD-MM-YYYY
nome maiúsculas (opcional) livre

Telefone com DDI

O celular no cadastro é o do paciente, não da instância. Nos projetos atuais vem do WTS session details (telefone do contato) já no formato internacional completo (5599...).

Campo id: 123 — o que sabemos

No payload de cadastro, id: 123 é hardcoded em ambos os projetos. Isso aparentemente é ignorado pelo OTIMUS (ele gera um paciente_id novo), mas o contrato não é explícito. Recomendações:

  • Deixar como está nos projetos legados (MEDCENTER, CLINFETO) para não mexer em comportamento.
  • Em projetos novos: testar enviar sem o campo ou com "id": 0 e confirmar com a clínica o comportamento.

Consistência de dados

O OTIMUS tem uma idiossincrasia: se você cadastra um paciente com CPF X e alguns minutos depois busca, ele aparece. Mas se a resposta do cadastro é malformada e você não re-busca, você não tem paciente_id. Por isso o padrão é sempre re-buscar após cadastrar.

E se o paciente já existe com dados antigos?

O cadastro em duplicado geralmente falha (OTIMUS detecta CPF duplicado). O efeito prático:

  1. _buscar_paciente_cpf() devolve o paciente existente → usamos ele.
  2. Se o paciente deu nome/data errada na conversa, mantemos os dados antigos (não atualizamos). Atualizar é responsabilidade humana no admin do OTIMUS.

Nunca enviar CPF sem normalizar

# errado — aceita mascara
cpf = "123.456.789-00"

# certo
cpf = _only_digits(cpf)  # "12345678900"

Vários endpoints OTIMUS aceitam só dígitos. Mandar com máscara é um problema silencioso que aparece depois.