Commit Graph

263 Commits

Author SHA1 Message Date
VPS 4 Builder 40bed96515 feat(newwhats): agenda inteligente — especialidades, situações, dúvidas, checklist do dono e pendências
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>
2026-07-06 15:40:51 +02:00
VPS 4 Builder f4c2c6861a feat(agenda): GET /dentista-contato — resolve WhatsApp do dentista (Parte A)
Endpoint para a Secretária IA acionar o dentista responsável num encaixe sub-hora.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 06:31:33 +02:00
VPS 4 Builder 668d672531 feat(agenda): 1h de espaçamento + filtro manhã/tarde no /slots
- 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>
2026-07-06 05:59:32 +02:00
VPS 4 Builder ca2cfa1f35 feat(agenda): janela IA-livre vs estendida (zona cinza) + fila da secretária humana
- 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>
2026-07-06 05:29:25 +02:00
VPS 4 Builder 773e8d4bf2 fix(agenda): fuso automático (America/Sao_Paulo) + dentista configura seus horários
- TZ=America/Sao_Paulo no backend (compose): a agenda deixa de rodar em UTC —
  horários/slots passam a ser Brasília automaticamente (corrige deslocamento de 3h).
- Acesso do DENTISTA: novo botão "Meus Horários" na agenda do dentista, abrindo
  a config direto nos horários/folgas DELE (HorariosConfig com initialTab/soDentista,
  restrito ao próprio registro). Antes só admin/donoclinica alcançavam a config.
- Config da clínica: gate passa a incluir donoconsultorio (dono de consultório).

Obs.: AgendaView.tsx também traz trabalho acumulado da agenda que estava no
working tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 04:44:38 +02:00
VPS 4 Builder 0a324bf8c8 feat(secretaria): folga do dentista por data (A) + salas alugadas na agenda (B)
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>
2026-07-06 02:12:50 +02:00
VPS 4 Builder 77dec56dd0 feat(secretaria): horários de funcionamento + feriados na agenda/Secretária
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>
2026-07-06 01:23:27 +02:00
VPS 4 Builder 03711e6280 feat(secretaria): agente/cérebro por número na aba Números
Adiciona o seletor "Agente / Cérebro deste número" no NumbersPanel: cada
número de WhatsApp passa a apontar para um agente (sec_numbers.agent_id),
tornando a Secretária, os nós do cérebro e a agenda separados POR SESSÃO.
Mostra o cérebro vinculado em cada card; tipos SecNumber.agent_id e
CalendarSlot.instance_id + param instance_id no calendar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:48:29 +02:00
VPS 4 Builder 09edeb9558 feat(newwhats): prévia via thumbnail Wasabi + guard de mídia irrecuperável
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 1m42s
- MessageBubble: carrega <url>.thumb.webp na prévia (fallback ao full via onError)
- mediaUnavailable proativo via flag media_recoverable (evita 400 em mídia legada)
- useNewInboxBridge: mapeia mediaRecoverable -> media_recoverable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.0.34
2026-07-04 19:05:42 +02:00
VPS 4 Builder 8b1801bd9c feat(agenda): layout em 2 colunas no desktop + sidebar recolhível
- AgendaView: controles migrados do header horizontal para uma coluna lateral
  esquerda (AGENDAR, ícones de ação em faixa horizontal de tamanho igual,
  presença, legenda de dentistas); calendário em flex-1 ocupando a altura toda
- Mobile revisto: cabeçalho compacto (título + AGENDAR) e calendário flex-1
- Sidebar do sistema recolhível no desktop (App: estado persistido + botão
  flutuante para reexibir); toggle fica ao lado do título "AGENDA CLÍNICA"
- Toolbar do FullCalendar compactada (título/botões/cabeçalhos menores)
- Título da agenda responsivo (não gera scroll horizontal na coluna estreita)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 02:32:54 +02:00
VPS 4 Builder c2a1f8f701 fix(newwhats): resolve conversa por 9º dígito e nome do paciente no slot da agenda
- WhatsChatDrawer: busca o chat pelos últimos 8 dígitos (o WhatsApp grava o jid BR
  sem o 9º dígito, ex. 556799591687), evitando falso "sem conversa"
