Inclusão da busca automática no Questor e reestruturação visual - Plano de Saúde

This commit is contained in:
Gabriel 2026-08-21 18:03:15 -03:00
parent 7fc771aafb
commit 1fe649ac86
17 changed files with 1052 additions and 73 deletions

View File

@ -2,7 +2,11 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(python -c ' *)", "Bash(python -c ' *)",
"Bash(.venv/Scripts/python.exe manage.py shell -c ' *)" "Bash(.venv/Scripts/python.exe manage.py shell -c ' *)",
"Bash(.venv/Scripts/python.exe manage.py makemigrations portal_api --dry-run --verbosity 2)",
"Bash(.venv/Scripts/python.exe manage.py makemigrations portal_api)",
"Bash(node --check static/js/importacao-plano-saude.js)",
"Bash(grep -n \"Histórico \\(\\\\`#ips-list-table\\\\`\\)\" CLAUDE.md)"
] ]
} }
} }

View File

@ -549,8 +549,8 @@ Pra adicionar uma operadora nova: criar `operadoras/<nome>/<arquivo>.py` impleme
### Modelos (`portal_api/models.py`) ### Modelos (`portal_api/models.py`)
- `ImportacaoPlanoSaude`: uma execução da ferramenta — `operadora`/`nome_operadora`, `tipos_lancamento` (JSONField, lista), `custeio_por_tipo` (JSONField, `{"mensalidade": {"titular": {"modo": "empresa"|"empregado"|"especifica", "limite_valor": float|None, "percentual": float|None}, "dependente": {...}}, "coparticipacao": {...}}` — ver regra de custeio acima), `regra_empresa` (CharField, blank — chave de `planos_saude.regras_empresa.REGRAS_EMPRESA` quando "mensalidade" foi custeada por uma regra especial em vez do `custeio_por_tipo["mensalidade"]` normal, ver "Regra empresa" abaixo), os dois arquivos anexados (`planilha_padrao`/`arquivo_operadora`, `FileField` com o mesmo padrão de validator de tamanho de `LinkFerramenta.icone`, só que 15MB em vez de 2MB — são documentos reais, não ícones), `status` (`revisao`/`concluida`), `criado_por`, `criado_em`/`concluida_em`. **Com histórico**: decisão explícita do usuário — cada importação fica salva (quem fez, quando, arquivos), não é um fluxo descartável. - `ImportacaoPlanoSaude`: uma execução da ferramenta — `operadora`/`nome_operadora`, `tipos_lancamento` (JSONField, lista), `custeio_por_tipo` (JSONField, `{"mensalidade": {"titular": {"modo": "empresa"|"empregado"|"especifica", "limite_valor": float|None, "percentual": float|None}, "dependente": {...}}, "coparticipacao": {...}}` — ver regra de custeio acima), `regra_empresa` (CharField, blank — chave de `planos_saude.regras_empresa.REGRAS_EMPRESA` quando "mensalidade" foi custeada por uma regra especial em vez do `custeio_por_tipo["mensalidade"]` normal, ver "Regra empresa" abaixo), os dois arquivos anexados (`planilha_padrao`/`arquivo_operadora`, `FileField` com o mesmo padrão de validator de tamanho de `LinkFerramenta.icone`, só que 15MB em vez de 2MB — são documentos reais, não ícones; `planilha_padrao` é `blank=True` desde que passou a poder vir de uma busca no Questor em vez de upload, ver "Planilha padrão via Questor (SQL)" abaixo), `competencia` (DateField, null — só preenchida quando a origem da planilha padrão foi essa busca no Questor), `status` (`revisao`/`concluida`), `criado_por`, `criado_em`/`concluida_em`. **Com histórico**: decisão explícita do usuário — cada importação fica salva (quem fez, quando, arquivos), não é um fluxo descartável.
- `ImportacaoPlanoSaudeLinha`: uma linha da planilha padrão já casada com o valor do mês (espelha `LinhaSistema` campo a campo) — **todos os campos são editáveis** na tela de revisão antes de gerar o CSV (decisão explícita do usuário, não só os valores). `valor`/`valor_empresa` ficam como `CharField` no mesmo formato string do pipeline (`"51,69"`/`"0"`), não `DecimalField`, pra manter fidelidade 1:1 com o CSV final sem risco de arredondamento. - `ImportacaoPlanoSaudeLinha`: uma linha da planilha padrão já casada com o valor do mês (espelha `LinhaSistema` campo a campo) — na tela de revisão, uma linha que já veio do processamento (upload ou Questor) só edita **Valor Empresa/Valor**; os demais campos (cadastro da pessoa) só ficam editáveis numa linha incluída manualmente via "Adicionar linha" (regra revista — nasceu como "todos os campos editáveis em qualquer linha", decisão do usuário depois de ver dados reais na tela: só uma linha nova precisa editar o cadastro, uma linha já casada não devia arriscar um cadastro certo sendo alterado por engano). Essa restrição é só de UI (`importacao-plano-saude.js`, `linhasIncluidasManualmente()` — deriva de `ImportacaoPlanoSaudeAlteracao` já carregada, sem campo novo), o backend continua aceitando PATCH em qualquer campo. `valor`/`valor_empresa` ficam como `CharField` no mesmo formato string do pipeline (`"51,69"`/`"0"`), não `DecimalField`, pra manter fidelidade 1:1 com o CSV final sem risco de arredondamento.
- `ImportacaoPlanoSaudeAuditoria`: espelha `ItemAuditoria` — os campos extraídos do arquivo da operadora (`motivo`/`nome`/`valor`/`detalhe`...) são read-only na tela; `resolvida`/`linha_vinculada` são a exceção, graváveis via a resolução manual (ver "Resolução manual de auditoria por nome" abaixo). `MOTIVOS_RESOLVIVEIS = ("NOME_DIVERGENTE", "NAO_CADASTRADO")` (atributo de classe) é a lista dos dois motivos "de leitura/grafia de nome" que aceitam esse fluxo — `VALOR_NEGATIVO`/`TIPO_INVALIDO` são outra categoria de problema (valor real negativo, tipo de despesa não mapeado) e não têm solução por "essa é a mesma pessoa". - `ImportacaoPlanoSaudeAuditoria`: espelha `ItemAuditoria` — os campos extraídos do arquivo da operadora (`motivo`/`nome`/`valor`/`detalhe`...) são read-only na tela; `resolvida`/`linha_vinculada` são a exceção, graváveis via a resolução manual (ver "Resolução manual de auditoria por nome" abaixo). `MOTIVOS_RESOLVIVEIS = ("NOME_DIVERGENTE", "NAO_CADASTRADO")` (atributo de classe) é a lista dos dois motivos "de leitura/grafia de nome" que aceitam esse fluxo — `VALOR_NEGATIVO`/`TIPO_INVALIDO` são outra categoria de problema (valor real negativo, tipo de despesa não mapeado) e não têm solução por "essa é a mesma pessoa".
- `ImportacaoPlanoSaudeAlteracao`: log de cada edição de campo/inclusão/exclusão de linha feita manualmente na revisão — ver seção "Alterações" abaixo. - `ImportacaoPlanoSaudeAlteracao`: log de cada edição de campo/inclusão/exclusão de linha feita manualmente na revisão — ver seção "Alterações" abaixo.
- `RegraCusteioPlanoSaude`: regra de custeio salva pra reaplicar em importações futuras (ex.: "092 - Unimed") — ver "Regras de custeio salvas" abaixo. Lista compartilhada, mesmo espírito de `LinkFerramenta`/`AcessoGeral` — o próprio model não tem FK pra nada; é `ImportacaoPlanoSaude.regra_custeio_salva` que aponta pra cá (opcional, `SET_NULL`), só como registro de qual regra (se alguma) foi aplicada pra preencher aquele formulário. - `RegraCusteioPlanoSaude`: regra de custeio salva pra reaplicar em importações futuras (ex.: "092 - Unimed") — ver "Regras de custeio salvas" abaixo. Lista compartilhada, mesmo espírito de `LinkFerramenta`/`AcessoGeral` — o próprio model não tem FK pra nada; é `ImportacaoPlanoSaude.regra_custeio_salva` que aponta pra cá (opcional, `SET_NULL`), só como registro de qual regra (se alguma) foi aplicada pra preencher aquele formulário.
@ -558,13 +558,35 @@ Pra adicionar uma operadora nova: criar `operadoras/<nome>/<arquivo>.py` impleme
### Fluxo e endpoints ### Fluxo e endpoints
`ImportacaoPlanoSaudeViewSet` (`/api/importacoes-plano-saude/`, `PermissaoApp("utilitarios", "importacao-plano-saude")` pra todos os métodos): `ImportacaoPlanoSaudeViewSet` (`/api/importacoes-plano-saude/`, `PermissaoApp("utilitarios", "importacao-plano-saude")` pra todos os métodos):
- `create()` (multipart, `ImportacaoPlanoSaudeCreateSerializer` valida a entrada) salva o model (isso já grava os 2 arquivos em `MEDIA_ROOT`) e roda `pipeline.processa_importacao()` **de forma síncrona** usando `instance.arquivo_operadora.path`/`instance.planilha_padrao.path` — sem fila/Celery, o arquivo típico processa em menos de um request. Se o processamento falhar (PDF num layout desconhecido etc.), apaga os arquivos recém-salvos + o registro órfão e devolve 400. - `create()` (multipart, `ImportacaoPlanoSaudeCreateSerializer` valida a entrada) resolve a planilha padrão (upload **ou** busca no Questor — ver "Planilha padrão via Questor (SQL)" abaixo), salva o model (isso já grava os 2 arquivos em `MEDIA_ROOT`) e roda `pipeline.processa_importacao()` **de forma síncrona** usando `instance.arquivo_operadora.path` + a lista de `LinhaSistema` já resolvida — sem fila/Celery, o arquivo típico processa em menos de um request. Se o processamento falhar (PDF num layout desconhecido etc.), apaga os arquivos recém-salvos + o registro órfão e devolve 400.
- `GET /operadoras/` (`@action` sem detail) devolve `pipeline.lista_operadoras()` — fonte única pro combobox pesquisável "Operadora" do formulário (`#ips-operadora-combo`, mesmo padrão de "Regra de custeio salva" — ver "Regras de custeio salvas" abaixo), sem duplicar a lista em JS. `label` já vem no formato `"<código> - <Nome>"` (ex.: `"3755 - Itamed Saúde"`) — o código é o de cadastro da operadora no Questor, pedido explícito do usuário pra identificar a operadora sem ambiguidade (útil quando duas operadoras têm nome parecido); editar em `pipeline.OPERADORAS`, não formatar o código separadamente no frontend. - `GET /operadoras/` (`@action` sem detail) devolve `pipeline.lista_operadoras()` — fonte única pro combobox pesquisável "Operadora" do formulário (`#ips-operadora-combo`, mesmo padrão de "Regra de custeio salva" — ver "Regras de custeio salvas" abaixo), sem duplicar a lista em JS. `label` já vem no formato `"<código> - <Nome>"` (ex.: `"3755 - Itamed Saúde"`) — o código é o de cadastro da operadora no Questor, pedido explícito do usuário pra identificar a operadora sem ambiguidade (útil quando duas operadoras têm nome parecido); editar em `pipeline.OPERADORAS`, não formatar o código separadamente no frontend.
- `POST /{id}/gerar/` monta o(s) CSV(s) a partir das **linhas já salvas** (isto é, já com qualquer edição feita na revisão — não reprocessa os arquivos originais) usando `leiaute_sistema.CABECALHO`; 1 tipo de lançamento vira um `.csv` direto, 2 tipos (mensalidade + coparticipação) viram um `.zip` com um `.csv` por tipo (`zipfile` em memória). Pode ser chamada de novo pra regerar depois de mais edições — não bloqueia edição subsequente. - `POST /{id}/gerar/` monta o(s) CSV(s) a partir das **linhas já salvas** (isto é, já com qualquer edição feita na revisão — não reprocessa os arquivos originais) usando `leiaute_sistema.CABECALHO`; 1 tipo de lançamento vira um `.csv` direto, 2 tipos (mensalidade + coparticipação) viram um `.zip` com um `.csv` por tipo (`zipfile` em memória). Sempre marca `status="concluida"` (+ `concluida_em`) — pode ser chamada de novo enquanto `concluida` (regera o mesmo arquivo a partir do que já está salvo), mas a partir daí toda edição de linha/auditoria/alteração fica bloqueada até reabrir (ver `reabrir()` abaixo e "Trava de edição pós-conclusão").
- `POST /{id}/reabrir/` volta `status="revisao"` (zera `concluida_em`) — contrapartida de `gerar()`, é o único jeito de voltar a editar uma importação concluída. Botão "Editar" na tela de Revisão, visível só quando `status === "concluida"`.
`ImportacaoPlanoSaudeLinhaViewSet` (`/api/importacoes-plano-saude-linhas/{id}/`, só GET/PATCH): edição de uma linha por vez, disparada por `blur`/`change` de cada `<input>` na tela de revisão — mesma permissão de toggle único, sem checagem de "dono". `ImportacaoPlanoSaudeLinhaViewSet` (`/api/importacoes-plano-saude-linhas/{id}/`, só GET/PATCH): edição de uma linha por vez, disparada por `blur`/`change` de cada `<input>` na tela de revisão — mesma permissão de toggle único, sem checagem de "dono". `create()`/`partial_update()`/`destroy()` recusam (400) se a importação já estiver `concluida` — ver "Trava de edição pós-conclusão" abaixo.
`ImportacaoPlanoSaudeAuditoriaViewSet` (`/api/importacoes-plano-saude-auditoria/{id}/resolver/`, só `POST`) — ver seção própria abaixo. `ImportacaoPlanoSaudeAuditoriaViewSet` (`/api/importacoes-plano-saude-auditoria/{id}/resolver/`, só `POST`) — ver seção própria abaixo; também recusa se a importação estiver `concluida`.
**Histórico (`#ips-list-table`): ordenação por coluna + filtro "estilo Excel" por coluna, os dois client-side** sobre o array já carregado (`GET /api/importacoes-plano-saude/`, sem paginação/filtro no servidor) — mesmo padrão de ordenação já usado em `#ua-table`/Ramais (`th[data-sort]`, ícone `↕`). O filtro (`criarFiltroColuna()`, `importacao-plano-saude.js`) nasceu como um segundo campo de texto por coluna, mas foi revisto a pedido do usuário pra imitar o filtro de planilha (Excel/Sheets): um botão de funil dentro do próprio `<th>` de cada coluna filtrável (Cód. Empresa/Operadora/Status/Criado por) abre um popup com busca + checklist dos **valores distintos daquela coluna** (reaproveita `.checklist-box`/`.checklist-search`/`.checklist-select-all`/`.modal-checkbox` de `components.css` — mesmo componente já usado nos checklists de Perfis de Acesso), tudo desmarcável/marcável, com "Aplicar"/"Limpar" no rodapé (só aplica no clique, não a cada checkbox — evita re-renderizar a lista principal a cada toque). Os filtros das 4 colunas combinam entre si (AND). `listFiltros[campo]` vale `null` (sem filtro) ou um `Set` dos valores brutos marcados; marcar **todos** os valores existentes equivale a `null` (sem filtro), pra um valor novo que apareça depois (operadora nova, por exemplo) não nascer excluído até o usuário marcá-lo manualmente. O botão de funil ganha `.is-active` (cor de destaque) enquanto a coluna tiver um filtro aplicado — mesmo sinal visual do funil "azul" do Excel. `.pa-table-wrap` normalmente usa `overflow:hidden` pra arredondar os cantos da tabela; só a tabela do histórico (`.ips-list-table-wrap`) sobrescreve pra `visible`, senão o popup (que precisa aparecer por cima das linhas, não só dentro do cabeçalho) seria cortado ali. Qualquer mudança de filtro (ou de ordenação) volta pra página 1. `listEmpty` mostra uma mensagem diferente conforme o caso: "Nenhuma importação realizada ainda." quando o histórico está mesmo vazio, "Nenhuma importação encontrada com esse filtro." quando o vazio é só resultado do filtro aplicado.
### Trava de edição pós-conclusão
Depois que `POST /{id}/gerar/` marca uma importação como `concluida`, editar/incluir/excluir uma linha (`ImportacaoPlanoSaudeLinhaViewSet`), resolver um item de auditoria (`ImportacaoPlanoSaudeAuditoriaViewSet.resolver`) ou reverter uma alteração (`ImportacaoPlanoSaudeAlteracaoViewSet.reverter`) passam a ser recusados (400) — decisão explícita do usuário, depois de ver que a tabela de revisão continuava 100% editável mesmo depois do arquivo já ter sido gerado e entregue. `_garante_importacao_em_revisao(importacao)` (função módulo-level em `views.py`, chamada no início de cada um desses pontos de escrita) é o único lugar que checa isso — levanta `ValidationError` com uma mensagem pedindo pra usar o botão "Editar" (`POST /{id}/reabrir/`) primeiro. `gerar()` em si nunca é bloqueado (pode ser chamado de novo com a importação já `concluida`, só regera o mesmo arquivo a partir do que está salvo).
No frontend (`importacao-plano-saude.js`), a tela de Revisão espelha essa trava puramente pra UX (a validação real é sempre a do backend acima): com `importacaoAtual.status === "concluida"`, toda célula da tabela de Mensalidade/Coparticipação vira texto (não `<input>`, nem Valor Empresa/Valor — a regra de "só Valor Empresa/Valor editáveis" descrita no bullet de `ImportacaoPlanoSaudeLinha` acima só se aplica quando a importação ainda está em revisão), o "×" de remover linha e o botão "Adicionar linha" somem, "Vincular pessoa" (Auditoria) e "Reverter" (Alterações) também somem. O botão "Editar" (`#ips-review-editar-btn`, ao lado de "Gerar Arquivo") aparece só nesse estado e chama `POST /{id}/reabrir/`, atualizando `importacaoAtual` e re-renderizando as abas.
Clicar em "Gerar Arquivo" (`gerarBtn`) sempre volta pro histórico (`showView("list")` + `refreshList()`) depois do download disparar — decisão explícita do usuário, já que a partir daí a importação está `concluida` e travada (ver acima), não há mais nada pra revisar de imediato na própria tela.
### Planilha padrão via Questor (SQL)
Até uma rodada anterior, a "planilha padrão" (cadastro dos beneficiários, sem valores — o mesmo que `le_planilha_padrao` lê de um CSV) só chegava por upload manual, exportado à mão do Questor. O usuário forneceu e validou uma consulta SQL equivalente contra o próprio banco do Questor, então "Nova Importação" ganhou um segundo caminho: buscar essa planilha automaticamente a partir de empresa (já resolvida pela `RegraCusteioPlanoSaude` escolhida) + operadora + competência (mês/ano digitado na tela) — **sem precisar mais exportar/anexar nada** nesse caso. O upload manual continua existindo como alternativa (Questor fora do ar, ou empresa ainda não migrada) — decisão explícita do usuário, não uma substituição total.
- **Toggle na tela** (`importacao-plano-saude.js`/`.html`, dentro do bloco "1. Planilha padrão"): dois radios, "Buscar automaticamente do Questor" (padrão) / "Anexar manualmente" — o primeiro revela um campo "Competência" **texto livre com máscara MM/AAAA** (`<input type="text" inputmode="numeric" placeholder="MM/AAAA" maxlength="7">` + `mascaraCompetencia()`/`competenciaParaIso()` em JS — deliberadamente **não** um `<input type="month">`: o seletor nativo do browser foi rejeitado pelo usuário como UX ruim; mesmo padrão de digitação livre já usado em `indicador-desempenho.js`/`pidIndMascaraCompetencia`, copiado aqui em vez de compartilhado, como as demais funções pequenas duplicadas entre telas), o segundo revela o `<input type="file">` de sempre. Trocar de modo limpa o outro campo, pra nunca mandar os dois juntos (o backend também recusa isso).
- **Backend**: `ImportacaoPlanoSaudeCreateSerializer.competencia` é um `serializers.DateField()` normal (mesmo padrão de `IndicadorApuracaoCreateSerializer.competencia`) — o frontend já manda o ISO `"AAAA-MM-01"` convertido a partir da máscara, nunca a string mascarada crua. `validate()` exige exatamente uma das duas origens (nunca as duas, nunca nenhuma).
- `ImportacaoPlanoSaudeViewSet.create()` (views.py): quando não veio arquivo, resolve a planilha **antes** de criar o registro — `portal_api.planos_saude.questor_planilha.busca_linhas_questor(codigo_empresa, codigo_operadora, competencia)` consulta `sqls.questor.QuestorSQL.consulta_planilha_plano_saude` (só leitura — `select_mappings_query`, nunca `execute`/`execute_returning`, ver [[feedback_bancos_externos_somente_leitura]]) via `DatabaseConnection("questor")`. Uma falha de conexão/consulta aqui devolve 400 direto, sem nada persistido ainda (diferente do caminho de upload, que só sabe se o arquivo é válido depois de já ter salvo o registro — por isso, nesse, o cleanup de arquivo/registro órfão continua sendo necessário). Zero linhas retornadas (empresa sem plano ativo na competência) também é 400. O resultado é serializado de volta pra CSV (`questor_planilha.linhas_para_csv_bytes`, mesmo formato de `leiaute_sistema.CABECALHO`) e salvo como `ContentFile` no próprio campo `planilha_padrao` — preserva o histórico completo mesmo pra importações que nunca tiveram upload. A "trava de conferência do código de empresa" (que confere que a planilha anexada tem alguma linha da empresa da regra) é pulada nesse caminho, redundante já que a consulta já filtrou por esse `codigo_empresa`.
- **`pipeline.processa_importacao`** deixou de ler o arquivo sozinho (não recebe mais `caminho_planilha_padrao: str`) — recebe `linhas_sistema_template: List[LinhaSistema]` já pronta, de qualquer uma das duas origens (`le_planilha_padrao(caminho)` pro upload, `busca_linhas_questor(...)` pro Questor) — a decisão de qual usar ficou inteiramente em `views.py create()`.
- **Código da operadora**: até então só existia embutido no `label` de `pipeline.OPERADORAS` (ex. `"5060 - Unimed Saúde"`, extraído por *string split* onde só o nome era preciso). Passou a existir como campo próprio (`OPERADORAS[chave]["codigo_operadora"]`, junto de `"nome"`) — é o valor usado pra filtrar a consulta por operadora (`codigooutemp` no Questor, código da OPERADORA, não confundir com `codigo_empresa` do cliente); `pipeline.label_operadora(chave)` calcula o `"<código> - <Nome>"` de exibição a partir desses dois campos onde ainda é preciso (`lista_operadoras()`, mensagens de erro, `nome_operadora` da importação).
- `ImportacaoPlanoSaude.competencia` (`DateField`, null) registra a competência usada — só preenchida quando a origem foi o Questor; exibida na tela de Revisão ao lado do nome da operadora.
### Resolução manual de auditoria por nome ### Resolução manual de auditoria por nome

