API Reference v2.0

Documentação completa de todos os endpoints — 15 módulos

Base URL: https://command-agenda-api.devsantos.workers.dev/api
Auth Admin: Authorization: Bearer {jwt_admin} — Sessão 8h
Auth Paciente: Authorization: Bearer {jwt_paciente} — Sessão 24h
Rotas Compartilhadas: especialidades, medicos, unidades, slots/disponiveis, backlog (aceitam ambos tokens)

Autenticação Admin 6 endpoints

POST /auth/login Autenticar usuário administrativo

Request Body

{
  "email": "admin@barueri.sp.gov.br",
  "senha": "MinhaSenh@123"
}

Response 200

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "usuario": {
    "id": 1,
    "nome": "Administrador",
    "email": "admin@barueri.sp.gov.br",
    "role": "administrador_master",
    "cargo": "Gestor de TI"
  }
}
Cria sessão em ca_sessao com JTI. Token expira em 8h.
POST /auth/logout Encerrar sessão (revogar JWT) Auth Required

Headers

Authorization: Bearer {token}

Response 200

{ "message": "Logout realizado com sucesso" }
Marca sessão como inativa no banco. Token fica revogado.
GET /auth/me Obter dados do usuário logado Auth Required

Response 200

{
  "id": 1,
  "nome": "Administrador",
  "email": "admin@barueri.sp.gov.br",
  "cpf": "12345678901",
  "cargo": "Gestor de TI",
  "role": "administrador_master"
}
POST /auth/validate-first-access Validar token de primeiro acesso

Request Body

{ "token": "abc123def456..." }

Response 200

{
  "valid": true,
  "email": "novo.usuario@barueri.sp.gov.br",
  "nome": "Novo Usuário"
}
POST /auth/first-access Definir nova senha (primeiro acesso)

Request Body

{
  "token": "abc123def456...",
  "email": "novo.usuario@barueri.sp.gov.br",
  "senhaTemporaria": "temp123",
  "novaSenha": "MinhaSenh@Segura123"
}
Requisitos: mín. 10 chars, maiúscula, minúscula, número e símbolo.

Response 200

{ "message": "Senha definida com sucesso" }
POST /auth/resend-first-access Reenviar e-mail de primeiro acesso Auth Required

Request Body

{ "usuarioId": 5 }

Response 200

{ "message": "E-mail reenviado com sucesso" }
Gera novo token e senha temporária, envia via Mailjet.

Autenticação Paciente 4 endpoints

POST /auth/paciente/login Login do paciente (CPF + senha)

Request Body

{
  "cpf": "12345678901",
  "senha": "minha_senha"
}

Response 200

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "paciente": {
    "id": 1,
    "nome": "José da Silva",
    "cpf": "12345678901",
    "tipo": "Comum"
  }
}
Token JWT com 24h de validade. Sessão em ca_sessao_paciente.
POST /auth/paciente/register Cadastrar novo paciente

Request Body

{
  "nomeCompleto": "Maria Oliveira",
  "cpf": "98765432100",
  "senha": "senha123",
  "dataNascimento": "1990-05-15",
  "telefone": "(11) 98765-4321",
  "email": "maria@email.com"
}

Response 201

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "paciente": {
    "id": 10,
    "nome": "Maria Oliveira",
    "cpf": "98765432100"
  }
}
CPF deve ter 11 dígitos válidos. Senha mín. 6 caracteres. Retorna token direto.
GET /auth/paciente/me Dados do paciente logado Auth Paciente

Response 200

{
  "id": 1,
  "nomecompleto": "José da Silva",
  "cpf": "12345678901",
  "telefone": "(11) 91234-5678",
  "email": "jose@email.com",
  "tipo": "Comum",
  "telefone_validado": 1
}
POST /auth/paciente/logout Encerrar sessão do paciente Auth Paciente

Response 200

{ "message": "Logout realizado com sucesso" }

Agendamentos 7 endpoints

