Cosmos.Impuestos.Contratos 0.46.0-beta

This is a prerelease version of Cosmos.Impuestos.Contratos.
dotnet add package Cosmos.Impuestos.Contratos --version 0.46.0-beta
                    
NuGet\Install-Package Cosmos.Impuestos.Contratos -Version 0.46.0-beta
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cosmos.Impuestos.Contratos" Version="0.46.0-beta" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cosmos.Impuestos.Contratos" Version="0.46.0-beta" />
                    
Directory.Packages.props
<PackageReference Include="Cosmos.Impuestos.Contratos" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cosmos.Impuestos.Contratos --version 0.46.0-beta
                    
#r "nuget: Cosmos.Impuestos.Contratos, 0.46.0-beta"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cosmos.Impuestos.Contratos@0.46.0-beta
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cosmos.Impuestos.Contratos&version=0.46.0-beta&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Cosmos.Impuestos.Contratos&version=0.46.0-beta&prerelease
                    
Install as a Cake Tool

Cosmos.Impuestos.Contratos

Eventos públicos de integración de Cosmos Impuestos (IPublicEvent). Un consumidor en otro bounded context referencia este paquete y publica/consume estos eventos sobre el bus para integrarse con Impuestos de forma eventual (async request-reply, event-carried state transfer y avisos de cambio).

El paquete distribuye los tipos C# de los contratos. No incluye stubs gRPC: la integración es por eventos sobre el bus de mensajería, no por RPC síncrono.


Instalación

<PackageReference Include="Cosmos.Impuestos.Contratos" Version="0.46.0-beta" />

Depende de Cosmos.EventDriven.Abstractions (IPublicEvent), Cosmos.Types y Cosmos.Types.IdentificacionesLegales.

Cambios incompatibles

Las versiones se listan de la más nueva a la más vieja. Una entrada está acá cuando obliga a shippear código para seguir siendo correcto, compile o no: una remoción, un cambio de forma, o un valor nuevo en un enum sobre el que se discrimina. Por ese último caso una entrada puede ser incompatible sin cambiar ninguna firma — ver «Miembros de enum agregados» al final.

0.46.0-beta — las solicitudes que Impuestos recibe no se pueden construir con forma inválida

Incompatible sin cambiar ninguna firma: quien construye estos tipos con null, vacío o un enum fuera de rango deja de ejecutar sin lanzar, y quien esperaba un rechazo por el bus ya no lo recibe.

  • Qué lanza al construirse (y al deserializarse, que usa el mismo constructor). CalculoTributarioSolicitado, SolicitarConfirmacionTributaria y SolicitarRegistroTributario, y los tipos que usan: ContextoTransaccional, ConceptoCalculo, LineaConfirmada, UbicacionesTransaccion y OrigenDelDesgravamen. Lanzan ArgumentNullException (un valor nulo), ArgumentException (vacío, en blanco o inválido) o ArgumentOutOfRangeException (un enum fuera de rango, una cantidad no positiva), con el campo y el motivo por el que hace falta.
  • Qué exige cada campo.
    • Texto no nulo ni en blanco: ReferenciaOrigen, ReferenciaSolicitud, TransaccionId, las sedes de las ubicaciones, ClasificacionTributaria y CodigoTributo.
    • Objeto no nulo: las identificaciones, Ubicaciones, Contexto, DesgloseConfirmado y Conceptos.
    • Id distinto de Guid.Empty: ConceptoCalculo.Id y ConceptoOrigen.
    • Valor definido en el enum: SubDominio, DireccionFiscal, EfectoFiscal y TipoTarifa.
    • Cantidad positiva y siempre junto con su unidad de medida (las dos o ninguna). ActividadEconomica, si viene, no en blanco.
    • Ningún id de concepto repetido dentro de una solicitud.
  • Qué no se restringe. Monto admite negativos y ConceptoPago vacío cuenta como no informado. Una lista de Conceptos vacía sigue siendo admisible: es una regla de negocio y Impuestos la rechaza, con DatosFaltantes en la propuesta o la confirmación de un desgravamen y con ComandoInvalido en un gravamen.
  • Ya no se publica el rechazo de una solicitud mal armada. Un mensaje que no se puede deserializar va a la cola de error del bus y no recibe CalculoTributarioRechazado, ConfirmacionTributariaRechazada ni EntregaDeRegistroTributarioRechazada. Esto reemplaza lo que decía 0.30.0-beta para Ubicaciones y Conceptos ausentes.
  • MotivoRechazoEntrega.SolicitudInvalida queda sin productor. El miembro y su ordinal se conservan porque están publicados y congelados; ningún flujo lo emite. Un consumidor que lo cubre en un switch conserva el arm. ComandoInvalido y DatosFaltantes siguen teniendo productores de negocio.
  • Sin cambio de forma, cambia un valor. La base de una línea por unidad de un desgravamen es una cantidad, no un monto: viaja con hasta 6 decimales en CalculoTributarioRealizado (antes, 2). Quien la devuelve tal como la recibió en LineaConfirmada.BaseGravable la ve reaparecer igual en RegistroTributarioEntregado.

Orden de despliegue. Quien emite estas solicitudes actualiza a esta versión antes de que Impuestos despliegue la que ya no valida la forma al recibir: con el paquete anterior, una solicitud mal armada queda en la cola de error y quien la emitió no recibe respuesta. Nada lo impide mecánicamente.

Qué hacer. Construir las solicitudes con datos completos (el constructor dice el campo que falta). Quien tenga dobles de prueba que construyan estos tipos con null, vacío o default los corrige. Quien esperaba un rechazo por el bus para una solicitud mal armada pasa a prevenirla al construirla.

0.44.0-beta — dos condiciones que cambian la tarifa a valores distintos se rechazan con motivo propio

Incompatible sin cambiar ninguna firma: un valor nuevo en MotivoRechazoCalculo rompe a quien haga switch exhaustivo, y quien discriminaba ConfiguracionIncoherente ya no recibe uno de sus casos.

  • MotivoRechazoCalculo.TarifasAlternativasIncoherentes (ordinal 20, al final). Dos condiciones que cambian la tarifa del mismo tributo coincidieron a la vez y señalan tarifas distintas: dos líneas alternativas distintas, o una línea y la del convenio. El motivoDetalle nombra el tributo y las dos condiciones. Lo corrige quien configura, alineando las condiciones o cerrando una: no invita a reintentar. Es el nombre que el libro le da a ese rechazo (tarifas_alternativas_incoherentes).
  • Casos que salen de ConfiguracionIncoherente. Hasta 0.43.0-beta ese caso llegaba como ConfiguracionIncoherente. El motivo sigue para las demás divergencias entre condiciones que coinciden a la vez (substitutos de ReverseCharge distintos, un ámbito jurisdiccional que solo una evalúa) y para la sustitución cíclica.
  • MotivoRechazoCalculo.CuantiaIncoherente queda sin productor. El miembro y su ordinal se conservan porque están publicados y congelados, pero ningún cálculo lo emite: la tarifa que una condición impone ya forma parte de la clave del grupo de cuantía mínima por transacción, así que dos conceptos del mismo grupo resuelven siempre la misma entrada. Un consumidor que lo cubre en un switch conserva el arm.
  • La confirmación no cambia. MotivoRechazoConfirmacion no tiene miembro para este caso, igual que no lo tiene para TarifaNoVigente ni CuantiaIncoherente: el cálculo de referencia que cae en él sigue llegando como DatosFaltantes.

Qué hacer. El consumidor que no ramifica por motivo solo lo registra: el número nuevo (20) queda guardado como tal. Quien tenga un switch exhaustivo sobre MotivoRechazoCalculo suma el miembro; si su arm por defecto lanza, el mensaje muere en vez de degradarse. Quien discriminaba ConfiguracionIncoherente para este caso pasa a discriminar TarifasAlternativasIncoherentes.

0.43.0-beta — un concepto de pago que no rige se rechaza con motivo propio, y el dato que el tributo exige y no llega, como datos faltantes

Incompatible sin cambiar ninguna firma: un valor nuevo en MotivoRechazoCalculo rompe a quien haga switch exhaustivo, y quien discriminaba TarifaNoVigente o, en la confirmación, ComandoInvalido ya no recibe algunos de sus casos.

  • MotivoRechazoCalculo.ConceptoPagoNoVigente (ordinal 19, al final). Un concepto de la solicitud informa un concepto de pago que no está en el catálogo efectivo del país (el estándar más los propios del tenant) o que el tenant suprimió desde una fecha anterior o igual a la de la transacción; el motivoDetalle nombra el código, el país y la fecha. Se rechaza aunque ningún tributo tarifique por concepto de pago. Lo corrige quien arma la solicitud (un código del catálogo) o quien configura (un concepto propio). Es el nombre que el libro le da a ese rechazo (concepto_pago_no_vigente).
  • Casos que salen de TarifaNoVigente. El concepto de pago inexistente que un tributo de cobertura abierta tarifaba llegaba como TarifaNoVigente, un motivo que no dice la causa. Ahora llega como ConceptoPagoNoVigente. Los que antes se descartaban en silencio (un tributo de cobertura cerrada) o se ignoraban (ningún tributo tarifaba por concepto de pago) ahora también se rechazan.
  • Casos que salen de ComandoInvalido y pasan a DatosFaltantes. El concepto de pago que la tarifa de un tributo exige y la solicitud no trae, y el lugar de ejecución que la regla de localización de un tributo necesita y la solicitud no trae. Vale para CalculoTributarioRechazado y para ConfirmacionTributariaRechazada, que re-ejecuta el cálculo. Esto revierte el criterio de 0.33.0-beta para esos dos casos: el libro llama datos_faltantes a la falta de información del contexto, ubicaciones requeridas incluidas, y comando_invalido a la solicitud que no cumple el contrato.
  • Un concepto de pago "" o solo con espacios cuenta como no informado, igual que null: no es una forma que el contrato rechace. Si un tributo candidato lo exige, el rechazo es DatosFaltantes.
  • En la confirmación, ConceptoPagoNoVigente no tiene miembro propio en MotivoRechazoConfirmacion (no cambia): el cálculo de referencia que lo produce llega como DatosFaltantes, igual que una clasificación no vigente (el miembro ClasificacionNoVigente de la confirmación existe pero no se produce).

Qué hacer. El consumidor que no ramifica por motivo solo lo registra: el número nuevo (19) queda guardado como tal. Quien tenga un switch exhaustivo sobre MotivoRechazoCalculo suma el miembro; si su arm por defecto lanza, el mensaje muere en vez de degradarse. Quien trataba ComandoInvalido de la confirmación como «mi solicitud está mal» por el concepto de pago o el lugar de ejecución faltante, ahora recibe DatosFaltantes: lo corrige igual (informa el dato y reintenta). Quien discriminaba TarifaNoVigente para un concepto de pago desconocido pasa a discriminar ConceptoPagoNoVigente.

0.41.0-beta — el libro al día: tarifa por unidad, perfil incompleto, cuantía sin umbral y el rechazo de la entrega con fecha

Incompatible. Rompe la compilación de quien nombre CalidadFiscalFaltante o construya EntregaDeRegistroTributarioRechazada, y a quien haga switch exhaustivo sobre los enums que crecen.

  • Una tarifa puede ser por unidad. TipoTarifa.PorUnidad (ordinal 3): el valor es cantidad × tarifa, y la base de la línea es la cantidad, no un monto. ConceptoCalculo gana Cantidad (decimal?) y UnidadDeMedida (UnidadDeMedida? del Nugget Cosmos.Types.UnidadesDeMedida, código de la Rec. 20 de UN/ECE, p. ej. GLL), al final y opcionales: se informan las dos o ninguna. Una unidad que no está en el catálogo (o con otras mayúsculas: gll) se rechaza como ComandoInvalido. Un concepto cuya entrada resuelta es por unidad y no trae cantidad se rechaza con CantidadNoInformada; si la trae en otra unidad, con UnidadIncompatible (el motor no convierte). Quien paga combustible (el caso de la sobretasa) debe mandar la cantidad.
  • UnidadDeMedida viaja en las líneas (UnidadDeMedida? del Nugget, null por defecto, al final): LineaDesglose, LineaDescartada, LineaConfirmada, LineaDesgloseExplicada, LineaDescartadaExplicada y LineaEntregada. Es la unidad de la base cuando el tipo de tarifa es PorUnidad; null en las demás líneas y en los productores anteriores. La confirmación debe devolverla tal como vino: una línea por unidad que la omite se compara como intervenida.
  • La unidad de medida es el Nugget de Cosmos.Types, y la dependencia es nueva. Cosmos.Impuestos.Contratos referencia Cosmos.Types.UnidadesDeMedida 1.0.0, como ya referencia Paises e IdentificacionesLegales: quien consume el paquete lo recibe transitivamente. Cambia el cable: la unidad viaja como un objeto, "unidadDeMedida": { "codigo": "GLL" } (el bus escribe solo el código; nombre y símbolo los resuelve el Nugget del lado que lee), no como el texto "GLL". Quien mande la unidad construye new UnidadDeMedida { Codigo = "GLL" } o la obtiene con UnidadDeMedida.Obtener("GLL").
  • MotivoRechazoCalculo.CalidadFiscalFaltante se llama ahora PerfilIncompleto y conserva su ordinal (1): el libro lo nombra así (perfil_incompleto). El bus serializa el enum como número, así que el cable no cambia; cambia el nombre: recompilar, y donde se haya guardado el texto CalidadFiscalFaltante aceptar los dos. Además, entre condiciones ya no hay atajo: una exclusión que gana ya no esconde que una activadora necesitaba un atributo no declarado, así que un cálculo que antes se resolvía puede ahora rechazarse con este motivo.
  • MotivoRechazoCalculo gana CantidadNoInformada (17) y UnidadIncompatible (18). La confirmación los sigue colapsando en DatosFaltantes; MotivoRechazoConfirmacion no cambia.
  • Efecto.SinCuantiaMinima (ordinal 7): la condición que gana aplica el tributo sin comparar contra el umbral de cuantía mínima. Se publica en la condición evaluada (GET /efectos, y el desenlace propio AplicoSinCuantiaMinima en condicionesEvaluadas[].resultadoEvaluacion de la lectura del registro).
  • EntregaDeRegistroTributarioRechazada.Fecha (DateTimeOffset, al final, sin valor por defecto): el instante en que Impuestos resolvió el rechazo. Quien construya el evento (un doble de prueba) la pasa; quien solo lo lee no cambia.
  • TributoDeColombia.SobretasaCombustibles (ordinal 12): la línea de la sobretasa a los combustibles sale homologada.

Qué hacer:

  • Radicación: recompilar y renombrar CalidadFiscalFaltante → PerfilIncompleto; seguir leyendo el texto viejo en la historia ya guardada. Si construye EntregaDeRegistroTributarioRechazada (dobles de prueba), pasar Fecha. Mandar Cantidad y UnidadDeMedida en los conceptos de combustible y devolver UnidadDeMedida en la confirmación.
  • Contabilidad: leer LineaEntregada.UnidadDeMedida junto con TipoTarifa.PorUnidad: la base de esa línea es una cantidad. Contemplar TributoDeColombia.SobretasaCombustibles.
  • Fronts que leen el registro o la explicación: contemplar PorUnidad en el tipo de tarifa y mostrar unidadDeMedida junto a la base; el switch sobre resultadoEvaluacion suma AplicoSinCuantiaMinima (el snapshot de openapi/consultas.json lo trae).
  • Todos: un switch exhaustivo sin arm por defecto sobre TipoTarifa, Efecto, MotivoRechazoCalculo o TributoDeColombia debe sumar el miembro nuevo, o el mensaje muere.

0.40.0-beta — un atributo que el perfil no declara se evalúa con su valor por defecto, y el cálculo lo publica

Un atributo booleano del perfil tiene tres estados: declarado verdadero, declarado falso y no declarado. El cálculo ya no lee el tercero como «no cumple»: lo evalúa con el valor por defecto de la definición del atributo y deja dicho que lo supuso. Incompatible sin cambiar ninguna firma obligatoria: un cálculo que hasta 0.39.0-beta se resolvía como si la calidad no se cumpliera puede ahora rechazarse, y quien discrimina los motivos de rechazo debe contemplarlo.

  • CalculoTributarioRealizado.AtributosSupuestos (IReadOnlyList<AtributoSupuesto>?, null por defecto, al final). AtributoSupuesto(Entidad, Atributo, Valor): Entidad es "Emisora" o "Contraparte", Atributo el nombre de la calidad y Valor el valor por defecto con que se evaluó. La lista es vacía si nada se supuso y null en los mensajes de un productor anterior a esta versión. Lo declarado por el perfil no viaja como supuesto.
  • MotivoRechazoCalculo.CalidadFiscalFaltante (miembro que ya existía; conserva su ordinal). Se emite ahora cuando una condición necesita un atributo que el perfil no declara y cuya definición no tiene valor por defecto. El detalle nombra, por condición, el atributo y la parte (emisora o contraparte) con su documento. La corrige quien configura el perfil o la definición: no invita a reintentar.
  • La confirmación no cambia de forma: si su cálculo de referencia cae en este caso, el rechazo llega con DatosFaltantes y el detalle del cálculo; MotivoRechazoConfirmacion no tiene un miembro propio.
  • Un consumidor que siga en 0.39.0-beta ignora AtributosSupuestos al deserializar y recibe CalidadFiscalFaltante con el ordinal que su enum ya nombra.

Qué hacer: donde se muestre o se cuente el motivo de un rechazo del cálculo, contemplar CalidadFiscalFaltante; donde se muestre el resultado, mostrar AtributosSupuestos para que el usuario sepa que el cálculo descansa en un supuesto.

0.39.0-beta — el rechazo por falta de índice tiene su motivo

Cuando la cuantía mínima de un tributo está expresada en una unidad de referencia (la UVT, …) y no hay índice vigente para esa unidad a la fecha de la transacción, el rechazo lleva ahora un motivo propio, en el cálculo y en la confirmación. Incompatible sin cambiar ninguna firma: un valor nuevo en dos enums sobre los que se discrimina rompe a quien haga switch exhaustivo sin arm por defecto, y quien discriminaba los motivos que antes recibía este caso deja de recibirlo.

  • MotivoRechazoCalculo.IndiceNoVigente, al final del enum (ordinal 16). El detalle nombra la unidad y la fecha, y lo completa quien publica el índice: no invita a reintentar. Hasta 0.38.0-beta ese caso llegaba como ConfiguracionTributariaNoEncontrada, así que quien discriminaba ese motivo deja de recibirlo por este caso. Ese motivo sigue existiendo para el país sin configuración.
  • MotivoRechazoConfirmacion.IndiceNoVigente, al final del enum (ordinal 11). La confirmación lo publica cuando su cálculo de referencia cae en el mismo caso. Hasta 0.38.0-beta llegaba como DatosFaltantes, así que quien discriminaba ese motivo deja de recibirlo por este caso. Lo que no tiene motivo propio sigue llegando como DatosFaltantes.
  • MotivoRechazoConfirmacion.ConceptoSinTributosAplicables queda reservado en su posición, sin cambiar ordinales: ningún flujo lo emite, porque un concepto confirmado sin tributos aplicables es legítimo y no se rechaza. Se conserva porque su ordinal está publicado; quien lo cubre en un switch conserva el arm.
  • Un consumidor que siga en 0.38.0-beta recibe los ordinales 16 (cálculo) y 11 (confirmación), que su enum no nombra.

Qué hacer: agregar IndiceNoVigente a los switch sobre MotivoRechazoCalculo y MotivoRechazoConfirmacion. Donde se muestre o se cuente el motivo de un rechazo, contemplarlo: no se resuelve reintentando.

0.38.0-beta — las decisiones del usuario dejan de viajar al cálculo, y la cuantía incoherente tiene su motivo

Excluir un tributo aplicado o incluir uno descartado es una edición del desglose que recibió quien cotiza, no un dato del cálculo, y toda decisión cae con el cambio fiscal que dispara el recálculo. Por eso el contrato deja de llevarlas en las dos direcciones. Incompatible: se quitan campos y tipos públicos, y quien los use deja de compilar. Además, un rechazo que llegaba como ConfiguracionIncoherente llega ahora como CuantiaIncoherente, lo que también es incompatible aunque compile.

  • Se quitan CalculoTributarioSolicitado.Exclusiones e Inclusiones, CalculoTributarioRealizado.ExclusionesSinEfecto e InclusionesSinEfecto y LineaDesglose.IncluidaPorElUsuario, con los tipos ExclusionDelUsuario, InclusionDelUsuario, DecisionSinEfecto y MotivoDecisionSinEfecto. Los dos eventos y la línea vuelven a la forma que tenían en 0.36.0-beta.
  • Un productor que sigue en 0.37.0-beta y manda decisiones recibe el cálculo como si no las hubiera mandado: se ignoran, no se rechazan. Tampoco se rechaza ya con ComandoInvalido una decisión mal formada ni un desgravamen con decisiones, porque no hay campo que las traiga. Un consumidor en 0.37.0-beta que lea la respuesta recibe IncluidaPorElUsuario en false y los avisos de decisiones sin efecto en null.
  • Dos miembros quedan reservados para siempre en su posición, sin cambiar ordinales: MotivoExclusion.ExcluidoPorElUsuario y RazonDeAplicacion.AplicoPorDecisionDelUsuario. Ningún cálculo los emite, y solo pueden aparecer en la explicación de una cotización calculada antes de esta versión (/explicaciones-de-cotizacion/{referenciaOrigen}). Quien los cubre en un switch conserva el arm.
  • La confirmación no cambia. SolicitarConfirmacionTributaria lleva el desglose que fija el consumidor, y su cálculo de referencia no conoce ninguna decisión: confirmar un desglose editado —una línea aplicada que se quitó, o una línea agregada desde un descarte— cuenta como intervención (HuboIntervencion).
  • MotivoRechazoCalculo.CuantiaIncoherente, al final del enum. Dos conceptos de un mismo grupo de cuantía mínima por transacción (mismo tributo, factor y jurisdicción) resolvieron entradas de tarifa con umbrales distintos. Solo ocurre con una condición propia que cambia la tarifa, y el detalle nombra el tributo y los umbrales. La corrige quien configura: no invita a reintentar. Hasta 0.37.0-beta ese caso llegaba como ConfiguracionIncoherente, así que quien discriminaba ese motivo deja de recibirlo. Un consumidor que siga en 0.37.0-beta recibe el ordinal 15, que su enum no nombra. La confirmación no cambia: si su cálculo de referencia cae en este caso, el rechazo sigue llegando con DatosFaltantes. La definición de dominio pide que la confirmación rechace con el mismo código que el cálculo, pero MotivoRechazoConfirmacion todavía no tiene este miembro, igual que no tiene TarifaNoVigente.
  • La misma versión agrega un campo aditivo: AsumidaHabitualmente en LineaDesglose, LineaDescartada y sus líneas de lectura. Está descrito más abajo, con los demás campos opcionales.

Qué hacer: quitar las referencias a los campos y tipos retirados; aplicar la exclusión o la inclusión sobre el desglose recibido, y confirmar ese desglose editado. Donde se muestre o se cuente el motivo de un rechazo del cálculo, contemplar CuantiaIncoherente.

0.37.0-beta — la solicitud de cálculo trae las decisiones del usuario sobre un tributo

Retirado en 0.38.0-beta: los campos y tipos de esta entrada ya no existen, salvo los dos miembros de enum, que quedan reservados (ver 0.38.0-beta). Se conserva como historia.

Quien cotiza puede decirle al cálculo «no apliques este tributo en este concepto» o «aplícalo aunque lo descartaste». Incompatible sin cambiar ninguna firma obligatoria: los campos son opcionales y una solicitud que no los envía da el mismo resultado de siempre, pero dos miembros nuevos en enums sobre los que se discrimina rompen a quien haga switch exhaustivo sin arm por defecto, o a quien trate toda línea descartada como «el cálculo la descartó».

  • CalculoTributarioSolicitado.Exclusiones (IReadOnlyList<ExclusionDelUsuario>?, null por defecto, al final). ExclusionDelUsuario(ConceptoOrigen, CodigoTributo). La línea de ese par pasa de aplicada a descartada, y con ella los tributos que dependen de él en ese concepto y la contra-línea de una autoliquidación. La descartada conserva base, tarifa y factor, con Valor en 0 y TraeTarifa en true, para poder reincorporarla. El concepto sigue contando para la cuantía mínima del grupo.
  • CalculoTributarioSolicitado.Inclusiones (IReadOnlyList<InclusionDelUsuario>?, null por defecto, al final). InclusionDelUsuario(ConceptoOrigen, CodigoTributo, EntradaDeTarifa?). La línea descartada de ese par se aplica con la base con que se descartó y, sin EntradaDeTarifa, con la tarifa que trae; con ella, con la tarifa de esa entrada del tarifario del tributo vigente a la fecha de la transacción. Un tributo que liquida un porcentaje de su padre se liquida sobre el valor del padre aplicado en ese concepto. La inclusión no cuenta para la cuantía mínima ni reincorpora a los tributos que dependen de ella: se piden aparte.
  • LineaDesglose.IncluidaPorElUsuario (bool, false por defecto, al final). true en la línea aplicada por una inclusión. En la explicación de la cotización (HTTP) esa línea sale con RazonDeAplicacion.AplicoPorDecisionDelUsuario; el bus no lleva RazonDeAplicacion.
  • CalculoTributarioRealizado.ExclusionesSinEfecto e InclusionesSinEfecto (IReadOnlyList<DecisionSinEfecto>?, null es «ninguna», al final). DecisionSinEfecto(ConceptoOrigen, CodigoTributo, Motivo). Son un aviso, no un rechazo: después de un recálculo el desglose cambia y una decisión anterior puede dejar de tener a qué aplicarse. El desglose es el que habría salido sin esa decisión. El motivo (MotivoDecisionSinEfecto) dice por qué: el cálculo no evaluó el tributo en el concepto, la línea ya estaba descartada (excluir) o aplicada (incluir), no trae tarifa y la inclusión no eligió entrada, la entrada no existe, no está vigente o es de otro tributo, o el padre no quedó aplicado.
  • Se rechazan con ComandoInvalido las decisiones mal formadas, que son un defecto de quien arma la solicitud: un mismo par concepto-tributo repetido, un par que se excluye y se incluye a la vez, o un concepto que la solicitud no trae.
  • Una solicitud de desgravamen con decisiones se rechaza con ComandoInvalido. Un desgravamen (OrigenDelDesgravamen) se prorratea desde el registro de la transacción de origen y no recalcula líneas, así que no tiene a qué aplicar una exclusión ni una inclusión: se rechaza en vez de callarla.
  • La confirmación no recibe las decisiones. SolicitarConfirmacionTributaria no cambia: el desglose que confirma el consumidor es la respuesta del cálculo con sus decisiones, y su cálculo de referencia no las conoce. Tanto la línea que el usuario excluyó como la que incluyó cuentan entonces como intervención (el confirmado difiere de la referencia), y la incluida queda en el registro sin la marca IncluidaPorElUsuario.

Miembros de enum agregados (al final, sin cambiar ordinales; ver el índice al final): MotivoExclusion.ExcluidoPorElUsuario — el usuario decidió no aplicar el tributo en el concepto —, RazonDeAplicacion.AplicoPorDecisionDelUsuario y el enum nuevo MotivoDecisionSinEfecto. ExcluidoPorElUsuario viaja por el bus (en Descartados) y por la explicación de la cotización (/explicaciones-de-cotizacion/{referenciaOrigen}): una línea descartada ya no siempre es «descartada por el cálculo». AplicoPorDecisionDelUsuario solo llega por esa lectura HTTP, porque el bus no lleva RazonDeAplicacion; en el bus la línea incluida se reconoce por IncluidaPorElUsuario. Qué hacer: agregar el arm de cada miembro nuevo donde se discrimine su enum; mostrar ExcluidoPorElUsuario como una decisión de quien cotizó, no como una regla; y quien deba pedir una decisión manda el par (ConceptoOrigen, CodigoTributo) del desglose que ya recibió.

0.34.0-beta — la identificación trae siempre el número tal como se escribió

Incompatible: el paquete pasa a depender de Cosmos.Types.IdentificacionesLegales 2.0.0, una versión mayor que el consumidor tiene que adoptar. La identificación de los eventos públicos (CalculoTributarioSolicitado, SolicitarConfirmacionTributaria, PerfilTributarioActualizado) trae siempre numeroOriginal: el número tal como se escribió (con sus separadores y mayúsculas), igual a numero cuando no se escribió distinto. numero no cambia: es el que se compara, se busca y se usa para empatar. numeroOriginal solo sirve para mostrar el número o entregarlo a un sistema que lo conoce escrito así. Un consumidor que preguntaba si NumeroOriginal era nulo debe usar TieneNumeroOriginalDistinto: con 2.0.0 nunca es nulo y la pregunta compila pero siempre da falso.

0.33.0-beta — un rechazo por configuración contradictoria o por un dato que el tributo exige dice quién lo corrige

Varios rechazos del cálculo salían como Indeterminado, que no dice de quién es el defecto. Incompatible sin cambiar ninguna firma: un valor nuevo en MotivoRechazoCalculo rompe a quien haga switch exhaustivo, quien discriminaba por Indeterminado ya no recibe estos casos, y quien discriminaba DatosFaltantes en ConfirmacionTributariaRechazada recibe ComandoInvalido en dos de ellos; además, quien mande conceptos con el mismo id ve su solicitud rechazada.

  • MotivoRechazoCalculo.ConfiguracionIncoherente — al final del enum. La configuración vigente existe pero se contradice o deja sin resolver qué debe pasar (dos condiciones que coinciden sin llevar al mismo resultado, una sustitución cíclica, …). La corrige quien configura: no invita a reintentar sin corregirla.
  • Casos que pasan de Indeterminado a un motivo que dice quién corrige:
    • a ConfiguracionIncoherente, las configuraciones contradictorias;
    • a ComandoInvalido, un dato que el contrato permite omitir pero que el tributo exige (el lugar de ejecución de un tributo localizado, el concepto de pago de una tarifa por concepto de pago);
    • a ConfiguracionTributariaNoEncontrada, la falta del valor de referencia de una unidad (UVT, …) a la fecha de la transacción.
  • Revierte el criterio de 0.29.0-beta para esos dos casos. Aquella versión dejó en Indeterminado lo que depende de la configuración del tenant, para no atribuirle a quien solicita algo que puede no ser suyo. El lugar de ejecución y el concepto de pago son datos que el contrato permite omitir y que solo quien arma la solicitud puede aportar, así que ahora el motivo se lo atribuye.
  • Una solicitud con ids de concepto repetidos se rechaza con ComandoInvalido. Antes se calculaba; ahora el cálculo y la confirmación de un gravamen la rechazan. Cada concepto debe identificarse una sola vez; el detalle nombra el id repetido.
  • La confirmación reclasifica dos de esos casos. Un rechazo del cálculo llega a ConfirmacionTributariaRechazada con uno de dos motivos: ComandoInvalido cuando el defecto es de la solicitud y DatosFaltantes para el resto, incluida la configuración incoherente; los demás motivos de la confirmación no cambian. Sin lugar de ejecución y sin concepto de pago pasan de DatosFaltantes a ComandoInvalido. No cambia ninguna firma. Qué hacer: quien discriminaba Indeterminado revisa estos casos; quien trataba DatosFaltantes de la confirmación como «faltó un dato que corrijo y reintento» trata ComandoInvalido igual; quien mandaba conceptos con el mismo id les da ids distintos.

0.31.0-beta — una jurisdicción puede tarifar por línea y declarar actividades no sujetas

En una jurisdicción con catálogo de actividades, el factor del tributo por actividad ya no es la actividad sino la línea tarifaria que el catálogo le asigna, y el catálogo también puede declarar una actividad no sujeta. Incompatible: un valor nuevo en MotivoExclusion rompe a quien haga switch exhaustivo; el campo nuevo es opcional.

public record LineaDesglose(/* … */, Guid ConceptoOrigen, string? ActividadEconomica = null);
public record LineaConfirmada(/* … */, Guid ConceptoOrigen, string? ActividadEconomica = null);
public record LineaDescartada(/* … */, IReadOnlyList<string>? ActividadesCandidatas = null,
    string? ActividadEconomica = null);

LineaDesgloseExplicada y LineaDescartadaExplicada (las lecturas) llevan el mismo campo, al final.

  • MotivoExclusion.ActividadNoSujeta — al final del enum. La jurisdicción reconoce la actividad del sujeto pasivo y la declara no sujeta: el tributo por actividad se descarta, sin tarifa que liquidar. No se corrige cambiando la solicitud: es lo que la jurisdicción dispone para esa actividad.
  • ActividadEconomica en las cinco líneas — la actividad por la que se tarifó cuando el factor es de actividad. null en las líneas de otros factores. Qué hacer: con catálogo, FactorUtilizado es la línea tarifaria (la llave de la tarifa) y de ella no se reconstruye la actividad: para saber cuál fue, leé ActividadEconomica.
  • La confirmación la devuelve tal cual. La comparación contra el cálculo de referencia incluye la actividad: una línea que la traía y se confirma sin ella se registra como intervención.
  • Sin catálogo de la jurisdicción FactorUtilizado sigue siendo la actividad, y ActividadEconomica la repite: toda línea con factor de actividad la trae, haya o no catálogo.
  • Para elegir entre las variantes con que una jurisdicción parte una clase, la consulta de actividades vigentes acepta jurisdiccion y devuelve variantes (codigo, nombre) en cada actividad; sin ella la respuesta es la de siempre.

