commit 3a6c3e1815bb573175a1126ef786b8aff10dcff0 Author: Gabriel Date: Tue Aug 18 10:57:39 2026 -0300 Commit inicial do Portal De Paula diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d793fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Ambiente virtual +.venv/ +venv/ +env/ + +# Variáveis de ambiente / segredos +.env +.env.* +!.env.example + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Django +staticfiles/ +db.sqlite3 +db.sqlite3-journal +*.log +local_settings.py + +# Distribuição / empacotamento +build/ +dist/ +*.egg-info/ +.eggs/ + +# Ferramentas de teste/cobertura +.coverage +.coverage.* +htmlcov/ +.pytest_cache/ +.tox/ + +# Editores / IDEs +.vscode/ +.idea/ +*.swp +*.swo + +# Sistema operacional +.DS_Store +Thumbs.db +desktop.ini + +# Configuração local do Claude Code +.claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b826065 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,581 @@ +# 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. + +## 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 seed_portal # recria os 8 perfis + usuários gabriel/bruno +python manage.py runserver +``` + +Abrir `http://localhost:8000/` — o Django serve `index.html` e as demais páginas do frontend diretamente (ver `config/urls.py`), então **não há mais um segundo servidor** (`python -m http.server`) para o frontend.Vamos desenvolver outra + +Contas de demonstração (criadas por `seed_portal`, senha via `set_password` do Django — não é mais texto puro): + +- `gabriel` / `gabriel` — perfil "Integração e Inovação" (código 8), o único com `gerencia_permissoes=True` (acesso total + gerencia Perfis de Acesso/Usuários) e o único com os `*-editar` de `links-ferramentas` (cartões e Acessos Gerais) e de `ramais` `True` (único perfil que pode reordenar/incluir/remover cartões em Links & Ferramentas, criar/editar seções e acessos em Acessos Gerais, e adicionar/editar ramal/criar ausência em Ramais, até que outro perfil seja liberado em Perfis de Acesso — ver "Modelo de permissões" abaixo). +- `bruno` / `bruno` — sem perfil vinculado, usado para testar o estado "sem acesso". + +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 instalados, e há um Postgres local acessível via `.env` — dá pra rodar `makemigrations`/`migrate`/`seed_portal`/`runserver` normalmente por aqui usando `.venv\Scripts\python.exe manage.py ...` (ou ativando o venv primeiro). 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 8 páginas HTML (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/` + +`static/img/` tem três variantes do logo "D De Paula Contadores" (D em degradê dourado/marrom + texto), todas PNG com fundo transparente: + +- `logo.png` — original, texto **preto**. Serve como fonte pra gerar as outras variantes e também é usada diretamente em `index.html` (login) quando `data-theme="light"` — o card do login usa `--bg-surface` (claro nesse tema), e o texto branco de `logo-branco.png` ficava ilegível contra ele. +- `logo-branco.png` — usada em `index.html` no login quando `data-theme="dark"` (default) e sempre no `sidebar__brand` dos 5 shells (a sidebar usa fundo frozen sempre escuro, independente do tema — ver "Sidebar" em `layout.css` — então não precisa alternar) — mesmo D colorido de `logo.png`, mas com o texto recolorido pra branco. Gerada programaticamente a partir de `logo.png` (script Python com Pillow: qualquer pixel opaco quase-neutro/escuro — `max(r,g,b) < 70` e `spread(r,g,b) < 12` — virou branco; o D nunca entra nesse filtro porque mesmo na sombra mais escura do degradê ele mantém um matiz quente nitidamente não-neutro). Se o logo oficial mudar, regerar `logo-branco.png` a partir do novo `logo.png` com o mesmo filtro, não editar à mão. +- A troca da logo do login por tema é feita em `theme.js` (`pidSyncLoginLogo()`): o `` de `index.html` carrega os dois caminhos resolvidos por `{% static %}` em `data-logo-dark`/`data-logo-light`, e o JS só troca o `src` conforme `data-theme` atual — chamado no load e no evento `pid:theme-changed`. +- `logo-mono.png` — versão totalmente monocromática (D **e** texto em branco/cinza claro). Não usada em nenhum template hoje; existe como variante alternativa (útil se algum dia precisar de um logo "chapado" sem o dourado do D). +- `favicon.png` — só o D (sem o texto "De Paula Contadores"), quadrado, 192×192, usado como ícone da aba do navegador (`` no `` das 7 páginas). Gerado a partir de `logo.png` com o mesmo filtro de `logo-branco.png` (pixel opaco quase-neutro/escuro — `max(r,g,b) < 70` e `spread(r,g,b) < 12` — é texto, não o D), mas em vez de recolorir esses pixels pra branco, eles são apagados (`alpha = 0`); o resultado é recortado pelo bounding box do que sobrou opaco e centralizado num canvas quadrado transparente (o D é mais alto que largo). Se o logo oficial mudar, regerar a partir do novo `logo.png` com o mesmo processo, não editar à mão. + +Tamanho: `.sidebar__logo` é `width: 200px; height: auto` (era 56×56 fixo, esmagava o logo — a arte é bem mais larga que alta, ~1.41:1 — e ficava pequena demais); encolhe pra `44px` quando a sidebar colapsa (desktop `.is-collapsed` e o breakpoint mobile), senão o logo vaza da faixa de 76px. + +### Backend serve o frontend (mesma origem) + +`config/urls.py` registra `path("api/", include("portal_api.urls"))` e, para cada página HTML do frontend (`index.html`, `portal.html`, `perfis-acesso.html`, `usuarios.html`, `calendario-individual.html`, `links-ferramentas.html`, `acessos-gerais.html`, `ramais.html`), uma rota `TemplateView` que resolve o arquivo em `templates/`. 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, `portal_api/`: + +| Arquivo | Conteúdo | +|---|---| +| `models.py` | `Usuario` (`AbstractUser` + `nome`, M2M `perfis`, M2M `departamentos` (pra `Departamento`, ver abaixo), campos cadastrais opcionais `codigo_folha`/`codigo_questor`/`codigo_tareffa`/`codigo_contabit`/`ramal` (`CharField`, `blank=True`) e `data_aniversario` (`DateField`, `null=True, blank=True`), `lideranca` (`BooleanField`, é gerente/coordenador) e M2M `liderados` (self-referential, `symmetrical=False`, `related_name="lideres"` — ver seção "Liderança" abaixo) — `email` já vem de `AbstractUser`, não precisou de campo novo; método `permissao_app(module_key, app_key)` — união genérica de um flag de `apps` entre os perfis vinculados), `Departamento` (só `nome`, `unique=True` — cadastro inline pela própria tela de Usuários, sem tela de administração dedicada como `PerfilAcesso`), `PerfilAcesso` (`permissoes` em `JSONField`, mesmo formato aninhado do frontend; mais o booleano dedicado `gerencia_permissoes`), `CompromissoAgenda`, `Favorito`, `WidgetUsuario`, `NotificacaoDispensada`, `LinkFerramenta` (`icone` é `ImageField`, requer Pillow), `LinkFerramentaFavorito` (favorito por usuário de um cartão de Links & Ferramentas — não confundir com `Favorito`), `AcessoGeralSecao`/`AcessoGeral` (cadastro de logins/acessos compartilhados da aplicação "Acessos Gerais", ver seção própria abaixo), `Ramal` (linha **avulsa** da tela de Ramais, sem `Usuario` por trás — colaboradores de verdade aparecem automaticamente na listagem, sem precisar de uma linha aqui; ver seção "Ramais" abaixo), `RamalAusencia` (período de ausência de um colaborador, com `esta_ativa()` calculando "ausente agora" em vez de armazenar), `TelefoneExterno` (subtela "Telefones Externos" de Ramais, sem `Usuario` por trás), `FuncaoTelefonia` (subtela "Funções de Telefonia" de Ramais, `Meta.ordering` por `comando` reproduz a ordem esperada sem campo de ordem manual), `ImportacaoPlanoSaude`/`ImportacaoPlanoSaudeLinha`/`ImportacaoPlanoSaudeAuditoria` (ferramenta "Importação de Plano de Saúde" em Utilitários, ver seção própria abaixo). | +| `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**, não em `static/js/profiles.js` (que só cacheia o payload recebido). | +| `serializers.py` | `PerfilAcessoSerializer`, `DepartamentoSerializer`, `UsuarioResumoSerializer` (`id`/`nome`/`departamentos`, usado nos dois lados de `liderados` e por `/api/usuarios-resumo/`), `UsuarioSerializer` (escrita, aceita `senha`+`perfis`+`departamentos`+`liderados`)/`UsuarioListSerializer` (leitura, `perfis`/`departamentos`/`liderados` aninhados), `CompromissoAgendaSerializer` (`sou_dono`, `dono_nome`, `dono_username`), `FavoritoSerializer`, `WidgetUsuarioSerializer`, `NotificacaoDispensadaSerializer`, `LinkFerramentaSerializer`, `LinkFerramentaFavoritoSerializer`, `AcessoGeralSecaoSerializer`, `AcessoGeralSerializer`, `RamalSerializer` (só das linhas avulsas — ver seção "Ramais"), `RamalAusenciaSerializer`, `TelefoneExternoSerializer`, `FuncaoTelefoniaSerializer`, `ImportacaoPlanoSaudeCreateSerializer`/`ImportacaoPlanoSaudeListSerializer`/`ImportacaoPlanoSaudeDetailSerializer`/`ImportacaoPlanoSaudeLinhaSerializer`/`ImportacaoPlanoSaudeAuditoriaSerializer` (ver seção "Importação de Plano de Saúde"). | +| `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 (ex.: Links & Ferramentas e Ramais, ver seção própria abaixo). | +| `views.py` | `login_view`/`logout_view`/`csrf_view` (auth por sessão), `me_view` (usuário + `permissoes_efetivas` já unidas no servidor + `lideranca`/`liderados`), `trocar_senha_view`, `usuarios_resumo_view`, `meus_liderados_view` (ver seção "Liderança"), `departamentos_resumo_view` (ver "Ramais"), `catalogo_view`, e os `ModelViewSet` de perfis/departamentos/usuários/compromissos/favoritos/widgets/notificações dispensadas/links e ferramentas/favoritos de links e ferramentas/seções e linhas de Acessos Gerais/ramais/ausências de ramal/importações de plano de saúde e suas linhas. | +| `admin.py` | Django admin básico para todos os models (uso interno, não é a UI do portal). | +| `management/commands/seed_portal.py` | Recria os 8 perfis padrão + `gabriel`/`bruno` + as 13 linhas de `FuncaoTelefonia` + o seed de `CategoriaEvento`. | +| `management/commands/seed_indicador_desempenho.py` | Popula o primeiro histórico do Indicador de Desempenho (7 `IndicadorCriterio` + 5 `IndicadorPercentualTipo`, idempotente) com os valores da planilha antiga — ver seção "Indicador de Desempenho" abaixo. | +| `planos_saude/` | Pacote Python puro (sem ORM) com o pipeline de extração/casamento de "Importação de Plano de Saúde", portado de `projects/project/` — ver seção própria abaixo. | +| `custo_contratacao/` | Pacote Python puro (sem ORM) da ferramenta "Simulação de Custo de Contratação" (Geradoc) — `tabelas.py` (seed/default das faixas de INSS/IRRF, hoje editáveis via `ParametroFiscalCustoContratacao`), `calculo.py` (`ParametrosFiscais` dataclass + `calcula_custo_empregado`), `pdf.py` (`gera_pdf_simulacao`, via `reportlab`). Ver seção própria abaixo. | +| `indicadores/` | Pacote Python puro (sem ORM) da ferramenta "Indicador de Desempenho" (Geradoc) — `tipos.py` (deriva o tipo de colaborador por empresa via Tareffa), `leiaute.py` (leitura das planilhas Tareffa/Honorários via `openpyxl`), `pipeline.py` (orquestração, `processa_apuracao`), `entregas.py` (cálculo dos 3 critérios automáticos), `calculo.py` (composição dos percentuais Individual/Grupo/Departamento e valores em R$), `recibo.py` (PDF do recibo por colaborador, via `reportlab`). Ver seção própria abaixo. | + +### 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` + `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 seção "Liderança") | +| `/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 "Feriados no Calendário Individual" abaixo | +| `/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/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 "Inativar usuário" abaixo) | +| `/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 "Calendário Individual e Widgets") 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 "Eventos Corporativos" abaixo | +| `/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 "Favoritos" abaixo) | +| `/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 "Calendário Individual e Widgets" abaixo | +| `/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" | +| `/api/links-ferramentas/`, `/api/links-ferramentas/{id}/` | GET/POST/PATCH/DELETE | lista **compartilhada** (não por usuário); leitura exige `apps["links-ferramentas-visualizar"]` e escrita exige `apps["links-ferramentas-editar"]` em `permissoes["links-ferramentas"]` (`PermissaoApp`, gate por método em `get_permissions()` — ver "Modelo de permissões" abaixo); POST é `multipart/form-data` (aceita upload de `icone`); `ordem` sempre é atribuída pelo servidor na criação (ignora o que vier no payload), reordenar é PATCH trocando o `ordem` de dois itens | +| `/api/links-ferramentas-favoritos/`, `/api/links-ferramentas-favoritos/{link_id}/` | GET/POST/DELETE | favorito **por usuário** de um cartão (chave natural é `link_id`, o id do `LinkFerramenta` — mesmo padrão de `app_id`/`notif_id`); exige só `apps["links-ferramentas-visualizar"]` (favoritar não precisa de editar); só afeta a ordem de exibição em Links & Ferramentas e o widget "Links Favoritos", nunca o `ordem` compartilhado (ver seção "Links & Ferramentas" abaixo) | +| `/api/acessos-gerais-secoes/`, `/api/acessos-gerais-secoes/{id}/` | GET/POST/PATCH/DELETE | seções do cadastro "Acessos Gerais" (aplicação dentro da seção Links & Ferramentas); leitura exige `apps["acessos-gerais-visualizar"]`, escrita exige `apps["acessos-gerais-editar"]`; excluir uma seção também exclui (`CASCADE`) os acessos dela; GET só lista seções sem `perfis_restritos` ou com interseção com os perfis do usuário (ver "Acessos Gerais" abaixo) | +| `/api/acessos-gerais/`, `/api/acessos-gerais/{id}/` | GET/POST/PATCH/DELETE | linhas (acessos/logins) dentro de uma seção; mesma permissão de `acessos-gerais-secoes`; `ordem` é escopada por `secao` (servidor calcula `max(ordem)` só entre as linhas da mesma seção) — ver seção "Acessos Gerais" abaixo | +| `/api/ramais/` | GET | diretório **mesclado**: todo usuário ativo (linha montada do cadastro, sem precisar de nenhum registro extra) + linhas avulsas de `Ramal`; leitura exige `apps.visualizar` — ver seção "Ramais" abaixo | +| `/api/ramais/`, `/api/ramais/{id}/` | POST/PATCH/DELETE | CRUD só das linhas avulsas (`Ramal`, sem `Usuario` por trás); escrita exige `apps.editar` | +| `/api/ramais/usuarios/` | GET | lista enxuta (`id`/`nome`) de usuários ativos pra alimentar o `` do formulário | +| `/api/importacoes-plano-saude/{id}/gerar/` | POST | monta o CSV (ou ZIP, se mais de um tipo de lançamento) a partir das linhas já revisadas/editadas e devolve como download binário; marca a importação como `concluida` | +| `/api/importacoes-plano-saude-linhas/{id}/` | GET/PATCH | edição de uma linha da revisão (todos os campos, não só valores); mesma permissão da importação, sem conceito de "dono" | +| `/api/regras-custeio-plano-saude/`, `/api/regras-custeio-plano-saude/{id}/` | GET/POST/PATCH/DELETE | banco de regras de custeio salvas (`nome`+`operadora`+`tipos_lancamento`+`custeio_por_tipo`+`observacoes`, ver "Regras de custeio salvas" abaixo) — mesma permissão de toggle único da ferramenta; lista compartilhada, sem "dono" | +| `/api/simulacao-custo-contratacao/gerar/` | POST | calcula (`portal_api.custo_contratacao.calculo.calcula_custo_empregado`) e devolve o PDF direto na resposta (`application/pdf`, sem persistir nada); `PermissaoApp`-like check manual via `permissao_app("geradoc", "simulacao-custo-contratacao")` — ver seção própria abaixo | +| `/api/parametros-fiscais-custo-contratacao/` | GET/PATCH | tabelas de INSS/IRRF + parâmetros da Lei 15.270/2025 usados pela simulação (`ParametroFiscalCustoContratacao`, singleton `pk=1`); mesma permissão da simulação, sem par visualizar/editar dedicado | +| `/api/indicadores-percentuais-tipo/` | GET/POST/DELETE | histórico de percentuais individual/grupo/departamento por tipo de colaborador (`IndicadorPercentualTipo`) — nunca editado in-place, só criado com `vigente_desde` novo; mesma permissão de toggle único `apps["indicador-desempenho"]` em `permissoes["geradoc"]` | +| `/api/indicadores-criterios/`, `/api/indicadores-criterios/{id}/` | GET/POST/PATCH/DELETE | CRUD do cadastro genérico de critérios (`IndicadorCriterio`) — nome/grupo/peso/período/papel/cálculo automático livres, editável pelo RH | +| `/api/indicadores-apuracoes/`, `/api/indicadores-apuracoes/{id}/` | GET/POST/DELETE | apuração mensal (`IndicadorApuracao`); POST é multipart (2 planilhas) e roda `indicadores.pipeline.processa_apuracao()` de forma síncrona dentro de um `transaction.atomic()`, persistindo colaboradores/empresas/respostas já calculados; DELETE também apaga os 2 arquivos de `MEDIA_ROOT` | +| `/api/indicadores-apuracoes/{id}/gerar/` | POST | gera um ZIP com um PDF de recibo por colaborador (`indicadores.recibo.gera_pdf_recibo`), a partir do que já está salvo (não reprocessa as planilhas); `colaborador_ids` opcional no corpo restringe a geração a só esses colaboradores (modal "Gerar Recibos" — um colaborador só, alguns específicos, por departamento ou todos); marca a apuração como `concluida` só quando a seleção cobre **todos** os colaboradores | +| `/api/indicadores-apuracoes/{id}/ajustar-grupo/`, `/recalcular-grupo/` | POST | ajusta (ou reverte) o `pct_grupo` de **todos** os colaboradores de um mesmo `gerente` na apuração de uma vez — "cada gerente representa um grupo" (ver seção própria abaixo) | +| `/api/indicadores-apuracoes/{id}/ajustar-departamento/`, `/recalcular-departamento/` | POST | idem, mas aplica a **todos** os colaboradores do `departamento` (id de um `IndicadorDepartamento`) informado no corpo (`{departamento, pct_departamento}`/`{departamento}`) — cada departamento tem sua própria meta de Departamento, ver "Departamento organizacional" abaixo | +| `/api/indicadores-departamentos/`, `/api/indicadores-departamentos/{id}/` | GET/POST/PATCH/DELETE | cadastro de departamentos (`IndicadorDepartamento`, nome/ativo) — mesma permissão de toggle único do Indicador de Desempenho, ver "Departamento organizacional" abaixo | +| `/api/indicadores-departamentos-gerentes/`, `/api/indicadores-departamentos-gerentes/{id}/` | GET/POST/PATCH/DELETE | relação gerente→departamento (`IndicadorDepartamentoGerente`, `nome_gerente` único) — mesma permissão, ver "Departamento organizacional" abaixo | +| `/api/indicadores-apuracoes/{id}/ajustar-honorario-empresa/` | POST | `{codigo_empresa, honorario}` — preenche (ou corrige) o honorário de uma empresa com `honorario_nao_encontrado=True` ou `honorario_ajustado_manualmente=True` de uma vez pra **todos** os colaboradores desta apuração que a têm (mesmo código), recalculando cada um (ver "Empresas sem Honorário"/"Empresas Ajustadas Manualmente" abaixo) | +| `/api/indicadores-apuracoes-colaboradores/{id}/` | GET/PATCH | ajuste manual do `pct_individual` de um colaborador (`pct_individual_ajustado_manualmente=True`); recalcula `valor_total` via `indicadores.calculo.recalcula_colaborador` | +| `/api/indicadores-apuracoes-colaboradores/{id}/recalcular/` | POST | reverte `pct_individual` pro modo automático (limpa o ajuste manual) e recalcula | +| `/api/indicadores-apuracoes-colaboradores/{id}/marcar-validado/` | POST | `{validado}` — checklist de revisão do RH, só grava o campo, sem recalcular nada (ver "Checklist de revisão do RH" abaixo) | +| `/api/indicadores-apuracoes-empresas/{id}/trocar-responsavel/` | POST | `{colaborador_id}` — reatribui essa linha (empresa+tipo) pra outro colaborador da mesma apuração, recalculando os dois (ver "Corrigir Responsável" abaixo) | +| `/api/indicadores-apuracoes-empresas/{id}/` | GET/PATCH | preenchimento manual do `honorario` de uma empresa com `honorario_nao_encontrado=True` (código não casou com a planilha de Honorários Por Cliente); zera essa flag e recalcula o colaborador | +| `/api/indicadores-apuracoes-respostas/{id}/` | GET/PATCH | edição de uma resposta de critério (SIM/NÃO/NÃO FAZ/NÃO SE APLICA) já existente; recalcula o colaborador | +| `/api/indicadores-apuracoes-respostas/aplicar-em-lote/` | POST | `{resposta_ids, valor}` — aplica o mesmo valor a várias respostas de uma vez (seleção múltipla da tela de revisão), recalculando todos os colaboradores afetados | + +### 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"). + +`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 seção própria abaixo). | +| `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 seção "Acessos Gerais" abaixo). | +| `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 seção "Ramais" abaixo). | +| `importacao-plano-saude.html` | Ferramenta de Utilitários: histórico + nova importação (upload) + revisão/geração do arquivo de lançamento de plano de saúde (ver seção "Importação de Plano de Saúde" abaixo). | +| `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 seção "Simulação de Custo de Contratação" abaixo). | +| `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 seção própria abaixo). | + +O item "Calendário De Paula" no menu **não é uma página local** — é um `` (continua favoritável, já que ainda é um `` — ver "Favoritos" abaixo) cujo clique é interceptado em `sidebar.js` (`PID_CALENDARIO_DEPAULA_URL`) pra abrir `https://depaula-tvcorporativa.lovable.app/calendario` num modal com ` + + + + + + + + + + + + + + + diff --git a/templates/calendario-individual.html b/templates/calendario-individual.html new file mode 100644 index 0000000..0787e73 --- /dev/null +++ b/templates/calendario-individual.html @@ -0,0 +1,604 @@ +{% load static %} + + + + + + Portal De Paula — Calendário Individual + + + + + + + + + + +
+ + +
+
+ +

Calendário Individual

+ +
+ + + + +
+ + +
+ + +
+
+ +
+
+
+ + + +
+

+
+ + + + + +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/custo-contratacao.html b/templates/custo-contratacao.html new file mode 100644 index 0000000..7d23ac4 --- /dev/null +++ b/templates/custo-contratacao.html @@ -0,0 +1,589 @@ +{% load static %} + + + + + + Portal De Paula — Simulação de Custo de Contratação + + + + + + + + + +
+ + +
+
+ +

Simulação de Custo de Contratação

+ +
+ + +
+ + +
+ + +
+
+ +
+ + +
+
+
+

Simulação de Custo de Contratação

+

Regime Empregado CLT. Preencha os dados abaixo e gere um PDF com o custo total para o empregador e o valor líquido recebido pelo empregado.

+
+
+ +
+ + +
+ +
+ + + + +
+

Remuneração

+ + + +

Itens adicionais somam à base de cálculo do INSS, FGTS, 13º, férias e IRRF, junto com o salário.

+ + + + + +
Outro item de remuneraçãoValor (R$)
+ +
+ +
+

Encargos

+ +
+ +
+

Benefícios

+

Custo do empregador — entram no Custo Total Empresa como custo variável, sem afetar o valor líquido do empregado.

+ + + + + + + + +
Outro benefícioValor (R$)
+ +
+ +
+