- AgendaView: slot usa pacienteNome || pacientenome (backend retorna minúsculo),
  corrigindo o card que não exibia o nome do paciente

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 00:29:02 +02:00
VPS 4 Builder 21bebd3256 feat(newwhats): ownership de sessões, auto-provisionamento, assinatura e drawer na agenda
- 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>
2026-07-02 23:26:31 +02:00
VPS 4 Builder 372431c0a1 style(newwhats): painel de agentes +20% no desktop e tela cheia no mobile
Painel do meio (selecionar agente) passa de 280px para 336px (md:) e, no mobile,
ocupa a tela (flex-1) com o painel principal oculto (hidden md:flex).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 05:20:03 +02:00
VPS 4 Builder ecf52d961a feat(newwhats): envia x-nw-clinica do workspace ativo (modelo de canal)
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>
2026-07-02 05:19:44 +02:00
VPS 4 Builder 3e1001b7c7 style(newwhats): /wa-sessions com sidebar do scoreodonto + tema branco
- wa-sessions sai do isStandaloneView → renderiza com a sidebar (área de
  administração); wa-inbox/wa-secretaria seguem em tela cheia.
- SessionsView convertida do dark para o tema branco/profissional (bg-gray-50/
  white, text-gray-*, badges claros, botão Conectar em brand); backdrops de
  modal e botões coloridos preservados.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 03:55:16 +02:00
VPS 4 Builder 821ae591d4 style(newwhats): tema branco/profissional na Secretária (/wa-secretaria)
Converte a SecretariaView do dark para o tema claro do scoreodonto
(bg-gray-50/white, text-gray-*, border-gray-*), preservando botões brand,
backdrops de modal e acentos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 00:42:34 +02:00
VPS 4 Builder 9babc69172 feat(newwhats): ponte de agenda real (slots/book) para a Secretária
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>
2026-07-02 00:42:33 +02:00
VPS 4 Builder 092635be5a fix(realtime): guarda cleanup de WebSocket em CONNECTING (StrictMode)
Fechar o socket ainda em CONNECTING (double-mount do StrictMode em dev) gerava
"WebSocket is closed before the connection is established". closeSocket() adia o
close para o onopen e neutraliza handlers; reconexão por queda inesperada intacta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:54:24 +02:00
VPS 4 Builder 3042ddca38 feat(newwhats): frontend do plugin (Inbox/Sessions/Secretária) em tela cheia
- /wa-inbox, /wa-sessions e /wa-secretaria rodam sem a sidebar do scoreodonto
  (isStandaloneView); cada tela tem seu "Voltar" (Secretária ganhou botão na ThinNav).
- SessionsView: banner "Você já possui acesso ao WhatsApp" quando há sessão ativa.
- avatares: usa a URL do CDN (contactAvatarUrl/instance.avatar) direto; Avatar
  exibe sem depender de version; self-heal no onError re-busca a foto atual.