GET /agendamentos Listar agendamentos (admin) Auth Admin

Query Parameters

ParamTipoDescrição
slotIdintegerFiltrar por slot
pacienteIdintegerFiltrar por paciente
statusstringAtivo, Cancelado, Confirmado, Não Compareceu
GET /agendamentos/por-data/{data} Agendamentos por data (YYYY-MM-DD) Auth Admin

Response 200

[{
  "ca_agendamentoid": 1,
  "ca_protocolo": "CA-ABC123XYZ",
  "ca_status": "Ativo",
  "ca_ordem": 1,
  "paciente_nome": "José da Silva",
  "paciente_cpf": "12345678901",
  "medico_nome": "Dr. João Silva",
  "especialidade_nome": "Cardiologia",
  "unidade_nome": "UBS Centro",
  "slot_datainicio": "2026-02-15 08:00",
  "slot_datafim": "2026-02-15 09:00"
}]
GET /agendamentos/meus Meus agendamentos (paciente logado) Auth Paciente
Rota exclusiva do paciente. Retorna agendamentos do paciente logado com detalhes de slot, médico, especialidade e unidade.
GET /agendamentos/buscar-paciente Buscar agendamentos por nome/CPF do paciente Auth Admin

Query Parameters

ParamTipoDescrição
qstringTermo de busca (nome ou CPF)
POST /agendamentos Criar agendamento Auth

Request Body

{
  "slotAgendaId": 123,
  "pacienteId": 456
}

Response 201

{
  "ca_agendamentoid": 789,
  "ca_protocolo": "CA-XYZ789ABC",
  "ca_ordem": 1,
  "ca_status": "Ativo"
}
Gera protocolo automático (CA-XXXXXXXXX). Valida vagas disponíveis no slot.
DELETE /agendamentos/{id} Cancelar agendamento Auth

Response 200

{ "message": "Agendamento cancelado com sucesso" }
Status muda para "Cancelado". Libera vaga no slot.
PUT /agendamentos/{id}/confirmar-presenca Confirmar presença do paciente Auth Admin

Response 200

{ "message": "Presença confirmada" }
PUT /agendamentos/{id}/nao-compareceu Marcar não comparecimento Auth Admin

Response 200

{ "message": "Não comparecimento registrado" }

Backlog / Fila de Espera 11 endpoints

GET /backlog Listar backlog (paginado, RBAC) Compartilhada

Query Parameters

ParamTipoDescrição
pageintegerPágina (default: 1)
limitintegerItens por página (default: 50)
prioridadestringAlta, Média, Baixa
especialidadeIdintegerFiltrar por especialidade
unidadeIdintegerFiltrar por unidade
medicoIdintegerFiltrar por médico
statusstringPendente, Aguardando Confirmação, Agendado, Cancelado
searchstringBusca por nome/CPF
Regra D-30: Itens finalizados (Agendado, Compareceu, Não Compareceu, Cancelado) com data passada são exibidos apenas dos últimos 30 dias. Paciente: vê apenas seus próprios itens.

Response 200

{
  "data": [{
    "backlogid": 1,
    "paciente_nome": "José da Silva",
    "paciente_cpf": "12345678901",
    "especialidade_nome": "Cardiologia",
    "prioridade": "Alta",
    "status": "Pendente",
    "data_preferida": "2026-02-15",
    "periodo_preferido": "Manhã",
    "origem": "Portal Paciente",
    "tipos_slot_elegiveis": "Normal,Prioritário",
    "datacriacao": "2026-02-01T10:00:00Z"
  }],
  "pagination": { "page": 1, "limit": 50, "total": 125, "totalPages": 3 }
}
GET /backlog/{id} Detalhes da solicitação (RBAC) Compartilhada
Admin vê qualquer item. Paciente vê apenas seus próprios.
POST /backlog Criar solicitação no backlog Compartilhada

Request Body