0.29.0-beta — la solicitud que no cumple el contrato tiene su motivo

Una solicitud mal armada se rechazaba con motivos que no decían de quién era el defecto: unos casos salían como Indeterminado y otros como DatoInvalido, y la confirmación los mezclaba con DatosFaltantes. Incompatible sin cambiar ninguna firma: un valor nuevo en MotivoRechazoCalculo rompe a quien haga switch exhaustivo, y quien discriminaba por DatoInvalido (la actividad informada vacía) o por DatosFaltantes en la confirmación de una solicitud mal armada ya no lo recibe.

  • MotivoRechazoCalculo.ComandoInvalido — al final del enum. La solicitud no cumple el contrato: un dato obligatorio ausente, vacío o con una forma que el contrato no admite. El defecto está en quien arma la solicitud, así que no invita a reintentar sin corregirla. Lo producen siete causas:

    • del concepto: el identificador vacío, la clasificación tributaria ausente o vacía, y la actividad económica informada vacía;
    • de la solicitud: la identificación emisora ausente, la de la contraparte ausente, la fecha de la transacción por defecto y la lista de conceptos vacía en un gravamen (la propuesta de un desgravamen sin conceptos devueltos sigue rechazándose con DatosFaltantes).

    El detalle nombra el concepto cuando el dato lo permite. Los casos que dependen de la configuración del tenant siguen en Indeterminado: no se le atribuye a quien solicita algo que puede no ser suyo.

  • DatoInvalido queda sin productor. Se conserva porque su ordinal está publicado. Qué hacer: quien lo discriminaba para la actividad informada vacía pasa a ComandoInvalido.

  • ConfirmacionTributariaRechazada reclasifica esos casos. Cuando el motor rechaza la solicitud de confirmación por estar mal armada, el motivo publicado pasa de DatosFaltantes a ComandoInvalido (MotivoRechazoConfirmacion.ComandoInvalido ya existía). Las demás fallas del motor siguen en DatosFaltantes. No cambia ninguna firma ni TransaccionYaConfirmada. Qué hacer: si tratabas DatosFaltantes de la confirmación como «faltó un dato que corrijo y reintento», tratá ComandoInvalido igual.

0.28.0-beta — MotivoRechazoCalculo gana DatosFaltantes; la confirmación de un desgravamen rechaza fuera del margen

Dos cambios de comportamiento en el flujo de desgravamen y uno en la medida de la intervención. Incompatible sin cambiar ninguna firma: un valor nuevo en MotivoRechazoCalculo rompe a quien haga switch exhaustivo, y quien discriminaba por DatoInvalido el caso de la propuesta sin conceptos ya no lo recibe.

  • MotivoRechazoCalculo.DatosFaltantes — al final del enum. La solicitud de cálculo de un desgravamen (OrigenDelDesgravamen) sin ningún concepto devuelto se rechaza con este motivo en vez de DatoInvalido: sin montos no hay prorrateo que proponer. Qué hacer: quien discrimine DatoInvalido para ese caso debe pasar a DatosFaltantes. DatoInvalido se conserva y sigue emitiéndose para otras formas inválidas de la solicitud (hoy, una actividad económica informada vacía).
  • ConfirmacionTributariaRechazada empieza a llevar IntervencionExcedeMargen — un miembro que ya existía en MotivoRechazoConfirmacion. Se emite cuando el DesgloseConfirmado de un desgravamen se aparta de su prorrateo en más de una unidad del último decimal de la escala del país, en el valor o en la base gravable, o en cualquier diferencia de tarifa, tipo, factor o línea (una línea faltante o sobrante cuenta). El desglose de una devolución deriva del gravamen de origen, así que solo puede diferir por redondeo. Qué hacer: confirmar el desglose que propuso el cálculo, sin modificarlo.
  • HuboIntervencion del registro tributario — en un gravamen, una diferencia de hasta esa unidad entre lo confirmado y el cálculo de referencia deja de marcar HuboIntervencion: es redondeo, no intervención. Más allá de la unidad sigue marcándose. El campo se consulta por HTTP; no viaja en el evento.

0.27.0-beta — el concepto informa la actividad económica y el motor no desempata

Un sujeto pasivo con varias actividades económicas generales dejaba la tarifa de un tributo por actividad al orden en que se registraron. Desde esta versión la actividad que corresponde a cada concepto la informa quien registra la transacción, y si no la informa y hay más de una candidata, el motor no elige. Incompatible: un valor nuevo en MotivoExclusion rompe a quien haga switch exhaustivo; los campos nuevos son opcionales.

public record ConceptoCalculo(Guid Id, string ClasificacionTributaria, decimal Monto, string? ConceptoPago,
    string? ActividadEconomica = null);

public record LineaDescartada(Tributo Tributo, decimal BaseGravable, decimal Tarifa, TipoTarifa TipoTarifa,
    decimal Valor, string? FactorUtilizado, Guid ConceptoOrigen, MotivoExclusion MotivoExclusion,
    string? ActividadRechazada = null, IReadOnlyList<string>? ActividadesCandidatas = null);
  • ConceptoCalculo.ActividadEconomica — el código de la actividad del sujeto pasivo que corresponde al concepto, en la solicitud de cálculo y en la de confirmación. null es «no la informo». Vale si el perfil del sujeto pasivo la tiene vigente a la fecha, general o inscrita en cualquier jurisdicción. Vacía o solo con espacios, el cálculo se rechaza con DatoInvalido y la confirmación con DatosFaltantes.
  • MotivoExclusion.ActividadAmbigua — el sujeto pasivo tiene varias actividades candidatas (las inscritas en la jurisdicción del tributo o, si no hay, las generales) y el concepto no informó cuál. La línea trae ActividadesCandidatas en orden ordinal. Qué hacer: indicar una de ellas en ActividadEconomica del concepto y recalcular. La confirmación no se rechaza por este motivo: se registra con el tributo descartado.
  • LineaDescartada.ActividadRechazada — con ActividadNoRegistrada, la actividad que el concepto informó y el perfil no tenía vigente. null si no se informó ninguna.
  • Para saber qué actividades tiene vigentes el sujeto pasivo a la fecha del documento: GET /perfiles-tributarios/{pais}/{tipoDocumento}/{numero}/actividades-economicas?fecha=yyyy-MM-dd (la fecha es obligatoria).

0.26.0-beta — los eventos de salida publican solo lo que un proceso usa al recibirlos

Los eventos de salida del cálculo, la confirmación y la entrega viajaban con el registro completo, y casi nada de eso lo usa quien los recibe: pesaba de más y obligaba a mantener en el bus lo que ya se consulta. Desde esta versión el bus lleva lo que se necesita para reconocer y tratar el resultado, y el resto se consulta. Rompe fuente; en el cable es compatible para quien solo lee lo que se conserva (ver abajo).

Qué sale de cada evento

Evento Sale Queda
CalculoTributarioRealizado ExplicacionDelCalculo; en cada LineaDesglose, RazonDeAplicacion y Explicacion; en cada LineaDescartada, Explicacion ReferenciaOrigen, Aplicados, Descartados, Correlacion
ConfirmacionTributariaRealizada Contexto, EntidadFiscalEmisora, EntidadFiscalContraparte, Jurisdiccion, HuboIntervencion, CalculoDeReferencia, LineasDescartadas, Periodo, Moneda, ConfiguracionUsada, y los montos de cada línea ReferenciaOrigen, RegistroTributarioId, DesgloseConfirmado como lista de LineaEtiquetada
RegistroTributarioEntregado los mismos campos, y los de cada línea que no son su tributo ni sus montos ReferenciaSolicitud, RegistroTributarioId, DesgloseConfirmado como lista de LineaEntregada

Tributo pierde Pais, Familia y MecanismoDeRecaudo. El país lo da el tenant; la familia y el mecanismo de recaudo se consultan. Al salir Pais, Codigo pasa a ser el primer parámetro posicional:

public record Tributo(
    string Codigo,
    string Nombre,
    Naturaleza Naturaleza,
    CaracterRetencion CaracterRetencion,
    TributoDeColombia? DeColombia);

public record LineaEtiquetada(Tributo Tributo, Guid ConceptoOrigen);

public record LineaEntregada(Tributo Tributo, decimal BaseGravable, decimal Tarifa, TipoTarifa TipoTarifa,
    decimal Valor);

LineaDesglose y LineaDescartada conservan sus montos, FactorUtilizado y ConceptoOrigen, y solo pierden la prosa. LineaConfirmada, la línea de la solicitud de confirmación, no cambia, ni tampoco ninguna solicitud entrante.

Tipos nuevos: LineaEtiquetada y LineaEntregada, en Compartidos. En el namespace Cosmos.Impuestos.Contratos.Lecturas —lo que devuelven las consultas HTTP, no el bus— TributoDeLectura, LineaDesgloseExplicada, LineaDescartadaExplicada y ResultadoDelCalculoExplicado: un consumidor del bus no los necesita.

Tipos borrados, porque fuera de estos eventos nada los usa: EntidadFiscal, AtributoSnapshot, Jurisdiccion, ConfiguracionUsada, TarifaUsada, CondicionEvaluada, IndiceDeReferenciaUsado, ConvenioUsado y CuantiaMinima (en Compartidos), y los enums UnidadDeReferencia y ResultadoEvaluacion. Las entradas de más abajo los nombran tal como eran en su versión.

En el cable. Cada campo que se conserva mantiene su nombre y su ruta (tributo.codigo, conceptoOrigen, baseGravable…), así que un consumidor con el paquete anterior lee lo mismo que leía. Lo que se retiró llega sin aviso del deserializador, y de dos maneras según el tipo del campo: los valores (montos, HuboIntervencion, Moneda) llegan en cero, en false o en el primer miembro del enum, o sea un dato falso que parece válido; los objetos (Contexto, las entidades fiscales, Jurisdiccion, ConfiguracionUsada) llegan nulos y fallan al leerse, ya en ejecución. En ningún caso el compilador avisa antes de actualizar el paquete: conviene actualizar antes de depender de esos campos.

Dónde está ahora lo que salió. Se consulta, y las dos consultas HTTP no cambian de forma:

  • La prosa del cálculo y la de cada línea: GET /explicaciones-de-cotizacion/{referenciaOrigen}?correlacion={guid}.
  • El registro confirmado completo —contexto, entidades, jurisdicción, período, moneda, cálculo de referencia, descartadas y procedencia de la configuración—: GET /registros-tributarios/{subDominio}/{transaccionId}. SolicitarRegistroTributario entrega solo el desglose confirmado.

Otros efectos observables

  • La entrega solo rechaza con RegistroInexpresable por un valor de las líneas del desglose confirmado que el contrato no pueda llevar. Ya no rechaza por moneda, dirección fiscal o efecto de condición: esos campos no viajan, y un registro con líneas sanas se entrega.
  • La transacción de origen ya no viaja en la entrega (antes en Contexto.TransaccionId): quien la pide ya la conoce, y el registro se identifica por RegistroTributarioId y ReferenciaSolicitud.
  • La confirmación ya no devuelve los montos: los mandó el consumidor. Devuelve el Tributo que Impuestos definió para cada línea y su concepto de origen, que es lo que sirve para emparejar.

0.25.0-beta — la identidad del tributo se agrupa en un objeto Tributo

La forma vigente de estos tipos está en 0.26.0-beta, arriba. LineaDesglose y LineaDescartada (en Compartidos) dejan de llevar la identidad del tributo como campos sueltos y pasan a agruparla en un único objeto:

public record Tributo(
    Pais Pais,
    string Codigo,
    string Nombre,
    FamiliaTributaria? Familia,
    Naturaleza Naturaleza,
    CaracterRetencion CaracterRetencion,
    MecanismoDeRecaudo? MecanismoDeRecaudo,
    TributoDeColombia? DeColombia);

public record LineaDesglose(
    Tributo Tributo, decimal BaseGravable, decimal Tarifa, TipoTarifa TipoTarifa, decimal Valor,
    string? FactorUtilizado, Guid ConceptoOrigen,
    RazonDeAplicacion? RazonDeAplicacion = null, Explicacion? Explicacion = null);

public record LineaDescartada(
    Tributo Tributo, decimal BaseGravable, decimal Tarifa, TipoTarifa TipoTarifa, decimal Valor,
    string? FactorUtilizado, Guid ConceptoOrigen, MotivoExclusion MotivoExclusion,
    Explicacion? Explicacion = null);

Se van los campos sueltos CodigoTributo, NombreTributo, Naturaleza, CaracterRetencion, MecanismoDeRecaudo, Familia y TributoDeColombia (este último se llama DeColombia dentro de Tributo, mismo enum). Tributo es el primer parámetro posicional de los dos records, así que rompe fuente a quien construya una LineaDesglose/LineaDescartada, y cambia la forma serializada para quien lea por clave (codigoTributo → tributo.codigo, y así con el resto).

  • Pais (Cosmos.Types.Paises.Pais) es nuevo: antes ninguna de las dos líneas lo llevaba. Se congela en el registro al momento del cálculo, desde el país de la configuración con que se calculó.

  • Fuera de Colombia la identidad del tributo es (Pais, Codigo), sin un campo por país: un campo TributoDe<País> por país sería una unión discriminada disfrazada de N nulables mutuamente excluyentes.

  • DeColombia conserva el mismo criterio de derivación por código que tenía TributoDeColombia antes de este cambio — ver «Riesgo aceptado» en «DeColombia en Tributo» abajo.

  • Absorbe #362: LineaDescartada nunca había llevado Familia ni el tributo de Colombia — solo la aplicada los tenía. Con el objeto compartido, el descarte los trae sin trabajo aparte.

  • La entrada de confirmación es una línea angosta: SolicitarConfirmacionTributaria.DesgloseConfirmado pasa a ser una lista de LineaConfirmada, que nombra el tributo solo por código:

    public record LineaConfirmada(string CodigoTributo, decimal BaseGravable, decimal Tarifa,
        TipoTarifa TipoTarifa, decimal Valor, string? FactorUtilizado, Guid ConceptoOrigen);
    

    El resto del Tributo lo define Impuestos y la ConfirmacionTributariaRealizada lo devuelve completo. En un gravamen sale del cálculo de referencia para ese tributo y ese concepto; si no está, del descarte que el consumidor reincorpora; y si tampoco, del catálogo de tributos vigente del tenant, lo que cuenta como intervención manual (huboIntervencion = true). Un código que no está en ninguno de los tres se rechaza con ComandoInvalido. En un desgravamen sale del registro origen, y un código que el origen no tiene sigue rechazándose con ConceptoNoExisteEnOrigen.

  • Corte directo, sin período de convivencia: no hay una versión intermedia con los campos sueltos deprecados conviviendo con Tributo.

