Cosmos.Impuestos.Contratos
0.33.0-beta
dotnet add package Cosmos.Impuestos.Contratos --version 0.33.0-beta
NuGet\Install-Package Cosmos.Impuestos.Contratos -Version 0.33.0-beta
<PackageReference Include="Cosmos.Impuestos.Contratos" Version="0.33.0-beta" />
<PackageVersion Include="Cosmos.Impuestos.Contratos" Version="0.33.0-beta" />
<PackageReference Include="Cosmos.Impuestos.Contratos" />
paket add Cosmos.Impuestos.Contratos --version 0.33.0-beta
#r "nuget: Cosmos.Impuestos.Contratos, 0.33.0-beta"
#:package Cosmos.Impuestos.Contratos@0.33.0-beta
#addin nuget:?package=Cosmos.Impuestos.Contratos&version=0.33.0-beta&prerelease
#tool nuget:?package=Cosmos.Impuestos.Contratos&version=0.33.0-beta&prerelease
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.33.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.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
Indeterminadoa 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.
- a
- Revierte el criterio de
0.29.0-betapara esos dos casos. Aquella versión dejó enIndeterminadolo 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
ConfirmacionTributariaRechazadacon uno de dos motivos:ComandoInvalidocuando el defecto es de la solicitud yDatosFaltantespara 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 deDatosFaltantesaComandoInvalido. No cambia ninguna firma. Qué hacer: quien discriminabaIndeterminadorevisa estos casos; quien tratabaDatosFaltantesde la confirmación como «faltó un dato que corrijo y reintento» trataComandoInvalidoigual; 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.ActividadEconomicaen las cinco líneas — la actividad por la que se tarifó cuando el factor es de actividad.nullen las líneas de otros factores. Qué hacer: con catálogo,FactorUtilizadoes 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
FactorUtilizadosigue siendo la actividad, yActividadEconomicala 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
jurisdicciony devuelvevariantes(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.DatoInvalidoqueda sin productor. Se conserva porque su ordinal está publicado. Qué hacer: quien lo discriminaba para la actividad informada vacía pasa aComandoInvalido.ConfirmacionTributariaRechazadareclasifica esos casos. Cuando el motor rechaza la solicitud de confirmación por estar mal armada, el motivo publicado pasa deDatosFaltantesaComandoInvalido(MotivoRechazoConfirmacion.ComandoInvalidoya existía). Las demás fallas del motor siguen enDatosFaltantes. No cambia ninguna firma niTransaccionYaConfirmada. Qué hacer: si tratabasDatosFaltantesde la confirmación como «faltó un dato que corrijo y reintento», tratáComandoInvalidoigual.
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 deDatoInvalido: sin montos no hay prorrateo que proponer. Qué hacer: quien discrimineDatoInvalidopara ese caso debe pasar aDatosFaltantes.DatoInvalidose conserva y sigue emitiéndose para otras formas inválidas de la solicitud (hoy, una actividad económica informada vacía).ConfirmacionTributariaRechazadaempieza a llevarIntervencionExcedeMargen— un miembro que ya existía enMotivoRechazoConfirmacion. Se emite cuando elDesgloseConfirmadode 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.HuboIntervenciondel registro tributario — en un gravamen, una diferencia de hasta esa unidad entre lo confirmado y el cálculo de referencia deja de marcarHuboIntervencion: 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.nulles «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 conDatoInvalidoy la confirmación conDatosFaltantes.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 traeActividadesCandidatasen orden ordinal. Qué hacer: indicar una de ellas enActividadEconomicadel concepto y recalcular. La confirmación no se rechaza por este motivo: se registra con el tributo descartado.LineaDescartada.ActividadRechazada— conActividadNoRegistrada, la actividad que el concepto informó y el perfil no tenía vigente.nullsi 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}.SolicitarRegistroTributarioentrega solo el desglose confirmado.
Otros efectos observables
- La entrega solo rechaza con
RegistroInexpresablepor 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 porRegistroTributarioIdyReferenciaSolicitud. - La confirmación ya no devuelve los montos: los mandó el consumidor. Devuelve el
Tributoque 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 campoTributoDe<País>por país sería una unión discriminada disfrazada de N nulables mutuamente excluyentes.DeColombiaconserva el mismo criterio de derivación por código que teníaTributoDeColombiaantes de este cambio — ver «Riesgo aceptado» en «DeColombiaenTributo» abajo.Absorbe #362:
LineaDescartadanunca había llevadoFamiliani 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.DesgloseConfirmadopasa a ser una lista deLineaConfirmada, 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
Tributolo define Impuestos y laConfirmacionTributariaRealizadalo 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 conComandoInvalido. En un desgravamen sale del registro origen, y un código que el origen no tiene sigue rechazándose conConceptoNoExisteEnOrigen.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
CalculoTributarioSolicitadoque no envíaOrigenDelDesgravamenno ve ningún cambio. Pero quien ya confirma desgravámenes conSolicitarConfirmacionTributariasí 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 es0.23.0-beta: lo que eraConfirmacionTributariaRealizadapasa a serConfirmacionTributariaRechazadaen tres casos nuevos. Concretamente:Conceptospasa a leerse, y no puede llegar vacío. Antes la referencia se derivaba prorrateando elDesgloseConfirmadoyConceptosno se leía. Ahora, siConceptosviene vacío, la confirmación se rechaza conMotivoRechazoConfirmacion.DatosFaltantes. Cubre también el concepto individual: todo concepto que venga enDesgloseConfirmadotiene que venir enConceptos, porque su monto devuelto es lo que se descuenta del saldo de la transacción origen; si falta, la confirmación se rechaza conDatosFaltantes.DesgravamenExcedeSaldogana 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.ConceptoNoExisteEnOrigentambién sale deConceptos, además deDesgloseConfirmado.- La referencia contra la que se compara lo confirmado cambia: se deriva de
Conceptoscon cierre por residuo, no delDesgloseConfirmadosin más.
CalculoTributarioSolicitadoganaOrigenDelDesgravamen({ 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 elRegistroTributariode la transacción de origen y deriva el desglose por prorrateo (un factor por concepto,R((valor × devuelto) ÷ gravado), hijosporcentajeDePadreincluidos, 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 conMotivoRechazoCalculo.DatoInvalido.MotivoRechazoCalculoganaOrigenNoEncontrado,ConceptoNoExisteEnOrigenyDesgravamenExcedeSaldo, al final — ver «Miembros de enum agregados» abajo. Estos tres sí son aditivos:MotivoRechazoCalculoes 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
(
DesgravamenExcedeSaldoen 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. MotivoRechazoCalculoganaClasificacionNoVigente, 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 porClasificacionExcluida: 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 recibirCalculoTributarioRechazado. 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, niCalculoTributarioRechazadopara un cálculo niConfirmacionTributariaRechazadapara 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 comodate, 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). ConDateTimeOffsetel 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 suDateTimeOffsetcomo la medianoche de ese día con el offset local de su máquina. PerfilTributarioActualizado.FechaDelCambiosigue siendoDateTimeOffset: 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ónhomologacion: 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):hastaes el primer día en que deja de regir. Un tramo conhasta = desdenunca rigió. - Se retira la réplica de clasificaciones por el bus:
ClasificacionTributariaPublicada,ClasificacionTributariaDesactivadayReplicaDeClasificacionesSolicitada, 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. Configuracionse 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) se rotula incompatible: quien
discriminaba el motivo anterior deja de recibirlos.
| Versión | Etiqueta | Enum | Miembro | Qué significa |
|---|---|---|---|---|
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.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.
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. |
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 produce uno u otro. |
SolicitarConfirmacionTributaria |
entrante | Solicita confirmar un registro tributario (gravamen/desgravamen); para un desgravamen, Conceptos trae el monto devuelto de cada uno. |
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. 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. |
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 y DeColombia.
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.
FechaDelCambioes 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 no reemplaza al recálculo a demanda, por dos razones. Hoy avisan los cambios que entran por la API HTTP, y los que entran por el servidor MCP de Impuestos todavía no. Y la entrega del aviso no está garantizada como atómica con el cambio: un cambio persistido puede quedar sin su aviso.
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 | Versions 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. |
-
net10.0
- Cosmos.EventDriven.Abstractions (>= 3.1.0)
- Cosmos.Types (>= 1.2.0)
- Cosmos.Types.Abstractions (>= 1.1.1)
- Cosmos.Types.IdentificacionesLegales (>= 1.3.0)
- Cosmos.Types.Paises (>= 1.1.1)
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.33.0-beta | 46 | 10/2/2026 |
| 0.32.0-beta | 99 | 10/1/2026 |
| 0.31.0-beta | 84 | 10/1/2026 |
| 0.30.0-beta | 42 | 9/30/2026 |
| 0.29.0-beta | 41 | 9/30/2026 |
| 0.28.0-beta | 43 | 9/30/2026 |
| 0.27.0-beta | 118 | 9/30/2026 |
| 0.26.0-beta | 87 | 9/29/2026 |
| 0.25.0-beta | 133 | 9/28/2026 |
| 0.24.0-beta | 61 | 9/28/2026 |
| 0.23.0-beta | 103 | 9/27/2026 |
| 0.22.0-beta | 67 | 9/27/2026 |
| 0.21.0-beta | 65 | 9/26/2026 |
| 0.20.0-beta | 57 | 9/25/2026 |
| 0.19.0-beta | 145 | 9/24/2026 |
| 0.17.0-beta | 138 | 9/21/2026 |
| 0.15.0-beta | 96 | 9/16/2026 |
| 0.14.0-beta | 267 | 8/21/2026 |
| 0.13.0-beta | 388 | 8/5/2026 |
| 0.11.0-beta | 77 | 7/28/2026 |
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.