Workspace tipo='laboratorio' ganha menu próprio (PAINEL DO LAB + BANCADA),
isolado das telas clínicas — espelha o tratamento de tipo='sala'. lab-bancada
reaproveita ProteseView (modo bancada); lab-home é o novo LabHomeView (fila por
status, atrasadas, entregues e faturamento do mês a partir do que já existe).
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>
- FASE2-PROTETICO-AREA: Laboratório como workspace de 1ª classe (tipo='laboratorio'),
roteamento por laboratorio_id, área de gestão própria, financeiro espelhado.
- FASE2-IMPL-2.0-2.1: detalhamento de implementação (migrações, backfill, bancada,
receita espelhada, menu da área).
- ANALISE-AREA-RECEPCAO-TC: gap de permissões da recepção fundamentado em modelos
validados (Treatment Coordinator / RBAC / Lab Case Manager).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Os menus de locador (MINHAS SALAS) e locatário (ALUGAR SALAS) eram exibidos a
"todos menos superadmin", vazando para funcionário e paciente — papéis que não
são titulares de sala e não tomam decisões de locação. Passa a exibir apenas a
papéis que podem ser titulares (donosala/donoclinica/donoconsultorio/admin/
dentista/biomedico/protetico).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PAGINAS_RBAC não listava 'proteses' nem 'contratos', então essas páginas não
eram concedíveis por nenhum cargo (nem via "MARCAR TODAS"/GERENTE, que derivam
de TODAS_PAGINAS = flatMap de PAGINAS_RBAC). Resultado: era impossível um dono
dar acesso à área de Prótese a um funcionário pela interface.
Adiciona ambas ao grupo ATENDIMENTO e inclui proteses/lancar-gto nos templates
RECEPÇÃO e AUXILIAR (alinhado ao papel de Treatment Coordinator).
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>
- ClinicasView: botão '+ Nova clínica/consultório' + modal de criação,
gateado pelo papel da conta (donoclinica/donoconsultorio/admin).
- Corrige applyWorkspace destrutivo no onboarding: usa refreshWorkspaces
(acrescenta a unidade à lista, em vez de sobrescrever as existentes).
Antes não havia caminho de UI para criar uma 2ª clínica fora do
onboarding; o backend já suportava N clínicas. Validado em DEV (8020).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Alinha o documento ao git HEAD/produção atual. Mantém intactas as
menções históricas (validação em v1.0.16, colisão de tag v1.0.14).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Atualiza STATUS-SEGURANCA-E-PRODUCAO.md: cabeçalho (prod v1.0.19), nova
seção "Fila A concluída" (push → fix CI → build → tag/promote → backup →
verificação), seção A marcada como feita, commits agora publicados e
"Como retomar" com pendências restantes (B + A.2) e rollback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O compose interpola environment: (com fail-fast `:?` em SCOREO_DB_PASS)
mesmo só no `docker compose build`, e o runner não tem .env → build abortava
(run #29, commit 8a27f3b). SCOREO_DB_PASS é runtime-only (environment:, não
build-arg), então um placeholder satisfaz a interpolação sem entrar na imagem.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- STATUS-SEGURANCA-E-PRODUCAO.md: a rotação do JWT_SECRET deixa de ser
atribuída ao commit fcf2fb1 (que só remove .env/adiciona .dockerignore) —
passa a constar como ação operacional na VPS, sem commit (segredo não
versionado). Ajusta a contagem: 13 commits da sessão (inclui este doc),
repo ahead 14 de origin/main (o 14º, docs/LEIA, é anterior à sessão).
- dev-build.sh: builda o ambiente de DEV injetando APP_VERSION/GIT_COMMIT/
BUILD_TIME a partir do git (git describe), espelhando o deploy-prod.sh.
/api/version em DEV deixa de mostrar apenas "dev".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- documentação/MAPA-FUNCIONAL-COMPLETO.md: inventário de rotas/telas/botões/fluxos,
matriz de módulos, mapa de conexões (mermaid) e notas — baseado em evidências do código.
- documentação/AUDITORIA-ESTRATEGICA-MODELAGEM.md: análise Pessoa→Vínculo→Workspace,
entidades mal classificadas e riscos arquiteturais.
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>
Refatora a autorização: extrai capabilityCore; requireCapability passa a derivar o
clinicaId da requisição (param/body/query) quando não há tenantGuard; adiciona
requireCapabilityRow(cap, tabela) p/ rotas /:id cujo clinicaId vem da LINHA.
Gateia 12 rotas (cap por cargo, espelhando o frontend):
- especialidades/procedimentos: POST + PUT/:id + DELETE (dono OU biomédico)
- planos: POST + PUT/:id + DELETE (dono)
- dentistas: POST + PUT/:id + DELETE/:id (dono)
GETs de catálogo seguem abertos (dados de referência usados em toda a app).
Corrige também risco pré-existente: POST/PUT de catálogo usavam authGuard SEM validar
vínculo (escrita cross-tenant) — agora validam vínculo + cargo.
Deferido: rotas /reorder (sem clinicaId/:id; baixo impacto) e horários de dentista (grupo agenda).
Validado em DEV: dono 404(fake)/dentista 403; GETs 200; negação por linha sem apagar dados.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aplica requireCapability('gestao-equipe') (dono-only por padrão; ou cargo com a
permissão) APÓS tenantGuard em 12 rotas: membros, setores e funcoes (GET + mutações).
- reset-senha: troca authGuard → tenantGuard + requireCapability (antes não validava
o vínculo do solicitante; só hierarquia).
- PUT /clinicas/:id/color: eleva de 'tem vínculo' para 'dono/admin' (igual a /nome).
- nome, PUT /clinicas/:clinicaId e PUT /areas já tinham checagem de dono (mantidos).
- GET /clinicas/:clinicaId (branding) segue legível por qualquer membro (por design).
Checagens de hierarquia (nivelNaClinica) preservadas como defesa secundária.
Validado em DEV: dono ok; dentista 403 (inclui /color); outra clínica 403.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adiciona requireCapability(cap) — espelha o isViewAllowed do frontend
(superadmin global; dono donoclinica/donoconsultorio/admin; cargo com
permissoes explícitas da funcao; senão mapa padrão por papel) — e aplica
APÓS o tenantGuard em 22 rotas:
- financeiro: GET/POST/PUT/DELETE + relatorio (cap 'financeiro')
- glosas: listar/itens/recebiveis/criar/recorrer/recuperar/excluir (cap 'glosas')
- comissoes: regras GET/PUT/DELETE + relatorio (cap 'comissoes')
- repasses: fechar/listar/detalhe/pagar/cancelar/comprovante (cap 'comissoes')
/repasses/comprovante/:id/raw fica de fora (URL assinada, sem tenantGuard).
Antes: bastava ter vínculo (tenantGuard) p/ chamar a API — UI escondia, backend não.
Validado em DEV: dono 200; dentista 403; outra clínica 403 (isolamento intacto).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /update (migração de banco) sai da lista sempre-liberada do isViewAllowed;
passa a exigir superadmin (não-superadmin cai na view padrão; deslogado vai p/ landing).
- ROUTE_MAP ganha /comissoes e /glosas (existiam em VIEW_TO_ROUTE mas faltavam aqui;
refresh/deep-link nessas telas caía em dashboard).
- (cadastro-dentista já estava mapeado — correção de equívoco da auditoria.)
- Validado em DEV: build compila, app sobe, rotas resolvem (HTTP 200 SPA).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Senha do banco deixa de ter default no compose: vem de SCOREO_DB_PASS (.env raiz
em DEV; provisionar fora do git em PROD). `:?` faz o up falhar se faltar (fail-fast).
- DRAGONFLY_URL (com senha) sai do compose e passa a vir do env_file backend/.env.
- Host/usuário/banco mantêm default (não são segredo).
- Validado em DEV: db (pgdev) ok, cache ok, login ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- backend/.env deixa de ser versionado (continha JWT_SECRET, DATABASE_URL,
DRAGONFLY_URL e chaves de IA); arquivo preservado em disco (dev e prod).
- backend/.dockerignore: impede que segredos sejam copiados para a imagem
(Dockerfile usa COPY . . e não havia .dockerignore).
- .gitignore reforçado (**/.env, .secrets, *.dump, backups de env).
- backend/.env.example documenta as variáveis sem valores.
- JWT_SECRET rotacionado em dev e prod (fora do git).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Público, sem auth. Checa Postgres (crítico → HTTP 503 se down) e Dragonfly/Redis
(opcional → "degraded" mas 200; "disabled" se cache off). Inclui version/commit/
environment (de BUILD_INFO) e uptime_s. Sem vazar mensagens de erro.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- VERDADE-HOJE.md e DEPLOY-PRODUCAO.md: produção v1.0.16 (commit 471c2c2);
tag promove por re-tag SEM rebuild (digest :v1.0.16 == :471c2c2, provado);
versão nasce da tag; constants.ts/update-version.js removidos. Passo 5 ✅.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- build.yml dividido em 2 jobs por ref:
• build (só push na main): builda 1x e publica por COMMIT (:<sha> + :latest), sem APP_VERSION.
• promote (só tag vX.Y.Z): re-tagueia :<sha> -> :vX.Y.Z via 'docker buildx imagetools create'
(SEM rebuild), valida integridade (imagem do commit existe; digest :versao == :commit;
front+back do mesmo commit) e deploya na VPS1. Falha se o commit não foi pushado.
- Versão semântica nasce da TAG e é injetada no deploy em runtime (compose.prod).
- Removidos frontend/constants.ts e update-version.js (fim do APP_VERSION manual).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pasta única scoreodonto.com/doc/ com 9 docs que prestam:
VERDADE-HOJE (estado, principal), DEPLOY-PRODUCAO (runbook), BANCO-DEV-ESTRATEGIA,
REGISTRY, AUDITORIA-FUNCIONAL, BACKLOG-FINANCEIRO-V1, CRM_Ortodontia, RX_ARQUITETURA, RX_BACKLOG.
- Removidos os docs de infra sobrepostos/desatualizados (estado consolidado em VERDADE-HOJE;
histórico fica no git): arquitetura.md, deploy.md, CHECKLIST-DEPLOY-PRODUCAO.md,
PENDENCIAS.md, PENDENCIAS_E_DEPLOY.md.
- Pasta documentação/ eliminada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- VERDADE-HOJE.md: reescrito p/ a verdade de 17/jun — banco DEV isolado (pgdev, não
compartilha mais), produção roda imagem v1.0.15, versão em runtime via /api/version.
Declarado documento principal (prevalece em divergência).
- deploy.md: §5 rollback ✅ (existe); §6 corrigida — remove o "não há CI/registry/deploy"
(era falso/contraditório); CI/CD por tag ATIVO, falta só o Passo 5.
- DEPLOY-PRODUCAO.md: status 🧪→✅ dos Passos 1/3/4 (promovidos em v1.0.15).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- backend: tabelas protese_os/_evento/_foto/_produtos + endpoints /api/protese/*
(laboratorios, os, bancada, status, foto/raw assinado, produtos); catálogo do protético;
custo de lab → DESPESA na entrega (idempotente, estorno no delete) + garantia com foto.
- Acesso: helper proteticoAcessivelPelaClinica (interno por vínculo OU diretório) valida o POST
e o catálogo; catálogos passam por tenantGuard; correção do label da notificação (tipo resolvido).
- Frontend: ProteseView (Bancada do protético / Minhas OS da clínica) + rota /proteses
+ item de menu PRÓTESES no Sidebar (dentista/protético/funcionário).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- buildInfo.ts: hook useAppVersion() consome GET /api/version (fonte única = backend),
com fallback ao carimbo do bundle; deixa de depender do APP_VERSION manual.
- Versão exibida em Configurações ("Sobre o sistema") + telas públicas (Login/WaitingInvite);
removida do rodapé do Sidebar.
- compose.prod/deploy-prod.sh: injetam APP_VERSION (=tag) e APP_ENV=PROD em runtime,
desacoplando a versão semântica do build (commit/build seguem da imagem).
- constants.ts marcado legado + bump V1.0.15 (transitório até o Passo 5; evita colisão de tag).
- DEPLOY-PRODUCAO.md reescrito com status ✅/🧪/🎯 + nota de fase transitória.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- remove z-[1002] do wrapper sempre-presente do sino (furava modais z-50..z-120)
- z aplicado só ao dropdown aberto (z-50) + backdrop (z-40)
- doc: RX apontado ao banco dev isolado
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DATABASE_URL parametrizado (${SCOREO_DB_*}); default mantém produção (VPS1) intacta
- server.js: não força SSL p/ host @pgdev (container dev local); SSL segue p/ VPS3
- .gitignore: ignora .env (override local de dev)
- doc: status de implementação do banco DEV isolado
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- VERDADE-HOJE.md: deploy/rollback/containers/versão refletem CI/CD por tag + registry
- DEPLOY-PRODUCAO.md: fluxo canônico = deploy por tag; manual vira fallback
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- build: fixa COMPOSE_PROJECT_NAME=scoreodontocom (imagem buildada batia com nome errado no tag/push → carimbo da Etapa 3 vazava)
- novo job deploy: só em tags v*, SSH VPS4->VPS1 roda deploy-prod.sh (push na main não deploya)
- deploy.md: Metade B + roadmap Etapa 6 = FEITO
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docker-compose.prod.yml: produção roda imagens versionadas do registry (sem build)
- deploy-prod.sh <tag>: pull da tag + up -d --no-build + verificação /api/version + rollback
- deploy.md: Metade A (CI) marcada como FEITA
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.gitea/workflows/build.yml — em push na main + tags v*, builda backend+frontend
com carimbo de versão e publica no registry (sem deploy). Login tolerante
(secret REGISTRY_TOKEN se existir; senão credencial do host). Retry p/ 499.
Runner self-hosted 'vps4-builder' (modo host) registrado na VPS4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- REGISTRY.md: login, convenção de nomes, build+push, pull/rollback, gotcha do 499
- arquitetura.md/deploy.md: registry agora ativo (git.clube67.com), não mais 'não existe'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- backend: endpoint GET /api/version + linha [VERSION] no boot (le APP_VERSION/GIT_COMMIT/BUILD_TIME/APP_ENV)
- frontend: buildInfo.ts le import.meta.env.VITE_* ; versão no rodapé do Sidebar
- Dockerfiles (front+back): ARG/ENV do carimbo de versão
- docker-compose.yml: build args (APP_VERSION/GIT_COMMIT/BUILD_TIME/APP_ENV) com defaults
- bump V1.0.12 -> V1.0.13
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>