{
  "pacienteId": 1,
  "especialidadeId": 3,
  "medicoId": null,
  "unidadeId": null,
  "data_preferida": "2026-02-20",
  "periodo_preferido": "Manhã",
  "horario_preferido": null,
  "prioridade": "Alta",
  "observacoes": "Urgente",
  "origem": "Portal Paciente"
}
Paciente envia sem pacienteId (usa o do token). Admin envia com pacienteId. tipos_slot_elegiveis é preenchido automaticamente do cadastro do paciente.
PUT /backlog/{id} Atualizar solicitação Auth Admin
Permite alterar preferências, prioridade, observações.
PATCH /backlog/{id}/alocar Alocar paciente em slot (operador) Auth Admin

Request Body

{
  "slotAgendaId": 42
}
Status muda para "Aguardando Confirmação". Notificação WhatsApp enviada ao paciente. Valida 3 pilares: vaga disponível, elegibilidade do tipo de slot, e cota da especialidade.
PATCH /backlog/{id}/confirmar Paciente confirma proposta Compartilhada
Cria agendamento automaticamente (INSERT em ca_agendamento). Status → "Agendado". Notificação WhatsApp de confirmação.
PATCH /backlog/{id}/recusar Paciente recusa proposta Compartilhada
Status volta para "Pendente". Slot é liberado. Paciente permanece na fila.
POST /backlog/{id}/alocar-dia Alocar paciente por dia (escolhe data) Auth Admin

Request Body

{
  "data": "2026-02-20",
  "slotAgendaId": 55
}
POST /backlog/{id}/alocar-inteligente Alocação automática inteligente Auth Admin
Auto-busca o melhor slot disponível com base nas preferências do paciente (data, período, especialidade, médico, unidade, tipos de slot elegíveis). Usa validação de 3 pilares.
PATCH /backlog/{id}/inativar Inativar solicitação (soft delete) Auth Admin
Marca campo ativo = 0. Não aparece mais na listagem.
DELETE /backlog/{id} Cancelar solicitação Auth

Response 200

{ "message": "Solicitação cancelada" }

Slots 6 endpoints

GET /slots/disponiveis Listar slots disponíveis (agrupados) Compartilhada

Query Parameters

ParamTipoDescrição
especialidadeIdintegerFiltrar por especialidade
medicoIdintegerFiltrar por médico
unidadeIdintegerFiltrar por unidade
dataIniciostringData início (YYYY-MM-DD)
dataFimstringData fim (YYYY-MM-DD)
tiposlotIdintegerFiltrar por tipo de slot

Response 200

[{
  "data": "2026-02-15",
  "hora": "08:00",
  "medicoId": 1,
  "medicoNome": "Dr. João Silva",
  "especialidadeId": 3,
  "especialidadeNome": "Cardiologia",
  "unidadeId": 2,
  "unidadeNome": "UBS Centro",
  "tiposlotId": 1,
  "tiposlotNome": "Normal",
  "limiteVagas": 10,
  "agendados": 3,
  "vagasDisponiveis": 7,
  "disponivel": true,
  "slots": [101, 102, 103]
}]
Slots são agrupados por data/hora/médico. Enriquecidos com nomes via cross-db lookup (mirrorEntity).
POST /slots/generate-periodo Gerar slots para um período (grade individual) Auth Admin

Request Body

{
  "gradeAgendaId": 1,
  "dataInicio": "2026-02-01",
  "dataFim": "2026-02-28",
  "horas": ["08", "09", "10", "14", "15"],
  "diasSemana": [1, 2, 3, 4, 5],
  "tiposSlotDistribuicao": [
    { "tipoSlotId": 1, "quantidade": 3 },
    { "tipoSlotId": 2, "quantidade": 1 }
  ]
}
POST /slots/generate-bulk Gerar slots em massa (wizard de 5 etapas) Auth Admin

Request Body

