From 3eda59f5aaf5120e00ddb71b3d188bb8a3dcc9be Mon Sep 17 00:00:00 2001 From: dmolinari Date: Fri, 17 Apr 2026 18:40:05 -0300 Subject: [PATCH] feat(adm-009): ExceptionFilter mapping for fiscal exceptions ({error, message} unified) --- src/api/SIGCM2.Api/Filters/ExceptionFilter.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/api/SIGCM2.Api/Filters/ExceptionFilter.cs b/src/api/SIGCM2.Api/Filters/ExceptionFilter.cs index b86c0e2..bf0f054 100644 --- a/src/api/SIGCM2.Api/Filters/ExceptionFilter.cs +++ b/src/api/SIGCM2.Api/Filters/ExceptionFilter.cs @@ -231,6 +231,91 @@ public sealed class ExceptionFilter : IExceptionFilter context.ExceptionHandled = true; break; + // ADM-009: TipoDeIva fiscal exceptions + case PorcentajeInmutableException: + context.Result = new ObjectResult(new + { + error = "inmutable_usar_nueva_version", + message = "El porcentaje de un TipoDeIva es inmutable. Creá una nueva versión vía POST /iva/{id}/nueva-version." + }) + { + StatusCode = StatusCodes.Status409Conflict + }; + context.ExceptionHandled = true; + break; + + case AlicuotaInmutableException: + context.Result = new ObjectResult(new + { + error = "inmutable_usar_nueva_version", + message = "La alícuota de IngresosBrutos es inmutable. Creá una nueva versión vía POST /iibb/{id}/nueva-version." + }) + { + StatusCode = StatusCodes.Status409Conflict + }; + context.ExceptionHandled = true; + break; + + case PredecesorYaCerradoException predecesorYaCerradoEx: + context.Result = new ObjectResult(new + { + error = "predecesora_ya_cerrada", + message = predecesorYaCerradoEx.Message + }) + { + StatusCode = StatusCodes.Status409Conflict + }; + context.ExceptionHandled = true; + break; + + case DuplicateCodigoException duplicateCodigoEx: + context.Result = new ObjectResult(new + { + error = "duplicate_codigo", + message = duplicateCodigoEx.Message + }) + { + StatusCode = StatusCodes.Status409Conflict + }; + context.ExceptionHandled = true; + break; + + case DuplicateProvinciaException duplicateProvinciaEx: + context.Result = new ObjectResult(new + { + error = "duplicate_provincia", + message = duplicateProvinciaEx.Message + }) + { + StatusCode = StatusCodes.Status409Conflict + }; + context.ExceptionHandled = true; + break; + + case TipoDeIvaNotFoundException tipoDeIvaNotFoundEx: + context.Result = new ObjectResult(new + { + error = "tipo_iva_not_found", + message = tipoDeIvaNotFoundEx.Message + }) + { + StatusCode = StatusCodes.Status404NotFound + }; + context.ExceptionHandled = true; + break; + + case IngresosBrutosNotFoundException ingresosBrutosNotFoundEx: + context.Result = new ObjectResult(new + { + error = "ingresos_brutos_not_found", + message = ingresosBrutosNotFoundEx.Message + }) + { + StatusCode = StatusCodes.Status404NotFound + }; + context.ExceptionHandled = true; + break; + // ADM-008: PuntoDeVenta exceptions case PuntoDeVentaNotFoundException puntoDeVentaNotFoundEx: context.Result = new ObjectResult(new @@ -285,6 +370,21 @@ public sealed class ExceptionFilter : IExceptionFilter context.ExceptionHandled = true; break; + // ADM-009: vigencia_desde_invalida — domain throws ArgumentException for invalid vigencia range + case ArgumentException argEx when argEx.Message.Contains("vigencia_desde_invalida") || + argEx.ParamName == "vigenciaDesde" || + argEx.Message.Contains("debe ser posterior"): + context.Result = new ObjectResult(new + { + error = "vigencia_desde_invalida", + message = argEx.Message + }) + { + StatusCode = StatusCodes.Status400BadRequest + }; + context.ExceptionHandled = true; + break; + case ValidationException validationEx: var errors = validationEx.Errors .GroupBy(e => e.PropertyName)