View File

@ -0,0 +1,66 @@
Cód. Contr/Contrat. Pagador;Id. Título;Número Título;Valor Bruto;Valor Líquido;Situação Título;Contratante;Id. Titular;Família;Id. Benef.;Cód.Beneficiário;Nome Beneficiário;Tipo Benef.;Grau Depend.;Código Combinação;Reservado;Valor;Competência;Tipo;Tipo Ato Médico;Cód. Serviço;Descrição Serviço;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;756165;TABATA CAROLINE GOES DA SILVA - 835199;756165;'9750167003613895;TABATA CAROLINE GOES DA SILVA;1;0 - TITULAR;10215;;272,77;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;756165;TABATA CAROLINE GOES DA SILVA - 835199;754971;'9750167003821633;WENDY MANOEL MIGUEL MELO DA SILVA;2;1 - CONJUGE;10008;;231,78;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;756165;TABATA CAROLINE GOES DA SILVA - 835199;756166;'9750167003613909;LUISA VALENTINA GOES DE MELO;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;762483;ADRIANA JARDIM DE CARVALHO - 835478;762483;'9750167003631320;ADRIANA JARDIM DE CARVALHO;1;0 - TITULAR;10008;;706,01;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;761760;TAWANY THYESKA DE PIERRI MARQUES - 835910;761760;'9750167003658732;TAWANY THYESKA DE PIERRI MARQUES;1;0 - TITULAR;10215;;238,92;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;761760;TAWANY THYESKA DE PIERRI MARQUES - 835910;761761;'9750167003658740;THAEMY LOHAYNE DE PIERRE BORGES;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;351,21;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;110;08/2026;Serviços ;Consulta;10101012;Consulta em consultorio no horario normal ou preestabelecido;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;45,65;08/2026;Serviços ;Exame;41301080;Ceratoscopia computadorizada - monocular;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;4,26;08/2026;Serviços ;Exame;41301323;Tonometria - binocular;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;51,08;08/2026;Serviços ;Exame;41501128;Paquimetria ultrassonica - monocular;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;766974;EDSON AUGUSTO DOS SANTOS - 838034;766974;'9750167003785599;EDSON AUGUSTO DOS SANTOS;1;0 - TITULAR;10008;;28,98;08/2026;Serviços ;Outros;50000560;Consulta ambulatorial por nutricionista com diretriz definida pela ANS - n 103;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;757635;JOICE GABRIELLY CONRADO - 840123;757635;'9750167003885879;JOICE GABRIELLY CONRADO;1;0 - TITULAR;10215;;199,65;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;272,8;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;74,25;08/2026;Serviços ;Consulta;10101039;Consulta em pronto socorro;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;3,72;08/2026;Serviços ;Exame;40301583;Colesterol HDL - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;4,96;08/2026;Serviços ;Exame;40301591;Colesterol LDL - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40301605;Colesterol total - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40301630;Creatinina - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40302040;Glicose - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40302504;Transaminase oxalacetica amino transferase aspartato - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40302512;Transaminase piruvica amino transferase de alanina - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;2,48;08/2026;Serviços ;Exame;40302547;Triglicerideos - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,74;08/2026;Serviços ;Exame;40302580;Ureia - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;5,58;08/2026;Serviços ;Exame;40302733;Hemoglobina glicada Fracao A1c - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;8,28;08/2026;Serviços ;Exame;40302830;Vitamina D 25 HIDROXI, pesquisa e/ou dosagem Vitamina D3;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;3,72;08/2026;Serviços ;Exame;40304361;Hemograma com contagem de plaquetas ou fracoes eritrograma, leucograma, plaquet;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;1,86;08/2026;Serviços ;Exame;40310060;Bacterioscopia Gram, Ziehl, Albert etc, por lamina;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;6,19;08/2026;Serviços ;Exame;40310213;Cultura, urina com contagem de colonias;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;11,15;08/2026;Serviços ;Exame;40310426;Antibiograma automatizado;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;2,48;08/2026;Serviços ;Exame;40311210;Rotina de urina caracteres fisicos, elementos anormais e sedimentoscopia;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;9,29;08/2026;Serviços ;Exame;40316190;Cortisol - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;12,39;08/2026;Serviços ;Exame;40316246;Estradiol - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;15,48;08/2026;Serviços ;Exame;40316270;Ferritina - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;8,05;08/2026;Serviços ;Exame;40316289;Foliculo estimulante, hormonio FSH - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;7,8;08/2026;Serviços ;Exame;40316335;Hormonio luteinizante LH - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;13,01;08/2026;Serviços ;Exame;40316408;Progesterona - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;10,53;08/2026;Serviços ;Exame;40316416;Prolactina - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;10,53;08/2026;Serviços ;Exame;40316491;T4 livre - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;10,53;08/2026;Serviços ;Exame;40316521;Tireoestimulante, hormonio TSH - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;10,53;08/2026;Serviços ;Exame;40316572;Vitamina B12 - pesquisa e/ou dosagem;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;143,99;08/2026;Serviços ;Exame;40601200;Procedimento diagnostico em peca anatomica ou cirurgica simples;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;74,25;08/2026;Serviços ;Exame;40808041;Mamografia digital bilateral com diretriz definida pela ANS - n 52;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;59,76;08/2026;Serviços ;Exame;40808130;Densitometria ossea - rotina: coluna e femur ou dois segmentos;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;38,52;08/2026;Serviços ;Exame;40901114;US - Mamas;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;755807;'9750167003885950;LUCIANA CARDOSO FERNANDES;1;0 - TITULAR;10008;;44,36;08/2026;Serviços ;Exame;40901300;US - Transvaginal utero, ovario, anexos e vagina;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;755807;LUCIANA CARDOSO FERNANDES - 840127;857049;'9750167018903290;JOEL MARCONCINI;2;1 - CONJUGE;10008;;490,29;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;780352;EVELYN CHRISTINE CURCINO - 847272;780352;'9750167013066030;EVELYN CHRISTINE CURCINO;1;0 - TITULAR;10215;;210,38;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;781791;TAILLANY TAMYRISS DE PIERRI MARQUES - 848274;781791;'9750167013085115;TAILLANY TAMYRISS DE PIERRI MARQUES;1;0 - TITULAR;10215;;199,65;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;781791;TAILLANY TAMYRISS DE PIERRI MARQUES - 848274;781791;'9750167013085115;TAILLANY TAMYRISS DE PIERRI MARQUES;1;0 - TITULAR;10215;;74,25;08/2026;Serviços ;Exame;40901122;US - Abdome total abdome superior, rins, bexiga, aorta, veia cava inferior e ad;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;781791;TAILLANY TAMYRISS DE PIERRI MARQUES - 848274;781791;'9750167013085115;TAILLANY TAMYRISS DE PIERRI MARQUES;1;0 - TITULAR;10215;;39,43;08/2026;Serviços ;Exame;40901211;US - Estruturas superficiais cervical ou axila ou musculo ou tendao;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;781791;TAILLANY TAMYRISS DE PIERRI MARQUES - 848274;781791;'9750167013085115;TAILLANY TAMYRISS DE PIERRI MARQUES;1;0 - TITULAR;10215;;44,36;08/2026;Serviços ;Exame;40901300;US - Transvaginal utero, ovario, anexos e vagina;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;795128;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372;795128;'9750167013261614;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG;1;0 - TITULAR;10215;;210,38;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;795128;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372;795128;'9750167013261614;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG;1;0 - TITULAR;10215;;74,25;08/2026;Serviços ;Consulta;10101039;Consulta em pronto socorro;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;795128;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372;798055;'9750167014716674;MARIA JULIA MERTIG FAGUNDES;2;10 - FILHO(A);10215;;151,51;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;795128;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372;798339;'9750167015073823;ROBERTO CARLOS CHINAIDER FAGUNDES;2;1 - CONJUGE;10215;;238,92;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;819205;FRANCISCO AQUINO CHAPARRO - 874116;819205;'9750167018482406;FRANCISCO AQUINO CHAPARRO;1;0 - TITULAR;10008;;272,8;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;819205;FRANCISCO AQUINO CHAPARRO - 874116;822752;'9750167018523960;CAMILA CASSIA MORAES COSTA;2;1 - CONJUGE;10008;;272,8;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;844618;FRANCIELLI SOUZA DE JESUS - 886633;844618;'9750167018752799;FRANCIELLI SOUZA DE JESUS;1;0 - TITULAR;10215;;290,61;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;844618;FRANCIELLI SOUZA DE JESUS - 886633;849666;'9750167018811701;ELOIZA DE SOUZA;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;844618;FRANCIELLI SOUZA DE JESUS - 886633;849667;'9750167018811710;ELENA DE SOUZA;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;853105;KESSY KOHNLEIN - 892339;853105;'9750167018854672;KESSY KOHNLEIN;1;0 - TITULAR;10220;;137,23;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;854462;DAIANE DA LUZ GUEIROS DA SILVA - 893274;854462;'9750167018871135;DAIANE DA LUZ GUEIROS DA SILVA;1;0 - TITULAR;10008;;213,95;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;854462;DAIANE DA LUZ GUEIROS DA SILVA - 893274;856557;'9750167018897436;RENAN GABRIEL DA LUZ DOMINGOS;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
5884128-0;3187862;201960424;6818,83;6818,83;Aberto;ROSSONI PIOTTO CIA LTDA - 00384013000454;854462;DAIANE DA LUZ GUEIROS DA SILVA - 893274;869895;'9750167019048971;LUCCA GABRIEL DA LUZ DE JESUS;2;10 - FILHO(A);10008;;130,13;08/2026;Mensalidade;;;;
1 Cód. Contr/Contrat. Pagador Id. Título Número Título Valor Bruto Valor Líquido Situação Título Contratante Id. Titular Família Id. Benef. Cód.Beneficiário Nome Beneficiário Tipo Benef. Grau Depend. Código Combinação Reservado Valor Competência Tipo Tipo Ato Médico Cód. Serviço Descrição Serviço
2 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 756165 TABATA CAROLINE GOES DA SILVA - 835199 756165 '9750167003613895 TABATA CAROLINE GOES DA SILVA 1 0 - TITULAR 10215 272,77 08/2026 Mensalidade
3 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 756165 TABATA CAROLINE GOES DA SILVA - 835199 754971 '9750167003821633 WENDY MANOEL MIGUEL MELO DA SILVA 2 1 - CONJUGE 10008 231,78 08/2026 Mensalidade
4 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 756165 TABATA CAROLINE GOES DA SILVA - 835199 756166 '9750167003613909 LUISA VALENTINA GOES DE MELO 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade
5 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 762483 ADRIANA JARDIM DE CARVALHO - 835478 762483 '9750167003631320 ADRIANA JARDIM DE CARVALHO 1 0 - TITULAR 10008 706,01 08/2026 Mensalidade
6 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 761760 TAWANY THYESKA DE PIERRI MARQUES - 835910 761760 '9750167003658732 TAWANY THYESKA DE PIERRI MARQUES 1 0 - TITULAR 10215 238,92 08/2026 Mensalidade
7 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 761760 TAWANY THYESKA DE PIERRI MARQUES - 835910 761761 '9750167003658740 THAEMY LOHAYNE DE PIERRE BORGES 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade
8 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 351,21 08/2026 Mensalidade
9 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 110 08/2026 Serviços Consulta 10101012 Consulta em consultorio no horario normal ou preestabelecido
10 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 45,65 08/2026 Serviços Exame 41301080 Ceratoscopia computadorizada - monocular
11 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 4,26 08/2026 Serviços Exame 41301323 Tonometria - binocular
12 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 51,08 08/2026 Serviços Exame 41501128 Paquimetria ultrassonica - monocular
13 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 766974 EDSON AUGUSTO DOS SANTOS - 838034 766974 '9750167003785599 EDSON AUGUSTO DOS SANTOS 1 0 - TITULAR 10008 28,98 08/2026 Serviços Outros 50000560 Consulta ambulatorial por nutricionista com diretriz definida pela ANS - n 103
14 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 757635 JOICE GABRIELLY CONRADO - 840123 757635 '9750167003885879 JOICE GABRIELLY CONRADO 1 0 - TITULAR 10215 199,65 08/2026 Mensalidade
15 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 272,8 08/2026 Mensalidade
16 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 74,25 08/2026 Serviços Consulta 10101039 Consulta em pronto socorro
17 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 3,72 08/2026 Serviços Exame 40301583 Colesterol HDL - pesquisa e/ou dosagem
18 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 4,96 08/2026 Serviços Exame 40301591 Colesterol LDL - pesquisa e/ou dosagem
19 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40301605 Colesterol total - pesquisa e/ou dosagem
20 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40301630 Creatinina - pesquisa e/ou dosagem
21 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40302040 Glicose - pesquisa e/ou dosagem
22 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40302504 Transaminase oxalacetica amino transferase aspartato - pesquisa e/ou dosagem
23 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40302512 Transaminase piruvica amino transferase de alanina - pesquisa e/ou dosagem
24 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 2,48 08/2026 Serviços Exame 40302547 Triglicerideos - pesquisa e/ou dosagem
25 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,74 08/2026 Serviços Exame 40302580 Ureia - pesquisa e/ou dosagem
26 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 5,58 08/2026 Serviços Exame 40302733 Hemoglobina glicada Fracao A1c - pesquisa e/ou dosagem
27 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 8,28 08/2026 Serviços Exame 40302830 Vitamina D 25 HIDROXI, pesquisa e/ou dosagem Vitamina D3
28 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 3,72 08/2026 Serviços Exame 40304361 Hemograma com contagem de plaquetas ou fracoes eritrograma, leucograma, plaquet
29 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 1,86 08/2026 Serviços Exame 40310060 Bacterioscopia Gram, Ziehl, Albert etc, por lamina
30 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 6,19 08/2026 Serviços Exame 40310213 Cultura, urina com contagem de colonias
31 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 11,15 08/2026 Serviços Exame 40310426 Antibiograma automatizado
32 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 2,48 08/2026 Serviços Exame 40311210 Rotina de urina caracteres fisicos, elementos anormais e sedimentoscopia
33 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 9,29 08/2026 Serviços Exame 40316190 Cortisol - pesquisa e/ou dosagem
34 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 12,39 08/2026 Serviços Exame 40316246 Estradiol - pesquisa e/ou dosagem
35 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 15,48 08/2026 Serviços Exame 40316270 Ferritina - pesquisa e/ou dosagem
36 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 8,05 08/2026 Serviços Exame 40316289 Foliculo estimulante, hormonio FSH - pesquisa e/ou dosagem
37 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 7,8 08/2026 Serviços Exame 40316335 Hormonio luteinizante LH - pesquisa e/ou dosagem
38 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 13,01 08/2026 Serviços Exame 40316408 Progesterona - pesquisa e/ou dosagem
39 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 10,53 08/2026 Serviços Exame 40316416 Prolactina - pesquisa e/ou dosagem
40 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 10,53 08/2026 Serviços Exame 40316491 T4 livre - pesquisa e/ou dosagem
41 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 10,53 08/2026 Serviços Exame 40316521 Tireoestimulante, hormonio TSH - pesquisa e/ou dosagem
42 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 10,53 08/2026 Serviços Exame 40316572 Vitamina B12 - pesquisa e/ou dosagem
43 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 143,99 08/2026 Serviços Exame 40601200 Procedimento diagnostico em peca anatomica ou cirurgica simples
44 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 74,25 08/2026 Serviços Exame 40808041 Mamografia digital bilateral com diretriz definida pela ANS - n 52
45 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 59,76 08/2026 Serviços Exame 40808130 Densitometria ossea - rotina: coluna e femur ou dois segmentos
46 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 38,52 08/2026 Serviços Exame 40901114 US - Mamas
47 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 755807 '9750167003885950 LUCIANA CARDOSO FERNANDES 1 0 - TITULAR 10008 44,36 08/2026 Serviços Exame 40901300 US - Transvaginal utero, ovario, anexos e vagina
48 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 755807 LUCIANA CARDOSO FERNANDES - 840127 857049 '9750167018903290 JOEL MARCONCINI 2 1 - CONJUGE 10008 490,29 08/2026 Mensalidade
49 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 780352 EVELYN CHRISTINE CURCINO - 847272 780352 '9750167013066030 EVELYN CHRISTINE CURCINO 1 0 - TITULAR 10215 210,38 08/2026 Mensalidade
50 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 781791 TAILLANY TAMYRISS DE PIERRI MARQUES - 848274 781791 '9750167013085115 TAILLANY TAMYRISS DE PIERRI MARQUES 1 0 - TITULAR 10215 199,65 08/2026 Mensalidade
51 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 781791 TAILLANY TAMYRISS DE PIERRI MARQUES - 848274 781791 '9750167013085115 TAILLANY TAMYRISS DE PIERRI MARQUES 1 0 - TITULAR 10215 74,25 08/2026 Serviços Exame 40901122 US - Abdome total abdome superior, rins, bexiga, aorta, veia cava inferior e ad
52 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 781791 TAILLANY TAMYRISS DE PIERRI MARQUES - 848274 781791 '9750167013085115 TAILLANY TAMYRISS DE PIERRI MARQUES 1 0 - TITULAR 10215 39,43 08/2026 Serviços Exame 40901211 US - Estruturas superficiais cervical ou axila ou musculo ou tendao
53 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 781791 TAILLANY TAMYRISS DE PIERRI MARQUES - 848274 781791 '9750167013085115 TAILLANY TAMYRISS DE PIERRI MARQUES 1 0 - TITULAR 10215 44,36 08/2026 Serviços Exame 40901300 US - Transvaginal utero, ovario, anexos e vagina
54 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 795128 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372 795128 '9750167013261614 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG 1 0 - TITULAR 10215 210,38 08/2026 Mensalidade
55 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 795128 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372 795128 '9750167013261614 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG 1 0 - TITULAR 10215 74,25 08/2026 Serviços Consulta 10101039 Consulta em pronto socorro
56 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 795128 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372 798055 '9750167014716674 MARIA JULIA MERTIG FAGUNDES 2 10 - FILHO(A) 10215 151,51 08/2026 Mensalidade
57 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 795128 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG - 858372 798339 '9750167015073823 ROBERTO CARLOS CHINAIDER FAGUNDES 2 1 - CONJUGE 10215 238,92 08/2026 Mensalidade
58 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 819205 FRANCISCO AQUINO CHAPARRO - 874116 819205 '9750167018482406 FRANCISCO AQUINO CHAPARRO 1 0 - TITULAR 10008 272,8 08/2026 Mensalidade
59 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 819205 FRANCISCO AQUINO CHAPARRO - 874116 822752 '9750167018523960 CAMILA CASSIA MORAES COSTA 2 1 - CONJUGE 10008 272,8 08/2026 Mensalidade
60 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 844618 FRANCIELLI SOUZA DE JESUS - 886633 844618 '9750167018752799 FRANCIELLI SOUZA DE JESUS 1 0 - TITULAR 10215 290,61 08/2026 Mensalidade
61 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 844618 FRANCIELLI SOUZA DE JESUS - 886633 849666 '9750167018811701 ELOIZA DE SOUZA 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade
62 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 844618 FRANCIELLI SOUZA DE JESUS - 886633 849667 '9750167018811710 ELENA DE SOUZA 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade
63 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 853105 KESSY KOHNLEIN - 892339 853105 '9750167018854672 KESSY KOHNLEIN 1 0 - TITULAR 10220 137,23 08/2026 Mensalidade
64 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 854462 DAIANE DA LUZ GUEIROS DA SILVA - 893274 854462 '9750167018871135 DAIANE DA LUZ GUEIROS DA SILVA 1 0 - TITULAR 10008 213,95 08/2026 Mensalidade
65 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 854462 DAIANE DA LUZ GUEIROS DA SILVA - 893274 856557 '9750167018897436 RENAN GABRIEL DA LUZ DOMINGOS 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade
66 5884128-0 3187862 201960424 6818,83 6818,83 Aberto ROSSONI PIOTTO CIA LTDA - 00384013000454 854462 DAIANE DA LUZ GUEIROS DA SILVA - 893274 869895 '9750167019048971 LUCCA GABRIEL DA LUZ DE JESUS 2 10 - FILHO(A) 10008 130,13 08/2026 Mensalidade

View File

@ -0,0 +1,23 @@
CODIGOEMPRESA;NOMEFUNC;CPFFUNC;CODIGOOUTEMP;DATAINICIAL;NOMEDEPENDENTE;CPFDEPENDENTE;VALOREMPRESA;VALOR;DESCRICAO
221;ADRIANA JARDIM DE CARVALHO;569.433.180-04;5060;01/02/2025;;;0;0;
221;DAIANE DA LUZ GUEIROS DA SILVA;080.947.869-22;5060;01/07/2026;RENAN GABRIEL DA LUZ DOMINGOS;800.859.409-84;0;0;
221;DAIANE DA LUZ GUEIROS DA SILVA;080.947.869-22;5060;01/07/2026;;;0;0;
221;DAIANE DA LUZ GUEIROS DA SILVA;080.947.869-22;5060;01/07/2026;LUCCA GABRIEL DA LUZ DE JESUS;002.261.929-11;0;0;
221;EDSON AUGUSTO DOS SANTOS;033.535.459-99;5060;01/06/2025;;;0;0;
221;EVELYN CHRISTINE CURCINO;102.022.109-79;5060;01/06/2025;;;0;0;
221;FRANCIELLI SOUZA DE JESUS;056.918.579-32;5060;01/02/2026;ELENA DE SOUZA;158.076.209-33;0;0;
221;FRANCIELLI SOUZA DE JESUS;056.918.579-32;5060;01/02/2026;;;0;0;
221;FRANCIELLI SOUZA DE JESUS;056.918.579-32;5060;01/02/2026;ELOIZA DE SOUZA;142.759.789-86;0;0;
221;FRANCISCO AQUINO CHAPARRO;043.273.629-80;5060;01/11/2025;CAMILA CASSIA MORAES COSTA;042.355.369-07;0;0;
221;FRANCISCO AQUINO CHAPARRO;043.273.629-80;5060;01/11/2025;;;0;0;
221;JOICE GABRIELLY CONRADO;013.817.749-03;5060;01/06/2025;;;0;0;
221;KESSY KOHNLEIN;107.820.969-30;5060;01/03/2026;;;0;0;
221;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG;121.074.169-58;5060;01/06/2025;;;0;0;
221;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG;121.074.169-58;5060;01/06/2025;ROBERTO CARLOS CHINAIDER FAGUNDES;078.468.529-06;0;0;
221;KETLIN RAYANI SILVA DE OLIVEIRA MERTIG;121.074.169-58;5060;01/06/2025;MARIA JULIA MERTIG FAGUNDES;156.306.539-89;0;0;
221;LUCIANA CARDOSO FERNANDES;044.214.069-02;5060;01/04/2026;JOEL MARCONCINI;969.398.939-20;0;0;
221;LUCIANA CARDOSO FERNANDES;044.214.069-02;5060;01/04/2026;;;0;0;
221;TABATA CAROLINE GOES DA SILVA;072.082.879-10;5060;01/06/2025;LUISA VALENTINA GOES MELO;107.096.349-60;0;0;
221;TABATA CAROLINE GOES DA SILVA;072.082.879-10;5060;01/06/2025;WENDY MANOEL MIGUEL MELO DA SILVA;080.701.949-60;0;0;
221;TABATA CAROLINE GOES DA SILVA;072.082.879-10;5060;01/06/2025;;;0;0;
221;TAILLANY TAMYRISS DE PIERRI MARQUES;120.239.569-43;5060;01/06/2025;;;0;0;
1 CODIGOEMPRESA NOMEFUNC CPFFUNC CODIGOOUTEMP DATAINICIAL NOMEDEPENDENTE CPFDEPENDENTE VALOREMPRESA VALOR DESCRICAO
2 221 ADRIANA JARDIM DE CARVALHO 569.433.180-04 5060 01/02/2025 0 0
3 221 DAIANE DA LUZ GUEIROS DA SILVA 080.947.869-22 5060 01/07/2026 RENAN GABRIEL DA LUZ DOMINGOS 800.859.409-84 0 0
4 221 DAIANE DA LUZ GUEIROS DA SILVA 080.947.869-22 5060 01/07/2026 0 0
5 221 DAIANE DA LUZ GUEIROS DA SILVA 080.947.869-22 5060 01/07/2026 LUCCA GABRIEL DA LUZ DE JESUS 002.261.929-11 0 0
6 221 EDSON AUGUSTO DOS SANTOS 033.535.459-99 5060 01/06/2025 0 0
7 221 EVELYN CHRISTINE CURCINO 102.022.109-79 5060 01/06/2025 0 0
8 221 FRANCIELLI SOUZA DE JESUS 056.918.579-32 5060 01/02/2026 ELENA DE SOUZA 158.076.209-33 0 0
9 221 FRANCIELLI SOUZA DE JESUS 056.918.579-32 5060 01/02/2026 0 0
10 221 FRANCIELLI SOUZA DE JESUS 056.918.579-32 5060 01/02/2026 ELOIZA DE SOUZA 142.759.789-86 0 0
11 221 FRANCISCO AQUINO CHAPARRO 043.273.629-80 5060 01/11/2025 CAMILA CASSIA MORAES COSTA 042.355.369-07 0 0
12 221 FRANCISCO AQUINO CHAPARRO 043.273.629-80 5060 01/11/2025 0 0
13 221 JOICE GABRIELLY CONRADO 013.817.749-03 5060 01/06/2025 0 0
14 221 KESSY KOHNLEIN 107.820.969-30 5060 01/03/2026 0 0
15 221 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG 121.074.169-58 5060 01/06/2025 0 0
16 221 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG 121.074.169-58 5060 01/06/2025 ROBERTO CARLOS CHINAIDER FAGUNDES 078.468.529-06 0 0
17 221 KETLIN RAYANI SILVA DE OLIVEIRA MERTIG 121.074.169-58 5060 01/06/2025 MARIA JULIA MERTIG FAGUNDES 156.306.539-89 0 0
18 221 LUCIANA CARDOSO FERNANDES 044.214.069-02 5060 01/04/2026 JOEL MARCONCINI 969.398.939-20 0 0
19 221 LUCIANA CARDOSO FERNANDES 044.214.069-02 5060 01/04/2026 0 0
20 221 TABATA CAROLINE GOES DA SILVA 072.082.879-10 5060 01/06/2025 LUISA VALENTINA GOES MELO 107.096.349-60 0 0
21 221 TABATA CAROLINE GOES DA SILVA 072.082.879-10 5060 01/06/2025 WENDY MANOEL MIGUEL MELO DA SILVA 080.701.949-60 0 0
22 221 TABATA CAROLINE GOES DA SILVA 072.082.879-10 5060 01/06/2025 0 0
23 221 TAILLANY TAMYRISS DE PIERRI MARQUES 120.239.569-43 5060 01/06/2025 0 0

View File