Descontos

+

Descontados direto do empregado — reduzem o valor líquido, sem afetar o custo total para o empregador. Aceitam valor em reais ou percentual do salário contratual (ex.: "6%").

+ + + + + + + +
Outro descontoValor (R$ ou %)
+ +
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/templates/importacao-plano-saude.html b/templates/importacao-plano-saude.html new file mode 100644 index 0000000..1602a8a --- /dev/null +++ b/templates/importacao-plano-saude.html @@ -0,0 +1,629 @@ +{% load static %} + + + + + + Portal De Paula — Importação de Plano de Saúde + + + + + + + + + + +
+ + +
+
+ +

Importação de Plano de Saúde

+ +
+ + +
+ + +
+ + +
+
+ +
+ + +
+
+
+
+

Importação de Plano de Saúde

+

Gere o arquivo de lançamento de mensalidade/coparticipação no leiaute do sistema, a partir do relatório da operadora.

+
+ +
+ + + +
+ + + + + + + + + + + + + + +
Cód. Empresa ↕Operadora ↕Tipos ↕Status ↕Criado por ↕Data ↕
+
+ +
+

+
+ + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..fbd9624 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,78 @@ +{% load static %} + + + + + + Portal De Paula — Login + + + + + + + + + + +
+ +
+ + + + + diff --git a/templates/indicador-desempenho.html b/templates/indicador-desempenho.html new file mode 100644 index 0000000..32effdb --- /dev/null +++ b/templates/indicador-desempenho.html @@ -0,0 +1,843 @@ +{% load static %} + + + + + + Portal De Paula — Indicador de Desempenho + + + + + + + + + + +
+ + +
+
+ +

