2025-12-29 15:37:45 +00:00
|
|
|
using DoliMiddlewareApi.Exceptions;
|
|
|
|
|
using DoliMiddlewareApi.Services;
|
|
|
|
|
using Microsoft.AspNetCore.Diagnostics;
|
|
|
|
|
|
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
|
|
|
|
|
|
// Add services to the container.
|
|
|
|
|
|
|
|
|
|
builder.Services.AddControllers()
|
|
|
|
|
.AddJsonOptions(options =>
|
|
|
|
|
{
|
|
|
|
|
// Serializar enums como strings en vez de números
|
|
|
|
|
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
|
|
|
|
|
|
|
|
|
// Usar camelCase para propiedades (id en vez de Id)
|
|
|
|
|
options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
|
|
|
|
|
});
|
|
|
|
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
|
|
|
|
builder.Services.AddOpenApi();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Typed Client - Inyecta HttpClient directamente en DolibarrApiClient
|
|
|
|
|
builder.Services.AddHttpClient<DolibarrApiClient>(client =>
|
|
|
|
|
{
|
|
|
|
|
client.BaseAddress = new Uri(builder.Configuration["Dolibarr:ApiUrl"]!);
|
|
|
|
|
client.DefaultRequestHeaders.Add("DOLAPIKEY", builder.Configuration["Dolibarr:ApiKey"]!);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var app = builder.Build();
|
|
|
|
|
|
|
|
|
|
// Global exception handler
|
|
|
|
|
app.UseExceptionHandler(errorApp =>
|
|
|
|
|
{
|
|
|
|
|
errorApp.Run(async context =>
|
|
|
|
|
{
|
|
|
|
|
var exceptionHandler = context.Features.Get<IExceptionHandlerFeature>();
|
|
|
|
|
var exception = exceptionHandler?.Error;
|
2025-12-30 12:20:08 +00:00
|
|
|
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
|
|
|
|
|
var env = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
|
2025-12-29 15:37:45 +00:00
|
|
|
|
2025-12-30 12:20:08 +00:00
|
|
|
// PASO 1: Clasificar el tipo de error
|
|
|
|
|
var (statusCode, title, detail, isExpectedError) = exception switch
|
2025-12-29 15:37:45 +00:00
|
|
|
{
|
2025-12-30 12:20:08 +00:00
|
|
|
// ERRORES ESPERADOS (del negocio/cliente) - OK mostrar detalles
|
|
|
|
|
NotFoundException notFound => (StatusCodes.Status404NotFound, "Not Found", notFound.Message, true),
|
|
|
|
|
UnauthorizedException => (StatusCodes.Status401Unauthorized, "Unauthorized", "Invalid API credentials", true),
|
|
|
|
|
ForbiddenException forbidden => (StatusCodes.Status403Forbidden, "Forbidden", forbidden.Message, true),
|
|
|
|
|
BadRequestException badRequest => (StatusCodes.Status400BadRequest, "Bad Request", badRequest.Message, true),
|
|
|
|
|
|
|
|
|
|
// ERROR DE API EXTERNA (Dolibarr) - Mostrar que es externo pero no detalles internos
|
|
|
|
|
ApiException apiEx => (StatusCodes.Status500InternalServerError, "External Service Error",
|
|
|
|
|
env.IsDevelopment() ? apiEx.Message : "The external service is temporarily unavailable", false),
|
|
|
|
|
|
|
|
|
|
// ERRORES INESPERADOS (bugs del programador)
|
|
|
|
|
_ => (StatusCodes.Status500InternalServerError, "Internal Server Error",
|
|
|
|
|
env.IsDevelopment()
|
|
|
|
|
? exception?.Message ?? "An unexpected error occurred" // DESARROLLO: muestra el error real
|
|
|
|
|
: "An unexpected error occurred. Please try again later.", // PRODUCCIÓN: mensaje genérico
|
|
|
|
|
false)
|
2025-12-29 15:37:45 +00:00
|
|
|
};
|
|
|
|
|
|
2025-12-30 12:20:08 +00:00
|
|
|
// PASO 2: SIEMPRE loguear (crítico para debugging en producción)
|
|
|
|
|
if (isExpectedError)
|
|
|
|
|
{
|
|
|
|
|
// Errores esperados: log como Warning (no son bugs, son flujo normal)
|
|
|
|
|
logger.LogWarning(exception,
|
|
|
|
|
"Expected error: {ExceptionType} | Path: {Path} | Message: {Message}",
|
|
|
|
|
exception?.GetType().Name, context.Request.Path, exception?.Message);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Errores inesperados: log como Error (son bugs que HAY QUE ARREGLAR)
|
|
|
|
|
logger.LogError(exception,
|
|
|
|
|
"UNHANDLED EXCEPTION: {ExceptionType} | Path: {Path} | Message: {Message} | StackTrace: {StackTrace}",
|
|
|
|
|
exception?.GetType().Name, context.Request.Path, exception?.Message, exception?.StackTrace);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PASO 3: Construir respuesta ProblemDetails (estándar RFC 7807)
|
2025-12-29 15:37:45 +00:00
|
|
|
context.Response.StatusCode = statusCode;
|
2025-12-30 12:20:08 +00:00
|
|
|
context.Response.ContentType = "application/problem+json";
|
|
|
|
|
|
|
|
|
|
var problemDetails = new Microsoft.AspNetCore.Mvc.ProblemDetails
|
|
|
|
|
{
|
|
|
|
|
Status = statusCode,
|
|
|
|
|
Title = title,
|
|
|
|
|
Detail = detail,
|
|
|
|
|
Instance = context.Request.Path
|
|
|
|
|
};
|
2025-12-29 15:37:45 +00:00
|
|
|
|
2025-12-30 12:20:08 +00:00
|
|
|
// PASO 4: EN DESARROLLO añadir info extra para debugging
|
|
|
|
|
if (env.IsDevelopment() && !isExpectedError)
|
2025-12-29 15:37:45 +00:00
|
|
|
{
|
2025-12-30 12:20:08 +00:00
|
|
|
problemDetails.Extensions["exceptionType"] = exception?.GetType().Name;
|
|
|
|
|
problemDetails.Extensions["stackTrace"] = exception?.StackTrace;
|
|
|
|
|
if (exception?.InnerException != null)
|
|
|
|
|
{
|
|
|
|
|
problemDetails.Extensions["innerException"] = exception.InnerException.Message;
|
|
|
|
|
problemDetails.Extensions["innerStackTrace"] = exception.InnerException.StackTrace;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await context.Response.WriteAsJsonAsync(problemDetails);
|
2025-12-29 15:37:45 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Configure the HTTP request pipeline.
|
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
|
|
|
{
|
|
|
|
|
app.MapOpenApi();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.UseHttpsRedirection();
|
|
|
|
|
|
|
|
|
|
app.UseAuthorization();
|
|
|
|
|
|
|
|
|
|
app.MapControllers();
|
|
|
|
|
|
|
|
|
|
app.Run();
|