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 @@
-Backend for Frontend para Dolibarr ERP
-Trabajo Fin de Grado — JavierMB — 2026
-TFG — Javier Mengual — 2026
+Tu frontend merece algo mejor que strings rotos
+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
+"string""1"Lo que tu frontend merece
+int"unpaid"
- ❌ 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 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.
+
- ✅ 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
+ +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.
- - -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.
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
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
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)]
- 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.
| 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",
+ 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.
// 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}
+ OpenAPI 3.1 generado al arrancar. Cada endpoint documentado.
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.
+# 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.
+
+
+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.
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 →
+ +