# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Contexto do projeto Portal interno da De Paula Contadores ("Portal De Paula"). `Portal/` **é** o próprio projeto Django — **Python 3.13 + Django 6.0 + Django REST Framework + PostgreSQL 14** — organizado no padrão convencional de um projeto Django (`manage.py` na raiz, app `portal_api/`, `templates/`, `static/`), servindo tanto a API (`/api/...`) quanto o frontend HTML/CSS/JS (mesma origem — ver "Arquitetura" abaixo). Até uma rodada anterior, todo o estado (sessão, usuários, perfis de acesso, favoritos, widgets, compromissos) vivia no `localStorage` do navegador — não havia backend. Isso mudou: o usuário decidiu a stack real e pediu a migração completa desses dados para o banco. Ver `plano.md` para o histórico de decisões rodada a rodada; consultar antes de mudar algo que pareça uma limitação (ex.: ausência de teste automatizado, remoção do calendário interno antigo) sem confirmar se foi decisão deliberada. **O que continua só no `localStorage`**: apenas a preferência de tema (claro/escuro e cor do tema) — é preferência de navegador, não dado de negócio, e ficou fora do escopo da migração por decisão explícita do usuário. ## Documentação dividida por aplicação Este arquivo cobre o que é **transversal** ao Portal (arquitetura, modelo de permissões, API, CSS, animações). A partir de 2026-08-26, a documentação detalhada de cada aplicação foi movida pra fora daqui, pra reduzir conflito de edição quando mais de uma pessoa mexe em aplicações diferentes ao mesmo tempo. Ver `prd.md` pra visão de produto (o quê/pra quem), `README.md` na raiz pro mapa de todas as aplicações, e `plano.md` pro histórico de decisões **estruturais/transversais** (o histórico específico de cada aplicação vive no `CHANGELOG.md` dela, ver abaixo). Cada aplicação tem uma pasta própria com (a) o detalhamento técnico do estado atual, (b) um `README.md` (resumo enxuto — o quê/pra quem, sem o detalhe técnico) e (c) um `CHANGELOG.md` (histórico rodada a rodada, extraído de `plano.md`). **A numeração de rodada não é global**: cada aplicação conta as próprias, e o mesmo número designa trabalhos diferentes em arquivos diferentes (ex.: "rodada 93" é uma coisa em `plano.md` e outra em `portal_api/dashboard_contabil/CHANGELOG.md`). Ao citar uma rodada, **sempre nomear o arquivo** — "ver rodada 45 em `portal_api/indicadores/CHANGELOG.md`", nunca só o número. Ver o topo de `plano.md` para o detalhamento. Aplicações com pacote Python próprio (`CLAUDE.md` **carregado automaticamente** pelo Claude Code ao trabalhar dentro da pasta): | Aplicação | Onde | |---|---| | Importação de Plano de Saúde | `portal_api/planos_saude/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | | Indicador de Desempenho | `portal_api/indicadores/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | | Simulação de Custo de Contratação | `portal_api/custo_contratacao/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | | Não Conformidades | `portal_api/nao_conformidades/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | | Relatório Contábil | `portal_api/dashboard_contabil/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | | Conciliação de Fornecedores | `portal_api/conciliacao_fornecedores/` — `CLAUDE.md` (técnico), `README.md`, `CHANGELOG.md` | Aplicações sem pacote Python dedicado (código ainda em `portal_api/models.py`/`views.py`/`serializers.py` — os arquivos em `docs//` **não** são carregados automaticamente, ler manualmente): | Aplicação | Onde | |---|---| | Ramais (diretório, Telefones Externos, Funções de Telefonia, modal de consulta rápida) | `docs/ramais/` — `ramais.md` (técnico), `README.md`, `CHANGELOG.md` | | Links & Ferramentas / Acessos Gerais | `docs/links-ferramentas-acessos-gerais/` — `links-ferramentas-acessos-gerais.md` (técnico), `README.md`, `CHANGELOG.md` | | Calendário Individual e Widgets (incl. Eventos Corporativos, feriados, widgets de `portal.html`) | `docs/calendario-individual/` — `calendario-individual.md` (técnico), `README.md`, `CHANGELOG.md` | | Favoritos (grade de `portal.html`) | `docs/favoritos/` — `favoritos.md` (técnico), `README.md`, `CHANGELOG.md` | | Perfis de Acesso / Usuários (telas administrativas, Liderança, inativação) | `docs/perfis-usuarios/` — `perfis-usuarios.md` (técnico), `README.md`, `CHANGELOG.md` | | Solicitações | `docs/solicitacoes/` — `solicitacoes.md` (técnico), `README.md`, `CHANGELOG.md` | | Temas Sazonais (marca P.I.D. sazonal — hoje só Halloween) | `docs/temas-sazonais/` — `temas-sazonais.md` (técnico), `README.md`, `CHANGELOG.md` | | Identidade visual (logos, marca P.I.D. na UI, animações genéricas, intro pós-login) — não é aplicação do menu, é camada transversal | `docs/identidade-visual/` — `identidade-visual.md` (técnico), `README.md`, `CHANGELOG.md` | **Cada endpoint mora na doc da sua aplicação.** A tabela de API mais abaixo tem só os transversais (auth, `/api/me/`, catálogo, perfis, usuários, favoritos, widgets, compromissos, notificações). Ao criar uma aplicação nova, documentar os endpoints dela na pasta dela. ## Como rodar / testar localmente ### Backend (obrigatório para qualquer teste agora — o frontend não funciona mais sozinho via `file://`/`http.server`) ``` cd Portal python -m venv .venv .venv\Scripts\activate # Windows pip install -r requirements.txt ``` Configurar as variáveis de ambiente do Postgres 14 antes de migrar — `config/settings.py` chama `load_dotenv(BASE_DIR / ".env")` e lê `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT` (o arquivo `.env` já existe na raiz de `Portal/`): ``` python manage.py makemigrations portal_api python manage.py migrate python manage.py runserver ``` **`python manage.py seed_portal` é só para ambiente novo/vazio (primeira criação do banco)** — não rodar mais neste ambiente. **O Portal já está em produção**: os 8 perfis de acesso e todas as contas de usuário (incluindo `gabriel`/`bruno`) já existem de verdade, com senhas e permissões mantidas pelos próprios usuários direto pela tela (Perfis de Acesso/Usuários) — não mais pelo seed. `seed_portal` resincroniza incondicionalmente `permissoes`/`ativo`/`gerencia_permissoes` dos 8 perfis de código fixo a partir de `catalogo.py` a cada execução (ver "Cuidado com `seed_portal.py`" mais abaixo) — rodar isso contra o banco já em uso reverteria qualquer permissão que um admin tenha customizado manualmente para um desses 8 perfis, sem nenhum aviso. Ao adicionar uma aplicação nova ao catálogo (novo `app_key`), a chave nova ainda entra automaticamente pros perfis certos na próxima vez que `seed_portal` rodar — mas evitar rodar só por causa disso; se for mesmo necessário, avisar o usuário antes e confirmar, e idealmente conferir os 8 perfis em Perfis de Acesso depois pra garantir que nenhuma customização foi perdida. Não há suíte de testes, lint ou build configurados neste projeto. ### Ambiente de desenvolvimento assistido O `.venv` do projeto já tem Python 3.13 + Django 6.0 + DRF + psycopg + python-dotenv + Pillow + nh3 + reportlab + openpyxl + holidays + docling instalados, e o Postgres acessível via `.env` é o **banco de produção** (não uma cópia de desenvolvimento) — dá pra rodar `makemigrations`/`migrate`/`runserver` normalmente por aqui usando `.venv\Scripts\python.exe manage.py ...` (ou ativando o venv primeiro), mas **nunca `seed_portal`** (ver "Como rodar / testar localmente" acima — recria/ressincroniza perfis já em uso de verdade). Isso deixou de ser uma limitação a partir da rodada em que o ambiente ganhou essas ferramentas (ver `plano.md`) — não assumir mais que só é possível revisar o backend estaticamente. ## Arquitetura ### Estrutura de pastas (padrão Django) ``` Portal/ ├── manage.py ├── requirements.txt ├── .env ├── config/ # settings.py, urls.py, wsgi.py, asgi.py — pacote de configuração do projeto ├── portal_api/ # único app Django (models, serializers, views, admin, migrations, seed) ├── templates/ # as 15 páginas HTML (13 shells + index.html + o relatório do Relatório Contábil) (TEMPLATES[0]["DIRS"] em settings.py aponta pra cá) ├── static/ # css/, js/, img/ — STATICFILES_DIRS em settings.py aponta pra cá ├── media/ # upload de usuário (hoje só ícones de LinkFerramenta) — MEDIA_ROOT em settings.py ├── CLAUDE.md └── plano.md ``` ### Logos em `static/img/` **Duas identidades visuais coexistem de propósito**: o logo cursivo "D De Paula Contadores" (`logo.png`/`logo-branco.png`/`logo-mono.png`), usado **só nos documentos e PDFs que a aplicação gera**, e a marca "P.I.D." (`pid-*.svg`), usada **só na UI do Portal** (favicon, login, sidebar). A separação é deliberada, não uma migração incompleta: um documento gerado (contrato, procuração, recibo do Indicador de Desempenho, PDF da Simulação de Custo, relatório do Relatório Contábil) é emitido como se o próprio escritório o tivesse gerado, e carrega a identidade dele perante o cliente. **Não migrar o logo de um gerador de documento para "P.I.D." (nem o contrário numa tela do Portal) sem confirmar com o usuário.** Ver `[[feedback_logos_documentos_vs_portal]]` na memória. Ver `docs/identidade-visual/identidade-visual.md` para o inventário de cada arquivo, quem consome cada um, o crossfade de `sidebar__brand` e o mecanismo de piscar os olhos do ícone (que exige `` inline, não ``). ### Backend serve o frontend (mesma origem) `config/urls.py` registra `path("api/", include("portal_api.urls"))` e, para cada uma das **15 páginas HTML** do frontend (`index.html` mais os 14 shells — ver a tabela em "Páginas" abaixo), uma rota `TemplateView` que resolve o arquivo em `templates/`. O 15º template, `dashboard-contabil-relatorio.html`, não tem rota própria: é renderizado por uma view do Relatório Contábil, não navegável pela URL. Os estáticos (`static/css`, `static/js`, `static/img`) são servidos por `django.contrib.staticfiles` automaticamente em `DEBUG` (via `STATICFILES_DIRS`) — não há mais nenhum `re_path`/`static_serve` manual em `urls.py`. Cada template usa `{% load static %}` + `{% static 'css/tokens.css' %}` (nunca um caminho hardcoded tipo `assets/css/...`, que não existe mais). Essa escolha (Django servindo o próprio frontend) existe para evitar CORS/cookie cross-origin: autenticação é por **sessão/cookie do Django**, então frontend e API precisam estar na mesma origem. Em produção, rodar `python manage.py collectstatic` (junta tudo em `STATIC_ROOT = BASE_DIR / "staticfiles"`) e servir esse diretório via whitenoise/nginx — `django.contrib.staticfiles` só serve automaticamente quando `DEBUG=True`. Uploads de usuário (ícones de `LinkFerramenta`) são um mecanismo separado: `MEDIA_URL`/`MEDIA_ROOT` em `settings.py`, servidos por `config/urls.py` via `static()` só quando `DEBUG=True` (em produção, servir `media/` também por whitenoise/nginx, igual ao `STATIC_ROOT`). ### Apps Django Um único app Django, `portal_api/`. **Os models, serializers e views de todas as aplicações vivem nos arquivos compartilhados** (`models.py` com ~2.600 linhas, `views.py` com ~5.100, `serializers.py` com ~2.800) — os pacotes abaixo contêm só lógica pura, sem ORM. Consequência prática: mexer nos models ou nas views de uma aplicação **não** carrega o `CLAUDE.md` dela automaticamente, porque esses arquivos não estão dentro do pacote. Ao trabalhar num model `Contabil*`, `NaoConformidade*`, `Indicador*`, `ImportacaoPlanoSaude*`, `Ramal*`, `AcessoGeral*` ou `LinkFerramenta*`, abrir a doc da aplicação correspondente (tabela em "Documentação dividida por aplicação" acima). | Arquivo | Conteúdo | |---|---| | `models.py` | Todos os models do projeto. Os transversais: `Usuario` (`AbstractUser` + `nome`, M2M `perfis`, M2M `departamentos`, campos cadastrais opcionais `codigo_folha`/`codigo_questor`/`codigo_tareffa`/`codigo_contabit`/`ramal`, `data_aniversario`, `lideranca` e M2M `liderados` self-referential com `related_name="lideres"`; método `permissao_app(module_key, app_key)` — união genérica de um flag de `apps` entre os perfis vinculados; método `eh_perfil_inovacao()` — checagem de nome fixo), `PerfilAcesso` (`permissoes` em `JSONField` + o booleano dedicado `gerencia_permissoes`), `Departamento` (só `nome`, cadastrado inline pela tela de Usuários, sem tela própria), `AjudaAplicacao`, `CompromissoAgenda`, `Favorito`, `WidgetUsuario`, `NotificacaoDispensada`. Os models de cada aplicação estão documentados na doc dela. | | `catalogo.py` | Fonte única da verdade do catálogo de módulos/aplicações do menu (`MODULES`, `MODULE_APPS`) — exposto só leitura via `GET /api/catalogo/`. Ao adicionar uma seção/aplicação nova ao menu, editar **aqui**, nunca em `static/js/profiles.js` (que só cacheia o payload recebido). | | `serializers.py` | Idem: todos os serializers. Os transversais são `PerfilAcessoSerializer`, `DepartamentoSerializer`, `UsuarioResumoSerializer` (`id`/`nome`/`departamentos`, usado nos dois lados de `liderados` e por `/api/usuarios-resumo/`), `UsuarioSerializer` (escrita) / `UsuarioListSerializer` (leitura, aninhados), `CompromissoAgendaSerializer`, `FavoritoSerializer`, `WidgetUsuarioSerializer`, `NotificacaoDispensadaSerializer`, `AjudaAplicacaoSerializer`. Também as constantes de allowlist do `nh3` (`RICHTEXT_ALLOWED_TAGS`/`_ATTRS`/`_SCHEMES`), compartilhadas por todos os campos de texto rico do projeto. | | `permissions.py` | `PodeGerenciarPermissoes` — gate único de `gerencia_permissoes()` para as telas administrativas; `PermissaoApp(module_key, app_key)` — classe genérica reutilizável que checa `Usuario.permissao_app()`, instanciada por view. Nenhuma subclasse nova é necessária para uma aplicação adotar o padrão, só instanciar com outra `app_key`. | | `views.py` | Idem: todas as views. As transversais são `login_view`/`logout_view`/`csrf_view`, `me_view` (usuário + `permissoes_efetivas` já unidas no servidor + `lideranca`/`liderados` + `eh_perfil_inovacao`), `trocar_senha_view`, `usuarios_resumo_view`, `meus_liderados_view`, `departamentos_resumo_view`, `catalogo_view`, `ajuda_aplicacao_view`, e os `ModelViewSet` de perfis/departamentos/usuários/compromissos/favoritos/widgets/notificações dispensadas. | | `admin.py` | Django admin básico para todos os models (uso interno, não é a UI do portal). | | `templatetags/contabil_extras.py` | Filtros de template (`moeda`/`percentual`/`indice`/`competencia`/`mes_curto`/`moeda_av`/`percentual_av`/`numero_bruto`) — único uso de template tags customizadas no projeto, só pelo relatório do Relatório Contábil. | | `management/commands/seed_portal.py` | Cria os 8 perfis padrão + `gabriel`/`bruno` + as 13 linhas de `FuncaoTelefonia` + o seed de `CategoriaEvento`; também realinha a sequence do Postgres por trás de `PerfilAcesso.codigo`. **Não rodar neste ambiente** — ver "Como rodar / testar localmente" acima. | | `management/commands/seed_indicador_desempenho.py` | Popula o primeiro histórico do Indicador de Desempenho (7 `IndicadorCriterio` + 5 `IndicadorPercentualTipo`, idempotente). | Pacotes Python puros (sem ORM), um por ferramenta — cada um com seu próprio `CLAUDE.md`/`README.md`/`CHANGELOG.md`: | Pacote | Ferramenta | |---|---| | `planos_saude/` | Pipeline de extração/casamento de "Importação de Plano de Saúde" (parsers por operadora, `matcher`, leiaute do Questor, regras de custeio). Serve as **duas** instâncias da ferramenta (clientes e De Paula). | | `custo_contratacao/` | "Simulação de Custo de Contratação" (Geradoc) — `tabelas.py` (faixas de INSS/IRRF), `calculo.py`, `pdf.py` via `reportlab`. | | `indicadores/` | "Indicador de Desempenho" (Geradoc) — `tipos.py`, `leiaute.py` (`openpyxl`), `pipeline.py`, `entregas.py`, `calculo.py`, `recibo.py` (PDF via `reportlab`), `departamentos.py`. | | `nao_conformidades/` | "Não Conformidades" (Relatórios > Qualidade) — leiautes dos exports do Sigsistem, `diff.py` (reabertura automática), `classificacao.py`, `pipeline.py`. | | `conciliacao_fornecedores/` | "Conciliação de Fornecedores" (Utilitários) — `parser.py` (razão do Questor em XLSX/CSV), `motor.py` (vínculos automáticos débito × crédito), `alertas.py` (situação/alertas recalculados a cada leitura), `exportacao.py` (XLSX). | | `dashboard_contabil/` | "Relatório Contábil" (Relatórios > Contabilidade) — `parser.py` (extração do PDF), `regras.py` (motor de auditoria), `formula.py` (avaliador de fórmula por `ast`), `indicadores.py`, `chaves.py` (chave natural de conta/linha, compartilhada por sincronização, observações e relatório), `exportacao.py` (XLSX), `resumo_pdf.py`. | ### API (sessão + CSRF, não token) | Endpoint | Método | Uso | |---|---|---| | `/api/auth/csrf/` | GET | garante o cookie `csrftoken` | | `/api/auth/login/` | POST | `{username, password}` → cria sessão | | `/api/auth/logout/` | POST | encerra sessão | | `/api/me/` | GET | usuário logado + `perfis` + `departamentos` (os próprios, pra alimentar o seletor de "Meu departamento" do Calendário Individual) + `gerencia_permissoes` + `eh_perfil_inovacao` (perfil "Inovação" vinculado, ver "Ajuda de aplicação" abaixo) + `permissoes_efetivas` (união já calculada no servidor) | | `/api/me/senha/` | POST | `{senha_atual, nova_senha}` | | `/api/me/liderados/` | PATCH | `{liderados: [id, ...]}` — só se `me.lideranca`; auto-gerenciamento de liderados (ver `docs/perfis-usuarios/perfis-usuarios.md`) | | `/api/catalogo/` | GET | módulos/aplicações/subgrupos do menu | | `/api/feriados/?ano=AAAA` | GET | feriados nacionais + estaduais (PR) do ano pedido (default: ano atual), via lib `holidays` — ver `docs/calendario-individual/calendario-individual.md` | | `/api/usuarios-resumo/` | GET | lista enxuta (`id`/`nome`) de usuários ativos — alimenta o seletor de liderados, sem exigir `gerencia_permissoes` (mesmo padrão de `/api/ramais/usuarios/`) | | `/api/departamentos-resumo/` | GET | lista enxuta (`id`/`nome`) de departamentos — alimenta os botões de filtro do modal de consulta rápida de Ramais, exige só `ramais-visualizar` (não `gerencia_permissoes` como `/api/departamentos/`) | | `/api/ajuda-aplicacoes//` | GET/PATCH | texto de "Mais informações" de uma aplicação (ver seção própria abaixo); GET livre a qualquer autenticado, PATCH exige `eh_perfil_inovacao` (perfil "Inovação", checagem de nome fixo, não uma flag em Perfis de Acesso) | | `/api/perfis/`, `/api/perfis/{codigo}/` | GET/POST/PUT/DELETE | CRUD de perfil — só quem tem `gerencia_permissoes` | | `/api/departamentos/`, `/api/departamentos/{id}/` | GET/POST/PUT/DELETE | CRUD de departamento — só quem tem `gerencia_permissoes`; usado pela tela de Usuários pra listar o checklist e cadastrar um novo departamento inline (sem tela própria) | | `/api/usuarios/`, `/api/usuarios/{id}/` | GET/POST/PATCH/DELETE | CRUD de conta — só quem tem `gerencia_permissoes`; `departamentos` é M2M igual `perfis` (lista de ids na escrita, objetos aninhados na leitura); `is_active` é gravável via PATCH (inativar/reativar, ver `docs/perfis-usuarios/perfis-usuarios.md`) | | `/api/compromissos/`, `/api/compromissos/{id}/` | GET/POST/PATCH/DELETE | GET já retorna só o que o usuário logado pode ver (próprios + `visibilidade="todos"` + `visibilidade="departamento"` com departamento em comum); campo `notificar_em` (calculado, ver `docs/calendario-individual/calendario-individual.md`) indica quando o lembrete passa a valer; criar/editar com `visibilidade` em `departamento`/`todos` (inclusive todo `eh_evento=True`, que força `visibilidade="todos"`) exige `apps["calendario-individual-criar-evento"]` (ver "Eventos Corporativos" abaixo) | | `/api/categorias-evento/`, `/api/categorias-evento/{id}/` | GET/POST/PATCH/DELETE | cadastro de categorias de evento (`nome`+`cor`) usado pelo Calendário Individual; GET livre a qualquer autenticado, escrita exige `apps["calendario-individual-criar-evento"]` — ver `docs/calendario-individual/calendario-individual.md` | | `/api/favoritos/`, `/api/favoritos/{app_id}/` | GET/POST/PATCH/DELETE | chave natural é `app_id`, não um id numérico; `ordem` é gravável via PATCH (drag-and-drop na grade de favoritos, ver `docs/favoritos/favoritos.md`) | | `/api/widgets/`, `/api/widgets/{tipo}/` | GET/POST/PATCH/DELETE | chave natural é `tipo`; `ordem` (reordenar por drag-and-drop) e `largura`/`altura` em px (redimensionamento) também são graváveis via PATCH — ver `docs/calendario-individual/calendario-individual.md` | | `/api/notificacoes-dispensadas/`, `/api/notificacoes-dispensadas/{notif_id}/` | GET/POST/DELETE | chave natural é `notif_id` (ex.: `"tool-widgets"`, `"event-42"`); `notifications.js` usa GET pra filtrar o que já foi dispensado e POST a cada X/"Limpar tudo" | **Esta tabela lista só os endpoints transversais.** Os de cada aplicação moram na doc dela (ver "Documentação dividida por aplicação" acima): Links & Ferramentas/Acessos Gerais, Ramais/Telefones Externos/Funções de Telefonia, Importação de Plano de Saúde (as duas instâncias), Simulação de Custo de Contratação, Indicador de Desempenho, Não Conformidades e Relatório Contábil. Ao criar uma aplicação nova, documentar os endpoints dela lá, não aqui. ### Frontend consumindo a API `static/js/api.js` é a base de tudo: `pidApiRequest(path, options)` faz `fetch` com `credentials:"include"`, injeta `X-CSRFToken` (lendo o cookie `csrftoken`, buscando-o via `/api/auth/csrf/` primeiro se ainda não existir) em métodos não seguros, e redireciona pra `index.html` em 401 por padrão (`redirectOn401: false` para os poucos casos onde 401 é esperado, como o próprio login). `access.js` expõe `pidGetMe()` — chamada única e **cacheada por página** (`pidMePromise`) para `GET /api/me/`. Cada arquivo controlador (`account.js`, `favorites.js`, `widgets.js`, `profiles.js`, `users-admin.js`, `calendar-individual.js`, `links-ferramentas.js`, `acessos-gerais.js`, `ramais-lookup.js`) chama `pidGetMe()` no início do seu próprio `DOMContentLoaded`, mas como todos rodam antes do primeiro `await` resolver, a promise cacheada garante **uma única requisição de rede** por carregamento de página, não uma por arquivo. `pidApiRequest` (em `api.js`) detecta `body instanceof FormData` e, nesse caso, **não** faz `JSON.stringify` nem define `Content-Type` manualmente — deixa o browser montar o `multipart/form-data` com o boundary certo. Usado pelo upload de `icone` em Links & Ferramentas e pelos dois arquivos anexados em "Nova Importação" de Plano de Saúde; todo o resto da API é JSON puro. A única resposta binária da API (`/importacoes-plano-saude/{id}/gerar/`, que devolve CSV/ZIP) não passa por `pidApiRequest` — usa um `fetch` manual dedicado (ver seção "Importação de Plano de Saúde"). **Escape de HTML obrigatório (`pidEscapeHtml`, `api.js`)**: todo dado vindo da API, do usuário ou de arquivo anexado que entra em HTML montado por template string (`innerHTML`, `insertAdjacentHTML`) passa por `pidEscapeHtml(texto)`, que trata `& < > " '` e serve para conteúdo **e** atributo. Sem isso, um texto com HTML (título de compromisso, nome de link, observação, dado do arquivo da operadora) vira código executado no navegador de quem abre a tela, inclusive de outros usuários (XSS armazenado). Corrigido em todo o frontend na revisão de interface de 2026-09-25 (ver `plano.md`). Regras: escapar o **dado**, num único ponto (sem escape duplo), nunca o HTML que o próprio código monta (ícones, fragmentos); dado atribuído por `.textContent`/`.value` já é seguro e não se escapa; **texto rico sanitizado no servidor com `nh3`** (Ajuda, observações de Acessos Gerais, Resumo do Fechamento) é HTML intencional e **não** se escapa. As funções locais de escape que ainda existem (`pidDcEscapeHtml`, `pidNcfEscapeHtml`, `pidIndEscapeHtml`, `pidConcEscape`, `escapeHtml`/`escapeAttr` do Plano de Saúde) delegam a ela; a versão antiga por `textContent`/`innerHTML` não escapava aspas e não deve voltar a ser usada. `pidApplyAccessVisibility` não recalcula mais união de permissões no cliente — usa `me.permissoes_efetivas`, já unida no backend (`permissoes_efetivas()` em `views.py`). ## Páginas | Página | Papel | |---|---| | `index.html` | Login. POST `/api/auth/login/`. | | `portal.html` | Shell principal: busca de aplicações, grade de favoritos, seção de Widgets. | | `calendario-individual.html` | Agenda pessoal: grade mensal + modal de criar/editar compromisso. | | `perfis-acesso.html` | CRUD de perfis de acesso (lista + edição com abas Permissões/Usuários do Escritório). | | `usuarios.html` | CRUD de contas de usuário (lista + edição com checklist de perfis). | | `links-ferramentas.html` | Grade de cartões de atalho para ferramentas externas; reordenar/incluir/remover só com `apps["links-ferramentas-editar"]` (ver `docs/links-ferramentas-acessos-gerais/links-ferramentas-acessos-gerais.md`). | | `acessos-gerais.html` | Cadastro de acessos/logins compartilhados organizados em seções e linhas, com popup de detalhes; criar/editar/excluir seções e linhas só com `apps["acessos-gerais-editar"]` (ver `docs/links-ferramentas-acessos-gerais/links-ferramentas-acessos-gerais.md`). | | `ramais.html` | Diretório de ramais internos, filtros por nome/departamento; adicionar/editar ramal, criar ausência e excluir só com `apps.editar` em `ramais` (ver `docs/ramais/ramais.md`). | | `importacao-plano-saude.html` | Ferramenta de Utilitários: histórico + cadastro de regras de custeio por empresa (separado da execução) + nova importação (upload, só aplica uma regra já cadastrada) + revisão/geração do arquivo de lançamento de plano de saúde (ver `portal_api/planos_saude/CLAUDE.md`). | | `importacao-plano-saude-de-paula.html` | Segunda instância da ferramenta acima, para o plano de saúde dos **próprios colaboradores** do escritório: tabelas, endpoints e permissão próprios, mas mesmo JS/CSS (parametrizados por `window.PID_IPS_CONFIG`) e mesmo pipeline de extração. Ver "Importação de Plano de Saúde - De Paula" em `portal_api/planos_saude/CLAUDE.md`. | | `custo-contratacao.html` | Ferramenta de Geradoc: formulário de simulação de custo de contratação (Empregado CLT) + painel colapsável de parâmetros fiscais, gera um PDF (ver `portal_api/custo_contratacao/CLAUDE.md`). | | `indicador-desempenho.html` | Ferramenta de Geradoc: histórico de apurações + nova apuração (upload das 2 planilhas) + cadastro de critérios/percentuais + revisão/geração dos recibos em PDF do Indicador de Desempenho do Fiscontábil (ver `portal_api/indicadores/CLAUDE.md`). | | `nao-conformidades.html` | Aplicação de Relatórios > Qualidade: gestão contínua das ocorrências/ações do Sigsistem, com dashboard e status interno de tratativa da Qualidade (ver `portal_api/nao_conformidades/CLAUDE.md`). | | `conciliacao-fornecedores.html` | Ferramenta de Utilitários: histórico + nova conciliação (código da empresa com nome buscado no Questor + razão de fornecedores .xlsx/.csv) + detalhe com cards de alerta e tabela de fornecedores expansível, vínculo manual, validação e exportação XLSX (ver `portal_api/conciliacao_fornecedores/CLAUDE.md`). | | `dashboard-contabil.html` | Aplicação de Relatórios > Contabilidade ("Relatório Contábil" na UI): histórico de análises + nova análise (upload do PDF de Balancete + DRE) + revisão de achados de auditoria (com observações por conta) + botão "Gerar Relatório" (relatório HTML autocontido, ver `portal_api/dashboard_contabil/CLAUDE.md`). | O item "Calendário De Paula" no menu **não é uma página local** — é um `` (continua favoritável, já que ainda é um `` — ver `docs/favoritos/favoritos.md`) cujo clique é interceptado em `sidebar.js` (`PID_CALENDARIO_DEPAULA_URL`) pra abrir `https://depaula-tvcorporativa.lovable.app/calendario` num modal com `