{
  "medicoId": 1,
  "unidadeId": 2,
  "especialidadeId": 3,
  "consultorioId": 1,
  "dataInicio": "2026-02-01",
  "dataFim": "2026-03-31",
  "configuracaoDias": {
    "1": ["08", "09", "10", "14", "15"],
    "2": ["08", "09", "10"],
    "3": ["14", "15", "16"],
    "4": ["08", "09", "10", "14", "15"],
    "5": ["08", "09", "10"]
  },
  "tiposSlotDistribuicao": [
    { "tipoSlotId": 1, "quantidade": 3 },
    { "tipoSlotId": 2, "quantidade": 2 },
    { "tipoSlotId": 3, "quantidade": 1 }
  ]
}

Response 201

{
  "message": "240 slots criados com sucesso",
  "resumo": {
    "totalSlots": 240,
    "diasProcessados": 40,
    "gradeAgendaId": 5
  }
}
Cria ou reutiliza grade existente (mesmo médico/unidade/especialidade). Gera slots para cada combinação dia/hora.
GET /slots/{id} Detalhes do slot Auth Admin
Retorna slot com detalhes enriquecidos (nomes de médico, especialidade, unidade, tipo de slot).
PUT /slots/{id} Atualizar slot Auth Admin
Permite alterar status, datas, tipo de slot, limite de vagas.
DELETE /slots/{id} Cancelar slot Auth Admin
Status → "Cancelado". Cancela agendamentos vinculados.

Grade de Agenda 8 endpoints

GET /grade-agenda Listar grades Auth Admin

Response 200

[{
  "ca_gradeagendaid": 1,
  "ca_medicoid": 1,
  "ca_especialidadeid": 3,
  "ca_unidadeid": 2,
  "ca_consultorioid": 1,
  "ca_vigencia": "Permanente",
  "ca_overbooking": 0,
  "ca_limitevagas": 10,
  "ca_status": "Ativo",
  "medico_nome": "Dr. João Silva",
  "especialidade_nome": "Cardiologia",
  "unidade_nome": "UBS Centro",
  "consultorio_nome": "Consultório 1"
}]
GET /grade-agenda/{id} Detalhes da grade Auth Admin
POST /grade-agenda Criar grade Auth Admin

Request Body

{
  "medicoId": 1,
  "especialidadeId": 3,
  "unidadeId": 2,
  "consultorioId": 1,
  "vigencia": "Permanente",
  "overbooking": false,
  "limitevagas": 10,
  "status": "Ativo"
}
GET /grade-agenda/{id}/cancelled-days Listar dias cancelados da grade Auth Admin

Response 200

[{
  "id": 1,
  "gradeagendaid": 5,
  "data": "2026-02-15",
  "motivo": "Feriado municipal",
  "cancelado_por": "Admin",
  "cancelado_em": "2026-02-10T14:00:00Z",
  "restaurado": 0
}]
GET /grade-agenda/{id}/cancelled-days/range Dias cancelados por período Auth Admin

Query: ?dataInicio=2026-02-01&dataFim=2026-02-28

POST /grade-agenda/{id}/cancel-day Cancelar dia específico Auth Admin

Request Body

{
  "data": "2026-02-15",
  "motivo": "Feriado municipal"
}
Cancela automaticamente slots livres do dia. Motivo obrigatório.
PUT /grade-agenda/{id}/restore-day Restaurar dia cancelado Auth Admin

Request Body

{ "data": "2026-02-15" }
Marca dia como restaurado e reativa slots que foram cancelados.
GET /grade-agenda/{id}/day-history/{data} Histórico de um dia específico Auth Admin
Retorna histórico de cancelamentos e restaurações do dia.

Estatísticas da Grade 4 endpoints

GET /grade-stats/day/{date} Estatísticas resumidas do dia Auth Admin

Response 200