@ -0,0 +1,24 @@
CODIGOEMPRESA;NOMEFUNC;CPFFUNC;CODIGOOUTEMP;DATAINICIAL;NOMEDEPENDENTE;CPFDEPENDENTE;VALOREMPRESA;VALOR;DESCRICAO
221;ADRIANA OLIVEIRA DOS SANTOS CODO;022.366.869-94;3755;01/12/2023;;;0;0;
221;ADRIANA OLIVEIRA DOS SANTOS CODO;022.366.869-94;3755;01/12/2023;RAFHAEL HENRIQUE CODO;133.710.699-23;0;0;
221;ANELISA CLAUDIA TOLEDO;028.627.449-30;3755;01/12/2023;MURILO AUGUSTO TOLEDO SILVA;140.796.419-40;0;0;
221;ANELISA CLAUDIA TOLEDO;028.627.449-30;3755;01/12/2023;;;0;0;
221;APARECIDA DOBNER;389.259.312-49;3755;01/12/2023;ANNA CAROLINA DOBNER WILLMBRINK;097.280.639-38;0;0;
221;APARECIDA DOBNER;389.259.312-49;3755;01/12/2023;;;0;0;
221;DIEGO RODRIGO DA FONSECA;007.377.729-30;3755;01/12/2023;;;0;0;
221;ELIANA KARPSAK LOPES;064.896.469-86;3755;01/12/2023;;;0;0;
221;ELIANA KARPSAK LOPES;064.896.469-86;3755;01/12/2023;LUCAS ANTONIO DE OLIVEIRA;066.200.469-83;0;0;
221;FERNANDO BERLATTO;040.875.749-38;3755;01/12/2023;;;0;0;
221;JANE MARILIA BEKER;025.273.319-39;3755;01/12/2023;;;0;0;
221;JESSICA DE FATIMA CATTANI DE PAULA;092.879.749-00;3755;01/12/2023;;;0;0;
221;JOAO VICTOR DE OLIVEIRA EGIDIO;051.271.009-08;3755;01/12/2023;;;0;0;
221;LEANDRA SPEROTTO;022.795.359-20;3755;01/12/2023;;;0;0;
221;LEANDRA SPEROTTO;022.795.359-20;3755;01/12/2023;LUCAS SPEROTTO SCHIRMANN;125.709.619-22;0;0;
221;LUCAS DE SOUZA;093.861.469-08;3755;01/12/2023;;;0;0;
221;MARCOS PAULO GOBBO;057.539.659-89;3755;01/12/2023;;;0;0;
221;MARIZA KARPSAK LOPES;726.088.899-72;3755;01/12/2023;EDUARDO KARPSAK LOPES;096.771.579-22;0;0;
221;MARIZA KARPSAK LOPES;726.088.899-72;3755;01/12/2023;;;0;0;
221;PAULA MICHELLI DA SILVA;073.516.259-05;3755;01/12/2023;;;0;0;
221;PAULA MICHELLI DA SILVA;073.516.259-05;3755;01/12/2023;EMANUELLY FERREIRA DA SILVA;152.794.939-70;0;0;
221;VANESSA JUIF TRINDADE;032.476.309-37;3755;01/12/2023;;;0;0;
221;ZAIRA DAVID BARBOSA;007.396.889-79;3755;01/12/2023;;;0;0;
1 CODIGOEMPRESA NOMEFUNC CPFFUNC CODIGOOUTEMP DATAINICIAL NOMEDEPENDENTE CPFDEPENDENTE VALOREMPRESA VALOR DESCRICAO
2 221 ADRIANA OLIVEIRA DOS SANTOS CODO 022.366.869-94 3755 01/12/2023 0 0
3 221 ADRIANA OLIVEIRA DOS SANTOS CODO 022.366.869-94 3755 01/12/2023 RAFHAEL HENRIQUE CODO 133.710.699-23 0 0
4 221 ANELISA CLAUDIA TOLEDO 028.627.449-30 3755 01/12/2023 MURILO AUGUSTO TOLEDO SILVA 140.796.419-40 0 0
5 221 ANELISA CLAUDIA TOLEDO 028.627.449-30 3755 01/12/2023 0 0
6 221 APARECIDA DOBNER 389.259.312-49 3755 01/12/2023 ANNA CAROLINA DOBNER WILLMBRINK 097.280.639-38 0 0
7 221 APARECIDA DOBNER 389.259.312-49 3755 01/12/2023 0 0
8 221 DIEGO RODRIGO DA FONSECA 007.377.729-30 3755 01/12/2023 0 0
9 221 ELIANA KARPSAK LOPES 064.896.469-86 3755 01/12/2023 0 0
10 221 ELIANA KARPSAK LOPES 064.896.469-86 3755 01/12/2023 LUCAS ANTONIO DE OLIVEIRA 066.200.469-83 0 0
11 221 FERNANDO BERLATTO 040.875.749-38 3755 01/12/2023 0 0
12 221 JANE MARILIA BEKER 025.273.319-39 3755 01/12/2023 0 0
13 221 JESSICA DE FATIMA CATTANI DE PAULA 092.879.749-00 3755 01/12/2023 0 0
14 221 JOAO VICTOR DE OLIVEIRA EGIDIO 051.271.009-08 3755 01/12/2023 0 0
15 221 LEANDRA SPEROTTO 022.795.359-20 3755 01/12/2023 0 0
16 221 LEANDRA SPEROTTO 022.795.359-20 3755 01/12/2023 LUCAS SPEROTTO SCHIRMANN 125.709.619-22 0 0
17 221 LUCAS DE SOUZA 093.861.469-08 3755 01/12/2023 0 0
18 221 MARCOS PAULO GOBBO 057.539.659-89 3755 01/12/2023 0 0
19 221 MARIZA KARPSAK LOPES 726.088.899-72 3755 01/12/2023 EDUARDO KARPSAK LOPES 096.771.579-22 0 0
20 221 MARIZA KARPSAK LOPES 726.088.899-72 3755 01/12/2023 0 0
21 221 PAULA MICHELLI DA SILVA 073.516.259-05 3755 01/12/2023 0 0
22 221 PAULA MICHELLI DA SILVA 073.516.259-05 3755 01/12/2023 EMANUELLY FERREIRA DA SILVA 152.794.939-70 0 0
23 221 VANESSA JUIF TRINDADE 032.476.309-37 3755 01/12/2023 0 0
24 221 ZAIRA DAVID BARBOSA 007.396.889-79 3755 01/12/2023 0 0

View File

@ -1043,6 +1043,30 @@ Sequência natural da rodada 77 (fundo de `.login-page`): o usuário pediu o mes
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. 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.
### 80. Buscar a planilha padrão do Questor via SQL (Importação de Plano de Saúde)
Até aqui, "Nova Importação" exigia exportar manualmente do Questor a "planilha padrão" (CSV) e anexá-la, além do relatório da operadora. Usuário validou uma consulta SQL contra o Questor (mesmo pacote `database/` só-leitura já usado em `empresas_questor.py`) que devolve exatamente essa informação a partir de `codigo_empresa` (já cadastrado em `RegraCusteioPlanoSaude`) + operadora + competência (mês/ano) — eliminando o passo manual na maioria dos casos.
- **Upload manual continua existindo**, como alternativa (Questor fora do ar, ou empresa ainda não migrada) — decisão explícita do usuário. "Nova Importação" ganhou um toggle "Buscar automaticamente do Questor" (padrão) / "Anexar manualmente", com um campo "Competência" (`<input type="month">`) só no primeiro modo.
- **Código da operadora**: até então só existia embutido no `label` de `pipeline.OPERADORAS` (ex. "5060 - Unimed Saúde"), extraído por *string split* onde precisava só do nome. Separado em campos próprios (`codigo_operadora`/`nome`), com `label_operadora()` calculando o label onde ainda é exibido — elimina o split e dá um valor confiável pra filtrar a consulta por operadora.
- **Nova consulta** (`sqls.questor.QuestorSQL.consulta_planilha_plano_saude`) e novo módulo `portal_api/planos_saude/questor_planilha.py` (`busca_linhas_questor`/`linhas_para_csv_bytes`) — a busca é resolvida **antes** de criar o registro `ImportacaoPlanoSaude` (uma falha de conexão nunca deixa nada órfão pra limpar, diferente do caminho de upload). `pipeline.processa_importacao` deixou de ler o arquivo sozinho (`caminho_planilha_padrao: str`) e passou a receber a lista de `LinhaSistema` já resolvida (`linhas_sistema_template`), de qualquer uma das duas origens — decisão de qual usar ficou em `views.py`.
- Mesmo vindo do Questor, um CSV é gerado (mesmo formato do upload) e salvo no campo `planilha_padrao` — preserva o histórico completo já documentado pra toda importação. Novo campo `ImportacaoPlanoSaude.competencia` (`DateField`, null) registra a competência usada, só quando a origem foi o Questor (migração `0045`).
- A "trava de conferência do código de empresa" (existente desde a rodada do Cadastro de Regras) é pulada quando a origem é Questor — redundante, já que a própria consulta já filtrou por aquele `codigo_empresa`.
**Ajuste no mesmo dia, depois do primeiro teste real**: o campo "Competência" nasceu como `<input type="month">` — usuário rejeitou o seletor nativo do browser como "horrível" e pediu o mesmo padrão já usado no Indicador de Desempenho: texto livre com máscara MM/AAAA (`mascaraCompetencia()`/`competenciaParaIso()`, cópia local das mesmas funções de `indicador-desempenho.js`), o usuário digita "082026" e o campo forma "08/2026" sozinho. Backend acompanhou: `competencia` no serializer é um `DateField` normal (mesmo padrão de `IndicadorApuracaoCreateSerializer`), recebendo o ISO `"AAAA-MM-01"` já convertido pelo JS, nunca a string mascarada crua.
**Segundo ajuste, mesmo teste real**: a coluna `DATAINICIAL` do CSV gerado via Questor saía no formato ISO (`2023-12-01`, `linha["datainicial"].isoformat()` — a renderização padrão do Postgres pra uma coluna `date`), mas o leiaute real de importação do sistema espera `DD/MM/AAAA` (`01/12/2023`) nessa coluna — mesmo formato que uma planilha padrão exportada manualmente do Questor já usa. Corrigido em `questor_planilha.busca_linhas_questor()` pra `linha["datainicial"].strftime("%d/%m/%Y")`. `data_inicial` é sempre um passthrough string em todo o pipeline (nenhum outro ponto faz parsing de data nele, só grava/lê a string), então a mudança não exigiu tocar em mais nenhum arquivo. Nenhum registro histórico ficou com o formato errado — a única importação de teste que chegou a usar a busca via Questor (antes deste ajuste) já tinha sido excluída pelo próprio usuário.
**Terceiro ajuste, mesmo dia — regra de edição da tabela de revisão revista**: até aqui, todo campo de `ImportacaoPlanoSaudeLinha` era editável em qualquer linha na tela de Revisão (decisão de uma rodada anterior). Vendo dados reais de uma importação via Questor (22 linhas, várias famílias), o usuário pediu pra restringir: só `Valor Empresa`/`Valor` editáveis numa linha que já veio do processamento (upload ou Questor) — os demais campos (cadastro da pessoa: nome, CPF, código, data...) só ficam editáveis numa linha incluída manualmente via "Adicionar linha", e só nela. Implementado 100% no frontend (`importacao-plano-saude.js`, `linhasIncluidasManualmente()`), sem campo novo: reaproveita o sinal que já existia em `ImportacaoPlanoSaudeAlteracao` (toda linha criada via "Adicionar linha" já gerava um registro `tipo="inclusao"` ali, só pra alimentar a aba "Alterações" — agora também decide, no cliente, quais linhas ganham os campos extras liberados). O backend continua aceitando PATCH em qualquer campo (nenhuma trava nova no serializer/model) — é uma restrição de UI, não de permissão.
**Quarto ajuste, mesmo dia — trava de edição depois de "Gerar Arquivo"**: o ajuste anterior já restringia *quais campos*, mas o usuário notou que uma importação já `concluida` (arquivo já gerado/entregue) continuava 100% editável — pediu pra travar de vez, com um botão "Editar" explícito pra reabrir quando for realmente necessário corrigir algo depois da conclusão. Diferente do ajuste anterior, este saiu **também no backend**: `_garante_importacao_em_revisao(importacao)` (nova função em `views.py`) é chamada em todo ponto que escreve dentro de uma importação já criada — `ImportacaoPlanoSaudeLinhaViewSet.perform_create/update/destroy`, `ImportacaoPlanoSaudeAuditoriaViewSet.resolver`, `ImportacaoPlanoSaudeAlteracaoViewSet.reverter` — e recusa (400) se `status == "concluida"`. Novo endpoint `POST /api/importacoes-plano-saude/{id}/reabrir/` (`ImportacaoPlanoSaudeViewSet.reabrir`) volta `status="revisao"` e zera `concluida_em` — único jeito de destravar. `gerar()` em si nunca é bloqueado (sempre pode regerar/rebaixar o arquivo já concluído). No frontend, a tela de Revisão some com todo controle de edição (inputs viram texto, "×"/"Adicionar linha"/"Vincular pessoa"/"Reverter" somem) quando `status === "concluida"`, e mostra um botão "Editar" (ao lado de "Gerar Arquivo") que chama o `reabrir()` e volta tudo ao normal. Testado ponta a ponta via `APIRequestFactory`/`force_authenticate` (sem afetar dados reais, dentro de uma transação revertida): reabrir muda o status, edita normalmente em revisão, e volta a bloquear ao marcar concluída de novo.
**Quinto ajuste, mesmo dia**: "Gerar Arquivo" passou a voltar direto pro histórico depois do download disparar (`showView("list")` + `refreshList()`) — como a importação já fica `concluida`/travada nesse momento (ajuste anterior), não fazia sentido continuar na tela de Revisão sem nada pra fazer ali.
**Sexto ajuste, mesmo dia — filtro no histórico**: com o histórico crescendo (23 importações já), o usuário pediu pra filtrar a lista, não só ordenar — ex.: ver todas as execuções da empresa 221, ou combinar empresa 221 + operadora 3755. Primeira versão foi um campo de texto por coluna (Cód. Empresa/Operadora/Criado por) + um `<select>` pra Status. 100% client-side, sem endpoint novo.
**Sétimo ajuste, mesmo dia — filtro revisado pra "estilo Excel"**: usuário pediu pra trocar os campos de texto por um filtro de planilha de verdade — clicar num ícone de funil na coluna e marcar/desmarcar os valores que aparecem numa lista, como no AutoFilter do Excel. Reescrito (`criarFiltroColuna()`) como um popup por coluna com busca + checklist dos valores distintos daquela coluna (reaproveita `.checklist-box` de Perfis de Acesso, sem CSS/componente novo pra isso) e botões "Aplicar"/"Limpar" — os filtros das 4 colunas continuam combinando entre si (AND). Precisou de um ajuste de CSS colateral: `.pa-table-wrap` corta com `overflow:hidden` pra arredondar os cantos da tabela, o que cortaria o popup também — resolvido com uma classe extra só nesta tabela (`.ips-list-table-wrap`) sobrescrevendo pra `overflow:visible`.
## Roadmap / próximos passos ## Roadmap / próximos passos
Nenhuma pendência explícita em aberto no momento, exceto a limitação conhecida Nenhuma pendência explícita em aberto no momento, exceto a limitação conhecida

View File

@ -0,0 +1,24 @@
# Generated by Django 6.0.7 on 2026-08-21 20:05
import portal_api.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('portal_api', '0044_empresaquestor'),
]
operations = [
migrations.AddField(
model_name='importacaoplanosaude',
name='competencia',
field=models.DateField(blank=True, null=True, verbose_name='Competência (Questor)'),
),
migrations.AlterField(
model_name='importacaoplanosaude',
name='planilha_padrao',
field=models.FileField(blank=True, max_length=255, upload_to='planos_saude/planilha_padrao/', validators=[portal_api.models.validar_tamanho_arquivo_plano_saude], verbose_name='Planilha padrão (Questor)'),
),
]

View File

