diff --git a/.claude/settings.json b/.claude/settings.json index 96eee43..e691b33 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,7 +2,11 @@ "permissions": { "allow": [ "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)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 0e407f6..4df4a4b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -549,8 +549,8 @@ Pra adicionar uma operadora nova: criar `operadoras//.py` impleme ### 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. -- `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. +- `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) — 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". - `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. @@ -558,13 +558,35 @@ Pra adicionar uma operadora nova: criar `operadoras//.py` impleme ### Fluxo e endpoints `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 `" - "` (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 `` 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 `` 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 `` 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 ``, 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** (`` + `mascaraCompetencia()`/`competenciaParaIso()` em JS — deliberadamente **não** um ``: 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 `` 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 `" - "` 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 diff --git a/media/planos_saude/operadora/221_-_RELATÓRIO_ITAMED_082026_S4KHFAY.pdf b/media/planos_saude/operadora/221_-_RELATÓRIO_ITAMED_082026_S4KHFAY.pdf new file mode 100644 index 0000000..30633d8 Binary files /dev/null and b/media/planos_saude/operadora/221_-_RELATÓRIO_ITAMED_082026_S4KHFAY.pdf differ diff --git a/media/planos_saude/operadora/221_-_RELATÓRIO_UNIMED_082026_MgsNBZR.csv b/media/planos_saude/operadora/221_-_RELATÓRIO_UNIMED_082026_MgsNBZR.csv new file mode 100644 index 0000000..46bf847 --- /dev/null +++ b/media/planos_saude/operadora/221_-_RELATÓRIO_UNIMED_082026_MgsNBZR.csv @@ -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;;;; diff --git a/media/planos_saude/planilha_padrao/questor_221_2026-08.csv b/media/planos_saude/planilha_padrao/questor_221_2026-08.csv new file mode 100644 index 0000000..6359c19 --- /dev/null +++ b/media/planos_saude/planilha_padrao/questor_221_2026-08.csv @@ -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; diff --git a/media/planos_saude/planilha_padrao/questor_221_2026-08_fDbSEFb.csv b/media/planos_saude/planilha_padrao/questor_221_2026-08_fDbSEFb.csv new file mode 100644 index 0000000..1ce1ed5 --- /dev/null +++ b/media/planos_saude/planilha_padrao/questor_221_2026-08_fDbSEFb.csv @@ -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; diff --git a/plano.md b/plano.md index 34d447a..90228f7 100644 --- a/plano.md +++ b/plano.md @@ -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. +### 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" (``) 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 `` — 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 `` 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) { listView.hidden = view !== "list"; formView.hidden = view !== "form"; @@ -483,6 +555,18 @@ document.addEventListener("DOMContentLoaded", async () => { 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) { if (totalPaginas <= 1) { listPagination.innerHTML = ""; @@ -515,11 +599,23 @@ document.addEventListener("DOMContentLoaded", async () => { 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() { - 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 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)); listPage = Math.min(listPage, totalPaginas); @@ -1200,7 +1296,7 @@ document.addEventListener("DOMContentLoaded", async () => { regracadNovoEmpresaNome.textContent = resultado.nome_empresa; } else { 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, // ex. "092" -> "92") — reflete isso no campo assim que a resposta @@ -1317,6 +1413,10 @@ document.addEventListener("DOMContentLoaded", async () => { validadorPlanilha.limpar(); validadorArquivo.limpar(); formError.textContent = ""; + formCompetencia.value = ""; + const radioQuestor = planilhaOrigemRadios.find((r) => r.value === "questor"); + if (radioQuestor) radioQuestor.checked = true; + atualizarPlanilhaOrigemView(); regraResolvidaAtual = null; renderResumoRegra(null); impEmpresaOps.limpar(); @@ -1354,8 +1454,13 @@ document.addEventListener("DOMContentLoaded", async () => { } } 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 = - `${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) { // 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 é @@ -1380,6 +1485,9 @@ document.addEventListener("DOMContentLoaded", async () => { } function renderTabs() { + if (reviewEditarBtn) { + reviewEditarBtn.hidden = importacaoAtual.status !== "concluida"; + } const tipos = importacaoAtual.tipos_lancamento || []; const tabKeys = [...tipos, "auditoria", "alteracoes"]; 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) { 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); if (linhaSortKey) { linhas = [...linhas].sort((a, b) => comparaValorRevisao(a[linhaSortKey], b[linhaSortKey]) * linhaSortDir); @@ -1475,22 +1608,27 @@ document.addEventListener("DOMContentLoaded", async () => { : ""; const linhasHtml = linhas - .map( - (linha) => ` - - - - - ${campos - .map( - ([campo]) => - `` - ) - .join("")} - ` - ) + `; + return ` + + ${removerHtml} + ${celulasHtml} + `; + }) .join(""); return ` @@ -1506,12 +1644,22 @@ document.addEventListener("DOMContentLoaded", async () => { ${linhasHtml || `Nenhuma linha.`} + ${concluida ? "" : `
-
`; + `}`; + } + + // 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() { @@ -1521,7 +1669,11 @@ document.addEventListener("DOMContentLoaded", async () => { return `

Nenhum item em auditoria — tudo foi lançado automaticamente.

`; } 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 .map((item) => { @@ -1530,7 +1682,7 @@ document.addEventListener("DOMContentLoaded", async () => { acaoHtml = `Resolvido${ item.linha_vinculada_nome ? ` — ${item.linha_vinculada_nome}` : "" }`; - } else if (PID_IPS_MOTIVOS_RESOLVIVEIS.includes(item.motivo)) { + } else if (importacaoAtual.status !== "concluida" && PID_IPS_MOTIVOS_RESOLVIVEIS.includes(item.motivo)) { acaoHtml = ``; } return ` @@ -1551,7 +1703,9 @@ document.addEventListener("DOMContentLoaded", async () => { ${colGroupHtml(campos, colWidthsAuditoria, PID_IPS_AUDITORIA_ACAO_WIDTH)} - ${campos + ${campos .map(([campo, label]) => colHeaderHtml(campo, label, "auditoria", { key: auditoriaSortKey, dir: auditoriaSortDir })) .join("")} @@ -1584,6 +1738,8 @@ document.addEventListener("DOMContentLoaded", async () => { const acaoHtml = alt.revertida ? `Revertida` + : importacaoAtual.status === "concluida" + ? `—` : ``; 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 (regracadFecharBtn) regracadFecharBtn.addEventListener("click", fecharCadastroRegras); if (regracadModal) { @@ -2062,6 +2233,12 @@ document.addEventListener("DOMContentLoaded", async () => { gerarBtn.disabled = true; try { 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) { reviewError.textContent = e.message; } finally { @@ -2144,7 +2321,12 @@ document.addEventListener("DOMContentLoaded", async () => { } 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
Ação
Ação ↕
clicável. + if (event.target.closest(".ips-col-filter")) return; const key = th.getAttribute("data-sort"); if (listSortKey === key) { 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 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 = ` + + `; + 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) => + `` + ) + .join("") + : `

Nenhum valor encontrado.

`; + 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) { listPagination.addEventListener("click", (event) => { if (event.target.closest("#ips-list-prev-btn")) { @@ -2175,22 +2499,40 @@ document.addEventListener("DOMContentLoaded", async () => { formError.textContent = "Selecione a empresa e a operadora."; return; } - if (!formPlanilha.files[0]) { - formError.textContent = "Anexe a planilha padrão do Questor."; - return; + const origemQuestor = planilhaOrigemAtual() === "questor"; + let competenciaIso = null; + 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]) { formError.textContent = "Anexe o arquivo da operadora."; return; } - if (validadorPlanilha.ehInvalido() || validadorArquivo.ehInvalido()) { - formError.textContent = "Corrija os arquivos anexados (veja a mensagem abaixo de cada um) antes de processar."; + if (validadorArquivo.ehInvalido()) { + formError.textContent = "Corrija o arquivo da operadora (veja a mensagem abaixo do campo) antes de processar."; return; } formError.textContent = ""; 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]); montarFormDataDeRegra(formData, regraResolvidaAtual); diff --git a/templates/importacao-plano-saude.html b/templates/importacao-plano-saude.html index 019f524..3b918f9 100644 --- a/templates/importacao-plano-saude.html +++ b/templates/importacao-plano-saude.html @@ -318,7 +318,7 @@ -
+
@@ -344,7 +344,7 @@

Nova Importação

-

Anexe a planilha padrão do Questor e o relatório enviado pela operadora.

+

Busque a planilha padrão do Questor por competência (ou anexe manualmente) e informe o relatório enviado pela operadora.

@@ -391,8 +391,22 @@

1. Planilha padrão (Questor)

-

Modelo em branco exportado do Questor, com o cadastro dos beneficiários (sem valores).

-
+

Cadastro dos beneficiários (sem valores) — buscado direto do Questor, ou anexado manualmente como alternativa.

+
+ + +
+ +
+