sincronización de producto ha empezado a fallar
This commit is contained in:
parent
abe534f4da
commit
bc858122af
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.
|
||||
|
|
@ -76,7 +76,6 @@ public class PrestashopClient {
|
|||
.queryParam("output_format", "JSON")
|
||||
.queryParam("display", "full")
|
||||
.queryParam("filter[reference]", exactRef)
|
||||
.queryParam("filter[active]", "[1]")
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(PrestashopProductDto.ListResponse.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue