ProyectoIntermodular/CLAUDE (1).md

227 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.