Files
GTI_GERENCIADOR_FISCAL/app/templates/gerador_chave.html
T
Gustavo 361c2ca770 feat: gerador de chave de instalacao (aluguel/compra) na tela /gerador-chave
Adiciona a segunda rotina recuperada do Chave.exe original: chave de
instalacao (8 caracteres tipicos), ao lado da chave de reativacao ja
existente na mesma tela, com um seletor de tipo.

Algoritmo (confirmado matematicamente contra os 3 exemplos reais
fornecidos, byte a byte):
- codigoCliente * 19 + 31 vira um numero inserido sem cifrar no meio
  da chave final.
- A modalidade ("30"=ALUGUEL / "00"=COMPRA) e cifrada com o MESMO
  algoritmo/chave interna ja usado na reativacao (soma modular + XOR),
  gerando 6 hex (3 bytes: 1 aleatorio + 2 cifrados).
- Chave final = 3 primeiros hex + valor do cliente + 3 ultimos hex.

100% client-side (mesmo js puro da reativacao), nunca grava no banco.
Verifiquei os 3 exemplos (DC45092C, 2E35083D, 3D25090C) reproduzindo
o algoritmo em Python antes de trocar o JS - bateram exatamente.

106/106 testes passando.
2026-08-13 16:08:48 -03:00

148 lines
6.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block titulo %}Gerador de Chave{% endblock %}
{% block conteudo %}
<div style="max-width:420px;margin:0 auto;">
<img src="/static/logo-gti-256x256.png" alt="GTI Grupo" style="width:72px;height:72px;border-radius:14px;display:block;margin:0 auto 16px;">
<h1 style="text-align:center;">Gerador de Chave</h1>
<label for="tipo">Tipo de chave</label>
<select id="tipo">
<option value="reativacao">Reativação (mês/ano)</option>
<option value="instalacao">Instalação (aluguel/compra)</option>
</select>
<label for="codigo">Código do cliente</label>
<input type="text" id="codigo" placeholder="Ex: 000123" maxlength="6" inputmode="numeric">
<div id="campos-reativacao">
<label for="referencia">Mês/ano de referência</label>
<input type="month" id="referencia" min="2000-01" max="2100-12">
</div>
<div id="campos-instalacao" style="display:none;">
<label for="modalidade">Modalidade</label>
<select id="modalidade">
<option value="ALUGUEL">Aluguel</option>
<option value="COMPRA">Compra</option>
</select>
</div>
<button id="gerar">Gerar chave</button>
<div id="erro" style="color:#f87171;margin-top:14px;display:none;"></div>
<div id="resultado" style="margin-top:20px;padding:14px;border-radius:8px;background:#17171a;border:1px solid #3a3a3e;border-left:3px solid #F5821F;word-break:break-all;font-family:'Courier New',monospace;color:#F5821F;display:none;"></div>
<button id="copiar" style="display:none;background:transparent;border:1px solid #3a3a3e;color:#a1a1aa;width:auto;padding:6px 10px;">Copiar</button>
</div>
<script>
const CHAVE_INTERNA =
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347" +
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ";
// ==========================
// REATIVAÇÃO — mês/cliente/ano cifrados por inteiro (22 caracteres)
// ==========================
function gerarReativacao(codigoCliente, mes, ano, byteInicial = null) {
const cliente = String(codigoCliente).padStart(6, '0');
const texto = String(mes).padStart(2, '0') + cliente + String(ano).slice(-2);
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
let anterior = aleatorio;
const resultado = [aleatorio];
const bytes = [];
for (let i = 0; i < texto.length; i++) bytes.push(texto.charCodeAt(i) & 0xff);
for (let i = 0; i < bytes.length; i++) {
const valor = (bytes[i] + anterior) % 255;
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
resultado.push(cifrado);
anterior = cifrado;
}
return resultado.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
}
// ==========================
// INSTALAÇÃO — só a modalidade ("30"=ALUGUEL / "00"=COMPRA) é cifrada
// (mesmo algoritmo de cifra da reativação, texto diferente); o valor
// derivado do código do cliente (cliente×19+31) entra sem cifrar, no
// meio dos 6 caracteres cifrados (3 antes + 3 depois).
// ==========================
function gerarInstalacao(codigoCliente, modalidade, byteInicial = null) {
const clienteNum = parseInt(String(codigoCliente), 10) || 0;
const valorCliente = clienteNum * 19 + 31;
const textoModalidade = modalidade === 'ALUGUEL' ? '30' : '00';
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
let anterior = aleatorio;
const bytes = [aleatorio];
for (let i = 0; i < textoModalidade.length; i++) {
const valor = (textoModalidade.charCodeAt(i) + anterior) % 255;
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
bytes.push(cifrado);
anterior = cifrado;
}
const criptografado = bytes.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
const parte1 = criptografado.substring(0, 3);
const parte2 = criptografado.substring(3, 6);
return `${parte1}${valorCliente}${parte2}`;
}
const $tipo = document.getElementById('tipo');
const $codigo = document.getElementById('codigo');
const $referencia = document.getElementById('referencia');
const $modalidade = document.getElementById('modalidade');
const $camposReativacao = document.getElementById('campos-reativacao');
const $camposInstalacao = document.getElementById('campos-instalacao');
const $erro = document.getElementById('erro');
const $resultado = document.getElementById('resultado');
const $copiar = document.getElementById('copiar');
(function preencherMesAtual() {
const hoje = new Date();
$referencia.value = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;
})();
$tipo.addEventListener('change', () => {
const ehReativacao = $tipo.value === 'reativacao';
$camposReativacao.style.display = ehReativacao ? 'block' : 'none';
$camposInstalacao.style.display = ehReativacao ? 'none' : 'block';
});
function mostrarErro(msg) {
$erro.textContent = msg;
$erro.style.display = 'block';
$resultado.style.display = 'none';
$copiar.style.display = 'none';
}
document.getElementById('gerar').addEventListener('click', () => {
$erro.style.display = 'none';
const codigoCliente = String($codigo.value || '').trim();
if (!/^[0-9]{1,6}$/.test(codigoCliente)) return mostrarErro('Código do cliente deve ter até 6 dígitos numéricos.');
const codigoFormatado = codigoCliente.padStart(6, '0');
let chave;
if ($tipo.value === 'reativacao') {
if (!/^\d{4}-\d{2}$/.test($referencia.value)) return mostrarErro('Selecione o mês/ano de referência.');
const [anoStr, mesStr] = $referencia.value.split('-');
const mesNum = Number(mesStr), anoNum = Number(anoStr);
if (!(mesNum >= 1 && mesNum <= 12)) return mostrarErro('Mês fora do intervalo 1-12.');
if (!(anoNum >= 2000 && anoNum <= 2100)) return mostrarErro('Ano inválido (use entre 2000 e 2100).');
chave = gerarReativacao(codigoFormatado, mesNum, anoNum);
} else {
chave = gerarInstalacao(codigoFormatado, $modalidade.value);
}
$resultado.textContent = chave;
$resultado.style.display = 'block';
$copiar.style.display = 'inline-block';
});
$copiar.addEventListener('click', () => {
navigator.clipboard.writeText($resultado.textContent).then(() => {
$copiar.textContent = 'Copiado!';
setTimeout(() => { $copiar.textContent = 'Copiar'; }, 1500);
});
});
</script>
{% endblock %}