12 KiB
TFG — Integración Dolibarr-PrestaShop
Quién soy
Estudiante DAM (Desarrollo de Aplicaciones Multiplataforma, FP Grado Superior). Este es mi TFG, evaluado por tribunal. Criterios: calidad de código, arquitectura limpia, justificación de decisiones técnicas, documentación, patrones correctos, sistema funcional end-to-end. Tengo que defenderlo oralmente — todo debe ser entendido y argumentable por mí.
Objetivo
Integrar Dolibarr (ERP) con PrestaShop (e-commerce), ambos en hosting compartido bajo httpdocs.
Requisitos del enunciado:
- Gestionar productos, propiedades y stock del ERP para integrarlos en PrestaShop.
- Registrar en el ERP cualquier venta realizada a través de PrestaShop.
Arquitectura (tres capas)
Capa 1 — Apps cliente (se hará después, no tocar ahora):
- JavaFX (escritorio)
- Kotlin Android (móvil)
- Panel web ligero (opcional)
Capa 2 — sync-service (esto es lo que construimos):
- Spring Boot 3 + Java 21 + Maven
- API REST propia que consumen las apps cliente
- Tareas
@Scheduledde sincronización - Persistencia local: mapeo de IDs y logs de sincronización
Capa 3 — Sistemas externos:
- Dolibarr: API REST nativa. Base:
https://prestashop.loading.net/dolibarr/api/index.php. Auth: headerDOLAPIKEY: <clave>. Formato: JSON. - PrestaShop: Webservice nativo. Base:
https://prestashop.loading.net/tienda/api. Auth:ws_keycomo query param (NO Basic Auth — nginx del hosting elimina el headerAuthorization). Formato: JSON con&output_format=JSON.- Bug PS 8.2.5: endpoint raíz
/api/devuelve 500. Usar siempre recursos específicos:/api/products,/api/orders, etc.
- Bug PS 8.2.5: endpoint raíz
Identificador común: SKU/referencia del producto — ref en Dolibarr, reference en PrestaShop. Nunca los IDs internos.
Stack tecnológico
| Capa | Elección |
|---|---|
| Lenguaje/Framework | Java 21 + Spring Boot 3 + Maven |
| Persistencia | Spring Data JPA. H2 en dev, MySQL/MariaDB en prod |
| Cliente HTTP | RestClient de Spring (no RestTemplate, no WebClient) |
| Serialización | Jackson (incluido en Spring Web) |
| Validación | Jakarta Validation |
| Logging | SLF4J + Logback (default Spring Boot) |
| Testing | JUnit 5 + Mockito + Spring Boot Test + WireMock |
| Docs API | SpringDoc OpenAPI (Swagger UI) |
| Scheduling | @Scheduled nativo. ShedLock si hace falta lock distribuido |
Estado actual
Fases 0–5 completadas. sync-service funcional end-to-end.
- Compila y arranca con
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev. - Código en Gitea:
https://projectes.ieslamar.org/mireya.2526/ProyectoIntermodular.git - Espejo en GitHub:
https://github.com/luklpz/ProyectoIntermodular.git(para Railway) - Despliegue: Railway.app (Loading.es hosting compartido no puede ejecutar procesos Java persistentes)
Pendiente:
- Fase 6 — Apps cliente (proyecto separado)
Modelo de dominio (entidades JPA locales)
ProductMapping:id,sku(único),dolibarrId,prestashopId,lastSyncedAt,syncStatus(enum:PENDING/SYNCED/ERROR),errorMessage.OrderMapping:id,prestashopOrderId,dolibarrOrderId,dolibarrInvoiceId,importedAt,status(enum).SyncLog:id,syncType(enum:PRODUCT_PUSH/STOCK_PUSH/ORDER_PULL),startedAt,finishedAt,itemsProcessed,itemsFailed,errorDetails(TEXT).
Flujos de sincronización
pushProductsToPrestashop— lee productos Dolibarr modificados desdelastSyncedAt, crea/actualiza en PrestaShop por SKU, actualizaProductMapping.pushStockToPrestashop— lee stock actual en Dolibarr de productos mapeados, empuja astock_availablesde PrestaShop.pullOrdersFromPrestashop— consulta pedidos PrestaShop desdelastSyncedAtconfilter[date_add]=..., crea/localiza cliente en Dolibarr, creacommande, opcionalmente factura, guardaOrderMapping.
Cada flujo escribe una entrada en SyncLog (éxito o fallo).
Roadmap por fases
-
✅ Fase 0 — Setup base
- Estructura de paquetes:
integration.dolibarr,integration.prestashop,sync,mapping,api,config - Perfiles
application-dev.yml(H2 MODE=MySQL) yapplication-prod.yml(MySQL) — ambos gitignoreados,.exampleen repo @ConfigurationPropertiescon@Validatedpara todas las properties sensibles- SpringDoc OpenAPI en
pom.xml
- Estructura de paquetes:
-
✅ Fase 1 — Clientes HTTP tipados
DolibarrClient:RestClient+ headerDOLAPIKEYglobal. Métodos: getProducts, getProductByRef, createProduct, updateProduct, updateStock, getOrCreateThirdparty, createOrder, createInvoiceFromOrderPrestashopClient:RestClient+ws_keycomo query param en cada petición. Métodos: getProducts, getProductByReference, createProduct, updateProduct, getStockAvailableForProduct, updateStockAvailable, getOrdersSince, getCustomer- DTOs separados por API con
@JsonIgnoreProperties(ignoreUnknown=true) DolibarrApiException,PrestashopApiExceptioncon body logueado
-
✅ Fase 2 — Modelo y persistencia
- Entidades JPA:
ProductMapping,OrderMapping,SyncLog - Repositorios Spring Data JPA
- Flyway:
V1__init.sqlcon tablas product_mapping, order_mapping, sync_log
- Entidades JPA:
-
✅ Fase 3 — Servicios de sincronización
ProductSyncService,StockSyncService,OrderSyncService- Cada uno devuelve
SyncResult(processed, failed) try/finallygarantiza queSyncLogse escribe siempre- NO
@Transactionalen batch: cadasave()auto-commit independiente
-
✅ Fase 4 — Scheduling
SyncSchedulercon@Scheduled(fixedDelayString=...)(delay post-finalización, no entre inicios)@ConditionalOnProperty(sync.scheduling.enabled)— desactivado en devThreadPoolTaskScheduler(poolSize=1)— ejecución secuencial, sin solapamiento
-
✅ Fase 5 — API REST propia
SyncController: POST/api/sync/{products,stock,orders}— sync manualMappingController: GET/api/mappings/products?status=,/api/mappings/ordersSyncLogController: GET/api/logs?type=,/api/logs/{id}- Spring Security: Basic Auth,
DelegatingPasswordEncoder({noop}dev /{bcrypt}prod), STATELESS, CSRF off - Swagger UI:
/swagger-ui.html
-
✅ Despliegue en Railway ← completado
- GitHub mirror:
https://github.com/luklpz/ProyectoIntermodular.git - URL pública:
https://proyectointermodular-production-a9c3.up.railway.app - Swagger UI:
https://proyectointermodular-production-a9c3.up.railway.app/swagger-ui.html - Health:
https://proyectointermodular-production-a9c3.up.railway.app/actuator/health - Root directory del servicio Railway:
sync-service - MySQL plugin de Railway — red privada (
mysql.railway.internal:3306) - Todas las credenciales en variables del servicio ProyectoIntermodular (ver sección Despliegue)
- GitHub mirror:
-
🔲 Verificación end-to-end con datos reales ← HACER ANTES DE FASE 6
- Ver sección "Verificación end-to-end" más abajo
-
Fase 6 — Apps cliente — proyecto separado, avisaré cuando toque
Verificación end-to-end con datos reales
Antes de Fase 6 hay que probar que los tres flujos funcionan con datos reales en Dolibarr y PrestaShop.
Swagger UI: https://proyectointermodular-production-a9c3.up.railway.app/swagger-ui.html
Credenciales: usuario admin, contraseña admin123
Flujo 1 — Productos Dolibarr → PrestaShop
- Crear un producto en Dolibarr (
https://prestashop.loading.net/dolibarr) con referencia/SKU única (ej:PROD-001), nombre, precio y stock. - En Swagger: click "Authorize" →
admin/admin123. POST /api/sync/products→ "Try it out" → "Execute".- Verificar respuesta:
{"processed": 1, "failed": 0}. - Comprobar que el producto aparece en PrestaShop (
https://prestashop.loading.net/tienda). GET /api/mappings/products→ debe listar el producto consyncStatus: SYNCED.
Flujo 2 — Stock Dolibarr → PrestaShop
- Modificar el stock del producto en Dolibarr.
POST /api/sync/stock→ "Try it out" → "Execute".- Verificar que el stock actualizado aparece en PrestaShop.
Flujo 3 — Pedidos PrestaShop → Dolibarr
- Crear un pedido en PrestaShop comprando el producto.
POST /api/sync/orders→ "Try it out" → "Execute".- Verificar respuesta:
{"processed": 1, "failed": 0}. - Comprobar que el pedido aparece en Dolibarr como
commande. GET /api/mappings/orders→ debe listar el pedido mapeado.
Ver logs de cualquier sync
GET /api/logs → lista todas las ejecuciones con duración, items procesados y errores.
GET /api/logs/{id} → detalle de una ejecución concreta.
Si algo falla
GET /api/logs→ campoerrorDetailsdel log tendrá el mensaje de error.- Los logs del servidor están en Railway → ProyectoIntermodular → Deploy Logs.
Despliegue (Railway)
Variables en el servicio ProyectoIntermodular (literales, NO Shared Variables):
SPRING_PROFILES_ACTIVE=prod
SPRING_DATASOURCE_URL=jdbc:mysql://mysql.railway.internal:3306/railway?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=<ver MySQL service → MYSQLPASSWORD>
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect
SPRING_JPA_HIBERNATE_DDL_AUTO=validate
SYNC_SCHEDULING_ENABLED=true
DOLIBARR_API_KEY=<ver application-dev.yml local>
PRESTASHOP_API_KEY=<ver application-dev.yml local>
API_SECURITY_PASSWORD={noop}admin123
Lecciones aprendidas Railway:
- Las Shared Variables NO inyectan automáticamente en servicios — poner todo en variables del servicio directamente
${{MySQL.MYSQLPASSWORD}}solo resuelve en variables propias del servicio, no en Shared Variables- MySQL 8 requiere
allowPublicKeyRetrieval=trueen la URL JDBC (usacaching_sha2_password) mysql.railway.internal:3306es la red privada interna — funciona sin configuración adicional
Flyway ejecuta V1__init.sql en primer arranque y crea las tablas automáticamente.
Convenciones de código
- Constructor injection con
@RequiredArgsConstructor(Lombok). Sin@Autowireden campos. - Lombok:
@Getter,@Setter,@RequiredArgsConstructor,@Buildercuando aporten. Nunca@Dataen entidades JPA (rompeequals/hashCodecon relaciones bidireccionales). - Nombres:
PascalCaseclases, minúsculas paquetes. - JavaDoc en clases públicas y métodos no triviales (para la memoria del TFG).
- Commits pequeños y atómicos, mensajes en imperativo en inglés.
- Tests: unitarios con Mockito para servicios; integración mínima con
@SpringBootTest+ WireMock.
Lo que NO quiero
- Microservicios, Kafka, Redis, Docker Compose con cinco contenedores.
- Dependencias sin valor real.
- CQRS, event sourcing, hexagonal estricto, DDD táctico complejo. Arquitectura en capas limpia es suficiente.
- Mezclar lógica Dolibarr y PrestaShop en la misma clase.
- Strings mágicos: todo a constantes o properties.
catch (Exception e)genérico.- Generar 15 archivos de golpe sin explicar qué hace cada uno.
Cómo trabajamos
- Discutir diseño antes de generar código grande.
- Justificar decisiones técnicas (las tengo que defender ante tribunal).
- Avisar de trade-offs explícitamente.
- Una fase del roadmap a la vez. No saltar fases.
- README.md actualizado: cómo arrancar, perfiles, variables de entorno, endpoints.
Decisiones técnicas clave (para defensa oral)
fixedDelaynofixedRate: espera a que termine antes de contar el siguiente intervalo. Evita solapamiento si una sync tarda más de lo esperado.ThreadPoolTaskScheduler(poolSize=1): jobs ejecutan secuencial, imposible ejecutar dos a la vez aunque lleguen a la vez.- No
@Transactionalen batch: fallo en item 3 no hace rollback de items 1-2 ya guardados. Procesamiento parcial es mejor que todo-o-nada para sync. try/finallyen services:SyncLogsiempre se escribe aunque explote algo inesperado.DelegatingPasswordEncoder: soporta{noop}en dev y{bcrypt}en prod con el mismo campo de configuración.- Flyway sobre Liquibase: menos verboso, SQL puro, suficiente para este proyecto.
- H2 con
MODE=MySQL: mismas migraciones Flyway funcionan en dev y prod sin cambios.