Procedimento Operacional Padrão das Atividades da Saúde Digital

A Saúde Digital atua como área de apoio técnico à Secretaria da Saúde, com foco na qualidade e consistência dos dados em saúde, garantindo que as informações dos sistemas assistenciais sejam corretamente transmitidas e utilizadas para fins de gestão.

Entre suas atividades está a transmissão de dados para o eSUS APS, com análise e correção de inconsistências identificadas pelo sistema, incluindo ajustes em cadastros de estabelecimentos de saúde quando necessário. A equipe também atua na transmissão de registros de vacinação para a RNDS, realizando correções cadastrais que viabilizam a conclusão dos envios.

No apoio à gestão, são utilizadas ferramentas como o Metabase e a solução interna upFiles, permitindo a análise de bases de dados e a identificação antecipada de inconsistências cadastrais. Além disso, a Saúde Digital realiza a atualização de painéis e gráficos, apoiando outros setores no acompanhamento da qualidade das informações.

Análise de Dados e Ferramentas de Apoio

Este capítulo apresenta o papel da Análise de Dados no contexto das atividades da Saúde Digital, descrevendo como informações oriundas dos sistemas de saúde são organizadas, qualificadas e transformadas em bases estruturadas para apoio à gestão, ao monitoramento e à tomada de decisão.

A Saúde Digital atua como área de suporte técnico responsável por viabilizar o uso estratégico dos dados, garantindo que relatórios e registros produzidos nos sistemas assistenciais possam ser tratados de forma padronizada e utilizados em ferramentas analíticas. Esse processo contribui para a identificação de inconsistências, o acompanhamento da qualidade da informação e a prevenção de problemas relacionados a faturamento e monitoramento de indicadores.

Neste capítulo são contextualizadas as principais ferramentas de apoio à análise de dados mantidas pela Saúde Digital, com destaque para aplicações que permitem a conversão de relatórios em estruturas compatíveis com banco de dados e sua integração com plataformas de visualização, como o Metabase. Também é apresentado o papel do upFiles nesse fluxo, enquanto solução utilizada para organizar e disponibilizar dados de forma estruturada.

Análise de Dados e Ferramentas de Apoio

Contexto da Análise de Dados na Saúde Digital

image.png

A Análise de Dados é uma atividade central no apoio técnico prestado pela Saúde Digital à Secretaria da Saúde. Seu objetivo é possibilitar que informações registradas nos sistemas assistenciais sejam compreendidas, avaliadas e utilizadas de forma qualificada para fins de gestão, monitoramento e apoio à tomada de decisão.

Os dados em saúde são produzidos em diferentes sistemas, com estruturas, regras e níveis de maturidade distintos. Por esse motivo, a simples existência de relatórios não garante, por si só, que a informação esteja pronta para uso analítico. Antes de serem utilizados em painéis e gráficos, os dados passam por etapas de verificação, organização e validação, buscando identificar inconsistências, limitações e possíveis impactos na interpretação dos resultados.

Nesse contexto, a Saúde Digital atua como área de apoio técnico, promovendo a transformação de dados brutos em bases mais estruturadas e analisáveis. Esse trabalho envolve análise crítica das fontes de dados, realização de testes de mesa e avaliação contínua da confiabilidade das informações, reconhecendo que nem todo dado disponível é, necessariamente, adequado para geração de indicadores ou visualizações gerenciais.

Graficos Metabase Saúde Digital.png

Também é importante destacar que os sistemas utilizados pela Secretaria da Saúde apresentam diferentes estágios de desenvolvimento no que se refere à geração e ao uso de dados. Enquanto alguns já permitem análises mais consolidadas, outros ainda exigem cuidados adicionais, uso indireto de relatórios ou validações mais extensas antes de qualquer aplicação analítica.

Este capítulo tem como finalidade contextualizar esse processo, apresentando a lógica da análise de dados adotada pela Saúde Digital e as ferramentas de apoio utilizadas. As páginas seguintes detalham o papel de cada ferramenta e os cuidados necessários para o uso responsável das informações, especialmente no apoio às atividades desenvolvidas por estagiários, técnicos e gestores que acessam painéis de dados.


Análise de Dados e Ferramentas de Apoio

Ferramentas de Apoio à Análise de Dados

image.png

A Análise de Dados na Saúde Digital é viabilizada por um conjunto de ferramentas que atuam de forma complementar, cada uma com um papel específico dentro do processo de organização, validação e uso das informações em saúde. Essas ferramentas oferecem suporte técnico para que os dados possam ser trabalhados de forma estruturada e analisável, respeitando as características e limitações de cada sistema de origem.

upFiles

O upFiles é uma ferramenta desenvolvida internamente pela Saúde Digital, criada para apoiar o tratamento e a padronização de dados provenientes de planilhas e relatórios. Seu principal objetivo é permitir que arquivos nos formatos CSV e XLS/XLSX sejam transformados em estruturas compatíveis com banco de dados, armazenadas em ambiente PostgreSQL.

Por meio do upFiles, é possível criar novas tabelas e inserir dados de forma controlada, permitindo tanto a sobrescrita quanto a manutenção e o acréscimo de registros, conforme a necessidade da análise. Essa funcionalidade possibilita maior reaproveitamento das bases de dados e padronização das informações ao longo do tempo.

image.png


Metabase

O Metabase é a ferramenta utilizada pela Saúde Digital para análise e visualização de dados. A partir das bases estruturadas, sejam elas oriundas do upFiles ou de conexões diretas a bancos de dados, o Metabase permite a criação de painéis, gráficos e consultas que apoiam o monitoramento e a tomada de decisão.

Os painéis disponibilizados refletem diretamente a qualidade e a estrutura das bases utilizadas, sendo fundamental compreender que a ferramenta apresenta os dados conforme estão armazenados, não substituindo a análise crítica nem os processos de validação realizados previamente.

image.png


Conexões diretas a bancos de dados

Além do uso de arquivos e bases intermediárias, a Saúde Digital também realiza conexões diretas a bancos de dados de aplicações sobre as quais possui maior controle técnico. Esse tipo de acesso permite análises mais consistentes e atualizadas, reduzindo etapas intermediárias de tratamento de dados.

Esse cenário é adotado, por exemplo, em sistemas que utilizam bancos de dados como SQL Server e PostgreSQL, associados a aplicações acompanhadas diretamente pela equipe da Saúde Digital, como o ESUSSAMU e, de forma mais limitada, o eSUS APS, sempre considerando o nível de maturidade e confiabilidade das informações disponíveis.

image.png


É importante ressaltar que, independentemente da ferramenta ou forma de acesso utilizada, a qualidade dos dados permanece como elemento central do processo de análise. A existência de ferramentas tecnológicas não elimina a necessidade de validação, testes e avaliação crítica das informações, reconhecendo que nem todo dado disponível resulta, necessariamente, em painéis ou indicadores confiáveis.

As páginas seguintes deste capítulo detalham, de forma mais aprofundada, a finalidade e o funcionamento de cada ferramenta, bem como os cuidados necessários para seu uso no apoio às atividades desenvolvidas por estagiários, técnicos e gestores.


Análise de Dados e Ferramentas de Apoio

upFiles - finalidade e papel no processo de dados

image.png

Criação de tabelas e estruturação de dados

O upFiles <https://saudedigital.sorocaba.sp.gov.br/upFiles/> é uma ferramenta desenvolvida internamente pela Saúde Digital para suprir limitações identificadas em soluções de upload de dados utilizadas de forma nativa por ferramentas de análise. Seu principal objetivo é permitir a criação de tabelas em banco de dados PostgreSQL a partir de planilhas, de forma padronizada, controlada e reutilizável, viabilizando análises posteriores no Metabase.

Diferentemente de soluções mais simples de upload, o upFiles foi concebido para trabalhar com grandes volumes de dados, sem limitação prática de tamanho de arquivo, e para permitir maior controle sobre a estrutura das tabelas criadas. Essa abordagem se mostrou necessária especialmente diante das limitações dos relatórios extraídos de sistemas como o Sistema de Gestão Pública de Saúde, que, em sua versão web, apresenta restrições significativas na organização e padronização dos dados.

Preparação prévia dos dados

Antes de iniciar qualquer processo no upFiles, é fundamental que os dados estejam organizados em formato de planilha padrão, com informações separadas por colunas, cada coluna representando um único tipo de dado. Essa organização é essencial para que o sistema consiga interpretar corretamente a estrutura do arquivo.

Em situações em que o relatório não apresenta cabeçalho adequado, o upFiles possui uma tratativa básica para identificar colunas a partir das primeiras linhas de dados. Nesses casos, colunas sem nome são criadas automaticamente e devem ser revisadas e renomeadas durante a etapa de configuração. Ainda assim, recomenda-se que os dados já sejam preparados previamente de forma estruturada, sempre que possível.

Etapa 1 – Envio do arquivo para início do processamento

O processo de criação de tabela no upFiles começa com o upload do arquivo, momento em que o sistema passa a analisar a estrutura da planilha.

image.png

Após o envio, o sistema realiza a leitura do arquivo e apresenta um preview com as primeiras 20 linhas, permitindo verificar como os dados foram interpretados antes de avançar para a configuração da tabela.

image.png

Etapa 2 – Configuração da tabela

Na etapa de configuração, o usuário define como a tabela será criada no banco de dados. Quando o objetivo é criar uma nova base, deve ser mantida a opção Criar nova tabela.

image.png

A definição do nome da tabela é obrigatória. O próprio sistema aplica automaticamente um prefixo identificador às tabelas criadas pelo upFiles, permitindo que elas sejam facilmente reconhecidas no banco de dados e listadas separadamente de outras tabelas que não se originam da ferramenta.

Chave Primária (PK)

Nesta etapa também é possível definir uma chave primária, conhecida como PK. A chave primária é um campo que identifica unicamente cada registro da tabela, sendo utilizada para evitar duplicidades e permitir atualizações controladas dos dados.

Quando o relatório não possui um identificador único confiável, o upFiles permite a criação de uma chave hash automática, solução especialmente útil em cenários em que os sistemas de origem não fornecem uma chave primária adequada.

image.png

Configuração das colunas

O upFiles utiliza, inicialmente, os nomes das colunas presentes na planilha, mas permite que esses nomes sejam editados para facilitar o uso futuro no Metabase. Para cada coluna, o sistema apresenta exemplos de valores, auxiliando na escolha do tipo de dado mais adequado.

Os tipos disponíveis incluem:

image.png

Também existe a opção de anonimização de colunas, recurso importante para atendimento à LGPD, especialmente em casos em que os dados possam ser utilizados em painéis com acesso ampliado.

Adicionalmente, é possível criar colunas customizadas com valor fixo, utilizadas em situações específicas para facilitar a integração futura entre bases distintas no Metabase.

Confirmação da estrutura da tabela

Ao final da configuração, a estrutura definida é confirmada. Nesta primeira execução, o sistema apenas cria a tabela no banco de dados, sem inserir os dados da planilha. Essa separação entre criação da tabela e carga de dados foi adotada como medida de segurança, garantindo maior controle sobre a estrutura antes da inserção efetiva das informações.

image.png

É importante destacar que a criação de tabelas no upFiles envolve conceitos de banco de dados e, por esse motivo, recomenda-se que essa etapa seja realizada por profissionais com maior familiaridade técnica ou sob orientação adequada.

As informações inseridas no sistema são registradas com identificação do responsável pelo envio e carimbo de data e hora, permitindo rastreabilidade e facilitando eventuais manutenções no banco de dados. Algumas ações, como a exclusão de tabelas, não são realizadas pela interface do upFiles e dependem de procedimentos técnicos diretos no banco de dados.

A próxima página deste capítulo apresenta a etapa de upload de dados, que consiste na inserção efetiva das informações na tabela já criada, processo consideravelmente mais simples e operacional.


