diff --git a/presentacion/index.html b/presentacion/index.html index 353f5f9..d1e2f44 100644 --- a/presentacion/index.html +++ b/presentacion/index.html @@ -3,404 +3,251 @@ - DoliMiddlewareApi - BFF para Dolibarr + 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 + +
+

TFG — Javier Mengual — 2026

+

DoliMiddlewareApi

+

Tu frontend merece algo mejor que strings rotos

+
+ + +
+

El frontend no debería tener que lidiar con esto

+

Dolibarr funciona. Pero su API devuelve un desastre que no puedes entregarle a un frontend moderno y decir "aquí tienes, apañatelas".

+
+
+

Lo que te da Dolibarr

+
+ IDs como "string"
+ Fechas en timestamp Unix
+ Estados que son números: "1"
+ snake_case inconsistente
+ Credenciales volando por ahí +
+
+
+

Lo que tu frontend merece

+
+ IDs como int
+ Fechas ISO 8601
+ Estados legibles: "unpaid"
+ camelCase
+ Un JWT y ya está +
+
+
-

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 -

+

Un escudo entre tu app y el caos

+

El patrón BFF —Backend for Frontend— no es un proxy. Es una capa que traduce, protege y enriquece. Tu frontend habla con él, y él se ocupa de lo demás.

+
+
Dolibarr
+ ⟶ +
BFF
+ ⟶ +
Tu frontend
-
-

- ✅ 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
  • +
      +
    • El frontend nunca habla con Dolibarr directamente
    • +
    • Si el ERP cambia algo, tocas un sitio, no cincuenta
    • +
    • Y puedes meterle cosas que Dolibarr jamás va a tener
-
-

Arquitectura general

- Arquitectura + +
+

Así funciona

+ 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

+ +
+

¿Por qué .NET 10?

+

Si voy a poner una API en producción, quiero dormir tranquilo tres años.

+ +
+
+

LTS hasta 2028

+

Tres años de soporte. Me da igual lo que pase meantime.

-
-

C# 14

-

Field-backed properties, extension types, ?=

+
+

Rendimiento

+

Lo suficientemente rápido para que la latencia no sea un problema.

-
-

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

+
+

Todo incluido

+

JWT, rate limiting, health checks, OpenAPI. Sin buscar librerías raras.

+
-

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

-
+

Lo que cubre

+
+

Auth — login, JWT

+

Facturas — CRUD, líneas, pagos, estado

+

Clientes — con contactos incluidos

+

Proveedor — facturas de proveedor

+

Bancos — cuentas y movimientos

+

Documentos — PDFs de facturas

+

Setup — diccionarios, país, compañía

+

Notificaciones — webhooks

-

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

Sí, gran parte es 1:1 con Dolibarr. Pero el BFF añade lo que el ERP no tiene: tipos correctos, estados legibles, enriquecimiento con nombres de cliente y notificaciones.

+
-

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

-
+

De francés y strings... a inglés y tipos

+
-

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",
+            

Dolibarr

+
{
+  "statut":    "1",
   "total_ttc": "150.50",
-  "date": "1715673600",
-  "socid": "3",
-  "note_public": null
+  "date":      "1715673600",
+  "socid":     "3"
 }
-

BFF (limpio)

-
{
-  "status": "unpaid",
-  "total": 150.50,
-  "date": "2024-05-14",
-  "clientId": 3,
-  "notePublic": null
+            

BFF

+
{
+  "status":   "unpaid",
+  "total":    150.50,
+  "date":     "2024-05-14",
+  "clientId": 3
 }
-

Resultado El frontend recibe datos limpios, tipados y en camelCase

+

Mismo dato. Otro mundo.

-
-

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}

+ +
+

Documentación automática

+ Swagger UI +

OpenAPI 3.1 generado al arrancar. Cada endpoint documentado.

+
-

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

La contraseña nunca sale del servidor

+

El frontend maneja un JWT. La API key de Dolibarr queda atrapada dentro del BFF. Si alguien intercepta el token del usuario, caduca en 8 horas y no tiene acceso al ERP.

+
+
+

1 — Login

+

El usuario se autentica. El BFF recibe la API key de Dolibarr y la guarda en memoria.

+
+
+

2 — JWT

+

El BFF genera un JWT para el navegador. La API key nunca sale de ahí.

+
+
+

3 — Cada petición

+

El JWT lleva un sessionId. Con ese sessionId se busca la API key en caché y se inyecta al vuelo.

+
+
+
+

Cada usuario tiene su propia API key aislada. Si se cachease en el HttpClient, todos compartirían la misma.

+
+
-

Despliegue con Docker

-
# compose.yaml
-services:
+    

Lo que un wrapper puede hacer que el ERP no

+

Al poner una capa por delante, puedes añadirle cosas que Dolibarr nunca va a tener. El ejemplo más claro: cuando una factura cambia de estado, el equipo se entera al momento. Sin abrir el sistema, sin mirar nada.

+
+
+

Teams, Slack, lo que sea

+

Configuras una URL y avisa donde quieras. Hoy es Teams, mañana puede ser Telegram, email, lo que haga falta.

+
+
+

La interfaz ya está

+

Añadir un canal nuevo es implementar una interfaz. El resto del código ni se toca. Así se escala un wrapper de verdad.

+
+
+
+ + +
+

Un comando y funciona

+

docker compose up

+
services:
   mysql:
     image: mysql:8.0
     environment:
-      MYSQL_ROOT_PASSWORD: dolibarr
       MYSQL_DATABASE: dolibarr
     volumes: [mysql_data:/var/lib/mysql]
 
@@ -409,62 +256,16 @@ services:
     depends_on: [mysql]
     ports: ["80:80"]
 
-  dolimiddlewareapi:
+  bff:
     build: ./DoliMiddlewareApi
-    ports: ["5000:8080"]
     depends_on: [dolibarr]
-
-  # redis:  # Cache distribuida (planificado)
-  #   image: redis:7-alpine
+ ports: ["5001:8080"]
+

MySQL, Dolibarr, el BFF. Tres contenedores, un comando.

-
-

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 →

+ +
+

Preguntas

@@ -474,13 +275,14 @@ ENTRYPOINT ["dotnet", "DoliMiddlewareApi.dll"]