# 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: 1. Gestionar productos, propiedades y stock del ERP para integrarlos en PrestaShop. 2. 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 `@Scheduled` de 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: header `DOLAPIKEY: `. Formato: JSON. - **PrestaShop**: Webservice nativo. Base: `https://prestashop.loading.net/tienda/api`. Auth: `ws_key` como **query param** (NO Basic Auth — nginx del hosting elimina el header `Authorization`). 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. **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 1. **`pushProductsToPrestashop`** — lee productos Dolibarr modificados desde `lastSyncedAt`, crea/actualiza en PrestaShop por SKU, actualiza `ProductMapping`. 2. **`pushStockToPrestashop`** — lee stock actual en Dolibarr de productos mapeados, empuja a `stock_availables` de PrestaShop. 3. **`pullOrdersFromPrestashop`** — consulta pedidos PrestaShop desde `lastSyncedAt` con `filter[date_add]=...`, crea/localiza cliente en Dolibarr, crea `commande`, opcionalmente factura, guarda `OrderMapping`. 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) y `application-prod.yml` (MySQL) — ambos gitignoreados, `.example` en repo - `@ConfigurationProperties` con `@Validated` para todas las properties sensibles - SpringDoc OpenAPI en `pom.xml` - ✅ **Fase 1 — Clientes HTTP tipados** - `DolibarrClient`: `RestClient` + header `DOLAPIKEY` global. Métodos: getProducts, getProductByRef, createProduct, updateProduct, updateStock, getOrCreateThirdparty, createOrder, createInvoiceFromOrder - `PrestashopClient`: `RestClient` + `ws_key` como 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`, `PrestashopApiException` con body logueado - ✅ **Fase 2 — Modelo y persistencia** - Entidades JPA: `ProductMapping`, `OrderMapping`, `SyncLog` - Repositorios Spring Data JPA - Flyway: `V1__init.sql` con tablas product_mapping, order_mapping, sync_log - ✅ **Fase 3 — Servicios de sincronización** - `ProductSyncService`, `StockSyncService`, `OrderSyncService` - Cada uno devuelve `SyncResult(processed, failed)` - `try/finally` garantiza que `SyncLog` se escribe siempre - NO `@Transactional` en batch: cada `save()` auto-commit independiente - ✅ **Fase 4 — Scheduling** - `SyncScheduler` con `@Scheduled(fixedDelayString=...)` (delay post-finalización, no entre inicios) - `@ConditionalOnProperty(sync.scheduling.enabled)` — desactivado en dev - `ThreadPoolTaskScheduler(poolSize=1)` — ejecución secuencial, sin solapamiento - ✅ **Fase 5 — API REST propia** - `SyncController`: POST `/api/sync/{products,stock,orders}` — sync manual - `MappingController`: GET `/api/mappings/products?status=`, `/api/mappings/orders` - `SyncLogController`: 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) - 🔲 **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 1. Crear un producto en Dolibarr (`https://prestashop.loading.net/dolibarr`) con referencia/SKU única (ej: `PROD-001`), nombre, precio y stock. 2. En Swagger: click "Authorize" → `admin` / `admin123`. 3. `POST /api/sync/products` → "Try it out" → "Execute". 4. Verificar respuesta: `{"processed": 1, "failed": 0}`. 5. Comprobar que el producto aparece en PrestaShop (`https://prestashop.loading.net/tienda`). 6. `GET /api/mappings/products` → debe listar el producto con `syncStatus: SYNCED`. ### Flujo 2 — Stock Dolibarr → PrestaShop 1. Modificar el stock del producto en Dolibarr. 2. `POST /api/sync/stock` → "Try it out" → "Execute". 3. Verificar que el stock actualizado aparece en PrestaShop. ### Flujo 3 — Pedidos PrestaShop → Dolibarr 1. Crear un pedido en PrestaShop comprando el producto. 2. `POST /api/sync/orders` → "Try it out" → "Execute". 3. Verificar respuesta: `{"processed": 1, "failed": 0}`. 4. Comprobar que el pedido aparece en Dolibarr como `commande`. 5. `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` → campo `errorDetails` del 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= SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect SPRING_JPA_HIBERNATE_DDL_AUTO=validate SYNC_SCHEDULING_ENABLED=true DOLIBARR_API_KEY= PRESTASHOP_API_KEY= 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=true` en la URL JDBC (usa `caching_sha2_password`) - `mysql.railway.internal:3306` es 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 `@Autowired` en campos. - Lombok: `@Getter`, `@Setter`, `@RequiredArgsConstructor`, `@Builder` cuando aporten. **Nunca `@Data` en entidades JPA** (rompe `equals/hashCode` con relaciones bidireccionales). - Nombres: `PascalCase` clases, 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) - **`fixedDelay` no `fixedRate`**: 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 `@Transactional` en 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/finally` en services**: `SyncLog` siempre 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.