{
  "data": "2026-02-15",
  "totalSlots": 50,
  "totalVagas": 200,
  "totalAgendados": 45,
  "vagasDisponiveis": 155,
  "especialidades": 5,
  "medicos": 8
}
GET /grade-stats/day/{date}/detail Detalhes completos do dia Auth Admin
Retorna slots, agendamentos, médicos e especialidades do dia com detalhes completos.
GET /grade-stats/day/{date}/doctors Médicos com agenda no dia Auth Admin
Lista médicos que possuem slots no dia, com contagem de vagas e agendamentos.
POST /grade-stats/check-availability Verificar disponibilidade (3 pilares) Auth Admin

Request Body

{
  "slotAgendaId": 42,
  "pacienteId": 10,
  "especialidadeId": 3
}

Response 200

{
  "disponivel": true,
  "validacoes": {
    "vagaDisponivel": true,
    "tipoSlotElegivel": true,
    "cotaEspecialidade": true
  }
}
3 Pilares: 1) Vaga disponível no slot, 2) Tipo de slot elegível para o paciente, 3) Cota da especialidade para o tipo de slot.

Especialidades 6 endpoints

GET /especialidades Listar especialidades Compartilhada

Query: ?ativas=true para filtrar apenas ativas

Response 200

[{
  "ca_especialidadeid": 1,
  "ca_nome": "Cardiologia",
  "ca_limitevagas": 10,
  "ca_status": "Ativo"
}]
POST /especialidades Criar especialidade Auth Admin
{
  "nome": "Dermatologia",
  "limiteVagas": 8,
  "status": "Ativo"
}
PUT /especialidades/{id} Atualizar especialidade Auth Admin
DELETE /especialidades/{id} Inativar especialidade (soft delete) Auth Admin
GET /especialidades/{id}/cotas Listar cotas de vagas por tipo de slot Auth Admin

Response 200

[{
  "especialidadeid": 1,
  "tiposlotid": 1,
  "tiposlot_nome": "Normal",
  "vagas": 3
},
{
  "especialidadeid": 1,
  "tiposlotid": 2,
  "tiposlot_nome": "Prioritário",
  "vagas": 2
}]
Soma das cotas deve ser igual ao limiteVagas da especialidade.
POST /especialidades/{id}/cotas Definir cotas de vagas Auth Admin

Request Body

{
  "cotas": [
    { "tiposlotid": 1, "vagas": 3 },
    { "tiposlotid": 2, "vagas": 2 },
    { "tiposlotid": 3, "vagas": 1 }
  ]
}
Substitui todas as cotas existentes (delete + insert).

Médicos 4 endpoints

GET /medicos Listar médicos (enriquecido com especialidades) Compartilhada

Query: ?especialidadeId=3 & status=Ativo

Response 200

[{
  "ca_medicoid": 1,
  "ca_nomecompleto": "Dr. João Silva",
  "ca_crm": "123456",
  "ca_crm_uf": "SP",
  "ca_cpf": "12345678901",
  "ca_telefone": "(11) 98765-4321",
  "ca_unidadeid": 2,
  "ca_limitevagas": 15,
  "ca_foto_url": "https://r2.../foto.jpg",
  "ca_status": "Ativo",
  "especialidades": [
    { "especialidadeid": 3, "nome": "Cardiologia" },
    { "especialidadeid": 7, "nome": "Clínica Geral" }
  ]
}]
Especialidades via JOIN em ca_medico_especialidade.
POST /medicos Criar médico Auth Admin
{
  "nomeCompleto": "Dra. Maria Santos",
  "crm": "654321",
  "crm_uf": "SP",
  "cpf": "98765432100",
  "dataNascimento": "1980-03-10",
  "telefone": "(11) 91234-5678",
  "email": "maria@email.com",
  "unidadeId": 2,
  "especialidadeIds": [3, 7],
  "limiteVagas": 15
}
Insere na ca_medico + ca_medico_especialidade (N:N).
PUT /medicos/{id} Atualizar médico Auth Admin
Atualiza dados + recria associações de especialidades se enviadas.
DELETE /medicos/{id} Inativar médico (soft delete) Auth Admin

