From 302bbea1633a05d8c41b8a1b156ba64b99f3d88f Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 22 Sep 2026 10:14:55 -0300 Subject: [PATCH] =?UTF-8?q?Inclus=C3=A3o=20operadoras=20grupo=20Tecnomyl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.json | 3 +- portal_api/planos_saude/CHANGELOG.md | 17 +++++++++ portal_api/planos_saude/CLAUDE.md | 9 +++-- portal_api/planos_saude/regras_empresa.py | 46 ++++++++++++++++------- portal_api/serializers.py | 16 ++++++-- 5 files changed, 68 insertions(+), 23 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 287d5be..9bad9a4 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -138,7 +138,8 @@ "Bash(C:/Users/Depaula/Documents/Portal/.venv/Scripts/python.exe -c ' *)", "Bash(cp \"C:/Users/Depaula/Documents/Logos/P.I.D. Logo Design Aniversário/Logo intro animation/export-56/Logo - DE PAULA CONTADORES - escrita branca \\(1\\).png\" \"C:/Users/Depaula/Documents/Portal/static/img/pid-aniversario-depaula-wordmark.png\" *)", "Bash(grep -o '`' \"C:/Users/Depaula/Documents/Portal/static/js/login-intro-aniversario.js\")", - "Bash(\"./.venv/Scripts/python.exe\" -)" + "Bash(\"./.venv/Scripts/python.exe\" -)", + "Bash(grep -n \"codigo_empresa\\\\` \\(código da empresa\\\\|unimed_1778_tecnomyl\\\\*\\\\*\\\\|Trava de compatibilidade generalizada\" portal_api/planos_saude/CLAUDE.md)" ], "additionalDirectories": [ "C:\\Users\\Depaula\\AppData\\Local\\Temp\\claude\\c--Users-Depaula-Documents-Portal\\3ea0ee22-e5fd-4030-98b1-ee2e71c16ce0\\scratchpad\\halloween-design", diff --git a/portal_api/planos_saude/CHANGELOG.md b/portal_api/planos_saude/CHANGELOG.md index 247cba9..a66b168 100644 --- a/portal_api/planos_saude/CHANGELOG.md +++ b/portal_api/planos_saude/CHANGELOG.md @@ -370,3 +370,20 @@ Depois de validar a regra da Unimed já cadastrada pra Tecnomyl (empresa 1778, v 3. **`_regra_amil_898_tecnomyl`** (`regras_empresa.py`, registrada como `amil_898_tecnomyl`): diferente da Tecnomyl-Unimed (teto por família), é uma regra **por pessoa**, sem interação entre membros da família — lê `linha.tipo_pessoa` (fallback "D" se em branco) e manda 100% pra `valor_empresa` (T/D) ou 100% pra `valor` (A). `chave_casamento="cpf"`, `tipos_lancamento=("mensalidade",)`. Validado com o pipeline completo (`processa_importacao` ponta a ponta, com uma planilha padrão sintética) além do teste isolado da função — nenhum erro na divisão titular/dependente vs. agregado. **Nota operacional pra próxima competência**: alguns dependentes do arquivo real vêm sem CPF (crianças, principalmente) — normalizado pra `"00000000000"`. Como a estratégia "cpf" (`_casa_por_cpf`) não aceita `vinculos_por_nome` (só a estratégia "nome" reaplica automaticamente um vínculo salvo), uma dessas pessoas vinculada manualmente via "Vincular pessoa" precisa ser vinculada de novo toda competência — comportamento pré-existente de qualquer operadora "cpf" (Amil, SulAmérica, MetLife), não introduzido por esta regra. Ainda não cadastrado em `RegraCusteioPlanoSaude` (banco de produção) — cadastro fica a cargo do usuário direto pela tela "Cadastro de Regras", não é feito por script/ORM neste ambiente. + + +### Rodada 133 — Regra de empresa passa a cobrir um grupo econômico (`codigos_empresa`) + +Ao cadastrar a regra de custeio da empresa **1855 (H2O INNOVATION LTDA)** na Unimed, o usuário levou um erro de validação: *"Esta regra especial foi cadastrada para a empresa código 1778, não para 1855."* Não era bug — era a trava de código de empresa funcionando como projetada. O que não estava previsto no desenho é que **1778 (Tecnomyl) e 1855 fazem parte do mesmo grupo e têm a MESMA condição negociada** (ajuda de custo de até R$ 661,61 por família), confirmado pelo usuário. + +O registro `REGRAS_EMPRESA` assumia **1 regra = 1 empresa** (`codigo_empresa`, string). Passou a assumir 1 regra = N empresas do mesmo grupo: o campo virou **`codigos_empresa`, uma tupla** (`("1778", "1855")` na `unimed_1778_tecnomyl`; as outras duas regras ficaram com um código só, na mesma forma de tupla). A alternativa — duplicar a regra, uma entrada por empresa com o mesmo cálculo — foi descartada: no dia que o teto mudar, seria preciso lembrar de mexer nas duas. + +Os três pontos que conferiam o código foram ajustados pra testar **pertinência na tupla** em vez de igualdade: as duas validações de cadastro (`RegraCusteioPlanoSaudeSerializer.validate()` e `ImportacaoPlanoSaudeCreateSerializer.validate()`, `serializers.py`) e a trava de processamento (`regras_empresa.valida_regra_empresa()`, que exige que a planilha padrão anexada tenha linha de alguma das empresas cobertas). As mensagens de erro passaram a listar os códigos aceitos ("... código 1778 ou 1855, não para X"). + +Nada mudou no cálculo: `_aplica_teto_familia` nunca olhou o código da empresa — ele só aparecia na trava. Verificado rodando a validação com planilhas das empresas 1778, 1855 e 9999 (as duas primeiras passam, a terceira continua bloqueada) e reconferindo o teto por família nos três cenários (abaixo do teto, acima do teto, titular sozinho). + +**Ainda na mesma rodada**, o usuário pediu pra cobrir mais duas empresas do grupo: a tupla fechou em `("1778", "1855", "1872", "1927")` — 1778 Tecnomyl Brasil, 1855 H2O Innovation, 1872 GS3 Digital e 1927 YVY Agricultura Digital. Os nomes das duas novas foram conferidos no Questor (consulta só leitura) antes de entrarem no registro, justamente pra pegar dígito trocado: um código errado aqui não dá erro nenhum, só aplica o teto de um cliente na folha de outro. O `label` passou a listar os quatro códigos ("1778/1855/1872/1927 - Unimed (Tecnomyl)") — quem cadastra a regra escolhe pelo código da empresa, então esconder os códigos no label tiraria justamente a conferência que importa no seletor. + +Na sequência o usuário confirmou que a **Amil Odonto (898) também vale pro grupo inteiro**, e a `amil_898_tecnomyl` recebeu a mesma tupla de quatro códigos. Vale notar que incluir um código aqui só DESTRAVA a possibilidade de cadastrar aquela regra pra aquela empresa — não aplica nada sozinho: uma empresa do grupo que não tenha o plano odontológico simplesmente nunca vai ter uma regra de custeio cadastrada pra Amil. + +**Cuidado ao mexer**: incluir um código a mais nessa tupla aplica silenciosamente o teto de um cliente na folha de outro — só fazer com confirmação de que as condições são idênticas. diff --git a/portal_api/planos_saude/CLAUDE.md b/portal_api/planos_saude/CLAUDE.md index f00f636..d8d0de5 100644 --- a/portal_api/planos_saude/CLAUDE.md +++ b/portal_api/planos_saude/CLAUDE.md @@ -261,18 +261,19 @@ Cobre regras de custeio negociadas com uma empresa específica que não cabem no - **Mutuamente exclusivo por TIPO, não em bloco**: no formulário de Cadastro de Regras, escolher uma regra especial trava (marca + desabilita + esconde os radios titular/dependente) só os checkboxes "Mensalidade"/"Coparticipação" que essa regra específica cobre — `aplicarTiposRegraEmpresa()` em `importacao-plano-saude.js`, chamada sempre que a regra selecionada muda (ao marcar/desmarcar o checkbox, ou ao escolher uma regra no picker). Uma regra que só cobre mensalidade (Tecnomyl) deixa "Coparticipação" livre pra configuração manual normalmente — mesmo comportamento de antes pra essa regra específica; a novidade é só que agora isso é decidido pelos `tipos_lancamento` de CADA regra, não fixo no código do formulário. Um checkbox travado (`.disabled`) não dispara `change` por clique do usuário, então a exclusividade mútua não precisa de nenhuma lógica extra nos handlers de "Mensalidade"/"Coparticipação" — só o handler de "Regra especial da empresa"/a seleção no picker chamam `aplicarTiposRegraEmpresa()`. - No backend, `RegraCusteioPlanoSaudeSerializer.validate()`/`ImportacaoPlanoSaudeCreateSerializer.validate()` calculam `regra_empresa_tipos` (interseção entre `REGRAS_EMPRESA[chave]["tipos_lancamento"]` e os tipos selecionados — erro claro se vier vazia), conferem `chave_casamento_para_tipo(tipo) == REGRAS_EMPRESA[chave]["chave_casamento"]` pra cada tipo coberto (não mais um "exige nome" hardcoded) e zeram `custeio_por_tipo[tipo]` só pros tipos em `regra_empresa_tipos` (os demais tipos selecionados continuam com custeio manual normal). `regras_empresa.valida_regra_empresa(regra_empresa_key, chave_casamento_por_tipo, linhas_sistema, tipos_selecionados)` devolve `(aplica, tipos_cobertos)` — `pipeline.processa_importacao` passa `regra_empresa_fn` pra `casa_individuos_com_planilha` só quando `tipo_lancamento in tipos_cobertos`. - **`matcher._casa_por_cpf` passou a suportar `regra_empresa_fn`** (antes só `_casa_por_nome` suportava) — sem agrupar por família (essa estratégia não tem esse conceito): acumula `(linha, valor_total)` de todo indivíduo casado por CPF e chama `regra_empresa_fn(linhas_e_valores, tipo_lancamento)` uma vez só, no fim, com todos os pares do tipo de lançamento inteiro. `aplica(linhas_e_valores, tipo_lancamento)` é a assinatura de toda regra agora (segundo argumento novo) — permite uma mesma função se comportar diferente por tipo (ver Ottimizza abaixo); a Tecnomyl recebe o parâmetro mas ignora (só é chamada pra "mensalidade" mesmo, via `tipos_lancamento`). -- **Registro** (`REGRAS_EMPRESA`): cada entrada tem `label`, `codigo_empresa` (código da empresa na planilha padrão pra qual a regra foi negociada), `operadora`, `chave_casamento`, `tipos_lancamento`, `aplica` (função que faz o cálculo) e `observacoes`. Pra cadastrar uma regra nova: escrever a função e registrar aqui — nada mais precisa mudar (`GET /api/importacoes-plano-saude/regras-empresa/` já reflete o registro, incluindo `tipos_lancamento`, consumido tanto pelo seletor dentro do Cadastro de Regras quanto pelo resumo só-leitura de "Nova Importação"). +- **Registro** (`REGRAS_EMPRESA`): cada entrada tem `label`, `codigos_empresa` (**tupla** dos códigos de empresa na planilha padrão pra os quais a regra foi negociada), `operadora`, `chave_casamento`, `tipos_lancamento`, `aplica` (função que faz o cálculo) e `observacoes`. + - **Por que uma tupla e não um código só** (mudou numa rodada posterior, a pedido do usuário): uma mesma condição negociada pode valer pra várias empresas do MESMO grupo econômico. Caso real que motivou: ao cadastrar a regra de custeio da empresa 1855 (H2O Innovation, grupo Tecnomyl) o usuário levou um "Esta regra especial foi cadastrada para a empresa código 1778, não para 1855" — a trava funcionando como projetada, mas o desenho de 1 regra = 1 empresa não previa grupo. A alternativa seria duplicar a regra (uma entrada por empresa, mesmo cálculo) — pior, porque o dia que o teto mudar é preciso lembrar de mexer nas duas. **Só incluir um código novo na tupla com confirmação de que as condições são idênticas**: o cálculo não olha o código da empresa pra nada, então um código a mais aqui aplica silenciosamente o teto de um cliente na folha de outro. Pra cadastrar uma regra nova: escrever a função e registrar aqui — nada mais precisa mudar (`GET /api/importacoes-plano-saude/regras-empresa/` já reflete o registro, incluindo `tipos_lancamento`, consumido tanto pelo seletor dentro do Cadastro de Regras quanto pelo resumo só-leitura de "Nova Importação"). - **Resumo de custeio + observações, só-leitura na tela de Revisão** (`#ips-review-regra-empresa-obs`, entre o cabeçalho "Revisão" e as abas Mensalidade/Coparticipação/Auditoria/Alterações — pedido explícito do usuário, rodada 108, "assim o usuário tem certeza que está conferindo com base na regra do cliente"): mostra sempre "Tipos cobertos" + uma linha por tipo de lançamento (`Mensalidade`/`Coparticipação` — "Titular: .../Dependente: ..." ou "usa regra especial da empresa —