diff --git a/NFCe.Tests/InfProtAlertGroupTests.cs b/NFCe.Tests/InfProtAlertGroupTests.cs
new file mode 100644
index 000000000..e5fce9291
--- /dev/null
+++ b/NFCe.Tests/InfProtAlertGroupTests.cs
@@ -0,0 +1,136 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NFe.Classes.Protocolo;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Serialization;
+
+namespace NFCe.Tests
+{
+ ///
+ /// Grupo PR13 do protocolo (NT 2018.005, ampliado pela NT 2026.002 §2.3).
+ ///
+ /// O leiaute descreve o PR13 como grupo de 0 a 5 ocorrências, sem elemento agrupador,
+ /// com PR14 cMsg em 0-1 e PR15 xMsg em 1-1. Antes desta mudança
+ /// infProt mapeava os dois em propriedades escalares, e as ocorrências repetidas
+ /// colapsavam: só a última sobrevivia à desserialização.
+ ///
+ ///
+ [TestClass]
+ public class InfProtAlertGroupTests
+ {
+ private const string Namespace = "http://www.portalfiscal.inf.br/nfe";
+
+ private static string ProtocoloXml(string ocorrencias)
+ {
+ return
+ "" +
+ "1SVRS" +
+ "53260446759666000129557280000000511348419115" +
+ "2026-08-08T10:00:00-03:00" +
+ "153260000000001abc" +
+ "120Autorizado o uso da NF-e, com alerta" +
+ ocorrencias +
+ "";
+ }
+
+ private static protNFe Desserializar(string xml)
+ {
+ var serializer = new XmlSerializer(typeof(protNFe), Namespace);
+ using (var reader = new StringReader(xml))
+ {
+ return (protNFe)serializer.Deserialize(reader);
+ }
+ }
+
+ [TestMethod]
+ public void Multiplas_ocorrencias_sao_preservadas_na_ordem_do_documento()
+ {
+ var protocolo = Desserializar(ProtocoloXml(
+ "172destinatario bloqueado" +
+ "999segundo alerta" +
+ "111terceiro alerta"));
+
+ var alertas = protocolo.infProt.Alertas;
+
+ Assert.AreEqual(3, alertas.Count);
+ CollectionAssert.AreEqual(
+ new[] { 172, 999, 111 },
+ alertas.Select(a => a.Codigo.Value).ToArray());
+ Assert.AreEqual("destinatario bloqueado", alertas[0].Mensagem);
+ Assert.AreEqual("terceiro alerta", alertas[2].Mensagem);
+ }
+
+ [TestMethod]
+ public void Ocorrencia_sem_cMsg_e_preservada_com_codigo_nulo()
+ {
+ // cMsg é 0-1 no leiaute: a SEFAZ pode retornar a mensagem sem código.
+ var protocolo = Desserializar(ProtocoloXml(
+ "172com codigo" +
+ "sem codigo" +
+ "999com codigo de novo"));
+
+ var alertas = protocolo.infProt.Alertas;
+
+ Assert.AreEqual(3, alertas.Count);
+ Assert.AreEqual(172, alertas[0].Codigo);
+ Assert.IsNull(alertas[1].Codigo);
+ Assert.AreEqual("sem codigo", alertas[1].Mensagem);
+ Assert.AreEqual(999, alertas[2].Codigo);
+ }
+
+ [TestMethod]
+ public void Protocolo_sem_o_grupo_produz_lista_vazia()
+ {
+ var protocolo = Desserializar(ProtocoloXml(string.Empty));
+
+ Assert.AreEqual(0, protocolo.infProt.Alertas.Count);
+ Assert.IsNull(protocolo.infProt.cMsg);
+ Assert.IsNull(protocolo.infProt.xMsg);
+ }
+
+ [TestMethod]
+ public void cMsg_sem_xMsg_seguinte_nao_forma_ocorrencia()
+ {
+ // xMsg é o campo obrigatório do grupo — sem ele não há ocorrência a reportar.
+ var protocolo = Desserializar(ProtocoloXml("172"));
+
+ Assert.AreEqual(0, protocolo.infProt.Alertas.Count);
+ }
+
+ [TestMethod]
+ public void Campos_escalares_seguem_expondo_a_primeira_ocorrencia()
+ {
+ // Compatibilidade com quem já consumia infProt.cMsg / infProt.xMsg
+ // (ex.: NFe.Danfe.Nativo/NFCe/DanfeNativoNfce.cs).
+ var protocolo = Desserializar(ProtocoloXml(
+ "172primeira" +
+ "999segunda"));
+
+ Assert.AreEqual(172, protocolo.infProt.cMsg);
+ Assert.AreEqual("primeira", protocolo.infProt.xMsg);
+ }
+
+ [TestMethod]
+ public void Round_trip_preserva_a_quantidade_de_ocorrencias()
+ {
+ var xml = ProtocoloXml(
+ "172com codigo" +
+ "sem codigo" +
+ "999outro");
+
+ var protocolo = Desserializar(xml);
+
+ var serializer = new XmlSerializer(typeof(protNFe), Namespace);
+ string reserializado;
+ using (var writer = new StringWriter())
+ {
+ serializer.Serialize(writer, protocolo);
+ reserializado = writer.ToString();
+ }
+
+ Assert.AreEqual(3, Regex.Matches(reserializado, "").Count);
+ Assert.AreEqual(2, Regex.Matches(reserializado, "").Count);
+ }
+ }
+}
diff --git a/NFe.Classes/Protocolo/infProt.cs b/NFe.Classes/Protocolo/infProt.cs
index 557357890..0625a1079 100755
--- a/NFe.Classes/Protocolo/infProt.cs
+++ b/NFe.Classes/Protocolo/infProt.cs
@@ -31,6 +31,8 @@
/* Rua Comendador Francisco josé da Cunha, 111 - Itabaiana - SE - 49500-000 */
/********************************************************************************/
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Xml.Serialization;
using DFe.Classes.Assinatura;
using DFe.Classes.Flags;
@@ -93,34 +95,127 @@ public string ProxyDhRecbto
///
public string xMotivo { get; set; }
- [XmlElement(ElementName = "cMsg")]
- public string ProxyccMsg
+ ///
+ /// PR13 - Grupo de mensagens da SEFAZ para o emissor (0-5 ocorrências).
+ ///
+ /// O leiaute descreve o PR13 como "Sequência XML": um grupo SEM elemento agrupador,
+ /// cujos campos aparecem em sequência direta dentro de infProt — PR14 cMsg
+ /// (ocorrência 0-1, opcional) e PR15 xMsg (ocorrência 1-1, obrigatório).
+ ///
+ ///
+ /// Modelado como coleção de escolha com
+ /// porque é o único arranjo do XmlSerializer que preserva a ORDEM entre dois nomes
+ /// de elemento distintos — e é a ordem que permite reconstruir cada ocorrência.
+ /// Mapear cMsg e xMsg em propriedades escalares, como antes, fazia as
+ /// ocorrências repetidas colapsarem: só a última sobrevivia.
+ ///
+ /// Ver para consumir os pares já reconstruídos.
+ ///
+ [XmlElement("cMsg", typeof(string))]
+ [XmlElement("xMsg", typeof(string))]
+ [XmlChoiceIdentifier(nameof(MensagensTipos))]
+ public string[] Mensagens { get; set; }
+
+ ///
+ /// Discriminador que diz, para cada posição de , se o valor veio
+ /// de um cMsg ou de um xMsg. Preenchido pelo XmlSerializer; não serializa.
+ ///
+ [XmlIgnore]
+ public TipoMensagemProtocolo[] MensagensTipos { get; set; }
+
+ ///
+ /// Ocorrências do PR13 já reconstruídas em pares código/mensagem, na ordem do documento.
+ ///
+ ///
+ /// O agrupamento é ancorado no xMsg, que é o campo obrigatório do grupo: cada
+ /// xMsg encerra uma ocorrência, e o cMsg imediatamente anterior — quando
+ /// existe — é o código dela. Um cMsg sem xMsg seguinte é ignorado, por não
+ /// formar ocorrência válida segundo o leiaute.
+ ///
+ [XmlIgnore]
+ public IReadOnlyList Alertas
{
get
{
- if (cMsg == null) return null;
- return cMsg.Value.ToString();
- }
- set
- {
- if (string.IsNullOrEmpty(value))
+ var resultado = new List();
+
+ if (Mensagens == null || MensagensTipos == null)
+ return resultado;
+
+ int? codigoPendente = null;
+
+ for (var i = 0; i < Mensagens.Length && i < MensagensTipos.Length; i++)
{
- cMsg = null;
- return;
+ if (MensagensTipos[i] == TipoMensagemProtocolo.cMsg)
+ {
+ codigoPendente = int.TryParse(Mensagens[i], out var codigo) ? codigo : (int?)null;
+ continue;
+ }
+
+ resultado.Add(new MensagemProtocolo(codigoPendente, Mensagens[i]));
+ codigoPendente = null;
}
- cMsg = int.Parse(value);
+
+ return resultado;
}
}
+ ///
+ /// PR14 - Código da primeira mensagem, quando há. Mantido por compatibilidade com quem
+ /// consumia o campo escalar; para todas as ocorrências use .
+ ///
[XmlIgnore]
- public int? cMsg { get; set; }
+ public int? cMsg
+ {
+ get { return Alertas.Select(a => a.Codigo).FirstOrDefault(c => c.HasValue); }
+ }
- public string xMsg { get; set; }
+ ///
+ /// PR15 - Texto da primeira mensagem, quando há. Mantido por compatibilidade com quem
+ /// consumia o campo escalar; para todas as ocorrências use .
+ ///
+ [XmlIgnore]
+ public string xMsg
+ {
+ get { return Alertas.Count > 0 ? Alertas[0].Mensagem : null; }
+ }
///
- /// PR13 - Assinatura XML do grupo identificado pelo atributo “Id”
+ /// PR90 - Assinatura XML do grupo identificado pelo atributo “Id”
/// A decisão de assinar a mensagem fica a critério da UF interessada.
///
public Signature Signature { get; set; }
}
+
+ ///
+ /// Discriminador dos campos do grupo PR13.
+ ///
+ [XmlType(IncludeInSchema = false)]
+ public enum TipoMensagemProtocolo
+ {
+ cMsg,
+ xMsg
+ }
+
+ ///
+ /// Uma ocorrência do grupo PR13: PR14 cMsg (opcional) e PR15 xMsg.
+ ///
+ public class MensagemProtocolo
+ {
+ public MensagemProtocolo(int? codigo, string mensagem)
+ {
+ Codigo = codigo;
+ Mensagem = mensagem;
+ }
+
+ ///
+ /// PR14 - Código da mensagem. Nulo quando a ocorrência veio sem cMsg.
+ ///
+ public int? Codigo { get; private set; }
+
+ ///
+ /// PR15 - Mensagem da SEFAZ para o emissor.
+ ///
+ public string Mensagem { get; private set; }
+ }
}
\ No newline at end of file