- deps: framer-motion, immer. Dev override com HMR (docker-compose.dev.yml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:54:24 +02:00
VPS 4 Builder 6593993b3d feat(newwhats): backend do satélite — proxy REST + túnel WS com chave mestra
- 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>
2026-07-01 20:52:18 +02:00
VPS 4 Builder 872acd800a fix(sidebar): itens WHATSAPP/SECRETÁRIA usam isPluginActive (ALWAYS_ON)
getActivePlugins() não inclui ALWAYS_ON_PLUGINS; o gate dos itens checava
activePluginIds.includes('newwhats') (sempre falso). Troca para isPluginActive
('newwhats'), que considera ALWAYS_ON — agora os menus aparecem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:14:59 +02:00
VPS 4 Builder 13af039555 feat(newwhats): pareamento por TOKEN — cole 1 token e auto-configura
- 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>
2026-06-28 19:05:13 +02:00
VPS 4 Builder 9874eb4345 docs(newwhats): ponte de config concluída (DB-backed + UI dropdown + testar)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:57:30 +02:00
VPS 4 Builder abbd9374b4 feat(newwhats): config DB-backed + UI dedicada (dropdown de clínicas, testar conexão)
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>
2026-06-28 18:55:54 +02:00
VPS 4 Builder 3751d84da8 feat(nginx): rota /nw/* -> backend (Secretária do motor consulta a clínica)
O motor NewWhats chama /nw/* no satélite (dados da clínica). nginx só roteava
/api/* ao backend; adiciona location /nw/ -> scoreodonto-backend:8018.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:15:03 +02:00
VPS 4 Builder 776c25c3de docs(newwhats): frontend (plugin/views) + pendências (proxy auth, ponte de config)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:59:08 +02:00
VPS 4 Builder b031117925 feat(frontend): plugin NewWhats — Inbox + Secretária (gating por papel)
- PluginsView: registra o plugin 'newwhats' (config só no catálogo, exclusivo do
  superadmin via view 'plugins' — já gateada em isViewAllowed).
- pluginRegistry: 'newwhats' em ALWAYS_ON (ativação por plugin é local/localStorage
  e não propaga; ALWAYS_ON garante Inbox/Secretária para TODOS os usuários).
- App.tsx: views wa-inbox/wa-secretaria (ViewKey, rotas, render); gating
  isPluginActive('newwhats') libera a qualquer usuário.
- Sidebar: itens WHATSAPP + SECRETÁRIA IA quando o plugin está ativo.
- Novas views consomem o proxy /api/nw/v1/* (motor ext-api). Estágio 1: funcional
  (lista/thread/envio; ask da secretária), visual aproximado do motor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:57:46 +02:00
VPS 4 Builder b21fb0f159 feat(newwhats): satélite WhatsApp/Secretária IA adaptado ao odonto
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>
2026-06-28 17:38:36 +02:00
VPS 4 Builder bfba577080 feat(agenda): isolamento por setor (opt-in) — fecha vazamento horizontal intra-clínica
build-and-promote / build (push) Successful in 1m5s
build-and-promote / promote (push) Has been skipped
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>
2026-06-25 20:51:30 +02:00
VPS 4 Builder facf7b48c2 fix(agenda): blindagem multi-tenant — overbooking sem conta, race no PUT, privacidade e índices
build-and-promote / build (push) Successful in 1m2s
build-and-promote / promote (push) Has been skipped
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>
2026-06-25 16:45:01 +02:00
VPS 4 Builder d83e0c82b2 feat(ui): dropdowns viram modal com busca (SearchSelect) na Avaliação/GTO e Nova OS
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 55s
Mesmo padrão do seletor de protético, generalizado para os demais dropdowns:

- Componente SearchSelect (modal genérico: trigger + busca + lista filtrável por rótulo/hint).
- LancarGTO: Especialidade/Área, Produto/Prótese, Procedimento, Dentista e Convênio/Credenciada
  — todos os <select> convertidos (0 selects restantes).
- Nova OS (ProteseView): Produto e Dentista convertidos.
- Cotação (RFQ): input de busca na lista de laboratórios a convidar (continua multi-seleção).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.0.33
2026-06-25 07:29:49 +02:00
VPS 4 Builder e6d8b532e3 feat(protese): seletor de protético em modal (busca, favorito/padrão, vincular por e-mail)
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>
2026-06-25 07:18:32 +02:00
VPS 4 Builder 8353a9d897 feat(protese): identificação por Nome+Sobrenome com desambiguação de homônimos
O protético identifica o trabalho pelo nome do paciente (etiqueta, sem CPF — ele não atende
paciente). Quando dois trabalhos têm o mesmo nome+sobrenome, desambigua sem expor dado sensível:

- util pacienteLabel.ts (rotulosPaciente / homonimoAtivo): comparação normalizada
  (case/acento/espaço-insensitive). Em colisão: 1) diferencia pela ORIGEM (dentista, senão
  clínica) → "João Silva · Dr. Carlos"; 2) se mesma origem, nº sequencial estável por data.
- Bancada (ProteseView) e Painel do lab (LabHomeView/Próximas entregas): nome exibido já
  desambiguado em toda a fila — vale para OS nascidas na clínica ou no lab.
- Modal "Dar entrada": campo único trocado por Nome + Sobrenome(s); aviso em tempo real quando
  há homônimo ativo ("acrescente outro sobrenome para diferenciar"), informando a origem do
  conflito.

Validado em DEV (3 homônimos "João Silva": origem distinta e mesma origem → sequencial).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 07:03:24 +02:00
VPS 4 Builder 7c8c8fc0d2 feat(protese): fluxo do lado do protético — entrada de OS, baixa/cobrança e importar OS indicada
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 52s
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>
v1.0.32
2026-06-25 04:43:35 +02:00
VPS 4 Builder 7566c01159 fix(protese): painel do lab usa fechamento próprio em vez do financeiro de pacientes (corrige 403)
LabHomeView chamava GET /api/financeiro (capability 'financeiro', negada ao protético) só para o
KPI "faturamento do mês" → 403 no console ao abrir o painel do laboratório. Passa a usar
GET /api/protese/lab/fechamento (custo das OS entregues no mês), que é do escopo do lab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 04:22:45 +02:00
VPS 4 Builder 87411e1566 feat(protese): CRM financeiro do laboratório (origem, contato, ticket, fechamento mensal)
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>
2026-06-25 04:07:11 +02:00
VPS 4 Builder 29b5b38678 fix(protese): protético acessa o Perfil/Marketplace para se publicar no diretório
Sem isso, o protético recém-cadastrado ficava invisível: o item PROFISSIONAIS não aparecia no
workspace laboratório (plugin não ativo) e o contexto laboratório bloqueava a tela — então ele não
tinha como publicar o perfil no diretório e nenhuma clínica o encontrava para enviar trabalho.

- Sidebar: PROFISSIONAIS aparece sempre para dentista/biomédico/protético (eles divulgam perfil),
  mesmo sem o plugin ativado.
- App: contexto 'laboratorio' libera marketplace-profissionais + diretorio-profissionais.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:56:01 +02:00
VPS 4 Builder 361f3809a7 docs(verdade-hoje): produção em v1.0.31 (4673f3c)
build-and-promote / build (push) Successful in 1m0s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 01:04:20 +02:00
VPS 4 Builder 4673f3c606 feat(protese): cadastro de protético interno (sem conta) + carteira no link mágico
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 1m2s
- 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>
v1.0.31
2026-06-24 22:11:07 +02:00
VPS 4 Builder dc5a2d89ae feat(protese): agenda & coleta do laboratório (integrada à custódia)
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>
2026-06-24 18:11:24 +02:00
VPS 4 Builder 7671199420 docs(verdade-hoje): produção em v1.0.30 (6cd8c1f)
build-and-promote / build (push) Successful in 1m3s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:00:41 +02:00
VPS 4 Builder 6cd8c1f9e1 feat(protese): marketplace transacional fatia 4 — avaliação, satisfação na nota e selo verificado
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 1m5s
- 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>
v1.0.30
2026-06-24 17:56:35 +02:00
VPS 4 Builder c084ad553b docs(verdade-hoje): produção em v1.0.29 (cd89ca2)
build-and-promote / build (push) Successful in 1m4s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:03:13 +02:00
VPS 4 Builder cd89ca245c feat(protese): marketplace transacional fatia 3 — cotação (RFQ) multi-lab
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 58s
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>
v1.0.29
2026-06-24 14:43:56 +02:00
VPS 4 Builder 07d798c370 docs(verdade-hoje): produção em v1.0.28 (6a88995)
build-and-promote / build (push) Successful in 50s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 08:00:31 +02:00
VPS 4 Builder 6a88995146 feat(protese): marketplace transacional fatia 2 — solicitar prótese cria a OS direto
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 47s
"Solicitar prótese" no card do protético abre o SolicitarProteseModal (lab pré-selecionado):
produto do catálogo (ou livre) + paciente + dentista + dentes + prazo + obs → cria a OS via
createProteseOS (que já aceita lab do diretório, sem vínculo prévio). Confirmação com CTA p/ a
Bancada. Modal auto-contido reusando getLaboratorioProdutos/getDentistas/getPacientes/createProteseOS.

Validado em DEV: OS criada para lab do diretório (Coroa de Zircônia, status solicitado).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.0.28
2026-06-24 07:56:33 +02:00
VPS 4 Builder f5a6dc7ea0 docs(verdade-hoje): produção em v1.0.27 (b5d093d)
build-and-promote / build (push) Successful in 53s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 07:52:30 +02:00
VPS 4 Builder b5d093d2ce feat(protese): marketplace transacional fatia 1 — vitrine de catálogo do laboratório
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 49s
doc/MARKETPLACE-TRANSACIONAL-PROTESE: desenho do transacional (vitrine → solicitação → cotação
→ reputação), reusando catálogo/propostas/OS/score existentes.

Fatia 1: no card do protético no marketplace, "Ver catálogo de próteses" expande os produtos
(nome, preço, garantia) — reusa getLaboratorioProdutos (o catálogo já é acessível a quem está no
diretório, zero endpoint novo). Botão de contato vira "Solicitar prótese". A clínica passa a
decidir vendo serviços + preço + garantia + ★nota antes de contratar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.0.27
2026-06-24 07:47:08 +02:00
VPS 4 Builder 489d2753ee docs(verdade-hoje): produção em v1.0.26 (d357f3a)
build-and-promote / build (push) Successful in 47s
build-and-promote / promote (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 07:40:35 +02:00
VPS 4 Builder d357f3a2e8 feat(protese): Nota ScoreOdonto pública no marketplace de profissionais
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 55s
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>
v1.0.26
2026-06-24 07:28:39 +02:00