Commit Graph
89 Commits
Author SHA1 Message Date
gti d95165fc8f Add 'gerenciador/' from commit '34cc9c75a57dedbf61a6e6a1ba1f35f76e72434b'
git-subtree-dir: gerenciador
git-subtree-mainline: f4f546c928
git-subtree-split: 34cc9c75a5
2026-09-29 14:43:12 -03:00
gti f4f546c928 chore: initialize GTI gerenciador fiscal monorepo 2026-09-29 14:43:05 -03:00
GustavoandClaude Sonnet 5 34cc9c75a5 feat: include CNPJ (digits) in the internal Fiscal client payload
Needed by the Fiscal's searchable client combobox (search by name/code/CNPJ).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 08:08:06 -03:00
GustavoandClaude Sonnet 5 b1f93c2d68 feat: read-only internal endpoint for GTI Fiscal to mirror clients
GET /internal/fiscal/clients returns every CLIENTE (active and inactive)
as {external_client_id, code, display_name, active} — CLIENTE.ID is the
identity (CODIGO is editable, same reasoning as FUNCIONARIO_PAINEL.ID in
SSO). Authenticated by a dedicated bearer token (FISCAL_INTERNAL_API_TOKEN,
never reused from SESSION_SECRET/SSO key/admin password), rate-limited per
IP, returns 503 rather than opening anonymously when the token isn't
configured. GTI Gerenciador stays the single source of truth: the Fiscal
Hub never receives MySQL credentials.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 22:36:40 -03:00
GustavoandClaude Opus 5.5 161da90e02 feat: bind the GTI Fiscal SSO assertion to the browser (state/nonce)
/sso/fiscal now requires the state that GTI Fiscal creates when the flow
starts and signs it as the "nonce" claim; GTI Fiscal checks it against
its own one-time cookie. Without a valid state the panel sends the
browser to GTI Fiscal's /login, so the flow always starts there. This
prevents someone from pushing an assertion for their own account into
another person's browser (login CSRF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:21:27 -03:00
GustavoandClaude Opus 5.5 9139a1f6f3 feat: single sign-on from the panel into GTI Fiscal
The sidebar "Fiscal" entry now goes to /sso/fiscal. For a logged-in,
active employee the panel issues a JWT signed with Ed25519 (the private
key exists only here; GTI Fiscal only holds the public key): iss/aud
fixed, sub = funcionario:<ID> (LOGIN is editable), 60 s lifetime, unique
jti that GTI Fiscal records to reject replays. No password, hash or panel
cookie leaves the panel.

The assertion is delivered in an auto-submitted POST, never in the URL,
so it stays out of proxy logs, browser history and Referer. That page
carries its own CSP allowing form-action only to the configured callback
origin; the security-headers middleware now keeps a route-defined CSP.

