2026-05-14 18:48:01 +00:00
<!DOCTYPE html>
< html lang = "es" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-05-24 11:32:08 +00:00
< title > DoliMiddlewareApi — BFF para Dolibarr< / title >
2026-05-14 18:48:01 +00:00
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.css" >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/theme/black.css" >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/plugin/highlight/monokai.css" >
< style >
2026-05-24 11:32:08 +00:00
.reveal h1,.reveal h2,.reveal h3{text-transform:none;letter-spacing:0}
.reveal section{padding:22px 46px;box-sizing:border-box}
.reveal h1{font-size:1.7em;font-weight:800;margin-bottom:.1em}
.reveal h2{font-size:1.15em;margin-bottom:.5em;color:#fff;font-weight:700}
.reveal p{font-size:.8em;line-height:1.55;margin:.35em 0}
.reveal ul{font-size:.78em;line-height:1.7;margin:.2em 0 .2em 1.3em}
.reveal pre{width:100%;margin:.5em 0;border-radius:5px}
.reveal pre code{font-size:.62em;line-height:1.25}
body,.reveal{background:#111;color:#eee}
.cols{display:flex;gap:16px;align-items:stretch}
.cols>*{flex:1}
2026-05-14 18:48:01 +00:00
.center{text-align:center}
2026-05-24 11:32:08 +00:00
.card{
background:#181818;
border:1px solid #333;
border-radius:6px;
padding:16px 20px;
margin:5px 0;
}
.callout{
background:#1c1a12;
border:1px solid #e2a84b44;
padding:14px 20px;
border-radius:6px;
margin:.7em 0;
}
.callout p{margin:0;font-size:.78em;line-height:1.5}
.badge{
display:inline-block;
background:#181818;
border:1px solid #444;
border-radius:3px;
padding:5px 12px;
font-size:.58em;
margin:2px;
color:#aaa;
font-family:'SF Mono',Consolas,monospace;
}
.flow{display:flex;align-items:center;justify-content:center;gap:16px;margin:1em 0;font-size:.85em}
.flow-box{padding:14px 26px;border-radius:6px;font-weight:700;text-align:center;font-size:.95em}
.flow-arrow{color:#e2a84b;font-size:1.5em;font-weight:800}
.img-full{width:100%;max-height:52vh;object-fit:contain;border-radius:6px;display:block;margin:0 auto}
.ep{display:grid;grid-template-columns:1fr 1fr;gap:6px 20px;margin-top:.4em}
.ep p{margin:0;line-height:1.65}
.sub{font-size:.68em;color:#888;line-height:1.5}
.glow{font-size:.95em;color:#e2a84b;font-weight:700}
.big{font-size:1.5em;font-weight:800}
2026-05-14 18:48:01 +00:00
< / style >
< / head >
< body >
< div class = "reveal" > < div class = "slides" >
2026-05-24 11:32:08 +00:00
<!-- PORTADA -->
< section class = "center" >
< p style = "font-size:.65em;color:#888;margin-bottom:.5em" > TFG — Javier Mengual — 2026< / p >
< h1 style = "color:#fff" > DoliMiddlewareApi< / h1 >
< p style = "font-size:.95em;color:#e2a84b;margin:.2em 0 1.8em" > Tu frontend merece algo mejor que strings rotos< / p >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- EL PROBLEMA -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > El frontend no debería tener que lidiar con esto< / h2 >
< p > Dolibarr funciona. Pero su API devuelve un desastre que no puedes entregarle a un frontend moderno y decir "aquí tienes, apañatelas".< / p >
< div class = "cols" style = "margin-top:.8em" >
< div class = "card" style = "border-top:3px solid #f87171" >
< p style = "font-size:.8em;color:#f87171;font-weight:700;margin:0 0 8px" > Lo que te da Dolibarr< / p >
< div class = "sub" style = "line-height:1.85" >
IDs como < code > "string"< / code > < br >
Fechas en timestamp Unix< br >
Estados que son números: < code > "1"< / code > < br >
snake_case inconsistente< br >
Credenciales volando por ahí
< / div >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #4ade80" >
< p style = "font-size:.8em;color:#4ade80;font-weight:700;margin:0 0 8px" > Lo que tu frontend merece< / p >
< div class = "sub" style = "line-height:1.85" >
IDs como < code > int< / code > < br >
Fechas ISO 8601< br >
Estados legibles: < code > "unpaid"< / code > < br >
camelCase< br >
Un JWT y ya está
< / div >
2026-05-14 18:48:01 +00:00
< / div >
< / div >
< / section >
2026-05-24 11:32:08 +00:00
<!-- BFF -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > Un escudo entre tu app y el caos< / h2 >
< p > 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.< / p >
< div class = "flow" style = "margin:1.3em 0" >
< div class = "flow-box" style = "background:#1e1510;color:#fb923c;border:2px solid #fb923c88" > Dolibarr< / div >
< span class = "flow-arrow" > ⟶< / span >
< div class = "flow-box" style = "background:#1a1710;color:#e2a84b;border:2px solid #e2a84b88" > BFF< / div >
< span class = "flow-arrow" > ⟶< / span >
< div class = "flow-box" style = "background:#0f1a11;color:#4ade80;border:2px solid #4ade8088" > Tu frontend< / div >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< ul >
< li > El frontend nunca habla con Dolibarr directamente< / li >
< li > Si el ERP cambia algo, tocas un sitio, no cincuenta< / li >
< li > Y puedes meterle cosas que Dolibarr jamás va a tener< / li >
< / ul >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- ARQUITECTURA -->
< section class = "center" >
< h2 > Así funciona< / h2 >
< img src = "arquitectura.png" class = "img-full" alt = "Arquitectura" style = "max-height:54vh" >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- .NET 10 -->
< section data-background-image = "dotnetall.jpg" data-background-size = "cover" data-background-position = "center" data-background-opacity = "0.12" >
< h2 > ¿Por qué .NET 10?< / h2 >
< p > Si voy a poner una API en producción, quiero dormir tranquilo tres años.< / p >
< img src = "dotnetall.jpg" style = "width:100%;max-height:32vh;object-fit:cover;object-position:center;border-radius:6px;display:block;margin-bottom:10px" >
< div class = "cols" style = "gap:10px" >
< div class = "card" style = "border-top:3px solid #e2a84b" >
< p style = "font-size:.76em;color:#e2a84b;font-weight:700;margin:0 0 3px" > LTS hasta 2028< / p >
< p class = "sub" > Tres años de soporte. Me da igual lo que pase meantime.< / p >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #e2a84b" >
< p style = "font-size:.76em;color:#e2a84b;font-weight:700;margin:0 0 3px" > Rendimiento< / p >
< p class = "sub" > Lo suficientemente rápido para que la latencia no sea un problema.< / p >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #e2a84b" >
< p style = "font-size:.76em;color:#e2a84b;font-weight:700;margin:0 0 3px" > Todo incluido< / p >
< p class = "sub" > JWT, rate limiting, health checks, OpenAPI. Sin buscar librerías raras.< / p >
2026-05-14 18:48:01 +00:00
< / div >
< / div >
< / section >
2026-05-24 11:32:08 +00:00
<!-- ENDPOINTS -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > Lo que cubre< / h2 >
< div class = "ep" >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Auth< / span > < span class = "sub" > — login, JWT< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Facturas< / span > < span class = "sub" > — CRUD, líneas, pagos, estado< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Clientes< / span > < span class = "sub" > — con contactos incluidos< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Proveedor< / span > < span class = "sub" > — facturas de proveedor< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Bancos< / span > < span class = "sub" > — cuentas y movimientos< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Documentos< / span > < span class = "sub" > — PDFs de facturas< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Setup< / span > < span class = "sub" > — diccionarios, país, compañía< / span > < / p >
< p > < span style = "color:#e2a84b;font-weight:600;font-size:.76em" > Notificaciones< / span > < span class = "sub" > — webhooks< / span > < / p >
< / div >
< div class = "callout" style = "margin-top:1em" >
< p > Sí, gran parte es 1:1 con Dolibarr. Pero el BFF añade lo que el ERP no tiene: < strong > tipos correctos< / strong > , < strong > estados legibles< / strong > , enriquecimiento con nombres de cliente y notificaciones.< / p >
2026-05-14 18:48:01 +00:00
< / div >
< / section >
2026-05-24 11:32:08 +00:00
<!-- TRANSFORMACIÓN -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > De francés y strings... a inglés y tipos< / h2 >
< div class = "cols" style = "align-items:flex-start;margin-top:.2em" >
< div >
< p class = "red" style = "font-size:.76em;font-weight:700;margin-bottom:5px" > Dolibarr< / p >
< pre > < code class = "json" style = "font-size:.62em" > {
"statut": "1",
"total_ttc": "150.50",
"date": "1715673600",
"socid": "3"
}< / code > < / pre >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div >
< p class = "green" style = "font-size:.76em;font-weight:700;margin-bottom:5px" > BFF< / p >
< pre > < code class = "json" style = "font-size:.62em" > {
"status": "unpaid",
"total": 150.50,
"date": "2024-05-14",
"clientId": 3
}< / code > < / pre >
2026-05-14 18:48:01 +00:00
< / div >
< / div >
2026-05-24 11:32:08 +00:00
< p style = "font-size:.78em;margin-top:.8em;color:#ccc" > Mismo dato. Otro mundo.< / p >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- SWAGGER -->
< section class = "center" >
< h2 > Documentación automática< / h2 >
< img src = "swagger.png" class = "img-full" alt = "Swagger UI" style = "max-height:50vh" >
< p class = "sub" style = "margin-top:.5em" > OpenAPI 3.1 generado al arrancar. Cada endpoint documentado.< / p >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- AUTENTICACIÓN -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > La contraseña nunca sale del servidor< / h2 >
< p > 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.< / p >
< div class = "cols" style = "margin-top:.8em" >
< div class = "card" style = "border-top:3px solid #e2a84b" >
< p style = "font-size:.78em;color:#e2a84b;font-weight:700;margin:0 0 4px" > 1 — Login< / p >
< p class = "sub" > El usuario se autentica. El BFF recibe la API key de Dolibarr y la guarda en memoria.< / p >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #4ade80" >
< p style = "font-size:.78em;color:#4ade80;font-weight:700;margin:0 0 4px" > 2 — JWT< / p >
< p class = "sub" > El BFF genera un JWT para el navegador. La API key nunca sale de ahí.< / p >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #fb923c" >
< p style = "font-size:.78em;color:#fb923c;font-weight:700;margin:0 0 4px" > 3 — Cada petición< / p >
< p class = "sub" > El JWT lleva un sessionId. Con ese sessionId se busca la API key en caché y se inyecta al vuelo.< / p >
2026-05-14 18:48:01 +00:00
< / div >
< / div >
2026-05-24 11:32:08 +00:00
< div class = "callout" style = "margin-top:.7em" >
< p > Cada usuario tiene su propia API key aislada. Si se cachease en el HttpClient, todos compartirían la misma.< / p >
2026-05-14 18:48:01 +00:00
< / div >
< / section >
2026-05-24 11:32:08 +00:00
<!-- NOTIFICACIONES -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > Lo que un wrapper puede hacer que el ERP no< / h2 >
< p > 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.< / p >
< div class = "cols" style = "margin-top:.7em" >
< div class = "card" style = "border-top:3px solid #c084fc" >
< p style = "font-size:.78em;color:#c084fc;font-weight:700;margin:0 0 4px" > Teams, Slack, lo que sea< / p >
< p class = "sub" > Configuras una URL y avisa donde quieras. Hoy es Teams, mañana puede ser Telegram, email, lo que haga falta.< / p >
2026-05-14 18:48:01 +00:00
< / div >
2026-05-24 11:32:08 +00:00
< div class = "card" style = "border-top:3px solid #4ade80" >
< p style = "font-size:.78em;color:#4ade80;font-weight:700;margin:0 0 4px" > La interfaz ya está< / p >
< p class = "sub" > Añadir un canal nuevo es implementar una interfaz. El resto del código ni se toca. Así se escala un wrapper de verdad.< / p >
2026-05-14 18:48:01 +00:00
< / div >
< / div >
< / section >
2026-05-24 11:32:08 +00:00
<!-- DESPLIEGUE -->
2026-05-14 18:48:01 +00:00
< section >
2026-05-24 11:32:08 +00:00
< h2 > Un comando y funciona< / h2 >
< p class = "sub" style = "margin-bottom:.3em" > docker compose up< / p >
< pre > < code class = "yaml" style = "font-size:.64em" > services:
2026-05-14 18:48:01 +00:00
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: dolibarr
volumes: [mysql_data:/var/lib/mysql]
dolibarr:
image: dolibarr/dolibarr:latest
depends_on: [mysql]
ports: ["80:80"]
2026-05-24 11:32:08 +00:00
bff:
2026-05-14 18:48:01 +00:00
build: ./DoliMiddlewareApi
depends_on: [dolibarr]
2026-05-24 11:32:08 +00:00
ports: ["5001:8080"]< / code > < / pre >
< p style = "font-size:.78em;margin-top:.7em;color:#bbb" > MySQL, Dolibarr, el BFF. Tres contenedores, un comando.< / p >
2026-05-14 18:48:01 +00:00
< / section >
2026-05-24 11:32:08 +00:00
<!-- CIERRE -->
< section class = "center" >
< h1 style = "font-size:2.2em" > Preguntas< / h1 >
2026-05-14 18:48:01 +00:00
< / section >
< / div > < / div >
< script src = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.js" > < / script >
< script src = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/plugin/highlight/highlight.js" > < / script >
< script >
Reveal.initialize({
hash: true,
2026-05-24 11:32:08 +00:00
slideNumber: 'c/t',
transition: 'fade',
transitionSpeed: 'fast',
plugins: [RevealHighlight],
width: 1100,
height: 680,
2026-05-14 18:48:01 +00:00
margin: 0.04,
2026-05-24 11:32:08 +00:00
center: false,
2026-05-14 18:48:01 +00:00
});
< / script >
< / body >
< / html >