decathlon-create-db
1. Descripción general
Según el pom.xml, el proyecto se describe como "Order Decathlon create DB". Es un microservicio batch (runner) que se ejecuta periódicamente para:
- Importar nuevos pedidos del marketplace Decathlon en sus dos plataformas Mirakl: EU (
decathlon.hawkerscoen Mirakl EU) y AU (Mirakl AU), transformándolos al modelo interno de logística (Order,OrderLine,Shipment,Customer...). - Detectar pedidos cancelados en Decathlon y reflejar el cambio en la base de datos interna (solo en EU) más una notificación a Slack (en EU y AU).
- Subir facturas (PDF + metadatos JSON) generadas por Axazure y depositadas en un servidor SFTP hacia la API de Facturas de Decathlon (solo EU).
Forma parte de la familia de runners de creación/actualización de pedidos de marketplace del ecosistema Hawkers (mismo patrón que coppel-create-db, dafiti-create-db, bradery-create-db), reutilizando la librería compartida logistics-commons como capa de persistencia.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | decathlon-create-db |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 |
| Spring Boot | 4 (heredado del parent) |
| Tipo de artefacto | jar (ejecutable, Spring Boot batch/CLI) |
| Módulos | No aplica (proyecto de módulo único) |
3. Arquitectura y diseño
No es una API REST: es una aplicación Spring Boot CLI compuesta por 5 CommandLineRunner que implementan Ordered y se ejecutan de forma secuencial al arrancar el proceso. Al finalizar el último runner, la aplicación se cierra.
Paquetes principales:
com.hawkersco.decathloncreatedb— clase principal (DecathlonCreateDbApplication) con@EnableJpaRepositories("com.hawkersco.logisticscommons.repository")..runner— los 5CommandLineRunner..config—DecathlonCreateDbConfig(declaración manual de beans delogistics-commons),DecathlonOrderProperties,DecathlonCreateDbProperties..utils—DecathlonCreateUtils, componente compartido de transformación de datos..model—InvoiceAxazureInfo(record para metadatos de factura leídos por SFTP).
flowchart TD
A["1. DecathlonCreateDbRunner<br/>Import EU (source 42)"] --> B["2. DecathlonCreateDbCheckRunner<br/>Cancelaciones EU"]
B --> C["3. DecathlonInvoicesRunner<br/>Facturas EU vía SFTP + API"]
C --> D["4. DecathlonAuCreateDbRunner<br/>Import AU (source 78)"]
D --> E["5. DecathlonAuCreateDbCheckRunner<br/>Cancelaciones AU + Slack + Exit JVM"]
A -.->|usa| U[DecathlonCreateUtils]
D -.->|usa| U
U -.->|persiste vía| LC[(logistics-commons<br/>services + JPA)]
Los servicios de logistics-commons (CustomerService, OrderService, ShipmentService...) no están anotados con @Component en la librería externa, por lo que DecathlonCreateDbConfig los declara manualmente como @Bean, junto con el PersistenceManagedTypes necesario para que Spring Data JPA escanee las entidades del paquete com.hawkersco.logisticscommons.dao.
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
com.hawkersco:logistics-commons:1.0.25-SNAPSHOT | Entidades JPA y servicios de logística compartidos (Order, Customer, Shipment...) |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | Utilidades comunes (DateUtils, SFTP, ZIP...) |
com.hawkersco:decathlon-client:1.0.25-SNAPSHOT | Cliente @HttpExchange para Mirakl Decathlon (EU/AU) y API de Facturas |
com.hawkersco:slack-client:1.0.25-SNAPSHOT | Notificaciones a Slack |
lombok | Generación de getters/setters/builders (sin versión explícita en el pom.xml, heredada del BOM) |
spring-boot-starter-test (test) | JUnit 5 + Spring Test |
Driver de base de datos: PostgreSQL (org.postgresql.Driver, declarado en application.properties, no hay dependencia explícita adicional en el pom.xml más allá de la transitiva).
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
| Mirakl Decathlon EU | HTTP (@HttpExchange vía DecathlonClient) | Entrante (lectura de pedidos/cancelaciones) | Fuente EU, source ID = 42 |
| Mirakl Decathlon AU | HTTP (@HttpExchange vía DecathlonAuClient) | Entrante | Fuente AU, source ID = 78 |
| Decathlon Invoices API | HTTP (@HttpExchange) | Saliente | Envío de facturas procesadas |
Servidor SFTP (sftp.hawkersco.com) | SFTP | Entrante/Saliente | Descarga de .json+.pdf desde PENDING/, mueve a PROCESSED/ tras subir la factura |
| Slack | HTTP (SlackClient) | Saliente | Alertas de cancelación y errores operativos |
PostgreSQL (logistics) | JDBC | Entrante/Saliente | Persistencia de pedidos vía logistics-commons |
7. Configuración
Todas las claves sensibles se muestran enmascaradas. En producción (application-pro.properties) los valores llegan por variables de entorno inyectadas como Secret de Kubernetes; en local (application.properties) el repositorio contiene actualmente valores reales hardcodeados (ver alerta de seguridad más abajo).
| Clave | Descripción | Ejemplo (producción) |
|---|---|---|
spring.datasource.url | URL JDBC de la BD logistics | ${dbLogisitcsUrl} |
spring.datasource.username | Usuario de BD | ${dbLogisitcsUsername} |
spring.datasource.password | Contraseña de BD | ${dbLogisitcsPassword} |
decathloncreatedb.order.premiumcountrylist | Países ISO-2 con envío premium | CH,HR,VA,MC,MA,LI,SM,GB,CZ,PL,HU,HR,BG,RO,DK,SE |
decathlon.credentials.url | URL base Mirakl EU | ${decathlonCredUrl} |
decathlon.credentials.key | API key Mirakl EU | ${decathlonCredKey} |
decathlon-au.credentials.url | URL base Mirakl AU | ${decathlonAuCredUrl} |
decathlon-au.credentials.key | API key Mirakl AU | ${decathlonAuCredKey} |
decathlon.order.group1 | Países ruta grupo 1 | BE,FR,IT,DE |
decathlon.order.group2 | Países ruta grupo 2 | AT,FI,GR,LU |
decathlon.order.group3 | Países ruta grupo 3 | EE,LT,LV,SI,SK |
slack.client.url | URL API Slack | ${slackClientUrl} |
slack.auth.token | Token bot de Slack | ${slackAuthToken} |
slack.channel.id | Canal de notificaciones | ${slackChannelId} |
erp.invoices.ftp.server | Host SFTP de facturas | ${erpInvoicesFtpServer} |
erp.invoices.ftp.port | Puerto SFTP | ${erpInvoicesFtpPort} |
erp.invoices.ftp.user | Usuario SFTP | ${erpInvoicesFtpUser} |
erp.invoices.ftp.password | Contraseña SFTP | ${erpInvoicesFtpPassword} |
erp.invoices.ftp.path | Ruta base en el SFTP | /src/INVOICES/ |
⚠️ Alerta de seguridad
El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano, entre ellas: contraseña de la base de datos PostgreSQL, API keys de Mirakl Decathlon EU y AU, token de bot de Slack (xoxb-...) y contraseña del servidor SFTP de facturas. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar todas las credenciales listadas en cuanto sea posible.
- Sustituir los valores hardcodeados de
application.propertiespor variables de entorno con valores de un entorno de desarrollo aislado (nunca credenciales de producción), siguiendo el patrón ya usado enapplication-pro.properties. - Revisar el historial de control de versiones del repositorio, ya que estas credenciales pueden seguir expuestas en commits anteriores aunque se corrijan ahora.
8. Persistencia
Base de datos PostgreSQL (logistics), acceso vía JPA a través de la librería logistics-commons (@EnableJpaRepositories("com.hawkersco.logisticscommons.repository")). spring.jpa.hibernate.ddl-auto=none: no hay generación ni migración automática del esquema desde este proyecto. Entidades relevantes usadas: Order, OrderLine, Customer, CustomerSource, Shipment, ShipmentLine, Source. No hay Flyway/Liquibase en este repositorio.
9. Procesos programados y mensajería
No hay @Scheduled ni listeners de colas: la periodicidad la impone el CronJob de Kubernetes (k8s/cronjob.yaml), que ejecuta el contenedor cada hora (schedule: "0 * * * *", concurrencyPolicy: Forbid). Al arrancar, Spring Boot ejecuta en orden los 5 runners:
| Orden | Runner | Función |
|---|---|---|
| 1 | DecathlonCreateDbRunner | Importa pedidos nuevos EU (source 42, prefijo MKDE) |
| 2 | DecathlonCreateDbCheckRunner | Detecta pedidos CANCELLED en EU, actualiza estado en BD y notifica a Slack |
| 3 | DecathlonInvoicesRunner | Descarga facturas pendientes del SFTP y las sube a la API de Facturas de Decathlon (solo EU) |
| 4 | DecathlonAuCreateDbRunner | Importa pedidos nuevos AU (source 78, prefijo MKDEAU) |
| 5 | DecathlonAuCreateDbCheckRunner | Detecta pedidos cancelados en AU, notifica a Slack y cierra la JVM (System.exit(SpringApplication.exit(context))) |
Detalles relevantes:
- Deduplicación: cada runner de importación comprueba
orderService.findBySourceAndCdOrderExternal(sourceId, orderId)antes de insertar. - Asignación de método de envío / tipo de servicio (
DecathlonCreateUtils.addShippingMethodServiceType): se decide por elchannelCodedel pedido frente agroup1/group2/group3, luego por la lista de países premium, y como último recurso por unswitchsobre el país ISO-2 de destino (ES/PT→23,AU/NZ→7,CO→8,MX→9,US→10, resto→20). DecathlonInvoicesRunner: pagina las solicitudes de documentos pendientes víadecathlonClient.getDocumentRequest(...)/getDocumentRequestNext(...), descarga por SFTP el.json(metadatos, deserializado aInvoiceAxazureInfo) y el.pdfdePENDING/; si solo encuentra uno de los dos ficheros, omite el pedido en esa pasada; si los ficheros ya estaban enPROCESSED/, los re-encola moviéndolos de vuelta aPENDING/. Reconecta la sesión SFTP cada 10 pedidos procesados. El importe de impuestos (totalTaxAmount) se envía siempre comoBigDecimal.ZEROa la API de Decathlon, independientemente del valor real de la factura.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a un PostgreSQL con el esquema logistics-commons ya migrado, y credenciales válidas de Mirakl Decathlon (EU/AU), Slack y SFTP en un application.properties local.
# Compilar sin tests (igual que en CI)
./mvnw -B -DskipTests clean install
# Ejecutar tests
./mvnw test
# Ejecutar la aplicación localmente
./mvnw spring-boot:run
Al ser un CommandLineRunner, no expone Actuator/health: la forma de verificar la ejecución es revisar el log de consola (procesa pedidos y termina) o consultar directamente la tabla orders en la base de datos tras la ejecución.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/decathlon-create-db:<tag>. - Orquestación: Kubernetes
CronJob(k8s/cronjob.yaml) en el clúster GKEpi-cluster-hw(zonaeurope-west3-a, proyectopi-saldum), namespacepi, ejecutándose cada hora. - CI/CD (Jenkins): pipeline con 3 etapas —
Checkout→Build & Push(sustituyeapplication-pro.propertiesporapplication.propertiesantes demvn clean package jib:build) →Deploy to GKE(borra elCronJobexistente con--ignore-not-foundy aplica el manifiesto templado víased). - Todas las variables sensibles se inyectan en el pod mediante un
Secretde Kubernetes llamado igual que la app (decathlon-create-db), referenciado en cadaenv.valueFrom.secretKeyRefdelcronjob.yaml.
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/decathlon-create-db/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). Los errores relevantes se gestionan puntualmente:
DecathlonCreateUtils.saveShipmentcapturaParseExceptiony notifica a Slack en vez de propagar la excepción.DecathlonInvoicesRunnerinspecciona el mensaje de la excepción de red buscando las cadenas"channel is not opened."o"Connection reset"para disparar una reconexión SFTP.- El resto de errores se registran mediante el logging estándar de Spring Boot (consola), sin un formato de log estructurado propio ni integración con un sistema centralizado de logs documentado en el repositorio.
13. Notas y consideraciones
- Asimetría EU vs. AU en cancelaciones:
DecathlonCreateDbCheckRunner(EU) actualiza el estado aCANCELLEDen base de datos y notifica a Slack.DecathlonAuCreateDbCheckRunner(AU) solo notifica a Slack, sin invocarorderService.updateProcessingStatusOrder(...)— los pedidos AU cancelados nunca cambian su estado en la base de datos interna. Si este comportamiento es involuntario, es un bug con impacto directo en la consistencia del estado de pedidos AU. - Asimetría de estilo de bucle:
DecathlonCreateDbRunner(EU) pagina con undo-while(hasOrders(response)), mientras queDecathlonAuCreateDbRunner(AU) usa un bucle infinitofor (;;) { ... return; ... }. Ambos son funcionalmente equivalentes pero estilísticamente inconsistentes; unificar el patrón facilitaría el mantenimiento. - Inconsistencia en la configuración de SFTP:
DecathlonInvoicesRunnerinyecta los parámetros de conexión SFTP con@Valueindividuales (erp.invoices.ftp.*) en lugar de agruparlos en una clase@ConfigurationPropertiesdedicada, a diferencia del patrón usado en proyectos hermanos comodafiti-create-db(que sí usa un recordFtpProperties). - Impuestos de factura siempre a cero:
DecathlonInvoicesRunner.uploadInvoicefijarequest.setTotalTaxAmount(BigDecimal.ZERO)de forma incondicional al subir la factura a Decathlon, sin usar eltaxAmountreal disponible enInvoiceAxazureInfo. Conviene confirmar si es intencional o un defecto pendiente de corregir. - Duplicado en la lista de países premium: la propiedad
decathloncreatedb.order.premiumcountrylistincluyeHRdos veces (CH,HR,VA,MC,MA,LI,SM,GB,CZ,PL,HU,HR,BG,RO,DK,SE), tanto enapplication.propertiescomo enapplication-pro.properties. No tiene efecto funcional (se usa comocontains), pero indica una copia manual sin depurar. CLAUDE.mdverificado: la descripción de la arquitectura, el orden de los runners, los IDs de fuente (EU=42, AU=78) y las clases de configuración coinciden con el código actual; no se han encontrado discrepancias relevantes en este proyecto.- Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en
application.properties.