0.24.0-beta — un pedido de cálculo puede ser un desgravamen; confirmar uno cambia de contrato

  • El camino de cálculo es aditivo; el de confirmación de un desgravamen NO lo es. Un solicitante de CalculoTributarioSolicitado que no envía OrigenDelDesgravamen no ve ningún cambio. Pero quien ya confirma desgravámenes con SolicitarConfirmacionTributaria sí tiene que shippear código, sin tocar ningún campo nuevo: es incompatible por la misma regla de arriba (obliga a shippear código para seguir siendo correcto, compile o no), y el precedente es 0.23.0-beta: lo que era ConfirmacionTributariaRealizada pasa a ser ConfirmacionTributariaRechazada en tres casos nuevos. Concretamente:
    • Conceptos pasa a leerse, y no puede llegar vacío. Antes la referencia se derivaba prorrateando el DesgloseConfirmado y Conceptos no se leía. Ahora, si Conceptos viene vacío, la confirmación se rechaza con MotivoRechazoConfirmacion.DatosFaltantes. Cubre también el concepto individual: todo concepto que venga en DesgloseConfirmado tiene que venir en Conceptos, porque su monto devuelto es lo que se descuenta del saldo de la transacción origen; si falta, la confirmación se rechaza con DatosFaltantes.
    • DesgravamenExcedeSaldo gana productor. Una devolución que, sumada a las anteriores del mismo concepto, supera lo que ese concepto se gravó en la transacción origen —antes se confirmaba igual— ahora se rechaza.
    • ConceptoNoExisteEnOrigen también sale de Conceptos, además de DesgloseConfirmado.
    • La referencia contra la que se compara lo confirmado cambia: se deriva de Conceptos con cierre por residuo, no del DesgloseConfirmado sin más.
  • CalculoTributarioSolicitado gana OrigenDelDesgravamen ({ SubDominio, TransaccionId }), opcional y al final — esta parte sí es aditiva. Si viene, la solicitud es la cotización de un desgravamen sobre esa transacción: Impuestos no ejecuta el motor, busca el RegistroTributario de la transacción de origen y deriva el desglose por prorrateo (un factor por concepto, R((valor × devuelto) ÷ gravado), hijos porcentajeDePadre incluidos, con el redondeo del país). Sin el campo, el flujo es el mismo de siempre (gravamen, motor). Una solicitud de desgravamen sin ningún concepto devuelto se rechaza con MotivoRechazoCalculo.DatoInvalido.
  • MotivoRechazoCalculo gana OrigenNoEncontrado, ConceptoNoExisteEnOrigen y DesgravamenExcedeSaldo, al final — ver «Miembros de enum agregados» abajo. Estos tres sí son aditivos: MotivoRechazoCalculo es el enum que crece sin coordinación (ver la nota antes de la tabla).
  • Cada desgravamen confirmado descuenta de un saldo por transacción de origen. El saldo sirve tanto a una compra como a una venta desgravada (el registro no distingue dirección). Proponer o confirmar un monto que, sumado a lo ya devuelto de un concepto, supere lo que ese concepto se gravó se rechaza (DesgravamenExcedeSaldo en el cálculo y en la confirmación) en vez de aceptarse — la validación es por monto del concepto, no por tributo dentro del concepto. La última devolución que completa un concepto sigue pasando por esa misma guarda; lo que cambia es que la referencia revierte, para cada tributo del concepto, el valor exacto que le queda por revertir (residuo) en vez del prorrateo redondeado, así que devoluciones sucesivas cuadran al centavo.

0.23.0-beta — una clasificación que no rige rechaza el cálculo, y los códigos vuelven a ser los del libro

  • Los códigos que viajan son los de erp-definiciones, tal cual. Se retira la homologación a códigos internos de 0.20.0-beta: clasificaciones, condiciones, regímenes y definiciones de calidad llegan en las trazas del cálculo y en las lecturas con el código que publica el libro (GRAV_19, perteneceRegimenIVA, RTF-01a, ZF-BAQ), que es también el que se envía al pedir un cálculo o escribir un perfil. Quien guardó o compara códigos internos tiene que volver a los del libro; no hay tabla de traducción en ningún sentido, y un nombre de calidad que no está en el catálogo vigente del país se rechaza al escribir el perfil.
  • MotivoRechazoCalculo gana ClasificacionNoVigente, al final. Un concepto cuya clasificación tributaria no existe en la configuración del país, o no está vigente a la fecha de la transacción, ya no produce un desglose en cero con todos los tributos descartados por ClasificacionExcluida: el cálculo se rechaza con este motivo, y el detalle nombra el código, el país y la fecha. Quien hoy recibe ese cero por un código mal escrito o de otro país empieza a recibir CalculoTributarioRechazado. Lo exige el libro de definiciones (R32 e I26).

0.22.0-beta — FechaTransaccion es un día

FechaTransaccion pasa de DateTimeOffset a DateOnly en CalculoTributarioSolicitado y en ContextoTransaccional, que viaja en SolicitarConfirmacionTributaria, RegistroTributarioEntregado y ConfirmacionTributariaRealizada.

  • En el cable es "2025-12-31" (yyyy-MM-dd). Un mensaje que siga mandando la fecha con hora ("2025-12-31T00:00:00-05:00") no deserializa: la solicitud no llega al cálculo y no hay respuesta, ni CalculoTributarioRechazado para un cálculo ni ConfirmacionTributariaRechazada para una confirmación. No hay versión intermedia que acepte los dos formatos, así que quien produce estos eventos tiene que actualizar el paquete y mandar el día de la transacción.
  • Por qué. El modelo de dominio de Impuestos en erp-definiciones (dominio/impuestos/modelo-dominio.md) tipa la fecha de la transacción como date, y para calcular solo importa el día: con él se eligen la configuración vigente y los índices de referencia (la UVT de ese año). Con DateTimeOffset el día se leía con el reloj del offset recibido, así que el mismo instante mandado desde otra zona podía caer en otro día, y el 31 de diciembre en otro año de UVT.
  • En la salida el día publicado es el que el cálculo usó, también en los registros confirmados antes de esta versión. Quien solo consume y sigue con el paquete anterior no se rompe: "2025-12-31" deserializa en su DateTimeOffset como la medianoche de ese día con el offset local de su máquina.
  • PerfilTributarioActualizado.FechaDelCambio sigue siendo DateTimeOffset: es cuándo quedó persistido un cambio, un instante y no un día.

Para el front (API HTTP, fuera de este paquete): GET /registros-tributarios/{subDominio}/{transaccionId} sirve contexto.fechaTransaccion como día, "2025-12-31", en vez de un instante con offset. Es el día con que se calculó, también en los registros anteriores. Se muestra como día, sin hora: dayjs(valor) lo lee como la medianoche local y conserva el día, pero new Date(valor) lo lee como medianoche UTC y en Colombia lo muestra el día anterior.

0.20.0-beta — la configuración estándar sale de ediciones publicadas

El estándar fiscal de cada país deja de ser una siembra y pasa a ser una edición inmutable, con número y huella, construida desde el contenido que publica el equipo fiscal. Lo que cambia para quien consume:

  • Códigos internos (revertido en 0.23.0-beta). Los códigos de clasificaciones, condiciones, regímenes, definiciones y calidades fiscales que viajan en los eventos (las trazas del cálculo) son los internos de la tabla de homologación, en mayúsculas con guion bajo en vez del código del libro (GRAV_19, regimenTributario). Quien guarde o compare códigos tiene que migrarlos con esa tabla, que vive en el repositorio de Impuestos, un archivo por catálogo y país: contenido/declarado/ (sección homologacion: código de la fuente → código interno). Los cálculos y perfiles anteriores conservan el nombre de la fuente y se siguen interpretando.
  • Vigencias semiabiertas. Toda vigencia es [desde, hasta): hasta es el primer día en que deja de regir. Un tramo con hasta = desde nunca rigió.
  • Se retira la réplica de clasificaciones por el bus: ClasificacionTributariaPublicada, ClasificacionTributariaDesactivada y ReplicaDeClasificacionesSolicitada, con sus topics. Ningún servicio la consumía: las clasificaciones de un país se leen por HTTP en la API de consultas. Las suscripciones que el Terraform de otros repos crea sobre esos topics quedan sin publicador y se pueden borrar.
  • Configuracion se conserva (Estandar / Personalizado).

Para el front (API HTTP, fuera de este paquete; detalle en el ADR-004 «Contrato afectado»): las excepciones se registran con los endpoints nuevos (decisiones de tratamiento con vigencia, suprimir y cambiar el fin de una tarifa, convenios), la cabecera de un tributo o una clasificación de la edición no se edita (422), desactivar acepta desde, las lecturas traen todos los tramos y marcan las supresiones, y la bandeja de revisión se consulta en GET /bandeja-de-revision/{pais}.

0.19.0-beta — MotivoExclusion gana TarifaNoConfigurada y ConvenioExime

Dos motivos de descarte nuevos, al final del enum, así que los existentes conservan su ordinal. Viajan en LineaDescartada.MotivoExclusion y en CondicionEvaluada.MotivoDescarte. Qué significa cada uno está en la tabla de «Miembros de enum agregados».

Es incompatible aunque no cambie ninguna firma: quien muestra un descarte lo rotula según su motivo, y un motivo que no reconoce cae al rótulo por defecto sin error. El descarte sigue trayendo su Explicacion, que sí nombra la causa; lo que se pierde es el rótulo corto.

En la misma versión, aditivo: TarifaUsada porta ConvenioUsado? Convenio, nullable con default y al final. Viene poblado cuando la tarifa la fijó un convenio para evitar la doble imposición, con el país contraparte (código ISO 3166-1 alfa-2, porque puede ser un país donde el productor no opera), el nombre del convenio y su instrumento normativo. TarifaUsada viaja en la ConfiguracionUsada de RegistroTributarioEntregado y de ConfirmacionTributariaRealizada.

0.18.0-beta — TipoTarifa gana PorMil

Una tarifa cuyo valor se lee por mil. Viaja en TarifaUsada, LineaDesglose y LineaDescartada. Miembro nuevo al final del enum, así que Porcentaje y Especifica conservan su ordinal.

La unidad de la Tarifa depende del tipo, y con PorMil deja de ser una fracción:

TipoTarifa Qué es Tarifa Ejemplo
Porcentaje fracción decimal 0.19 es 19 %
Especifica monto absoluto en la moneda del registro 1900 son 1.900
PorMil el número en ‰, no una fracción 4.14 es 4,14 ‰

Rompe a quien haga switch exhaustivo sin arm por defecto y, más callado, a quien formatee la tarifa según su tipo: el tipo que no reconoce cae al caso por defecto y pierde la unidad sin error, y quien lo trate como Porcentaje (× 100) muestra 4.14 como 414 %. Es ese segundo caso —el que ninguna firma delata— el que la vuelve incompatible y no aditiva.

0.15.0-beta — cierre de la reestructuración a agregados por elemento

Tres cambios en la misma versión.

MotivoRechazoCalculo.AtributoFiscalFaltante → CalidadFiscalFaltante. Conserva su ordinal, así que el wire no cambia y quien deserialice por número no ve nada. Rompe fuente a quien nombre el miembro en código; se arregla con el rename.

ConfiguracionUsada pierde CatalogoId. Viaja en RegistroTributarioEntregado.ConfiguracionUsada y en ConfirmacionTributariaRealizada.ConfiguracionUsada, los dos únicos eventos que la portan; ningún evento de cálculo la lleva. El catálogo por país dejó de existir —la configuración tributaria pasó a un stream por elemento— así que el campo venía viajando Guid.Empty. Es el primer parámetro posicional del record, así que rompe fuente a quien construya una ConfiguracionUsada, y deja de serializar la clave catalogoId para quien la lea.

La misma remoción aplica al response de GET /registros-tributarios/{subDominio}/{transaccionId}, que no lleva versión semántica: ahí la clave desaparece sin más señal que esta nota. No hay sustituto porque no hay dato que sustituir. La procedencia de la configuración —qué stream de tributo, estándar o del tenant, produjo cada línea— no viaja por el bus: se obtiene por ese mismo GET, en configuracionUsada.tributosUsados, que llega nulo en los registros cuya traza no la congeló (los anteriores a la reestructuración).

ConfiguracionTributariaNoEncontrada vuelve a emitirse. Entre el despliegue de la reestructuración y esta versión, un cálculo contra un país sin tributos configurados no rechazaba: publicaba CalculoTributarioRealizado con el desglose vacío, o sea un cálculo que se veía exitoso y no llevaba nada. Vuelve a llegar CalculoTributarioRechazado con ese motivoCodigo y un motivoDetalle que nombra qué configuración falta. No agrega ningún miembro al enum: lo que cambia es que el valor vuelve a llegar. Rompe a quien haya adaptado su código a lo observado en esa ventana, tratando el desglose vacío como respuesta válida. Que el desglose quede vacío sigue siendo un desenlace legítimo cuando la configuración sí está y ningún tributo aplica: ése no rechaza.

0.14.0-beta — Naturaleza gana Provision

La tercera dirección de un tributo frente al valor de la operación. Viaja en LineaDesglose.Naturaleza y LineaDescartada.Naturaleza. Miembro nuevo al final del enum, así que los dos valores existentes conservan su ordinal.

Rompe a quien haga switch exhaustivo sin arm por defecto y, más callado, a quien particione un desglose en dos grupos por naturaleza: una partición binaria deja las provisiones afuera sin error, y es ese segundo caso —el que ninguna firma delata— el que la vuelve incompatible y no aditiva.

La configuración fiscal estándar todavía no declara ningún tributo como provisión, pero el alta y la modificación de tributo sí admiten la naturaleza nueva: una configuración propia puede emitirla en cuanto esta versión esté desplegada del lado del productor. Conviene adoptarla antes de eso, no después.

0.12.0-beta — Explicacion queda en las cinco partes del argumento

{ Efecto, Hechos, Regla, Contraste?, Advertencia? }. Se van Causa y Detalle, la prosa corrida que 0.11.0 conservó deprecada; quien quiera prosa encadena las partes que necesite. Y se va Autoridad, que nunca pudo ser no-nula: ninguna traza congelada porta la fuente normativa de la regla. El día que el catálogo la incorpore, Autoridad vuelve como campo nullable al final — aditivo para quien la lea, pero cambia la firma del constructor, así que quien construya una Explicacion recompila.

Contraste y Advertencia quedan sin valor por omisión: siguen siendo nullables, pero se pasan siempre. Rompe a quien lea Causa, Detalle o Autoridad, y a quien construya una Explicacion. Impuestos no persiste la explicación (la regenera on-read desde la traza congelada), así que no hay migración de este lado; un consumidor que la haya guardado encuentra las claves de más al deserializar.

0.11.0-beta — Explicacion pasa a forma estructurada

De (Causa, Detalle) a { Efecto, Hechos, Regla, Autoridad?, Contraste?, Advertencia? }: el resultado observable para el lector, los hechos que lo fundan, la norma que autoriza el paso de unos a otro, su fuente cuando la traza la conserva, el desenlace alternativo, y el alcance que la explicación no verificó.

Causa y Detalle se conservan deprecados y derivados de esa estructura, para que un consumidor que todavía no la renderice siga recibiendo texto coherente; se remueven en 0.12.0. Rompe a quien construya una Explicacion; no rompe a quien solo la lea.

0.7.0-beta — el país de los eventos públicos pasa a VO validado

Jurisdiccion.Pais (en ClasificacionTributariaPublicada / Desactivada) y ReplicaDeClasificacionesSolicitada.Pais pasan de enum a VO con wire objeto { "codigo": "CO" }. Cambia la forma serializada del campo país — coordinación cross-BC con los consumidores.

0.6.0-beta — la identidad fiscal pasa al tipo compartido IdentificacionLegal

La identificación del emisor y la contraparte deja de ser el modelo local Compartidos.IdentificacionFiscal (país como enum + número plano) y pasa a ser Cosmos.Types.IdentificacionesLegales.IdentificacionLegal (del paquete Cosmos.Types). Afecta a CalculoTributarioSolicitado, SolicitarConfirmacionTributaria y al modelo EntidadFiscal de las respuestas.

Cambia el wire (un consumidor de una versión previa se rompe hasta adoptar este formato):

  • El país viaja como objeto { "codigo": "CO" } (antes era un entero del enum).
  • El número se acompaña de tipo de documento y dígito de verificación, y se valida contra el catálogo del país (tipo país-scoped × número × DV). La igualdad de la identidad recae en (tipo, número, país); el dígito de verificación no participa.

El borde de Impuestos resuelve el perfil por la clave canónica {país}:{tipoDocumento}:{número}, así que el número que se envíe debe corresponder al del alta del perfil (el paquete normaliza el número al construir la identidad).

Miembros de enum agregados

Índice completo de las altas de miembro, que es la clase de cambio que no delata ninguna firma. Ningún alta cambia una firma y todas se agregan al final, así que los valores existentes conservan su ordinal y quien deserialice por número no cambia. Aun así rompen a quien haga switch exhaustivo sin arm por defecto: si ese arm lanza, el mensaje muere en vez de degradarse.

Por eso la columna «Etiqueta» no siempre dice lo mismo que el efecto. Una versión se etiqueta incompatible cuando el alta abre además un modo de rotura que ninguna firma delata —una partición o un formateo por tipo que deja el miembro nuevo afuera sin error—, y aditiva cuando el switch es el único riesgo, porque el consumidor no ramifica por ese enum — pero para un consumidor con switch exhaustivo las dos obligan a shippear código igual. Si tenés un arm por defecto que lanza, tratá las dos filas como incompatibles.

MotivoRechazoCalculo es el caso donde esto pasa más seguido: su conjunto es interno al productor para su observabilidad —el consumidor no ramifica por el código, solo lo registra o muestra el detalle—, así que un alta que no reclasifica nada crece sin coordinación, siempre agregando al final. El alta que saca casos de Indeterminado o de otro motivo (0.28.0-beta, 0.29.0-beta, 0.33.0-beta, 0.43.0-beta, 0.44.0-beta) se rotula incompatible: quien discriminaba el motivo anterior deja de recibirlos.

