Commit Graph
45 Commits
Author SHA1 Message Date
GustavoandClaude Sonnet 5 b5098a3aed feat: redesign da tela de log (badges de status)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 00:38:01 -03:00
GustavoandClaude Sonnet 5 9cef2c8d17 feat: redesign de minha-conta e gerador de chave
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 00:35:05 -03:00
GustavoandClaude Sonnet 5 9114303507 feat: redesign da lista e formulario de funcionarios (badges de perfil/status)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 00:32:07 -03:00
GustavoandClaude Sonnet 5 18029de9f8 feat: redesign do detalhe de cliente (contatos e status de chaves)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 22:04:31 -03:00
GustavoandClaude Sonnet 5 343f61ade7 feat: redesign da lista e formulario de cliente (cartoes, grid, tabela responsiva)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 22:01:11 -03:00
GustavoandClaude Sonnet 5 e21b1646a7 feat: redesign da tela de login com identidade GTI
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:57:37 -03:00
GustavoandClaude Sonnet 5 310f5e1f71 feat: sistema de design (tokens claro/escuro) + sidebar responsiva em base.html
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:53:30 -03:00
GustavoandClaude Sonnet 5 7656f2261e docs: corrige README contraditorio sobre HTTP Basic e SESSION_SECRET
1. Frase de abertura ainda dizia 'login HTTP Basic em toda rota',
   contradizendo a secao 'Login e permissoes' mais abaixo no mesmo
   arquivo (ja correta, fala de login por sessao). Atualizada para
   'login por sessao em toda rota'.
2. A lista de variaveis de ambiente para rodar localmente (secao
   'Rodando localmente') nao mencionava SESSION_SECRET, mesmo a secao
   'Login e permissoes' ja instruindo a defini-la. Adicionada a lista,
   com nota de que e opcional em dev (chave aleatoria em memoria a cada
   start, sessoes caem a cada restart) mas deve ser fixa em producao.
