Files
scoreodonto.com/doc/MODO1-LINK-SEGURO-PROTESE.md
VPS 4 Builder 4673f3c606
build-and-promote / build (push) Has been skipped
build-and-promote / promote (push) Successful in 1m2s
feat(protese): cadastro de protético interno (sem conta) + carteira no link mágico
- 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>
2026-06-24 22:11:07 +02:00

7.6 KiB

Modo 1 — Provedor de Prótese SEM conta (link seguro /p/TOKEN)

Desenho (24/jun/2026). Evolução registrada como "fora de escopo" em PROVEDOR-PROTESE-DECISAO.md. Princípio (decisão arquitetural): a clínica opera 100% da OS; o protético não precisa ter conta. O link é a porta de entrada do ecossistema — acompanha hoje, vira conta amanhã.

Objetivo

A clínica cria/gere a OS normalmente. O protético recebe um link seguro (WhatsApp/e-mail) e, sem login, acompanha e atua na OS dele. Com CTA para criar conta grátis (conversão).

Clínica cria OS ──► gera link /p/TOKEN ──► WhatsApp/e-mail ──► Protético abre (sem conta)
                                                                  ├─ vê a OS (sem CPF/valores do paciente)
                                                                  ├─ confirma recebimento/devolução
                                                                  ├─ avança etapa / anexa foto
                                                                  └─ responde ocorrência
                                                          "Crie sua conta grátis" ──► cadastro (card Protético)

1. Escopo do token: por OS (não por protético)

Escopo mínimo = menor superfície de risco. Um token dá acesso a uma OS. Vantagens: revogável individualmente, expira por trabalho, não expõe a carteira inteira do protético. (Evolução: um token "carteira" por protético-na-clínica que lista várias OS — fica para depois.)

2. Modelo de dados (revogável e auditável)

