468 lines
53 KiB
Markdown
468 lines
53 KiB
Markdown
# 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 `localStorage` do
|
||
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) e `bruno`/`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.md` pro 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.js` ficou preso na assinatura antiga** de
|
||
`pidResolveAccess()` (`{ profile }` em vez de `{ profiles }`) depois da
|
||
migração da rodada 5 (ver `docs/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óprio `display` (`.no-access`, `.notif-badge`, `.app-card`) ignoravam o
|
||
`hidden` do JS, porque uma regra de autor com `display` explí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 em `base.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:hidden` para esconder, o que
|
||
não impede clique nem navegação por Tab. Corrigido adicionando
|
||
`visibility:hidden` + `pointer-events:none` ao 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:
|
||
|
||
1. Construir esse backend completo (models, API REST, banco Postgres) —
|
||
não só adaptar o frontend a um contrato assumido.
|
||
2. 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.
|
||
3. 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
|
||
único `portal_api/`) — reorganizado na rodada 14 para o padrão de pastas
|
||
de um projeto Django de verdade. Ver `CLAUDE.md` → "Arquitetura" para o
|
||
detalhamento de cada arquivo e a tabela de endpoints.
|
||
- Model de usuário customizado (`Usuario`, estendendo `AbstractUser`) 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_APPS` hardcoded em `profiles.js`) virou `portal_api/catalogo.py`,
|
||
fonte única da verdade, exposto só leitura via `GET /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()` em `views.py`) e consumida pronta via
|
||
`GET /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 `localStorage` foi reescrito para
|
||
`async`/`fetch` contra a API (`assets/js/api.js`, hoje `static/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/` e `portal_api/` subiram
|
||
de `Portal/backend/` para a raiz de `Portal/` — `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á que `TEMPLATES[0]["DIRS"]` aponta agora.
|
||
- `assets/css`, `assets/js` e `img/` viraram `static/css`, `static/js` e
|
||
`static/img` (pasta `assets/` removida) — `STATICFILES_DIRS` (novo em
|
||
`settings.py`) aponta pra lá, e `STATIC_ROOT` foi adicionado para
|
||
`collectstatic` em 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.py` perdeu os `re_path`/`static_serve` manuais para
|
||
`assets/`/`img/` — `django.contrib.staticfiles` já serve `static/`
|
||
automaticamente em `DEBUG` a partir de `STATICFILES_DIRS`, sem
|
||
configuração extra.
|
||
- Bug corrigido de passagem: o `.env` já existia (com as credenciais do
|
||
Postgres) mas `settings.py` nunca chamava `load_dotenv()` — ele nunca
|
||
tinha sido lido de verdade. Corrigido ao mexer em `settings.py` por causa
|
||
da mudança de `BASE_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.innerHTML` era reconstruído (removendo o botão clicado do DOM) antes
|
||
do clique terminar de subir até o listener global em `document` que 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 com `event.stopPropagation()` no handler
|
||
de dismiss em `notifications.js`.
|
||
- **Notificação reaparecendo no reload**: era o comportamento documentado
|
||
até então (`notifications.js` guardava a lista só em memória, "zera a cada
|
||
reload") — o usuário pediu explicitamente para persistir no backend em vez
|
||
de usar `localStorage`. Criado o model `NotificacaoDispensada` (chave
|
||
natural `usuario` + `notif_id`, mesmo padrão de `Favorito`/`WidgetUsuario`)
|
||
e o endpoint `/api/notificacoes-dispensadas/`. `notifications.js` agora
|
||
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`) usava `btn-outline`, destoando dos outros dois botões da mesma barra de ações ("Adicionar Ramal", "Novo Chamado"), ambos `btn-solid`. Trocado pra `btn-solid`, sem nada mais mudar no comportamento — ver `docs/ramais/CHANGELOG.md` pro 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 em `tokens.css` (`:root[data-theme="light"]`) só pra essas três variáveis; fundo/borda da sidebar continuam frozen como antes. `CLAUDE.md` atualizado 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) pra `pid-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-*` em `tokens.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 entre `pid-icone.svg`/`pid-icone-escuro.svg` conforme `data-theme` (`pidSyncLoginLogo()` em `theme.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 de `portal.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__brand` passou a ter duas logos sempre no DOM, sobrepostas com `position:absolute` dentro de um container de altura fixa, alternando por `opacity`+`scale()` (crossfade, não troca de `src`) 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`) e `static/js/login-intro.js` (motor de animação — easing/`animate`/`clamp`/render por `requestAnimationFrame`, tudo portado das fórmulas extraídas). Cores/formas idênticas às já usadas em `pid-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 de `index.html`, junto de "Bree Serif"/"Pinyon Script" já usadas ali.
|
||
- **Fluxo**: `auth.js`, no sucesso do POST de login, chama `pidPlayLoginIntro(onDone)` em vez de navegar direto — só ao final da animação (`onDone`) marca `sessionStorage.pid_reveal_portal="1"` e navega pra `portal.html`.
|
||
- **"Aparecendo primeiro pela barra lateral"**: novo `static/js/portal-reveal.js` (só `portal.html`, IIFE de topo tipo `theme.js`) — se a sessionStorage tiver a marca, aplica `html.pid-entering` antes da primeira pintura (evita flash) e remove a classe ~350ms depois do `DOMContentLoaded`. `layout.css` esconde `.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 a `portal.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 natural `app_key`, `texto`, `atualizado_em`/`atualizado_por`) + endpoint `GET`/`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/` ganhou `eh_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 via `nh3` antes 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 de `api.js` em **todo** shell, inclusive `index.html`): `pidConfirm(mensagem, opcoes)` (Promise<boolean>) e `pidAlert(mensagem, opcoes)` (Promise<void>) 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.perigoso` troca 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 por `pidConfirm`/`pidAlert`. Um modal bespoke que já existia em `importacao-plano-saude.js` só pra esse mesmo motivo (`#ips-regracad-confirm-fechar-modal`, no "Cadastro de Regras") foi removido e consolidado no componente genérico. **Não migrado**: um `window.prompt()` em `users-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 campo `access` (`{type:"gerencia"}` ou `{type:"module", module:"..."}`) reproduzindo o mesmo gate que já esconde o item correspondente no menu (`pidApplyAccessVisibility` em `access.js`) — `pidNotifToolElegivel()` filtra a lista antes de montar `toolNotifications`, 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) virou `dataIso: "AAAA-MM-DD"` — `pidNotifToolExpirada()` compara contra a data de hoje (`PID_NOTIF_TOOL_EXPIRA_DIAS = 10`); passado o prazo, `notifications.js` chama `POST /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 a `pidEventosElegiveisAgora()` 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
|
||
|
||
Pedido (2026-08-27): evoluir a skill do Claude `analise-ncs` (ver `projects/Controladoria - Analise NCs/`), que gerava sob demanda um Excel de 7 abas a partir de dois arquivos exportados do Sigsistem (ocorrências .xlsx + ações .xls, esse último HTML disfarçado), pra uma aplicação dentro do Portal com gestão **contínua**: identificar novas ocorrências sem análise, análises sem ação, acompanhamentos recentes sem tratativa da Qualidade, priorizar vencidas/vencendo em 7 dias, permitir marcar manualmente o que foi tratado, e um dashboard (ações abertas, motivos de abertura, clientes/colaboradores com maior incidência).
|
||
|
||
Decisões de negócio confirmadas antes de implementar: status de tratativa é controle interno do Portal (sem escrita de volta no Sigsistem), mas reabre sozinho quando uma importação nova traz algo diferente do que existia no momento do tratamento; guardar o histórico completo de acompanhamentos (não só o último, como a skill fazia); colaborador com maior incidência = `Indicado para Descrever Análise da Ocorrência`; motivo de abertura = `Tipo(s) de Causa(s) da Ocorrência`; permissão de toggle único **restrita por padrão** (só Integração e Inovação nasce com acesso — diferente de "Relatório Setorial", que herda automático via `SECTORAL_KEYS`).
|
||
|
||
Nova aplicação em Relatórios > Qualidade (subgrupo novo em `catalogo.py`, mesmo formato de subgrupo-com-uma-tool já usado em Auditorias) — pacote Python puro `portal_api/nao_conformidades/` (sem pandas, mesmo padrão de `planos_saude`/`indicadores`), 4 models novos, 3 `ModelViewSet` + dashboard, tela nova com 3 abas. Núcleo técnico: reabertura automática por diff (`nao_conformidades/diff.py`) — cada ocorrência/ação tratada guarda um snapshot congelado, comparado a cada importação nova contra o estado atual (ação por data do último acompanhamento; ocorrência por hash de texto da análise ou ação nova aberta). Validado de ponta a ponta com dados reais da amostra fornecida: totais batem com o relatório de referência da skill em tudo, exceto "Total de Ações Abertas" (a skill original contava também linhas sem nenhuma ação de fato aberta — artefato do script, decisão deliberada de não replicar); reimportação idempotente; dois bugs de robustez corrigidos (`assunto` virou `TextField` — um export real trouxe 395 caracteres; e um `SerializerMethodField` que chamava uma propriedade inexistente no model). Interação em navegador não testada nesta rodada (sem ferramenta de automação de browser disponível) — pipeline/API validados via chamadas HTTP diretas, tela construída seguindo os padrões visuais já validados de `importacao-plano-saude.html`/`indicador-desempenho.html`. Detalhe completo em `portal_api/nao_conformidades/CLAUDE.md`.
|
||
|
||
### 89. Não Conformidades — redesign de Dashboard/Gestão a partir de teste real na tela
|
||
|
||
Depois de testar a rodada 88 na tela, o usuário deu uma lista concreta de feedback de UX. Resumo (detalhe completo em `portal_api/nao_conformidades/CHANGELOG.md`): Dashboard virou a aba padrão ao abrir; 4 cards e as 3 tabelas de ranking ficaram clicáveis (drill-down direto pra Gestão já filtrada); rankings de Clientes/Colaboradores passaram a mostrar a contagem quebrada por tipo com cor (NC/Reclamação/Outros) em vez de restringir o filtro, a pedido explícito do usuário; dois KPIs de tempo médio novos (preenchimento de análise, abertura de ação) — um terceiro (resolução de ações) foi descartado antes de implementar porque o campo correspondente está 100% vazio no export real, e o usuário confirmou pra não criar um card sem dado; seletor de período virou chips em vez de `<select>`. Na Gestão: toda linha ganhou um botão de expandir (mesmo padrão de `.cc-painel-fiscal__toggle`) que revela ocorrência completa ou o histórico de acompanhamentos (mais recente primeiro) — substituiu o modal de histórico, que ficava colado no botão "Marcar como tratado"; a coluna única "Status" de Ações virou duas (Prazo e Tratativa), com filtros em chips combináveis; um campo de busca único passou a filtrar as 3 listas da Gestão de uma vez, por colaborador/empresa/assunto.
|
||
|
||
Dois bugs reais corrigidos durante o teste: uma corrida (`ativarTab()` tinha efeito colateral de carregamento que corria em paralelo com o carregamento explícito de um drill-down, e o que terminasse por último vencia — às vezes mostrando o filtro errado) e um alinhamento de texto (célula de painel de detalhe herdava `text-align:right` de `.pa-table td:last-child` por ser sempre "a única/última" célula da linha). Instalado Playwright ad-hoc neste ambiente pra validar a tela de ponta a ponta (login, drill-downs, expandir, alternar tratativa, filtros combinados) — não é dependência do projeto, só ferramenta de teste desta sessão.
|
||
|
||
## 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 no `CHANGELOG.md` de Calendário Individual). Não recriar um
|
||
`calendario.html` interno 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`/
|
||
`APIRequestFactory` ad-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.md` de cada
|
||
aplicação — ver `README.md` na 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.
|
||
|
||
### 90. Não Conformidades — tempo médio de execução da ação e ciclo completo
|
||
|
||
O usuário trouxe um export do Sigsistem diferente do original ("com
|
||
finalizadas" — traz o histórico de ações já concluídas, 3474 linhas contra
|
||
287 do export padrão, `Data de Finalização` preenchida em 3187), viabilizando
|
||
o terceiro KPI de tempo médio que tinha sido descartado na rodada 89 por
|
||
falta de dado real. `tempos_medios` do dashboard ganhou uma 3ª etapa
|
||
(execução da ação, abertura → finalização) e um KPI de ciclo completo
|
||
(emissão da ocorrência → finalização da ação), cada um com o tamanho da
|
||
amostra (`n`) junto da média — inclusive retroativo às 2 etapas que já
|
||
existiam. A execução também é quebrada por categoria de ação
|
||
(Correção/Ação Corretiva/Outros), porque a média geral (~62 dias) escondia
|
||
uma variância enorme (1 a ~793 dias) entre tipos bem diferentes. No
|
||
frontend, uma primeira versão trocou os cards por um funil visual com setas
|
||
entre etapas — revertida no mesmo dia a pedido do usuário, que preferiu
|
||
manter o card grid original (agora com 4 cards em vez de 2, cada um com o
|
||
`n` da amostra). Detalhe completo em
|
||
`portal_api/nao_conformidades/CHANGELOG.md` (rodada 90) e
|
||
`portal_api/nao_conformidades/CLAUDE.md` (seção "Tempos médios").
|
||
|
||
### 91. Não Conformidades — upload travando com arquivo real grande (upsert virou lote)
|
||
|
||
Usuário reportou "Erro ao processar a solicitação." ao reprocessar os mesmos dois arquivos "com finalizadas" da rodada 90, e perguntou se seria a extensão `.xls`/`.xlsx` ou incompatibilidade com o ambiente Linux de produção. Nenhum dos dois: reproduzido localmente com os arquivos reais, o upload funcionava, só levava ~60s — tempo o bastante pra estourar o timeout de um worker WSGI em produção (gunicorn, 30s por padrão) ou disparar o autoreload do `manage.py runserver` em dev, derrubando a conexão sem nenhum problema de dado por trás. Causa raiz: o upsert (rodada 88) fazia uma ida ao banco por item (`update_or_create`/`get_or_create`), aceitável nos ~300 registros da amostra inicial mas não nos ~1700 ocorrências/~3400 ações/~9000 acompanhamentos do export real (quase 28 mil idas ao banco). Reescrito pra lote (`bulk_create`/`bulk_update`) e pra pular quem não mudou nada desde a última importação (reimportação periódica traz de volta o histórico inteiro, não só o novo) — reimportar o mesmo arquivo sem mudança real caiu de ~60s pra ~4s. Nenhuma migração, nenhuma mudança de contrato de API. Detalhe completo em `portal_api/nao_conformidades/CHANGELOG.md` (rodada 91) e `portal_api/nao_conformidades/CLAUDE.md` (seção "Upsert").
|