Versión Etiqueta Enum Miembro Qué significa
0.44.0-beta incompatible MotivoRechazoCalculo TarifasAlternativasIncoherentes Dos condiciones que cambian la tarifa del mismo tributo coincidieron a la vez y señalan tarifas distintas (dos líneas alternativas, o una línea y la del convenio); el detalle nombra las dos condiciones. La corrige quien configura. Hasta 0.43.0-beta llegaba como ConfiguracionIncoherente.
0.43.0-beta incompatible MotivoRechazoCalculo ConceptoPagoNoVigente El concepto informa un concepto de pago que el catálogo del país no tiene o que el tenant suprimió a la fecha de la transacción; el detalle nombra el código, el país y la fecha. Lo corrige quien arma la solicitud o quien configura. Hasta 0.42.0-beta llegaba como TarifaNoVigente (tributo de cobertura abierta), se descartaba en silencio (cobertura cerrada) o se ignoraba (ningún tributo tarifaba por concepto de pago).
0.41.0-beta incompatible MotivoRechazoCalculo CantidadNoInformada, UnidadIncompatible El concepto resuelve una entrada de tarifa por unidad y no informa la cantidad, o la informa en otra unidad; el detalle nombra el concepto, el tributo y la unidad. Los corrige quien arma la solicitud.
0.41.0-beta incompatible TipoTarifa PorUnidad Tarifa por unidad: valor = cantidad × tarifa y la base de la línea es una cantidad (con su UnidadDeMedida). Quien dividía valor entre base para obtener una tasa debe contemplarlo.
0.41.0-beta incompatible Efecto SinCuantiaMinima La condición que gana aplica el tributo sin comparar contra el umbral de cuantía mínima.
0.41.0-beta incompatible TributoDeColombia SobretasaCombustibles La línea de la sobretasa a los combustibles de la edición de Colombia sale homologada.
0.39.0-beta incompatible MotivoRechazoCalculo IndiceNoVigente La cuantía mínima de un tributo está expresada en una unidad de referencia (la UVT, …) sin índice vigente a la fecha de la transacción; el detalle nombra la unidad y la fecha. Hasta 0.38.0-beta llegaba como ConfiguracionTributariaNoEncontrada.
0.39.0-beta incompatible MotivoRechazoConfirmacion IndiceNoVigente El cálculo de referencia de la confirmación cae en el mismo caso. Hasta 0.38.0-beta llegaba como DatosFaltantes.
0.38.0-beta incompatible MotivoRechazoCalculo CuantiaIncoherente Dos conceptos de un mismo grupo de cuantía mínima por transacción (mismo tributo, factor y jurisdicción) resolvieron entradas de tarifa con umbrales distintos; solo ocurre con una condición propia que cambia la tarifa. La corrige quien configura. Antes ese caso llegaba como ConfiguracionIncoherente. Sin productor desde 0.44.0-beta: el miembro y su ordinal se conservan.
0.37.0-beta incompatible MotivoExclusion ExcluidoPorElUsuario Reservado: no se emite desde 0.38.0-beta; solo aparece al explicar una cotización calculada antes. El usuario decidió no aplicar el tributo en el concepto (Exclusiones de la solicitud). La línea conserva base y tarifa, con Valor en 0, para poder reincorporarla. Es incompatible porque quien tratara todo descarte como una decisión del cálculo ahora recibe uno que no lo es.
0.37.0-beta incompatible RazonDeAplicacion AplicoPorDecisionDelUsuario Reservado: no se emite desde 0.38.0-beta; solo aparece al explicar una cotización calculada antes. La línea se aplicó porque el usuario la pidió (Inclusiones de la solicitud) siendo que el cálculo la había descartado. Solo llega por la explicación de la cotización (HTTP); hasta 0.37.0-beta, en el bus la línea llevaba LineaDesglose.IncluidaPorElUsuario. Quien explique por qué se aplicó un tributo con un switch sin arm por defecto debe cubrirlo.
0.37.0-beta aditiva MotivoDecisionSinEfecto TributoNoEvaluado, YaDescartado, YaAplicado, SinTarifa, EntradaNoEncontrada, EntradaNoVigente, EntradaDeOtroTributo, PadreNoAplicado Retirado en 0.38.0-beta junto con los campos que lo llevaban. Enum nuevo: por qué el cálculo no pudo aplicar una decisión del usuario. Solo viaja en ExclusionesSinEfecto e InclusionesSinEfecto, campos nuevos, así que nadie lo discrimina antes de esta versión.
0.35.0-beta aditiva MotivoRechazoCalculo ResultadoExcedeSizeMaximo El resultado del cálculo no cabe en el mensaje que lo entrega (el máximo es de 240 KiB). Antes ese caso no tenía desenlace: el envío fallaba y el solicitante esperaba indefinidamente. El tamaño medido y el máximo van en MotivoDetalle. No invita a reintentar sin cambiar la transacción: el mismo cálculo produce el mismo resultado. Ver 0.35.0-beta arriba.
0.33.0-beta incompatible MotivoRechazoCalculo ConfiguracionIncoherente La configuración vigente existe pero se contradice o deja sin resolver qué debe pasar (dos condiciones que coinciden sin llevar al mismo resultado, una sustitución cíclica, …). La corrige quien configura: no invita a reintentar sin corregirla. Antes esos casos salían como Indeterminado. Ver 0.33.0-beta arriba.
0.31.0-beta incompatible MotivoExclusion ActividadNoSujeta La jurisdicción reconoce la actividad del sujeto pasivo y la declara no sujeta: el tributo por actividad se descarta sin tarifa que liquidar, y la línea trae la actividad en ActividadEconomica. Ver 0.31.0-beta arriba.
0.29.0-beta incompatible MotivoRechazoCalculo ComandoInvalido La solicitud no cumple el contrato: un dato obligatorio ausente, vacío o con una forma que el contrato no admite. El defecto está en quien arma la solicitud: no invita a reintentar sin corregirla. Antes esos casos salían como Indeterminado o DatoInvalido. Ver 0.29.0-beta arriba.
0.28.0-beta incompatible MotivoRechazoCalculo DatosFaltantes La solicitud de un desgravamen no trae ningún concepto devuelto: sin montos no hay prorrateo que proponer. Antes ese caso salía como DatoInvalido. Ver 0.28.0-beta arriba.
0.27.0-beta incompatible MotivoExclusion ActividadAmbigua El sujeto pasivo tiene varias actividades económicas candidatas y el concepto no informó cuál le corresponde: el tributo por actividad se descarta con las candidatas en ActividadesCandidatas, en orden ordinal. Se resuelve indicando una en ConceptoCalculo.ActividadEconomica y recalculando. Ver 0.27.0-beta arriba.
0.24.0-beta aditiva MotivoRechazoCalculo OrigenNoEncontrado La solicitud pide un desgravamen por prorrateo (OrigenDelDesgravamen) y no existe un registro tributario confirmado para la transacción de origen que indica.
0.24.0-beta aditiva MotivoRechazoCalculo ConceptoNoExisteEnOrigen Un concepto del desgravamen no se gravó en la transacción de origen, el registro no conserva su monto, o se gravó con monto 0 — en los tres casos falta el denominador del que derivar el prorrateo.
0.24.0-beta aditiva MotivoRechazoCalculo DesgravamenExcedeSaldo El desgravamen propuesto, sumado a lo ya devuelto de ese mismo concepto, superaría lo que ese concepto se gravó en la transacción origen (la validación es por monto del concepto, no por tributo dentro del concepto).
0.23.0-beta incompatible MotivoRechazoCalculo ClasificacionNoVigente Un concepto de la solicitud referencia una clasificación tributaria que no existe en la configuración del país o no está vigente a la fecha de la transacción. Antes el cálculo salía en cero, con todos los tributos descartados por ClasificacionExcluida, indistinguible de un concepto que de verdad no grava. Es incompatible aunque el consumidor no ramifique por el motivo: lo que era un CalculoTributarioRealizado pasa a ser un CalculoTributarioRechazado. El defecto está en la solicitud (el código): se corrige del lado del solicitante. Ver 0.22.0-beta arriba.
0.19.0-beta incompatible MotivoExclusion TarifaNoConfigurada El tarifario de un tributo que declara cerrada su cobertura no tiene ninguna tarifa, en ninguna vigencia, para el concepto: el concepto no es de su alcance y la línea se descarta en vez de rechazar el cálculo. Cualquier otra tarifa faltante —en un tributo de cobertura abierta, o un hueco de vigencia en un concepto que el tarifario sí incluye— sigue rechazando el cálculo con TarifaNoVigente. Ver 0.19.0-beta arriba.
0.19.0-beta incompatible MotivoExclusion ConvenioExime El convenio para evitar la doble imposición con el país de residencia fiscal del beneficiario fija tarifa cero para la retención. La línea se descarta, y la condición del convenio queda en la traza con este mismo motivo. Ver 0.19.0-beta arriba.
0.18.0-beta incompatible TipoTarifa PorMil Una tarifa cuyo valor se lee por mil. La Tarifa de la línea llega en ‰, no como fracción: 4.14 significa 4,14‰, y quien la trate como porcentaje (× 100) la muestra como 414 %. Viaja en TarifaUsada, LineaDesglose y LineaDescartada. Además del switch, rompe —más callado— a quien formatee la tarifa según su tipo: un tipo que no reconoce cae al caso por defecto y pierde la unidad sin error. Ver 0.18.0-beta arriba.
0.17.0-beta aditiva MotivoRechazoCalculo JurisdiccionNoEncontrada Una ubicación de la transacción (sede emisora, sede de contraparte o lugar de ejecución) no referencia ninguna jurisdicción vigente del país a la fecha, así que el cálculo no se puede computar. Antes ese caso no tenía desenlace: la traducción lanzaba, el mensaje moría en dead-letter y el solicitante esperaba indefinidamente — mismo defecto que 0.13.0-beta arregló para MotivoRechazoEntrega. El defecto está en los datos maestros de jurisdicciones del productor, no en la solicitud: no invita a reintentar sin corregirlos.
0.14.0-beta incompatible Naturaleza Provision La tercera dirección de un tributo frente al valor de la operación (qué es y qué se sigue de ella está en el propio enum, que es su sede). Viaja en LineaDesglose.Naturaleza y LineaDescartada.Naturaleza. Además del switch, rompe —más callado— a quien particione un desglose en dos grupos por naturaleza: la partición binaria deja las provisiones afuera sin error. Ver 0.14.0-beta arriba.
0.13.0-beta aditiva MotivoRechazoEntrega RegistroInexpresable El registro existe y la solicitud era válida, pero porta un valor sin equivalente conocido en un campo que el contrato necesita. Antes ese caso no tenía desenlace: la traducción lanzaba, el mensaje moría en dead-letter y el solicitante esperaba indefinidamente. A diferencia de RegistroNoEncontrado, no invita a reintentar: el defecto está en lo ya persistido del lado del productor.

Otras versiones agregaron eventos o campos, no miembros, y fueron aditivas sin reserva: 0.21.0-beta (LineaDesglose portó Familia y TributoDeColombia, los dos nullable con default; hoy Familia no viaja en el bus y DeColombia vive en Tributo, ver «DeColombia en Tributo» más abajo), 0.16.0-beta (evento saliente nuevo PerfilTributarioActualizado; ver «Aviso de cambio del perfil tributario» más abajo), 0.10.0-beta (CalculoTributarioRealizado portó ExplicacionDelCalculo, que 0.26.0-beta retiró) y 0.8.0-beta (los tres eventos del flujo de cálculo portan un Guid Correlacion, token opaco que el productor devuelve verbatim para que el solicitante descarte respuestas superseded; los mensajes legacy deserializan a Guid.Empty).

0.30.0-beta no agrega tipos ni miembros y es aditiva sin reserva: una solicitud con Ubicaciones o Conceptos ausentes deja de quedar sin respuesta. Antes la traducción lanzaba, el mensaje moría en dead-letter y el solicitante esperaba indefinidamente. Ahora CalculoTributarioRechazado sale con ComandoInvalido en un gravamen y con DatosFaltantes en la propuesta de un desgravamen sin conceptos; ConfirmacionTributariaRechazada sale con ComandoInvalido, salvo la confirmación de un desgravamen sin conceptos, que sale con DatosFaltantes. La propuesta de un desgravamen sin Ubicaciones sigue aceptándose. Los dos motivos ya los lee cualquier consumidor de 0.29.0-beta.

0.35.0-beta agrega un miembro a MotivoRechazoCalculo, ResultadoExcedeSizeMaximo (al final, ordinal 14), y es aditiva: no reclasifica ningún caso que hoy llegue con otro motivo. Un cálculo cuyo resultado no cabe en el mensaje que lo entrega (el máximo es de 240 KiB) ya no falla mudo en el envío con el solicitante esperando: publica CalculoTributarioRechazado con este motivo. El tamaño medido y el máximo van en MotivoDetalle, así que quien muestre el detalle no necesita ramificar por el motivo. No invita a reintentar: la misma transacción produce el mismo resultado, y el detalle dice que la transacción no se puede partir sin alterar el cálculo. Solo rompe a quien haga switch exhaustivo sobre MotivoRechazoCalculo sin arm por defecto.

0.32.0-beta agrega un tipo y un campo opcional, y es aditiva sin reserva: Compartidos.Jurisdiccion(Codigo, Nombre) y JurisdiccionSubnacional al final de LineaDesglose, LineaDescartada, LineaDesgloseExplicada y LineaDescartadaExplicada. Es la jurisdicción en que el cálculo localizó el tributo de la línea, con el nombre que tenía al calcular, para mostrarla sin consultar el catálogo. Es null en los tributos nacionales, en los descartes previos a la localización (JurisdiccionNoAplica, ClasificacionExcluida), en los cálculos anteriores al campo y en la propuesta de un desgravamen: sus líneas se prorratean del desglose confirmado, que no congela la jurisdicción. La línea que confirma el consumidor (LineaConfirmada) no cambia, y la comparación contra el cálculo de referencia no usa el campo: confirmar sin cambios sigue sin contar como intervención.

0.38.0-beta agrega también un campo opcional, aditivo sin reserva (la versión es incompatible por lo que describe su entrada en «Cambios incompatibles»): AsumidaHabitualmente (bool, false por defecto, al final) en LineaDesglose y en LineaDescartada, y el mismo campo en las dos líneas de lectura de la explicación de la cotización (LineaDesgloseExplicada y LineaDescartadaExplicada, HTTP). Es un dato del catálogo, no la decisión: el tributo declara que el pagador suele asumir esa retención (solo los tributos sustractivos pueden declararlo; en Colombia, RETEFUENTE_EXTERIOR), y quien consume la propone asumida y decide por documento. Impuestos no conoce la decisión y el motor no usa el atributo: solo lo copia a la línea, aplicada o descartada.

No viaja en Tributo, TributoDeLectura ni LineaEntregada: la entrega y el Tributo compartido no cambian. En los registros anteriores al campo es false, y ahí no distingue «no se asume» de «registro viejo». No cambia ningún miembro ni ordinal.

0.36.0-beta agrega un campo opcional y es aditiva sin reserva: Tributo.CodigoPadre (string?, null por defecto, al final), y el mismo campo en TributoDeLectura. Es el código del tributo del que depende el tributo de la línea, tal como rigió para ese cálculo —no el del catálogo de hoy—, y es null si el tributo no depende de otro. Llega a toda línea que lleva un Tributo (LineaDesglose, LineaDescartada, LineaEntregada, LineaEtiquetada) y a las dos líneas de lectura. Sirve para que quien arma o valida un desglose exija que un tributo hijo no viaje sin su padre en el mismo concepto, sin consultar el catálogo: RIVA trae CodigoPadre = "IVA" y IVA lo trae null. En los registros anteriores al campo también es null, y ahí no distingue «sin padre» de «registro viejo». No cambia ningún miembro ni ordinal.

Esa misma entrada documenta lo que #470 cambió en el comportamiento del contrato sin tener entrada propia, y que un consumidor que confirma gravámenes debe tratar:

  • LineaDescartada.TraeTarifa (bool, false por defecto, al final). true cuando Tarifa es una que el motor encontró y con la que se puede incluir el tributo; FactorUtilizado no es esa señal (hay tributos de factor fija o porcentajeDePadre que tienen tarifa y nunca llevan factor). Los descartes por perfil o por condición jurisdiccional, por cuantía mínima, por dependencia del padre y por padre no aplicado la informan, así que Tarifa puede venir distinta de cero en descartes que antes salían en cero: TraeTarifa es la señal para distinguirlos. En el descarte por convenio que exime, Tarifa es la del tributo sin el convenio (con la del convenio, cero, incluirlo no cobraría nada) y Valor sigue en cero. La lectura (LineaDescartadaExplicada) no lleva TraeTarifa.
  • Dos rechazos nuevos en la confirmación de un gravamen. ConfirmacionTributariaRechazada con ComandoInvalido, donde antes llegaba ConfirmacionTributariaRealizada, en dos casos: una línea cuyo tributo el cálculo de referencia no evaluó para ese concepto (no está entre sus aplicados ni sus descartados; incluye el tributo de la otra dirección y el original que sustituyó un reverse charge), que ya no se completa desde el catálogo; y una línea de un tributo hijo cuyo padre no viene en el desglose del mismo concepto. MotivoDetalle distingue uno de otro y nombra el tributo. Qué hacer: confirmar solo tributos que el cálculo evaluó, y, para incluir un hijo descartado por dependencia del padre, incluir también al padre del mismo concepto. El enum MotivoRechazoConfirmacion no cambia.

0.42.0-beta agrega campos opcionales y es aditiva sin reserva: ningún campo se retira ni cambia de tipo, ningún miembro de enum se agrega y quien ya compila sigue compilando; lo único que cambia de valor es lo que se dice más abajo. Los tres campos van al final y como propiedades init con default [], no como parámetros posicionales: C# no admite = [] como valor por defecto de un parámetro IReadOnlyList<Guid>, así que quien construye una línea o un bloque (un doble de prueba) no cambia, y un mensaje de un productor anterior los deserializa vacíos.

  • LineaDesgloseExplicada.CondicionesDecisorias y LineaDescartadaExplicada.CondicionesDecisorias (IReadOnlyList<Guid>, vacía por defecto). Los ids de las condiciones que decidieron el desenlace de la línea: son la misma selección de la que sale la prosa (Hechos, Regla, Contraste), no una segunda lectura. Una línea aplicada publica el id de la condición que la dejó en pie: la activadora que la aplicó (AplicoConLinea), la que modificó su tarifa (ModificoTarifa), la que generó la autoliquidación (GeneroAutoliquidacion) o la que sustituyó a otro tributo (SustituyoTributo); un descarte sin activadora ganadora publica las activadoras del grupo que no se cumplieron; una exclusión concurrente, cada una de las que bastaron; un descarte por ClasificacionExcluida y una línea sin condición que la decidiera, la lista vacía. Un id solo no identifica la condición: una condición evaluada para varios conceptos deja un registro por concepto, así que el par (condicionId, conceptoOrigen de la línea) resuelve en exactamente una entrada de configuracionUsada.condicionesEvaluadas del mismo documento. Quien arme la lista de condiciones del panel debe resolver por ese par y no solo por el id.
  • DescarteAgrupado.Conceptos (IReadOnlyList<Guid>, vacía por defecto). Los conceptoOrigen de las líneas que formaron el bloque, sin repetir y en el orden en que el cálculo los produjo. La agrupación sigue siendo por la prosa a través de todos los conceptos; el campo solo declara a cuáles alcanza, para que el panel ubique el bloque sin deducirlo del texto. Un bloque de un productor anterior trae la lista vacía, y ahí no distingue «ningún concepto» de «registro viejo».
  • GET /explicaciones-de-cotizacion/{referenciaOrigen} expone configuracionUsada, con la misma forma que la del registro (GET /registros-tributarios/{subDominio}/{transaccionId}), junto a los tres campos de siempre. Es un response de la API de consulta y no viaja en el paquete: ResultadoDelCalculoExplicado no cambia, y un cliente que lo deserializa ignora el campo nuevo. Con ella la cotización publica, por línea, las mismas condicionesDecisorias que el registro y se pueden resolver igual.
  • CondicionEvaluadaResponse.tarifaAlternativa (API de consulta, registro y cotización): la tarifa que fija la condición ganadora, sea una que aplica el tributo con una tarifa específica (Aplicar) o una que solo cambia la tarifa (CambiarTarifa); null en las demás.

Lo que cambia sin tocar ninguna firma es la prosa, y quien la compare por texto exacto debe saberlo: el resumen del documento rotula su cifra «Valor de la operación» en toda dirección (antes «Total a pagar», «Total a cobrar» o «Total de la transacción»; la cifra y su regla no cambian); el efecto de cada línea cita el factor con que se tarifó, por su nombre congelado, y la base de la propia línea, y ya no el concepto de pago; el contraste niega primero la condición que decidió la línea, no enumera criterios que la transacción ya cumple y redacta en cláusula propia la disyunción de un supuesto; una jurisdicción exigida u observada por una condición se cita por su nombre y no por su código; el nombre de un tributo en medio de la oración va en minúscula salvo sus siglas («retención en la fuente»); un descarte sin activadora ganadora narra en Hechos y Regla qué criterio de cada activadora no se cumplió contra qué valor observado, y ya no dice que «el perfil no cumple las condiciones»; y una activadora que no evalúa calidades (AplicarSinCalidades, AplicarPorDefecto) deja de producir la Advertencia de traza incompleta. Además, la línea descartada de un tributo que se liquida sobre su padre informa como Base el valor liquidado del padre en ese concepto (0 si el padre no aplicó) y ya no el monto del concepto, en los descartes por exclusión, convenio y cuantía mínima; la Tarifa y el monto comparado no cambian.

Por eso es aditiva y no incompatible, aunque el valor de Base cambie: LineaDescartada solo viaja en CalculoTributarioRealizado, y su único lector por el bus es Radicación, que ignora la base publicada del hijo al reincorporarlo. Contabilidad solo recibe RegistroTributarioEntregado, que lleva líneas aplicadas (LineaEntregada). Quien lea esa Base por otra vía debe saber que el hijo descartado ya no repite el monto del concepto.

Qué hacer: ObligacionesPorPagar.Front puede leer condicionesDecisorias y conceptos para armar el panel sin replicar reglas del motor. Radicación no cambia.

Cambios compatibles

Las versiones que solo agregan —tipos, topics, campos opcionales— sin obligar a nadie a shippear código no van en «Cambios incompatibles»: se listan acá, de la más nueva a la más vieja, para que quien integre sepa qué es nuevo.

0.45.0-beta — el registro tributario se anula por el bus

Solo agrega tipos y topics; no cambia ninguna firma ni la forma de ningún evento existente, y ningún enum existente gana miembros. Se registra acá para que quien integre sepa qué es nuevo.

  • Eventos nuevos. SolicitarAnulacionTributaria(ReferenciaOrigen, SubDominio, TransaccionId, Motivo, UsuarioId) (topic impuestos-solicitar-anulacion-tributaria; ReferenciaOrigen, TransaccionId, Motivo y UsuarioId nulos, vacíos o en blanco lanzan al construirla y al deserializarla, así que un mensaje incompleto no se puede armar con el contrato), AnulacionTributariaRealizada(ReferenciaOrigen, SubDominio, TransaccionId, RegistroTributarioAnuladoId) (topic impuestos-anulacion-tributaria-realizada) y AnulacionTributariaRechazada(ReferenciaOrigen, SubDominio, TransaccionId, MotivoCodigo, MotivoDetalle) (topic impuestos-anulacion-tributaria-rechazada). Toda solicitud produce una de las dos respuestas.
  • Sin registro vigente es éxito. RegistroTributarioAnuladoId es null cuando la transacción no tenía registro vigente (la confirmación se rechazó o ya estaba anulado): un reenvío responde igual.
  • Enum nuevo MotivoRechazoAnulacion: ComandoInvalido (la solicitud viola otra regla de dominio al aplicarse; un dato obligatorio ausente o en blanco ya no llega acá) y DesgravamenesVigentes (el gravamen tiene desgravámenes vigentes; el detalle lista sus TransaccionId, que hay que reversar primero).
  • Efectos observables. Anular libera la clave de la transacción: confirmarla de nuevo crea un registro nuevo, y GET /registros-tributarios/{subDominio}/{transaccionId} responde 404 mientras no se reconfirme. Un desgravamen sobre un gravamen anulado se rechaza con OrigenNoEncontrado.

Eventos

Namespace: Cosmos.Impuestos.Contratos.Eventos. Modelos compartidos en Cosmos.Impuestos.Contratos.Compartidos; enums en Cosmos.Impuestos.Contratos.Enums.

Evento Dirección Propósito
CalculoTributarioSolicitado entrante Solicita la cotización tributaria de unos conceptos. Impuestos resuelve el contexto y ejecuta el motor sin mutar estado; si trae OrigenDelDesgravamen, no ejecuta el motor y deriva el desglose por prorrateo de la transacción de origen. No lleva decisiones del usuario: quien cotiza las aplica sobre el desglose recibido (ver 0.38.0-beta). No se puede construir con forma inválida (ver 0.46.0-beta); una lista de Conceptos vacía sí.
CalculoTributarioRealizado saliente Resultado de éxito de la cotización (aplicados/descartados, sin prosa), correlacionado por ReferenciaOrigen.
CalculoTributarioRechazado saliente Resultado de rechazo de la cotización; contrapartida de CalculoTributarioRealizado, misma ReferenciaOrigen. Toda solicitud que se puede construir (y deserializar) produce uno u otro; la mal armada va a la cola de error sin respuesta (ver 0.46.0-beta).
SolicitarConfirmacionTributaria entrante Solicita confirmar un registro tributario (gravamen/desgravamen); para un desgravamen, Conceptos trae el monto devuelto de cada uno. No se puede construir con forma inválida (ver 0.46.0-beta); una lista de Conceptos vacía sí.
ConfirmacionTributariaRealizada saliente Resultado de éxito de la confirmación: el id del RegistroTributario recién persistido y la etiqueta de cada línea (LineaEtiquetada: su tributo y su concepto de origen). Correlacionado por ReferenciaOrigen.
ConfirmacionTributariaRechazada saliente Resultado de rechazo de la confirmación; contrapartida de ConfirmacionTributariaRealizada, misma ReferenciaOrigen. No persiste ningún registro.
SolicitarRegistroTributario entrante Consulta pura: pide el contenido de un registro tributario ya confirmado, por SubDominio + TransaccionId (los dos, y la ReferenciaSolicitud, obligatorios al construirla). Responde con RegistroTributarioEntregado o EntregaDeRegistroTributarioRechazada.
RegistroTributarioEntregado saliente El desglose confirmado del registro, a demanda: por cada línea, su tributo y sus montos (LineaEntregada). El resto del registro se consulta por HTTP. Correlacionado por ReferenciaSolicitud.
EntregaDeRegistroTributarioRechazada saliente Resultado de rechazo de la entrega; contrapartida de RegistroTributarioEntregado, misma ReferenciaSolicitud. MotivoRechazoEntrega.SolicitudInvalida se conserva sin productor: la solicitud mal armada no se puede construir.
SolicitarAnulacionTributaria entrante Anula el registro tributario vigente de una transacción, por SubDominio + TransaccionId, con su Motivo y UsuarioId. Responde con AnulacionTributariaRealizada o AnulacionTributariaRechazada.
AnulacionTributariaRealizada saliente Resultado de éxito de la anulación; RegistroTributarioAnuladoId es null si no había registro vigente.
AnulacionTributariaRechazada saliente Resultado de rechazo de la anulación (MotivoRechazoAnulacion); contrapartida de AnulacionTributariaRealizada, misma ReferenciaOrigen.
PerfilTributarioActualizado saliente Aviso de que el perfil tributario de un tercero cambió en algo que afecta el cálculo. No lleva el estado del perfil.

ReferenciaOrigen es una correlación opaca que define el consumidor (p. ej. su identificador de transacción) y que Impuestos eco-devuelve en el evento de respuesta para enrutar el resultado.

DeColombia en Tributo

Tributo es la identidad del tributo de una línea del bus, agrupada aparte de los montos. Lo que un proceso necesita para reconocerlo y tratarlo es Codigo, Nombre, Naturaleza, CaracterRetencion, DeColombia y, desde 0.36.0-beta, CodigoPadre: el código del tributo del que depende (RIVA → IVA), o null si no depende de otro. Es el padre que rigió para ese cálculo, no el del catálogo de hoy. La familia (FamiliaTributaria) y el mecanismo de recaudo (MecanismoDeRecaudo) no viajan por el bus desde 0.26.0-beta: se consultan en tributo.familia y tributo.mecanismoDeRecaudo de las dos consultas HTTP (TributoDeLectura). Familia sigue distinguiendo el impuesto de su retención junto con Naturaleza: RIVA e IVA comparten familia (ValorAgregado) pero tienen Naturaleza distinta (Sustractivo/Aditivo); para eso, en el bus, alcanza con Naturaleza y Codigo.

Tributo.DeColombia (TributoDeColombia): un miembro por código de tributo de la edición de Colombia (Iva, Riva, Rica, y así con cada tributo declarado en esa edición). Es append-only y viaja como entero en el bus, igual que cualquier otro enum de este contrato. Se deriva de Tributo.Codigo, que ya viaja congelado, así que también lo llevan los registros anteriores a la existencia de este campo — acá null no distingue "registro viejo" de "no es un tributo colombiano": significa que el código no coincide con ningún tributo de la edición de Colombia. El mapeo es por código y no por quién declaró el tributo ni en qué país: un tributo propio del tenant (de cualquier país, incluida Colombia) que reuse uno de esos códigos publica el mismo miembro que el tributo de la edición — ver «Riesgo aceptado» abajo.

Riesgo aceptado: el mapeo de DeColombia es por código y no conoce ni el país de la línea ni si el tributo es de la edición o propio del tenant. Un tenant de cualquier país —incluida Colombia— que da de alta un tributo propio con un código que coincide con uno de la edición de Colombia (p. ej. RIVA, o IVA para un tenant colombiano con su propio tributo de ese código) publica ese mismo miembro. Los consumidores de este campo operan hoy solo en Colombia, así que la colisión no tiene efecto observable adverso; se documenta acá para quien extienda esa integración a otro país o discrimine por este campo asumiendo que solo lo publica la edición.

Aviso de cambio del perfil tributario

PerfilTributarioActualizado se publica en el topic CanalImpuestos.TopicPerfilTributarioActualizado y lleva tres datos: la identificación del tercero (IdentificacionLegal), el id del perfil y FechaDelCambio. Es un aviso, no una transferencia de estado: no lleva calidades fiscales, actividades económicas ni razón social. Quien reaccione vuelve a pedir el cálculo, y el cálculo lee el perfil vigente.

  • Uno por comando, no uno por cambio: un comando que persiste varios cambios a la vez avisa una sola vez.
  • Solo cuando lo persistido afecta el cálculo. Un cambio de razón social no avisa, tampoco un aseguramiento del perfil que no cambió nada, y un comando rechazado no avisa nunca. La creación del perfil sí avisa: antes de ella el cálculo rechazaba por perfil no encontrado.
  • FechaDelCambio es cuándo quedó persistido el cambio, no la vigencia del dato tributario, que puede ser pasada o futura. Sirve para ordenar avisos, no para decidir desde cuándo rige el perfil.
  • El envelope lleva la identidad de quien originó el cambio: tenant, usuario y membresía de organización.
  • El aviso se guarda en la misma transacción que el cambio: un cambio guardado no queda sin su aviso, y uno que no llega a guardarse no deja aviso. Avisan igual los cambios que entran por la API HTTP y los que entran por el servidor MCP de Impuestos, que escribe llamando a esa API.
  • El aviso no reemplaza al recálculo a demanda: se entrega después del guardado y de forma asíncrona, y solo informa que hay algo que recalcular.

Consulta de la explicación de una cotización

Una cotización no se persiste como registro, pero deja su traza estructurada mientras la obligación sigue pendiente. La explicación se redacta al leer, con la misma redacción que el registro confirmado.

Una propuesta de desgravamen no deja traza de cotización, porque no hay motor que explicar: la consulta responde 404 igual que si la referencia no existiera. El desglose de un desgravamen se deriva por prorrateo del registro de origen, no del motor, así que no hay condiciones evaluadas ni descartes que narrar. Su procedencia se obtiene, una vez confirmado, por GET /registros-tributarios/{subDominio}/{transaccionId}.

GET /explicaciones-de-cotizacion/{referenciaOrigen}?correlacion={guid}

referenciaOrigen es la misma referencia opaca con que se solicitó el cálculo, sin reinterpretar; una referencia con / no matchea la ruta y da 404 del ruteo. correlacion es la aplicada por quien consume: la del cálculo cuyo desglose muestra, no la vigente.

Estado Cuándo
200 Hay traza para la referencia y su correlación coincide. El cuerpo es la explicación redactada, con la de las líneas descartadas.
400 Falta correlacion o no es un Guid.
404 No hay traza para la referencia, o la traza guardada es de otra correlación. Los dos casos son indistinguibles a propósito: la consulta falla cerrado y nunca devuelve prosa de un cálculo distinto al que se muestra.

Hay a lo sumo una traza por referencia: recotizar la reemplaza, así que un cliente con una correlación anterior recibe 404 y debe degradar. La traza se elimina, en la medida de lo posible, cuando se confirma el registro tributario de esa referencia (si la eliminación falla la confirmación sigue y la traza puede quedar); desde entonces la explicación se obtiene por GET /registros-tributarios/{subDominio}/{transaccionId}.

Desde 0.26.0-beta la traza es la única fuente de la explicación de una cotización: el evento ya no la lleva. Guardarla es un intento: si falla, CalculoTributarioRealizado se publica igual, porque los números valen más que la prosa, y esa cotización responde 404 en esta consulta. Recotizar la referencia la recupera. Quien necesite la explicación debe tratar el 404 como «sin explicación disponible» y no como un error.

Uso

using Cosmos.Impuestos.Contratos.Eventos;

// publicar una solicitud
await bus.PublishAsync(new CalculoTributarioSolicitado(referenciaOrigen, /* … */));

// consumir el resultado
public Task Handle(CalculoTributarioRealizado evento) { /* enrutar por evento.ReferenciaOrigen */ }

El nombre del servicio/cola productor para suscribirse a los eventos entrantes lo define el wiring del consumidor (SuscribirseAServicio).

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Cosmos.Impuestos.Contratos:

Package Downloads
Cosmos.Impuestos.Mensajeria

Extensiones de Wolverine para integrarse con Cosmos Impuestos sobre Azure Service Bus (topología topic-por-evento): publica las solicitudes tributarias a sus topics y, por separado, permite escuchar los resultados y broadcasts de Impuestos por una subscription propia. Publicar y escuchar son capacidades componibles para que un servicio de borde pueda solicitar sin suscribirse a los resultados.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.46.0-beta 116 10/25/2026
0.45.0-beta 66 10/25/2026
0.44.0-beta 64 10/21/2026
0.43.0-beta 96 10/20/2026
0.42.0-beta 118 10/15/2026
0.41.0-beta 163 10/13/2026
0.40.0-beta 119 10/11/2026
0.39.0-beta 96 10/10/2026
0.38.0-beta 84 10/9/2026
0.37.0-beta 186 10/6/2026
0.36.0-beta 89 10/6/2026
0.35.0-beta 67 10/6/2026
0.33.0-beta 71 10/2/2026
0.32.0-beta 195 10/1/2026
0.31.0-beta 108 10/1/2026
0.30.0-beta 65 9/30/2026
0.29.0-beta 61 9/30/2026
0.28.0-beta 65 9/30/2026
0.27.0-beta 248 9/30/2026
0.26.0-beta 108 9/29/2026
Loading failed

0.46.0-beta (incompatible) — las solicitudes que Impuestos recibe no se pueden construir con forma inválida. Lanzan al
construirse y al deserializarse (ArgumentNullException, ArgumentException o ArgumentOutOfRangeException, con el campo y el
motivo): CalculoTributarioSolicitado, SolicitarConfirmacionTributaria, SolicitarRegistroTributario y los tipos que usan
(ContextoTransaccional, ConceptoCalculo, LineaConfirmada, UbicacionesTransaccion, OrigenDelDesgravamen). Exigen texto no
nulo ni en blanco (ReferenciaOrigen, ReferenciaSolicitud, TransaccionId, ClasificacionTributaria, CodigoTributo y las sedes),
objetos no nulos (identificaciones, Ubicaciones, Contexto, DesgloseConfirmado, Conceptos), ids distintos de Guid.Empty,
enums con valor definido, cantidad positiva siempre junto con su unidad, actividad económica (si viene) no en blanco, y ids de
concepto sin repetir en la solicitud; la lista completa, campo por campo, está en el README del paquete. Monto admite negativos y ConceptoPago vacío cuenta como no informado: no se restringen. Una lista de conceptos vacía sigue
siendo admisible: Impuestos la rechaza con DatosFaltantes en un desgravamen y con ComandoInvalido en un gravamen. Ya no se
publica el rechazo de una solicitud mal armada: un mensaje que no se puede deserializar va a la cola de error del bus, no
recibe CalculoTributarioRechazado, ConfirmacionTributariaRechazada ni EntregaDeRegistroTributarioRechazada. Por eso
MotivoRechazoEntrega.SolicitudInvalida queda sin productor: el miembro y su ordinal se conservan. Rompe a quien construya
estos tipos con null, vacío o un enum fuera de rango (deja de ejecutar sin lanzar) y a quien esperara esos rechazos por el
bus. Orden de despliegue: quien emite actualiza a esta versión antes de que Impuestos despliegue la que ya no valida la
forma al recibir; si no, una solicitud mal armada queda en la cola de error y quien la emitió sin respuesta. Además, sin
cambiar ninguna firma, la base de una línea por unidad en un desgravamen viaja con hasta 6 decimales (es una cantidad, no
un monto).

0.45.0-beta (aditiva) — anulación del registro tributario por el bus. Eventos nuevos: SolicitarAnulacionTributaria
(ReferenciaOrigen, SubDominio, TransaccionId, Motivo, UsuarioId; topic impuestos-solicitar-anulacion-tributaria),
AnulacionTributariaRealizada (RegistroTributarioAnuladoId es null si la transacción no tenía registro vigente; topic
impuestos-anulacion-tributaria-realizada) y AnulacionTributariaRechazada (topic impuestos-anulacion-tributaria-rechazada).
Enum nuevo MotivoRechazoAnulacion { ComandoInvalido, DesgravamenesVigentes }; el detalle de DesgravamenesVigentes lista las
transacciones de los desgravámenes que hay que reversar primero. Anular libera la clave de la transacción: se puede volver a
confirmar y nace un registro nuevo; un desgravamen sobre un gravamen anulado se rechaza con OrigenNoEncontrado. No cambia
ninguna firma ni la forma de ningún evento existente.

0.44.0-beta (incompatible) — dos condiciones que cambian la tarifa a valores distintos se rechazan con motivo propio.
MotivoRechazoCalculo gana TarifasAlternativasIncoherentes (20, al final): dos condiciones que cambian la tarifa del mismo
tributo coincidieron a la vez y señalan tarifas distintas (dos líneas alternativas distintas, o una línea y la del convenio);
el detalle nombra las dos condiciones. Saca casos de ConfiguracionIncoherente, que sigue para las demás divergencias entre
condiciones que coinciden. CuantiaIncoherente queda sin productor: el miembro y su ordinal se conservan. No cambia ninguna
firma ni la forma de ningún evento. Rompe a quien haga switch exhaustivo sobre MotivoRechazoCalculo y a quien discriminaba
ConfiguracionIncoherente por este caso.

0.43.0-beta (incompatible) — un concepto de pago que no rige se rechaza con motivo propio, y el dato que el tributo exige y
no llega se rechaza como datos faltantes. MotivoRechazoCalculo gana ConceptoPagoNoVigente (19, al final): el concepto informa
un concepto de pago que el catálogo del país no tiene o que el tenant suprimió a la fecha de la transacción; el detalle nombra
el código, el país y la fecha. Saca casos de otros motivos: de TarifaNoVigente, el concepto de pago inexistente que un tributo
de cobertura abierta tarifaba; y de ComandoInvalido, el concepto de pago y el lugar de ejecución que un tributo exige y la
solicitud no trae, que ahora llegan como DatosFaltantes, tanto en CalculoTributarioRechazado como en
ConfirmacionTributariaRechazada. Un concepto de pago vacío o en blanco cuenta como no informado. No cambia ninguna firma ni la
forma de ningún evento. Rompe a quien haga switch exhaustivo sobre MotivoRechazoCalculo y a quien discriminaba TarifaNoVigente
o, en la confirmación, ComandoInvalido por esos casos.

0.42.0-beta (aditiva) — la explicación declara lo que decidió cada línea y a qué conceptos alcanza cada bloque.
LineaDesgloseExplicada y LineaDescartadaExplicada ganan CondicionesDecisorias (IReadOnlyList<Guid>, propiedad init con default [],
al final): los ids de las condiciones que decidieron la línea, la misma selección de la que sale la prosa; el par (id,
ConceptoOrigen de la línea) resuelve en una sola condición de la configuración usada del mismo documento. DescarteAgrupado gana
Conceptos (IReadOnlyList<Guid>, propiedad init con default []): los conceptos de las líneas del bloque, sin repetir y en orden de
cálculo. Quien construye estos tipos no cambia y un mensaje anterior los deserializa vacíos. Cambia la prosa sin tocar firmas: el
resumen rotula su cifra «Valor de la operación» en toda dirección; el efecto cita el factor y la base de la línea; el contraste
niega primero la condición decisoria y no enumera criterios ya cumplidos; las jurisdicciones se citan por nombre y el nombre del
tributo va en minúscula salvo sus siglas; el descarte sin activadora ganadora narra el criterio no cumplido; y la activadora que
no evalúa calidades deja de advertir una traza incompleta. La línea descartada de un hijo informa como Base el valor del padre en
el concepto. No rompe a nadie que compile contra 0.41.0-beta.

0.41.0-beta (incompatible) — el libro al día. ConceptoCalculo gana Cantidad y UnidadDeMedida (opcionales, las dos o
ninguna) y TipoTarifa gana PorUnidad: una tarifa por unidad (la sobretasa a los combustibles, en galones) liquida
cantidad × valor. LineaDesglose, LineaDescartada, LineaConfirmada, LineaDesgloseExplicada, LineaDescartadaExplicada y
LineaEntregada ganan UnidadDeMedida (UnidadDeMedida? del Nugget Cosmos.Types.UnidadesDeMedida, dependencia nueva; null por
defecto, al final; en el cable viaja como { "codigo": "GLL" }, no como texto). MotivoRechazoCalculo renombra
CalidadFiscalFaltante a PerfilIncompleto conservando el ordinal (1) y gana CantidadNoInformada (17) y UnidadIncompatible
(18). Efecto gana SinCuantiaMinima (7). EntregaDeRegistroTributarioRechazada gana Fecha (DateTimeOffset, al final, sin valor
por defecto: quien construya el evento deja de compilar hasta pasar Fecha). TributoDeColombia gana SobretasaCombustibles (12). Rompe a quien compile contra
CalidadFiscalFaltante, a quien construya EntregaDeRegistroTributarioRechazada y a quien haga switch exhaustivo sobre estos
enums; el texto «CalidadFiscalFaltante» ya guardado como nombre de motivo deja de coincidir con el enum.

0.40.0-beta (incompatible) — un atributo que el perfil no declara deja de leerse como «no cumple». El cálculo lo evalúa
con el valor por defecto de su definición y lo publica: CalculoTributarioRealizado gana AtributosSupuestos
(IReadOnlyList<AtributoSupuesto>?, null por defecto, al final), con AtributoSupuesto(Entidad, Atributo, Valor) y Entidad
"Emisora" o "Contraparte". Es vacío si nada se supuso y nulo en los mensajes de productores anteriores. Si la definición
no tiene valor por defecto y una condición necesita el atributo, el cálculo se rechaza con CalculoTributarioRechazado y
MotivoRechazoCalculo.CalidadFiscalFaltante, miembro que ya existía y conserva su ordinal: un cálculo que en 0.39 se
resolvía como si la calidad no se cumpliera ahora puede rechazarse, y quien discrimina los motivos debe contemplarlo. La
confirmación no cambia de forma: su rechazo sigue llegando con DatosFaltantes y el detalle del cálculo.

0.39.0-beta (incompatible) — MotivoRechazoCalculo y MotivoRechazoConfirmacion ganan IndiceNoVigente al final (ordinales
16 y 11), sin cambiar los existentes. Es el rechazo cuando la cuantía mínima de un tributo está expresada en una unidad de referencia (la
UVT, …) y no hay índice vigente para esa unidad a la fecha de la transacción; el detalle nombra la unidad y la fecha, y lo
completa quien publica el índice. Hasta 0.38 ese caso llegaba como ConfiguracionTributariaNoEncontrada en el cálculo y como
DatosFaltantes en la confirmación: quien discriminaba esos motivos deja de recibirlo, y quien haga switch exhaustivo sin
arm por defecto debe agregar el nuevo miembro. MotivoRechazoConfirmacion.ConceptoSinTributosAplicables queda reservado en
su posición, sin cambiar ordinales: ningún flujo lo emite, porque un concepto confirmado sin tributos aplicables es
legítimo y no se rechaza.

0.38.0-beta (incompatible) — las decisiones del usuario sobre un tributo dejan de viajar al cálculo. Excluir o incluir
un tributo en un concepto es una edición del desglose que recibió quien cotiza, y toda decisión cae con el cambio
fiscal que dispara el recálculo. Se quitan CalculoTributarioSolicitado.Exclusiones e Inclusiones,
CalculoTributarioRealizado.ExclusionesSinEfecto e InclusionesSinEfecto, LineaDesglose.IncluidaPorElUsuario y los tipos
ExclusionDelUsuario, InclusionDelUsuario, DecisionSinEfecto y MotivoDecisionSinEfecto. Un productor en 0.37 que
todavía mande decisiones recibe el cálculo como si no las hubiera mandado: se ignoran, no se rechazan.
MotivoExclusion.ExcluidoPorElUsuario y RazonDeAplicacion.AplicoPorDecisionDelUsuario quedan reservados en su posición,
sin cambiar ordinales: ningún cálculo los emite, y solo aparecen al explicar una cotización calculada antes de esta
versión. La confirmación no cambia: confirmar un desglose que quien cotiza editó (quitó una línea aplicada o agregó una
desde un descarte) cuenta como intervención. MotivoRechazoCalculo gana CuantiaIncoherente al final, sin cambiar
ordinales: dos conceptos de un mismo grupo de cuantía mínima por transacción (mismo tributo, factor y jurisdicción)
resolvieron entradas de tarifa con umbrales distintos, algo que solo ocurre con una condición propia que cambia la
tarifa. Hasta 0.37 ese caso salía como ConfiguracionIncoherente; quien discriminaba ese motivo deja de recibirlo. La
confirmación no cambia: ese rechazo sigue llegando con DatosFaltantes.
En la misma versión, aditivo: LineaDesglose y LineaDescartada ganan AsumidaHabitualmente (bool, false por defecto, al final):
el catálogo declara que el pagador suele asumir la retención de ese tributo (en Colombia, la retención en la fuente por
pagos al exterior). Quien consume la propone asumida y decide por documento: es un dato del catálogo, no la decisión, e
Impuestos no la conoce. Las lecturas de la explicación de la cotización (LineaDesgloseExplicada y
LineaDescartadaExplicada, HTTP) lo traen también. No viaja en Tributo, TributoDeLectura ni LineaEntregada, y es false en
los registros anteriores al campo. No cambia ningún miembro ni ordinal.

0.37.0-beta (incompatible) — la solicitud de cálculo trae decisiones del usuario sobre un tributo de un concepto.
CalculoTributarioSolicitado gana Exclusiones (no aplicar el tributo en el concepto) e Inclusiones (aplicar uno que el
cálculo descartó, con la tarifa de la línea o de una EntradaDeTarifa elegida), opcionales y al final. La respuesta,
CalculoTributarioRealizado, gana ExclusionesSinEfecto e InclusionesSinEfecto: lo que no se pudo aplicar, con su
MotivoDecisionSinEfecto (enum nuevo); es un aviso, no un rechazo. LineaDesglose gana IncluidaPorElUsuario. Miembros
nuevos al final, sin cambiar ordinales: MotivoExclusion.ExcluidoPorElUsuario y RazonDeAplicacion.
AplicoPorDecisionDelUsuario; rompen a quien haga switch exhaustivo sin arm por defecto o trate todo descarte como una
decisión del cálculo; AplicoPorDecisionDelUsuario solo llega por la explicación de la cotización (HTTP), porque el bus
no lleva RazonDeAplicacion y ahí la línea incluida se reconoce por IncluidaPorElUsuario. Una solicitud con decisiones
mal formadas (par repetido, par excluido e incluido a la vez, concepto ajeno) o una de desgravamen con decisiones se
rechaza con ComandoInvalido. La confirmación no cambia.

0.36.0-beta (aditiva) — Tributo gana CodigoPadre (string?, null por defecto, al final): el código del tributo del que
depende el tributo de la línea, tal como rigió para ese cálculo, o null si no depende de otro. Viaja en toda línea que
lleva un Tributo (aplicada, descartada, entregada y etiquetada), así que quien arma o valida un desglose puede exigir que
un tributo hijo no viaje sin su padre en el mismo concepto sin consultar el catálogo. Es null en los registros anteriores
al campo. TributoDeLectura (las consultas HTTP) lo trae también. Esta entrada documenta además lo que ya cambió con #470
y no tenía entrada: LineaDescartada.TraeTarifa (true cuando Tarifa es una que el motor encontró y con la que se puede
incluir el tributo; Tarifa puede ser distinta de 0 en descartes que antes salían en 0, y en el descarte por convenio es la
del tributo sin el convenio), y dos rechazos nuevos de la confirmación de un gravamen, ConfirmacionTributariaRechazada
con ComandoInvalido: la línea de un tributo que el cálculo no evaluó para ese concepto y la de un tributo hijo cuyo padre
no viene en el desglose del mismo concepto. Un consumidor que confirma gravámenes debe tratar esos dos rechazos.

