# 4 — Modelo de dados DDL completo em `sql/schema.sql`. Aqui só o raciocínio. ## Hierarquia ``` estudio (inquilino) └── usuario equipe, com login └── projeto ├── imagem o ESPAÇO: "Hall — recepção". Estável entre versões. │ └── versao arquivo daquela imagem numa rodada: v1, v2, v3 ├── rodada ciclo de revisão, com fase e prazo ├── revisor pessoa do lado do cliente, sem conta └── comentario preso a uma versão, com pino opcional ``` ## As três escolhas que importam **Imagem e versão são tabelas separadas.** A imagem é o espaço conceitual, o que o cliente chama de "a recepção". A versão é o arquivo. Sem essa separação não dá para comparar v1 com v2 nem migrar comentário entre versões, porque não existiria a noção de "a mesma imagem". **Comentário aponta para versão, não para imagem.** Um comentário nasceu olhando um arquivo específico, e o pino em 62%, 48% só faz sentido naquele enquadramento. Migração para a versão seguinte é cópia explícita com `herdado_de_id`, nunca reaproveitamento da mesma linha. **Pino em porcentagem, não em pixel.** `DECIMAL(5,2)` de 0 a 100. Sobrevive a mudança de resolução entre versões e não depende do nível de zoom em que foi criado. ## Autoria de comentário `revisor_id` e `usuario_id`, um dos dois nulo, com `CHECK` garantindo pelo menos um. Evita uma tabela de "pessoas" que misturaria quem tem senha com quem não tem — são coisas diferentes, com ciclos de vida diferentes. ## Isolamento `estudio_id` está em toda tabela de negócio, inclusive nas que já poderiam derivá-lo por join. É desnormalização proposital: permite filtrar sem join, e torna óbvio numa revisão de código quando o filtro faltou. O valor vem sempre da sessão, nunca da requisição. ## Migração de comentários entre versões Ao criar a v(n): ```sql INSERT INTO comentarios (estudio_id, versao_id, rodada_id, revisor_id, usuario_id, texto, pin_x, pin_y, origem, status, herdado_de_id) SELECT c.estudio_id, :nova_versao_id, :nova_rodada_id, c.revisor_id, c.usuario_id, c.texto, c.pin_x, c.pin_y, c.origem, 'pendente', c.id FROM comentarios c WHERE c.versao_id = :versao_anterior_id AND c.estudio_id = :estudio_id AND c.status IN ('pendente','execucao'); ``` `resolvido` e `nao_aplica` ficam para trás. A cadeia `herdado_de_id` conta a história de um apontamento que atravessou várias rodadas — útil para mostrar ao cliente que algo foi pedido três vezes. ## Fase da rodada como guarda `rodadas.fase` não é enfeite de interface, é a regra de autorização: | fase | cliente escreve | equipe lê comentário | equipe escreve | |---|---|---|---| | rascunho | não | não | não | | cliente | **sim** | **não** | não | | fechada | não | não | não | | equipe | não | sim | **sim** | | concluida | não | sim | não | Precisa ser verificada no servidor, em toda rota. Esconder na interface não é suficiente. ## Auditoria `eventos` registra abertura e fechamento de fase, publicação de versão, regeneração de token, e a conclusão de cada revisor. É o que responde "quando exatamente o prazo fechou" numa discussão contratual. Só grava, nunca altera.