Files
comparador-notas/ANALISE_DUPLICIDADE_LAYOUT.md
T

7.4 KiB

Análise — Duplicidade de Layouts ao Salvar

Data: 03/03/2026
Status: Análise / Proposta de Correção


1. Problema Identificado

Ao clicar em "💾 Salvar" na tela de gerenciamento de layouts, o sistema permite salvar um layout com um nome já existente, criando entradas duplicadas no banco sem qualquer aviso ou confirmação ao usuário.

O PRD (RF03) exige que, ao existir conflito de nome, o sistema pergunte ao usuário se deseja sobrescrever o existente ou salvar com novo nome. Esse comportamento está implementado apenas no fluxo de importação JSON, mas está ausente no fluxo de salvar layout atual.


2. Raiz do Problema — Análise por Camada

2.1 Camada Infrastructure — sqlite/migrations.rs

A tabela layouts foi criada na migration_v1 sem nenhuma constraint UNIQUE no campo nome:

-- migration_v1 (src/infrastructure/sqlite/migrations.rs)
CREATE TABLE IF NOT EXISTS layouts (
    id    INTEGER PRIMARY KEY AUTOINCREMENT,
    nome  TEXT NOT NULL,   -- ← sem UNIQUE
    tipo  TEXT NOT NULL CHECK(tipo IN ('csv', 'xlsx')),
    ...
);

O banco de dados não impede, por si só, a inserção de registros com o mesmo nome.


2.2 Camada Infrastructure — sqlite/layout_repository.rs

A função salvar executa um INSERT direto, sem qualquer verificação prévia:

// src/infrastructure/sqlite/layout_repository.rs — fn salvar()
conn.execute(
    "INSERT INTO layouts (nome, tipo, ...) VALUES (?1, 'csv', ...)",
    params![nome, ...],
)?;

Existe a função existe_nome no mesmo arquivo, que verifica duplicidade por consulta, mas ela não é chamada pelo fluxo de salvar layout atual — apenas pelo fluxo de importação JSON.


2.3 Camada Application — usecases/layouts.rs

A função salvar_layout só verifica se o id está presente:

// src/application/usecases/layouts.rs — fn salvar_layout()
if let Some(id) = layout.id() {
    layout_repository::atualizar(conn, layout)   // atualiza se tem id
} else {
    layout_repository::salvar(conn, layout)       // insere direto sem checar nome
}

A verificação de nome duplicado (existe_nome) está implementada em importar_layout_json, mas não foi replicada em salvar_layout.


2.4 Camada UI — ui/screens/layouts.rs

A função salvar_layout_atual monta o layout sempre com id: None e chama salvar_layout sem tratar o caso de conflito de nome:

// src/ui/screens/layouts.rs — fn salvar_layout_atual()
let layout = match app.tipo_arquivo_atual.clone() {
    TipoArquivo::Csv => Layout::Csv {
        id: None,   // ← sempre None, nunca verifica se nome já existe
        nome: app.nome_layout_atual.trim().to_string(),
        config: app.layout_csv_atual.clone(),
    },
    ...
};

match salvar_layout(conn, &layout) {
    Ok(_) => { ... }  // ← não há tratamento para conflito de nome
    Err(e) => { ... }
}

O fluxo de resolução de conflito (sobrescrever / salvar com novo nome) existe apenas em importar_json, que trata ErroLayout::NomeConflitante, mas mesmo nesse caso o tratamento está incompleto (há um TODO no código).


3. Resumo dos Pontos Falhos

# Localização Problema
1 sqlite/migrations.rsmigration_v1 Nenhum UNIQUE constraint em nome na tabela layouts
2 usecases/layouts.rssalvar_layout Não consulta existe_nome antes de inserir
3 domain/errors.rs Falta variante de erro para duplicidade no fluxo de salvar (apenas existe em importar JSON)
4 ui/screens/layouts.rssalvar_layout_atual Não trata conflito de nome; não exibe opção de sobrescrever ou renomear
5 ui/screens/layouts.rsimportar_json Tratamento de NomeConflitante incompleto (TODO pendente)