Upload de Dados em tabelas estruturadas

Após a criação e validação da estrutura de uma tabela no upFiles, a próxima etapa consiste no upload dos dados propriamente ditos. Essa fase é operacionalmente mais simples do que a criação da tabela, pois utiliza uma estrutura já definida previamente, reduzindo o risco de inconsistências estruturais.

Assim como na etapa inicial de criação, o processo começa com o upload do arquivo, seguido da visualização de um preview com as primeiras linhas, permitindo uma conferência visual para garantir que os dados foram interpretados corretamente pelo sistema.

image.png

Seleção da tabela de destino

Com os dados validados visualmente, a etapa seguinte consiste em alterar a opção de configuração para Upload de Dados. Nesse momento, a interface passa a apresentar uma lista de tabelas já existentes, criadas anteriormente por meio do próprio upFiles.

image.png

Validação de compatibilidade entre planilha e tabela

Durante a etapa de configuração do upload, o upFiles realiza uma verificação automática de compatibilidade entre a estrutura da planilha enviada e a estrutura da tabela selecionada como destino. Essa validação considera, principalmente, a quantidade e a ordem das colunas, uma vez que o mapeamento dos dados ocorre por posição.

A lista exibida contempla apenas tabelas originadas pelo upFiles, facilitando a identificação correta da base que receberá os novos dados. A seleção da tabela correta é essencial para garantir a integridade das informações e a continuidade das análises futuras no Metabase.

Definição do comportamento do upload

Após selecionar a tabela de destino, o usuário deve definir como os novos dados serão tratados em relação aos registros já existentes. Existem duas possibilidades principais:

image.png

Quando a opção de adicionar aos dados existentes é selecionada, torna-se necessário definir o comportamento em caso de registros duplicados, com base na chave primária previamente configurada.

Tratamento de duplicidades

Nos casos em que um registro do novo arquivo possui a mesma chave primária de um registro já existente na tabela, o sistema permite duas abordagens:

image.png

Essa escolha deve considerar o objetivo da base de dados e a forma como os relatórios são atualizados ao longo do tempo.

Identificação e auditoria do upload

Antes da execução do upload, o sistema solicita a identificação do responsável pelo envio, por meio do CPF. Essa informação é utilizada exclusivamente para fins de rastreabilidade e auditoria, permitindo identificar quando e por quem determinado conjunto de dados foi inserido.

image.png

Após a confirmação, o processo de upload é iniciado e os dados passam a integrar a tabela selecionada, ficando disponíveis para análise no Metabase.

image.png

Uso dos dados no Metabase

Concluído o upload, o trabalho de estruturação no upFiles é finalizado. A partir desse ponto, a exploração, análise e visualização dos dados passam a ser realizadas diretamente no Metabase, por meio da criação ou atualização de painéis, gráficos e consultas.

Essa separação entre estruturação de dados e análise visual reforça a lógica adotada pela Saúde Digital, garantindo maior controle sobre as bases de dados e mais segurança no uso das informações.


Análise de Dados e Ferramentas de Apoio

Metabase: análise e visualização de dados

image.png

A análise de dados na Saúde Digital tem como objetivo transformar bases estruturadas em informações compreensíveis, capazes de apoiar o monitoramento de processos, a avaliação de resultados e a tomada de decisão. Essa análise ocorre a partir de dados previamente organizados e validados, sendo a visualização uma etapa final desse processo.

Ferramentas de Business Intelligence, conhecidas como ferramentas de BI, são utilizadas para representar dados por meio de gráficos, tabelas e painéis interativos. Essas ferramentas não produzem nem corrigem dados, mas permitem explorar informações já existentes, facilitando sua leitura por diferentes perfis de usuários, como gestores, técnicos e equipes de apoio.

Entre as ferramentas de BI mais conhecidas estão soluções amplamente difundidas no mercado, como o Power BI, além de alternativas open source. Todas compartilham o mesmo princípio, transformar dados estruturados em visualizações que auxiliam a compreensão da informação.

image.png


O Metabase no contexto da Saúde Digital

No âmbito da Saúde Digital, o Metabase é a ferramenta adotada para análise e visualização de dados. Um dos fatores que motivaram sua escolha é o fato de se tratar de uma ferramenta open source, sem custo de licenciamento para a administração pública, além de permitir integração direta com bancos de dados utilizados pelo setor.

O Metabase é utilizado para criar gráficos e painéis a partir de bases estruturadas, seja por meio de tabelas preparadas no upFiles ou por conexões diretas a bancos de dados. Ele deve ser compreendido como uma ferramenta de apoio à análise, refletindo exatamente os dados disponíveis nas bases acessadas.

image.png


Organização das informações no Metabase

Antes de criar ou acessar visualizações no Metabase, é importante entender como a ferramenta organiza os conteúdos. Essa organização facilita a navegação, a interpretação das informações e o controle de acesso.

As coleções também ajudam no controle de acesso, pois os perfis de usuários podem ser configurados para acessar apenas determinadas coleções, garantindo que cada público visualize apenas as informações adequadas.

Essa estrutura permite que o Metabase apresente informações de forma organizada e segura, desde análises simples até painéis mais completos, respeitando o perfil de acesso e a qualidade dos dados disponíveis.

image.png

Atenção à escolha da coleção ao criar painéis

Antes de criar um painel no Metabase, é importante definir em qual coleção ele será armazenado, pois essa escolha influencia diretamente a organização das análises e o controle de acesso às informações.

No Metabase, os perfis de acesso são organizados por setor, e cada perfil visualiza apenas as coleções que lhe foram atribuídas. Dessa forma, manter os painéis nas coleções corretas facilita a configuração dos acessos e garante que cada setor veja apenas as informações pertinentes.

A coleção Saúde Digital é destinada, em geral, a painéis de uso interno do setor, como análises técnicas, correções de cadastro, validações de dados e painéis em fase de teste, que não são voltados à divulgação ampla.

Já a coleção Gestão SES concentra painéis consolidados e compartilhados, utilizados por mais de um setor da Secretaria da Saúde. Nela ficam análises que atendem simultaneamente áreas como Atenção Primária, Atenção Especializada e Vigilância, como painéis de absenteísmo ou de transmissão de vacinas à RNDS.

Sempre que um painel tiver uso transversal ou interesse de múltiplos setores, a orientação é que ele seja criado ou organizado na coleção Gestão SES, facilitando o gerenciamento dos perfis de acesso e promovendo o uso seguro e consistente das informações.


Exemplo prático: criação de gráficos no Metabase a partir do zero

Embora já existam painéis consolidados sobre a transmissão de dados de vacinas para a RNDS, este exemplo parte da criação de um novo painel do zero, na coleção Gestão SES, com finalidade exclusivamente didática. O objetivo é ilustrar conceitos básicos do Metabase e a lógica de construção de gráficos, sem aprofundar em análises complexas.

image.png

image.png

É importante destacar que o Metabase, assim como outras ferramentas de Business Intelligence, não depende apenas da ferramenta em si, mas principalmente de testes, validações e análise crítica dos dados. Muitas dessas validações se assemelham ao chamado teste de mesa, amplamente utilizado na formação em Tecnologia da Informação, no qual o comportamento dos dados é analisado antes de qualquer conclusão.

O Metabase oferece diversos tipos de gráficos e visualizações, além de recursos como filtros, sumarizações, ordenações e junções entre tabelas. Neste exemplo, no entanto, será utilizada uma estrutura simples, baseada em uma única tabela, sem junções, apenas para demonstrar o funcionamento básico da ferramenta.


Estratégia de construção dos gráficos

Uma prática comum na construção de painéis é iniciar pela criação de um gráfico base, considerando todos os dados e filtros que poderão ser utilizados posteriormente. Esse gráfico inicial serve como modelo e pode ser duplicado, facilitando a criação de novas visualizações sem a necessidade de repetir configurações.


Neste exemplo, serão criados dois gráficos, ambos a partir da mesma tabela de transmissão de dados:

Essa abordagem torna o processo mais ágil e reduz a chance de inconsistências entre os gráficos do mesmo painel.


Gráfico 1: total de registros por status de integração

O primeiro gráfico tem como objetivo oferecer uma visão geral da base de dados, apresentando o total de registros existentes e sua distribuição por status de integração, como enviados, com erro ou outros estados disponíveis na tabela.

Para iniciar a construção desse gráfico, acesse o painel previamente criado e inclua um novo conteúdo. Isso pode ser feito tanto pela opção de criar uma nova pergunta quanto pela opção de adicionar um gráfico, pois ambas conduzem ao mesmo ambiente de construção de análises do Metabase.

image.png

Ao iniciar esse processo, o primeiro passo é selecionar a tabela que será utilizada como base da análise. É a partir dessa tabela que o Metabase irá explorar os dados, aplicar as sumarizações definidas e transformar as informações em visualizações gráficas.

image.png

Neste momento, você estará visualizando a tela de montagem visual de gráficos do Metabase, que utiliza uma interface intuitiva e não exige conhecimento de codificação. Essa abordagem permite que a análise seja construída de forma guiada, facilitando o uso mesmo por usuários com menor familiaridade técnica.

Nessa tela, já é possível identificar, na seção Dados, qual é a tabela que está sendo utilizada como base para a análise. Essa conferência é importante para garantir que a visualização está sendo construída a partir da fonte correta antes de avançar para as demais configurações.

Para este exemplo, os outros botões de ação disponíveis na interface podem ser ignorados neste primeiro momento. Funcionalidades como unir dados, criar colunas personalizadas, adicionar filtros para limitar os resultados, ordenar informações ou definir limite de linhas serão exploradas posteriormente, conforme o usuário se familiarizar com a ferramenta e passar a trabalhar com outros conjuntos de dados.

image.png

Prosseguindo com a criação do gráfico, ao clicar no botão Resumir, o Metabase passa a trabalhar os dados de forma agregada, ou seja, deixa de exibir os registros linha a linha e passa a resumir a informação com base em funções e agrupamentos. Essa etapa é fundamental para a construção de gráficos e indicadores, pois é nela que definimos “o que queremos contar, somar ou analisar” e “como esses dados devem ser agrupados”.

Dentro do Resumir, o primeiro passo é escolher uma função ou métrica, que define qual tipo de cálculo será aplicado sobre os dados. Entre as opções disponíveis, estão, por exemplo:

Para este exemplo, será utilizada a função Contagem de linhas, pois cada linha da tabela representa um registro de transmissão.

image.png

Após escolher a função, o Metabase permite definir por qual campo os dados serão agrupados, criando as chamadas “quebras” da análise. É nesse ponto que informamos, por exemplo, que a contagem deve ser separada por status, unidade de saúde, período ou qualquer outra categoria relevante. Essa combinação entre função e agrupamento é o que dá sentido à visualização final.

image.png

Ao combinar essa contagem com o campo Status, o Metabase passa a apresentar quantos registros existem em cada situação, como enviados, com erro ou em outros estados possíveis, oferecendo uma visão geral simples e objetiva do cenário de transmissão.

Nesse ponto, as configurações necessárias para o gráfico já estão definidas. Ainda assim, é importante destacar que, a qualquer momento, é possível clicar em Visualizar para acompanhar como o gráfico está sendo montado e verificar se o resultado corresponde ao esperado.

Antes de salvar a análise, é necessário ajustar a forma de visualização do gráfico. Para isso, clique em Visualizar e, na tela seguinte, utilize novamente a opção Visualizar para escolher o tipo de gráfico mais adequado para representar os dados, como barras, rosca ou tabela, conforme o objetivo da análise.

image.png

É recomendável utilizar o ícone de engrenagem para ajustar a forma como os dados são exibidos no gráfico. Nessa área, é possível configurar elementos visuais que facilitam a leitura e a interpretação da informação apresentada.

image.png