Without a session the employee goes through the normal login and comes
back; plain HTTP is redirected to the public HTTPS address; `next` only
accepts a relative path; the .env bootstrap account (no stable ID) is
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 08:35:35 -03:00
GustavoandClaude Opus 5.5 4f9bea7799 feat: add a "Fiscal" link to the sidebar
Opens the GTI Fiscal Hub (https://gtifiscal.gtigrupo.com.br) in a new
tab. Link only: GTI Fiscal keeps its own login, the panel's
authentication is unchanged. Already deployed to production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 00:37:50 -03:00
Gustavo 2e23904b99 fix: tabelas nao ficam mais espremidas no mobile (colunas quebrando letra por letra) - min-width em vez de width fixo, permitindo rolagem horizontal 2026-08-20 14:13:25 -03:00
Gustavo ab8912fce7 fix: adiciona cache-busting nos arquivos estaticos (css/js) para evitar layout desatualizado apos deploy 2026-08-20 13:52:07 -03:00
Gustavo 48d17d15b5 feat: ajusta colunas, alinhamento e densidade da tabela de clientes 2026-08-20 13:35:05 -03:00
Gustavo 9b7210d41c docs: plano da melhoria de ui/ux da tabela de clientes 2026-08-20 12:09:55 -03:00
Gustavo 66c50b252a docs: spec da melhoria de ui/ux da tabela de clientes 2026-08-20 12:08:54 -03:00
Gustavo d2135bce5d fix: botao mostrar/ocultar do log nao abria (elemento interativo dentro de summary nao propaga toggle nativo) 2026-08-20 11:48:54 -03:00
Gustavo cf742b4341 feat: secoes da tela de log comecam fechadas, com clique para abrir 2026-08-20 11:39:11 -03:00
Gustavo 565649e50d docs: plano das secoes escondidas na tela de log 2026-08-20 11:34:43 -03:00
Gustavo ca5db5b46f docs: spec das secoes escondidas na tela de log 2026-08-20 11:33:41 -03:00
Gustavo 4ec3c8df88 feat: limite de 20 registros e botao de limpar log (admin) 2026-08-20 10:52:48 -03:00
Gustavo 9fbc57b0ad feat: bolinha de status ativo/inativo clicavel na lista de clientes 2026-08-20 10:51:28 -03:00
Gustavo 0c82170c26 feat: alternar_ativo em CLIENTE e limpar em LOG_PAINEL (camada de banco) 2026-08-20 10:49:43 -03:00
Gustavo 0fdf4ab4e3 feat: campo Ativo no cadastro e filtro de status na listagem de clientes 2026-08-20 09:53:55 -03:00
Gustavo 710d688d38 feat: filtro de status (ativos/inativos/todos) na rota de listagem 2026-08-20 09:52:28 -03:00
Gustavo 4fd4649edd feat: coluna ATIVO em CLIENTE e filtro na camada de banco 2026-08-20 09:49:19 -03:00
Gustavo 53dcd1c9d1 feat: botao de gerar chave de reativacao por linha na lista de clientes 2026-08-20 09:31:13 -03:00
Gustavo b830ce14dc feat: passa csrf_token no contexto da listagem de clientes 2026-08-20 09:30:08 -03:00
GustavoandClaude Sonnet 5 9a7d12f941 fix: achados da revisao final (remove TAMANHO_PAGINA morto, httponly no cookie de paginacao, limpa testes)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 09:11:08 -03:00
GustavoandClaude Sonnet 5 9ce6bcdcad feat: seletor de linhas por pagina e paginacao numerada na listagem de clientes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 09:01:12 -03:00
Gustavo 5a3c4ac092 feat: logout apaga o cookie de preferencia de linhas por pagina 2026-08-20 08:55:56 -03:00
Gustavo 819c1f8e28 feat: GET /clientes le por_pagina da query/cookie e expoe contexto de paginacao 2026-08-20 08:52:17 -03:00
Gustavo 9d5702a01e feat: funcoes puras de resolucao de tamanho de pagina e janela de paginas 2026-08-20 08:48:35 -03:00
GustavoandClaude Sonnet 5 f940ff5768 feat: bloqueia CNPJ/CPF duplicado ao cadastrar ou editar cliente
Mesmo padrão já usado pro CODIGO (db_clientes.codigo_em_uso): nova
db_clientes.cnpj_em_uso(documento, ignorar_id=None) checada em
criar()/editar() antes de gravar. A comparação ignora pontuação dos dois
lados (não só normaliza o valor recebido) porque CLIENTE.CNPJ tem
formatação inconsistente em registros legados (alguns com máscara,
outros sem) — sem isso, um cadastro novo com o mesmo documento de um
legado ainda mascarado passaria batido.

app/templates/cliente_form.html: nova mensagem de erro
'erro_cnpj_duplicado', mesmo estilo visual do erro de código duplicado.

6 testes novos (unitário do db + integração do router pros dois
fluxos), suíte completa (196 testes) passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 09:13:43 -03:00
GustavoandClaude Sonnet 5 eed473c5f3 fix: telefone de contato sem DDI e updates/excludes que fingiam sucesso
- app/routers/contatos.py: CLIENTE_CONTATO.TELEFONE passa a ser gravado
  sempre com DDI 55 (normalizar_telefone_com_ddi, mesma normalização já
  usada em CLIENTE.TELEFONE/CELULAR) — confirmado no node '10 - Validar
  Telefones' do workflow n8n de produção (ER6Piy9yu93uOVuY) que o
  telefone é usado exatamente como está gravado, sem nenhuma
  normalização própria. Um contato cadastrado pelo painel sem o
  funcionário digitar '55' na frente nunca recebia a chave automática
  por WhatsApp — falha silenciosa, sem nenhum erro visível.

- app/db/clientes.py, app/db/contatos.py, app/db/funcionarios.py:
  excluir_cliente/atualizar_contato/excluir_contato/redefinir_senha
  passam a retornar bool (cur.rowcount > 0) em vez de None — um
  ID inexistente não gera erro nenhum do MySQL (WHERE só não bate com
  nada), então sem checar rowcount o router não tinha como saber que
  não alterou/excluiu nada.

- app/routers/clientes.py, app/routers/contatos.py,
  app/routers/funcionarios.py: editar/excluir/redefinir-senha/
  alternar-ativo passam a checar essa existência e mostrar 'não
  encontrado' em vez de registrar 'sucesso' no log e redirecionar pra
  uma tela que também vai dizer que não encontrou nada.

- app/templates/clientes_lista.html: busca não estava com urlencode nos
  links de paginação/ordenação — um termo de busca com '&' (comum em
  razão social, ex. 'Fulano & Cia') cortava o resto da query string do
  link, perdendo página/ordenação.

Suíte completa (192 testes) passando, com 13 testes novos cobrindo os
casos de 'não encontrado' e o DDI do telefone de contato.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 11:50:37 -03:00
GustavoandClaude Sonnet 5 d7a08ddd8d security: rate limit no login, cookies Secure condicional, security headers e deps com CVE
- app/rate_limit.py: bloqueia por IP após 5 tentativas de senha erradas
  em 5 minutos em POST /login (rota pré-autenticação, hoje sem nenhuma
  proteção contra força bruta).
- Cookie de sessão (routers/auth.py) e cookie CSRF (main.py) passam a
  sair com Secure quando a requisição chega em HTTPS (request.url.scheme)
  — hoje é sempre False porque produção ainda serve HTTP puro, mas o
  cookie passa a ficar protegido automaticamente assim que um TLS for
  colocado na frente, sem precisar editar isso de novo.
- main.py: novo middleware de security headers (X-Content-Type-Options,
  X-Frame-Options: DENY, Referrer-Policy, Content-Security-Policy,
  Strict-Transport-Security condicional a HTTPS).
- requirements.txt: fastapi 0.115.0 -> 0.115.6 (starlette >=0.40.0,
  corrige DoS por memória não limitada ao parsear multipart/form-data —
  GHSA-f96h-pmfr-66vw), python-multipart 0.0.9 -> 0.0.20 (várias
  vulnerabilidades de DoS corrigidas), jinja2 3.1.4 -> 3.1.6 (escapes de
  sandbox). httpx/uvicorn/PyMySQL já estavam em versões sem CVE
  conhecida — conferido via osv.dev.
- Dockerfile: container não roda mais como root.

Suíte completa (181 testes) passando, incluindo testes novos para o
rate limiter (unitários + integração via POST /login) e smoke test
manual confirmando headers, fluxo de login com CSRF e o bloqueio por
força bruta.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 11:17:41 -03:00
GustavoandClaude Sonnet 5 b0830651f4 feat(painel-clientes-web): favicon com a logomarca da GTI
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 14:23:44 -03:00
GustavoandClaude Sonnet 5 dea9dc4401 fix: corrige achados da revisao final (DDI idempotente, mascara DDI 12/13 digitos, status DV, validacao no load, autofill dispara mascaras)
Endereca 5 achados da revisao final de branch:
- Important #1: normalizar_telefone_com_ddi agora e idempotente (nao duplica 55 independente do tamanho)
- Important #2: mascara de telefone detecta DDI colado por tamanho total (12/13 digitos), nao mais so pelo campo especifico
- Minor #3: buscarCnpj pula lookup na Receita quando DV do CNPJ e invalido, preservando a mensagem de erro
- Minor #4: detectarEValidarDocumento roda uma vez no carregamento da pagina
- Minor #5: autofill da Receita dispara evento input para acionar as mascaras de UF/CEP/TELEFONE

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 12:31:20 -03:00
GustavoandClaude Sonnet 5 eec7ce21b5 feat: validação de CNPJ e máscaras de telefone/UF/CEP no formulário de cliente
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 12:21:15 -03:00
GustavoandClaude Sonnet 5 59f15d9ac0 feat: bloquear CNPJ com DV inválido, gravar telefone/UF/CEP normalizados
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 12:18:24 -03:00
GustavoandClaude Sonnet 5 8b5e7e251e feat: normalizar_telefone_com_ddi/normalizar_uf/normalizar_cep
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 12:14:39 -03:00
GustavoandClaude Sonnet 5 5c577a644a fix(seguranca): move algoritmo de geracao de chave do JS pro backend
O algoritmo (CHAVE_INTERNA + funcoes gerarReativacao/gerarInstalacao)
ficava 100% legivel no codigo-fonte da pagina — qualquer pessoa com
acesso ao painel conseguia abrir o DevTools e extrair a logica pra
gerar chaves de licenca fora do sistema.

Move tudo pra app/gerador_chave.py (roda so no servidor) e expoe via
POST /gerador-chave/gerar (autenticado, protegido por CSRF). A pagina
agora so manda os dados do formulario e mostra o resultado que volta
do backend. Algoritmo verificado byte-a-byte contra os 3 exemplos
reais conhecidos (cliente 000001, ALUGUEL). Bonus: o byte inicial
agora usa secrets.randbelow (CSPRNG) em vez de Math.random() do JS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:03:19 -03:00
GustavoandClaude Sonnet 5 a3a84abd37 fix: campo codigo do cliente so aceita numeros + botoes de tabela nao quebram linha
Gerador de chave: filtra caracteres nao numericos do campo codigo em
tempo real (input event), alem da validacao ja existente no submit.

Tabelas: a.botao/button ganham white-space: nowrap, corrigindo o
botao 'Abrir' (e outros) quebrando em duas linhas depois que as
celulas de dado passaram a permitir quebra de texto longo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 09:19:44 -03:00
GustavoandClaude Sonnet 5 f6cf0f2e53 fix: botao de ver senha no login ficava esticado pra largura total
.login-cartao button { width: 100%; } tinha mais especificidade CSS
que .botao-ver-senha e forcava o botao do olho a ocupar 100% da
largura do campo, jogando o icone (alinhado por padrao a esquerda no
flex) pro canto esquerdo por cima do input. Regra do width:100% agora
so mira o botao de submit; o campo de senha vira um wrapper flex
(input + botao lado a lado), mais robusto que posicionamento absoluto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 08:56:20 -03:00
GustavoandClaude Sonnet 5 1728a2dbdd fix: sidebar fixa na tela (nao sobe com tabelas longas) + mostrar/ocultar senha no login
Sidebar antes esticava junto com o conteudo (align-items:stretch do
flex), entao em telas com tabela longa (Clientes) o rodape (tema/sair/
recolher) ficava fora da area visivel sem rolar a pagina toda. Agora
fica position:sticky, presa ao topo da viewport, com rolagem propria
se a lista de links for maior que a tela.

Login ganha um botao de olho pra mostrar/ocultar a senha digitada.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 08:52:35 -03:00
GustavoandClaude Sonnet 5 7febe7c652 fix: evita rolagem horizontal nas tabelas (celulas com quebra de linha)
O white-space: nowrap em toda celula de tabela impedia o texto de
quebrar linha, entao valores longos de Razao/Fantasia/CNPJ estouravam
a largura da tabela e forcavam rolagem horizontal mesmo em telas
grandes. Cabecalhos continuam sem quebra (nomes curtos); celulas de
dado agora quebram linha quando necessario.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 08:45:11 -03:00
GustavoandClaude Sonnet 5 6aeed3af7e fix: aplica classe ativo nos links da sidebar conforme rota atual
Adiciona class condicional (ativo se request.url.path.startswith(...))
em cada link de app/templates/base.html, usando o estilo .sidebar-nav
a.ativo que ja existia em estilo.css mas nunca era aplicado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 00:48:37 -03:00
GustavoandClaude Sonnet 5 9012795973 fix: corrige contraste AA do laranja de acento no tema claro
- --cor-acento-contraste passa de branco pra #17171a (texto escuro sobre
  fundo laranja em botoes, unificando com o tema escuro) - contraste ~7:1
- novo token --cor-acento-texto (#B3510C no tema claro, #F5821F no
  tema escuro) usado para laranja como cor de TEXTO sobre fundo claro
  (link padrao, badge-acento, item ativo da sidebar, chave gerada em
  gerador_chave.html) - contraste 5.12:1 contra branco, acima do
  minimo AA de 4.5:1

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 00:48:12 -03:00
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