Unidades de Saúde 5 endpoints

GET /unidades Listar unidades (com consultórios inline) Compartilhada

Response 200

[{
  "ca_unidadeid": 2,
  "ca_nome": "UBS Centro",
  "ca_tipo": "Clínica",
  "ca_logradouro": "Rua Principal",
  "ca_numero": "100",
  "ca_bairro": "Centro",
  "ca_cidade": "Barueri",
  "ca_uf": "SP",
  "ca_cep": "06400-000",
  "ca_telefone": "(11) 4199-0000",
  "ca_horario_abertura": "07:00",
  "ca_horario_fechamento": "18:00",
  "ca_dias_funcionamento": "1,2,3,4,5",
  "ca_status": "Ativo",
  "consultorios": [
    { "ca_consultorioid": 1, "ca_nome": "Consultório 1", "ca_status": "Ativo" },
    { "ca_consultorioid": 2, "ca_nome": "Consultório 2", "ca_status": "Ativo" }
  ]
}]
POST /unidades Criar unidade (com consultórios e horários) Auth Admin
{
  "nome": "UBS Jardim Paulista",
  "tipo": "Clínica",
  "logradouro": "Av. Brasil",
  "numero": "500",
  "complemento": "Bloco A",
  "bairro": "Jardim Paulista",
  "cidade": "Barueri",
  "uf": "SP",
  "cep": "06400-000",
  "telefone": "(11) 4199-1111",
  "horarioAbertura": "07:00",
  "horarioFechamento": "18:00",
  "diasFuncionamento": "1,2,3,4,5",
  "horariosPorDia": {
    "1": { "abertura": "07:00", "fechamento": "18:00" },
    "6": { "abertura": "08:00", "fechamento": "12:00" }
  },
  "consultorios": [
    { "nome": "Consultório 1" },
    { "nome": "Consultório 2" }
  ]
}
PUT /unidades/{id} Atualizar unidade Auth Admin
DELETE /unidades/{id} Inativar unidade (soft delete) Auth Admin
Verifica se há grades ativas antes de inativar.
PATCH /unidades/{id}/reativar Reativar unidade Auth Admin

Consultórios 5 endpoints

GET /consultorios Listar consultórios Auth Admin

Query: ?unidadeId=2 & status=Ativo

POST /consultorios Criar consultório Auth Admin
{
  "nome": "Consultório 3",
  "unidadeId": 2
}
PUT /consultorios/{id} Atualizar consultório Auth Admin
DELETE /consultorios/{id} Inativar consultório (soft delete) Auth Admin
Verifica se há grades ativas vinculadas.
PATCH /consultorios/{id}/reativar Reativar consultório Auth Admin

Tipos de Slot 4 endpoints

GET /tipos-slot Listar tipos de slot Auth

Response 200

[{
  "ca_tiposlotid": 1,
  "ca_nome": "Normal",
  "ca_finalidade": "Consulta regular",
  "ca_canalorigem": "Aberto",
  "ca_cor": "#3b82f6",
  "ca_agendavel": 1,
  "ca_status": "Ativo"
}]
POST /tipos-slot Criar tipo de slot Auth Admin
{
  "nome": "Prioritário",
  "finalidade": "Atendimento prioritário",
  "canalOrigem": "Aberto",
  "cor": "#ef4444",
  "agendavel": true
}
PUT /tipos-slot/{id} Atualizar tipo de slot Auth Admin
DELETE /tipos-slot/{id} Inativar tipo de slot Auth Admin

Pacientes 11 endpoints

GET /pacientes Listar pacientes (busca por CPF/nome) Auth Admin

Query: ?cpf=123&search=silva&limit=50

Response 200