3. A mencao ao ALTER TABLE FUNCIONARIO_PAINEL ADD COLUMN PERFIL estava
   em tom de fato passado. Ajustada para o mesmo padrao de aviso ja
   usado para LOG_PAINEL/FUNCIONARIO_PAINEL nesse README ('precisa ser
   rodado manualmente em producao tambem'), deixando explicito que sem
   rodar isso todo login falha com 500 (a query de autenticacao
   seleciona uma coluna PERFIL que nao existe na tabela antiga).

Sem teste automatizado - mudanca só de documentacao.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:19:09 -03:00
GustavoandClaude Sonnet 5 e5f5d4211c fix: desativar/rebaixar funcionario nao revogava sessao ativa (ate 12h)
Perfil e status ativo vinham so do cookie assinado (session.py), entao um
funcionario desativado (ATIVO=0) ou rebaixado (ADMIN->PADRAO) continuava
com acesso total ate o cookie expirar (ate 12h) - inclusive a
/funcionarios se era admin.

exigir_login (app/auth.py) agora reconfirma contra o banco a cada
requisicao quando o login nao e o da conta de bootstrap (ADMIN_USER do
.env, que nao tem linha em FUNCIONARIO_PAINEL e continua sempre admin via
variavel de ambiente): busca db_funcionarios.obter_por_login(login); se
nao existir mais ou ATIVO != 1, levanta NaoAutenticado (forca novo
login); caso contrario, monta o UsuarioLogado com o PERFIL atual do
banco, nao o do cookie - rebaixamento tambem passa a ter efeito imediato.

Testes de regressao em tests/test_auth.py:
- test_exigir_login_autentica_funcionario_ativo
- test_exigir_login_revoga_sessao_ao_desativar_funcionario
- test_exigir_admin_usa_perfil_atual_do_banco_nao_do_cookie
- test_exigir_login_conta_bootstrap_nao_consulta_banco

Ajustou tambem test_exigir_admin_rejeita_padrao (usava um login fake sem
linha em FUNCIONARIO_PAINEL, que agora conflita com a checagem nova -
passou a usar a fixture funcionario_teste). Suite inteira roda
147 passed apos a mudanca.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:18:58 -03:00
GustavoandClaude Sonnet 5 ba8ffaede7 fix: bloqueia open-redirect via next=/\evil.com no login
_proximo_seguro() (app/routers/auth.py) so recusava next que comecasse
com "//", mas navegadores modernos tratam "/\" como equivalente a
"//" (URL protocol-relative), entao next=/\evil.com ainda era um open
redirect explorável via link de login malicioso.

Bloqueia tambem o prefixo "/\".

Teste de regressao em tests/test_router_auth.py: POST /login com
next=/\evil.com precisa redirecionar para /clientes, nao para o host
externo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:18:46 -03:00
GustavoandClaude Sonnet 5 aa1c528197 fix: botao Sair quebrado em /clientes, /log e /gerador-chave (csrf_token vazio)
base.html usava {{ csrf_token }}, variavel que so algumas rotas passam
explicitamente no contexto do template. Em /clientes (GET), /log e
/gerador-chave a variavel nunca era passada, entao o campo oculto do
formulario de logout renderizava com value="" e exigir_csrf (app/csrf.py)
rejeitava o POST /logout com 403 - o usuario nao conseguia clicar em
'Sair' logo apos logar, ja que /clientes e para onde todo login cai.

Corrige na raiz: usa {{ csrf_token or request.state.csrf_token }}, caindo
para o valor que csrf_cookie_middleware (app/main.py) ja popula em toda
requisicao quando a rota nao passar csrf_token explicitamente. Protege
tambem qualquer rota futura que esqueca de passar a variavel.

Testes de regressao em tests/test_main.py: confirma que GET /clientes
renderiza um csrf_token nao vazio no formulario de logout, e que
POST /logout funciona (303) apos visitar /clientes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:18:39 -03:00
GustavoandClaude Sonnet 5 a71b990a3c docs: documenta login por sessao e perfis admin/padrao no README
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:04:37 -03:00
GustavoandClaude Sonnet 5 64990b6db2 fix: atualiza tests/test_main.py para login por sessao
tests/test_main.py nao fazia parte do mapa de arquivos do plano de
permissoes/sessao e ficou quebrado (HTTP Basic Auth) apos a troca pra
login por sessao nas tasks anteriores. Atualiza os 6 testes pra usar
cookie de sessao (app.session.criar_token) em vez de header Basic, e
ajusta a expectativa de GET / sem login (agora redireciona 303 pra
/login em vez de pedir credenciais 401).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:01:36 -03:00
GustavoandClaude Sonnet 5 81f730df9e feat: perfil admin/padrao em funcionarios, protege ultimo admin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:56:52 -03:00
GustavoandClaude Sonnet 5 a2a5fc7905 feat: nav com botao Sair, Minha conta e Funcionarios so para admin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:51:52 -03:00
GustavoandClaude Sonnet 5 09424e8afb feat: liga login por sessao, middleware de usuario e handler de NaoAutenticado
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:49:05 -03:00
GustavoandClaude Sonnet 5 4a5c70861d feat: tela Minha conta para trocar a propria senha
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:44:15 -03:00
GustavoandClaude Sonnet 5 c4079b3e47 feat: rotas de login/logoff (app/routers/auth.py)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:38:43 -03:00
GustavoandClaude Sonnet 5 1b80290f6f feat: login por sessao (troca HTTP Basic), UsuarioLogado, exigir_admin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:33:30 -03:00
GustavoandClaude Sonnet 5 718e845651 feat: coluna PERFIL em FUNCIONARIO_PAINEL + contar_admins_ativos
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:29:45 -03:00
GustavoandClaude Sonnet 5 95b1da7c10 feat: token de sessao assinado (app/session.py)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:25:18 -03:00
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
Gustavo 3158a416a9 feat: preenchimento automatico de dados do cliente a partir do CNPJ
Ao sair do campo CNPJ no formulario de cliente, busca automaticamente
na BrasilAPI (publica, gratuita, sem chave - dados abertos da Receita)
e preenche Razao/Fantasia/Endereco/Cidade/Bairro/UF/CEP/Telefone/
E-mail, so nos campos que estiverem vazios (nunca sobrescreve o que
ja foi digitado). So funciona para CNPJ - CPF e dado pessoal, sem
consulta publica, e a rota recusa (404) se detectar CPF.

- app/cnpj_lookup.py: cliente HTTP pra BrasilAPI, degrada em silencio
  (retorna None) se nao encontrar/rede cair/formato nao reconhecido
  (ex.: CNPJ alfanumerico novo, que a BrasilAPI pode nao cobrir ainda).
- Nova rota GET /clientes/consulta-cnpj/{documento}, chamada via
  fetch() no formulario (JS vanilla, sem framework).
- Testado ponta a ponta contra a API real.

106/106 testes passando (10 novos).
2026-08-13 15:05:26 -03:00
Gustavo d18a5e6e29 feat: cabecalhos clicaveis para ordenar (crescente/decrescente) a lista de clientes
Codigo, Razao, Fantasia e CNPJ agora sao links no cabecalho da tabela:
clicar ordena crescente; clicar de novo na mesma coluna alterna pra
decrescente (e vice-versa). Trocar de coluna sempre comeca em
crescente. Seta (▲/▼) indica a coluna/direcao ativa.

db_clientes.listar_clientes() ganha o parametro direcao (whitelist
asc/desc, igual ja acontecia com ordenar_por). Links de
Anterior/Proxima da paginacao preservam a direcao escolhida.

97/97 testes passando.
2026-08-13 14:37:25 -03:00
Gustavo 2f1d008404 fix: busca de clientes restrita ao campo escolhido em ordenar_por
Antes, digitar qualquer coisa na busca comparava com CODIGO, CNPJ,
RAZAO e FANTASIA ao mesmo tempo (OR), entao escolher 'Ordenar por:
Codigo' e buscar '242' trazia tambem clientes cujo CNPJ ou Razao
continham '242' em outro lugar. Agora a busca usa so a coluna
selecionada no dropdown (mesma whitelist ja usada pra ordenacao).

listar_clientes()/contar_clientes() e o template ganharam o ajuste;
o dropdown mudou de rotulo pra 'Buscar/ordenar por' pra deixar claro
que controla os dois.

93/93 testes passando.
2026-08-13 14:24:09 -03:00
Gustavo 8d83122e98 feat: login individual por funcionario (cadastro, senha com hash, ativar/desativar)
- Tabela FUNCIONARIO_PAINEL (nova, ja criada no banco de dev) +
  app/db/funcionarios.py: nome/login/senha (hash scrypt, stdlib, sem
  dependencia nova) por funcionario, nunca excluido de verdade (so
  ativo/inativo).
- app/senha.py: gerar_hash/verificar_senha com hashlib.scrypt + salt
  aleatorio por senha.
- app/auth.py: login passa a checar duas fontes, nessa ordem -
  ADMIN_USER/ADMIN_PASSWORD (bootstrap, continua exigindo as duas
  preenchidas) e depois FUNCIONARIO_PAINEL (so contas ATIVO=1). O
  nome retornado (usado no log) passa a ser o nome de verdade do
  funcionario, nao mais sempre 'admin'.
- Tela /funcionarios: listar, criar (com confirmacao de senha e
  checagem de login duplicado), editar nome/login, redefinir senha
  (form separado) e ativar/desativar. Tudo logado em LOG_PAINEL.
- Testado ponta a ponta contra o app real: funcionario criado via
  tela consegue logar de verdade, e a acao aparece no /log com o
  nome de quem fez.

93/93 testes passando (23 novos).
2026-08-13 14:19:58 -03:00
Gustavo dad1abf30b fix: parametro correto da API de rows do n8n (limit, nao take)
Testado contra a instancia real (devn8n.gtigrupo.com.br) com uma API
key de verdade: 'take' retorna 400 'Unknown query parameter'; o nome
certo e 'limit'. Confirmado end-to-end - /log agora mostra os envios
reais (ERRO_ENVIO, CLIENTE_NAO_CADASTRADO etc.) da Data Table
pagamentos_processados.
2026-08-13 14:14:40 -03:00
Gustavo 9ed0bf57c2 feat: log de acoes/envios, ordenacao/filtro, codigo sequencial, campos opcionais
- Tabela LOG_PAINEL (nova, ja criada no banco de dev) + app/db/log.py:
  toda criacao/edicao/exclusao de cliente e contato fica registrada
  (usuario, acao, sucesso/erro, detalhe). Tela /log mostra esse
  historico.
- app/n8n_client.py: leitura opcional (N8N_API_URL/N8N_API_KEY) da
  Data Table pagamentos_processados do n8n, mostrando os envios
  automaticos de chave na mesma tela /log. Degrada de forma segura
  (lista vazia) se nao configurado ou se a chamada falhar.
- Lista de clientes ganha ordenacao configuravel (codigo/razao/
  fantasia/CNPJ) via whitelist, alem da busca que ja existia.
- CODIGO duplicado agora BLOQUEIA de verdade na criacao/edicao (sem
  bypass) — antes so avisava com opcao de confirmar mesmo assim.
- Formulario de novo cliente sugere o proximo CODIGO sequencial
  (editavel), ignorando a faixa reservada para dados de teste
  (>= 900000) para nao ser contaminado por sobra de teste no banco.
- IMPLANTACAO/MENSALIDADE/JUROS_MORA/JUROS_MULTA/DATA_CONTRATO/
  VENCIMENTO_CONTRATO deixam de ser obrigatorios — em branco, gravam
  default (0,00 / hoje) em vez de bloquear o cadastro. Só CODIGO/CNPJ/
  RAZAO/FANTASIA continuam obrigatorios.
- Limpeza: removidas 10 linhas de teste vazadas (CODIGO 999902) que
  ficaram no banco de dev por sessoes de teste interrompidas.

69/69 testes passando (16 novos).
2026-08-13 14:04:24 -03:00
Gustavo bb8fd596b1 feat: protecao CSRF (double-submit cookie) e paginacao da lista de clientes
CSRF: novo app/csrf.py + middleware em app/main.py garantem um cookie
csrf_token por navegador; todo formulario que muda estado (criar/editar/
excluir cliente, adicionar/editar/excluir contato) embute esse valor
como campo oculto, e Depends(exigir_csrf) confere no POST que o valor
enviado bate com o cookie antes de deixar a rota executar. Verificado
ponta a ponta com o app real rodando (curl): token errado -> 403,
token correto -> 303.

Paginacao: db_clientes.listar_clientes ganha offset, nova
contar_clientes(); GET /clientes aceita ?pagina=N (100 clientes por
pagina), template mostra Anterior/Proxima e o total.

53/53 testes passando (8 novos).
2026-08-13 11:02:17 -03:00
Gustavo 96cbd8a294 fix: normalizar CNPJ/telefone de CLIENTE antes de gravar (sem mascara)
CLIENTE.CNPJ/TELEFONE/CELULAR agora sao gravados sempre sem pontuacao
(so digitos, e letras no caso do CNPJ alfanumerico), igual ao que
CLIENTE_CONTATO.TELEFONE ja fazia. Resolve a inconsistencia de formato
apontada na revisao final do plano (achado 7): duas tabelas do mesmo
app guardando telefone/documento em formatos diferentes.

So afeta escritas novas feitas pelo painel (criar/editar cliente) -
registros legados existentes so ficam sem mascara quando forem
reeditados e salvos aqui; nao faz backfill em massa dos dados atuais.
2026-08-13 10:54:37 -03:00
Gustavo 9b04877d59 feat: formulario de edicao de contato na tela de detalhe do cliente
Adiciona UI (details/summary, sem JS) para editar nome/telefone/email
de um contato existente, usando a rota POST /clientes/{id}/contatos/{id}/editar
que ja existia no router e no db mas nunca tinha formulario associado.
Cobre com testes de rota (sucesso + telefone duplicado) e um teste
verificando que a tela de detalhe realmente renderiza o formulario.
2026-08-13 10:52:50 -03:00
GustavoandClaude Sonnet 5 0d839949fe fix: validacao de formulario, auth fail-closed, loop de confirmacao, hardening e docs (achados da revisao final)
- Fix 1 (critico): cliente_form.html agora tem required/type por campo
  (date/number) e o router captura DataError/IntegrityError em
  criar/editar, reexibindo o form com erro_gravacao em vez de 500.
- Fix 2 (importante): auth.py falha fechado quando ADMIN_USER/
  ADMIN_PASSWORD nao estao configurados (nao tenta mais compare_digest
  com string vazia).
- Fix 3 (importante): avisos de codigo duplicado e DV invalido agora
  carregam a flag de confirmacao um do outro adiante, quebrando o loop
  quando ambos disparam no mesmo cliente.
- Fix 4 (menor): corrige precedencia 'or 42' -> 'and 42' no lambda de
  teste de criar_cliente.
- Fix 5 (menor): desabilita /docs, /redoc, /openapi.json no FastAPI().
- Fix 6 (menor): comentario explicando o valor de Prorrogacao no fixture
  de teste de chaves.
- Fix 7 (menor): .env.example usa host.docker.internal por padrao,
  docker-compose.yml ganha extra_hosts para Linux, README.md novo com
  quickstart real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:46:23 -03:00
Gustavo 145f784577 feat: monta app final (main.py) e Docker end-to-end 2026-08-13 10:33:29 -03:00
Gustavo 9ebe6b1c68 feat: pagina do gerador de chave manual (client-side, sem gravar no banco) 2026-08-13 10:28:52 -03:00
Gustavo f1139e8b3a feat: rotas de CLIENTE_CONTATO na tela de detalhe do cliente 2026-08-13 10:25:20 -03:00
Gustavo 2c7bf429fc fix: DV usa valor normalizado, botao de confirmar DV invalido, TemplateResponse sem warning 2026-08-13 10:12:26 -03:00
Gustavo c7f2c5f92b feat: rotas e telas de CLIENTE (listar, criar, editar, detalhe, excluir) 2026-08-13 10:07:15 -03:00
Gustavo e93a0f3d1d feat: login HTTP Basic 2026-08-13 10:00:52 -03:00
Gustavo f510a1e17e feat: leitura somente-leitura de status em Clientes_Chaves 2026-08-13 09:58:21 -03:00
Gustavo fd4b749c87 feat: CRUD de CLIENTE_CONTATO 2026-08-13 09:54:26 -03:00
Gustavo 680b7433a4 fix: garantir limpeza de CLIENTE_CONTATO mesmo se assert falhar (test_excluir_cliente_com_contato_bloqueia) 2026-08-13 09:51:13 -03:00
Gustavo 2ecf866678 feat: CRUD de CLIENTE 2026-08-13 09:47:52 -03:00
Gustavo 880329bb8e feat: factory de conexao MySQL 2026-08-13 09:45:26 -03:00
Gustavo 669e864141 feat: normalizacao de documento + validacao de DV (CPF/CNPJ alfanumerico) 2026-08-13 09:42:10 -03:00
Gustavo 143ad71822 chore: scaffold painel-clientes-web 2026-08-13 09:39:28 -03:00