Indicador de Desempenho

+ +
+ + +
+ + +
+ + +
+
+ +
+ + +
+
+
+
+

Indicador de Desempenho

+

Apuração mensal do indicador do Fiscontábil a partir das planilhas de Serviços Tareffa e Honorários Por Cliente.

+
+
+ + +
+
+ +
+ + + + + + + + + + + + +
CompetênciaColaboradoresStatusCriado porData
+
+ +
+ + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/links-ferramentas.html b/templates/links-ferramentas.html new file mode 100644 index 0000000..c282b87 --- /dev/null +++ b/templates/links-ferramentas.html @@ -0,0 +1,454 @@ +{% load static %} + + + + + + Portal De Paula — Links & Ferramentas + + + + + + + + + + +
+ + +
+
+ +

Links & Ferramentas

+ +
+ + + + +
+ + +
+ + +
+
+ +
+ + +
+
+
+

Links & Ferramentas

+

Acesso rápido às ferramentas e sistemas usados no dia a dia.

+
+ +
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/perfis-acesso.html b/templates/perfis-acesso.html new file mode 100644 index 0000000..3ba2df1 --- /dev/null +++ b/templates/perfis-acesso.html @@ -0,0 +1,467 @@ +{% load static %} + + + + + + Portal De Paula — Perfis de Acesso + + + + + + + + + +
+ + +
+
+ +

