Backend for Frontend para Dolibarr ERP
Trabajo Fin de Grado — JavierMB — 2026
❌ Dolibarr devuelve todo como string (ids, fechas, decimales, estados)
❌ API REST inconsistente (snake_case, unix timestamps, codigos numericos)
❌ Exponer el ERP directamente al navegador = riesgo de seguridad
❌ El frontend tendria que parsear y transformar todo manualmente
✅ El BFF transforma, valida y cachea — frontend recibe datos limpios
✅ JWT propio — credenciales Dolibarr nunca llegan al navegador
✅ Contratos tipados con DataAnnotations y mappers
Un BFF es una API disenada exclusivamente para un tipo de cliente frontend.
Elegimos .NET 10 LTS (soporte hasta noviembre 2028). Ventajas clave:
PERFORMANCE
JIT inlining, escape analysis, NativeAOT mas rapido y ligero
C# 14
Field-backed properties, extension types, ?=
ASP.NET CORE 10
OpenAPI 3.1 por defecto, validacion built-in, rate limiting nativo
LTS = Long Term Support — parches de seguridad y soporte durante 3 anos. La eleccion correcta para un TFG.
Backend
.NET 10 LTS
ASP.NET Core Web API
JWT Bearer Auth
Rate Limiting nativo
Health Checks
Patrones
CQRS-lite (command/query)
Service Layer
Static Mappers
ProblemDetails (RFC 7807)
IMemoryCache
Interface segregation
Infra
Docker + Compose
MySQL 8.0
Dolibarr (container)
xUnit + Moq
Swagger / OpenAPI
Controllers/
AuthController
ClientsController
InvoicesController
DocumentController
✓ [ProducesResponseType]
Services/
Auth/ AuthService, TokenCache, JwtProvider
Clients/ IDolibarrApiClient interfaz
InvoiceService, ClientService, DocumentService
Dtos/
command/ CreateInvoice...
query/ InvoiceDto...
Dolibarr/ *Response...
Otros
Mappers/
Exceptions/
Program.cs
clave IDolibarrApiClient es una interfaz — facilita mocking y testing
Dos capas de autenticacion transparentes para el frontend:
FRONTEND ↔ BFF
JWT HMAC-SHA256
Claims: sessionId + username
Expiracion: 30 min
ASP.NET Core valida
BFF ↔ DOLIBARR
DOLAPIKEY por sesion
Cacheada en IMemoryCache
Clave: sessionId del JWT
Inyectada como header
✅ La API key de Dolibarr nunca sale del servidor
POST /api/Auth/login{ sessionId → DOLAPIKEY }{ sessionId, username } (30 min)Authorization: Bearer para cada requestCada request: JWT → sessionId → DOLAPIKEY en cache → header inyectado
✓ JWT secreto via variable de entorno (nunca hardcodeado)
ASP.NET Core 10 incluye rate limiting nativo. Lo usamos para proteger el login:
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = 429;
options.GlobalLimiter = PartitionedRateLimiter
.Create<HttpContext, string>(context =>
{
if (context.Request.Path.StartsWith("/api/Auth/login"))
return RateLimitPartition.GetSlidingWindowLimiter(
"login", _ => new SlidingWindowRateLimiterOptions
{
PermitLimit = 5, Window = TimeSpan.FromMinutes(1)
});
return RateLimitPartition.GetNoLimiter("default");
});
});
Maximo 5 intentos de login por minuto. Exceso → 429 Too Many Requests.
HEALTH CHECKS
Endpoint /health que verifica el estado del BFF. Ideal para Docker y Kubernetes.
builder.Services.AddHealthChecks();
app.MapHealthChecks("/health");
AUTHORIZE + SWAGGER DOCS
Todos los controllers con [Authorize]. Todos los endpoints documentados con [ProducesResponseType] — OpenAPI生成 automático.
[ProducesResponseType(typeof(InvoiceDetailDto),
StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails),
StatusCodes.Status404NotFound)]
| Metodo | Ruta | Descripcion | Auth |
|---|---|---|---|
| Auth | |||
| POST | /api/Auth/login | Login → JWT | No |
| Clients | |||
| GET | /api/Clients | Listar clientes + contactos | JWT |
| Invoices | |||
| GET | /api/Invoices | Listar (pag., filtros) | JWT |
| GET | /api/Invoices/{id} | Detalle con lineas | JWT |
| POST | /api/Invoices | Crear factura | JWT |
| PUT | /api/Invoices/{id} | Actualizar | JWT |
| PATCH | /api/Invoices/{id}/status | Cambiar estado | JWT |
| POST | /api/Invoices/{id}/validate | Validar borrador | JWT |
| DELETE | /api/Invoices/{id} | Eliminar borrador | JWT |
| POST | /api/Invoices/{id}/lines | Anadir linea | JWT |
| DELETE | /api/Invoices/{id}/lines/{lineId} | Eliminar linea | JWT |
| GET | /api/Invoices/{id}/payments | Listar pagos | JWT |
| POST | /api/Invoices/{id}/payments | Registrar pago | JWT |
| Document | |||
| GET | /api/Document/invoice/{ref}/pdf | Generar PDF | JWT |
| Health | |||
| GET | /health | Estado del servicio | No |
El BFF no es solo un proxy — anade valor:
VALIDACION
[Required], [Range], [StringLength]
Paginacion 1-based con validacion
REGLAS DE NEGOCIO
Solo drafts se pueden eliminar, validar o anadir lineas
Estados limitados: draft/unpaid/paid
TRANSFORMACION
Strings → int/decimal/DateOnly"1" → "unpaid"
Enriquecer con nombres de cliente
public async Task ValidateInvoiceAsync(int id)
{
var invoice = await GetRawInvoiceAsync(id);
if (invoice.Statut != "0")
throw new ForbiddenException("Solo se pueden validar facturas en borrador");
await _apiClient.PostAsync($"invoices/{id}/validate", null);
}
Command (entrada)
Query (salida)
Tambien Dolibarr DTOs modelan la respuesta cruda del ERP — todo como string.
La comunicacion con Dolibarr se abstrae en una interfaz, permitiendo mocking en tests:
public interface IDolibarrApiClient
{
Task<T> GetResourceAsync<T>(string endpoint) where T : class;
Task<List<T>> GetCollectionAsync<T>(string endpoint) where T : class;
Task<string> PostAsync(string endpoint, object requestBody);
Task<string> PutAsync(string endpoint, object requestBody);
Task DeleteAsync(string endpoint);
}
PRODUCCION
DolibarrApiClient — inyecta DOLAPIKEY desde cache y llama al ERP real
TESTS
Mock<IDolibarrApiClient> — tests unitarios sin depender de Dolibarr
Dolibarr (crudo)
{
"statut": "1",
"total_ttc": "150.50",
"date": "1715673600",
"socid": "3",
"note_public": null
}
BFF (limpio)
{
"status": "unpaid",
"total": 150.50,
"date": "2024-05-14",
"clientId": 3,
"notePublic": null
}
Resultado El frontend recibe datos limpios, tipados y en camelCase
// Excepciones de dominio
public class NotFoundException : Exception { }
public class UnauthorizedException : Exception { }
public class ForbiddenException : Exception { }
public class BadRequestException : Exception { }
public class ApiException : Exception { }
// Handler global (Program.cs)
app.UseExceptionHandler(options =>
{
options.Map<NotFoundException>(ex => Results.Problem(
statusCode: 404, title: "Recurso no encontrado", detail: ex.Message));
options.Map<UnauthorizedException>(ex => Results.Problem(
statusCode: 401, title: "No autorizado", detail: ex.Message));
// Forbidden -> 403, BadRequest -> 400, Api -> 500
});
Todas las respuestas siguen ProblemDetails RFC 7807: {"type":"...", "title":"...", "status":404}
# compose.yaml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: dolibarr
MYSQL_DATABASE: dolibarr
volumes: [mysql_data:/var/lib/mysql]
dolibarr:
image: dolibarr/dolibarr:latest
depends_on: [mysql]
ports: ["80:80"]
dolimiddlewareapi:
build: ./DoliMiddlewareApi
ports: ["5000:8080"]
depends_on: [dolibarr]
# redis: # Cache distribuida (planificado)
# image: redis:7-alpine
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
USER $APP_UID # No-root por seguridad
EXPOSE 8080 8081
ENTRYPOINT ["dotnet", "DoliMiddlewareApi.dll"]
.NET 10 Multi-stage build — runtime ligero, usuario no-root por defecto
✓ BFF justificado — el frontend no deberia lidiar con APIs inconsistentes
✓ Doble token — credenciales Dolibarr nunca salen del servidor
✓ Contratos tipados — DTOs con validacion evitan datos incorrectos
✓ Seguridad nativa — Rate limiting, health checks y [Authorize]
✓ Errores centralizados — RFC 7807 ProblemDetails consistentes
✓ Abstraccion para testing — IDolibarrApiClient permite mocking completo
☐ Tests unitarios (proyecto xUnit + Moq preparado, IDolibarrApiClient facilita mocking)
☐ Redis para cache distribuida
☐ Response caching
☐ Metadatos de paginacion (X-Total-Count)
☐ CI/CD
☐ Mas endpoints: productos, pedidos, terceros...
DoliMiddlewareApi — BFF para Dolibarr
.NET 10 LTS • JWT • Rate Limiting • Health Checks • Docker
Preguntas →