0.35.0-beta (aditiva) — MotivoRechazoCalculo gana ResultadoExcedeSizeMaximo (al final, ordinal 14). Un cálculo cuyo
resultado no cabe en el mensaje que lo entrega (máximo de 240 KiB) ahora publica CalculoTributarioRechazado en vez de
fallar mudo en el envío. El tamaño medido y el máximo van en MotivoDetalle; no hace falta ramificar por el motivo.
No invita a reintentar: la misma transacción produce el mismo resultado.

0.34.0-beta (incompatible) — depende de Cosmos.Types.IdentificacionesLegales 2.0.0: la identificación de los eventos
públicos trae siempre numeroOriginal (el número tal como se escribió; igual a numero si no se escribió distinto). Se
compara, busca y empata por numero; numeroOriginal solo se muestra o se entrega a quien conoce el documento escrito
así. Un consumidor que preguntaba si numeroOriginal es nulo debe usar TieneNumeroOriginalDistinto.

0.33.0-beta (incompatible) — MotivoRechazoCalculo gana ConfiguracionIncoherente al final, sin cambio de ordinales: la
configuración vigente existe pero se contradice o deja sin resolver qué debe pasar (dos condiciones que coinciden sin
llevar al mismo resultado, una sustitución cíclica, …); la corrige quien configura y reintentar no cambia el desenlace.
Varios rechazos que antes llegaban como Indeterminado llegan ahora con un motivo que dice quién corrige:
ConfiguracionIncoherente, ComandoInvalido (un dato que el tributo exige y la solicitud no trae, como el lugar de
ejecución de un tributo localizado o el concepto de pago de una tarifa por concepto de pago) y
ConfiguracionTributariaNoEncontrada (falta el valor de referencia de una unidad a la fecha de la transacción). Un
rechazo del cálculo llega a ConfirmacionTributariaRechazada con uno de dos motivos: ComandoInvalido cuando el defecto
es de la solicitud y DatosFaltantes para el resto, incluida la configuración incoherente; los demás motivos de la
confirmación no cambian.
Una solicitud con ids de concepto repetidos, que antes se calculaba, ahora se rechaza con ComandoInvalido en el cálculo
y en la confirmación de un gravamen. En la confirmación, la solicitud sin lugar de ejecución o sin concepto de pago
pasa de DatosFaltantes a ComandoInvalido; revierte el criterio de 0.29 para esos dos casos: el contrato permite omitir
el dato y solo quien arma la solicitud puede aportarlo. Rompe a quien discrimine Indeterminado, a quien discrimine
DatosFaltantes de ConfirmacionTributariaRechazada, a quien mande conceptos con el mismo id y a quien haga switch
exhaustivo sobre MotivoRechazoCalculo.

0.32.0-beta (aditiva) — cada línea del cálculo dice la jurisdicción subnacional de su tributo. Tipo nuevo
Compartidos.Jurisdiccion(Codigo, Nombre); LineaDesglose, LineaDescartada y las dos explicadas ganan
JurisdiccionSubnacional, opcional y al final. Es nula en los tributos nacionales, en los descartes anteriores a la
localización del tributo (JurisdiccionNoAplica, ClasificacionExcluida), en los cálculos anteriores al campo y en la
propuesta de un desgravamen, cuyas líneas se prorratean del desglose confirmado. La línea que confirma el consumidor no la trae ni la comparación contra el cálculo la usa: confirmar sin cambios no
cuenta como intervención.

0.31.0-beta (incompatible) — MotivoExclusion gana ActividadNoSujeta al final: la jurisdicción reconoce la actividad
del sujeto pasivo y la declara no sujeta, así que el tributo por actividad se descarta sin tarifa. Las cinco líneas
del cálculo (LineaDesglose, LineaConfirmada, LineaDescartada y las dos explicadas) ganan ActividadEconomica, opcional:
en una jurisdicción con catálogo de actividades FactorUtilizado pasa a ser la línea tarifaria y de ella no se
reconstruye la actividad. Rompe a quien haga switch exhaustivo sobre MotivoExclusion; quien confirma una línea que
trae actividad tiene que devolverla, porque la comparación contra el cálculo de referencia la incluye.

0.30.0-beta (aditiva) — una solicitud sin ubicaciones o sin conceptos recibe respuesta. Antes la traducción lanzaba y
el mensaje quedaba sin respuesta; ahora se rechaza con ComandoInvalido en el cálculo de un gravamen y en la
confirmación, salvo los Conceptos ausentes de un desgravamen, que van a DatosFaltantes igual que la propuesta de un
desgravamen sin Conceptos. La propuesta de un desgravamen sin Ubicaciones sigue aceptándose. No cambia ningún
tipo ni miembro.

0.29.0-beta (incompatible) — MotivoRechazoCalculo gana ComandoInvalido al final: la solicitud que no cumple el contrato.
Lo producen siete causas: el identificador del concepto vacío, su clasificación tributaria ausente o vacía, su
actividad económica informada vacía, la identificación emisora ausente, la de la contraparte ausente, la fecha de
la transacción por defecto y la lista de conceptos vacía en un gravamen. DatoInvalido queda sin productor y se conserva porque su
ordinal está publicado. La confirmación pasa esos casos de DatosFaltantes a ComandoInvalido; las demás fallas del
motor siguen en DatosFaltantes. Las métricas de Indeterminado bajan. Rompe a quien discrimine DatoInvalido para la
actividad informada vacía o DatosFaltantes en la confirmación de una solicitud mal armada, y a quien haga switch
exhaustivo sobre MotivoRechazoCalculo. Guía en el README.

0.28.0-beta (incompatible) — MotivoRechazoCalculo gana DatosFaltantes; la confirmación de un desgravamen rechaza
fuera del margen. La propuesta de un desgravamen sin conceptos devueltos rechaza con DatosFaltantes en vez de
DatoInvalido (que se conserva para otras formas inválidas). ConfirmacionTributariaRechazada empieza a llevar
IntervencionExcedeMargen cuando el desglose confirmado de un desgravamen se aparta de su prorrateo en más de una
unidad del último decimal de la escala del país, o en cualquier diferencia de tarifa, tipo, factor o línea. En un
gravamen, una diferencia dentro de ese margen deja de marcar HuboIntervencion. Rompe a quien discrimine
DatoInvalido para el desgravamen sin conceptos y a quien haga switch exhaustivo sobre MotivoRechazoCalculo.
Guía en el README.

0.27.0-beta (incompatible) — el concepto informa la actividad económica y el motor no desempata entre varias.
ConceptoCalculo gana ActividadEconomica (opcional): la actividad del sujeto pasivo que corresponde al concepto.
LineaDescartada gana ActividadRechazada y ActividadesCandidatas (opcionales). MotivoExclusion gana
ActividadAmbigua al final: con varias actividades candidatas y ninguna informada, el tributo por actividad se
descarta con las candidatas en orden ordinal; quien consume indica una en el concepto y recalcula. Rompe a quien
haga switch exhaustivo sobre MotivoExclusion. Guía en el README.

0.26.0-beta (incompatible) — los eventos de salida publican solo lo que un proceso usa al recibirlos.
CalculoTributarioRealizado pierde la prosa (ExplicacionDelCalculo, y RazonDeAplicacion y Explicacion de cada
línea). ConfirmacionTributariaRealizada queda en ReferenciaOrigen, RegistroTributarioId y DesgloseConfirmado
como lista de LineaEtiquetada (tributo y concepto de origen); RegistroTributarioEntregado, en ReferenciaSolicitud,
RegistroTributarioId y DesgloseConfirmado como lista de LineaEntregada (tributo y montos). Tributo pierde Pais,
Familia y MecanismoDeRecaudo, y Codigo pasa a ser su primer parámetro. Se borran los tipos que quedaron sin uso
(EntidadFiscal, AtributoSnapshot, Jurisdiccion, ConfiguracionUsada, TarifaUsada, CondicionEvaluada,
IndiceDeReferenciaUsado, ConvenioUsado, CuantiaMinima y los enums UnidadDeReferencia y ResultadoEvaluacion).
Rompe fuente. En el cable, lo que se conserva mantiene su ruta, y lo retirado llega sin aviso: los valores en
cero o false (un dato falso que parece válido) y los objetos nulos. Lo que salió se consulta por HTTP, y las dos consultas no cambian de forma. Guía completa en el README.

0.25.0-beta (incompatible) — LineaDesglose y LineaDescartada reemplazan sus campos sueltos de identidad
(CodigoTributo, NombreTributo, Naturaleza, CaracterRetencion, MecanismoDeRecaudo, Familia,
TributoDeColombia) por un único objeto Tributo { Pais, Codigo, Nombre, Familia, Naturaleza,
CaracterRetencion, MecanismoDeRecaudo, DeColombia }. Es el primer parámetro posicional de los dos records:
rompe a quien construya una LineaDesglose/LineaDescartada y cambia la forma serializada para quien lea por
clave. Fuera de Colombia la identidad del tributo es (Pais, Codigo), sin campo por país. Absorbe #362:
LineaDescartada ahora lleva Familia y DeColombia, que antes solo tenía la aplicada. La entrada también
cambia: SolicitarConfirmacionTributaria.DesgloseConfirmado pasa a ser una lista de LineaConfirmada, una
línea angosta que nombra el tributo solo por CodigoTributo; Impuestos completa el Tributo desde el cálculo de
referencia, sus descartes o su catálogo (intervención manual), y rechaza con ComandoInvalido un código que no
conoce. Corte directo, sin período de convivencia. Guía completa y riesgo aceptado del mapeo de DeColombia por código en el README.

0.24.0-beta (incompatible) — quien ya confirma desgravámenes con SolicitarConfirmacionTributaria tiene que
shippear código, sin tocar ningún campo nuevo: Conceptos pasa a leerse y no puede llegar vacío (antes no
se leía; vacío rechaza con DatosFaltantes, y también rechaza con DatosFaltantes un concepto que viene en
DesgloseConfirmado sin su monto en Conceptos), DesgravamenExcedeSaldo gana productor (antes
se confirmaba igual), y la referencia se deriva de Conceptos con cierre por residuo en vez del
DesgloseConfirmado sin más. El camino de cálculo sí es aditivo: CalculoTributarioSolicitado gana
OrigenDelDesgravamen ({ SubDominio, TransaccionId }), opcional y al final; si viene, la solicitud es la
cotización de un desgravamen: Impuestos no ejecuta el motor, busca el RegistroTributario de la transacción
de origen y deriva el desglose por prorrateo (un factor por concepto, R((valor × devuelto) ÷ gravado),
hijos incluidos, con el redondeo del país). MotivoRechazoCalculo gana OrigenNoEncontrado,
ConceptoNoExisteEnOrigen y DesgravamenExcedeSaldo (al final, ordinales 8 a 10). Cada desgravamen confirmado
descuenta de un saldo por transacción de origen (compra o venta desgravada): proponer o confirmar un monto
que, sumado a lo ya devuelto de un concepto, supere lo que ese concepto se gravó se rechaza en vez de
aceptarse (por monto del concepto, no por tributo dentro del concepto); la devolución que completa un concepto revierte, en la
referencia, el valor exacto que le queda a cada tributo (residuo). Guía en el README.

0.23.0-beta (incompatible) — MotivoRechazoCalculo gana ClasificacionNoVigente (al final, ordinal 7). Un
concepto cuya clasificación no existe o no está vigente a la fecha de la transacción rechaza el cálculo en
vez de devolver un desglose en cero con todo descartado por ClasificacionExcluida. Además se retira la
homologación de 0.20.0-beta: clasificaciones, condiciones, regímenes y definiciones de calidad viajan con el
código que publica erp-definiciones, tal cual (GRAV_19, perteneceRegimenIVA, RTF-01a, ZF-BAQ), sin tabla de
traducción. Guía en el README.

0.22.0-beta (incompatible) — FechaTransaccion pasa de DateTimeOffset a DateOnly en
CalculoTributarioSolicitado y en ContextoTransaccional (SolicitarConfirmacionTributaria,
RegistroTributarioEntregado, ConfirmacionTributariaRealizada). En el cable es "yyyy-MM-dd"; un mensaje
con hora ya no deserializa y no recibe respuesta (ni CalculoTributarioRechazado ni
ConfirmacionTributariaRechazada). El modelo de dominio de Impuestos en erp-definiciones tipa la fecha
como date: con un instante, el día se leía con el reloj del offset y el mismo instante desde otra zona
podía caer en otro día (el 31 de diciembre, en otro año de UVT). PerfilTributarioActualizado.FechaDelCambio
sigue siendo un instante. Guía en el README.

0.21.0-beta (aditiva) — LineaDesglose gana dos campos nullable al final: Familia (FamiliaTributaria:
ValorAgregado, Consumo, Renta, ActividadEconomica, Contribucion, Otro), la familia del tributo, igual en
los tres países; y TributoDeColombia (TributoDeColombia), el miembro cuyo código coincide con el de un
tributo de la edición de Colombia, null en cualquier otro caso (el mapeo es por código, no por si el
tributo es de la edición o propio del tenant: ver README para el riesgo aceptado de colisión). Los dos
enums son append-only y viajan como entero en el bus. Un registro anterior al congelamiento de Familia la
publica null; Familia sí se congela en la línea, TributoDeColombia se deriva del código ya congelado y por
eso también lo llevan los registros anteriores. Guía completa en el README.

0.20.0-beta (incompatible) — la configuración estándar sale de ediciones publicadas. Los códigos de
clasificaciones, condiciones, regímenes, definiciones y calidades fiscales que viajan en los eventos son
los internos de la tabla de homologación, no el código del libro (revertido en 0.23.0-beta); toda vigencia
es [desde, hasta) con hasta = el primer día en que deja de regir. Se retira la réplica de clasificaciones por el
bus (ClasificacionTributariaPublicada, ClasificacionTributariaDesactivada, ReplicaDeClasificacionesSolicitada y sus
topics), que ningún servicio consumía. Guía de migración en el README.

0.19.0-beta (incompatible) — MotivoExclusion gana TarifaNoConfigurada y ConvenioExime (al final,
ordinales 8 y 9). Viajan en LineaDescartada y CondicionEvaluada. Rompe a quien rotule el descarte según
su motivo: el que no reconoce cae al rótulo por defecto sin error. Aditivo en la misma versión:
TarifaUsada porta ConvenioUsado? Convenio (país contraparte, nombre e instrumento normativo del
convenio que fijó la tarifa), nullable con default.

0.18.0-beta (incompatible) — TipoTarifa gana PorMil (al final, ordinal 2). Su Tarifa llega en ‰
(4.14 = 4,14‰), no como fracción: multiplicarla por cien la muestra como 414 %. Viaja en TarifaUsada,
LineaDesglose y LineaDescartada. Rompe a quien haga switch exhaustivo sin arm por defecto y, sin
error, a quien formatee la tarifa según su tipo: el tipo que no reconoce cae al caso por defecto y
pierde la unidad.

0.17.0-beta (aditiva) — MotivoRechazoCalculo gana JurisdiccionNoEncontrada (al final, ordinal 6).
Un cálculo cuya ubicación no resuelve jurisdicción ahora publica CalculoTributarioRechazado en vez
de morir en dead-letter.

0.16.0-beta (aditiva) — PerfilTributarioActualizado, un evento saliente nuevo con su topic
(CanalImpuestos.TopicPerfilTributarioActualizado).

Avisa que el perfil tributario de un tercero cambió en algo que afecta el cálculo. Lleva solo la
identificación del tercero, el id del perfil y la fecha del cambio: NO lleva el estado del perfil, así
que quien reaccione vuelve a pedir el cálculo. Se emite uno por comando, no uno por cambio. Un cambio de
razón social no avisa, un aseguramiento que no movió nada tampoco, y la creación del perfil sí.
FechaDelCambio es cuándo quedó persistido el cambio, no la vigencia del dato tributario. El aviso no
reemplaza al recálculo a demanda, por dos razones: hoy avisan los cambios que entran por la API HTTP y no
los que entran por el servidor MCP de Impuestos, y la entrega del aviso no está garantizada como atómica
con el cambio.

No cambia ningún tipo existente. Quien venga de 0.14.0-beta o anterior pasa por 0.15.0-beta, que SÍ es
incompatible: ver README.md.

Historial completo de cambios incompatibles, y el índice de los miembros de enum agregados: README.md.