[{
  "ca_pacienteid": 1,
  "ca_nomecompleto": "José da Silva",
  "ca_cpf": "12345678901",
  "ca_datanascimento": "1985-06-20",
  "ca_telefone": "(11) 91234-5678",
  "ca_email": "jose@email.com",
  "ca_tipo": "Comum",
  "ca_tipo_vinculo": "Munícipe",
  "ca_status": "Ativo",
  "ca_telefone_validado": 1,
  "ca_funcionario": 0,
  "ca_origem": "Portal Paciente"
}]
GET /pacientes/{id} Detalhes do paciente Auth Admin
POST /pacientes Criar paciente (admin) Auth Admin
{
  "nomeCompleto": "Maria Santos",
  "cpf": "98765432100",
  "dataNascimento": "1990-05-15",
  "telefone": "(11) 98765-4321",
  "email": "maria@email.com",
  "tipo": "Prioritário",
  "tipo_vinculo": "Munícipe",
  "nome_mae": "Ana Santos"
}
Detecção automática de funcionário via CPF. Status = Ativo.
PUT /pacientes/{id} Atualizar paciente Auth Admin
DELETE /pacientes/{id} Inativar paciente (soft delete) Auth Admin
PATCH /pacientes/{id}/reativar Reativar paciente Auth Admin
POST /pacientes/{id}/validar-telefone Enviar código OTP via WhatsApp Auth
Gera código de 6 dígitos. Validade: 5 minutos. Máx 3 tentativas. Cooldown 60s entre envios.
POST /pacientes/{id}/confirmar-telefone Confirmar código OTP Auth

Request Body

{ "codigo": "123456" }
Se válido, marca telefone_validado = 1. Se inválido, incrementa tentativas.
DELETE /pacientes/{id}/validacao-telefone Remover validação de telefone Auth Admin
Reseta telefone_validado para 0.
GET /pacientes/{id}/tipos-slot Tipos de slot elegíveis do paciente Auth Admin

Response 200

[
  { "tiposlotid": 1, "nome": "Normal" },
  { "tiposlotid": 2, "nome": "Prioritário" }
]
PUT /pacientes/{id}/tipos-slot Atualizar tipos de slot elegíveis Auth Admin

Request Body

{
  "tipoSlotIds": [1, 2, 3]
}
Substitui todas as associações (delete + insert em ca_paciente_tiposlot).

Usuários Administrativos 4 endpoints

Requer permissão manage_users (apenas administrador_master).
GET /usuarios Listar usuários (paginado) RBAC: admin_master

Query: ?page=1&limit=20&search=admin&role=operador&status=ativo

GET /usuarios/{id} Detalhes do usuário RBAC: admin_master
POST /usuarios Criar usuário (gera senha temp + e-mail) RBAC: admin_master

Request Body

{
  "nome": "Operador Novo",
  "email": "operador@barueri.sp.gov.br",
  "cpf": "11122233344",
  "cargo": "Operador de Agendamento",
  "role": "operador"
}
Gera senha temporária aleatória + token de primeiro acesso. E-mail enviado via Mailjet.

Response 201

{
  "usuario": {
    "id": 5,
    "nome": "Operador Novo",
    "email": "operador@barueri.sp.gov.br",
    "role": "operador",
    "primeiro_acesso": 1
  }
}
PUT /usuarios/{id} Atualizar usuário RBAC: admin_master
Permite alterar nome, email, cpf, cargo, role, ativo, senha.

Upload (R2 Storage) 2 endpoints

POST /upload/medico/{id}/foto Upload foto do médico Auth Admin

Content-Type: multipart/form-data

CampoTipoDescrição
fotoFileImagem (JPEG, PNG, WebP). Máx 5MB

Response 200

{
  "foto_url": "https://pub-xxxxx.r2.dev/medicos/1/foto-1234567890.jpg"
}
Armazena no Cloudflare R2. Atualiza ca_medico.ca_foto_url. Deleta foto anterior se existir.
DELETE /upload/medico/{id}/foto Remover foto do médico Auth Admin
Remove do R2 e limpa ca_foto_url.