@ -563,8 +563,16 @@ class ImportacaoPlanoSaude(models.Model):
"Planilha padrão (Questor)", "Planilha padrão (Questor)",
upload_to="planos_saude/planilha_padrao/", upload_to="planos_saude/planilha_padrao/",
max_length=255, max_length=255,
blank=True,
validators=[validar_tamanho_arquivo_plano_saude], validators=[validar_tamanho_arquivo_plano_saude],
) )
# Preenchida só quando a planilha padrão veio de uma busca direta no
# Questor (via SQL) em vez de upload manual — ver
# portal_api.planos_saude.questor_planilha. Nesse caso, planilha_padrao
# ainda é salva (um CSV gerado no mesmo formato do upload), só que essa
# data indica a origem alternativa. Fica em branco pra toda importação
# que veio por upload.
competencia = models.DateField("Competência (Questor)", null=True, blank=True)
arquivo_operadora = models.FileField( arquivo_operadora = models.FileField(
"Arquivo da operadora", "Arquivo da operadora",
upload_to="planos_saude/operadora/", upload_to="planos_saude/operadora/",
@ -591,8 +599,14 @@ class ImportacaoPlanoSaudeLinha(models.Model):
"""Uma linha da planilha padrão (leiaute do sistema) dentro de uma """Uma linha da planilha padrão (leiaute do sistema) dentro de uma
ImportacaoPlanoSaude, já com o valor do mês casado pelo pipeline — ImportacaoPlanoSaude, já com o valor do mês casado pelo pipeline —
espelha `portal_api.planos_saude.modelos.LinhaSistema` campo a campo. espelha `portal_api.planos_saude.modelos.LinhaSistema` campo a campo.
Todos os campos (não só os valores) são editáveis na tela de revisão Todos os campos aceitam PATCH no backend (sem trava aqui), mas a tela de
antes de gerar o CSV final (decisão explícita do usuário).""" revisão só expõe `valor_empresa`/`valor` como editáveis numa linha que já
veio do processamento — os demais campos (cadastro da pessoa) só ficam
editáveis numa linha incluída manualmente via "Adicionar linha" (decisão
revista do usuário; ver `linhasIncluidasManualmente()` em
`importacao-plano-saude.js`, que decide isso no cliente a partir de
`ImportacaoPlanoSaudeAlteracao` — não há trava correspondente aqui no
model/serializer, é só a UI que restringe)."""
importacao = models.ForeignKey(ImportacaoPlanoSaude, on_delete=models.CASCADE, related_name="linhas") importacao = models.ForeignKey(ImportacaoPlanoSaude, on_delete=models.CASCADE, related_name="linhas")
tipo_lancamento = models.CharField("Tipo de lançamento", max_length=20) tipo_lancamento = models.CharField("Tipo de lançamento", max_length=20)

View File

@ -11,7 +11,6 @@ OPERADORAS abaixo. Nada mais precisa mudar.
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Optional from typing import Dict, List, Optional
from portal_api.planos_saude.leiaute_sistema import le_planilha_padrao
from portal_api.planos_saude.matcher import casa_individuos_com_planilha from portal_api.planos_saude.matcher import casa_individuos_com_planilha
from portal_api.planos_saude.modelos import ItemAuditoria, LinhaSistema from portal_api.planos_saude.modelos import ItemAuditoria, LinhaSistema
from portal_api.planos_saude.regras_empresa import valida_regra_empresa from portal_api.planos_saude.regras_empresa import valida_regra_empresa
@ -29,38 +28,56 @@ CUSTEIOS_VALIDOS = ("empresa", "empregado", "especifica")
OPERADORAS = { OPERADORAS = {
"amil_odonto_mensalidade": { "amil_odonto_mensalidade": {
"label": "3758 - Amil Odonto", "codigo_operadora": "3758",
"nome": "Amil Odonto",
"parser": AmilOdontoMensalidade, "parser": AmilOdontoMensalidade,
}, },
"unimed_saude": { "unimed_saude": {
"label": "5060 - Unimed Saúde", "codigo_operadora": "5060",
"nome": "Unimed Saúde",
"parser": UnimedSaude, "parser": UnimedSaude,
}, },
"itamed_saude": { "itamed_saude": {
"label": "3755 - Itamed Saúde", "codigo_operadora": "3755",
"nome": "Itamed Saúde",
"parser": ItamedSaude, "parser": ItamedSaude,
}, },
"dental_uni_odonto_mensalidade": { "dental_uni_odonto_mensalidade": {
"label": "1723 - Dental Uni Odonto", "codigo_operadora": "1723",
"nome": "Dental Uni Odonto",
"parser": DentalUniOdontoMensalidade, "parser": DentalUniOdontoMensalidade,
}, },
"unimed_oeste_pr_saude": { "unimed_oeste_pr_saude": {
"label": "4709 - Unimed Oeste do Paraná", "codigo_operadora": "4709",
"nome": "Unimed Oeste do Paraná",
"parser": UnimedOestePrSaude, "parser": UnimedOestePrSaude,
}, },
"bradesco_saude": { "bradesco_saude": {
"label": "1386 - Bradesco Saúde", "codigo_operadora": "1386",
"nome": "Bradesco Saúde",
"parser": BradescoSaude, "parser": BradescoSaude,
}, },
"bradesco_dental_odonto_mensalidade": { "bradesco_dental_odonto_mensalidade": {
"label": "3759 - Bradessaude Odonto", "codigo_operadora": "3759",
"nome": "Bradessaude Odonto",
"parser": BradescoDentalOdontoMensalidade, "parser": BradescoDentalOdontoMensalidade,
}, },
} }
def label_operadora(operadora_key: str) -> str:
""""<código de cadastro da operadora no Questor> - <Nome>" (ex.:
"5060 - Unimed Saúde") — esse código é da OPERADORA, não do
`codigo_empresa` do cliente que contratou o plano. Fonte única pra
montar o label a partir dos campos separados de OPERADORAS, usada
tanto por `lista_operadoras()` quanto por quem precisava do `label`
bruto antes desta função existir."""
dados = OPERADORAS[operadora_key]
return f"{dados['codigo_operadora']} - {dados['nome']}"
def lista_operadoras() -> List[Dict[str, str]]: def lista_operadoras() -> List[Dict[str, str]]:
return [{"key": chave, "label": dados["label"]} for chave, dados in OPERADORAS.items()] return [{"key": chave, "label": label_operadora(chave)} for chave in OPERADORAS]
@dataclass @dataclass
@ -73,7 +90,7 @@ class ResultadoProcessamento:
def processa_importacao( def processa_importacao(
operadora_key: str, operadora_key: str,
caminho_arquivo_operadora: str, caminho_arquivo_operadora: str,
caminho_planilha_padrao: str, linhas_sistema_template: List[LinhaSistema],
tipos_selecionados: List[str], tipos_selecionados: List[str],
custeio_por_tipo: Dict[str, dict], custeio_por_tipo: Dict[str, dict],
regra_empresa_key: Optional[str] = None, regra_empresa_key: Optional[str] = None,
@ -90,6 +107,11 @@ def processa_importacao(
float|None}, "dependente": {...}}`). Réplica do loop de main.py, com o float|None}, "dependente": {...}}`). Réplica do loop de main.py, com o
acréscimo do filtro por tipos_selecionados e do custeio configurável. acréscimo do filtro por tipos_selecionados e do custeio configurável.
`linhas_sistema_template` já vem pronta (lida do arquivo via
`le_planilha_padrao` ou buscada no Questor via
`questor_planilha.busca_linhas_questor` — decisão de qual das duas
fontes usar é de quem chama, em views.py).
`regra_empresa_key`, quando informado, substitui o custeio configurável `regra_empresa_key`, quando informado, substitui o custeio configurável
de "mensalidade" por uma regra especial cadastrada em de "mensalidade" por uma regra especial cadastrada em
portal_api.planos_saude.regras_empresa (ex.: teto de custeio por portal_api.planos_saude.regras_empresa (ex.: teto de custeio por
@ -102,7 +124,6 @@ def processa_importacao(
parser_operadora = operadora_info["parser"]() parser_operadora = operadora_info["parser"]()
individuos, auditoria_extracao = parser_operadora.extrai(caminho_arquivo_operadora) individuos, auditoria_extracao = parser_operadora.extrai(caminho_arquivo_operadora)
linhas_sistema_template = le_planilha_padrao(caminho_planilha_padrao)
regra_empresa_fn = None regra_empresa_fn = None
if regra_empresa_key: if regra_empresa_key:

View File

@ -0,0 +1,77 @@
"""
Busca a "planilha padrão" (mesma informação que o Questor hoje exporta
manualmente pra Importação de Plano de Saúde) direto do banco do Questor,
via SQL — alternativa ao upload manual do CSV. Ver
`sqls.questor.QuestorSQL.consulta_planilha_plano_saude` pra consulta em si
(fornecida e validada pelo usuário) e `ImportacaoPlanoSaudeViewSet.create`
em views.py pra onde isso é consumido.
Só leitura (`select_mappings_query`, nunca `execute`/`execute_returning` —
ver [[feedback_bancos_externos_somente_leitura]] na memória do projeto).
"""
from datetime import date
from typing import List
from database.connection import DatabaseConnection
from sqls.questor import QuestorSQL
from portal_api.planos_saude.leiaute_sistema import CABECALHO
from portal_api.planos_saude.modelos import LinhaSistema
def busca_linhas_questor(codigo_empresa: str, codigo_operadora: str, competencia: date) -> List[LinhaSistema]:
"""Consulta o Questor e devolve a lista de `LinhaSistema` pra essa
empresa+operadora+competência — mesmo formato que `le_planilha_padrao`
devolveria a partir do CSV exportado manualmente. `valor_empresa`/`valor`
sempre voltam como o placeholder `"0"` (a própria consulta já devolve
`0::numeric` pros dois — são sempre sobrescritos depois pelo casamento
com o arquivo da operadora, nunca usados como estão). Propaga qualquer
exceção de conexão/consulta pro chamador — diferente de
`empresas_questor.resolve_nome_empresa`, aqui o dado é essencial ao
fluxo, não só informativo pra exibição."""
query = QuestorSQL.consulta_planilha_plano_saude(codigo_empresa, codigo_operadora, competencia)
with DatabaseConnection("questor") as db:
linhas_query = db.select_mappings_query(query)
return [
LinhaSistema(
codigo_empresa=str(linha["codigoempresa"]),
nome_func=(linha["nomefunc"] or "").strip(),
cpf_func=(linha["cpffunc"] or "").strip(),
codigo_out_emp=str(linha["codigooperadora"]),
# DD/MM/AAAA — mesmo formato que a planilha padrão exportada
# manualmente do Questor já usa nessa coluna (leiaute de
# importação real, não o formato ISO que o Postgres devolve por
# padrão pra uma coluna `date`).
data_inicial=linha["datainicial"].strftime("%d/%m/%Y") if linha["datainicial"] else "",
nome_dependente=(linha["nomedepend"] or "").strip(),
cpf_dependente=(linha["cpfdepend"] or "").strip(),
valor_empresa="0",
valor="0",
descricao="",
)
for linha in linhas_query
]
def linhas_para_csv_bytes(linhas: List[LinhaSistema]) -> bytes:
"""Serializa `linhas` no mesmo formato/cabeçalho de
`leiaute_sistema.CABECALHO` (CSV `;`-delimitado, UTF-8) — usado só pra
persistir um snapshot fiel no campo `ImportacaoPlanoSaude.planilha_padrao`
quando a origem foi o Questor (busca via SQL), preservando o histórico
completo do jeito que já existe pro caminho de upload manual."""
linhas_texto = [";".join(CABECALHO)]
for linha in linhas:
linhas_texto.append(";".join([
linha.codigo_empresa,
linha.nome_func,
linha.cpf_func,
linha.codigo_out_emp,
linha.data_inicial,
linha.nome_dependente,
linha.cpf_dependente,
linha.valor_empresa,
linha.valor,
linha.descricao,
]))
return ("\r\n".join(linhas_texto) + "\r\n").encode("utf-8-sig")

View File

@ -507,7 +507,15 @@ class ImportacaoPlanoSaudeCreateSerializer(serializers.Serializer):
`ImportacaoPlanoSaudeViewSet.create` em views.py.""" `ImportacaoPlanoSaudeViewSet.create` em views.py."""
operadora = serializers.ChoiceField(choices=list(OPERADORAS.keys())) operadora = serializers.ChoiceField(choices=list(OPERADORAS.keys()))
planilha_padrao = serializers.FileField() # Exatamente uma das duas origens da planilha padrão precisa vir
# preenchida — ver validate(): upload manual (comportamento original)
# ou busca automática no Questor por competência (mês/ano digitado na
# tela, formato "AAAA-MM").
planilha_padrao = serializers.FileField(required=False)
# Mesmo formato de entrada de IndicadorApuracaoCreateSerializer — o
# frontend converte a máscara "MM/AAAA" pro ISO "AAAA-MM-01" antes de
# enviar (ver pidIpsCompetenciaParaIso em importacao-plano-saude.js).
competencia = serializers.DateField(required=False, allow_null=True)
arquivo_operadora = serializers.FileField() arquivo_operadora = serializers.FileField()
tipos_lancamento = serializers.CharField() tipos_lancamento = serializers.CharField()
# Chave de REGRAS_EMPRESA (portal_api.planos_saude.regras_empresa) — ver # Chave de REGRAS_EMPRESA (portal_api.planos_saude.regras_empresa) — ver
@ -582,6 +590,29 @@ class ImportacaoPlanoSaudeCreateSerializer(serializers.Serializer):
attrs["tipos_lancamento_lista"] = tipos attrs["tipos_lancamento_lista"] = tipos
attrs["custeio_por_tipo"] = custeio_por_tipo attrs["custeio_por_tipo"] = custeio_por_tipo
attrs["regra_empresa"] = regra_empresa_key attrs["regra_empresa"] = regra_empresa_key
# Exatamente uma origem pra planilha padrão: upload manual (arquivo)
# ou busca automática no Questor (competência). Ver
# ImportacaoPlanoSaudeViewSet.create, em views.py, pra onde cada uma
# das duas é de fato resolvida em `linhas_sistema_template`.
planilha_padrao = attrs.get("planilha_padrao")
competencia = attrs.get("competencia")
if planilha_padrao and competencia:
raise serializers.ValidationError(
{"competencia": "Anexe a planilha manualmente ou informe a competência para buscar no Questor — não os dois."}
)
if not planilha_padrao and not competencia:
raise serializers.ValidationError(
{"planilha_padrao": "Anexe a planilha padrão ou informe a competência para buscar no Questor."}
)
if competencia:
regra_custeio_salva = attrs.get("regra_custeio_salva")
if not regra_custeio_salva or not regra_custeio_salva.codigo_empresa:
raise serializers.ValidationError(
{"competencia": "Buscar a planilha no Questor exige selecionar a empresa (regra de custeio cadastrada)."}
)
return attrs return attrs
@ -747,6 +778,7 @@ class ImportacaoPlanoSaudeListSerializer(serializers.ModelSerializer):
"id", "id",
"nome_operadora", "nome_operadora",
"codigo_empresa", "codigo_empresa",
"competencia",
"tipos_lancamento", "tipos_lancamento",
"custeio_por_tipo", "custeio_por_tipo",
"status", "status",
@ -782,6 +814,7 @@ class ImportacaoPlanoSaudeDetailSerializer(serializers.ModelSerializer):
"nome_operadora", "nome_operadora",
"codigo_empresa", "codigo_empresa",
"nome_empresa", "nome_empresa",
"competencia",
"tipos_lancamento", "tipos_lancamento",
"custeio_por_tipo", "custeio_por_tipo",
"regra_empresa", "regra_empresa",
@ -846,14 +879,11 @@ class ImportacaoPlanoSaudeDetailSerializer(serializers.ModelSerializer):
def _nome_operadora_sem_codigo(operadora_key: str) -> str: def _nome_operadora_sem_codigo(operadora_key: str) -> str:
"""`pipeline.OPERADORAS[chave]["label"]` vem no formato "<código de """Nome da operadora (ex.: "Unimed Saúde"), sem o código de cadastro
cadastro da operadora no Questor> - <Nome>" (ex.: "5060 - Unimed dela no Questor — usado só pra compor o `nome` derivado de
Saúde") — esse código é da OPERADORA, não do `codigo_empresa` do
cliente que contratou o plano; usado só pra compor o `nome` derivado de
RegraCusteioPlanoSaude sem repetir esse código, que não tem relação com RegraCusteioPlanoSaude sem repetir esse código, que não tem relação com
a empresa.""" o `codigo_empresa` do cliente que contratou o plano."""
label = OPERADORAS[operadora_key]["label"] return OPERADORAS[operadora_key]["nome"]
return label.split(" - ", 1)[1] if " - " in label else label
class RegraCusteioPlanoSaudeSerializer(serializers.ModelSerializer): class RegraCusteioPlanoSaudeSerializer(serializers.ModelSerializer):

View File

@ -7,6 +7,7 @@ import zipfile
from typing import Any, Iterable from typing import Any, Iterable
import holidays import holidays
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import UploadedFile from django.core.files.uploadedfile import UploadedFile
from django.db import transaction from django.db import transaction
from django.db.models import Max, Q, QuerySet from django.db.models import Max, Q, QuerySet
@ -70,6 +71,7 @@ from .planos_saude import pipeline as planos_saude_pipeline
from .planos_saude import regras_empresa as planos_saude_regras_empresa from .planos_saude import regras_empresa as planos_saude_regras_empresa
from .planos_saude.leiaute_sistema import CABECALHO as PLANO_SAUDE_CABECALHO from .planos_saude.leiaute_sistema import CABECALHO as PLANO_SAUDE_CABECALHO
from .planos_saude.leiaute_sistema import formata_valor_br, le_planilha_padrao, parse_valor_br from .planos_saude.leiaute_sistema import formata_valor_br, le_planilha_padrao, parse_valor_br
from .planos_saude.questor_planilha import busca_linhas_questor, linhas_para_csv_bytes
from .planos_saude.regras_empresa import RegraEmpresaIncompativelError from .planos_saude.regras_empresa import RegraEmpresaIncompativelError
from .serializers import ( from .serializers import (
AcessoGeralSecaoSerializer, AcessoGeralSecaoSerializer,
@ -698,7 +700,7 @@ def _valida_arquivo_operadora(arquivo: UploadedFile, operadora_key: str) -> dict
"valido": False, "valido": False,
"mensagem": ( "mensagem": (
f"Não foi possível reconhecer este arquivo como um relatório da operadora " f"Não foi possível reconhecer este arquivo como um relatório da operadora "
f"{operadora_info['label']} — confira se é o arquivo certo e se o formato " f"{planos_saude_pipeline.label_operadora(operadora_key)} — confira se é o arquivo certo e se o formato "
"(PDF ou CSV, conforme esperado por essa operadora) está correto." "(PDF ou CSV, conforme esperado por essa operadora) está correto."
), ),
} }
@ -746,6 +748,20 @@ def _snapshot_linha_plano_saude(linha: ImportacaoPlanoSaudeLinha) -> dict[str, A
return dados return dados
def _garante_importacao_em_revisao(importacao: ImportacaoPlanoSaude) -> None:
"""Bloqueia qualquer edição de linha/auditoria/alteração de uma
ImportacaoPlanoSaude já `concluida` — `ImportacaoPlanoSaudeViewSet.reabrir`
é o único jeito de voltar a editar (volta o status pra `revisao`).
Chamada em todo ponto que grava algo dentro de uma importação já criada
(linha/auditoria/alteração), exceto `gerar()` em si, que só lê o que já
está salvo e pode ser chamado de novo pra rebaixar o mesmo arquivo
mesmo depois de concluída."""
if importacao.status == ImportacaoPlanoSaude.STATUS_CONCLUIDA:
raise ValidationError(
{"detail": 'Esta importação já foi concluída. Clique em "Editar" para reabri-la antes de alterar.'}
)
class ImportacaoPlanoSaudeViewSet(viewsets.ModelViewSet): class ImportacaoPlanoSaudeViewSet(viewsets.ModelViewSet):
"""Ferramenta "Importação de Plano de Saúde" (Utilitários) — permissão de """Ferramenta "Importação de Plano de Saúde" (Utilitários) — permissão de
toggle único (sem par visualizar/editar, ver catalogo.py "utilitarios"), então toggle único (sem par visualizar/editar, ver catalogo.py "utilitarios"), então
@ -823,24 +839,66 @@ class ImportacaoPlanoSaudeViewSet(viewsets.ModelViewSet):
custeio_por_tipo = dados["custeio_por_tipo"] custeio_por_tipo = dados["custeio_por_tipo"]
operadora_key = dados["operadora"] operadora_key = dados["operadora"]
operadora_info = planos_saude_pipeline.OPERADORAS[operadora_key] operadora_info = planos_saude_pipeline.OPERADORAS[operadora_key]
regra_custeio_obj = dados.get("regra_custeio_salva")
competencia = dados.get("competencia")
planilha_padrao_arquivo = dados.get("planilha_padrao")
linhas_sistema_template = None
planilha_padrao_gerada = None
if not planilha_padrao_arquivo:
# Sem upload: busca a planilha padrão no Questor, ANTES de criar
# a importação — uma falha de conexão/consulta aqui nunca deixa
# nada órfão pra limpar (diferente do caminho de upload, que só
# sabe se o arquivo é válido depois de já ter salvo o registro).
codigo_empresa_regra = normalizar_codigo_empresa(regra_custeio_obj.codigo_empresa)
codigo_operadora = operadora_info["codigo_operadora"]
try:
linhas_sistema_template = busca_linhas_questor(codigo_empresa_regra, codigo_operadora, competencia)
except Exception:
return Response(
{
"detail": (
"Não foi possível buscar a planilha padrão no Questor agora. "
"Tente novamente em alguns instantes ou anexe a planilha manualmente."
)
},
status=status.HTTP_400_BAD_REQUEST,
)
if not linhas_sistema_template:
return Response(
{
"detail": (
f"Nenhum beneficiário encontrado no Questor para a empresa {regra_custeio_obj.codigo_empresa} "
f"com a operadora {operadora_info['nome']} na competência informada."
)
},
status=status.HTTP_400_BAD_REQUEST,
)
planilha_padrao_gerada = ContentFile(
linhas_para_csv_bytes(linhas_sistema_template),
name=f"questor_{codigo_empresa_regra}_{competencia:%Y-%m}.csv",
)
importacao = ImportacaoPlanoSaude.objects.create( importacao = ImportacaoPlanoSaude.objects.create(
operadora=operadora_key, operadora=operadora_key,
nome_operadora=operadora_info["label"], nome_operadora=planos_saude_pipeline.label_operadora(operadora_key),
tipos_lancamento=tipos, tipos_lancamento=tipos,
custeio_por_tipo=custeio_por_tipo, custeio_por_tipo=custeio_por_tipo,
regra_empresa=dados["regra_empresa"], regra_empresa=dados["regra_empresa"],
regra_custeio_salva=dados.get("regra_custeio_salva"), regra_custeio_salva=regra_custeio_obj,
criado_por=request.user, criado_por=request.user,
planilha_padrao=dados["planilha_padrao"], competencia=competencia,
planilha_padrao=planilha_padrao_arquivo or planilha_padrao_gerada,
arquivo_operadora=dados["arquivo_operadora"], arquivo_operadora=dados["arquivo_operadora"],
) )
try: try:
if planilha_padrao_arquivo:
linhas_sistema_template = le_planilha_padrao(importacao.planilha_padrao.path)
resultado = planos_saude_pipeline.processa_importacao( resultado = planos_saude_pipeline.processa_importacao(
operadora_key=operadora_key, operadora_key=operadora_key,
caminho_arquivo_operadora=importacao.arquivo_operadora.path, caminho_arquivo_operadora=importacao.arquivo_operadora.path,
caminho_planilha_padrao=importacao.planilha_padrao.path, linhas_sistema_template=linhas_sistema_template,
tipos_selecionados=tipos, tipos_selecionados=tipos,
custeio_por_tipo=custeio_por_tipo, custeio_por_tipo=custeio_por_tipo,
regra_empresa_key=dados["regra_empresa"] or None, regra_empresa_key=dados["regra_empresa"] or None,
@ -878,9 +936,10 @@ class ImportacaoPlanoSaudeViewSet(viewsets.ModelViewSet):
# `regra_empresa_chave` (onde é redundante com a checagem que # `regra_empresa_chave` (onde é redundante com a checagem que
# `regras_empresa.valida_regra_empresa()` já faz dentro do try acima) # `regras_empresa.valida_regra_empresa()` já faz dentro do try acima)
# — protege contra o registro em REGRAS_EMPRESA ficar dessincronizado # — protege contra o registro em REGRAS_EMPRESA ficar dessincronizado
# da RegraCusteioPlanoSaude correspondente. # da RegraCusteioPlanoSaude correspondente. Quando a planilha veio do
regra_custeio_obj = dados.get("regra_custeio_salva") # Questor (não upload), essa conferência é redundante — a consulta já
if regra_custeio_obj and regra_custeio_obj.codigo_empresa: # filtrou pelo próprio `codigo_empresa` da regra — então é pulada.
if planilha_padrao_arquivo and regra_custeio_obj and regra_custeio_obj.codigo_empresa:
todas_linhas = [linha for linhas in resultado.linhas_por_tipo.values() for linha in linhas] todas_linhas = [linha for linhas in resultado.linhas_por_tipo.values() for linha in linhas]
# A planilha padrão traz o código cru do Questor (pode ter zero à # A planilha padrão traz o código cru do Questor (pode ter zero à
# esquerda, ex. "092"), enquanto RegraCusteioPlanoSaude.codigo_empresa # esquerda, ex. "092"), enquanto RegraCusteioPlanoSaude.codigo_empresa
@ -974,12 +1033,27 @@ class ImportacaoPlanoSaudeViewSet(viewsets.ModelViewSet):
response["Content-Disposition"] = f'attachment; filename="importacao_plano_saude_{importacao.id}.zip"' response["Content-Disposition"] = f'attachment; filename="importacao_plano_saude_{importacao.id}.zip"'
return response return response
@action(detail=True, methods=["post"])
def reabrir(self, request: Request, pk: str | None = None) -> Response:
"""Contrapartida de gerar(): volta uma importação `concluida` pra
`revisao`, liberando de novo a edição de linhas/auditoria/alterações
(ver `_garante_importacao_em_revisao`) — botão "Editar" na tela de
Revisão, visível só quando `status == "concluida"`."""
importacao = self.get_object()
importacao.status = ImportacaoPlanoSaude.STATUS_REVISAO
importacao.concluida_em = None
importacao.save(update_fields=["status", "concluida_em"])
serializer = ImportacaoPlanoSaudeDetailSerializer(importacao)
return Response(serializer.data)
class ImportacaoPlanoSaudeLinhaViewSet(viewsets.ModelViewSet): class ImportacaoPlanoSaudeLinhaViewSet(viewsets.ModelViewSet):
"""Edição das linhas de uma importação já criada — todos os campos (não só os """Edição das linhas de uma importação já criada — todos os campos (não só os
valores) são editáveis na tela de revisão, decisão explícita do usuário. Mesma valores) são editáveis na tela de revisão (com a restrição de UI + backend
permissão de toggle único de ImportacaoPlanoSaudeViewSet; qualquer usuário de `_garante_importacao_em_revisao`: nada disso é permitido depois que a
autorizado pode editar linha de qualquer importação, sem conceito de "dono". importação foi concluída, até reabri-la). Mesma permissão de toggle único
de ImportacaoPlanoSaudeViewSet; qualquer usuário autorizado pode editar
linha de qualquer importação, sem conceito de "dono".
`create()` permite incluir manualmente uma linha nova (botão "Adicionar linha" `create()` permite incluir manualmente uma linha nova (botão "Adicionar linha"
na revisão) — nasce em branco/"0" e entra automaticamente no CSV gerado, já na revisão) — nasce em branco/"0" e entra automaticamente no CSV gerado, já
que `gerar()` (na outra view) lê todas as linhas da importação sem distinguir que `gerar()` (na outra view) lê todas as linhas da importação sem distinguir
@ -1004,6 +1078,7 @@ class ImportacaoPlanoSaudeLinhaViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer: ImportacaoPlanoSaudeLinhaCreateSerializer) -> None: def perform_create(self, serializer: ImportacaoPlanoSaudeLinhaCreateSerializer) -> None:
importacao = serializer.validated_data["importacao"] importacao = serializer.validated_data["importacao"]
_garante_importacao_em_revisao(importacao)
tipo = serializer.validated_data["tipo_lancamento"] tipo = serializer.validated_data["tipo_lancamento"]
maior_ordem = ImportacaoPlanoSaudeLinha.objects.filter( maior_ordem = ImportacaoPlanoSaudeLinha.objects.filter(
importacao=importacao, tipo_lancamento=tipo importacao=importacao, tipo_lancamento=tipo
@ -1022,6 +1097,7 @@ class ImportacaoPlanoSaudeLinhaViewSet(viewsets.ModelViewSet):
# serializer.instance ainda reflete os valores ANTES do save() abaixo — # serializer.instance ainda reflete os valores ANTES do save() abaixo —
# é o que permite comparar campo a campo o que de fato mudou. # é o que permite comparar campo a campo o que de fato mudou.
linha_anterior = serializer.instance linha_anterior = serializer.instance
_garante_importacao_em_revisao(linha_anterior.importacao)
valores_anteriores = { valores_anteriores = {
campo: getattr(linha_anterior, campo) campo: getattr(linha_anterior, campo)
for campo in PLANO_SAUDE_CAMPOS_ALTERACAO for campo in PLANO_SAUDE_CAMPOS_ALTERACAO
@ -1042,6 +1118,7 @@ class ImportacaoPlanoSaudeLinhaViewSet(viewsets.ModelViewSet):
) )
def perform_destroy(self, instance: ImportacaoPlanoSaudeLinha) -> None: def perform_destroy(self, instance: ImportacaoPlanoSaudeLinha) -> None:
_garante_importacao_em_revisao(instance.importacao)
ImportacaoPlanoSaudeAlteracao.objects.create( ImportacaoPlanoSaudeAlteracao.objects.create(
importacao=instance.importacao, importacao=instance.importacao,
tipo=ImportacaoPlanoSaudeAlteracao.TIPO_EXCLUSAO, tipo=ImportacaoPlanoSaudeAlteracao.TIPO_EXCLUSAO,
@ -1076,6 +1153,7 @@ class ImportacaoPlanoSaudeAlteracaoViewSet(viewsets.GenericViewSet):
própria reversão não gera um novo registro de alteração (evita um própria reversão não gera um novo registro de alteração (evita um
loop de "reverter a reversão").""" loop de "reverter a reversão")."""
alteracao = self.get_object() alteracao = self.get_object()
_garante_importacao_em_revisao(alteracao.importacao)
if alteracao.revertida: if alteracao.revertida:
raise ValidationError({"detail": "Esta alteração já foi revertida."}) raise ValidationError({"detail": "Esta alteração já foi revertida."})
@ -1154,6 +1232,7 @@ class ImportacaoPlanoSaudeAuditoriaViewSet(viewsets.GenericViewSet):
sobrescrever sem querer um lançamento que já casou automaticamente sobrescrever sem querer um lançamento que já casou automaticamente
com outra pessoa do arquivo da operadora.""" com outra pessoa do arquivo da operadora."""
item = self.get_object() item = self.get_object()
_garante_importacao_em_revisao(item.importacao)
if item.motivo not in ImportacaoPlanoSaudeAuditoria.MOTIVOS_RESOLVIVEIS: if item.motivo not in ImportacaoPlanoSaudeAuditoria.MOTIVOS_RESOLVIVEIS:
raise ValidationError({"detail": "Esse item de auditoria não pode ser resolvido manualmente."}) raise ValidationError({"detail": "Esse item de auditoria não pode ser resolvido manualmente."})

View File

@ -1,3 +1,4 @@
from datetime import date
from textwrap import dedent from textwrap import dedent
from database.query import SQLQuery from database.query import SQLQuery
@ -20,3 +21,112 @@ class QuestorSQL:
e.codigoempresa = :codigo_empresa e.codigoempresa = :codigo_empresa
""") """)
return SQLQuery(sql=sql, params={"codigo_empresa": codigo_empresa}) return SQLQuery(sql=sql, params={"codigo_empresa": codigo_empresa})
@staticmethod
def consulta_planilha_plano_saude(
codigo_empresa: str, codigo_operadora: str, competencia: date
) -> SQLQuery:
"""Mesma informação da "planilha padrão" que o Questor hoje exporta
manualmente pra Importação de Plano de Saúde (ver
`portal_api.planos_saude.questor_planilha.busca_linhas_questor`),
pra uma única empresa+operadora+competência — consulta fornecida e
validada pelo usuário. `competencia` é sempre o primeiro dia do mês
(ex.: `date(2026, 8, 1)`); `codigo_operadora` filtra pra só o plano
da operadora selecionada (é `codigooutemp`, código de cadastro da
OPERADORA no Questor — não confundir com `codigo_empresa`, ver
`pipeline.OPERADORAS`)."""
sql = dedent(r"""
with periodo_plano as (
select
codigoempresa,
codigofunccontr,
codigooutemp,
max(datainicial) datainicial
from
funcplanosaude
where
codigoempresa = :codigo_empresa
and codigooutemp = :codigo_operadora
and datainicial <= :competencia
and (datafinal is null or datafinal >= :competencia)
group by
codigoempresa,
codigofunccontr,
codigooutemp
),
dados_plano_func as (
select
codigoempresa,
codigofunccontr,
codigooutemp,
datainicial,
datafinal,
null::int codigodepend
from
funcplanosaude
union
select
codigoempresa,
codigofunccontr,
codigooutemp,
datainicial,
null::date datafinal,
codigodepend
from
funcplanosaudedepend
)
select
d.codigoempresa,
fp.nomefunc,
fp.cpffunc,
d.codigooutemp codigooperadora,
oe.nomeoutemp nomeoperadora,
d.datainicial,
dep.nomedepend,
dep.cpfdepend,
0::numeric(18,2) valorempresa,
0::numeric(18,2) valor,
null::text descricao
from
dados_plano_func d
join
periodo_plano p on
d.codigoempresa = p.codigoempresa
and d.codigofunccontr = p.codigofunccontr
and d.codigooutemp = p.codigooutemp
and d.datainicial = p.datainicial
join
empresa e on
d.codigoempresa = e.codigoempresa
join
funccontrato fc on
d.codigoempresa = fc.codigoempresa
and d.codigofunccontr = fc.codigofunccontr
join
funcpessoa fp on
fc.codigofuncpessoa = fp.codigofuncpessoa
join
outraempresa oe on
d.codigooutemp = oe.codigooutemp
left join
dependente dep on
d.codigoempresa = dep.codigoempresa
and d.codigofunccontr = dep.codigofunccontr
and d.codigodepend = dep.codigodepend
where
d.codigoempresa = :codigo_empresa
and d.codigooutemp = :codigo_operadora
and (fc.datadem is null or fc.datadem >= :competencia)
order by
fp.nomefunc,
fp.cpffunc,
codigooperadora
""")
return SQLQuery(
sql=sql,
params={
"codigo_empresa": codigo_empresa,
"codigo_operadora": codigo_operadora,
"competencia": competencia,
},
)

View File

@ -61,6 +61,85 @@
color: var(--accent); color: var(--accent);
} }
/* Filtro "estilo Excel" por coluna do histórico (ips-list-table) — botão de
funil dentro do próprio <th> de ordenação (não conflita com o clique de
ordenar: o JS ignora cliques dentro de .ips-col-filter antes de tratar
como ordenação) + popup com busca/checklist (reaproveita .checklist-*
de components.css, mesmo componente já usado nos checklists de Perfis de
Acesso/Departamento). .pa-table-wrap normalmente usa overflow:hidden pra
arredondar os cantos da tabela — sem abrir mão disso pra tabela nenhuma,
ips-list-table-wrap (só esta tabela) sobrescreve pra visible, senão o
popup (que precisa "vazar" pra fora do cabeçalho, sobre as linhas) seria
cortado ali. */
.ips-list-table-wrap {
overflow: visible;
}
.ips-col-filter {
position: relative;
display: inline-block;
margin-left: var(--space-1);
}
.ips-col-filter__btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
padding: 0;
border: none;
background: none;
color: var(--text-muted);
cursor: pointer;
border-radius: var(--radius-sm);
vertical-align: middle;
}
.ips-col-filter__btn:hover {
color: var(--text-primary);
background: var(--bg-canvas);
}
.ips-col-filter__btn.is-active {
color: var(--accent);
}
.ips-col-filter__popup {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 20;
width: 220px;
padding: var(--space-3);
background: var(--bg-surface-raised);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
text-align: left;
text-transform: none;
font-weight: 400;
letter-spacing: normal;
color: var(--text-primary);
}
.ips-col-filter__popup .checklist-box {
max-height: 220px;
}
.ips-col-filter__actions {
display: flex;
justify-content: space-between;
gap: var(--space-2);
margin-top: var(--space-2);
}
.ips-col-filter__actions button {
flex: 1;
padding: var(--space-1) var(--space-2);
font-size: 0.8rem;
}
/* Paginação do histórico — só Anterior/Próxima + "Página X de Y", sem /* Paginação do histórico — só Anterior/Próxima + "Página X de Y", sem
numeração de páginas (lista tende a ser curta; um seletor de tamanho de numeração de páginas (lista tende a ser curta; um seletor de tamanho de
página, como em ramais-lookup.js, seria demais pra esse caso). */ página, como em ramais-lookup.js, seria demais pra esse caso). */
@ -144,6 +223,16 @@
margin-bottom: var(--space-3); margin-bottom: var(--space-3);
} }
/* Toggle "Buscar automaticamente do Questor" / "Anexar manualmente" — só
dois radios lado a lado, mesmo espírito de .ips-custeio-radios (radio +
.modal-checkbox), sem o recuo/coluna deles porque aqui não há
titular/dependente por baixo, só a escolha em si. */
.ips-planilha-origem-opcoes {
display: flex;
gap: var(--space-5);
margin-bottom: var(--space-3);
}
.ips-file-field { .ips-file-field {
position: relative; position: relative;
display: flex; display: flex;
@ -518,6 +607,21 @@
background: var(--bg-canvas); background: var(--bg-canvas);
} }
/* Célula não editável (todo campo fora de Valor Empresa/Valor, numa linha
que não foi incluída manualmente via "Adicionar linha") — mesmo padding
de .ips-table-input, pra não desalinhar a coluna, mas sem borda/hover de
input, já que não é editável. */
.ips-table-value {
display: block;
width: 100%;
min-width: 30px;
padding: var(--space-2);
color: var(--text-secondary);
font-size: 0.85rem;
overflow: hidden;
text-overflow: ellipsis;
}
.ips-table-scroll { .ips-table-scroll {
width: 100%; width: 100%;
height: 420px; height: 420px;

View File

@ -87,6 +87,10 @@ async function pidCriarImportacaoPlanoSaude(formData) {
return pidApiRequest("/importacoes-plano-saude/", { method: "POST", body: formData }); return pidApiRequest("/importacoes-plano-saude/", { method: "POST", body: formData });
} }
async function pidReabrirImportacaoPlanoSaude(id) {
return pidApiRequest(`/importacoes-plano-saude/${id}/reabrir/`, { method: "POST" });
}
async function pidValidarArquivoPlanoSaude(tipo, arquivo, operadoraKey) { async function pidValidarArquivoPlanoSaude(tipo, arquivo, operadoraKey) {
const formData = new FormData(); const formData = new FormData();
formData.append("tipo", tipo); formData.append("tipo", tipo);
@ -214,6 +218,14 @@ document.addEventListener("DOMContentLoaded", async () => {
const formPlanilhaName = document.getElementById("ips-form-planilha-name"); const formPlanilhaName = document.getElementById("ips-form-planilha-name");
const formPlanilhaRemoveBtn = document.getElementById("ips-form-planilha-remove"); const formPlanilhaRemoveBtn = document.getElementById("ips-form-planilha-remove");
const formPlanilhaStatus = document.getElementById("ips-form-planilha-status"); const formPlanilhaStatus = document.getElementById("ips-form-planilha-status");
// Origem da planilha padrão: buscar do Questor (padrão) ou anexar
// manualmente (alternativa — Questor fora do ar, ou empresa ainda não
// migrada). Ver ImportacaoPlanoSaudeCreateSerializer.validate() no
// backend, que exige exatamente uma das duas.
const planilhaOrigemRadios = Array.from(document.querySelectorAll('input[name="ips-planilha-origem"]'));
const planilhaQuestorField = document.getElementById("ips-planilha-questor-field");
const planilhaUploadField = document.getElementById("ips-planilha-upload-field");
const formCompetencia = document.getElementById("ips-form-competencia");
const formArquivo = document.getElementById("ips-form-arquivo"); const formArquivo = document.getElementById("ips-form-arquivo");
const formArquivoName = document.getElementById("ips-form-arquivo-name"); const formArquivoName = document.getElementById("ips-form-arquivo-name");
const formArquivoRemoveBtn = document.getElementById("ips-form-arquivo-remove"); const formArquivoRemoveBtn = document.getElementById("ips-form-arquivo-remove");
@ -296,6 +308,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const reviewRegraEmpresaObsTexto = document.getElementById("ips-review-regra-empresa-obs-texto"); const reviewRegraEmpresaObsTexto = document.getElementById("ips-review-regra-empresa-obs-texto");
const reviewError = document.getElementById("ips-review-error"); const reviewError = document.getElementById("ips-review-error");
const reviewBackBtn = document.getElementById("ips-review-back-btn"); const reviewBackBtn = document.getElementById("ips-review-back-btn");
const reviewEditarBtn = document.getElementById("ips-review-editar-btn");
const gerarBtn = document.getElementById("ips-gerar-btn"); const gerarBtn = document.getElementById("ips-gerar-btn");
const tabsEl = document.getElementById("ips-tabs"); const tabsEl = document.getElementById("ips-tabs");
const panelsEl = document.getElementById("ips-tab-panels"); const panelsEl = document.getElementById("ips-tab-panels");
@ -314,6 +327,14 @@ document.addEventListener("DOMContentLoaded", async () => {
let listSortKey = "criado_em"; let listSortKey = "criado_em";
let listSortDir = -1; let listSortDir = -1;
let listPage = 1; let listPage = 1;
// Filtros da lista principal, estilo Excel — client-side, sobre o array
// já carregado. Cada chave é a mesma de PID_IPS_LISTA_SORT_VALOR (não por
// acaso: é o extrator de valor bruto reaproveitado tanto pra ordenar
// quanto pra filtrar essa coluna) e vale `null` (sem filtro, mostra tudo)
// ou um Set com os valores marcados no popup daquela coluna; combinam
// entre si (AND) — ex.: empresa "221" + operadora "3755 - Itamed Saúde"
// já restringe pras duas condições juntas.
let listFiltros = { codigo_empresa: null, nome_operadora: null, status: null, criado_por: null };
let selectedIds = new Set(); let selectedIds = new Set();
let currentPageIds = []; let currentPageIds = [];
let colWidthsLinha = {}; let colWidthsLinha = {};
@ -449,6 +470,57 @@ document.addEventListener("DOMContentLoaded", async () => {
getOperadoraKey: () => formOperadora.value, getOperadoraKey: () => formOperadora.value,
}); });
function planilhaOrigemAtual() {
const marcado = planilhaOrigemRadios.find((r) => r.checked);
return marcado ? marcado.value : "questor";
}
function atualizarPlanilhaOrigemView() {
const questor = planilhaOrigemAtual() === "questor";
planilhaQuestorField.hidden = !questor;
planilhaUploadField.hidden = questor;
// Trocar de modo limpa o outro — evita enviar um arquivo esquecido
// junto com uma competência (ou vice-versa), o que o backend recusaria
// mesmo (ImportacaoPlanoSaudeCreateSerializer exige exatamente uma
// origem).
if (questor) {
validadorPlanilha.limpar();
} else {
formCompetencia.value = "";
}
}
planilhaOrigemRadios.forEach((radio) => {
radio.addEventListener("change", atualizarPlanilhaOrigemView);
});
// Máscara MM/AAAA de digitação — mesmo padrão já usado em
// pidIndMascaraCompetencia/pidIndCompetenciaParaIso (indicador-desempenho.js),
// copiado aqui em vez de compartilhado (cada arquivo mantém sua própria
// cópia dessas funções pequenas, mesmo padrão de comparaValoresUsuario
// etc.). Usuário só digita "082026" (ou já com a barra) e o campo forma
// "08/2026" sozinho; nunca usar um `<input type="month">` aqui — o
// seletor nativo do browser foi considerado horrível pelo usuário.
function mascaraCompetencia(valor) {
const digitos = (valor || "").replace(/\D/g, "").slice(0, 6);
if (digitos.length <= 2) return digitos;
return `${digitos.slice(0, 2)}/${digitos.slice(2)}`;
}
function competenciaParaIso(valor) {
const match = /^(\d{2})\/(\d{4})$/.exec(valor || "");
if (!match) return null;
const mes = Number(match[1]);
if (mes < 1 || mes > 12) return null;
return `${match[2]}-${match[1]}-01`;
}
if (formCompetencia) {
formCompetencia.addEventListener("input", (event) => {
event.target.value = mascaraCompetencia(event.target.value);
});
}
function showView(view) { function showView(view) {
listView.hidden = view !== "list"; listView.hidden = view !== "list";
formView.hidden = view !== "form"; formView.hidden = view !== "form";
@ -483,6 +555,18 @@ document.addEventListener("DOMContentLoaded", async () => {
criado_em: (imp) => imp.criado_em || "", criado_em: (imp) => imp.criado_em || "",
}; };
// Colunas com filtro "estilo Excel" no cabeçalho do histórico — mesma
// chave de PID_IPS_LISTA_SORT_VALOR, reaproveitado como extrator do valor
// bruto de cada linha pro popup também (ver criarFiltroColuna). "status"
// ganha um rótulo amigável no popup (a lista mostra "Em revisão", não
// "revisao"), as outras exibem o próprio valor.
const PID_IPS_STATUS_LABELS = { revisao: "Em revisão", concluida: "Concluída" };
function labelValorFiltroLista(campo, valor) {
if (campo === "status") return PID_IPS_STATUS_LABELS[valor] || valor;
return valor || "(Vazio)";
}
function renderListPagination(totalPaginas) { function renderListPagination(totalPaginas) {
if (totalPaginas <= 1) { if (totalPaginas <= 1) {
listPagination.innerHTML = ""; listPagination.innerHTML = "";
@ -515,11 +599,23 @@ document.addEventListener("DOMContentLoaded", async () => {
if (listBulkCount) listBulkCount.textContent = `${count} selecionada${count === 1 ? "" : "s"}`; if (listBulkCount) listBulkCount.textContent = `${count} selecionada${count === 1 ? "" : "s"}`;
} }
function importacaoPassaNosFiltros(imp) {
return Object.entries(listFiltros).every(([campo, valoresPermitidos]) => {
if (!valoresPermitidos) return true; // null = sem filtro nessa coluna
return valoresPermitidos.has(PID_IPS_LISTA_SORT_VALOR[campo](imp));
});
}
function renderList() { function renderList() {
listEmpty.hidden = importacoes.length !== 0; const filtradas = importacoes.filter(importacaoPassaNosFiltros);
listEmpty.hidden = filtradas.length !== 0;
listEmpty.textContent =
importacoes.length === 0
? "Nenhuma importação realizada ainda."
: "Nenhuma importação encontrada com esse filtro.";
const extrator = PID_IPS_LISTA_SORT_VALOR[listSortKey]; const extrator = PID_IPS_LISTA_SORT_VALOR[listSortKey];
const ordenadas = [...importacoes].sort((a, b) => comparaValoresLista(extrator(a), extrator(b)) * listSortDir); const ordenadas = [...filtradas].sort((a, b) => comparaValoresLista(extrator(a), extrator(b)) * listSortDir);
const totalPaginas = Math.max(1, Math.ceil(ordenadas.length / IPS_LIST_PAGE_SIZE)); const totalPaginas = Math.max(1, Math.ceil(ordenadas.length / IPS_LIST_PAGE_SIZE));
listPage = Math.min(listPage, totalPaginas); listPage = Math.min(listPage, totalPaginas);
@ -1200,7 +1296,7 @@ document.addEventListener("DOMContentLoaded", async () => {
regracadNovoEmpresaNome.textContent = resultado.nome_empresa; regracadNovoEmpresaNome.textContent = resultado.nome_empresa;
} else { } else {
regracadNovoEmpresaNome.className = "ips-regracad-novo__empresa-nome ips-regracad-novo__empresa-nome--nao-encontrada"; regracadNovoEmpresaNome.className = "ips-regracad-novo__empresa-nome ips-regracad-novo__empresa-nome--nao-encontrada";
regracadNovoEmpresaNome.textContent = "Empresa não encontrada no Questor."; regracadNovoEmpresaNome.textContent = "Empresa não encontrada.";
} }
// O backend só busca/salva o código normalizado (sem zero à esquerda, // O backend só busca/salva o código normalizado (sem zero à esquerda,
// ex. "092" -> "92") — reflete isso no campo assim que a resposta // ex. "092" -> "92") — reflete isso no campo assim que a resposta
@ -1317,6 +1413,10 @@ document.addEventListener("DOMContentLoaded", async () => {
validadorPlanilha.limpar(); validadorPlanilha.limpar();
validadorArquivo.limpar(); validadorArquivo.limpar();
formError.textContent = ""; formError.textContent = "";
formCompetencia.value = "";
const radioQuestor = planilhaOrigemRadios.find((r) => r.value === "questor");
if (radioQuestor) radioQuestor.checked = true;
atualizarPlanilhaOrigemView();
regraResolvidaAtual = null; regraResolvidaAtual = null;
renderResumoRegra(null); renderResumoRegra(null);
impEmpresaOps.limpar(); impEmpresaOps.limpar();
@ -1354,8 +1454,13 @@ document.addEventListener("DOMContentLoaded", async () => {
} }
} }
const regraEmpresaSufixo = importacao.regra_empresa_label ? ` — Regra empresa: ${importacao.regra_empresa_label}` : ""; const regraEmpresaSufixo = importacao.regra_empresa_label ? ` — Regra empresa: ${importacao.regra_empresa_label}` : "";
// "competencia" só vem preenchida quando a planilha padrão foi buscada
// do Questor (não upload manual) — ver ImportacaoPlanoSaude.competencia.
const competenciaSufixo = importacao.competencia
? ` — Competência: ${importacao.competencia.slice(5, 7)}/${importacao.competencia.slice(0, 4)}`
: "";
reviewSubtitle.textContent = reviewSubtitle.textContent =
`${importacao.nome_operadora} — criada em ${new Date(importacao.criado_em).toLocaleString("pt-BR")}${regraEmpresaSufixo}`; `${importacao.nome_operadora} — criada em ${new Date(importacao.criado_em).toLocaleString("pt-BR")}${regraEmpresaSufixo}${competenciaSufixo}`;
if (reviewRegraEmpresaObs) { if (reviewRegraEmpresaObs) {
// As duas regras (empresa e de custeio salva) são mutuamente exclusivas // As duas regras (empresa e de custeio salva) são mutuamente exclusivas
// na criação — nunca as duas preenchidas ao mesmo tempo — mas o bloco é // na criação — nunca as duas preenchidas ao mesmo tempo — mas o bloco é
@ -1380,6 +1485,9 @@ document.addEventListener("DOMContentLoaded", async () => {
} }
function renderTabs() { function renderTabs() {
if (reviewEditarBtn) {
reviewEditarBtn.hidden = importacaoAtual.status !== "concluida";
}
const tipos = importacaoAtual.tipos_lancamento || []; const tipos = importacaoAtual.tipos_lancamento || [];
const tabKeys = [...tipos, "auditoria", "alteracoes"]; const tabKeys = [...tipos, "auditoria", "alteracoes"];
if (!activeTab || !tabKeys.includes(activeTab)) activeTab = tabKeys[0]; if (!activeTab || !tabKeys.includes(activeTab)) activeTab = tabKeys[0];
@ -1459,8 +1567,33 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
} }
// Colunas sempre editáveis, em qualquer linha — o resto do cadastro
// (nome, CPF, código, data...) já vem certo do casamento automático (ou
// da busca no Questor) e só devia ser tocado numa linha incluída à mão
// (ver linhasIncluidasManualmente() abaixo), nunca numa linha existente
// por engano.
const PID_IPS_CAMPOS_SEMPRE_EDITAVEIS = ["valor_empresa", "valor"];
// Uma linha é "incluída manualmente" se tiver um ImportacaoPlanoSaudeAlteracao
// tipo="inclusao" ainda não revertido apontando pra ela — mesmo sinal que já
// existia pra alimentar a aba "Alterações", não precisou de nenhum campo
// novo no backend. Só essas linhas ganham os demais campos editáveis;
// qualquer linha que já veio do processamento (upload ou Questor) só edita
// Valor Empresa/Valor.
function linhasIncluidasManualmente() {
const ids = new Set();
(importacaoAtual.alteracoes || []).forEach((alt) => {
if (alt.tipo === "inclusao" && !alt.revertida && alt.linha) ids.add(alt.linha);
});
return ids;
}
function panelHtmlParaTipo(tipo) { function panelHtmlParaTipo(tipo) {
const campos = PID_IPS_CAMPOS_LINHA; const campos = PID_IPS_CAMPOS_LINHA;
// Importação concluída: nada é editável até reabrir (botão "Editar" no
// cabeçalho da Revisão) — nem os valores, nem adicionar/remover linha.
const concluida = importacaoAtual.status === "concluida";
const incluidasManualmente = linhasIncluidasManualmente();
let linhas = (importacaoAtual.linhas || []).filter((l) => l.tipo_lancamento === tipo); let linhas = (importacaoAtual.linhas || []).filter((l) => l.tipo_lancamento === tipo);
if (linhaSortKey) { if (linhaSortKey) {
linhas = [...linhas].sort((a, b) => comparaValorRevisao(a[linhaSortKey], b[linhaSortKey]) * linhaSortDir); linhas = [...linhas].sort((a, b) => comparaValorRevisao(a[linhaSortKey], b[linhaSortKey]) * linhaSortDir);
@ -1475,22 +1608,27 @@ document.addEventListener("DOMContentLoaded", async () => {
: ""; : "";
const linhasHtml = linhas const linhasHtml = linhas
.map( .map((linha) => {
(linha) => ` const editavelLivre = !concluida && incluidasManualmente.has(linha.id);
<tr data-linha-id="${linha.id}"> const celulasHtml = campos
<td class="ips-table-actions"> .map(([campo]) => {
<button type="button" class="icon-btn ips-remove-linha" data-remover-linha="${linha.id}" aria-label="Remover linha" title="Remover linha"> const editavel = !concluida && (editavelLivre || PID_IPS_CAMPOS_SEMPRE_EDITAVEIS.includes(campo));
return editavel
? `<td><input type="text" class="ips-table-input" data-linha-campo="${campo}" value="${escapeAttr(linha[campo])}" /></td>`
: `<td><span class="ips-table-value">${escapeHtml(linha[campo])}</span></td>`;
})
.join("");
const removerHtml = concluida
? ""
: `<button type="button" class="icon-btn ips-remove-linha" data-remover-linha="${linha.id}" aria-label="Remover linha" title="Remover linha">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
</button> </button>`;
</td> return `
${campos <tr data-linha-id="${linha.id}">
.map( <td class="ips-table-actions">${removerHtml}</td>
([campo]) => ${celulasHtml}
`<td><input type="text" class="ips-table-input" data-linha-campo="${campo}" value="${escapeAttr(linha[campo])}" /></td>` </tr>`;
) })
.join("")}
</tr>`
)
.join(""); .join("");
return ` return `
@ -1506,12 +1644,22 @@ document.addEventListener("DOMContentLoaded", async () => {
<tbody>${linhasHtml || `<tr class="pa-empty-row"><td colspan="${campos.length + 1}">Nenhuma linha.</td></tr>`}</tbody> <tbody>${linhasHtml || `<tr class="pa-empty-row"><td colspan="${campos.length + 1}">Nenhuma linha.</td></tr>`}</tbody>
</table> </table>
</div> </div>
${concluida ? "" : `
<div class="ips-add-row"> <div class="ips-add-row">
<button type="button" class="btn-outline" data-ips-add-linha="${tipo}"> <button type="button" class="btn-outline" data-ips-add-linha="${tipo}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14" stroke-linecap="round"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14" stroke-linecap="round"/></svg>
Adicionar linha Adicionar linha
</button> </button>
</div>`; </div>`}`;
}
// Valor sintético (não vem da API) só pra ordenar a coluna "Ação" —
// prefixo numérico garante o agrupamento (pendente/resolvido/sem ação)
// independente da ordenação alfabética do texto exibido em cada estado.
function auditoriaAcaoOrdem(item) {
if (item.resolvida) return "1-resolvido";
if (PID_IPS_MOTIVOS_RESOLVIVEIS.includes(item.motivo)) return "0-pendente";
return "2-sem-acao";
} }
function panelHtmlAuditoria() { function panelHtmlAuditoria() {
@ -1521,7 +1669,11 @@ document.addEventListener("DOMContentLoaded", async () => {
return `<p class="ips-empty">Nenhum item em auditoria — tudo foi lançado automaticamente.</p>`; return `<p class="ips-empty">Nenhum item em auditoria — tudo foi lançado automaticamente.</p>`;
} }
if (auditoriaSortKey) { if (auditoriaSortKey) {
itens = [...itens].sort((a, b) => comparaValorRevisao(a[auditoriaSortKey], b[auditoriaSortKey]) * auditoriaSortDir); const valorOrdenacao =
auditoriaSortKey === "acao" ? auditoriaAcaoOrdem : (item) => item[auditoriaSortKey];
itens = [...itens].sort(
(a, b) => comparaValorRevisao(valorOrdenacao(a), valorOrdenacao(b)) * auditoriaSortDir
);
} }
const linhasHtml = itens const linhasHtml = itens
.map((item) => { .map((item) => {
@ -1530,7 +1682,7 @@ document.addEventListener("DOMContentLoaded", async () => {
acaoHtml = `<span class="ips-auditoria-resolvido" title="Vinculado a: ${escapeAttr(item.linha_vinculada_nome || "")}">Resolvido${ acaoHtml = `<span class="ips-auditoria-resolvido" title="Vinculado a: ${escapeAttr(item.linha_vinculada_nome || "")}">Resolvido${
item.linha_vinculada_nome ? ` — ${item.linha_vinculada_nome}` : "" item.linha_vinculada_nome ? ` — ${item.linha_vinculada_nome}` : ""
}</span>`; }</span>`;
} else if (PID_IPS_MOTIVOS_RESOLVIVEIS.includes(item.motivo)) { } else if (importacaoAtual.status !== "concluida" && PID_IPS_MOTIVOS_RESOLVIVEIS.includes(item.motivo)) {
acaoHtml = `<button type="button" class="btn-outline ips-vincular-btn" data-vincular-auditoria="${item.id}">Vincular pessoa</button>`; acaoHtml = `<button type="button" class="btn-outline ips-vincular-btn" data-vincular-auditoria="${item.id}">Vincular pessoa</button>`;
} }
return ` return `
@ -1551,7 +1703,9 @@ document.addEventListener("DOMContentLoaded", async () => {
<table class="pa-table"> <table class="pa-table">
${colGroupHtml(campos, colWidthsAuditoria, PID_IPS_AUDITORIA_ACAO_WIDTH)} ${colGroupHtml(campos, colWidthsAuditoria, PID_IPS_AUDITORIA_ACAO_WIDTH)}
<thead> <thead>
<tr><th class="ips-table-actions">Ação</th>${campos <tr><th class="ips-table-actions ips-col-sortable${
auditoriaSortKey === "acao" ? (auditoriaSortDir === 1 ? " is-sort-asc" : " is-sort-desc") : ""
}" data-col-campo="acao" data-col-target="auditoria">Ação <span class="ips-sort-icon">↕</span></th>${campos
.map(([campo, label]) => colHeaderHtml(campo, label, "auditoria", { key: auditoriaSortKey, dir: auditoriaSortDir })) .map(([campo, label]) => colHeaderHtml(campo, label, "auditoria", { key: auditoriaSortKey, dir: auditoriaSortDir }))
.join("")}</tr> .join("")}</tr>
</thead> </thead>
@ -1584,6 +1738,8 @@ document.addEventListener("DOMContentLoaded", async () => {
const acaoHtml = alt.revertida const acaoHtml = alt.revertida
? `<span class="ips-alteracao-revertida">Revertida</span>` ? `<span class="ips-alteracao-revertida">Revertida</span>`
: importacaoAtual.status === "concluida"
? `<span class="ips-empty-cell">—</span>`
: `<button type="button" class="btn-outline ips-reverter-btn" data-reverter-alteracao="${alt.id}">Reverter</button>`; : `<button type="button" class="btn-outline ips-reverter-btn" data-reverter-alteracao="${alt.id}">Reverter</button>`;
return ` return `
@ -1736,6 +1892,21 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
} }
if (reviewEditarBtn) {
reviewEditarBtn.addEventListener("click", async () => {
reviewEditarBtn.disabled = true;
try {
importacaoAtual = await pidReabrirImportacaoPlanoSaude(importacaoAtual.id);
reviewError.textContent = "";
renderTabs();
} catch (e) {
reviewError.textContent = e.message;
} finally {
reviewEditarBtn.disabled = false;
}
});
}
if (regracadBtn) regracadBtn.addEventListener("click", abrirCadastroRegras); if (regracadBtn) regracadBtn.addEventListener("click", abrirCadastroRegras);
if (regracadFecharBtn) regracadFecharBtn.addEventListener("click", fecharCadastroRegras); if (regracadFecharBtn) regracadFecharBtn.addEventListener("click", fecharCadastroRegras);
if (regracadModal) { if (regracadModal) {
@ -2062,6 +2233,12 @@ document.addEventListener("DOMContentLoaded", async () => {
gerarBtn.disabled = true; gerarBtn.disabled = true;
try { try {
await pidGerarArquivoPlanoSaude(importacaoAtual.id); await pidGerarArquivoPlanoSaude(importacaoAtual.id);
// Depois de baixar o arquivo, volta pro histórico — mesmo pedido do
// usuário pro fluxo de "Nova Importação" (abrirRevisao já leva pra
// lá), já que gerar() marca a importação como concluída e não há
// mais nada a revisar imediatamente depois de baixar o arquivo.
showView("list");
await refreshList();
} catch (e) { } catch (e) {
reviewError.textContent = e.message; reviewError.textContent = e.message;
} finally { } finally {
@ -2144,7 +2321,12 @@ document.addEventListener("DOMContentLoaded", async () => {
} }
listSortHeaders.forEach((th) => { listSortHeaders.forEach((th) => {
th.addEventListener("click", () => { th.addEventListener("click", (event) => {
// Ignora cliques que vieram de dentro do popup de filtro (ver
// criarFiltroColuna abaixo) — sem isso, abrir o filtro ou marcar um
// checkbox dele também reordenava a coluna, já que o filtro mora
// dentro do mesmo <th> clicável.
if (event.target.closest(".ips-col-filter")) return;
const key = th.getAttribute("data-sort"); const key = th.getAttribute("data-sort");
if (listSortKey === key) { if (listSortKey === key) {
listSortDir *= -1; listSortDir *= -1;
@ -2157,6 +2339,148 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
}); });
// Filtro "estilo Excel" por coluna do histórico — botão de funil dentro
// do próprio <th> de ordenação, popup com busca + checklist de valores
// distintos (reaproveita .checklist-* de components.css). `filtrosColuna`
// guarda um `fechar()` por coluna, pra fechar todos ao abrir outro ou ao
// clicar fora de qualquer um deles.
const filtrosColuna = [];
function fecharTodosOsFiltrosColuna() {
filtrosColuna.forEach((f) => f.fechar());
}
function criarFiltroColuna(campo) {
const th = listSortHeaders.find((el) => el.getAttribute("data-sort") === campo);
if (!th) return;
const wrapper = document.createElement("span");
wrapper.className = "ips-col-filter";
wrapper.innerHTML = `
<button type="button" class="ips-col-filter__btn" aria-label="Filtrar coluna" aria-expanded="false" title="Filtrar">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" stroke-linejoin="round"/></svg>
</button>
<div class="ips-col-filter__popup" hidden>
<input type="text" class="checklist-search" placeholder="Buscar..." />
<label class="modal-checkbox checklist-select-all"><input type="checkbox" checked /> Selecionar todos</label>
<div class="checklist-box"></div>
<div class="ips-col-filter__actions">
<button type="button" class="btn-ghost ips-col-filter__limpar">Limpar</button>
<button type="button" class="btn-solid ips-col-filter__aplicar">Aplicar</button>
</div>
</div>`;
th.appendChild(wrapper);
const btn = wrapper.querySelector(".ips-col-filter__btn");
const popup = wrapper.querySelector(".ips-col-filter__popup");
const searchInput = wrapper.querySelector(".checklist-search");
const selectAllInput = wrapper.querySelector(".checklist-select-all input");
const box = wrapper.querySelector(".checklist-box");
const limparBtn = wrapper.querySelector(".ips-col-filter__limpar");
const aplicarBtn = wrapper.querySelector(".ips-col-filter__aplicar");
const extrator = PID_IPS_LISTA_SORT_VALOR[campo];
let marcados = null; // Set em edição — só existe enquanto o popup está aberto
function valoresDistintos() {
return Array.from(new Set(importacoes.map(extrator))).sort((a, b) =>
labelValorFiltroLista(campo, a).localeCompare(labelValorFiltroLista(campo, b), "pt-BR", { sensitivity: "base" })
);
}
function renderItens() {
const termo = searchInput.value.trim().toLowerCase();
const visiveis = valoresDistintos().filter((v) => labelValorFiltroLista(campo, v).toLowerCase().includes(termo));
box.innerHTML = visiveis.length
? visiveis
.map(
(v) =>
`<label class="checklist-item"><input type="checkbox" data-valor-filtro="${escapeAttr(v)}" ${marcados.has(v) ? "checked" : ""} /><span class="checklist-item__info">${escapeHtml(labelValorFiltroLista(campo, v))}</span></label>`
)
.join("")
: `<p class="checklist-empty">Nenhum valor encontrado.</p>`;
selectAllInput.checked = visiveis.length > 0 && visiveis.every((v) => marcados.has(v));
}
function abrir() {
// `marcados` sempre recomeça a partir do filtro já aplicado (ou de
// todos os valores existentes, se a coluna ainda não tem filtro) —
// nunca do que ficou de uma edição anterior cancelada sem "Aplicar".
marcados = listFiltros[campo] ? new Set(listFiltros[campo]) : new Set(valoresDistintos());
searchInput.value = "";
renderItens();
popup.hidden = false;
btn.setAttribute("aria-expanded", "true");
}
function fechar() {
popup.hidden = true;
marcados = null;
btn.setAttribute("aria-expanded", "false");
}
function atualizarBotaoAtivo() {
btn.classList.toggle("is-active", !!listFiltros[campo]);
}
btn.addEventListener("click", (event) => {
event.stopPropagation();
const estavaAberto = !popup.hidden;
fecharTodosOsFiltrosColuna();
if (!estavaAberto) abrir();
});
popup.addEventListener("click", (event) => event.stopPropagation());
searchInput.addEventListener("input", renderItens);
selectAllInput.addEventListener("change", () => {
const termo = searchInput.value.trim().toLowerCase();
const visiveis = valoresDistintos().filter((v) => labelValorFiltroLista(campo, v).toLowerCase().includes(termo));
if (selectAllInput.checked) visiveis.forEach((v) => marcados.add(v));
else visiveis.forEach((v) => marcados.delete(v));
renderItens();
});
box.addEventListener("change", (event) => {
const input = event.target.closest("[data-valor-filtro]");
if (!input) return;
const valor = input.getAttribute("data-valor-filtro");
if (input.checked) marcados.add(valor);
else marcados.delete(valor);
renderItens();
});
limparBtn.addEventListener("click", () => {
listFiltros[campo] = null;
fechar();
atualizarBotaoAtivo();
listPage = 1;
renderList();
});
aplicarBtn.addEventListener("click", () => {
const todos = valoresDistintos();
// Marcar tudo equivale a "sem filtro" — mantém null nesse caso, senão
// um valor novo que apareça depois (operadora nova, por exemplo)
// ficaria de fora até o usuário marcá-lo manualmente de novo.
const marcouTudo = todos.length === marcados.size && todos.every((v) => marcados.has(v));
listFiltros[campo] = marcouTudo ? null : new Set(marcados);
fechar();
atualizarBotaoAtivo();
listPage = 1;
renderList();
});
filtrosColuna.push({ fechar });
}
["codigo_empresa", "nome_operadora", "status", "criado_por"].forEach(criarFiltroColuna);
document.addEventListener("click", (event) => {
if (!event.target.closest(".ips-col-filter")) fecharTodosOsFiltrosColuna();
});
if (listPagination) { if (listPagination) {
listPagination.addEventListener("click", (event) => { listPagination.addEventListener("click", (event) => {
if (event.target.closest("#ips-list-prev-btn")) { if (event.target.closest("#ips-list-prev-btn")) {
@ -2175,22 +2499,40 @@ document.addEventListener("DOMContentLoaded", async () => {
formError.textContent = "Selecione a empresa e a operadora."; formError.textContent = "Selecione a empresa e a operadora.";
return; return;
} }
if (!formPlanilha.files[0]) { const origemQuestor = planilhaOrigemAtual() === "questor";
formError.textContent = "Anexe a planilha padrão do Questor."; let competenciaIso = null;
return; if (origemQuestor) {
competenciaIso = competenciaParaIso(formCompetencia.value);
if (!competenciaIso) {
formError.textContent = "Informe a competência no formato MM/AAAA para buscar a planilha no Questor.";
return;
}
} else {
if (!formPlanilha.files[0]) {
formError.textContent = "Anexe a planilha padrão do Questor.";
return;
}
if (validadorPlanilha.ehInvalido()) {
formError.textContent = "Corrija a planilha anexada (veja a mensagem abaixo do campo) antes de processar.";
return;
}
} }
if (!formArquivo.files[0]) { if (!formArquivo.files[0]) {
formError.textContent = "Anexe o arquivo da operadora."; formError.textContent = "Anexe o arquivo da operadora.";
return; return;
} }
if (validadorPlanilha.ehInvalido() || validadorArquivo.ehInvalido()) { if (validadorArquivo.ehInvalido()) {
formError.textContent = "Corrija os arquivos anexados (veja a mensagem abaixo de cada um) antes de processar."; formError.textContent = "Corrija o arquivo da operadora (veja a mensagem abaixo do campo) antes de processar.";
return; return;
} }
formError.textContent = ""; formError.textContent = "";
const formData = new FormData(); const formData = new FormData();
formData.append("planilha_padrao", formPlanilha.files[0]); if (origemQuestor) {
formData.append("competencia", competenciaIso);
} else {
formData.append("planilha_padrao", formPlanilha.files[0]);
}
formData.append("arquivo_operadora", formArquivo.files[0]); formData.append("arquivo_operadora", formArquivo.files[0]);
montarFormDataDeRegra(formData, regraResolvidaAtual); montarFormDataDeRegra(formData, regraResolvidaAtual);

View File

@ -318,7 +318,7 @@
<button type="button" class="btn-danger-outline" id="ips-list-bulk-delete-btn">Excluir selecionadas</button> <button type="button" class="btn-danger-outline" id="ips-list-bulk-delete-btn">Excluir selecionadas</button>
</div> </div>
<div class="pa-table-wrap"> <div class="pa-table-wrap ips-list-table-wrap">
<table class="pa-table" id="ips-list-table"> <table class="pa-table" id="ips-list-table">
<thead> <thead>
<tr> <tr>
@ -344,7 +344,7 @@
<div class="ips-header"> <div class="ips-header">
<div> <div>
<h2>Nova Importação</h2> <h2>Nova Importação</h2>
<p class="ips-subtitle">Anexe a planilha padrão do Questor e o relatório enviado pela operadora.</p> <p class="ips-subtitle">Busque a planilha padrão do Questor por competência (ou anexe manualmente) e informe o relatório enviado pela operadora.</p>
</div> </div>
<button type="button" class="btn-outline" id="ips-form-back-btn">Voltar</button> <button type="button" class="btn-outline" id="ips-form-back-btn">Voltar</button>
</div> </div>
@ -391,8 +391,22 @@
<label>Arquivos</label> <label>Arquivos</label>
<div class="ips-upload-box"> <div class="ips-upload-box">
<p class="ips-upload-box__title">1. Planilha padrão (Questor)</p> <p class="ips-upload-box__title">1. Planilha padrão (Questor)</p>
<p class="ips-upload-box__hint">Modelo em branco exportado do Questor, com o cadastro dos beneficiários (sem valores).</p> <p class="ips-upload-box__hint">Cadastro dos beneficiários (sem valores) — buscado direto do Questor, ou anexado manualmente como alternativa.</p>
<div class="ips-file-field"> <div class="ips-planilha-origem-opcoes">
<label class="modal-checkbox">
<input type="radio" name="ips-planilha-origem" id="ips-planilha-origem-questor" value="questor" checked />
Buscar automaticamente do Questor
</label>
<label class="modal-checkbox">
<input type="radio" name="ips-planilha-origem" id="ips-planilha-origem-upload" value="upload" />
Anexar manualmente
</label>
</div>
<div id="ips-planilha-questor-field" class="modal-field">
<label for="ips-form-competencia">Competência</label>
<input type="text" id="ips-form-competencia" inputmode="numeric" placeholder="MM/AAAA" maxlength="7" autocomplete="off" />
</div>
<div class="ips-file-field" id="ips-planilha-upload-field" hidden>
<label class="btn-outline ips-file-field__btn" for="ips-form-planilha"> <label class="btn-outline ips-file-field__btn" for="ips-form-planilha">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 16V4M12 4l4 4M12 4L8 8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" stroke-linejoin="round"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 16V4M12 4l4 4M12 4L8 8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" stroke-linecap="round" stroke-linejoin="round"/></svg>
Selecionar arquivo Selecionar arquivo
@ -439,6 +453,7 @@
</div> </div>
<div class="ips-header-actions"> <div class="ips-header-actions">
<button type="button" class="btn-outline" id="ips-review-back-btn">Voltar</button> <button type="button" class="btn-outline" id="ips-review-back-btn">Voltar</button>
<button type="button" class="btn-outline" id="ips-review-editar-btn" hidden>Editar</button>
<button type="button" class="btn-solid" id="ips-gerar-btn">Gerar Arquivo</button> <button type="button" class="btn-solid" id="ips-gerar-btn">Gerar Arquivo</button>
</div> </div>
</div> </div>