Nova aba no super admin (/superadmin/secretaria): tabela com quem ligou/desligou a
Secretária de cada número, quando e o MOTIVO do desligamento. Endpoint
/api/superadmin/secretaria-desligamentos (superadminGuard) busca o log global no
motor (resolve um e-mail de dono pareado; log é global). Item no Sidebar + rotas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/dashboard/stats devolvia a agenda inteira da clínica (nomes de pacientes de
todos os dentistas) + faturamento/despesas/growth no payload para QUALQUER membro,
inclusive dentista. Pior: cache por clínica (dashboard:stats:<clinica>) servia o
mesmo JSON a todos os papéis. Agora: dentista vê só a própria agenda; financeiro
só p/ admin/donoclinica/donoconsultorio/funcionario; cache por escopo (papel/dentista).
Verificado: dentista recém-cadastrado (murilo) → agenda vazia + financeiro 0; dono → tudo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Os papéis (sec_numbers) são resolvidos no motor pela conta (x-nw-email). Um
usuário que só existe no scoreodonto (ex.: recepcionista) não tem conta no motor,
então buscar os papéis com o e-mail DELE retorna "conta não encontrada" → todos os
números liberados apareciam como "Sem papel definido". Agora o papel de cada sessão
liberada é buscado com o e-mail do DONO da sessão (cache por e-mail). Verificado:
recep passa a ver o número como role=clinic ("Clínica / Recepção · Rui Cesar").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GET /encaixe/analisar: ocupação do horário desejado (quem está nele) + horário
livre mais cedo (flex-inclusivo) p/ oferecer/encaixar.
- POST /encaixe/mover: remarca um agendamento (o A aceitou adiantar), com checagem
de conflito no destino (findConflict ganha excludeId p/ ignorar o próprio).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O index.js (já no main) importa createAccountsRoute/provisionNewwhatsAccount do
proxy.js, mas esses exports nunca foram commitados — só existiam no working tree.
Isso quebrava o boot do backend na imagem de produção (SyntaxError: proxy.js does
not provide an export named 'createAccountsRoute') → 502. Commita proxy.js (exports
createAccountsRoute/provisionNewwhatsAccount/operatorSignature/createWsTunnel) e
server.js (usa operatorSignature + parser 12mb em /api/nw/v1), deixando o main
consistente com o backend que já roda em dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fecha furos de identificação relatados:
- Telefone compartilhado na família: nova pergunta telefone_compartilhado +
cautelas IDENTIDADE/TELEFONE COMPARTILHADO — a IA nunca assume quem fala pelo
número (ex.: cadastrado no filho, mas quem fala é a mãe; ou pergunta do marido)
e confirma nome + para quem é antes de agendar; se não bater, encaminha ao humano.
- Profissional não cadastrado: cautela para citações de dentista fora da lista
(ex.: prótese com Dr. de fora) — encaminha ao humano e pede no resumo que o
dono cadastre o profissional.
- Pendência de config quando telefone_compartilhado=Sim (completar cadastro dos familiares).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Satélite da Secretária IA:
- /contexto (dentistas+especialidades+situações+cautelas) e /duvida (encaminhar
ao humano sem agendar); agenda_pedidos ganha tipo=duvida + resumo.
- Situações da clínica: tabela agenda_situacoes + config (dono) + aba na UI.
- Checklist do dono (checklist-defs.js + agenda_checklist): perguntas Sim/Não
conferidas contra o banco (dentistas/pacientes) → pendências de config +
cautelas p/ a IA não chutar.
- Área de PENDÊNCIAS DO DONO (PendenciasDono.tsx): engloba config + fila
operacional (horários/dúvidas); botão+badge no /wa-secretaria (só o dono).
- Fila humana (PedidosPendentes): cards de dúvida + resolver; /resolver.
- Botão DESLIGAR global da Secretária no ThinNav (secPowerApi, visível a todos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Espaçamento/duração padrão do slot passa de 30 para 60 min (1h entre atendimentos).
- /slots aceita ?periodo=manha|tarde (a Secretária pergunta o período antes de oferecer
e busca só aquele período).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Duplicar horário para todos os dias (botão na grade).
- Janela do dentista com DOIS limites: "IA agenda livre" [inicio,fim] e "aceito
confirmar também" (mais cedo/mais tarde) — colunas hora_inicio_flex/hora_fim_flex.
- Zona cinza: quando o cliente pede um horário fora da janela livre, a Secretária IA
NÃO agenda — registra um pedido (POST /pedido) e diz "vou confirmar e retorno".
- Fila da secretária humana: tabela agenda_pedidos + endpoints (listar/confirmar/
recusar); tela PedidosPendentes (confirmar → vira agendamento real, com escolha do
dentista); botão com BADGE de contagem na Agenda (poll a cada 30 min) para staff.
Testado em dev: dentista configura flex; IA registra pedido de 8h30 e responde "vou
verificar com a secretária"; humana confirma → agendamento criado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A — Folga/férias do dentista por DATA: tabela dentistas_folgas; o dentista (ou o
dono) marca dias em que não atende; o /slots pula o dentista nessas datas.
Endpoints /dentista/:id/folgas (GET/POST/DELETE) + UI na aba Dentistas.
B — Salas alugadas: o dentista com sala_reserva aprovada passa a ter horários
oferecidos pela Secretária no quarto (disponibilidade = janela da reserva,
casada pelo usuario_id). /slots ganha passada de salas (slot com sala_id +
local); /book grava agendamentos.sala_id (coluna aditiva). Geração passa a usar
CANDIDATE_CAP e ordena por horário (clínica e salas competem).
Testado em dev: folga pula os dias do dentista; reserva de sala gera slots com o
nome da sala.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Secretária passa a saber quando a clínica está aberta e a NÃO oferecer
horário/agendar em dia fechado (domingo, fora do horário, feriado).
- feriados.js: feriados nacionais BR (fixos + móveis via Páscoa) + resolver
com exceções da clínica.
- agenda-schema.js: tabelas clinicas_horarios (horário do dono) e feriados
(exceções/fechamentos), idempotentes.
- agenda-config.js: CRUD com ownership (horário/feriado da clínica = dono;
horário do dentista = ele mesmo ou o dono), em /api/nw/agenda-config.
- agenda-bridge.js: GET /status (aberto hoje?, motivo, próximo aberto, grade,
feriados) + /slots agora = clínica ∩ dentista − feriado.
- HorariosConfig.tsx + botão no AgendaSettingsModal: telas de configuração
(abas Clínica e Dentistas).
Testado em dev: a Secretária ofereceu "amanhã" ao pedirem "hoje" num domingo,
com os horários configurados da clínica.
Obs.: index.js também traz trabalho acumulado do satélite (proxy de mídia via
Wasabi/storage) que estava no working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Drawer de WhatsApp focado (WhatsChatDrawer) aberto do slot do agendamento, com
dropdown de números do paciente + grupo familiar e fluxo de iniciar conversa
- Auto-provisionamento de conta no motor: eager no cadastro/convite + self-heal no
proxy (404 "conta não encontrada" → provisiona e refaz o request)
- Ownership por sessão (wa_session_authz): dono do workspace (owner_id ou vínculo
donoclinica/donoconsultorio) tem poder total; delega re-scan, excluir sessão,
apagar conversa/mensagem, acesso ao Inbox e à Secretária — por conta
- Enforcement no proxy: guardSessionAction (scan/rescan/delete sessão),
guardAreaAccess (inbox/secretaria), guardInboxDestructive (apagar conversa/msg)
- Endpoints /api/nw/authz (dono gerencia) e /api/nw/access (acesso agregado)
- Assinatura do operador nas mensagens (*Nome*, desambiguação de homônimos → *Rui C.*)
- UI: painel "Gerenciar acesso" com 5 toggles; menu e botões de apagar condicionados
- Proxy de mídia /api/nw/media + re-download sob demanda
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nwClient inclui o header x-nw-clinica (id do SCOREODONTO_ACTIVE_WORKSPACE) e o
proxy REST o encaminha ao motor — assim as ações manuais escopam a ponte de
agenda para a clínica do workspace ativo (por-requisição).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expõe /api/nw/agenda/* (server-to-server, segredo compartilhado x-nw-agenda-secret,
preso ao clinica_id) para o motor operar na agenda REAL do scoreodonto:
- GET /slots — horários livres (dentistas_horarios ou fallback comercial seg–sex
08–18/30min) menos os agendamentos existentes e o passado.
- POST /book — cria agendamento real com anti-overbooking (mesma regra do server)
e find-or-create de paciente pelo telefone do WhatsApp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- proxy REST resolve a conta SEMPRE por x-nw-email (email do JWT verificado);
remove o fallback "sem email" (a chave mestra sem email dá 400 no motor).
- túnel WS (/api/nw/v1/stream) injeta x-nw-email no handshake e valida o JWT
por ?token=; server.js roteia o upgrade (/api/ws do app + /stream do plugin).
- testMotorKey usa email-sonda (404 = chave mestra válida).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backend POST /api/nw/config/token (superadmin): decodifica nwpair_<base64url({u,k,s})>,
VALIDA no motor (/api/ext/v1/sessions) e salva URL+chave+webhook. Token inválido/
sem conexão é rejeitado antes de salvar.
- Frontend: campo 'Token de pareamento' como caminho principal (textarea + Aplicar);
estado da conexão; clínica no dropdown (local); campos manuais em 'configuração
manual' (avançado).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend:
- config.js: config lida da tabela settings (id=newwhats_config) com fallback ENV;
loadConfigFromDb/saveConfigToDb + cache.
- index.js: endpoints superadmin GET/PUT /api/nw/config (secrets nunca em claro;
branco mantém valor), GET /api/nw/clinicas (dropdown), GET /api/nw/status (testar
conexão). server.js passa { superadminGuard }.
Frontend:
- PluginsView: NewwhatsConfigModal dedicado (carrega/salva no backend, dropdown de
clínicas em vez de ID cru, botão Testar conexão). Substitui o form genérico p/ newwhats.
Resolve UX ruim: nada de digitar ID de clínica; config passa a funcionar de fato.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clona o satélite do mercado adaptado ao scoreodonto (ESM, sem PluginManager):
- backend/newwhats/: config (ENV), auth (x-nw-key), routes /nw/* (clinica,
especialidades, dentistas, procedimentos, planos — multi-tenant por clinica_id),
sync-knowledge (base de conhecimento odonto → motor), webhook-receiver (HMAC,
enxuto), proxy REST /api/nw/v1/* → motor /api/ext/v1/*, manifest, INTEGRACAO-MOTOR.md.
- server.js: registerNewwhats(app, pool) antes do listen.
- Removidos (não úteis): media-transcribe (motor já transcreve), smart-router,
personas, follow-up-detector, context-builder.
- Config por ENV: NEWWHATS_URL/INTEGRATION_KEY/WEBHOOK_SECRET/CLINICA_ID.
- ativo::int=1 normaliza boolean vs smallint entre tabelas.
- Validado: buildKnowledgeText gera 1195 chars de dados reais; registro ok no express 5.2.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A agenda só isolava entre clínicas; dentro da mesma clínica, qualquer membro
via TUDO (todos os setores/equipes). Agora o setor passa a valer:
- agendamentos.setor_id + dentistas.setor_id (migration) + índice
(clinica_id, setor_id, start_time).
- POST carimba o setor: override em dentistas.setor_id, senão o setor do
VÍNCULO do próprio dentista (Gestão de Equipe, UI existente), senão o setor
de quem agenda. NULL = "geral".
- GET filtra (resolverSetorUsuario): dono/admin e membro SEM setor veem tudo
(compat — clínica que não usa setores não muda); membro COM setor vê só o
seu setor + os gerais (NULL). Salas não filtram.
Equipe de protético/biomédico = setor próprio, herda a mesma regra. O
anti-overbooking continua GLOBAL e acima do isolamento (isola a visão, mantém
a integridade). Validado em dev: dono/sem-setor veem 3/3; setor A vê só A+geral;
setor B vê só B+geral.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auditoria da agenda (clínicas/setores, profissional multi-local):
- anti-overbooking: agrupa registros do mesmo profissional por EMAIL quando
usuario_id é NULL (dentista sem conta que atende fixo numa clínica e avulso
em outras) — antes só agrupava por usuario_id e deixava passar duplo-agendamento.
- PUT (reagendar): checagem de conflito + UPDATE + auditoria agora numa única
transação (FOR UPDATE efetivo) — fecha a race de dois reagendamentos no mesmo slot.
- privacidade entre tenants: resposta 409 deixa de expor id/clinica_id do
compromisso alheio (helper slotOcupadoPublico) — só o horário.
- performance: índices (clinica_id,start_time) e (dentistaid,start_time) +
janela ?from&to opcional no GET (retrocompatível).
- cadastro de dentista: e-mail obrigatório no backend (todo dentista precisa de
conta/identidade global; protético interno sem conta usa outro fluxo).
Validado em dev.scoreodonto.com (pgdev). Produção intocada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O dropdown só listava protéticos internos (vinculados) ou do diretório público — um protético com
conta fora do diretório (ex.: claudemir) não aparecia para a clínica. Reformulado:
- Componente ProteticoPicker (modal): busca por nome/e-mail; estrela de FAVORITO; marcar PADRÃO
(pré-selecionado na próxima OS/GTO, só um por clínica); e "Adicionar por e-mail", que VINCULA um
protético existente como interno — resolve o caso de não estar no diretório, sem expô-lo publicamente.
- Backend: tabela protese_protetico_pref (favorito/padrão por clínica); GET /protese/laboratorios
passa a retornar favorito/padrao e ordena padrão→favorito→interno→nota→nome; POST .../:id/pref e
POST /protese/laboratorios/vincular.
- Picker aplicado no LancarGTO (avaliação/GTO) e na NovaOSModal; ProteticoInternoInline (protético
SEM conta) mantido ao lado.
Validado em DEV na CONSULTT CLINIC: claudemir ausente → vinculado por e-mail → aparece interno e,
como favorito+padrão, no topo da lista.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fecha o ciclo operacional do laboratório, antes só "receptor passivo" das OS da clínica.
1. Dar entrada em OS pelo lab — POST /api/protese/lab/os: o dono do laboratório registra um
trabalho recebido fora do app, escolhendo cliente existente OU avulso (clínica/consultório/
dentista, materializado como registro mínimo) + produto do catálogo ou tipo livre. A OS já
entra com laboratorio_id e custódia no laboratório. UI: botão "Dar entrada" + modal na LabHome.
2. Dar baixa em pagamento — já existia (lado 'lab'); mantido na PagamentosSecao.
3. Solicitar pagamento — POST /api/protese/os/:id/cobrar: calcula o saldo devedor (custo − pago)
e notifica o cliente, registrando no histórico. UI: botão "Solicitar pagamento (saldo)" na
PagamentosSecao do modo bancada.
4. Importar/vincular OS indicada — GET /api/protese/lab/os-indicadas (OS com protetico_id=me e
laboratorio_id NULL) + POST /api/protese/os/:id/vincular-lab (seta laboratorio_id, status→recebido,
notifica a clínica). Sem isso a OS só aparecia ao dono via fallback; importando, a equipe/técnicos
e o financeiro do lab passam a vê-la. UI: seção "OS indicadas a você" na LabHome.
origemDe agora reconhece cliente avulso tipo 'dentista'. Validado em DEV (todos os 4 endpoints).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Atende ao pedido de CRM financeiro do protético — antes as telas Clientes/Financeiro existiam mas
não distinguiam origem nem traziam relacionamento/fechamento.
- lab/clientes agora traz por cliente: ORIGEM (Clínica / Consultório / Dentista / Biomédico, derivada
de clinicas.tipo + role do owner), CONTATO (telefone), TICKET MÉDIO, ÚLTIMO PEDIDO, além de
OS/ativas/atrasadas/faturado/recebido/a-receber.
- Novo lab/fechamento: faturado x recebido por mês.
- LabClientesView reformulada: aba "Clientes (CRM)" com resumo por origem + tabela
(origem/contato/ticket/últ. pedido/a-receber); aba "Financeiro" com KPIs + FECHAMENTO MENSAL + extrato.
Validado em DEV: cliente Clínica (a-receber 0) e Dentista avulso (a-receber 100); fechamento 06/2026
faturado 550 / recebido 450.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Protético interno: a clínica cadastra um protético que não usa a plataforma. Cria usuário "mudo"
(e-mail placeholder @interno.local + senha-fantasma; usuarios.email é NOT NULL/UNIQUE) + vínculo
interno (fora do diretório). POST /protese/proteticos-internos; botão "Cadastrar protético interno"
na Nova OS, que recarrega o dropdown e seleciona o novo.
- Carteira no link /p/TOKEN: botão "Meus trabalhos" → trabalhos em andamento/finalizados + mini
financeiro mensal (valores pagos ao protético). GET /api/p/:token/carteira, escopo da relação
clínica↔protético do token (não vaza outras clínicas; sem dados do paciente).
Validado em DEV: interno criado e acessível no dropdown; OS+link; carteira com andamento + R$250/2026-06.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Novo módulo de logística do lab (doc/AGENDA-COLETA-LAB):
- protese_coleta: agendamento de coleta (lab busca) / entrega (lab leva), com data/hora,
endereço, responsável, status. Ligado à OS.
- POST /protese/os/:id/coleta (agendar), GET /protese/lab/agenda (cronológica),
POST /protese/coleta/:id/status. 'realizada' MOVE a custódia automaticamente
(coleta→laboratorio, entrega→clinica) — fonte única do movimento físico. GET detalhe traz coletas[].
- UI: tela AGENDA (lab-agenda) agrupada por dia com "marcar realizada"; seção Coleta/entrega na
aba Monitoramento da OS (clínica e lab).
Validado em DEV: agenda coleta → realizada → custódia=laboratorio; lab vê na agenda.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- A clínica avalia o laboratório (1-5★ + comentário) após a entrega (protese_avaliacao, 1 por OS,
reavaliável). POST /protese/os/:id/avaliar; avaliação volta no GET detalhe.
- Nota ScoreOdonto passa a combinar 0.6·operacional + 0.4·satisfação (estrelas) quando há avaliações.
Selo "Verificado" (≥5 entregas). Exposto no marketplace (★nota · nº avaliações · Verificado) e nos
indicadores do lab (KPI Satisfação). Helper notasScorePorProtetico unificado (dropdown + marketplace).
- UI: seção "Avaliar o laboratório" na OS entregue (lado clínica); selo/avaliações no card do marketplace.
- Correção de passagem: FileText/Star importados em ProteseView (FileText faltava desde a fatia 3).
Validado em DEV: 6 entregas + 5★/3★ → nota 9.2, satisfação 4★, verificado (marketplace e indicadores).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entidades protese_rfq + protese_rfq_cotacao. A clínica descreve o trabalho e convida N labs;
cada lab cota (valor/prazo/obs); a clínica compara (ordenado por preço) e escolhe → vira OS
(custo = valor da cotação) e a RFQ fecha (demais recusadas).
- Backend: POST/GET /protese/rfq, GET /protese/rfq/recebidas, POST /protese/rfq/cotacao/:id,
POST /protese/rfq/:id/escolher (cria a OS).
- Lado lab: tela Cotações (lab-cotacoes) — RFQs recebidas + responder.
- Lado clínica: botão Cotações na tela de Prótese — pedir multi-lab + comparar + escolher.
Validado em DEV ponta a ponta: pedido → cotação R$420 → escolha → OS criada → RFQ fechada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/profissionais/marketplace passa a trazer a 'nota' dos protéticos (helper
notasScorePorProtetico, mesma fórmula da Fase 5 — só com volume mínimo). Card do protético
no marketplace exibe ★ nota. Ordem mantida por proximidade; a nota é comparação visível.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fecha o Modo 1 (link seguro do protético sem conta):
- GET /api/protese/os/:id/link/status: link ativo, nº de acessos, último acesso e expiração.
- UI (aba Monitoramento, modo clínica): mostra status do link com Copiar / WhatsApp / Revogar
e os contadores de acesso/expiração; gera sob demanda.
Conversão por herança é nativa (a OS já aponta para o protetico_id do destinatário). Validado em
DEV: acessos contados, expiração 60 dias, revogação → status inativo + leitura pública 404.
Modo 1 concluído (1a leitura + 1b ação + 1c gestão).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pelo link /p/TOKEN o protético (sem conta) passa a ATUAR na OS, tudo auditado como "<nome> (link)":
- POST /api/p/:token/custodia — confirmar recebimento / devolução (clínica↔lab)
- POST /api/p/:token/status — avançar a produção (recebido…finalizado); entregue/cancelado
seguem exclusivos da clínica (400 pelo link)
- POST /api/p/:token/foto — anexar foto do trabalho
Página pública ganha a seção "Atualizar status" (custódia/etapa/foto) enquanto a OS não está
finalizada; header alterna Acompanhamento/Somente leitura.
Validado em DEV: recebimento, avanço de etapa, foto; entregue pelo link → 400.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provedor de Prótese SEM conta (doc/MODO1-LINK-SEGURO-PROTESE):
- Token por OS: tabela protese_os_link (aleatório 24B base64url, expira 60 dias, revogável,
conta acessos). POST /protese/os/:id/link (gera/recupera) + POST .../link/revogar.
- Leitura pública GET /api/p/:token (sem auth): payload curado com a mesma blindagem soLab —
SEM paciente_id (CPF) e SEM valor cobrado ao paciente; mostra trabalho, etapas, componentes,
fotos, custódia, ocorrências, histórico e custo do lab.
- Página pública ProtesePublicView (mobile-first 320px+), interceptada no index.tsx antes do
app autenticado; CTA "Criar conta grátis". Botão Gerar link / WhatsApp / Copiar na aba
Monitoramento (modo clínica).
- Validado em DEV: leitura, blindagem (CPF/valor ocultos), token inválido→404, revogação→404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provedor de Prótese, Fase 5 (doc/PROVEDOR-PROTESE-DECISAO.md) — sobre a captura das fases 2-4:
- Indicadores do lab (GET /protese/lab/indicadores, só dono): entregues, tempo médio, atraso %,
retrabalho % (garantia), ocorrências resolvidas %, componentes ausentes. Tela lab-indicadores.
- Nota ScoreOdonto (0-10) = 10 − (atraso%×3 + retrabalho%×4 + ocorrência%×3), só com ≥3 entregas
("score em formação" abaixo disso). Apenas ocorrências RESOLVIDAS pesam (contraditório respeitado).
- Ranking: getProteseLaboratorios devolve a nota por protético e ordena internos→nota→nome; a
clínica vê ★ nota no dropdown de Nova OS e Lançar GTO.
Roadmap do Provedor de Prótese concluído (Fases 1-5). Marketplace transacional e Modo 1 (link
seguro sem conta) ficam como evolução futura.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provedor de Prótese, Fase 4 (doc/PROVEDOR-PROTESE-DECISAO.md):
- PF/PJ: clinicas.pessoa_tipo (individual | laboratório) + documento (CPF/CNPJ) editáveis.
Tela "Laboratório & Equipe" (lab-equipe). GET/PATCH /protese/lab.
- Equipe: novo vinculos.role='tecnico_protese'. Dono convida por e-mail (ensureUsuarioPorEmail
→ credencial temporária). GET/POST/DELETE /protese/lab/equipe (só dono).
- Acesso estendido: proteseOsAccesso reconhece o técnico (vínculo no laboratorio_id) e a bancada
passa a usar labDoUsuario (dono OU técnico). Técnico opera a produção; menu reduzido e sem
clientes/financeiro/equipe (endpoints retornam vazio/403). Gestão é exclusiva do dono.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provedor de Prótese, Fase 3 (doc/PROVEDOR-PROTESE-DECISAO.md):
- Clientes (lab-clientes): clínicas que enviam OS, com OS totais/ativas/atrasadas/entregues,
faturado e a-receber por cliente. GET /protese/lab/clientes (agrega por clinica_id no escopo
do laboratório).
- Financeiro do lab (lab-financeiro): KPIs faturado/recebido/a-receber + extrato de recebimentos
(pagamentos lado lab) com clínica/paciente/forma/data. GET /protese/lab/pagamentos.
Regra: faturado = custo das OS entregues (≠ garantia); a-receber = faturado − recebido.
- Menu do laboratório ganha CLIENTES e FINANCEIRO (Sidebar + App rotas/contexto + nova
LabClientesView com abas).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provedor de Prótese, Fase 1 do roadmap (doc/PROVEDOR-PROTESE-DECISAO.md):
- Origem da OS: bancada e detalhe passam a trazer clinica_nome (subquery em clinicas);
o protético vê a clínica de origem na lista da bancada e no topo do modal. Destrava o
cenário multi-clínica (Clínica A/B/C → mesmo laboratório).
- Blindagem de CPF (LGPD): confirmado que pacientes.id É o CPF. O provedor de prótese
(lab que não é da clínica) deixa de receber paciente_id no detalhe e na bancada,
mantendo paciente_nome; a clínica continua vendo o CPF para operar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Etapas repetíveis/puláveis: cada etapa tem ação Ir/Voltar/Repetir (não só "avançar
para a próxima"). 'entregue' segue na Linha do tempo.
- Ajustes do trabalho (aba Etapas): editar cor/material (PATCH .../os/:id); trocar o
trabalho por outro + MOTIVO obrigatório (POST .../trocar); acréscimo/redução do valor
cobrado do paciente com observação (POST .../ajuste-valor) — tudo auditado no histórico.
- Privacidade: o protético (lab que não é da clínica) NÃO vê o que o paciente paga/deve —
GET detalhe omite pagamentos do lado clínica e zera o valor cobrado; a bancada também
zera o valor. O ajuste de valor do paciente é exclusivo da clínica (403 para o lab). No
front, o modo bancada esconde "Recebido do paciente".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Aba "Linha do tempo" movida para o FINAL (entregue/cancelar continuam nela).
- Etapas agora avançáveis também pela clínica (não só pelo lab): nova lista "Etapas do
trabalho" na 1ª aba, com botão "Avançar" por etapa. Backend: avanço da produção passa
a aceitar lab OU clínica de origem (auditado por actor). 'entregue' segue na timeline
(exige foto).
- Impressão por etapa em DOIS formatos: térmica (bobina 80mm) e laser (A4), com os dados
da OS preenchidos no recibo (paciente, trabalho, dentista, lab, dentes, material/cor,
componentes, prazo, local atual + assinaturas).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganiza o gerenciamento da OS em ABAS (sem o scrollão único):
- Etapas: dados + stepper; substitui entregue/cancelar por "imprimir cupom" de cada
etapa (comprovante imprimível que circula com o trabalho — janela de impressão).
- Linha do tempo: histórico com data/hora/autor; AQUI ficam marcar entregue / cancelar
(e o fluxo do lab / acionar garantia).
- Itens & Valores: componentes, fotos e pagamentos.
- Monitoramento: custódia do trabalho (clínica ↔ laboratório) — "protético retirou" /
"devolveu à clínica", com local atual em destaque e histórico de movimentações.
Backend: protese_os.local_atual + tabela protese_os_custodia + POST .../custodia
(valida transição, registra movimento e evento). GET detalhe expõe local_atual + custodia.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reformula o modal de detalhe da OS de prótese, fechando os gaps de gestão:
- Etapas: stepper visual do fluxo (solicitado→…→entregue) com a etapa atual e datas.
- Fotos: agora anexáveis TAMBÉM no modo clínica (antes só na bancada — beco sem saída
para entregar), com autoria (quem anexou), data e legenda; remover foto. Toda foto
entra no histórico.
- Componentes enviados: nova tabela protese_os_componente — item, qtd, quem enviou e
quando (ex.: implante, pilar, modelo). Add/remover.
- Valores recebidos (dois lados): nova tabela protese_os_pagamento — lado='clinica'
(paciente→clínica) e lado='lab' (clínica→lab), com totais, forma, autor e data.
- Auditoria: histórico com data+HORA e autor; foto/componente/pagamento viram eventos
rastreados. Helpers carregaOSComAcesso/logEventoOS reaproveitados nos sub-recursos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Workspace do protético passa a ser tipo='laboratorio' (re-tag dos existentes +
novos nascem assim no /api/register). Área de gestão própria, como a clínica.
- Roteamento por laboratorio_id: OS nascem com o lab (POST /os e GTO→OS); a
Bancada filtra por laboratorio_id com fallback por protetico_id (OS legadas).
Helper labDoProtetico(). Backfill aditivo + índice.
- Receita espelhada (2.1): a entrega lança RECEITA no workspace do lab
(clinica_id=laboratorio_id, valor=os.custo), espelho da DESPESA da clínica —
idempotente (financeiro_lab_id), isolado em try/catch.
fix: corrige coluna `paciente_nome`→`pacientenome` no INSERT da DESPESA de prótese
(bug pré-existente da Fase 1, em produção): a entrega de OS com custo>0 abortava
com "column paciente_nome does not exist". A mesma correção vale para a RECEITA nova.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Funcionário/membro com vínculo na clínica dona da sala passa a ter a sala como
workspace de acesso OPERACIONAL (papel_sala='operador'): vê a agenda da sala e
bloqueia horário para manutenção, sem locação nem financeiro. O Painel da Sala
detecta o modo operador e esconde recebíveis/relatório/baixa, mostrando só a
agenda + ação de bloqueio.
Bloqueio de manutenção: reserva tipo='manutencao' (coluna nova, default
'locacao'), valor 0, nasce 'confirmada' (sem aprovação) e NÃO gera recebível.
Reaproveita o anti-conflito de horário da sala (409 em sobreposição) e o
cancelamento por solicitante/unidade. A agenda renderiza o bloqueio em cinza.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Itens de GTO ligados a um produto do catálogo do protético geram, na
finalização da GTO, uma OS de prótese (status 'solicitado') na Bancada do
laboratório — idempotente por gto_item_id e isolado em try/catch para nunca
derrubar o finalize. Migrações aditivas (gto_items.produto_id/protetico_id,
protese_os.gto_item_id, re-tag de especialidades para area='protese').
No Lançar GTO, a especialidade de prótese troca o seletor de procedimentos
pelo fluxo laboratório → produto → valor → observação. Busca de pacientes
passa a ser server-side com debounce (antes filtrava só a 1ª página).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Novo helper requireCapabilityFromOrders(cap, tabela): deriva o clinicaId do 1º
item de req.body.orders, fechando o RBAC dos catálogos.
- Aplica em PUT /{especialidades,procedimentos,dentistas}/reorder (cap do catálogo).
Antes eram authGuard puro (qualquer autenticado reordenava catálogo de qualquer clínica).
- Validado em DEV: dono 200; dentista 403; id inexistente 404; lista vazia 400.
Marketplace (salas/sala-reservas/propostas): AUDITADO — já protegido por checagem de
POSSE nos handlers (owner_usuario_id / profissional_id destinatário / vínculo p/ cancelar).
Não há gap; capability-gating seria incorreto (modelo é posse cross-clínica). Sem mudança.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gateia 16 rotas clínicas remanescentes a 'agenda' (staff clínico; bloqueia paciente):
- coleções (requireCapability): agenda/presenca (GET+POST), agenda/pendencias,
agenda/atividade, agenda/importar-google, interesses (GET+POST), dentistas/horarios (POST),
dentistas/:dentistaId/horarios (GET)
- /:id (requireCapabilityRow, clinicaId da linha): agenda/pendencias/:id/resolver,
dentistas/horarios/:id (DELETE), agendamentos/:id/{avaliacao,historico},
pacientes/:id/{score,faltas,familia}
As /:id eram authGuard SEM validação de tenant — agora o requireCapabilityRow valida
vínculo+cargo pela clínica da linha (fecha também o gap cross-tenant).
Confirmado que telas de paciente (MeusTratamentos/Notifications) não usam nenhuma delas;
detalhes de paciente são só do PatientsView (funcionário+dono).
Validado em DEV: dono+dentista passam em tudo (200/404); row real→200, fake→404.
Residual cosmético (fora do sub-pass): rotas /reorder (sem clinicaId/:id, baixo impacto).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gateia 18 rotas (tenantGuard + requireCapability), espelhando o frontend:
- orcamentos (GET/POST/PUT/DELETE) → 'orcamentos' (funcionário + dono)
- contratos (modelos CRUD+duplicar; instâncias CRUD+enviar+assinar+cancelar+
gerar-cobranca; GETs) → 'contratos' (funcionário + dono)
'assinar' incluído: usa tenantGuard+loadContratoScoped (quem assina é membro da
clínica registrando a assinatura via ContratosView — não há fluxo de contraparte
externa). Todos os endpoints de contrato são usados só no ContratosView (funcionário+dono).
Propostas (marketplace, autorização por posse do profissional) ficam para sub-pass próprio.
Validado em DEV: dono acessa (fake→404); dentista 403.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gateia 15 rotas (cap espelhando o frontend):
- leads (GET/POST/converter) → 'leads' (funcionário + dono)
- pacientes mutações (export, POST, PUT/:id) → 'agenda' (staff clínico; bloqueia
paciente; INCLUI dentista — OrthoView cria/edita paciente)
- agendamentos (POST + ações /🆔 PUT, DELETE, confirmar, falta, remarcar, restaurar,
reencaminhar, solicitar-avaliacao) → 'agenda' (via requireCapabilityRow nas /:id)
Escolha de 'agenda' como discriminador 'staff clínico vs paciente': é a única capability
presente em funcionário/dentista/biomédico/protético/dono e AUSENTE em paciente — casa com
quem realmente opera (inclui o dentista que mexe em paciente pelo Ortho).
GETs de operação (lista de agendamentos/pacientes, detalhes) seguem abertos (consumidos por
todas as telas clínicas). Validado em DEV: dono/dentista passam onde devem; leads nega dentista;
reads 200; negação por linha sem apagar dados.
Deferido (sub-pass): presença, pendências/resolver, importar-google, interesses,
detalhes de paciente (score/faltas/família) e horários de dentista.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>