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

118 lines
7.6 KiB
Markdown

# 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)
```sql
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.