Com o gráfico finalizado, basta clicar em Salvar para definir um nome e, se necessário, incluir uma descrição, facilitando a identificação e o entendimento do gráfico dentro do painel e da coleção em que ele será armazenado.

image.png

Após salvar o gráfico, você será redirecionado automaticamente ao painel, onde já será possível visualizar o gráfico inserido. Observe que, na parte superior da página, o painel se encontra em modo de edição, o que faz com que os botões de ação relacionados à edição estejam visíveis.

Nesse modo, já é possível redimensionar e reposicionar os gráficos na tela, bem como adicionar filtros ao painel, que poderão ser utilizados posteriormente para refinar as visualizações. Essas funcionalidades permitem personalizar a apresentação das informações de acordo com a necessidade da análise.

As configurações de layout e de filtros do painel não serão detalhadas neste material. A proposta é que você explore essas possibilidades diretamente na ferramenta, de forma gradual, à medida que se familiariza com o uso do Metabase.

image.png


Gráfico 2: duplicação e reaproveitamento do gráfico base

Agora que o primeiro gráfico já foi criado e salvo, o próximo passo é sair do modo de edição do painel. Essa etapa é necessária porque a criação de um novo gráfico a partir de um existente ocorre por meio da edição do gráfico já criado, e não diretamente dentro do painel em modo de edição.

Com o painel em modo de visualização, selecione o gráfico já existente e acesse a opção de edição. A partir desse ponto, o Metabase permite alterar as configurações do gráfico conforme necessário, como ajustes de agrupamento, filtros ou tipo de visualização, reutilizando toda a estrutura já definida anteriormente.

image.png

Após realizar as alterações desejadas, ao clicar em Salvar, escolha a opção Salvar como novo gráfico. Dessa forma, o gráfico original é preservado, e uma nova visualização é criada a partir dele, reaproveitando a base de dados e as configurações iniciais, o que facilita a criação de novos gráficos de forma mais rápida e consistente.

image.png

Essa abordagem permite trabalhar com um gráfico base, a partir do qual novas análises podem ser geradas, mantendo padronização e reduzindo retrabalho na construção dos painéis.

image.png


Considerações sobre validação e interpretação

Este exemplo demonstra apenas o uso básico do Metabase para a criação de gráficos simples. Na prática, análises mais robustas exigem testes contínuos, avaliação cuidadosa dos resultados, possíveis uniões entre tabelas e, sempre que necessário, a comparação com outras fontes de informação, garantindo que os dados apresentados sejam consistentes e úteis para a gestão.

O Metabase deve ser compreendido como uma ferramenta de apoio à análise, e não como uma fonte automática de verdade. A qualidade das visualizações produzidas está diretamente relacionada à qualidade da base de dados utilizada e ao cuidado na definição das métricas, filtros e critérios de agrupamento adotados durante a análise.

Por esse motivo, a interpretação dos gráficos e painéis deve ser feita de forma crítica e contextualizada, considerando as limitações dos dados e o objetivo da análise. Visualizações bem construídas auxiliam a tomada de decisão, mas não substituem a análise técnica nem o conhecimento dos processos que originaram os dados.

Para aprofundamento técnico sobre o uso da ferramenta, além da realização de testes práticos pelo próprio usuário, recomenda-se a consulta ao manual oficial do Metabase, especialmente o material introdutório sobre dashboards e visualizações.

Introdução aos Dashboards do Metabase.


Análise de Dados e Ferramentas de Apoio

Fluxo de Atualização dos Painéis de Dados

Nas páginas anteriores, foram apresentados os conceitos básicos de análise de dados e o uso do Metabase como ferramenta de visualização. A partir deste ponto, o foco passa a ser a manutenção e atualização dos painéis já existentes, garantindo que as informações exibidas continuem refletindo, de forma adequada, a realidade dos dados disponíveis.

Para acessar os conteúdos específicos, utilize o menu lateral esquerdo da Estante SES, onde estão organizados, em estrutura de árvore, os painéis cuja atualização mínima é de responsabilidade da equipe da Saúde Digital. Cada painel possui sua própria página, com orientações direcionadas ao seu contexto.

A atualização dos painéis não consiste na recriação dos gráficos, mas sim na atualização das bases de dados que os alimentam. Esse processo é essencial para que gestores e equipes que utilizam os painéis tenham acesso a informações atuais, consistentes e úteis para o acompanhamento das atividades e apoio à tomada de decisão.

Em cada página, serão apresentados exemplos de painéis já existentes, indicando a origem dos dados, qual tabela deve ser atualizada no upFiles e como verificar, no Metabase, se a atualização foi realizada com sucesso. O objetivo é orientar a equipe quanto ao fluxo de atualização, sem aprofundar em decisões técnicas que podem variar conforme o tipo de dado ou sistema de origem.

É importante destacar que nem todos os painéis possuem a mesma periodicidade ou utilizam as mesmas fontes de dados. Por esse motivo, cada painel é tratado individualmente, respeitando as particularidades do sistema de origem, do formato do relatório e da finalidade da informação apresentada.


Integração de Vacinas à RNDS

Onde encontrar o painel no Metabase Metabase > Gestão SES > Integração de Vacinas à RNDS
Sistema utilizado para coleta dos dados Sistema de Gestão Pública de Saúde - SIS <https://sisweb.sorocaba.sp.gov.br>
Módulo/Local do relatório no Sistema Integração > RNDS > Integração RIA (Vacinas)
Período que deve ser atualizado Semanal, com upload realizado uma vez por semana, considerando sempre os dados da semana anterior. A atualização deve ocorrer às segundas-feiras, após a finalização da transmissão e correção das vacinas registradas na sexta-feira e no sábado.
Tabela utilizada no upFile uf_ria_rnds_vacinas
Tipo de atualização no upFile Upload de Dados > Adicionar aos dados existentes > Atualizar (sobrescrever com novos)
Como validar a atualização no Metabase

Conferir se o volume total de registros do painel aumentou de forma compatível com a quantidade de imunizações transmitidas no período analisado e, principalmente, se a distribuição por status apresenta apenas os valores esperados (É importante considerar que a exportação do SIS pode conter registros duplicados da chave primária (Código), e que esse comportamento é tratado no upFiles de acordo com o tipo de atualização selecionado durante o upload.).

 

O gráfico de Status de Integração deve concentrar os registros nos status Enviado e Erro. A presença de status como Lote não gerado ou Lote gerado indica inconsistências no processo de preparação ou envio dos dados e deve ser tratada no sistema de origem antes de novas atualizações.

Ao acessar a tela de Integração RIA (Vacinas), limpe o campo de unidade de saúde, garantindo que os dados sejam consultados de forma consolidada. Em seguida, selecione o período correspondente à semana anterior, preferencialmente utilizando o calendário do sistema para marcar o primeiro dia da semana (domingo) até o último dia (sábado).

Após definir o período, marque a opção “Exibir Enviados” e execute a busca para obter os dados de transmissão.

image.png

Essa etapa pode levar, em média, entre 5 e 10 minutos para que o sistema conclua o processamento e retorne a lista de imunizações.

Assim que os dados forem exibidos no sistema, clique no botão de exportação de dados e selecione a opção MS Excel para realizar o download do arquivo.

image.png

Após realizar o upload dos dados no upFiles, acompanhe principalmente o gráfico de Status de Integração ao longo do tempo no Metabase. Nesse gráfico, espera-se que sejam apresentados apenas registros com os status “Enviado” e “Erro”, que representam o fluxo regular de transmissão das informações.

Caso sejam identificados outros status, isso caracteriza um ponto de atenção no processo. Nessas situações, pode ser necessário realizar a correção dos dados dos pacientes, proceder com o reenvio das informações, reemitir o relatório no sistema de origem e, posteriormente, efetuar novamente o upload no upFiles, mantendo a mesma configuração já estabelecida para este painel.

É importante destacar que a extração e o upload dos dados devem ser realizados sempre considerando períodos de até uma semana. A utilização de intervalos maiores aumenta significativamente a demanda de recursos do servidor, podendo impactar o desempenho do sistema e comprometer o funcionamento de outros serviços. O uso de períodos semanais contribui para a estabilidade do ambiente e para a continuidade das operações.

image.png

O gráfico de Status de Integração ao longo do tempo tem como finalidade acompanhar o comportamento das transmissões de imunizações para a RNDS, permitindo identificar rapidamente tanto as situações esperadas quanto os pontos que demandam atenção no processo.

Status apresentados no gráfico:

Observações importantes:

Dessa forma, o comportamento esperado do gráfico é a concentração dos registros apenas nos status Enviado e Erro, sendo este último utilizado como insumo para monitoramento, análise crítica e ações de melhoria contínua no processo de registro e transmissão de imunizações.

image.png


Relatório de Agendas de Serviço

Onde encontrar o painel no Metabase

Metabase > Gestão SES > Relatório de Agendas de Serviço > Alta com Continuidade do Cuidado

Metabase > Gestão SES > Relatório de Agendas de Serviço > Atenção Primária à Saúde

Metabase > Gestão SES > Relatório de Agendas de Serviço > Policlínica Municipal Dr. Edward Maluf

 

Sistema utilizado para coleta dos dados Sistema de Gestão Pública de Saúde - SIS <https://sisweb.sorocaba.sp.gov.br>
Módulo/Local do relatório no Sistema Regulação > Relatórios > Relatório de pacientes agendados
Período que deve ser atualizado Semanal, com upload realizado uma vez por semana, considerando sempre os dados da semana anterior. A atualização deve ocorrer após o fechamento automático das faltas no sistema (preferencialmente as terças-feiras), que acontece com um intervalo de dois dias, garantindo que os registros de comparecimento e ausência estejam consolidados no momento da extração.
Tabela utilizada no upFile uf_agenda_de_servicos
Tipo de atualização no upFile Upload de Dados > Adicionar aos dados existentes > Atualizar (sobrescrever com novos)
Como validar a atualização no Metabase

A validação dos dados de agenda é realizada de forma comparativa, considerando as limitações do relatório disponível no sistema de origem. Para cada período, os dados precisam ser extraídos separadamente em dois conjuntos, pacientes recepcionados e pacientes faltosos, que juntos compõem a base utilizada para o controle de absenteísmo. A extração e o upload dos dados devem ocorrer preferencialmente às terças-feiras, garantindo que o sistema já tenha aplicado o fechamento automático das faltas.

 

Após o upload no upFiles, a validação consiste em verificar se os valores apresentados no Metabase estão coerentes com a quantidade aproximada de registros observada durante o upload, especialmente por meio do gráfico que apresenta a proporção entre atendidos e faltosos. O painel considera exclusivamente esses dois tipos de agendamento, não contemplando análises de vagas abertas ou outros estados.

Ao acessar a tela de Relatório de Pacientes Agendados, selecione o período correspondente à semana anterior, preferencialmente utilizando o calendário do sistema para marcar o primeiro dia da semana (domingo) até o último dia (sábado). Em seguida, no campo Situação, selecione a opção Recepcionado e clique em Gerar CSV para realizar o download dos dados.

image.png

Após a conclusão do primeiro download, reaproveite os demais campos já preenchidos, alterando apenas o campo Situação, selecionando a opção Faltou, e clique novamente em Gerar CSV para obter o segundo arquivo necessário à composição da análise de absenteísmo.

image.png

O sistema, por padrão, salva o arquivo com o nome ram_rel_pacientes_agendados_[data_atual]. Recomenda-se renomear os arquivos antes do upload, substituindo o termo agendados por recepcionado ou faltou, conforme o conteúdo de cada arquivo, a fim de evitar confusão durante o processo de envio ao upFiles.

É importante destacar que a extração e o upload dos dados devem ser realizados sempre considerando períodos de até uma semana. A utilização de intervalos maiores aumenta significativamente a demanda de recursos do servidor, podendo impactar o desempenho do sistema e comprometer o funcionamento de outros serviços. O uso de períodos semanais contribui para a estabilidade do ambiente e para a continuidade das operações.

image.png

É importante lembrar que o processo de obtenção dos dados para este painel ocorre em duas etapas distintas: uma extração considerando a Situação – Recepcionado e outra considerando a Situação – Faltou, sendo ambas necessárias para a composição da análise de absenteísmo.

Após o download dos arquivos, lembre-se de realizar o upload duas vezes no upFiles, uma para cada arquivo, respeitando a situação correspondente a cada conjunto de dados.

image.png


Transmissão de Vacinas a RNDS

Este capítulo apresenta o processo de transmissão de vacinas à Rede Nacional de Dados em Saúde (RNDS), no contexto das atividades da Saúde Digital, abordando conceitos gerais da integração RIA, orientações para a transmissão no sistema, correção de registros e tratamento dos erros mais comuns identificados no envio das informações de imunização.

Transmissão de Vacinas a RNDS

Contextualização

image.png


Este material tem como objetivo orientar o usuário sobre como realizar a transmissão de vacinas no SIS, especificamente:

Além do passo a passo da transmissão, são apresentadas orientações gerais sobre erros, pendências e correções que podem ocorrer durante esse processo.

O que é a transmissão de vacinas

A transmissão de vacinas é o processo pelo qual os registros de vacinação realizados no sistema municipal (SIS) são enviados para a Rede Nacional de Dados em Saúde (RNDS), por meio da Integração RIA (Vacinas).

Na prática, isso significa que todas as vacinas aplicadas nas unidades de saúde precisam ser comunicadas aos sistemas nacionais, garantindo que a informação esteja disponível de forma unificada no âmbito do SUS.

Esse envio não acontece automaticamente no momento do registro da vacina. Ele depende da execução do processo de transmissão, que reúne os registros em lotes e os envia para a RNDS.


O que é a RNDS

A Rede Nacional de Dados em Saúde (RNDS) é a plataforma do Ministério da Saúde responsável por integrar e centralizar informações de saúde de todo o país.

No contexto da imunização, a RNDS permite que:

 

Por isso, manter a transmissão de vacinas em dia e sem erros é fundamental para a qualidade da informação em saúde.


O papel do SIS na transmissão de vacinas

O SIS é o sistema onde ocorre:

Ou seja, o SIS funciona como o elo entre a aplicação da vacina na unidade de saúde e a base nacional de dados.

Somente os registros corretamente preenchidos e devidamente transmitidos são aceitos pela RNDS.


O que é a Integração RIA (Vacinas)

A Integração RIA (Vacinas) é o mecanismo utilizado pelo SIS para enviar os dados de vacinação à RNDS.

Por meio dessa integração, o sistema:

Todo esse processo é realizado em uma tela específica do sistema e exige atenção, pois falhas no preenchimento dos dados podem gerar erros na transmissão.


Por que a transmissão de vacinas é importante

A realização correta da transmissão de vacinas é essencial porque:

A ausência de transmissão ou a transmissão com erros pode resultar em pendências, inconsistências de dados e necessidade de ajustes posteriores.


Transmissão de Vacinas a RNDS

Envio de Lote (RIA / RNDS)

image.png


A transmissão de vacinas no SIS é o processo responsável por enviar os registros de vacinação do sistema municipal para a RNDS, por meio da Integração RIA (Vacinas).
Esse procedimento garante que as vacinas aplicadas sejam devidamente comunicadas aos sistemas nacionais.

Este manual orienta exclusivamente o passo a passo da transmissão, desde a seleção do período até o envio do lote.
As orientações sobre erros, pendências ou falhas na transmissão estão descritas em outro capítulo e não fazem parte deste material.


Onde realizar a transmissão no sistema

No SIS, a transmissão de vacinas é realizada no seguinte caminho de menu:

Integração → RNDS → Integração RIA (Vacinas)

image.png


Passo a passo para transmissão de vacinas

Para acessar a tela de Integração RIA (Vacinas)

  1. Acesse o SIS com seu usuário e senha.

  2. No menu superior, clique em Integração.

  3. Em seguida, clique em RNDS.

  4. Selecione a opção Integração RIA (Vacinas).

image.png


Limpar a unidade pré-selecionada

Ao abrir a tela, o sistema apresenta automaticamente uma unidade pré-definida.

  1. Localize o campo Unidade.

  2. Apague a unidade que vem preenchida automaticamente, deixando o campo em branco.

image.png

Atenção:A transmissão só deve ser realizada após a remoção da unidade pré-setada, conforme o fluxo definido pelo município.


Definir o período da transmissão

  1. Preencha o campo Data Inicial com a data de início do período que será transmitido.

  2. Preencha o campo Data Final com a data final do período.

  3. Após preencher as datas, clique em Buscar.

Aguarde o carregamento das informações.

image.png


Selecionar os registros e preparar o lote

Após a busca, o sistema exibirá a lista de registros de vacinação.

  1. Clique na opção Marcar todos para selecionar todos os registros listados.

  2. Em seguida, clique no botão Preparar Lote.

O sistema iniciará o preparo do lote. Esse processo pode levar alguns minutos.

image.png


Enviar o lote de vacinas

Após o preparo do lote ser concluído:

  1. Selecione novamente todos os registros (caso o sistema desmarque automaticamente).

  2. Clique no botão Enviar Lote.

  3. Aguarde a finalização do envio.

O processo de transmissão pode demorar cerca de 45 minutos.

Durante esse período:

image.png


Aguardar a conclusão da transmissão

image.png


Considerações finais


Transmissão de Vacinas a RNDS

Problemas e Correções

image.png


É indicado que as transmissões sejam realizadas com intervalo de dois dias anteriores a data da transmissão, para que haja tempo hábil da transmissão por parte de todas as unidades de saúde. 

Sempre que transmitido, o transmissor deve verificar os logs de erro para realizar as correções e realizar nova transmissão. Há disponibilização de um documento específico para Mensagens de Erro no Portal de Serviços - RIA.

Também foi criado, baseado nesse documento, um GPT específico que pode orientar quanto aos erros demonstrados em log (clique aqui para acessar o GPT "Assistente de Erros RNDS - Por DIEGO BISPO FERNANDES"). 


Dos erros mais comuns encontrados nas transmissões, até o presente momento (28/05/2025), as soluções são dadas a partir de:

Para os erros de dados Cadastrais, encontramos uma solução que apoia a retransmissão. Trata-se de um script Python que realiza login no sistema com as credenciais de quem estiver atarefado a realizar as correções, verifica a lista de prontuários indicada pelo corretor e que, ao executar, entra prontuário a prontuário consultando os dados na integração ao CADSUS para capturar o CPF do Paciente, Apagar o CNS e corrigir a informação de Município de Nascimento do Paciente.

Antes de fazer download do Script, é importante garantir que o seu computador tenha os recursos necessários para a execução do mesmo, para isso, preparamos os materiais abaixo:

Preparando ambiente Python para Windows

Execute no PowerShell o script abaixo

# Instalar Python 3.12
winget install --id Python.Python.3.12 -e --accept-package-agreements --accept-source-agreements

# Recarregar variáveis de ambiente
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
            [System.Environment]::GetEnvironmentVariable("Path","User")

# Criar ambiente virtual
python -m venv venv

# Ativar ambiente virtual
.\venv\Scripts\Activate

# Atualizar pip
python -m pip install --upgrade pip

# Instalar dependências necessárias
pip install requests beautifulsoup4

# Teste de imports
python - << EOF
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import time
import re
print("Ambiente Python pronto para execução do script.")
EOF

 

Preparando ambiente Python para Zorin (Linux)

Execute no Terminal o script abaixo:

#!/bin/bash

# Atualizar repositórios
sudo apt update -y

# Instalar Python 3, pip, venv e dependências de compilação
sudo apt install -y python3 python3-pip python3-venv build-essential

# Criar ambiente virtual
python3 -m venv venv

# Ativar ambiente virtual
source venv/bin/activate

# Atualizar pip
pip install --upgrade pip

# Instalar dependências necessárias
pip install requests beautifulsoup4

# Teste de imports
python - << 'EOF'
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import time
import re
print("Ambiente Python pronto para execução do script.")
EOF

 

Agora, com o ambiente preparado, você pode baixar o script no link a seguir: wsCorrigeCadastroSIS.py

import requests
from bs4 import BeautifulSoup
from datetime import datetime
import time
import re

#                    🔐 CONFIGURAÇÃO DE USUÁRIO E SENHA - EDITE AQUI 🔐
# ═══════════════════════════════════════════════════════════════════════════════════════
USUARIO_SIS = "[INFORME SEU SMS]"  # ⬅️ EDITE AQUI: Informe seu SMS
SENHA_SIS = "[INFORME SUA SENHA]"  # ⬅️ EDITE AQUI: Coloque a senha do sistema
# ═══════════════════════════════════════════════════════════════════════════════════════
#                    📋 LISTA DE PRONTUÁRIOS - COLE AQUI 📋
# ═══════════════════════════════════════════════════════════════════════════════════════
# IMPORTANTE: Cole a lista de prontuários abaixo, um por linha
# 
# REGRAS DE FORMATAÇÃO:
# - Cada prontuário deve estar em uma linha separada
# - Separe os prontuários por VÍRGULA
# - O ÚLTIMO prontuário NÃO deve ter vírgula no final
# ═══════════════════════════════════════════════════════════════════════════════════════
PRONTUARIOS = [
1035375,
459159,
760375,
995608,
1136393
]
# ═══════════════════════════════════════════════════════════════════════════════════════
#                    FIM DA LISTA DE PRONTUÁRIOS
# ═══════════════════════════════════════════════════════════════════════════════════════

# === CONFIGURAÇÕES GERAIS ===
# O sistema adiciona automaticamente o domínio ao usuário
USUARIO_PADRAO = f"{USUARIO_SIS}@sisweb.sorocaba.sp.gov.br"
SENHA_PADRAO = SENHA_SIS
URL_BASE = "https://sisweb.sorocaba.sp.gov.br"

# Valores padrão usados quando campos obrigatórios estão vazios
COD_MUNICIPIO_PADRAO = "3552205"
UF_PADRAO = "SP"
COD_PAIS_NASC_PADRAO = "10"  # Brasil (igual exemplo do formulário)

# Lista para registrar prontuários pulados por falta de CPF
PRONTUARIOS_SEM_CPF = []

# Lista para registrar prontuários processados com sucesso (melhor visibilidade em integrações)
PRONTUARIOS_CORRIGIDOS = []

# Lista para armazenar registros detalhados das atualizações (para CSV)
REGISTROS_ATUALIZACAO = []


def fazer_login(usuario=None, senha=None):
    """
    Realiza login no SISWEB e retorna (session, csrf_token).
    Se usuário/senha não forem informados, usa os padrões.
    Adiciona automaticamente @sisweb.sorocaba.sp.gov.br se o usuário não tiver domínio.
    """
    if not usuario:
        usuario = USUARIO_PADRAO
    if not senha:
        senha = SENHA_PADRAO
    
    # Adiciona o domínio automaticamente se o usuário não tiver @
    if "@" not in usuario:
        usuario = f"{usuario}@sisweb.sorocaba.sp.gov.br"

    print(f"🔐 Fazendo login no SISWEB com o usuário {usuario}...")

    session = requests.Session()
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
        "Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6",
        "Connection": "keep-alive",
        "X-Requested-With": "XMLHttpRequest",
    })

    # Página de login para obter o CSRF
    res_login = session.get(f"{URL_BASE}/login")
    res_login.raise_for_status()

    try:
        csrf_token = res_login.text.split('meta name="csrf-token" content="')[1].split('"')[0]
    except Exception:
        raise RuntimeError("Não foi possível localizar o CSRF token na tela de login.")

    # A "conta" é a parte antes do @
    conta = usuario.split("@")[0] if "@" in usuario else usuario

    payload = {
        "utf8": "✓",
        "page": "",
        "page_query": "",
        "conta": conta,
        "password": senha,
        "commit": "Entrar",
    }

    resp = session.post(
        f"{URL_BASE}/login/create",
        data=payload,
        headers={
            "Referer": f"{URL_BASE}/login",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "X-CSRF-Token": csrf_token,
        },
    )
    resp.raise_for_status()
    print("✅ Login realizado.\n")
    return session, csrf_token


def buscar_paciente_id_por_prontuario(session, csrf_token, numprontuario):
    """
    Usa o endpoint amb/paciente.json para encontrar o ID interno do paciente (DT_RowId)
    a partir do número de prontuário.
    """
    params = {
        "draw": "1",
        "order[0][column]": "1",
        "order[0][dir]": "asc",
        "start": "0",
        "length": "1",
        "search[value]": "",
        "search[regex]": "false",
        "search_operator": "",
        "amb_paciente[numprontuario]": str(numprontuario),
        # datas podem ser vazias; o filtro principal é o prontuário
        "amb_paciente[datcadastro]": "",
        "amb_paciente[datalteracao]": "",
        "workMode": "wmSearchResult",
        "oldWorkMode": "wmSearch",
        "_": str(int(time.time() * 1000)),  # timestamp em ms, igual ao navegador
    }

    resp = session.get(
        f"{URL_BASE}/amb/paciente.json",
        params=params,
        headers={
            "Accept": "application/json, text/javascript, */*; q=0.01",
            "X-CSRF-Token": csrf_token,
            "X-Requested-With": "XMLHttpRequest",
            "Referer": f"{URL_BASE}/amb/paciente",
        },
    )
    resp.raise_for_status()

    try:
        data = resp.json()
    except Exception as e:
        print(f"  ❌ Erro ao decodificar JSON de amb/paciente.json (prontuário {numprontuario}): {e}")
        print(f"  📄 Resposta bruta: {resp.text[:500]}")
        return None

    registros = data.get("data", [])
    if not registros:
        print(f"  ❌ Nenhum registro encontrado em amb/paciente.json para o prontuário {numprontuario}.")
        return None

    primeiro = registros[0]
    paciente_id = primeiro.get("DT_RowId")
    if not paciente_id:
        print(f"  ❌ Campo DT_RowId não encontrado no retorno de amb/paciente.json para o prontuário {numprontuario}.")
        return None

    print(f"  🔎 Paciente encontrado em amb/paciente.json: ID interno {paciente_id}.")
    return paciente_id


def carregar_form_edicao(session, paciente_id):
    """
    Carrega a tela de edição do paciente e devolve (soup, authenticity_token_form).
    """
    url_edit = f"{URL_BASE}/amb/paciente/{paciente_id}/edit"
    params = {
        "workMode": "wmEdit",
        "oldWorkMode": "wmBrowse",
    }

    resp = session.get(
        url_edit,
        params=params,
        headers={
            "Accept": "text/html, */*; q=0.01",
            "Referer": f"{URL_BASE}/amb/paciente",
        },
    )
    resp.raise_for_status()

    soup = BeautifulSoup(resp.text, "html.parser")

    # authenticity_token do formulário (hidden field)
    token_input = soup.find("input", {"name": "authenticity_token"})
    if not token_input or not token_input.get("value"):
        raise RuntimeError("Não foi possível localizar authenticity_token no formulário de edição.")

    authenticity_token = token_input["value"]
    return soup, authenticity_token


def extrair_payload_form(soup):
    """
    Lê todos os campos do formulário principal de paciente e devolve um dict
    pronto para ser usado no POST (patch).
    """
    form = soup.find("form")
    if not form:
        raise RuntimeError("Formulário de paciente não encontrado na página de edição.")

    payload = {}

    # Inputs
    for inp in form.find_all("input"):
        name = inp.get("name")
        if not name:
            continue

        input_type = (inp.get("type") or "").lower()

        if input_type in ["checkbox", "radio"]:
            if inp.has_attr("checked"):
                value = inp.get("value", "1")
            else:
                # Não envia checkbox/radio não marcados
                continue
        else:
            value = inp.get("value", "")

        payload[name] = value

    # Textareas
    for ta in form.find_all("textarea"):
        name = ta.get("name")
        if not name:
            continue
        payload[name] = ta.text or ""

    # Selects
    for sel in form.find_all("select"):
        name = sel.get("name")
        if not name:
            continue
        option_sel = sel.find("option", selected=True)
        if option_sel is not None:
            value = option_sel.get("value", "")
        else:
            # fallback: primeira opção
            first_opt = sel.find("option")
            value = first_opt.get("value", "") if first_opt else ""
        payload[name] = value

    return payload


def buscar_cpf_cadsus(session, csrf_token, payload, numprontuario):
    """
    Tenta buscar o CPF na integração CADSUS usando nome do paciente e nome da mãe.
    Retorna o CPF (string) ou None se não encontrar.
    """
    nome = payload.get("amb_paciente[nompaciente]", "").strip()
    mae = payload.get("amb_paciente[nommae]", "").strip()

    if not nome or not mae:
        print("  ⚠️ Não há nome e/ou nome da mãe suficientes para pesquisar no CADSUS.")
        return None

    params = {
        "amb_paciente[nome_completo_cadsus]": nome,
        "amb_paciente[nome_mae_cadsus]": mae,
        "amb_paciente[nome_pai_cadsus]": "",
        "amb_paciente[data_nascimento_cadsus]": "",
        "amb_paciente[cpf_cadsus]": "",
        "amb_paciente[cns_cadsus]": "",
        "amb_paciente[tela]": "paciente",
        "_": str(int(time.time() * 1000)),
    }

    try:
        resp = session.get(
            f"{URL_BASE}/int/data_sus/cad_sus/pesquisar",
            params=params,
            headers={
                "Accept": "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01",
                "X-CSRF-Token": csrf_token,
                "X-Requested-With": "XMLHttpRequest",
                "Referer": f"{URL_BASE}/amb/paciente",
            },
            timeout=30,
        )
        resp.raise_for_status()
    except Exception as e:
        print(f"  ❌ Erro ao consultar CADSUS para prontuário {numprontuario}: {e}")
        return None

    # A resposta é um JavaScript que injeta uma tabela HTML. Procuramos os atributos data-cpf no botão.
    # O HTML vem escapado dentro de string JS, então primeiro "desescapamos" aspas.
    texto = resp.text.replace('\\"', '"')
    # Primeiro data-cpf encontrado será usado
    m = re.search(r'data-cpf="([^"]+)"', texto)
    if not m:
        print(f"  ⚠️ Nenhum CPF encontrado no CADSUS para prontuário {numprontuario}.")
        return None

    cpf = m.group(1).strip()
    print(f"  🔄 CPF obtido via CADSUS para prontuário {numprontuario}: {cpf}")
    return cpf


def adicionar_mensagem_observacao(payload, mensagem, usuario=None):
    """
    Adiciona uma mensagem no campo de observação (infoadicional), preservando o conteúdo existente.
    Se usuario não for informado, usa o padrão.
    """
    if not usuario:
        usuario = USUARIO_PADRAO
    
    # Extrai o operador (parte antes do @)
    operador = usuario.split("@")[0] if "@" in usuario else usuario
    
    # Data e hora atual
    agora = datetime.now()
    data_hora = agora.strftime("%d/%m/%Y %H:%M")
    
    # Monta a mensagem completa
    mensagem_completa = f"{data_hora} - Atualizado com Script pelo(a) Operador(a) {operador}, resultado: {mensagem}"
    
    # Pega o conteúdo atual do campo de observação
    campo_obs = "amb_paciente[infoadicional]"
    conteudo_atual = payload.get(campo_obs, "").strip()
    
    # Se já tem conteúdo, adiciona nova linha antes da nova mensagem
    if conteudo_atual:
        novo_conteudo = f"{conteudo_atual}\n{mensagem_completa}"
    else:
        novo_conteudo = mensagem_completa
    
    payload[campo_obs] = novo_conteudo
    return mensagem_completa


def ajustar_campos_obrigatorios(payload, numprontuario):
    """
    Ajusta/valida campos obrigatórios:
    - CPF não pode ficar vazio (retorna False se faltar)
    - Município de nascimento não pode ficar vazio (preenche com padrão se necessário)
    - Endereço estruturado não pode ficar vazio (CEP + logradouro + bairro)
    
    Retorna:
    - True: todos os campos obrigatórios estão OK
    - False: faltam dados obrigatórios (CPF ou endereço)
    """
    global PRONTUARIOS_SEM_CPF

    # Município de nascimento - sempre preenche se vazio
    codmun_nasc = payload.get("amb_paciente[codmunicipionasc]", "").strip()
    if not codmun_nasc:
        print(f"  ℹ️ Município de nascimento vazio, preenchendo com padrão {COD_MUNICIPIO_PADRAO}/{UF_PADRAO}.")
        payload["amb_paciente[codmunicipionasc]"] = COD_MUNICIPIO_PADRAO
        if "amb_paciente[sglufnasc]" in payload and not payload.get("amb_paciente[sglufnasc]", "").strip():
            payload["amb_paciente[sglufnasc]"] = UF_PADRAO

    # País de nascimento (mantém se já tiver; se não tiver, preenche Brasil padrão)
    if "amb_paciente[codpaisnasc]" in payload and not payload.get("amb_paciente[codpaisnasc]", "").strip():
        payload["amb_paciente[codpaisnasc]"] = COD_PAIS_NASC_PADRAO

    cpf = payload.get("amb_paciente[numcpf]", "").strip()
    if not cpf:
        print(f"  ⚠️ CPF vazio no prontuário {numprontuario}.")
        PRONTUARIOS_SEM_CPF.append(numprontuario)
        return False

    # Endereço estruturado: município de residência, CEP, logradouro e bairro
    campos_endereco = {
        "amb_paciente[codmunicipiores]": "município de residência",
        "amb_paciente[codcep]": "CEP",
        "amb_paciente[codlogradouro]": "código do logradouro",
        "amb_paciente[codbairro]": "código do bairro",
    }

    for campo, desc in campos_endereco.items():
        if campo not in payload or not str(payload.get(campo, "")).strip():
            print(
                f"  ⚠️ Campo de endereço '{desc}' ({campo}) está vazio no prontuário {numprontuario}."
            )
            return False

    return True


def tem_cns(payload):
    """
    Verifica se o payload tem algum campo de CNS preenchido.
    Retorna True se encontrar algum campo de CNS/cartão SUS com valor não vazio.
    """
    for chave in payload.keys():
        nome_lower = chave.lower()
        # pega qualquer campo de CNS e qualquer variação de numcartao/numccartao
        if "cns" in nome_lower or "numcartao" in nome_lower:
            valor = str(payload.get(chave, "")).strip()
            if valor:  # Se tem algum valor, retorna True
                return True
    return False


def obter_cns_antes(payload):
    """
    Retorna o primeiro valor de CNS encontrado no payload (antes de limpar).
    Usado para registrar no CSV.
    """
    for chave in payload.keys():
        nome_lower = chave.lower()
        if "cns" in nome_lower or "numcartao" in nome_lower:
            valor = str(payload.get(chave, "")).strip()
            if valor:
                return valor
    return ""


def limpar_cns(payload):
    """
    Zera todos os campos relacionados a CNS:
    - Qualquer campo cujo nome contenha 'cns'
    - Campos de cartão SUS usados como CNS (ex: numcartao / numccartao)
    """
    chaves_apagadas = []

    for chave in list(payload.keys()):
        nome_lower = chave.lower()
        # pega qualquer campo de CNS e qualquer variação de numcartao/numccartao
        if "cns" in nome_lower or "numcartao" in nome_lower:
            payload[chave] = ""
            chaves_apagadas.append(chave)

    return chaves_apagadas


