62 KiB
Plano — Portal De Paula (Protótipo Interno)
Visão geral
Reformulação do portal interno da De Paula Contadores. O pedido original foi "estruturar um protótipo visual, mas funcional, para avaliarmos e estruturarmos melhor o frontend do portal" — reaproveitando a essência do portal atual (referência: capturas de tela do sistema em produção, tema escuro roxo/dourado, sidebar com dezenas de seções, busca de aplicações, cards de atalho, login com logo circular), não recriando do zero.
Decisões de arquitetura tomadas no início e válidas até a rodada 13:
- Stack: HTML + CSS + JS puro, sem framework, sem build step, sem dependência externa além das fontes do sistema.
- Sem backend: todo o estado (sessão, usuários, perfis de acesso,
favoritos, widgets, compromissos, tema) vivia no
localStoragedo navegador. - Visual: modernizar a estrutura existente (sidebar + topbar + busca + cards + login), não clonar pixel a pixel as capturas de referência.
Isso valeu como protótipo de validação de fluxo/desenho. Na rodada 13 o
usuário definiu a stack real de backend e pediu a migração completa — ver
essa seção para a arquitetura atual. Este documento foca no histórico de
decisões estruturais/transversais — rodadas que mudaram mais de uma
aplicação ao mesmo tempo, o mecanismo de autenticação, o modelo de
permissões em si, a identidade visual, ou a organização de pastas do
projeto — e no que ainda está em aberto no nível do portal como um todo.
Para arquitetura técnica atual (páginas, API, ordem de scripts, modelo de
permissões, como o CSS está dividido entre arquivos), ver CLAUDE.md.
A partir de 2026-08-26, o histórico específico de cada aplicação foi
extraído deste arquivo para um CHANGELOG.md dentro da pasta de cada
uma (portal_api/<app>/CHANGELOG.md para as 3 com pacote Python;
docs/<app>/CHANGELOG.md para as demais — ver README.md na raiz para o
mapa completo), pelo mesmo motivo que levou a documentação técnica a ser
dividida por aplicação (CLAUDE.md/docs/*.md): reduzir conflito de
edição quando mais de uma aplicação está sendo trabalhada ao mesmo tempo, e
manter cada changelog focado no que interessa a quem só mexe naquela
aplicação. Uma rodada que só aparece no CHANGELOG.md de uma aplicação
específica não tem entrada aqui, e vice-versa.
A numeração de rodada NÃO é global (corrigido em 2026-09-21; até então
este arquivo afirmava o contrário). Na prática, cada aplicação passou a
contar as próprias rodadas, e o mesmo número foi reutilizado por trabalhos
diferentes em arquivos diferentes — por exemplo, "rodada 93" é a remoção do
placeholder "Relatório Setorial" aqui e a DRE agrupada por árvore em
portal_api/dashboard_contabil/CHANGELOG.md; "rodada 94" designa quatro
assuntos distintos em quatro arquivos; as rodadas 95 a 101 são reivindicadas
ao mesmo tempo por Temas Sazonais e pelo Relatório Contábil, com conteúdos
sem nenhuma relação. O mesmo vale para 107, 108 e 132.
Consequência, e a regra a seguir: toda citação de rodada precisa nomear o
arquivo ("ver rodada 45 em portal_api/indicadores/CHANGELOG.md"), nunca
só o número. Um "ver rodada 94" solto é ambíguo e não deve ser escrito.
Renumerar o histórico inteiro não vale o esforço e não está planejado.
Cronologia de construção
1. Protótipo inicial — login + shell
Tela de login (logo, usuário/senha, "esqueceu senha" apontando para
Integração e Inovação) e o shell principal: sidebar com as 14 seções do
documento original de arquitetura, topbar com hamburger/"Acessar
Ramais"/notificações, busca de aplicações e uma grade de cards de atalho
fixos (Links, OS, Ramais, Relatórios, Calendário, Solicitações). Tema
claro/escuro com toggle salvo em localStorage. Um modal de "simular perfil
de acesso" (persona) deixava escolher entre as 7 personas do documento
original para ver o menu mudar — mecanismo removido depois (ver rodada
4).
4. Login real (fim do simulador de persona)
O usuário pediu para poder testar de verdade o cadastro de acessos — "podemos testar na realidade como irá funcionar". Isso trocou o mecanismo inteiro:
- Login parou de aceitar qualquer senha; passou a validar contra contas
reais em
pid_users_v1/v2(assets/js/users.js). - Duas contas de demonstração criadas:
gabriel/gabriel(perfil Integração e Inovação) ebruno/bruno(sem perfil vinculado, para testar o estado "sem acesso"). - O modal de simulação de persona foi removido; a visibilidade do menu
passou a vir do perfil de acesso de verdade vinculado ao usuário logado
(
access.js→pidResolveAccess). - Cada um dos 7 perfis do documento original ganhou um registro real em
Perfis de Acesso (Diretoria, Departamento Pessoal, Gerencial, Legalização,
Fisco/Contábil, Financeiro, Protocolo), mais Integração e Inovação (8 ao
todo) — ver
docs/perfis-usuarios/CHANGELOG.mdpro histórico específico da tela de Perfis de Acesso/Usuários.
6. Bugs encontrados e corrigidos
Três bugs reais surgiram e foram corrigidos durante o uso, em áreas diferentes do portal:
users-admin.jsficou preso na assinatura antiga depidResolveAccess()({ profile }em vez de{ profiles }) depois da migração da rodada 5 (verdocs/perfis-usuarios/CHANGELOG.md) — a tela de Usuários redirecionava sempre de volta pro Principal antes de renderizar, dando a impressão de "não abre". Corrigido ajustando a desestruturação.- CSS sobrescrevendo o atributo
hidden: componentes que definem seu própriodisplay(.no-access,.notif-badge,.app-card) ignoravam ohiddendo JS, porque uma regra de autor comdisplayexplícito vence a regra padrão do navegador[hidden]{display:none}mesmo com especificidade igual. A mensagem de "sem perfil vinculado" continuava aparecendo por cima do dashboard normal mesmo com o perfil já vinculado. Corrigido com uma regra global embase.css:[hidden] { display: none !important; }— resolveu esse caso e mais dois latentes (badge de notificação, filtro de busca nos cards). - Submenus recolhidos continuavam clicáveis/focáveis: o accordion do
sidebar só usava
max-height:0+overflow:hiddenpara esconder, o que não impede clique nem navegação por Tab. Corrigido adicionandovisibility:hidden+pointer-events:noneao estado fechado.
13. Migração para backend Django + PostgreSQL
O usuário definiu a stack real do backend: Python 3.13 + Django 6.0 + PostgreSQL 14, e pediu três coisas na mesma rodada:
- Construir esse backend completo (models, API REST, banco Postgres) — não só adaptar o frontend a um contrato assumido.
- Migrar para o banco tudo que hoje existia no
localStorage, exceto a preferência de tema: usuários, perfis de acesso, favoritos, widgets e compromissos do Calendário Individual. - Autenticação via sessão/cookie do Django (decisão explícita, não token/JWT) — o que levou à escolha de servir o frontend estático pelo próprio Django (mesma origem), evitando complicação de cookie de sessão cross-origin.
O que mudou estruturalmente:
- Projeto Django novo, inicialmente em
Portal/backend/(config/+ app únicoportal_api/) — reorganizado na rodada 14 para o padrão de pastas de um projeto Django de verdade. VerCLAUDE.md→ "Arquitetura" para o detalhamento de cada arquivo e a tabela de endpoints. - Model de usuário customizado (
Usuario, estendendoAbstractUser) desde o início — trocar depois de criado o projeto é doloroso. - Senha passou a usar hashing nativo do Django (
set_password/check_password) — antes era texto puro no protótipo; melhoria de segurança real, não só troca de armazenamento. - Catálogo de módulos/aplicações do menu (antes
PID_MODULES/PID_MODULE_APPShardcoded emprofiles.js) virouportal_api/catalogo.py, fonte única da verdade, exposto só leitura viaGET /api/catalogo/. - A união de permissões entre múltiplos perfis de um usuário (rodada 5),
antes calculada no cliente (
access.js→pidResolveAccess/pidApplyAccessVisibility), passou a ser calculada uma única vez no servidor (permissoes_efetivas()emviews.py) e consumida pronta viaGET /api/me/. - A lógica de "quem vê qual compromisso" do Calendário Individual (rodada
10 —
pidEventsVisibleTo) também migrou pro backend (CompromissoAgendaViewSet.get_queryset). - Todo o frontend que lia/escrevia
localStoragefoi reescrito paraasync/fetchcontra a API (assets/js/api.js, hojestatic/js/api.js— ver rodada 14 — é o módulo novo), mantendo a mesma estrutura de arquivos e nomes de função sempre que possível para minimizar o tamanho do diff. assets/js/users.js(armazenamento local de usuários) foi removido — usuários agora só existem no banco, via/api/usuarios/.
Limitação do ambiente onde essa rodada foi feita: só havia Python 3.8 e
nenhum servidor PostgreSQL disponíveis, então não foi possível rodar
makemigrations/migrate/runserver nem testar o fluxo ponta a ponta
durante a implementação — o código foi revisado estaticamente com cuidado
(sintaxe, convenções do DRF, coerência entre serializers/views/frontend).
Rodar de verdade — incluindo gerar a migração inicial — fica para o
ambiente com Python 3.13 + Postgres 14 (ver rodada 17 no CHANGELOG.md de
Links & Ferramentas/Acessos Gerais, onde esse ambiente ficou disponível).
14. Reorganização de pastas no padrão Django
O backend tinha sido criado numa subpasta Portal/backend/, com o
frontend (HTML/CSS/JS) solto na raiz de Portal/ ao lado dela — funcional,
mas não era a estrutura convencional de um projeto Django. Pedido: "reorganize
a estrutura das pastas, conforme o padrão de um projeto Django". Mudanças:
manage.py,requirements.txt,.env,config/eportal_api/subiram dePortal/backend/para a raiz dePortal/—Portal/passou a ser o projeto Django, não conter um subprojeto.- As 5 páginas HTML foram movidas para
Portal/templates/(antes soltas na raiz) — é para lá queTEMPLATES[0]["DIRS"]aponta agora. assets/css,assets/jseimg/viraramstatic/css,static/jsestatic/img(pastaassets/removida) —STATICFILES_DIRS(novo emsettings.py) aponta pra lá, eSTATIC_ROOTfoi adicionado paracollectstaticem produção.- Os 5 templates passaram a usar
{% load static %}+{% static 'css/x.css' %}em vez de caminhos hardcoded (assets/css/x.css) — forma idiomática do Django de referenciar estáticos. config/urls.pyperdeu osre_path/static_servemanuais paraassets//img/—django.contrib.staticfilesjá servestatic/automaticamente emDEBUGa partir deSTATICFILES_DIRS, sem configuração extra.- Bug corrigido de passagem: o
.envjá existia (com as credenciais do Postgres) massettings.pynunca chamavaload_dotenv()— ele nunca tinha sido lido de verdade. Corrigido ao mexer emsettings.pypor causa da mudança deBASE_DIR.
Nenhuma mudança de comportamento — só de organização de arquivos; os endpoints da API, os models e a lógica do frontend continuam os mesmos da rodada 13.
15. Dispensar notificações persiste por usuário
Bug relatado: ao clicar no X de uma notificação individual, o card do dropdown fechava inteiro (comportamento incorreto) e, além disso, qualquer notificação dispensada (X individual ou "Limpar tudo") voltava a aparecer depois de um reload da página.
- Fechamento indevido do dropdown: causado pela ordem de eventos —
list.innerHTMLera reconstruído (removendo o botão clicado do DOM) antes do clique terminar de subir até o listener global emdocumentque fecha o dropdown em clique fora; como o botão já não estava mais na árvore,dropdown.contains(event.target)avaliava falso, fechando o card como se fosse clique externo. Corrigido comevent.stopPropagation()no handler de dismiss emnotifications.js. - Notificação reaparecendo no reload: era o comportamento documentado
até então (
notifications.jsguardava a lista só em memória, "zera a cada reload") — o usuário pediu explicitamente para persistir no backend em vez de usarlocalStorage. Criado o modelNotificacaoDispensada(chave naturalusuario+notif_id, mesmo padrão deFavorito/WidgetUsuario) e o endpoint/api/notificacoes-dispensadas/.notifications.jsagora busca os IDs já dispensados no carregamento e filtra a lista antes de renderizar, e persiste via POST a cada X clicado ou "Limpar tudo". Importante: só o estado de dispensada passou a ser real — o conteúdo das notificações de "nova ferramenta" (PID_NEW_TOOLS_NOTIFICATIONS) continua mockado/hardcoded no frontend, só os itens de compromisso vêm de dados reais. Ver rodada 87 para o restante da evolução desse sistema.
20. Logo com texto branco + sidebar maior
Dois bugs visuais de marca reportados com print: (1) a logo da tela de
login (logo.png) tem o texto "DePaula Contadores" em preto, ilegível
contra o fundo escuro do card de login (--bg-surface); (2) a logo da
sidebar (logo-mono.png, versão toda em branco/cinza claro) estava
pequena demais (.sidebar__logo era 56×56 fixo, espremendo uma arte que é
bem mais larga que alta) — pedido explícito pra usar ali uma versão com o
D colorido e a letra branca, num tamanho maior.
Essa variante (D colorido + texto branco) não existia como arquivo — só
existiam logo.png (D colorido, texto preto) e logo-mono.png (tudo
branco, D incluído). Gerada uma nova, logo-branco.png, com um script
Python usando Pillow: varre logo.png pixel a pixel e recolore pra branco
só os opacos quase-neutros/escuros (max(r,g,b) < 70 e spread(r,g,b) < 12)
— o degradê dourado/marrom do D nunca bate nesse filtro porque mesmo na
sombra mais escura mantém um matiz quente (testado: pixel mais escuro do D
amostrado foi (80,65,42), spread 38; texto era (0,0,0) puro, spread 0).
572.209 pixels alterados. Resultado confirmado visualmente (Read da imagem
gerada) antes de usar.
Trocado logo.png → logo-branco.png no login (index.html) e
logo-mono.png → logo-branco.png nos 5 shells (mesmo arquivo nos dois
lugares — ambos os fundos são escuros, então a mesma variante serve para
os dois). .sidebar__logo foi de 56×56 fixo pra width:200px; height:auto (a arte é ~1.41:1, largura bem maior que altura — forçar
quadrado a esmagava); adicionado encolhimento pra 44px nos dois estados
de sidebar colapsada (.is-collapsed no desktop, breakpoint mobile) pra
não vazar da faixa de 76px.
logo.png e logo-mono.png continuam no repo (não usados em nenhum
template agora) — o primeiro como fonte pra regerar logo-branco.png se a
arte oficial mudar, o segundo como variante alternativa disponível. Essa
logo foi substituída pela marca "P.I.D." na rodada 73.
25. Favicon
Pedido: "no ícone da aba do navegador, deve constar esta logo minimalista do escritório" — o usuário passou um print de referência: só o D (o mesmo pen/quill em degradê dourado/marrom de logo.png), sem o texto "De Paula Contadores", sobre fundo transparente.
Esse recorte isolado do D não existia como arquivo — as três variantes de static/img/ sempre têm o texto junto. Gerado favicon.png a partir de logo.png reaproveitando o mesmo filtro criado na rodada 20 pra logo-branco.png (pixel opaco quase-neutro/escuro = texto, nunca o D, que mantém matiz quente até na sombra mais escura do degradê) — mas em vez de recolorir esses pixels pra branco, apagados (alpha=0) pra isolar só o D; resultado recortado pelo bounding box do que sobrou e centralizado num canvas quadrado transparente (o D é mais alto que largo), redimensionado pra 192×192. Resultado comparado visualmente (Read da imagem gerada) contra o print do usuário antes de usar — bateu.
Ligado via <link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}"> no <head> das 7 páginas (index.html + os 5 shells + ramais.html). Nenhuma mudança de Python; manage.py check limpo por precaução. Substituído pelo ícone da marca "P.I.D." na rodada 73.
26. Bug: busca de aplicações deixava grupos do menu abertos depois de limpar
Reportado com print: depois de pesquisar em "Pesquise por Aplicação..." (search.js) e apagar a busca, os grupos do menu (Portais, Geradoc etc.) que a busca tinha aberto pra mostrar o resultado continuavam abertos, em vez de voltar ao estado de antes de buscar.
Causa: search.js só tinha o caminho de abrir um nav-group (classList.add("is-open")) quando ele dava match durante a digitação — nunca fechava de volta quando o termo era apagado. Corrigido guardando, no início de cada sessão de busca (primeira tecla digitada), quais grupos já estavam abertos manualmente (gruposAbertosAntesDaBusca, um Set); ao limpar o campo, cada grupo volta exatamente pro estado daquele snapshot, em vez de simplesmente remover is-open de todo mundo (o que fecharia até um grupo que o usuário tinha aberto manualmente antes de buscar). Só front-end (static/js/search.js), nenhuma mudança de backend.
27. Botão "Criar Ausência" fora do padrão + texto do menu no tema claro
Dois ajustes pequenos:
- Botão "Criar Ausência" (
ramais.html) usavabtn-outline, destoando dos outros dois botões da mesma barra de ações ("Adicionar Ramal", "Novo Chamado"), ambosbtn-solid. Trocado prabtn-solid, sem nada mais mudar no comportamento — verdocs/ramais/CHANGELOG.mdpro restante do histórico de Ramais. - Texto do menu no tema claro: a sidebar é intencionalmente congelada (fundo sempre escuro nos dois temas, ver
CLAUDE.md→ "CSS — organização entre arquivos"), mas as variáveis de texto (--sidebar-text-primary/--sidebar-text-secondary/--sidebar-text-muted) nunca tinham sido sobrescritas pro tema claro — pedido explícito pra virarem branco puro nesse tema, pra melhorar a legibilidade contra o fundo que continua escuro. Adicionada uma exceção deliberada emtokens.css(:root[data-theme="light"]) só pra essas três variáveis; fundo/borda da sidebar continuam frozen como antes.CLAUDE.mdatualizado pra documentar a exceção, já que a regra geral (não usar overrides de tema na sidebar) continua valendo pro resto.
28. Fonte "Bree Serif" no título da tela inicial
Pedido: trocar a fonte do texto "Portal De Paula" no topbar da tela inicial (portal.html) pra Bree Serif, serif, negrito — só ali, não em --font-sans (usada em todo o resto do portal) nem no .topbar__title dos outros shells (que mostram outros títulos, como "Ramais"/"Administração").
Primeira fonte externa do projeto — até aqui só fontes do sistema (--font-sans). Carregada via Google Fonts (<link rel="preconnect"> + <link ... family=Bree+Serif>) no <head> de portal.html. Estilo aplicado num id novo, #portal-title (no mesmo <h1 class="topbar__title"> de sempre, só ganhou o id), colocado em widgets.css por ser o único CSS próprio da página — mesmo não sendo um widget. Ajustado por iterações diretas do usuário logo em seguida: negrito removido, tamanho aumentado (1.3rem), negrito devolvido, negrito removido de novo — estado final é Bree Serif 400/1.3rem. Na mesma leva de ajustes, o usuário pediu a mesma fonte também no título "Portal De Paula" da tela de login (.login-card__title, login.css) — index.html ganhou o mesmo <link> do Google Fonts.
Este título foi removido de portal.html na rodada 73 (identidade visual "P.I.D."), quando o slogan da marca foi movido pro login — ver login.css em "CSS — organização entre arquivos" no CLAUDE.md.
29. Animações — pedido explícito pra ignorar prefers-reduced-motion
Pedido: "inclua animações nas páginas, para deixá-las fluidas", com duas condições explícitas — rápidas ("otimizando o tempo") e que ignorem a preferência de acessibilidade do sistema operacional/navegador do usuário (prefers-reduced-motion). Isso é uma inversão deliberada da prática padrão de acessibilidade (que normalmente desativaria animação quando essa preferência está ativa) — decisão do dono do produto pro próprio portal interno, registrada aqui pra não ser "corrigida" de volta sem confirmar de novo.
Implementado com três @keyframes genéricos em base.css (pidFadeIn, pidFadeSlideUp, pidScaleIn — único CSS carregado por toda página, inclusive index.html), aplicados via animation (não transition, que não anima a troca display:none ↔ visível causada por hidden) em: .modal-overlay/.modal-card (todo modal do app), .account-dropdown/.notif-dropdown, .page-content (toda navegação entre páginas) e .login-card. Botões (.btn-solid/.btn-outline/.btn-ghost/.btn-danger-outline/.icon-btn) ganharam transform: scale() no :active como feedback de clique, e .pa-table td (reaproveitada por Perfis de Acesso/Usuários/Ramais) ganhou transition: background no hover da linha, que antes trocava sem transição nenhuma. Durações todas curtas (120–200ms, a maioria usando os tokens --transition-fast/--transition-base que já existiam). Nenhum @media (prefers-reduced-motion: reduce) foi adicionado — ausência deliberada, não descuido (ver CLAUDE.md → "Animações").
54. Sexta cor de tema: Vermelho
Adicionada mais uma opção de tema de cor (--accent) ao seletor de swatches do modal "Gerenciar Usuário": "Vermelho" (key: "vermelho", #ef4444 no tema escuro / #b91c1c no tema claro), ao lado das 5 já existentes (roxo/azul/verde/âmbar/rosa). Só duas mudanças, já que a UI do seletor é gerada dinamicamente a partir de PID_COLOR_THEMES (theme.js, consumida por account.js): nova entrada no array PID_COLOR_THEMES e o par de blocos :root[data-color-theme="vermelho"]/:root[data-theme="light"][data-color-theme="vermelho"] em tokens.css, mesmo padrão das outras cores.
Como --danger (usado em feriados/selos de ausente etc.) já era um tom de vermelho (#e5484d) escolhido antes de "vermelho" existir como cor de tema selecionável, agora há uma sobreposição visual esperada quando o usuário escolhe o tema "Vermelho": elementos que usam --accent (ex.: pill "somente_eu" do Calendário Individual) ficam bem parecidos com elementos de erro/alerta que usam --danger. Diferente de --gold/--teal/--slate/--coral (escolhidos deliberadamente fora das cores de tema pra nunca colidir com --accent), --danger não foi criado pensando nisso — é uma coincidência aceita ao adicionar "Vermelho" à lista, não um bug. Se isso incomodar na prática, ajustar --danger para um tom fora da família "vermelho" é a correção futura, não mudar a cor do tema.
73. Nova identidade visual "P.I.D." — favicon, login e sidebar
Usuário forneceu uma nova marca (C:\Users\Depaula\Documents\Projetos\LOGOS\pid-marca, ver pid-marca-leiame.md) e pediu a adoção progressiva no Portal, mantendo o logo cursivo "D De Paula Contadores" só nos documentos/PDFs gerados pela aplicação (contrato, procuração, recibo do Indicador de Desempenho, simulação de custo de contratação) — decisão explícita: esses documentos são emitidos como se o próprio escritório os tivesse gerado, carregam a identidade dele perante o cliente, não a do Portal como ferramenta interna.
- Favicon: trocado de
favicon.png(variante antiga do "D", rodada 25) prapid-icone-escuro.svg(ícone quadrado da marca nova, variante fundo escuro) nas 11 páginas. - Login (
index.html): o card ficou congelado escuro nos dois temas (tokens dedicados--login-card-bg/--login-field-bg/--login-border/--login-text-*emtokens.css, nunca redefinidos em:root[data-theme="light"]— mesmo padrão da sidebar) — pedido explícito depois de uma primeira tentativa (só a caixa de login/senha escura) não ter sido o que o usuário queria; só o fundo da página ao redor do card continua claro/escuro por tema. A logo alterna entrepid-icone.svg/pid-icone-escuro.svgconformedata-theme(pidSyncLoginLogo()emtheme.js— removido na rodada 76), e o texto "Portal De Paula" virou "Portal Interno da De Paula". Um slogan da marca ("Grandes aplicações de todos os tamanhos.", fonte "Pinyon Script"/dourado#c6a24a) foi adicionado — testado primeiro no topbar deportal.html(rodada 28), removido de lá a pedido do usuário, e reintroduzido no login (rodapé, depois movido pra logo abaixo do título), tamanho ajustado até ficar legível sem competir com o título. - Sidebar (10 shells):
sidebar__brandpassou a ter duas logos sempre no DOM, sobrepostas composition:absolutedentro de um container de altura fixa, alternando poropacity+scale()(crossfade, não troca desrc) conforme a sidebar está expandida (pid-logo-horizontal-escuro.svg, assinatura com texto) ou colapsada (pid-icone-escuro.svg, só o símbolo) — sempre as variantes "-escuro", já que a sidebar é sempre escura nos dois temas. Tamanho da logo expandida aumentado em duas rodadas (176px/70% → 220px/92% → 250px/96% da largura útil) até o subtítulo "PORTAL INTERNO DA DE PAULA" ficar legível.
Ver docs/identidade-visual/identidade-visual.md ("Logos em static/img/") pro estado final detalhado de cada arquivo.
74. Animação de intro pós-login (porta de pid-intro-escuro.html)
Usuário forneceu um segundo arquivo da mesma pasta de marca, pid-intro-escuro.html — um bundle de "Design Canvas" (Anthropic) com uma animação de apresentação da marca P.I.D. (composição em 4 cenas: o ícone se desenha, ganha rosto, desliza pra revelar o wordmark "P.I.D." + tagline, e por fim voa/esmaece). Pediu pra reproduzir essa animação como transição entre o login e a tela Principal: ao clicar "Entrar" com sucesso, a animação toca por inteiro e só depois a tela Principal aparece, "pela barra lateral primeiro".
O arquivo fornecido não é HTML/CSS simples — é um bundle auto-contido do editor de canvas com um runtime próprio (React + motor de composição por tempo autorado "OM", fontes e o JSX fonte embutidos como blobs base64/gzip dentro de um <script type="__bundler/manifest">). Decodificado (script Python ad-hoc, gzip+base64 por uuid) pra extrair o JSX de verdade (pid-intro.jsx) e a biblioteca de easing/interpolação (animations-v3.jsx) — essas fórmulas na íntegra permitiram portar a coreografia fielmente (mesmas durações de cena, mesmas curvas de easing hand-rolled tipo Popmotion, mesmo timing de blink dos olhos) pra vanilla JS/CSS, sem carregar o runtime React/canvas original (pesado demais e não pensado pra produção).
- Cenas (mesma duração do original, 9.7s no total): Build (2.6s, contorno do ícone se desenha, aba dourada cai, tela do "rosto" abre) → Face (2s, olhos aparecem com pop e piscam, sorriso dourado se desenha) → Wordmark (2.8s, ícone desliza pra esquerda enquanto "P.I.D." + régua dourada + "Portal Interno da De Paula" aparecem) → Portal (2.3s, wordmark some, ícone encolhe e "voa" até o canto superior esquerdo real da janela — não as coordenadas de um frame de vídeo 1920×1080 do arquivo original, recalibradas pra mirar onde a logo colapsada da sidebar (
.sidebar__logo--icon, 40px) realmente fica — e a cena inteira esmaece). - Arquivos novos:
static/css/login-intro.css(sóindex.html) estatic/js/login-intro.js(motor de animação — easing/animate/clamp/render porrequestAnimationFrame, tudo portado das fórmulas extraídas). Cores/formas idênticas às já usadas empid-icone-escuro.svg/pid-logo-horizontal-escuro.svg(hardcoded, não são tokens de tema — é a paleta fixa da marca). Fontes "Space Grotesk" (wordmark) e "DM Mono" (tagline) adicionadas ao<link>de fontes deindex.html, junto de "Bree Serif"/"Pinyon Script" já usadas ali. - Fluxo:
auth.js, no sucesso do POST de login, chamapidPlayLoginIntro(onDone)em vez de navegar direto — só ao final da animação (onDone) marcasessionStorage.pid_reveal_portal="1"e navega praportal.html. - "Aparecendo primeiro pela barra lateral": novo
static/js/portal-reveal.js(sóportal.html, IIFE de topo tipotheme.js) — se a sessionStorage tiver a marca, aplicahtml.pid-enteringantes da primeira pintura (evita flash) e remove a classe ~350ms depois doDOMContentLoaded.layout.cssesconde.main-content(topbar+conteúdo) enquanto essa classe está presente, sem afetar.sidebar— como a sidebar não é tocada, ela fica visível o tempo todo e "aparece primeiro", com o resto do app surgindo num fade logo depois. Acesso direto aportal.html(sem passar pelo login) não aciona nada disso, já que a sessionStorage nunca é setada nesse caminho.
Ainda não testado num navegador de verdade — a próxima rodada deve conferir o resultado visual completo (timing, posição do "fly" final em diferentes tamanhos de tela, e o crossfade da sidebar) antes de considerar fechado.
Três ajustes no mesmo dia, depois do primeiro teste real (funcional, mas com pontos a melhorar): (1) a cor de fundo do overlay (#241c33, roxo-tinto do arquivo original) destoava do fundo real do Portal — trocada pra #121017, o mesmo valor de --bg-canvas no tema escuro, pra a sequência login→animação→portal ler como uma coisa só; (2) o overlay aparecia de repente por cima do card de login ainda visível ("de um modo bruto") — agora o card se dissolve (.login-card.is-leaving) enquanto o overlay sobe (.login-intro.is-visible), um cross-fade de 350ms, e só depois disso o ícone começa a se desenhar; (3) a cena final (Portal) tinha uma pausa parada depois do ícone já ter chegado ao canto até o fade acontecer — comprimida de 2,3s pra 0,95s (wordmark some quase na hora, ícone voa em 0,6s, tudo esmaece logo em seguida, sem intervalo morto) — duração total da animação caiu de 9,7s pra 8,35s.
Quarto ajuste, mesmo dia: a revelação da tela Principal (rodada 74) só escondia .main-content, deixando a sidebar sempre visível desde o início (sem nenhuma entrada própria) — usuário pediu mais fluidez, com a sidebar de fato aparecendo antes do resto. portal-reveal.js passou a aplicar duas classes em sequência: pid-entering-sidebar (esconde só a sidebar, com um leve opacity+translateX(-16px)) sai primeiro, 120ms depois do DOMContentLoaded; só 200ms depois disso é que pid-entering (esconde .main-content) sai, revelando o resto do app.
Quinto e sexto ajustes, rodada seguinte: (5) usuário pediu pra acelerar a construção do ícone — Build e Face (2,6s/2s no original) foram pra 1s cada, Wordmark ("a parte onde aparece o nome do portal") ficou igual (2,8s, pedido explícito de não mexer), e Portal ("a parte na qual ele some") foi arredondada pra ~1s também — total da animação caiu de 8,35s pra 5,7s; os deslocamentos internos de cada cena precisaram ser reproporcionados pra caber nas durações novas (não é só trocar CUES, os offsets tipo Build+1.15 etc. também mudaram), e o terceiro blink do olho (na cena Portal) foi removido por não caber mais visivelmente numa cena tão curta. (6) A entrada da sidebar em portal.html (ajuste anterior, opacity+translateX(-16px)) foi considerada "ainda não satisfatória" — pequena demais pra ler como "a barra lateral surgindo da esquerda pra direita". Trocada por um slide de verdade: translateX(-100%) (a largura inteira da sidebar, não 16px) com uma transição própria de 420ms e curva de chegada suave (cubic-bezier(0.16, 1, 0.3, 1), não var(--transition-base)); o tempo de espera antes de revelar o resto do app também subiu de 200ms pra 420ms, pra não sobrepor as duas entradas.
Sétimo ajuste, rodada seguinte: o fade de entrada (card se dissolvendo + overlay subindo, antes do ícone começar a se desenhar) estava "muito rápido" — a duração (350ms, tanto em login.css/login-intro.css quanto em PID_INTRO_FADE_MS de login-intro.js) subiu pra 500ms, um ajuste pequeno de propósito ("bem pouco, só pra ficar mais fluído").
75. Logo da sidebar vira link pra "Principal", com os olhos do ícone piscando ao clicar
Usuário pediu duas coisas sobre a marca da sidebar (sidebar__brand, presente nos 10 shells): (1) transformá-la num botão/link que navegue pra portal.html (tela Principal) e (2) uma animação de piscar nos olhos do ícone (o "disquete") ao clicar.
.sidebar__brand deixou de ser <div> e virou <a href="portal.html" id="sidebar-brand-link"> — mudança de tag que não quebra nada, já que todo CSS do crossfade (rodada 20/73) é por classe. O ponto difícil: as duas logos ali (pid-logo-horizontal-escuro.svg expandida, pid-icone-escuro.svg colapsada) eram <img src="...svg">, e uma <img> é uma imagem opaca — não dá pra CSS/JS da página alcançar elementos internos dela (os olhos) pra animar. Solução: inlinear o markup das duas logos como <svg> de verdade, direto no HTML dos 10 shells (os arquivos .svg continuam existindo em static/img/, só não são mais referenciados por essa parte da sidebar — favicon continua usando o arquivo normalmente).
Dentro do SVG inline, os dois olhos (círculo creme + glint escuro) ficam cada um num <g class="sidebar-icon-eye__lid"> (renomeada pra .pid-icon-eye na rodada seguinte, ver abaixo) sem nenhum transform de atributo XML — detalhe técnico importante: um transform CSS aplicado a um elemento que já tem transform de atributo substitui o atributo inteiro (perderia a posição do olho). static/js/sidebar-brand.js (novo, incluído nos 10 shells) escuta o clique: ignora cliques modificados (ctrl/cmd/shift/meio, deixa abrir em nova aba normal), senão previne a navegação, adiciona .is-blinking nos olhos (dispara @keyframes pidSidebarBlink em layout.css, scaleY(1)→0.05→1, 200ms) e só navega de fato 260ms depois — tempo da piscada terminar de tocar antes da página trocar, mesmo espírito já usado na animação de intro do login (rodada 74).
76. Mesma piscada no ícone do login + logo do login deixa de alternar por tema
Usuário pediu duas coisas: (1) o mesmo efeito de piscar do olho (rodada 75) também no ícone do card de login, ao clicar; (2) usar a mesma logo nos dois temas no login (parar de alternar pid-icone.svg/pid-icone-escuro.svg conforme data-theme, ver rodada 73).
(2) foi resolvida primeiro, e simplificou (1): já que o card do login é congelado escuro nos dois temas desde a rodada 73, a alternância de logo por tema nunca fez muito sentido — removida (pidSyncLoginLogo() tirada de theme.js, os atributos data-logo-dark/data-logo-light tirados de index.html); a logo do login agora sempre usa as cores de pid-icone-escuro.svg, sem checagem de tema nenhuma. Isso deixou pid-icone.svg (a variante clara) sem nenhum consumidor no Portal.
Pra (1), mesmo problema técnico da rodada 75 (não dá pra animar dentro de um <img>) — o ícone do login virou <button type="button" id="login-logo-btn"> com o SVG inline dentro (igual à sidebar), e static/js/login-logo-blink.js (novo, só index.html) dispara a piscada ao clicar — sem preventDefault/delay/navegação, já que estamos na própria tela de login, é só um toque decorativo.
Refatoração de nomes, pra reaproveitar entre os dois lugares: como agora dois contextos diferentes (sidebar e login) piscam o mesmo ícone, .sidebar-icon-eye__lid/pidSidebarBlink (que moravam em layout.css, não carregado por index.html) foram renomeados pra .pid-icon-eye/pidIconBlink e movidos pra base.css (único CSS comum às duas telas) — layout.css e os 10 shells foram atualizados pra usar o nome novo.
77. Fundo da tela de login no tema claro batendo com o fundo real do Portal
Usuário reportou que, no tema claro, o fundo da tela de login (.login-page, a área ao redor do card) ficava com um tom acinzentado/amarronzado bem diferente do fundo de verdade da tela Principal nesse tema (um lavanda bem claro, quase branco). Causa: .login-page sempre aplicou um scrim escuro + um brilho radial por cima do --bg-canvas, pensados pra dar profundidade num fundo escuro — no tema claro, esse mesmo scrim escuro por cima de um --bg-canvas já claro produzia esse cinza sujo, nada parecido com o --bg-canvas puro que portal.html usa (via body em base.css).
Corrigido com um override :root[data-theme="light"] .login-page { background: var(--bg-canvas); } — no tema claro, zera o scrim/glow e usa só a cor sólida, igual ao resto do Portal; no tema escuro (padrão) nada mudou, o scrim+glow continuam dando a profundidade de antes.
78. Mesmo ajuste na animação de intro: fundo claro no tema claro, texto do wordmark escuro
Sequência natural da rodada 77 (fundo de .login-page): o usuário pediu o mesmo pro fundo da animação de intro (#login-intro, rodada 74) — no tema claro, também devia usar o --bg-canvas claro, não o #121017 escuro fixo. Só que aqui tinha uma pegadinha que .login-page não tinha: o wordmark da animação ("P.I.D." + tagline) usa texto claro, pensado pra contrastar contra um fundo escuro — mudar só o fundo pro claro deixaria esse texto ilegível.
login-intro.css ganhou os overrides de tema claro: .login-intro vira background: var(--bg-canvas), e .login-intro__letter/.login-intro__tagline viram cores escuras (#241c33/rgba(36, 28, 51, 0.62)) só nesse tema. O ícone (corpo roxo + tela escura do disquete) não precisou de nenhum ajuste — já tem contraste de sobra nos dois fundos. Tema escuro (padrão) ficou intocado.
79. Fundo de .login-page no tema claro refinado: brilho roxo mais claro + base levemente mais escura
Depois de ver o fundo liso (var(--bg-canvas), rodada 77), usuário pediu um refinamento: "um tom de roxo um pouco mais claro e o fundo branco levemente escurecido", só no tema claro. Trocado por radial-gradient(circle at 20% 20%, rgba(var(--accent-rgb), 0.12), transparent 45%) sobre #ece7f2 (levemente mais escuro que o --bg-canvas puro, #f3f1f7) — o brilho radial já existia no tema escuro com opacidade 0.25, aqui ficou bem mais sutil (0.12) pra não ficar turvo sobre um fundo claro. A cor exata é fixa só nesta regra (não altera o token --bg-canvas), então o resto do Portal no tema claro continua com o tom original.
81. "Mais informações" por aplicação (botão "?")
Pedido explícito do usuário: um botão "?" ao lado do nome de qualquer aplicação, mostrando "Mais informações" no hover e abrindo, ao clicar, um modal com um texto de ajuda (objetivo/processo/cuidados/resultado esperado). Visualizar é livre a qualquer autenticado; editar é restrito a quem tem o perfil "Inovação" vinculado.
- Novo model
AjudaAplicacao(chave naturalapp_key,texto,atualizado_em/atualizado_por) + endpointGET/PATCH /api/ajuda-aplicacoes/<app_key>/. A checagem de quem pode editar é por nome fixo do perfil (Usuario.eh_perfil_inovacao()/models.PERFIL_INOVACAO_NOME), mesmo padrão já usado pro selo "Restrito" de Relatórios Gerenciais (nome === "Diretoria") — decisão explícita do usuário pra não precisar aparecer na árvore de Perfis de Acesso.GET /api/me/ganhoueh_perfil_inovacao. - Ligado por ora só em Importação de Plano de Saúde (
static/js/ajuda-aplicacao.js,pidCriarBotaoAjuda()), com um texto inicial já estruturado e salvo no banco — mecanismo genérico o bastante pra outra aplicação só precisar do botão+tooltip no HTML. - Ganhou imagens embutidas (pedido explícito, mesmo mecanismo de "Observações" de Acessos Gerais): editor
<div contenteditable>com colar/arrastar imagem, sanitizado no servidor vianh3antes de salvar. As constantes de allowlist do nh3 foram generalizadas (ACESSO_GERAL_OBSERVACOES_ALLOWED_*→RICHTEXT_ALLOWED_*,serializers.py) pra serem compartilhadas pelos dois campos.
A correção de um bug real do seed_portal.py encontrado nesta mesma rodada (reescrevia nome de um PerfilAcesso já existente) e o restante da limpeza de nomenclatura estão no CHANGELOG.md de Perfis de Acesso/Usuários.
82. Modal de confirmação/aviso genérico — fim do window.confirm/window.alert nativo
Usuário viu o popup nativo do Chrome ("192.168.x.x:8000 diz...") na confirmação de "Sair sem salvar" do modal de "Mais informações" (rodada 81) e pediu, de forma geral: nenhum popup deve usar o diálogo nativo do browser — sempre um modal dentro do próprio Portal, no padrão visual dele.
static/js/confirm-modal.js(novo, incluído logo depois deapi.jsem todo shell, inclusiveindex.html):pidConfirm(mensagem, opcoes)(Promise) epidAlert(mensagem, opcoes)(Promise) compartilham o mesmo modal (#pid-confirm-modal), que empilha por cima de qualquer modal já aberto (.modal-overlay--top, z-index maior) sem fechá-lo.opcoes.perigosotroca o botão de ação pra.btn-danger-outline.- Migração completa (pedido explícito — "migre as demais"): todo
window.confirm()/window.alert()do app (31 ocorrências em 8 arquivos —acessos-gerais.js,calendar-individual.js,links-ferramentas.js,importacao-plano-saude.js,ramais.js,profiles.js,indicador-desempenho.js,users-admin.js) foi trocado porpidConfirm/pidAlert. Um modal bespoke que já existia emimportacao-plano-saude.jssó pra esse mesmo motivo (#ips-regracad-confirm-fechar-modal, no "Cadastro de Regras") foi removido e consolidado no componente genérico. Não migrado: umwindow.prompt()emusers-admin.js(renomear departamento) — tipo de popup diferente (pede texto), sem componente equivalente ainda.
87. Notificações de "nova ferramenta": gate de permissão + expiração automática em 10 dias
Usuário viu, pelo dropdown do sino, notificações de ferramenta antigas (algumas com mais de duas semanas) ainda listadas como novas, e pediu duas correções: (1) notificação de uma aplicação que o usuário não tem permissão de acessar nunca deve aparecer; (2) toda notificação de "nova ferramenta" deve valer por só 10 dias — depois disso, cair sozinha pra dispensadas, sem esperar o usuário clicar no X.
- Cada entrada de
PID_NEW_TOOLS_NOTIFICATIONS(notifications.js) ganhou um campoaccess({type:"gerencia"}ou{type:"module", module:"..."}) reproduzindo o mesmo gate que já esconde o item correspondente no menu (pidApplyAccessVisibilityemaccess.js) —pidNotifToolElegivel()filtra a lista antes de montartoolNotifications, então uma notificação sem permissão nunca aparece nem no sino nem no histórico. date: "DD/MM"(string pré-formatada, sem ano) viroudataIso: "AAAA-MM-DD"—pidNotifToolExpirada()compara contra a data de hoje (PID_NOTIF_TOOL_EXPIRA_DIAS = 10); passado o prazo,notifications.jschamaPOST /api/notificacoes-dispensadas/sozinho no carregamento da página (mesma chamada do X manual), então a notificação passa a seguir 100% as regras já existentes de dispensa/histórico/restauração (ver rodada 15) — nenhum mecanismo novo no backend.toolNotificationsAgora(recorte "elegível agora", análogo apidEventosElegiveisAgora()pros compromissos) exclui as expiradas por prazo tanto na carga inicial quanto depois de um "Restaurar" — restaurar uma notificação de ferramenta com mais de 10 dias mantém o rastro no histórico, mas não a devolve ao sino.- Ver
CLAUDE.md, seção "Armazenamento" → "Histórico de notificações" pro detalhamento completo.
88. Não Conformidades (Relatórios > Qualidade) — nova aplicação
Evolui a skill do Claude analise-ncs (Excel sob demanda a partir de dois exports do Sigsistem) pra uma gestão contínua dentro do Portal: reabertura automática por diff quando algo muda desde o último tratamento, histórico completo de acompanhamentos e um dashboard (ações vencidas/vencendo, motivos de abertura, clientes/colaboradores com maior incidência). Nasce restrita ao perfil "Inovação" (mesmo padrão adotado depois pro Dashboard Contábil). Histórico técnico completo (models, pacote portal_api/nao_conformidades/, e as rodadas seguintes de refinamento) em portal_api/nao_conformidades/CHANGELOG.md/CLAUDE.md — a partir daqui, rodadas específicas desta aplicação não duplicam entrada aqui (ver nota no topo deste arquivo).
92. Dashboard Contábil (Relatórios > Contabilidade) — nova aplicação
Nova aplicação em Relatórios > Contabilidade (nasce restrita ao perfil "Inovação", mesmo padrão de "Não Conformidades"): o contador anexa o PDF de Balancete + DRE (modelo Questor), a ferramenta extrai as contas/linhas e roda um motor de regras de auditoria, com revisão de achados/observações. Subgrupo "Contabilidade" novo em catalogo.py dentro de relatorios. Histórico técnico completo (fórmulas, models, desafio de extração do PDF) e das rodadas seguintes (94+, incluindo o botão "Gerar Dashboard") em portal_api/dashboard_contabil/CHANGELOG.md/CLAUDE.md — a partir daqui, rodadas específicas desta aplicação não duplicam entrada aqui (ver nota no topo deste arquivo).
93. Menu "Relatórios" — remoção do placeholder "Relatório Setorial"
Pedido (2026-09-14): remover o item "Relatório Setorial" (href="#", sem tela própria) do menu Relatórios, já que o tópico agora tem itens de verdade ("Qualidade" → Não Conformidades, "Contabilidade" → Relatório Contábil). Removido de MODULE_APPS["relatorios"] em catalogo.py e do <li data-app="relatorio-setorial"> hardcoded nas 13 páginas-shell (o menu é replicado por template, não um componente único — ver "Ordem de <script>" no CLAUDE.md raiz pro mesmo padrão de duplicação entre shells). O item "Indicadores" (outro placeholder href="#" dentro de Relatórios) não foi tocado, não fazia parte do pedido. Perfis já existentes no banco podem manter uma chave órfã "relatorio-setorial" dentro de permissoes["relatorios"]["apps"] — inofensiva, mesmo padrão já visto quando "Gerar Contrato"/"Gerar Procuração" foram removidos de Geradoc (ver comentário em catalogo.py).
94. Temas sazonais (Halloween) — logo/mascote vampiro, intro de abertura, teias de aranha e opt-out
Pedido (2026-09-15): deixar uma logo e uma introdução de abertura com tema de Halloween, aplicadas só durante outubro, revertendo pro P.I.D. normal depois — sem descartar nada do que já existe. Implementado como mecanismo 100% runtime (nenhum arquivo original alterado ou removido — fora da janela ativa, tudo volta sozinho): troca de ícone/favicon/assinatura pelo mascote vampiro, uma intro de abertura pós-login alternativa, uma animação de "susto" ao clicar na logo, teias de aranha decorativas nos cantos do login/tela Principal (com uma aranha "matável" ao clicar, óbito persistido) e um toggle no menu da conta pro usuário desligar temas sazonais por preferência própria (ou acessibilidade — aracnofobia). Ainda em preview (PID_HALLOWEEN_PREVIEW_FORCE força ligado pra revisão visual, antes de confirmar a janela de outubro definitivamente). Histórico técnico completo (rodadas 94-100) e detalhamento em docs/temas-sazonais/CHANGELOG.md/temas-sazonais.md — a partir daqui, rodadas específicas desta aplicação não duplicam entrada aqui (ver nota no topo deste arquivo).
149. Revisão da documentação (2026-09-21)
Rodada de documentação, não de código. Nenhum arquivo .py/.js/.html/.css
foi tocado. O usuário pediu uma avaliação dos .md do projeto e depois a
aplicação das correções de fato e da reorganização.
Correções de fato (o que estava escrito e era falso):
README.mdmandava rodarpython manage.py seed_portalno passo a passo de "Como rodar localmente", enquanto oCLAUDE.mddedica um parágrafo a dizer que isso nunca deve rodar neste ambiente (o banco do.envé produção). Removido do passo a passo e substituído por um aviso.- Uma aplicação inteira não estava documentada em lugar nenhum:
importacao-plano-saude-de-paula(item de menu emcatalogo.py, rota emconfig/urls.py, 7 models, 5 grupos de rota, template de 911 linhas). Os docstrings demodels.py/views.pyjá apontavam para uma seção doCLAUDE.mddeportal_api/planos_saude/que nunca tinha sido escrita. Escrita agora, mais entrada no changelog daquela aplicação, no mapa doREADME.md, na tabela de Páginas doCLAUDE.mde noprd.md. - A numeração de rodada não é global, ao contrário do que este arquivo e os
12 changelogs afirmavam. 15 números aparecem em mais de um arquivo e em 12
deles o assunto é diferente (93, 94, 95-101, 107, 108, 132). Corrigido aqui,
no
CLAUDE.mde no cabeçalho de todos os changelogs; a regra agora é que toda citação de rodada nomeie o arquivo. - Contagens desatualizadas no
CLAUDE.md, que são instruções na prática ("replicar nos 10 shells"): "as 8 páginas HTML" (são 15 templates), "10 shells" em 5 lugares (são 13), "as 11 páginas" do favicon (são 14), "as outras 11 páginas" compage-content--wide(são 12). pid-favicon.svgepid-logo-horizontal.svgeram descritos como se estivessem emstatic/img/e nunca foram copiados para lá; os três SVGs sazonais que existem na pasta não constavam do inventário.- O
prd.mdlistava Não Conformidades e Relatório Contábil, duas aplicações completas, dentro do bullet "Reservados no menu, sem tela própria ainda". Viraram seção própria, junto de Temas Sazonais e da variante De Paula.
Reorganização:
portal_api/dashboard_contabil/CLAUDE.mdreescrito como estado atual: 65% dele (129 KB de ~196 KB) eram seções datadas por rodada, ao lado de um changelog de 110 KB com as mesmas rodadas. Reescrito por subsistema, sem cronologia, 196 KB → 79 KB, sem descartar fato técnico (a redução é consolidação de repetição mais a remoção do histórico, que já estava no changelog). Ganhou a tabela dos 22 endpoints da ferramenta, que não existia.CLAUDE.mdda raiz: 100 KB → 67 KB. É o arquivo carregado em toda sessão, e ~40% dele era detalhe de uma área só. Três movimentos: (a) os 36 endpoints de aplicação saíram da tabela de API para a doc de cada aplicação, deixando só os 19 transversais mais um ponteiro — a tabela era metade índice, metade despejo, e não tinha nenhuma linha do Relatório Contábil nem de Não Conformidades; (b) "Logos" (9,4 KB) e "Animações" (12,0 KB) viraramdocs/identidade-visual/, com resumo e ponteiro na raiz; (c) "Ajuda de aplicação" e "Modal de confirmação" condensados, tirando a lista de migração arquivo a arquivo (que é história, não estado) e corrigindo um bullet que estava na seção errada.- Formato de changelog padronizado:
### Rodada N — Títulonos 12 arquivos (o Relatório Contábil usava### N.nas 58 entradas, Não Conformidades misturava os dois). O título do changelog do Relatório Contábil ainda dizia "Dashboard Contábil", nome trocado na rodada 120.
Não mexido, fica para decisão do usuário: uma tabela de roteamento por
prefixo (model/JS/CSS/rota → doc), que resolveria o problema de fundo descrito
abaixo, e o que fazer com docs/manual/ e projects/, que estão fora do mapa
do README.md.
Problema de fundo levantado e não resolvido: a documentação divide as
aplicações entre "com pacote Python" (CLAUDE.md carregado automaticamente) e
"sem pacote" (ler manualmente), o que sugere que trabalhar numa aplicação traz
a doc dela junto. Na prática quase nunca traz: o código de todas elas vive em
portal_api/models.py (2.638 linhas), views.py (5.086) e serializers.py
(2.764), fora dos pacotes, que têm só 590 a 1.625 linhas de helper puro cada.
Editar os models ou as views do Relatório Contábil não dispara o CLAUDE.md
dele.
150. Escape de HTML em todo o frontend (XSS) (2026-09-25)
Primeira correção da revisão de interface feita com a skill web-design-guidelines
(relatório em docs/revisao-interface-2026-09-25.docx, item 1.1; versão
explicada em docs/revisao-interface-2026-09-25-resumo.docx). O usuário pediu
para aplicar só este item por enquanto.
Problema: em várias telas, dado vindo da API, do usuário ou de arquivo
anexado entrava em innerHTML sem escape, ou com uma função de escape que não
tratava aspas e era usada dentro de atributo. Um texto com HTML virava código
executado no navegador de quem abria a tela. Nos casos de dado compartilhado
(compromisso com visibilidade "todos" no sino e no widget, links, ramais), no
navegador de outros usuários (XSS armazenado).
Correção:
- Função compartilhada
pidEscapeHtml(texto)emstatic/js/api.js(carregado em todos os 15 shells, antes dos outros scripts), tratando& < > " ', para conteúdo e atributo. - As funções locais passaram a delegar a ela:
pidDcEscapeHtml/dcEscapeAttr(Relatório Contábil),pidNcfEscapeHtml(Não Conformidades),pidIndEscapeHtml/pidIndEscapeAttr(Indicador),pidConcEscape/pidConcEscapeAttr(Conciliação),escapeHtml/escapeAttr(Plano de Saúde). As três primeiras epidConcEscapeeram baseadas emtextContente deixavam"passar (observação do contador emaria-label, tipo de ocorrência emvalue, nome de quem validou emtitle). - Escape aplicado nas interpolações sem nenhum escape:
notifications.js(título/dono do compromisso, escapados uma vez na criação do objeto),widgets.js,dual-select.js,favorites.js,links-ferramentas.js,acessos-gerais.js,calendar-individual.js,ramais-lookup.js,ramais.js,profiles.js,users-admin.js,importacao-plano-saude.js(auditoria com nome/CPF/detalhe do arquivo da operadora, histórico, abas, alterações),nao-conformidades.js(códigos),indicador-desempenho.jsedashboard-contabil.js(rótulos com valor de reserva vindo da API, chaves de indicador personalizado).custo-contratacao.js,ajuda-aplicacao.js,account.js,seasonal-theme.js,search.js,access.js,sidebar.js,auth.jseevents.jsforam revisados e não tinham ponto inseguro. - Não escapado de propósito: texto rico sanitizado no servidor com
nh3(Ajuda, observações de Acessos Gerais, Resumo do Fechamento), HTML montado pelo próprio código (ícones, fragmentos), dado atribuído portextContent/value, mensagens depidConfirm/pidAlert(o modal usatextContent) e ids numéricos.hrefcomjavascript:não é risco porqueLinkFerramenta.urleAcessoGeral.urlsãoURLField(só http/https/ftp). - Única mudança visível: onde um campo vinha vazio (
null), a célula mostrava "undefined" em alguns pontos do Plano de Saúde e agora fica vazia.
Validação: só frontend, sem banco, API ou migration, e não exige reiniciar o
runserver. Sintaxe dos 17 arquivos alterados conferida com um analisador de
JavaScript (esprima, instalado só na pasta de rascunho da sessão). Não
testado no navegador (sem renderização neste ambiente). Roteiro sugerido:
cadastrar textos como Teste <b>negrito</b> "aspas" & cia em compromisso, link,
ramal, perfil, acesso e observação, e conferir que aparecem literalmente em
todas as telas onde são exibidos, sem & sobrando. A regra ficou registrada
no CLAUDE.md ("Frontend consumindo a API").
151. Revisão de interface: estrutura comum (login, Principal, sino, conta, widgets) (2026-10-01)
Continuação da revisão de interface (ver rodada 150 neste arquivo), na frente
da estrutura comum: o que todo shell carrega (api.js, auth.js,
access.js, account.js, favorites.js, notifications.js, theme.js,
ajuda-aplicacao.js, tokens.css, base.css, layout.css,
components.css) mais index.html/login.css e portal.html/widgets.js/
widgets.css. As telas de cada aplicação foram corrigidas em paralelo por
outras frentes, no CHANGELOG.md de cada uma.
- Erro de carga deixa de ser silencioso:
pidRequireAuth(auth.js) distingue 401 (pidApiRequestjá redireciona e devolvenull) de qualquer outro erro (servidor fora, 500), que agora mostrapidAlert"Não foi possível carregar seus dados..." em vez de deixar a página sem menu e sem explicação. No sino, falha ao buscar compromissos ou notificações dispensadas não derruba mais o resto (cada carga emtry/catch), e vira aviso dentro da lista, nunca "Nenhuma notificação". O mesmo em favoritos e widgets. - Mensagem genérica com próximo passo (
api.js,PID_ERRO_GENERICO): "Erro ao processar a solicitação. Tente novamente; se persistir, contate a Inovação." - Envio duplo e falha silenciosa: estrela/remover/reordenar favoritos,
adicionar/remover/reordenar widgets, dispensar/Limpar tudo/Restaurar
notificação, Salvar liderados, Salvar senha e Salvar da Ajuda travam o
próprio controle durante a requisição e avisam a falha por
pidAlert. Na reordenação (favoritos e widgets), a tela sempre recarrega do servidor depois do PATCH, então uma falha volta à ordem realmente gravada. Falha ao salvar o tamanho de widget avisa (antes.catch(() => {})). Remover favorito e remover widget pedem confirmação simples (não destrutiva). - Sair sem salvar só quando há alteração: Gerenciar Usuário (compara os
liderados vinculados com
me.liderados, inclusive ao ir pra "Alterar senha") e Ajuda de aplicação (compara o HTML do editor com o da abertura). Troca de senha com sucesso mostra "Senha alterada.". - Armazenamento bloqueado (janela privada, política do navegador): leitura e
gravação de tema em
theme.jse osessionStorageda intro emauth.jsemtry/catch. - Login: campo de login com
spellcheck="false"/autocapitalize="none", placeholders com "…", "Contate a Inovação" deixou de ser linkhref="#"(texto com o mesmo destaque,.login-help__destaque),.btn-primarycom estado:disabled. portal.html: placeholders com "…", botão "Fechar" no modal "Adicionar Widget", campos de senha comnamee usuário ocultoautocomplete="username"(preenchido poraccount.jscomme.username) pro gerenciador de senhas. Os modais de senha dos outros shells não foram alterados nesta frente.- CSS transversal:
color-schemedark/light emtokens.css(edarkfixo na sidebar e no card do login, que são congelados escuros);select/textareaherdam fonte/cor;touch-action: manipulatione sem realce de toque em controles;overscroll-behavior: containem modal e lista de notificações; estado:disabledpara.btn-solid/.btn-outline/.btn-ghost/.btn-danger-outline(esmaecido, sem hover nem escala); título de notificação quebra palavra longa; nome no.persona-chipcorta com reticências; botões de remover de favorito/widget visíveis em tela de toque (@media (hover: none)); datas do widget de agenda com algarismos tabulares. - Datas:
notifications.jsewidgets.jsformatam comIntl.DateTimeFormat("pt-BR"), montando aDatepelas partes da data pura ("AAAA-MM-DD"), sem passar pornew Date(string)(que seria UTC e cairia no dia anterior).
Fora desta rodada, de propósito (pedido do usuário, fica pra outra): tirar
defer de theme.js/portal-reveal.js, pular as intros do login, estado na
URL, trocar animações de filter, fontes do login/sidebar, favorito como <a>.
Nada de acessibilidade de teclado/leitor de tela nem prefers-reduced-motion.
Validação: só frontend, sem banco, API ou migration, não exige reiniciar o
runserver. Todos os JS alterados passaram em node --check. Não testado no
navegador (sem renderização neste ambiente).
Limitações conhecidas / decisões assumidas
- Calendário De Paula (empresa) não tem mais versão interna — depende
inteiramente do link externo
depaula-tvcorporativa.lovable.app(ver rodada 9 noCHANGELOG.mdde Calendário Individual). Não recriar umcalendario.htmlinterno sem confirmar com o usuário. - Sem teste automatizado (nem no frontend, nem no backend Django) — todo o
fluxo é validado manualmente no navegador ou via
django.test.Client/APIRequestFactoryad-hoc dentro de cada rodada de implementação, nunca numa suíte que rode sozinha. - Tema (claro/escuro e cor) continua só no
localStorage— é preferência de navegador, decisão deliberada de manter fora da migração para o banco. - Limitações específicas de escopo de cada aplicação (v1 cobrindo só uma
modalidade/departamento, uma operadora ainda não validada com arquivo
real, etc.) estão documentadas no
README.md/CLAUDE.mdde cada aplicação — verREADME.mdna raiz pro mapa completo.
Roadmap / próximos passos
Nenhuma pendência estrutural explícita em aberto no momento — cada rodada acima foi fechada a pedido do usuário. Ao retomar o projeto, perguntar o que vem a seguir em vez de assumir.
Uma limitação específica de aplicação que segue em aberto: o Indicador de
Desempenho resolve o departamento de um colaborador pelo gerente, o que
quebra o caso de um gerente que supervisiona pessoas de departamentos
diferentes (ver portal_api/indicadores/CHANGELOG.md, rodada 45, e
portal_api/indicadores/CLAUDE.md → "Departamento organizacional") — só
mexer nisso se o usuário confirmar que quer uma exceção por colaborador.
O passo natural mais amplo que falta é validar de ponta a ponta num
navegador de verdade um conjunto de funcionalidades que só foram revisadas
estaticamente ou testadas parcialmente logo após a migração pra Django
(rodadas 13–14): Links & Ferramentas, a tela de Ramais completa, Acessos
Gerais (incluindo a restrição de seção por perfil e as observações ricas
com imagem), Eventos Corporativos no Calendário Individual com um perfil
sem a permissão de criar evento, Importação de Plano de Saúde ponta a
ponta com um arquivo real de cada operadora, Simulação de Custo de
Contratação conferindo o PDF gerado e a Lei 15.270/2025, e Indicador de
Desempenho com uma apuração completa nova — ver o CHANGELOG.md de cada
aplicação para o que já foi de fato testado rodada a rodada desde então.
A tela de Não Conformidades (rodadas 88–89) já foi validada de ponta a
ponta, inclusive em navegador de verdade via Playwright (login, drill-downs
do Dashboard, expandir ocorrência/ação, marcar tratado/reabrir, filtros
combinados) — ver portal_api/nao_conformidades/CHANGELOG.md. Falta só o
upload real do formulário "Nova Importação" pelo navegador (testado até
agora só via API), e a conferência do usuário sobre o resultado visual do
redesign da rodada 89 e dos novos cards de tempo médio da rodada 90.