4. Proposta de Correção

4.1 Migration v2 — Adicionar UNIQUE em nome

Arquivo: src/infrastructure/sqlite/migrations.rs

Adicionar uma segunda migration que cria uma UNIQUE constraint na coluna nome. Atualizar VERSAO_SCHEMA_ATUAL de 1 para 2 e chamar migration_v2 no bloco condicional de aplicação de migrations.

-- migration_v2
CREATE UNIQUE INDEX IF NOT EXISTS idx_layouts_nome ON layouts (nome);

Usar um índice único em vez de recriar a tabela é mais seguro, pois preserva dados já existentes. Bancos que já possuem duplicatas precisariam de tratamento antes da migração (ex.: renomear automaticamente os conflitantes com sufixo numérico).


4.2 Retornar Erro Tipado de Conflito em salvar_layout

Arquivo: src/application/usecases/layouts.rs — função salvar_layout

Antes da inserção, verificar se já existe layout com o mesmo nome usando layout_repository::existe_nome. Retornar um erro tipado (ex.: ErroLayout::NomeConflitante) em vez de uma String quando o nome já existir e nenhum id foi fornecido.

Fluxo esperado:

1. layout.id() é None (novo layout)
2. Consultar existe_nome(conn, layout.nome())
3. Se existir → retornar Err(ErroLayout::NomeConflitante(nome))
4. Se não existir → prosseguir com INSERT

A assinatura da função deverá mudar de Result<i64, String> para Result<i64, ErroLayout> para que o chamador consiga distinguir o tipo de erro.


4.3 Tratar Conflito na UI — Fluxo de Sobrescrever ou Renomear

Arquivo: src/ui/screens/layouts.rs — função salvar_layout_atual

Ao receber Err(ErroLayout::NomeConflitante(nome)), a UI deve exibir um modal de confirmação com duas opções:

  • Sobrescrever — buscar o id do layout existente pelo nome, montar o layout com esse id e chamar salvar_layout novamente (o use case irá para o branch atualizar)
  • Cancelar — descartar a operação sem salvar

A opção "Salvar com novo nome" pode ser considerada, porém aumenta a complexidade da tela. Para a correção mínima, sobrescrever ou cancelar já atende ao PRD.

O estado da App pode precisar de um campo intermediário para guardar o layout pendente de confirmação entre frames do egui, ou se o sistema já possui um mecanismo de AcaoModal, criar uma variante AcaoModal::ConfirmarSobrescritaLayout(Layout) e processar no handler de ações do modal.


4.4 Completar o TODO em importar_json

Arquivo: src/ui/screens/layouts.rs — função importar_json

O bloco Err(ErroLayout::NomeConflitante(nome)) contém um TODO que não implementa o fluxo completo. Após a correção do item 4.3, a mesma lógica de modal de confirmação deve ser reutilizada aqui, chamando importar_layout_json com sobrescrever_se_existir: true quando o usuário confirmar.


5. Ordem de Implementação Sugerida

1. migration_v2 (constraint UNIQUE) ............ infrastructure/sqlite/migrations.rs
2. Erro tipado em salvar_layout ................. application/usecases/layouts.rs
3. Modal de conflito na UI (salvar atual) ....... ui/screens/layouts.rs
4. Modal de conflito na UI (importar JSON) ...... ui/screens/layouts.rs  (completar TODO)

6. Impacto da Correção

Aspecto Antes Depois
Layouts duplicados no banco Possível Bloqueado pelo banco (UNIQUE)
Feedback ao usuário Nenhum — salva silenciosamente Modal com escolha: sobrescrever ou cancelar
Conformidade com PRD (RF03) Parcial (apenas importação JSON) Completa em todos os fluxos
Importação JSON com conflito TODO incompleto Resolvido com mesma lógica