def salvar_paciente(session, csrf_token, paciente_id, payload):
    """
    Envia o PATCH para salvar o paciente com CNS apagado.
    """
    url_save = f"{URL_BASE}/amb/paciente/{paciente_id}"

    # Garante parâmetros de modo de trabalho e método PATCH
    payload.setdefault("utf8", "✓")
    payload["_method"] = "patch"
    payload.setdefault("workMode", "wmEdit")
    payload.setdefault("oldWorkMode", "wmBrowse")

    resp = session.post(
        url_save,
        data=payload,
        headers={
            "Accept": "text/html, */*; q=0.01",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "X-CSRF-Token": csrf_token,
            "X-Requested-With": "XMLHttpRequest",
            "Referer": f"{URL_BASE}/amb/paciente",
            "Origin": URL_BASE,
        },
    )

    if resp.status_code not in (200, 302):
        print(f"  ❌ Erro ao salvar (HTTP {resp.status_code}).")
        return False

    return True


def processar_prontuario(session, csrf_token, numprontuario, indice=None, total=None, usuario=None):
    global REGISTROS_ATUALIZACAO
    
    # Mostra progresso se indice e total foram fornecidos
    if indice is not None and total is not None:
        print(f"\n📋 Prontuário {indice}/{total} - Processando prontuário {numprontuario}...")
    else:
        print(f"\n📋 Processando prontuário {numprontuario}...")

    # Inicializa registro para CSV
    registro = {
        "prontuario": numprontuario,
        "cpf_antes": "",
        "cns_antes": "",
        "municipio_antes": "",
        "estado_antes": "",
        "cpf_depois": "",
        "cns_apagado": "Não",
        "sucesso": "Não",
        "mensagem": "",
        "data_hora": datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
        "operador": ""
    }

    try:
        paciente_id = buscar_paciente_id_por_prontuario(session, csrf_token, numprontuario)
        if not paciente_id:
            registro["mensagem"] = "Paciente não encontrado"
            registro["operador"] = usuario.split("@")[0] if usuario and "@" in usuario else (USUARIO_PADRAO.split("@")[0] if "@" in USUARIO_PADRAO else USUARIO_PADRAO)
            REGISTROS_ATUALIZACAO.append(registro)
            return

        soup, authenticity_token = carregar_form_edicao(session, paciente_id)
        payload = extrair_payload_form(soup)

        # Garante que o authenticity_token correto vá no payload
        payload["authenticity_token"] = authenticity_token

        # === CAPTURA DADOS ANTES DAS ALTERAÇÕES ===
        cpf_antes = payload.get("amb_paciente[numcpf]", "").strip()
        cns_antes = obter_cns_antes(payload)
        municipio_antes = payload.get("amb_paciente[codmunicipionasc]", "").strip()
        estado_antes = payload.get("amb_paciente[sglufnasc]", "").strip()
        
        registro["cpf_antes"] = cpf_antes
        registro["cns_antes"] = cns_antes
        registro["municipio_antes"] = municipio_antes
        registro["estado_antes"] = estado_antes

        # Verifica CPF atual (antes de buscar no CADSUS)
        cpf_atual = cpf_antes
        cpf_capturado_cadsus = None  # Rastreia se o CPF foi capturado do CADSUS
        
        # Se já tem CPF e não tem CNS, pode pular para acelerar o processo
        if cpf_atual:
            if not tem_cns(payload):
                print(f"  ⏭️ Prontuário já tem CPF e não tem CNS. Pulando para acelerar o processo.")
                registro["mensagem"] = "Prontuário já estava correto (tem CPF e não tem CNS)"
                registro["operador"] = usuario.split("@")[0] if usuario and "@" in usuario else (USUARIO_PADRAO.split("@")[0] if "@" in USUARIO_PADRAO else USUARIO_PADRAO)
                REGISTROS_ATUALIZACAO.append(registro)
                return
        
        # Se CPF estiver vazio, tenta buscar via integração CADSUS
        if not cpf_atual:
            print("  🔎 CPF vazio no cadastro, tentando buscar via integração CADSUS...")
            cpf_cadsus = buscar_cpf_cadsus(session, csrf_token, payload, numprontuario)
            if cpf_cadsus:
                payload["amb_paciente[numcpf]"] = cpf_cadsus
                cpf_capturado_cadsus = cpf_cadsus  # Marca que foi capturado do CADSUS

        # === LOGA DADOS IMPORTANTES DO CADASTRO ANTES DE QUALQUER ALTERAÇÃO ===
        def pega(nome):
            return payload.get(nome, "")

        print("  🔍 Dados atuais no cadastro:")
        print(f"    - Prontuário...............: {pega('amb_paciente[numprontuario]')}")
        print(f"    - Nome paciente............: {pega('amb_paciente[nompaciente]')}")
        print(f"    - Data nascimento..........: {pega('amb_paciente[datnascimento]')}")
        print(f"    - Nome mãe.................: {pega('amb_paciente[nommae]')}")
        print(f"    - CPF......................: {pega('amb_paciente[numcpf]')}")
        print(f"    - Cartão SUS (numcartao)...: {pega('amb_paciente[numcartao]')}")
        print(f"    - Cod logradouro...........: {pega('amb_paciente[codlogradouro]')}")
        print(f"    - Cod bairro...............: {pega('amb_paciente[codbairro]')}")
        print(f"    - CEP......................: {pega('amb_paciente[codcep]')}")
        print(f"    - Cod munic. nascimento....: {pega('amb_paciente[codmunicipionasc]')}")
        print(f"    - UF nascimento............: {pega('amb_paciente[sglufnasc]')}")

        # Ajusta campos obrigatórios (sempre preenche município de nascimento se vazio)
        campos_ok = ajustar_campos_obrigatorios(payload, numprontuario)
        
        # Se faltar CPF ou endereço, ainda tenta salvar pelo menos o município de nascimento
        if not campos_ok:
            cpf = payload.get("amb_paciente[numcpf]", "").strip()
            tem_endereco_completo = all([
                payload.get("amb_paciente[codmunicipiores]", "").strip(),
                payload.get("amb_paciente[codcep]", "").strip(),
                payload.get("amb_paciente[codlogradouro]", "").strip(),
                payload.get("amb_paciente[codbairro]", "").strip(),
            ])
            
            if not cpf:
                # Falta CPF - adiciona mensagem e tenta salvar pelo menos município de nascimento
                usuario_para_msg = usuario if usuario else USUARIO_PADRAO
                mensagem = adicionar_mensagem_observacao(
                    payload, 
                    "sem sucesso por dados insuficientes para coleta de CPF via integracao",
                    usuario=usuario_para_msg
                )
                print(f"  📝 Mensagem adicionada no campo de observacao: {mensagem}")
                print("  ⚠️ Tentando salvar pelo menos municipio de nascimento...")
                
                # Tenta salvar mesmo sem CPF completo (só município de nascimento)
                ok = salvar_paciente(session, csrf_token, paciente_id, payload)
                registro["cpf_depois"] = ""
                registro["cns_apagado"] = "Sim" if cns_antes else "Não"
                registro["sucesso"] = "Parcial" if ok else "Não"
                registro["mensagem"] = "sem sucesso por dados insuficientes para coleta de CPF via integracao"
                registro["operador"] = usuario_para_msg.split("@")[0] if "@" in usuario_para_msg else usuario_para_msg
                REGISTROS_ATUALIZACAO.append(registro)
                if ok:
                    print(f"  ✅ Municipio de nascimento salvo com sucesso para o prontuario {numprontuario}.")
                else:
                    print(f"  ❌ Falha ao salvar prontuario {numprontuario}.")
                return
            
            elif not tem_endereco_completo:
                # Falta endereço - não salva para não quebrar o cadastro
                print("  ⏭️ Prontuario pulado por falta de informacao de endereco obrigatoria.")
                registro["mensagem"] = "Pulado por falta de informação de endereço obrigatória"
                registro["operador"] = usuario.split("@")[0] if usuario and "@" in usuario else (USUARIO_PADRAO.split("@")[0] if "@" in USUARIO_PADRAO else USUARIO_PADRAO)
                REGISTROS_ATUALIZACAO.append(registro)
                return
        
        # Limpa CNS (inclui numcartao)
        chaves_cns = limpar_cns(payload)
        cns_foi_apagado = len(chaves_cns) > 0
        if not chaves_cns:
            print("  ℹ️ Nenhum campo de CNS/cartao SUS encontrado para limpar (talvez ja esteja vazio).")
        else:
            print(f"  ✂️ Campos CNS/cartao SUS apagados: {', '.join(chaves_cns)}")

        # Salva
        ok = salvar_paciente(session, csrf_token, paciente_id, payload)
        if ok:
            # Adiciona mensagem de sucesso no campo de observação
            usuario_para_msg = usuario if usuario else USUARIO_PADRAO
            cpf_final = payload.get("amb_paciente[numcpf]", "").strip()
            
            # Monta a mensagem de sucesso
            if cpf_capturado_cadsus:
                # CPF foi capturado do CADSUS
                mensagem_texto = f"atualizacao realizada com sucesso, CNS apagado e inserido CPF {cpf_capturado_cadsus} capturado pela integração CADSUS"
            else:
                # CPF já existia no cadastro
                mensagem_texto = f"atualizacao realizada com sucesso, CNS apagado. CPF {cpf_final} já estava no prontuário"
            
            mensagem = adicionar_mensagem_observacao(
                payload,
                mensagem_texto,
                usuario=usuario_para_msg
            )
            print(f"  📝 Mensagem de sucesso adicionada no campo de observacao: {mensagem}")
            
            # Salva novamente com a mensagem de sucesso
            salvar_paciente(session, csrf_token, paciente_id, payload)
            
            print(f"  ✅ Salvamento reportado como sucesso pelo servidor para o prontuario {numprontuario}.")
            # Marca como corrigido (para consumo por integracoes / API)
            global PRONTUARIOS_CORRIGIDOS
            PRONTUARIOS_CORRIGIDOS.append(numprontuario)
            
            # Atualiza registro para CSV
            registro["cpf_depois"] = cpf_final
            registro["cns_apagado"] = "Sim" if cns_foi_apagado else "Não"
            registro["sucesso"] = "Sim"
            registro["mensagem"] = mensagem_texto
            registro["operador"] = usuario_para_msg.split("@")[0] if "@" in usuario_para_msg else usuario_para_msg
        else:
            print(f"  ❌ Falha ao salvar prontuario {numprontuario} (HTTP diferente de 200/302).")
            # Atualiza registro para CSV
            registro["cpf_depois"] = payload.get("amb_paciente[numcpf]", "").strip()
            registro["cns_apagado"] = "Sim" if cns_foi_apagado else "Não"
            registro["sucesso"] = "Não"
            registro["mensagem"] = "Falha ao salvar (erro HTTP)"
            registro["operador"] = usuario.split("@")[0] if usuario and "@" in usuario else (USUARIO_PADRAO.split("@")[0] if "@" in USUARIO_PADRAO else USUARIO_PADRAO)
        
        # Adiciona registro ao CSV
        REGISTROS_ATUALIZACAO.append(registro)

        # Pequena pausa entre pacientes
        time.sleep(1)

    except Exception as e:
        print(f"  ❌ Erro ao processar prontuário {numprontuario}: {e}")
        registro["mensagem"] = f"Erro ao processar: {str(e)}"
        registro["operador"] = usuario.split("@")[0] if usuario and "@" in usuario else (USUARIO_PADRAO.split("@")[0] if "@" in USUARIO_PADRAO else USUARIO_PADRAO)
        REGISTROS_ATUALIZACAO.append(registro)


def gerar_txt_relatorio(nome_arquivo=None, operador=None):
    """
    Gera um arquivo TXT simples com o relatório de atualizações.
    Formato do nome: YYYY-MM-DD HHMM ws Corrige CPF SIS [operador].txt
    Conteúdo: Prontuário e status de sucesso (similar ao terminal)
    """
    if not REGISTROS_ATUALIZACAO:
        print("\n⚠️ Nenhum registro para gerar relatório.")
        return
    
    if not nome_arquivo:
        agora = datetime.now()
        # Formato: YYYY-MM-DD HHMM (data ao contrário + hora sem dois pontos)
        data_hora = agora.strftime("%Y-%m-%d %H%M")
        
        # Extrai o operador (parte antes do @) se não foi informado
        if not operador:
            # Tenta pegar do primeiro registro ou usa padrão
            if REGISTROS_ATUALIZACAO and REGISTROS_ATUALIZACAO[0].get('operador'):
                operador = REGISTROS_ATUALIZACAO[0]['operador']
            else:
                operador = USUARIO_SIS
        
        nome_arquivo = f"{data_hora} ws Corrige CPF SIS {operador}.txt"
    
    try:
        with open(nome_arquivo, 'w', encoding='utf-8') as txtfile:
            # Escreve cabeçalho
            txtfile.write("=" * 60 + "\n")
            txtfile.write("RELATÓRIO DE ATUALIZAÇÕES\n")
            txtfile.write(f"Operador: {operador}\n")
            txtfile.write(f"Data/Hora: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n")
            txtfile.write("=" * 60 + "\n\n")
            
            # Agrupa por status
            sucesso = []
            falha = []
            outros = []
            
            for registro in REGISTROS_ATUALIZACAO:
                prontuario = registro['prontuario']
                status = registro['sucesso']
                
                if status == "Sim":
                    sucesso.append(prontuario)
                elif status == "Não":
                    falha.append(prontuario)
                else:
                    outros.append((prontuario, status))
            
            # Escreve prontuários com sucesso
            if sucesso:
                txtfile.write("✅ PRONTUÁRIOS ATUALIZADOS COM SUCESSO:\n")
                for p in sucesso:
                    txtfile.write(f"  - {p}\n")
                txtfile.write(f"\nTotal: {len(sucesso)} prontuário(s)\n\n")
            
            # Escreve prontuários com falha
            if falha:
                txtfile.write("❌ PRONTUÁRIOS NÃO ATUALIZADOS:\n")
                for p in falha:
                    txtfile.write(f"  - {p}\n")
                txtfile.write(f"\nTotal: {len(falha)} prontuário(s)\n\n")
            
            # Escreve outros status (parcial, pulado, etc)
            if outros:
                txtfile.write("⚠️ OUTROS STATUS:\n")
                for p, status in outros:
                    txtfile.write(f"  - {p} ({status})\n")
                txtfile.write(f"\nTotal: {len(outros)} prontuário(s)\n\n")
            
            # Resumo final
            txtfile.write("=" * 60 + "\n")
            txtfile.write(f"RESUMO:\n")
            txtfile.write(f"  Total processado: {len(REGISTROS_ATUALIZACAO)}\n")
            txtfile.write(f"  Sucesso: {len(sucesso)}\n")
            txtfile.write(f"  Falha: {len(falha)}\n")
            if outros:
                txtfile.write(f"  Outros: {len(outros)}\n")
            txtfile.write("=" * 60 + "\n")
        
        print(f"\n📄 Relatório TXT gerado com sucesso: {nome_arquivo}")
        print(f"   Total de registros: {len(REGISTROS_ATUALIZACAO)}")
        return nome_arquivo
    except Exception as e:
        print(f"\n❌ Erro ao gerar relatório TXT: {e}")
        return None


def main():
    global REGISTROS_ATUALIZACAO
    REGISTROS_ATUALIZACAO = []  # Limpa registros anteriores
    
    inicio = datetime.now()
    print(f"🚀 Início da execução: {inicio.strftime('%d/%m/%Y %H:%M:%S')}")

    session, csrf_token = fazer_login()

    # Acessa uma vez a tela de cadastro (como no uso manual)
    try:
        session.get(
            f"{URL_BASE}/amb/paciente",
            headers={
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
                "Referer": f"{URL_BASE}/desktop",
            },
        )
    except Exception:
        # Não é crítico, segue o fluxo mesmo assim
        pass

    total_prontuarios = len(PRONTUARIOS)
    for indice, num in enumerate(PRONTUARIOS, start=1):
        processar_prontuario(session, csrf_token, num, indice=indice, total=total_prontuarios, usuario=USUARIO_PADRAO)

    # Resumo de prontuários não alterados por falta de CPF
    if PRONTUARIOS_SEM_CPF:
        print("\n⚠️ Prontuários NÃO alterados por estarem sem CPF:")
        for p in PRONTUARIOS_SEM_CPF:
            print(f"  - {p}")
    else:
        print("\n✅ Nenhum prontuário foi pulado por falta de CPF.")

    # Gera relatório TXT
    operador_txt = USUARIO_SIS  # Usa o operador configurado
    gerar_txt_relatorio(operador=operador_txt)

    fim = datetime.now()
    duracao = (fim - inicio).total_seconds()
    print(f"\n🏁 Fim da execução em {duracao:.1f} segundos.")


def run_batch(prontuarios, usuario=None, senha=None):
    """
    Executa a correção para uma lista de prontuários e retorna um resumo em dict.
    Pensado para uso via API / interface web.
    """
    global PRONTUARIOS_SEM_CPF, PRONTUARIOS_CORRIGIDOS, REGISTROS_ATUALIZACAO
    PRONTUARIOS_SEM_CPF = []
    PRONTUARIOS_CORRIGIDOS = []
    REGISTROS_ATUALIZACAO = []

    inicio = datetime.now()
    session, csrf_token = fazer_login(usuario=usuario, senha=senha)

    # Acessa uma vez a tela de cadastro (como no uso manual)
    try:
        session.get(
            f"{URL_BASE}/amb/paciente",
            headers={
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
                "Referer": f"{URL_BASE}/desktop",
            },
        )
    except Exception:
        pass

    total_prontuarios = len(prontuarios)
    usuario_para_processar = usuario if usuario else USUARIO_PADRAO
    for indice, num in enumerate(prontuarios, start=1):
        processar_prontuario(session, csrf_token, num, indice=indice, total=total_prontuarios, usuario=usuario_para_processar)

    fim = datetime.now()
    duracao = (fim - inicio).total_seconds()

    return {
        "total": len(prontuarios),
        "corrigidos": PRONTUARIOS_CORRIGIDOS,
        "nao_alterados_sem_cpf": PRONTUARIOS_SEM_CPF,
        "duracao": duracao,
    }


if __name__ == "__main__":
    main()

Transmissão de Fichas para o ESUSAPS

Transmissão de Fichas para o ESUSAPS

Como Transmitir as Fichas do ESUS

image.png


Este manual tem como objetivo orientar, de forma simples e clara, o processo inicial de transmissão de fichas no e-SUS APS, especificamente a etapa de exportação dos dados a partir do SISWEB.

Nesse procedimento, os dados registrados no SISWEB são exportados em arquivos, obedecendo os padrões LEDI estabelecidos pelo Ministério da Saúde, que posteriormente serão importados no e-SUS APS, permitindo a consolidação da produção do período selecionado.

A correta realização dessa etapa é fundamental para garantir que as informações sejam organizadas e preparadas adequadamente para envio. As orientações sobre erros, inconsistências ou falhas no processo serão abordadas em outro capítulo deste material.


Onde será realizado o processo no SIS

Após entrar no sistema, na aba de pesquisa, pesquise pelo módulo ESUS e nele você irá encontrar Exportar dados para o e-SUS.

Captura de tela de 2026-01-05 14-42-44.png

Antes de começar o passo a passo, é importante lembrar que você terá que selecionar qual ficha você irá exportar. Elas ficarão do lado esquerdo na tela em uma espécie de "menu":

image.png

Vale lembrar também que, TODAS as fichas devem ser exportadas.


Passo a Passo para exportar as fichas

   Ao abrir a tela de exportação, localize e preencha os campos:

A Exportação deve ser realizada sempre considerando como data incial o primeiro dia do ano e a data final como 01 dia útil anterior ao dia da extração.

Clique no ícone de pesquisa. Em seguida, o sistema exibirá os arquivos referentes ao período selecionado. Verifique se os resultados correspondem às datas informadas.

image.png

Importante: Cuidado ao colocar a data, pois dependendo do intervalo de tempo, o sistema pode demorar tanto para procurar como para exportar, tendo risco de dar erro ao exportar, procure colocar um baixo intervalo de tempo, a não ser que você faça exportações de forma recorrente, neste caso dificilmente dará problema. 

Após isso clique no botão de download que está como Gerar Arquivo, assim será feita a exportação das fichas.

Agora vamos para o passo a passo das importações.

Passo a Passo para importar as fichas

Após a exportação dos dados no SISWEB, é necessário realizar a importação dos arquivos no e-SUS APS <https://esusaps.sorocaba.sp.gov.br/>.

Essa etapa permite que as fichas exportadas sejam recebidas pelo sistema, possibilitando a consolidação da produção e a verificação de registros válidos ou com inconsistências.

O passo a passo a seguir orienta como acessar o módulo correto e importar os arquivos gerados anteriormente.

Tela para Login:

image.png

Conferência das fichas importadas

A conferência deve ser feita sempre após cada importação, garantindo a qualidade das informações transmitidas.

Logo após fazer o login, certifique que está no módulo Transmissão de Dados, se não estiver basta abrir o menu da esquerda e mudar.

image.png

Na Transmissão de dados, selecione a aba de Recebimento e selecione para importar os arquivos.

image.png


Relatório de Inconsistências

Após a importação, o sistema exibirá os arquivos recebidos, apresentando a quantidade de fichas em cada situação:

Essa separação permite identificar rapidamente se há necessidade de correção.

Após a importação dos arquivos, é indispensável realizar a conferência final das fichas recebidas.
Verifique se o total de registros importados corresponde ao volume esperado para o período transmitido e se não houve importação duplicada do mesmo arquivo.

O sistema apresentará a separação entre fichas válidas e fichas inválidas. Quando houver fichas inválidas, isso indica a presença de inconsistências que impedem a aceitação dos registros. Nesses casos, estará disponível a opção de gerar o Relatório de Inconsistências, que detalha os motivos pelos quais as fichas não foram importadas corretamente.

A análise desse relatório é fundamental para a correção dos registros e posterior retransmissão dos dados. O processo de correção das inconsistências será abordado em um capítulo específico deste manual.

O processo de transmissão não deve ser considerado finalizado sem a conferência das fichas importadas. A ausência dessa verificação pode resultar em perda de produção ou no envio de dados incompletos ou inconsistentes.

image.png

Ao clicar no ícone localizado à direita da linha do arquivo importado, é possível gerar o relatório contendo apenas as fichas inválidas, facilitando a análise e a correção dos registros.

A geração desses relatórios é uma etapa fundamental do processo de transmissão, pois garante que todas as informações enviadas estejam corretas e aptas para consolidação da produção.

No próximo capítulo, será apresentado o passo a passo para analisar os Relatórios de Inconsistências, corrigir as informações no sistema e realizar um novo envio das fichas corrigidas.


Transmissão de Fichas para o ESUSAPS

Correção de Erros e Inconsistências

image.png


 

Este manual tem como objetivo orientar, de forma simples e clara, o processo de identificação e correção de erros e inconsistências encontrados após a transmissão das fichas do e-SUS APS.

Durante a transmissão, algumas fichas podem ser consideradas inválidas. Nesses casos, o sistema disponibiliza um relatório de erros que deve ser analisado para que as correções sejam realizadas corretamente no SISWEB, garantindo a integridade das informações enviadas ao Ministério da Saúde.


Onde realizar o processo no sistema


Passo a passo para correção de erros e inconsistências

1. Identificar fichas inválidas no e-SUS APS

Após realizar a transmissão das fichas no e-SUS APS, verifique a coluna “Fichas válidas/inválidas”.

image.png


2. Baixar o relatório de inconsistências

  1. Clique no botão de download (image.png) disponível na linha da transmissão.

  2. Será gerado um arquivo de relatório contendo as inconsistências encontradas.

image.png



3. Organizar os relatórios 

Para facilitar a visualização e o controle, recomenda-se organizar os arquivos em pastas no Google Drive.

  1. Acesse o Google Drive.

  2. Clique em Novo > Nova pasta.
    image.png
    image.png

  3. Utilize o padrão de nomeação: Ano.Mês.Dia (exemplo: 2026.01.05).
    image.png


  1. Dentro da pasta criada:

    • Salve as fichas baixadas do SISWEB.

    • Crie uma subpasta chamada Relatórios.

    • Armazene nela todos os relatórios de inconsistências do e-SUS APS. Para saber mais sobre como transmitir as fichas, acesse: Como transmitir as fichas do ESUS.


4. Abrir e analisar o relatório

  1. Localize o arquivo do relatório.

  2. Clique com o botão direito e selecione Abrir com > Planilhas Google.

image.png

Ao abrir a planilha, concentre-se apenas nas seguintes colunas:

Essas informações são suficientes para identificar o que precisa ser corrigido.


5. Liberar fichas para edição no SISWEB

  1. Acesse o SISWEB.

  2. Pesquise pela opção “Liberar Fichas para Edição”.

image.png

  1. Clique em Adicionar (image.png).
    image.png

  2. Informe todos os UUIDs que serão corrigidos.

  3. Clique em Salvar (image.png).

⚠️ Atenção: esse processo pode levar alguns minutos, dependendo da quantidade de UUIDs informados.


6. Localizar a ficha para correção

  1. No SISWEB, pesquise por “e-SUS Ficha”.

  2. Selecione o tipo de ficha correspondente ao erro (ex.: Ficha de Procedimentos, Atendimento Individual, etc.).

image.png

  1. Na tela da ficha:

    • Clique em Limpar (image.png).

    • Informe o UUID dado serializado.

    • Clique em Pesquisar (image.png).

image.png


7. Correção de erro de CNS do profissional (exemplo comum)

Quando o erro estiver relacionado ao CNS do profissional:

  1. Identifique o CNS informado na ficha.

  2. Consulte o cadastro do profissional no CNES.

Caso o profissional não seja encontrado, solicite por e-mail:

⚠️ Se o profissional não possuir cadastro no CNES, a unidade deverá solicitar o cadastramento oficial.

image.png


8. Ajustar os dados e salvar

  1. Após corrigir as informações necessárias na ficha:

    • Revise todos os campos obrigatórios.

    • Clique em Salvar.

  2. Repita o procedimento para todos os UUIDs listados no relatório.


Considerações finais

A correção das inconsistências é fundamental para garantir a qualidade das informações enviadas ao Ministério da Saúde. Sempre que houver fichas inválidas, siga este procedimento antes de realizar uma nova transmissão.

Em caso de dúvidas ou erros não previstos neste manual, entre em contato com a equipe de suporte da Saúde Digital.


Cadastro de Endereçamento no SIS


Cadastro de Endereçamento no SIS

Contextualização

image.png


Este manual foi elaborado para orientar o uso da funcionalidade de Endereçamento do Sistema SIS, responsável pelo cadastro e manutenção de informações como bairros, logradouros, vínculo entre bairro e logradouro e CEP.

O correto preenchimento desses dados é essencial para garantir a padronização dos endereços utilizados no sistema, impactando diretamente a qualidade dos cadastros, atendimentos e consultas realizadas pelas unidades de saúde.


Introdução

   Este manual tem como objetivo orientar o cadastro de endereçamento no sistema SIS, abrangendo as seguintes informações:

Para facilitar, siga a ordem: Bairro -> Logradouro -> Logradouro do Bairro -> CEP

Esse cadastro é utilizado tanto para a cidade de Sorocaba quanto para outros municípios da região, como Alumínio, e é fundamental para garantir o correto registro de endereços nos atendimentos e cadastros do sistema.

O procedimento é geralmente realizado pela equipe de TI ou administrativa, e este material foi elaborado pensando em usuários sem familiaridade prévia com o SIS.


Onde encontrar o cadastro no sistema

Após acessar o sistema SIS com seu usuário e senha, os cadastros de endereçamento ficam disponíveis no seguinte caminho:

Menu → Pesquisa → Endereçamento

image.png

Dentro da área de Endereçamento, você encontrará as seguintes abas:

Antes de começar os cadastros, siga a ordem sugerida anteriormente, verifique se o bairro ou logradouro já existe no sistema antes de criar um novo cadastro, evitando duplicidades. E verifique se a cidade está correta.
Na maioria dos cadastros, a cidade utilizada será Sorocaba, podendo variar conforme a necessidade.


Cadastro de Endereçamento no SIS

Cadastro de Bairro

image.png


Nesta página, você aprenderá como realizar o cadastro de bairro no SISWEB.

Esse é o primeiro passo do processo de endereçamento e deve sempre ser realizado antes dos demais cadastros, pois o bairro será utilizado nas etapas seguintes, como cadastro de endereço, atendimentos e consultas.

O preenchimento correto dessas informações garante a padronização dos endereços no sistema, evita inconsistências e reduz problemas em registros futuros.

O procedimento é simples e pode ser realizado mesmo por usuários que não possuem familiaridade com o SIS, desde que sigam o passo a passo descrito abaixo.

Atenção – Validação obrigatória antes de cadastrar

Antes de criar um novo bairro, é obrigatório verificar se ele já não está cadastrado no sistema.

Essa validação evita:

   • Cadastro de bairros duplicados
   • Diferenças de grafia para o mesmo bairro
   • Problemas na padronização dos endereços

Para realizar essa verificação:

   • Utilize sempre o botão Localizar (ícone de lupa)
   • Pesquise pelo nome do bairro antes de clicar em Adicionar

Somente prossiga com o cadastro se tiver certeza de que o bairro ainda não existe no sistema.


Etapa inicial

Esta etapa serve para criar o bairro que será utilizado nos cadastros de endereço.

Na maioria dos casos, o município será Sorocaba, mas o cadastro também pode ser utilizado para bairros de outros municípios da região, quando necessário.

Passo a passo

Vamos iniciar o cadastro acessando o módulo Endereçamento e a tela “Bairro”.

image.png

Conforme recomendação anterior, antes de iniciar o cadastro, clique no botão Localizar (ícone de lupa) e, na lista que for carregada, verifique se o bairro já está cadastrado, para evitar cadastros duplicados.

image.png

Caso seja confirmado que não há cadastro do bairro, clique em Adicionar.

image.png

O cadastro de bairros é relativamente simples, sendo necessário apenas garantir o preenchimento dos dados mínimos, que são Município e Bairro.

image.png

Os demais parametros podem ser mantidos como estão.

Pronto, você acabou de cadastrar um bairro e pode prosseguir a proxima etapa.


Cadastro de Endereçamento no SIS

Cadastro de Logradouro

Após a conclusão do cadastro de bairro, o próximo passo do processo de endereçamento no sistema SIS é o cadastro de logradouro.

Nesta etapa, são registrados os nomes das ruas, avenidas, travessas, alamedas, entre outros tipos de vias, que posteriormente poderão ser vinculados aos bairros cadastrados.
Esse cadastro é essencial para a correta composição dos endereços utilizados no sistema.

O procedimento é simples e deve ser realizado com atenção, garantindo que o nome do logradouro esteja correto e padronizado, evitando duplicidades e inconsistências nos cadastros.


Cadastro de Logradouro

O logradouro corresponde ao nome da rua, avenida, alameda, travessa, entre outros.

   Na maioria dos casos, o município será Sorocaba, mas também pode ser usado para outras cidades da região.

Passo a passo

  1. Acesse a aba Logradouro.

    image.png

  2. Clique em Novo ou Adicionar.

  3. Preencha os campos obrigatórios:

    • Cidade: cidade desejada.

    • Tipo de logradouro: Rua, Avenida, Travessa, etc.

    • Nome do logradouro: nome completo da via.

  4. Verifique se os dados estão corretos.

  5. Clique em Salvar.

image.png

O código do "Tipo" muda de acordo se é rua, avenida, travessa. Em seguida uma lista com os tipos e seus respectivos códigos.


Cadastro de Endereçamento no SIS

Cadastro de Logradouro do Bairro

Após o cadastro do logradouro, o próximo passo no processo de endereçamento no sistema SIS é o vínculo entre o bairro e o logradouro, realizado por meio do cadastro de Logradouro do Bairro.

Nesta etapa, o sistema associa o logradouro previamente cadastrado ao bairro correspondente, permitindo que essa relação seja utilizada corretamente nos cadastros de endereço.
Normalmente, esse vínculo é feito entre o bairro e o logradouro recém-criados, mas também pode ser utilizado para associações já existentes no sistema.

A realização correta desse procedimento é essencial para garantir que os endereços fiquem completos, organizados e disponíveis para uso nas demais funcionalidades do sistema.


Cadastro de Logradouro do Bairro

Nesta tela, o sistema faz o vínculo entre o logradouro e o bairro.

Normalmente, será vinculado o logradouro recém-criado ao bairro recém-criado.

Passo a passo

  1. Acesse a aba Logradouro do Bairro.

    image.png

  2. Clique em Novo ou Adicionar.

  3. Preencha os campos:

    • Cidade: selecione a cidade.

    • Bairro: selecione o bairro desejado.

    • Logradouro: selecione o logradouro correspondente.

  4. Confira se o vínculo está correto.

  5. Clique em Salvar.

image.png

O número inicial e final deve ser colocado caso o logradouro seja "Avenida". Em caso de dúvida perguntar para seu superior.


Cadastro de Endereçamento no SIS

Cadastro de CEP

Após a realização do vínculo entre bairro e logradouro, o último passo do processo de endereçamento no sistema SIS é o cadastro de CEP.

Nesta etapa, o CEP é associado à cidade, ao bairro e ao logradouro, garantindo que o endereço fique completo e corretamente padronizado para utilização nos cadastros e atendimentos do sistema.

Além do cadastro, essa funcionalidade também permite a consulta de CEPs já registrados, facilitando a verificação de informações e evitando duplicidades.
O correto preenchimento desses dados é essencial para assegurar a consistência e a confiabilidade das informações de endereço no SIS.


Cadastro de CEP

O cadastro de CEP serve para vincular o CEP à cidade, bairro e logradouro, garantindo que o endereço fique completo e padronizado no sistema.

Além do cadastro, essa aba também pode ser utilizada para consulta de CEPs já existentes.

Passo a passo para cadastro

  1. Acesse a aba CEP.

    image.png

     

  2. Clique em Novo ou Adicionar.

  3. Preencha os campos:

    • CEP: informe o número do CEP.

    • Cidade: normalmente Sorocaba.

    • Bairro: selecione o bairro (geralmente o recém-criado).

    • Logradouro: selecione o logradouro correspondente.

  4. Verifique se todas as informações estão corretas.

  5. Clique em Salvar.

image.png


Consulta de CEP

A aba CEP também pode ser utilizada para consultar endereços já cadastrados. Basta apenas filtrar de acordo com a pesquisa que você deseja.

Pode filtrar apenas por bairro para ver os logradouros que possuem cadastrados e o respectivos CEPs ou filtra por bairro e logradouro para ver qual CEP está cadastro sem ter que ver a lista inteira de logradouros

image.png

Com a conclusão dessas etapas, o cadastro de endereçamento estará disponível para uso em todo o sistema SIS, garantindo maior padronização e qualidade das informações.