48 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. A numeração de rodada usada aqui é a mesma referenciada nos
changelogs de cada aplicação (e vice-versa) — uma rodada que só aparece no
CHANGELOG.md de uma aplicação específica não tem entrada aqui, e
vice-versa.
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 CLAUDE.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).
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.