This commit is contained in:
parent
abe534f4da
commit
86c12556e5
226
CLAUDE (1).md
226
CLAUDE (1).md
|
|
@ -1,226 +0,0 @@
|
||||||
# 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: <clave>`. 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=<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=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.
|
|
||||||
Loading…
Reference in New Issue