diff --git a/presentacion/arquitectura.png b/presentacion/arquitectura.png new file mode 100644 index 0000000..e1d4498 Binary files /dev/null and b/presentacion/arquitectura.png differ diff --git a/presentacion/index.html b/presentacion/index.html new file mode 100644 index 0000000..353f5f9 --- /dev/null +++ b/presentacion/index.html @@ -0,0 +1,487 @@ + + + + + + DoliMiddlewareApi - BFF para Dolibarr + + + + + + +
+ +
+

DoliMiddlewareApi

+

Backend for Frontend para Dolibarr ERP

+

Trabajo Fin de Grado — JavierMB — 2026

+
+ .NET 10 LTS + ASP.NET Core + Docker + JWT Auth +
+
+ +
+

Agenda

+
    +
  1. El problema
  2. Patron BFF y arquitectura
  3. +
  4. .NET 10 y por que LTS
  5. Stack tecnologico
  6. +
  7. Autenticacion dual
  8. API y endpoints
  9. +
  10. DTOs, Mappers y CQRS-lite
  11. Testabilidad: IDolibarrApiClient
  12. +
  13. Seguridad: Rate Limiting y mas
  14. Errores RFC 7807
  15. +
  16. Docker y despliegue
  17. Lecciones y futuro
  18. +
+
+ +
+

El problema

+
+

+ ❌ 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 +

+
+
+ +
+

Patron BFF y arquitectura

+

Un BFF es una API disenada exclusivamente para un tipo de cliente frontend.

+
+
+
JS Frontend
JavaScript
+
+ ↔ +
+
BFF
.NET 10
+
+ ↔ +
+
Dolibarr
ERP
+
+
+
    +
  • Abstrae la API legacy detras de endpoints limpios y RESTful
  • +
  • Transforma tipos (strings a int/decimal/DateOnly, estados numericos a texto)
  • +
  • Aplica reglas de negocio y validacion
  • +
  • Centraliza autenticacion: el frontend solo maneja JWT
  • +
+
+ +
+

Arquitectura general

+ Arquitectura +
+ +
+

.NET 10 LTS — por que esta eleccion

+

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.

+
+ +
+

Stack tecnologico

+
+
+

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

+
+
+
+ +
+

Estructura del proyecto

+
+
+

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

+
+ +
+

Autenticacion dual

+

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

+
+ +
+

Flujo de login

+
    +
  1. Frontend envia credenciales → POST /api/Auth/login
  2. +
  3. BFF autentica contra Dolibarr y recibe DOLAPIKEY
  4. +
  5. BFF genera sessionId (GUID) y cachea { sessionId → DOLAPIKEY }
  6. +
  7. BFF genera JWT con claims { sessionId, username } (30 min)
  8. +
  9. Frontend usa JWT en Authorization: Bearer para cada request
  10. +
+

Cada request: JWT → sessionId → DOLAPIKEY en cache → header inyectado

+

✓ JWT secreto via variable de entorno (nunca hardcodeado)

+
+ +
+

Seguridad: Rate Limiting

+ +

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.

+
+ +
+

Seguridad: Health Checks + Authorize

+ +
+
+

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)]
+
+
+
+ +
+

API — Endpoints

+ + + + + + + + + + + + + + + + + + + + + + +
MetodoRutaDescripcionAuth
Auth
POST/api/Auth/loginLogin → JWTNo
Clients
GET/api/ClientsListar clientes + contactosJWT
Invoices
GET/api/InvoicesListar (pag., filtros)JWT
GET/api/Invoices/{id}Detalle con lineasJWT
POST/api/InvoicesCrear facturaJWT
PUT/api/Invoices/{id}ActualizarJWT
PATCH/api/Invoices/{id}/statusCambiar estadoJWT
POST/api/Invoices/{id}/validateValidar borradorJWT
DELETE/api/Invoices/{id}Eliminar borradorJWT
POST/api/Invoices/{id}/linesAnadir lineaJWT
DELETE/api/Invoices/{id}/lines/{lineId}Eliminar lineaJWT
GET/api/Invoices/{id}/paymentsListar pagosJWT
POST/api/Invoices/{id}/paymentsRegistrar pagoJWT
Document
GET/api/Document/invoice/{ref}/pdfGenerar PDFJWT
Health
GET/healthEstado del servicioNo
+
+ +
+

Logica en el BFF

+

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);
+}
+
+ +
+

CQRS-lite: Command y Query DTOs

+
+
+

Command (entrada)

+
  • CreateTokenDto
  • CreateInvoiceDto
  • CreateInvoiceLineDto
  • CreateInvoicePaymentDto
  • UpdateInvoiceDto
  • UpdateInvoiceStatusDto
+
+
+

Query (salida)

+
  • InvoiceDto / InvoiceDetailDto
  • InvoiceLineDto
  • InvoicePaymentDto
  • ClientDto
  • LoginResponse
+
+
+

Tambien Dolibarr DTOs modelan la respuesta cruda del ERP — todo como string.

+
+ +
+

Testabilidad: IDolibarrApiClient

+

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

+
+
+
+ +
+

Mappers: Antes vs Despues

+
+
+

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

+
+ +
+

Gestion de errores — RFC 7807

+
// 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}

+
+ +
+

Pipeline de middleware

+
    +
  1. ExceptionHandler — ProblemDetails RFC 7807
  2. +
  3. RateLimiter (🔒 5 req/min en login)
  4. +
  5. Swagger (solo Development)
  6. +
  7. HTTPS Redirection
  8. +
  9. CORS (localhost:3000, 3001, 5173)
  10. +
  11. Authentication (JWT Bearer)
  12. +
  13. Authorization
  14. +
  15. MapControllers
  16. +
  17. HealthChecks (/health)
  18. +
+
+ +
+

Despliegue con Docker

+
# 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
+
+ +
+

Dockerfile

+
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

+
+ +
+

Lecciones aprendidas

+
+

✓ 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

+
+
+ +
+

Mejoras pendientes

+
+

☐ 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...

+
+
+ +
+

🙏 Gracias!

+

DoliMiddlewareApi — BFF para Dolibarr

+

.NET 10 LTS • JWT • Rate Limiting • Health Checks • Docker

+

Preguntas →

+
+ +
+ + + + + + \ No newline at end of file