Administração

+ +
+ + +
+ + +
+ + +
+
+ +
+
+
+

Perfis de Acesso

+
+ + + +
+
+ +
+ + + + + + + + + +
CódigoPerfil de Acesso
+ +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/portal.html b/templates/portal.html new file mode 100644 index 0000000..213394f --- /dev/null +++ b/templates/portal.html @@ -0,0 +1,453 @@ +{% load static %} + + + + + + Portal De Paula + + + + + + + + + + + + + +
+ + +
+
+ +

Portal De Paula

+ +
+ + + + +
+ + +
+ + +
+
+ +
+ + +
+ + +
+ + +
+ +
+
+

Widgets

+ +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/ramais.html b/templates/ramais.html new file mode 100644 index 0000000..5ddbe5a --- /dev/null +++ b/templates/ramais.html @@ -0,0 +1,668 @@ +{% load static %} + + + + + + Portal De Paula — Ramais + + + + + + + + + + +
+ + +
+
+ +

Ramais

+ +
+ + +
+ + +
+ + +
+
+ +
+ + +
+
+
+

Ramais

+

Diretório de ramais internos da De Paula Contadores.

+
+
+ +
+ + + + + +
+ +
+
+
+ + +
+
+ + +
+
+ +
+ + + +
+ +
+ + + + + + + + + + +
NomeDepartamentoRamal
+ +
+
+ + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/usuarios.html b/templates/usuarios.html new file mode 100644 index 0000000..ae8f081 --- /dev/null +++ b/templates/usuarios.html @@ -0,0 +1,498 @@ +{% load static %} + + + + + + Portal De Paula — Usuários + + + + + + + + + +
+ + +
+
+ +

Administração

+ +
+ + +
+ + +
+ + +
+
+ +
+
+
+

Usuários

+
+ + +
+
+ +
+ + + + + + + + + + + +
LoginNomePerfil de AcessoStatus
+ +
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +