Logo DATASUS

Um recurso deveras interessante que os sistemas da área da Saúde devem possuem é a possibilidade de consultar o número do Cartão Nacional de Saúde (CNS, o famoso Cartão SUS) através da própria aplicação, não é verdade?

Há um tempo atrás, não havia um meio oficial de se fazer tal integração. Até cheguei a fazer, por meios ~não oficiais~, funcionando por um bom tempo até que essa solução parou de funcionar.

Agora temos um WebService oficial que podemos consumir para consultar o Cadastro Nacional do SUS.

Vou mostrar como consumí-lo na plataforma .NET da Microsoft, utilizando o C#. Você vai precisar de:

- MS Visual Studio (2010 ou acima)
- MS .NET Framework 4.0 ou acima
- Conexão com a Internet

Quanto ao ambiente do DATASUS, vou utilizar os endereços, parâmetros de consulta e credenciais de HOMOLOGAÇÃO, que estão disponíveis de forma pública através da documentação em: http://datasus.saude.gov.br/interoperabilidade/catalogo-de-servicos (PDF). Para utilizar em ambiente de produção, o site apresenta quais os requesitos que devem ser cumpridos.

Abra o Visual Studio e inicie um novo projeto. No meu caso, optei por uma Biblioteca de Classes, que permitirá que utilizemos esta rotina em outros projetos, reaproveitanto código. Renomeie o “Class1.cs” para um nome sugestivo.

No projeto, adicione uma Service Reference, colocando na URL o seguinte endereço: https://servicoshm.saude.gov.br/cadsus/CadsusService/v5r0?wsdl

Coloquei o sugestivo nome de “SvcAcessoDATASUS” como o namespace que irá conter as classes geradas através do WebService informado.

Service Reference para o serviço do CADSUS

No arquivo de configuração, serão criadas algumas entradas que correspondem ao “binding”, como será feito o acesso ao serviço pelas classes proxy criadas automaticamente, que pode ser através de SOAP 1.2 ou 1.0. Fiquemos com o 1.2. Veja um exemplo de como criei o meu arquivo de configuração:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="EndpointConsultaCNSConfig" value="CadsusServicePort"/>
    <add key="UserNameWebServiceDATASUS" value="CADSUS.CNS.PDQ.PUBLICO"/>
    <add key="PasswordWebServiceDATASUS" value="kUXNmiiii#RDdlOELdoe00966"/>
    <add key="CNESWebServiceDATASUS" value="6963447"/>
    <add key="UsuarioCNESWebServiceDATASUS" value="LEONARDO"/>
  </appSettings>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="CadsusServiceSOAP12Binding">
                    <textMessageEncoding messageVersion="Soap12" />
                  <security authenticationMode="UserNameOverTransport" enableUnsecuredResponse="true"></security>
                    <httpsTransport />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="https://servicoshm.saude.gov.br/cadsus/CadsusService/v5r0"
                binding="customBinding" bindingConfiguration="CadsusServiceSOAP12Binding"
                contract="SvcAcessoDATASUS.CadsusServicePortType" name="CadsusServicePort" />
        </client>
    </system.serviceModel>
</configuration>

Já aproveitei e coloquei algumas configurações, em <appSettings>, de autenticação do serviço e qual configuração de binding irei utilizar, para não ficar de forma hard-coded.

Além disso, precisamos colocar um parâmetro (que não é criado automaticamente na criação da Service Reference) a mais no que diz respeito a autenticação SOAP e o recebimento de respostas que serão feitas de maneira “insegura”, mesmo com a URL do serviço sendo https: a tag <security>, com os atrubutos authenticationMode=”UserNameOverTransport” e enableUnsecuredResponse=”true”.

Voltando às <appSettings>, note que precisamos de chaves para acesso ao WebService em si, que serão informadas no cabeçalho da mensagem SOAP (UserNameWebServiceDATASUS e PasswordWebServiceDATASUS) e um usuário de uma Unidade de Saúde, que serão informadas na chamada de métodos (CNESWebServiceDATASUS e UsuarioCNESWebServiceDATASUS). Como dito anteriormente, estou utilizando os dados públicos disponíveis no site do DATASUS para homologação dos sistemas.

Para facilitar minha vida, criei um método chamado “ConsultaFederal”, que retorna um DataTable (que é BEM mais fácil de manipular depois do que um XML ou mesmo algumas classes) com alguns dados retornados pela consulta. Vamos ao código:

public DataTable ConsultaFederal(string pNome, string pNomeMae, string pNomePai, DateTime pDataNascimento, string pNumeroCNS, string pCPF, out string msgRetorno)
{
	msgRetorno = String.Empty;
	
	/*DataTable de retorno*/
	DataTable dtRet = new DataTable();
	dtRet.Columns.Add(new DataColumn("NOME", typeof(System.String)));
	dtRet.Columns.Add(new DataColumn("NOME_MAE", typeof(System.String)));
	dtRet.Columns.Add(new DataColumn("NOME_PAI", typeof(System.String)));
	dtRet.Columns.Add(new DataColumn("DATA_NASCIMENTO", typeof(System.DateTime)));
	dtRet.Columns.Add(new DataColumn("TIPO_CARTAO", typeof(System.String)));
	dtRet.Columns.Add(new DataColumn("NUMERO_CARTAO", typeof(System.String)));
	dtRet.Columns.Add(new DataColumn("MUNICIPIO_RESIDENCIA", typeof(System.String)));

	//Chamada ao WebService do DATASUS
	try
	{
		using (CadsusServicePortTypeClient consultaCNS = new CadsusServicePortTypeClient(ConfigurationManager.AppSettings["EndpointConsultaCNSConfig"].ToString()))
		{
			//Atribui usuário e senha de acesso ao WebService e dados do Estabelecimento de Saúde
			consultaCNS.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["UserNameWebServiceDATASUS"].ToString();
			consultaCNS.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["PasswordWebServiceDATASUS"].ToString();

			CNESUsuarioType dadosCNES = new CNESUsuarioType() { CNES = ConfigurationManager.AppSettings["CNESWebServiceDATASUS"].ToString(), Usuario = ConfigurationManager.AppSettings["UsuarioCNESWebServiceDATASUS"].ToString() };

			//Caso o nº do CNS seja informado, é outro tipo de consulta, específica (outro método do WS a ser chamado). A abaixo é a consulta por outros dados
			if (String.IsNullOrEmpty(pNumeroCNS))
			{
				//Inicializa filtro de pesquisa
				FiltroPesquisa filtroPesquisa = new FiltroPesquisa();
				filtroPesquisa.tipoPesquisa = TipoPesquisaType.APROXIMADA;

				if (!String.IsNullOrEmpty(pNome))
					filtroPesquisa.nomeCompleto = new NomeCompletoType() { Nome = pNome };
				if (!String.IsNullOrEmpty(pNomeMae))
					filtroPesquisa.Mae = new NomeCompletoType() { Nome = pNomeMae };
				if (!String.IsNullOrEmpty(pNomePai))
					filtroPesquisa.Pai = new NomeCompletoType() { Nome = pNomePai };
				if (!pDataNascimento.Equals(DateTime.MinValue))
					filtroPesquisa.dataNascimento = pDataNascimento;
				if (!String.IsNullOrEmpty(pCPF))
					filtroPesquisa.CPF = new CPFType() { numeroCPF = pCPF };

				//Prepara objeto para fazer a requisição ao WebService
				requestPesquisar paramsPesquisa = new requestPesquisar();
				paramsPesquisa.FiltroPesquisa = filtroPesquisa;
				paramsPesquisa.CNESUsuario = dadosCNES;
				paramsPesquisa.higienizar = false;

				//Chamada ao WebService
				ResultadoPesquisa[] resultWS = consultaCNS.pesquisar(paramsPesquisa);

				//Transporta resultados para o DataTable de retorno. Se a pesquisa não retornou registros, é disparada uma exceção que é tratada no primeiro "catch".
				foreach (ResultadoPesquisa result in resultWS)
				{
					string munResidencia = "NÃO DISPONÍVEL";
					if (result.MunicipioResidencia != null)
						munResidencia = String.Format("{0} - {1}", result.MunicipioResidencia.codigoMunicipio, result.MunicipioResidencia.nomeMunicipio);
					
					dtRet.Rows.Add(
						result.NomeCompleto.Nome,
						result.Mae.Nome,
						result.Pai.Nome,
						result.dataNascimento,
						(result.CNS.tipoCartao.Equals(TipoCNSType.D) ? "DEFINITIVO" : "PROVISORIO"),
						result.CNS.numeroCNS,
						munResidencia);
				}

			}
			else //Consulta específica por número de Cartão SUS.
			{
				//Filtro de Pesquisa
				CNSType filtroConsulta = new CNSType();
				filtroConsulta.numeroCNS = pNumeroCNS;

				//Prepara objeto para requisição ao WebService
				requestConsultar paramsConsulta = new requestConsultar();
				paramsConsulta.CNESUsuario = dadosCNES;
				paramsConsulta.CNS = filtroConsulta;

				responseConsultar resultWS = consultaCNS.consultar(paramsConsulta);

				foreach (CNSType cartao in resultWS.UsuarioSUS.Cartoes)
				{
					//Coloca registro no DataTable de retorno: para cada nº do cartão do SUS encontrado.
					dtRet.Rows.Add(
						resultWS.UsuarioSUS.NomeCompleto.Nome,
						(resultWS.UsuarioSUS.Mae != null) ? resultWS.UsuarioSUS.Mae.Nome : "NÃO DISPONÍVEL",
						(resultWS.UsuarioSUS.Pai != null) ? resultWS.UsuarioSUS.Pai.Nome : "NÃO DISPONIVEL",
						resultWS.UsuarioSUS.dataNascimento,
						cartao.tipoCartao.Equals(TipoCNSType.D) ? "DEFINITIVO" : "PROVISÓRIO",
						cartao.numeroCNS,
						(resultWS.UsuarioSUS.Enderecos.Endereco.Municipio != null) ? String.Format("{0} - {1}", resultWS.UsuarioSUS.Enderecos.Endereco.Municipio.codigoMunicipio, resultWS.UsuarioSUS.Enderecos.Endereco.Municipio.nomeMunicipio) : "NÃO DISPONÍVEL"
						);
				}
			}
		}
	}
	catch (System.ServiceModel.FaultException<AcessoDATASUSv2.SvcAcessoDATASUS.MSFalha> errWS) //Erros enviados pelo WebService
	{
		List<string> arMsgsErro = new List<string>();
		foreach (MensagemType msgErrosWS in errWS.Detail.Mensagem)
		{
			arMsgsErro.Add(msgErrosWS.descricao);
		}

		msgRetorno = "A consulta retornou a(s) seguinte(s) mensagem(ns): " + String.Join(" / ", arMsgsErro.ToArray());
		dtRet = null;
	}
	catch (Exception ex) //Erro geral da aplicação
	{
		msgRetorno = "[ERRO] " +  ex.Message;
		dtRet = null;
	}

	return dtRet;
}

A nossa consulta ao WebService do SUS será feita por: Nome do Usuário do SUS, Nome da Mãe, Nome do Pai, Data de Nascimento, Número do CPF e Número do Cartão SUS. Todos os dados, exceto o Número do Cartão SUS (CNS), podem ser combinados na pesquisa, enquanto a pesquisa por nº de CNS será somente por ele, isoladamente, mesmo que os outros parâmetros tenham sido informados.

Primeiramente, crio um DataTable que irá receber os dados vindos do WebService, com a devida transformação, claro. Cada linha irá retornar o Nome do Usuário (do SUS), o nome da mãe, nome do pai, data de nascimento, tipo de cartão (Definitivo ou Provisório), o número do Cartão SUS em si e o município de residência. Estes são os dados mais comuns que utilizamos em uma consulta do gênero.

Depois, criamos uma instância da classe proxy que irá fazer o acesso ao WebService. A classe de serviço criada através da Service Reference chama-se “CadsusServicePortTypeClient”. Criada essa instância, já obtemos do arquivo de configuração os dados de autenticação ao WebService e as informações do CNES:

using (CadsusServicePortTypeClient consultaCNS = new CadsusServicePortTypeClient(ConfigurationManager.AppSettings["EndpointConsultaCNSConfig"].ToString()))
{
	//Atribui usuário e senha de acesso ao WebService e dados do Estabelecimento de Saúde
	consultaCNS.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["UserNameWebServiceDATASUS"].ToString();
	consultaCNS.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["PasswordWebServiceDATASUS"].ToString();

	CNESUsuarioType dadosCNES = new CNESUsuarioType() { CNES = ConfigurationManager.AppSettings["CNESWebServiceDATASUS"].ToString(), Usuario = ConfigurationManager.AppSettings["UsuarioCNESWebServiceDATASUS"].ToString() };
	
	/*continua...*/
}

 

Nomeamos esta instância na variável “consultaCNS”. Ao criar essa instância, passaremos no construtor de CadsusServicePortTypeClient o nome da configuração de endpoint que utilizaremos (lá no arquivo App.Config, lembra)? Em vez de passar ela diretamente, pego essa configuração da seção appSettings, através da classe ConfigurationManager.

Passamos as informações de autenticação ao WebService através da propriedade ClientCredentials de consultaCNS. O cabeçalho de autenticação SOAP será criado a partir do que informarmos nela.

Uma outra variável, a dadosCNES, do tipo CNESUsuarioType, será utilizada para passagem de parâmetros na chamada dos métodos. Nela informamos os dados do CNES, que também obtemos através do arquivo de configuração através do ConfigurationManager.

Feitas as configurações da classe proxy para acesso ao WebService, passaremos a construir a pesquisa em si. Dividi esse método em duas partes: consulta pelo número do CNS isoladamente e consulta por outros dados. Note pelo código completo que cada consulta é feita por métodos distintos do WebService do CADSUS.

Começaremos pela consulta por Nome, Data de Nascimento, etc:

/*...*/
if (String.IsNullOrEmpty(pNumeroCNS))
{
	//Inicializa filtro de pesquisa
	FiltroPesquisa filtroPesquisa = new FiltroPesquisa();
	filtroPesquisa.tipoPesquisa = TipoPesquisaType.APROXIMADA;

	if (!String.IsNullOrEmpty(pNome))
		filtroPesquisa.nomeCompleto = new NomeCompletoType() { Nome = pNome };
	if (!String.IsNullOrEmpty(pNomeMae))
		filtroPesquisa.Mae = new NomeCompletoType() { Nome = pNomeMae };
	if (!String.IsNullOrEmpty(pNomePai))
		filtroPesquisa.Pai = new NomeCompletoType() { Nome = pNomePai };
	if (!pDataNascimento.Equals(DateTime.MinValue))
		filtroPesquisa.dataNascimento = pDataNascimento;
	if (!String.IsNullOrEmpty(pCPF))
		filtroPesquisa.CPF = new CPFType() { numeroCPF = pCPF };

	//Prepara objeto para fazer a requisição ao WebService
	requestPesquisar paramsPesquisa = new requestPesquisar();
	paramsPesquisa.FiltroPesquisa = filtroPesquisa;
	paramsPesquisa.CNESUsuario = dadosCNES;
	paramsPesquisa.higienizar = false;

	//Chamada ao WebService
	ResultadoPesquisa[] resultWS = consultaCNS.pesquisar(paramsPesquisa);

	//Transporta resultados para o DataTable de retorno. Se a pesquisa não retornou registros, é disparada uma exceção que é tratada no primeiro "catch".
	foreach (ResultadoPesquisa result in resultWS)
	{
		string munResidencia = "NÃO DISPONÍVEL";
		if (result.MunicipioResidencia != null)
			munResidencia = String.Format("{0} - {1}", result.MunicipioResidencia.codigoMunicipio, result.MunicipioResidencia.nomeMunicipio);
		
		dtRet.Rows.Add(
			result.NomeCompleto.Nome,
			result.Mae.Nome,
			result.Pai.Nome,
			result.dataNascimento,
			(result.CNS.tipoCartao.Equals(TipoCNSType.D) ? "DEFINITIVO" : "PROVISORIO"),
			result.CNS.numeroCNS,
			munResidencia);
	}
}
/*...*/

 

O filtro da pesquisa é definido em uma variável do tipo FiltroPesquisa (vinda do WSDL), que chamaremos de filtroPesquisa. Em sua propriedade “tipoPesquisa”, colocaremos a enumeração (também vinda do WSDL) TipoPesquisaType.APROXIMADA. Isto serve para pesquisa por parte do nome, por exemplo.

Em seguida, preenchemos algumas outras propriedades correspondentes aos parâmetros que informamos no nosso método ConsultaFederal. Note que para cada atribuição eu crio uma instância da classe correspondente à propriedade já inicializando algumas propriedades.

Para enfim chamar o método do WebService, é necessário criar um objeto do tipo requestPesquisar, que coloquei na variável paramsPesquisa, e passei os parâmetros nas propriedades: o nosso filtro de pesquisa, os dados do CNES que obtemos anteriormente quando criamos a classe de acesso ao serviço, e uma propriedade chamada “higienizar”.

Finalmente chamamos o método “pesquisar”, passando como parâmetro a nossa variável “paramsPesquisa”, e recebemos como retorno um array de objetos ResultadoPesquisa, que colocamos em uma variável nomeada “resultWS”.

Cada item de “resultWS” contém um registro de um Usuário do SUS. Daí iremos percorrer esse array com um foreach, e a partir dele preencher o nosso DataTable de retorno.

E se a consulta não retornou registros? Nesse caso em vez do array vir vazio, é disparada uma exceção. Devemos tratar estes erros e mostrar a mensagem para o usuário. Vejam como tratei os erros:

try
{
	/*...*/
}
catch (System.ServiceModel.FaultException<AcessoDATASUSv2.SvcAcessoDATASUS.MSFalha> errWS) //Erros enviados pelo WebService
{
	List<string> arMsgsErro = new List<string>();
	foreach (MensagemType msgErrosWS in errWS.Detail.Mensagem)
	{
		arMsgsErro.Add(msgErrosWS.descricao);
	}

	msgRetorno = "A consulta retornou a(s) seguinte(s) mensagem(ns): " + String.Join(" / ", arMsgsErro.ToArray());
	dtRet = null;
}
catch (Exception ex) //Erro geral da aplicação
{
	msgRetorno = "[ERRO] " +  ex.Message;
	dtRet = null;
}

 

O primeiro “catch” trata de erros (ou mensagens de alerta, apenas) vindos como resposta do WebService, como por exemplo retorno vazio, campos inválidos, entre outros. O segundo “catch” serve para erros mais gerais, como falha de conexão ou outro erro do “nosso lado” da aplicação.

Agora vamos detalhar a parte do método que trata de consultas diretamente pelo número do CNS:

if (String.IsNullOrEmpty(pNumeroCNS))
{
	/*...*/
}
else //Consulta específica por número de Cartão SUS.
{
	//Filtro de Pesquisa
	CNSType filtroConsulta = new CNSType();
	filtroConsulta.numeroCNS = pNumeroCNS;

	//Prepara objeto para requisição ao WebService
	requestConsultar paramsConsulta = new requestConsultar();
	paramsConsulta.CNESUsuario = dadosCNES;
	paramsConsulta.CNS = filtroConsulta;

	responseConsultar resultWS = consultaCNS.consultar(paramsConsulta);

	foreach (CNSType cartao in resultWS.UsuarioSUS.Cartoes)
	{
		//Coloca registro no DataTable de retorno: para cada nº do cartão do SUS encontrado.
		dtRet.Rows.Add(
			resultWS.UsuarioSUS.NomeCompleto.Nome,
			(resultWS.UsuarioSUS.Mae != null) ? resultWS.UsuarioSUS.Mae.Nome : "NÃO DISPONÍVEL",
			(resultWS.UsuarioSUS.Pai != null) ? resultWS.UsuarioSUS.Pai.Nome : "NÃO DISPONIVEL",
			resultWS.UsuarioSUS.dataNascimento,
			cartao.tipoCartao.Equals(TipoCNSType.D) ? "DEFINITIVO" : "PROVISÓRIO",
			cartao.numeroCNS,
			(resultWS.UsuarioSUS.Enderecos.Endereco.Municipio != null) ? String.Format("{0} - {1}", resultWS.UsuarioSUS.Enderecos.Endereco.Municipio.codigoMunicipio, resultWS.UsuarioSUS.Enderecos.Endereco.Municipio.nomeMunicipio) : "NÃO DISPONÍVEL"
			);
	}
}

 

Este é um pouco mais simples: o parâmetro de consulta (o número do Cartão SUS) irá em um objeto do tipo CNSType, que colocamos na variável filtroConsulta, e preenchemos a propriedade “numeroCNS” com o número que queremos consultar.

O parâmetro que iremos passar para o método do WebService irá em uma variável do tipo requestConsultar, onde informamos em suas propriedades os dados do CNES (igual ao outro) e os dados do CNS.

Finalmente, chamamos o método “consultar()” do WebService, que irá retornar um objeto do tipo responseConsultar, que colocaremos na variável resultWS.

O resultado da pesquisa é colocado na propriedade UsuarioSUS.Cartoes da nossa variável de retorno, ela sendo um array do tipo CNSType. É isso mesmo, caso o mesmo usuário seja cadastrado mais de uma vez no Cartão SUS, ele apresentará todos os cadastros.

Por fim, percorremos esse array e preenchemos o nosso DataTable de retorno. As mesmas observações de tratamento de erros podem ser aplicadas aqui, como de fato o são, já que o “catch” atende aos dois métodos de consulta que tratamos aqui.

Antes de finalizar, cabem algumas observações adicionais:

- Posso construir este código através do .NET Framework 3.5 e Visual Studio 2008?

Poder, pode. A Web Reference será criada, mas não será possível utilizar a propriedade enableUnsecuredResponse na hora de configurar o binding.

Mesmo aplicando o patch no .NET Framework que habilita esse atributo, o WebService retornará com erro ao executar os métodos de pesquisa, mesmo o código estando correto.

Criar a Service Reference pelo VS 2010 como explicado, e depois mudando o Target Framework da biblioteca de classes para .NET 3.5 e adicionando o binário gerado (a DLL gerada) como referência em seu projeto VS 2008 pode funcionar.

Caso seu sistema esteja executando em .NET 3.5 (ou abaixo) e quiser realizar essa consulta, sugiro disponibilizá-la através de um WebService construído em .NET (asmx), retornando os dados em XML, para ser compatível com esses Frameworks, e implementar esse WebService construído em .NET em seu sistema. Sei que é chato criar mais uma camada, mas essa foi a solução que necessitei construir.

Enfim, mesmo com a limitação acima, é assim que se consome o WebService do DATASUS em uma aplicação .NET!

Atualização 17/052017!

Agora com um exemplo de implementação dos métodos de consulta acima, em uma aplicação Console:

Download Exemplo de Consulta ao WebService CADSUS (368kb, zip)

Agora você pode baixar (e contribuir também, via pull-requests) também pelo GitHub: https://github.com/leonelfraga/acessodatasus