| Data de elaboração | 08/04/2025 |
| Responsável pelo estudo | João Vitor Paulino Nobre |
| Equipe do estudo | Tropa de Elite |
| Alvo | SOLAR |
| Origem | Objetivo estratégico: Estudo para definição de estratégia de correção de identificadores duplicados de empreendimentos. |
| Objetivo | Elaborar uma estrategia tecnica para solução de problema de identificadores de empreendimentos duplicados |
Por padrão arquitetural, todo Empreendimento recém-registrado recebe um identificador único e sequencial gerado pelo método GerarIdentificador. O formato estrito é S.{ano}.{sequência} (exemplo: S.2022.5).
Este código representa o empreendimento físico em si, e não o seu proprietário legal. Portanto, é uma entidade que deve persistir inalterada mesmo após mudanças de propriedade. A causa raiz das recentes duplicidades foi mapeada para uma vulnerabilidade na estratégia de geração destes códigos, que dependia da ordem global de inserção no banco de dados em vez de uma busca isolada por ano.

Para compreender a falha mecânica e as ramificações das tentativas de correção, detalhamos abaixo a arquitetura da funcionalidade, o erro lógico no código e os impactos diretos nos dados.
A funcionalidade de alteração de titularidade transfere a posse de um empreendimento através de uma operação de clonagem (clone operation):
Ativo = false.Ativo = true.Importante: Por design, ambas as linhas compartilham intencionalmente o mesmo Identificador. A flag Ativo atua como o discriminador exclusivo que define qual registro é o vigente.
A implementação original do método GerarIdentificador (localizado em src\Dominio\CadastroContexto\Empreendimento.cs) possuía uma limitação de escopo na consulta:
Id.1.O comportamento imprevisto: A divergência ocorria quando uma troca de titularidade acontecia entre anos. Como a clonagem insere uma nova linha carregando o identificador do ano anterior, esse clone tornava-se a «última linha inserida por Id». Ao cadastrar um novo empreendimento no ano novo, o sistema lia o ano anterior desse clone, detectava a divergência de anos e reiniciava a sequência para 1. Caso já existisse um registro S.{anoAtual}.1 legítimo cadastrado previamente, o sistema gerava uma duplicata.
A correção já aplicada no sistema abandona a busca global pela última linha inserida.
CorrigirOsEmpreendimentosComIdentificadoresDuplicados)Um script de correção foi introduzido e executado em 19 de março de 2024 com o intuito de reparar as colisões de identificadores.
O Contexto da Tabela Unificada e a Volumetria do Impacto: A tabela de Empreendimentos é um repositório histórico unificado, onde registros de múltiplos anos convivem no mesmo espaço. Ao iterar sobre essa tabela com regras de negócio genéricas, o script alterou o estado de 275 registros. A distribuição reflete a data de execução da rotina: a maior parte ocorreu no ano de 2023 (249 registros), seguido pelo ano de 2024 (21 registros), e casos pontuais em 2022 (5 registros).
O comportamento imprevisto do patch ocorreu devido a particularidades do design de domínio que não foram mapeadas na consulta inicial:
Para ilustrar a falha de escopo e a correção necessária, abaixo está o comparativo entre a abordagem anterior e a nova lógica proposta para 2025:
Código Anterior (Executado em 19 de março de 2024):
public class CorrigirOsEmpreendimentosComIdentificadoresDuplicados
{
private readonly SolarContexto contexto;
public CorrigirOsEmpreendimentosComIdentificadoresDuplicados(SolarContexto contexto)
{
this.contexto = contexto;
}
public async Task Executar()
{
await CorrigirIdentificador(2022);
await CorrigirIdentificador(2023);
await CorrigirIdentificador(2024);
await contexto.SaveChangesAsync();
}
private async Task CorrigirIdentificador(int ano)
{
var empreendimentos = await contexto.Empreendimentos
.Where(x => x.Identificador.Contains($"{ano}"))
.ToListAsync();
for (var i = 0; i < empreendimentos.Count; i++)
{
var novoIdentificador = $"S.{ano}.{i + 1}";
var identificadorAtual = empreendimentos[i].Identificador;
if (identificadorAtual == novoIdentificador) continue;
empreendimentos[i].CorrigirIdentificador(novoIdentificador);
}
}
}
Solução Proposta para 2025:
public record RegistroDeCorrecao(int Id, int EmpreendedorId, string IdentificadorAnterior, string IdentificadorNovo);
public class CorrigirEmpreendimentosDuplicados(SolarContexto contexto)
{
private readonly SolarContexto contexto = contexto;
public async Task<IReadOnlyList<RegistroDeCorrecao>> Executar(int ano)
{
return await CorrigirIdentificador(ano);
}
private async Task<IReadOnlyList<RegistroDeCorrecao>> CorrigirIdentificador(int ano)
{
var prefixo = $"S.{ano}.";
var empreendimentosAtivos = await contexto.Empreendimentos
.Where(x => x.Ativo && x.Identificador.StartsWith(prefixo))
.OrderBy(x => x.Id)
.ToListAsync();
var duplicados = empreendimentosAtivos
.GroupBy(x => x.Identificador)
.Where(g => g.Count() > 1)
.SelectMany(g => g.OrderBy(x => x.Id).Skip(1))
.OrderBy(x => x.Id)
.ToList();
if (duplicados.Count == 0)
return [];
var maiorNumeroSequencial = empreendimentosAtivos
.Select(x => int.Parse(x.Identificador.Split('.')[2]))
.Max();
var proximoNumero = maiorNumeroSequencial + 1;
var registros = new List<RegistroDeCorrecao>();
foreach (var empreendimento in duplicados)
{
var identificadorAnterior = empreendimento.Identificador;
var novoIdentificador = $"{prefixo}{proximoNumero}";
proximoNumero++;
empreendimento.CorrigirIdentificador(novoIdentificador);
registros.Add(new RegistroDeCorrecao(empreendimento.Id, empreendimento.EmpreendedorId, identificadorAnterior, novoIdentificador));
}
await contexto.SaveChangesAsync();
return registros;
}
}
O Comportamento em Tempo de Execução:
Para ilustrar a discrepância gerada, é pertinente notar que transferências de titularidade possuem um distanciamento temporal (meses ou anos) em relação ao cadastro original.
Como exemplo, tomemos um empreendimento cadastrado no início de 2022 (S.2022.10), transferido em fevereiro de 2024:
S.2022.10 (Inativo)S.2022.10 (Ativo)Ao executar a rotina anterior, a consulta listou ambos os registros por conterem a string «2022». Por não possuírem filtro de atividade ou agrupamento, os itens foram iterados com base em posições distintas na memória. O registro ID 500 foi processado, por exemplo, na 9ª posição, mantendo seu identificador S.2022.10 por coincidência de índice. O registro ID 8050 foi processado centenas de posições depois (ex: 149ª posição). A lógica atrelou o novo identificador ao índice atual (149 + 1), atualizando-o para S.2022.150.
O Resultado: Houve o rompimento sistêmico do vínculo. A titularidade vigente assumiu um identificador incompatível com o documento original, enquanto a rastreabilidade histórica ficou isolada.
A Nova Abordagem para 2025
Para sanear a base de 2025 (que ainda reflete as colisões originais), uma nova estrutura foi desenhada com diretrizes defensivas e critérios restritos:
Recuperação via Auditoria
Embora o espelhamento em nível de domínio de anos anteriores apresente inconsistências, a rastreabilidade estrutural foi mantida. O projeto conta com um mecanismo nativo de Auditoria que intercepta alterações na base, registrando as chaves da linha e os valores anteriores e atuais. Toda renumeração promovida pela manutenção de 19 de março de 2024 foi registrada. Os identificadores originais continuam preservados na tabela Auditorias, possibilitando o cruzamento desses dados com a base atual para uma reversão assistida do estado.
A inconsistência na geração de identificadores foi resolvida em nível de código-fonte. O reestabelecimento da integridade dos dados no banco demandará ações segmentadas, com prioridade de execução realinhada com base no risco operacional atual.
A investigação dos logs da Auditoria revelou que os anos de 2022 e 2023, apesar de terem sofrido a renumeração imprevista, assumiram uma ordenação estruturalmente estável e rastreável. Por outro lado, 2024 e 2025 apresentam inconsistências ativas de duplicatas. Diante disso, o plano de recuperação seguirá a seguinte priorização:
Estes anos concentram o risco operacional e devem ser alvos das intervenções imediatas da equipe técnica.
Auditoria (19/03/2024). Caso a ordem atual já esteja coincidente com o espelhamento original devido a este alinhamento acidental, a etapa de Restauração em banco será suprimida (evitando transações desnecessárias). A equipe avançará diretamente para a execução do patch de correção para eliminar as duplicatas ativas do restante do ano.Engloba a maior parte da volumetria impactada pela rotina de março (249 registros em 2023 e 5 em 2022). Como estes anos possuem uma sequência estabelecida e não geram novas duplicatas derivadas do bug original, o risco operacional diário é mitigado.
Auditorias com a base atual para devolver os registros aos seus identificadores originais pré-março de 2024. A rotina incluirá o rastreio de clones gerados após o incidente, garantindo que titularidades vigentes que herdaram identificadores corrompidos também sejam atualizadas em cascata.| # | Documento | Link |
|---|---|---|
| 1 | Repositório do projeto SOLAR | Gitlab |