CREATE TABLE IF NOT EXISTS protese_os_link (
    token TEXT PRIMARY KEY,            -- aleatório longo (≥ 32 bytes base62), NÃO sequencial
    os_id TEXT NOT NULL,
    protetico_id TEXT,                 -- a quem se destina (rastreio)
    created_by TEXT,                   -- quem na clínica gerou
    created_at TIMESTAMPTZ DEFAULT NOW(),
    expires_at TIMESTAMPTZ,            -- opcional (ex.: +30 dias); null = sem expirar
    revoked_at TIMESTAMPTZ,            -- revogação manual
    last_access_at TIMESTAMPTZ,
    acessos INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_protese_os_link_os ON protese_os_link (os_id);

Preferir tabela a JWT puro: permite revogar e auditar acessos. O token nunca contém dados.

3. Rotas públicas (sem authGuard; o token é a credencial)

Todas resolvem os_id pelo token (valida não-revogado, não-expirado) e aplicam a mesma blindagem do provedor: sem paciente_id/CPF, sem valores do paciente (reusa a lógica soLab).

Rota Ação
GET /api/p/:token OS: nº, clínica, dentista, paciente (nome, conforme permissão), prazo, status, observações, fotos, componentes, ocorrências, custódia. Sem CPF/valores do paciente.
POST /api/p/:token/custodia confirmar recebimento / devolução (clínica↔lab)
POST /api/p/:token/status avançar a produção (recebido→…→finalizado)
POST /api/p/:token/foto anexar foto do trabalho (+ ocorrência)
POST /api/p/:token/ocorrencias/:id/resposta responder ocorrência (contraditório)

Cada ação registra no histórico da OS um actor "Protético (link)" com o protetico_id do token — mantendo a auditoria. Mesmas validações de status/foto já existentes.

4. Geração e envio (lado clínica)

Na OS (modal, aba Etapas ou Monitoramento): botão "Enviar ao protético"POST /api/protese/os/:id/link gera/recupera o token e devolve a URL https://scoreodonto.com/p/TOKEN. Atalhos: WhatsApp (https://wa.me/?text=…) e e-mail (mailto:). Botão "Revogar link" para invalidar.

5. Frontend público /p/:token

Rota fora do app autenticado (sem Sidebar/login). Página enxuta que reusa as seções do OSDetailModal em modo "público/lab" (etapas, fotos, componentes, custódia, ocorrências — sem financeiro/clientes). Topo com a marca + rodapé fixo:

"Gostou de acompanhar por aqui? Crie sua conta grátis e gerencie todos os seus laboratórios." → leva ao cadastro já com o card Protético (adicionado) e e-mail pré-preenchido.

6. Segurança (checklist)

  • Token aleatório ≥ 32 bytes, base62, não sequencial; comparação em tempo constante.
  • Escopo de 1 OS; expiração opcional; revogável; rate-limit por token/IP.
  • Sem CPF e sem valores do paciente (mesma blindagem soLab da Fase 1).
  • Ações registram actor no histórico (auditoria); last_access_at/acessos para detectar abuso.
  • OS entregue/cancelado → link vira somente leitura.

7. Conversão (o "porquê de negócio")

O link é aquisição: o protético experimenta o produto sem fricção e, ao criar conta, herda o histórico (as OS daquela clínica já estão ligadas ao protetico_id dele). Liga direto ao marketplace/score (Fase 5) — quanto mais clínicas o usam, mais OS e melhor o score do protético.

8. Faseamento sugerido

  • 1a (núcleo) — IMPLEMENTADA 24/jun: tabela protese_os_link (token aleatório 24 bytes base64url, expira em 60 dias, revogável); POST /api/protese/os/:id/link (gera/recupera) + POST …/link/revogar; GET /api/p/:token (leitura pública curada — sem CPF, sem valor do paciente; conta acessos). Página pública /p/:token (ProtesePublicView, mobile-first 320px+, interceptada no index.tsx antes do app autenticado); botão "Gerar link / WhatsApp / Copiar" na aba Monitoramento (modo clínica). CTA "Criar conta grátis". Validado em DEV (leitura, blindagem, token inválido→404, revogação→404).
  • 1b (ação) — IMPLEMENTADA 24/jun: o protético, pelo link, confirma recebimento/devolução (POST /api/p/:token/custodia), avança a produção (/status, só recebido…finalizado — nunca entregue/cancelado, que são da clínica) e anexa foto (/foto). Tudo auditado como "<nome> (link)". Página pública ganha a seção "Atualizar status" (botões de custódia, etapa e foto) enquanto a OS não está finalizada. Validado em DEV: recebimento, avanço, foto, e entregue pelo link → 400 (bloqueado).
  • 1c (gestão) — IMPLEMENTADA 24/jun: UI da clínica (aba Monitoramento) gere o link: GET /api/protese/os/:id/link/status traz link ativo, nº de acessos, último acesso e expiração; botões Copiar / WhatsApp / Revogar. Validado em DEV: acessos contados, expiração 60 dias, revogação → status inativo + leitura pública 404.

    Conversão por herança: nativa — a OS já aponta para o protetico_id do destinatário, então ao entrar/criar conta ele já é dono do histórico. (Vincular um cadastro NOVO com e-mail diferente às OS é caso de borda — fica para evolução, evitando duplicar usuário.)

Modo 1 concluído (1a leitura + 1b ação + 1c gestão).

Extras (24/jun)

  • Carteira pela link (GET /api/p/:token/carteira): botão "Meus trabalhos" na página pública → trabalhos em andamento + finalizados + mini financeiro mensal (valores pagos ao protético). Escopo = a relação clínica↔protético da OS do token (não vaza outras clínicas; sem dados do paciente).
  • Protético INTERNO (sem conta) (POST /api/protese/proteticos-internos): a clínica cadastra um protético que não usa a plataforma — cria um usuário "mudo" (e-mail placeholder ..@interno.local, senha-fantasma; usuarios.email é NOT NULL/UNIQUE) + vínculo interno (fora do diretório). Botão "Cadastrar protético interno" na Nova OS. A partir daí recebe OS e acompanha pelo link, sem conta.

Nenhuma refatoração: reusa OS/eventos/custódia/ocorrências/blindagem já existentes. Só adiciona a camada de token público.