Skip to main content

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.hawkersco en 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

CampoValor
artifactIddecathlon-create-db
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4 (heredado del parent)
Tipo de artefactojar (ejecutable, Spring Boot batch/CLI)
MódulosNo 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 5 CommandLineRunner.
  • .configDecathlonCreateDbConfig (declaración manual de beans de logistics-commons), DecathlonOrderProperties, DecathlonCreateDbProperties.
  • .utilsDecathlonCreateUtils, componente compartido de transformación de datos.
  • .modelInvoiceAxazureInfo (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

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.hawkersco:logistics-commons:1.0.25-SNAPSHOTEntidades JPA y servicios de logística compartidos (Order, Customer, Shipment...)
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTUtilidades comunes (DateUtils, SFTP, ZIP...)
com.hawkersco:decathlon-client:1.0.25-SNAPSHOTCliente @HttpExchange para Mirakl Decathlon (EU/AU) y API de Facturas
com.hawkersco:slack-client:1.0.25-SNAPSHOTNotificaciones a Slack
lombokGeneració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

SistemaProtocoloDirecciónDetalle
Mirakl Decathlon EUHTTP (@HttpExchange vía DecathlonClient)Entrante (lectura de pedidos/cancelaciones)Fuente EU, source ID = 42
Mirakl Decathlon AUHTTP (@HttpExchange vía DecathlonAuClient)EntranteFuente AU, source ID = 78
Decathlon Invoices APIHTTP (@HttpExchange)SalienteEnvío de facturas procesadas
Servidor SFTP (sftp.hawkersco.com)SFTPEntrante/SalienteDescarga de .json+.pdf desde PENDING/, mueve a PROCESSED/ tras subir la factura
SlackHTTP (SlackClient)SalienteAlertas de cancelación y errores operativos
PostgreSQL (logistics)JDBCEntrante/SalientePersistencia 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).

ClaveDescripciónEjemplo (producción)
spring.datasource.urlURL JDBC de la BD logistics${dbLogisitcsUrl}
spring.datasource.usernameUsuario de BD${dbLogisitcsUsername}
spring.datasource.passwordContraseña de BD${dbLogisitcsPassword}
decathloncreatedb.order.premiumcountrylistPaíses ISO-2 con envío premiumCH,HR,VA,MC,MA,LI,SM,GB,CZ,PL,HU,HR,BG,RO,DK,SE
decathlon.credentials.urlURL base Mirakl EU${decathlonCredUrl}
decathlon.credentials.keyAPI key Mirakl EU${decathlonCredKey}
decathlon-au.credentials.urlURL base Mirakl AU${decathlonAuCredUrl}
decathlon-au.credentials.keyAPI key Mirakl AU${decathlonAuCredKey}
decathlon.order.group1Países ruta grupo 1BE,FR,IT,DE
decathlon.order.group2Países ruta grupo 2AT,FI,GR,LU
decathlon.order.group3Países ruta grupo 3EE,LT,LV,SI,SK
slack.client.urlURL API Slack${slackClientUrl}
slack.auth.tokenToken bot de Slack${slackAuthToken}
slack.channel.idCanal de notificaciones${slackChannelId}
erp.invoices.ftp.serverHost SFTP de facturas${erpInvoicesFtpServer}
erp.invoices.ftp.portPuerto SFTP${erpInvoicesFtpPort}
erp.invoices.ftp.userUsuario SFTP${erpInvoicesFtpUser}
erp.invoices.ftp.passwordContraseña SFTP${erpInvoicesFtpPassword}
erp.invoices.ftp.pathRuta 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:

  1. Rotar todas las credenciales listadas en cuanto sea posible.
  2. Sustituir los valores hardcodeados de application.properties por variables de entorno con valores de un entorno de desarrollo aislado (nunca credenciales de producción), siguiendo el patrón ya usado en application-pro.properties.
  3. 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:

OrdenRunnerFunción
1DecathlonCreateDbRunnerImporta pedidos nuevos EU (source 42, prefijo MKDE)
2DecathlonCreateDbCheckRunnerDetecta pedidos CANCELLED en EU, actualiza estado en BD y notifica a Slack
3DecathlonInvoicesRunnerDescarga facturas pendientes del SFTP y las sube a la API de Facturas de Decathlon (solo EU)
4DecathlonAuCreateDbRunnerImporta pedidos nuevos AU (source 78, prefijo MKDEAU)
5DecathlonAuCreateDbCheckRunnerDetecta 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 el channelCode del pedido frente a group1/group2/group3, luego por la lista de países premium, y como último recurso por un switch sobre 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ía decathlonClient.getDocumentRequest(...)/getDocumentRequestNext(...), descarga por SFTP el .json (metadatos, deserializado a InvoiceAxazureInfo) y el .pdf de PENDING/; si solo encuentra uno de los dos ficheros, omite el pedido en esa pasada; si los ficheros ya estaban en PROCESSED/, los re-encola moviéndolos de vuelta a PENDING/. Reconecta la sesión SFTP cada 10 pedidos procesados. El importe de impuestos (totalTaxAmount) se envía siempre como BigDecimal.ZERO a 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 (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/decathlon-create-db:<tag>.
  • Orquestación: Kubernetes CronJob (k8s/cronjob.yaml) en el clúster GKE pi-cluster-hw (zona europe-west3-a, proyecto pi-saldum), namespace pi, ejecutándose cada hora.
  • CI/CD (Jenkins): pipeline con 3 etapas — CheckoutBuild & Push (sustituye application-pro.properties por application.properties antes de mvn clean package jib:build) → Deploy to GKE (borra el CronJob existente con --ignore-not-found y aplica el manifiesto templado vía sed).
  • Todas las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (decathlon-create-db), referenciado en cada env.valueFrom.secretKeyRef del cronjob.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.saveShipment captura ParseException y notifica a Slack en vez de propagar la excepción.
  • DecathlonInvoicesRunner inspecciona 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 a CANCELLED en base de datos y notifica a Slack. DecathlonAuCreateDbCheckRunner (AU) solo notifica a Slack, sin invocar orderService.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 un do-while(hasOrders(response)), mientras que DecathlonAuCreateDbRunner (AU) usa un bucle infinito for (;;) { ... return; ... }. Ambos son funcionalmente equivalentes pero estilísticamente inconsistentes; unificar el patrón facilitaría el mantenimiento.
  • Inconsistencia en la configuración de SFTP: DecathlonInvoicesRunner inyecta los parámetros de conexión SFTP con @Value individuales (erp.invoices.ftp.*) en lugar de agruparlos en una clase @ConfigurationProperties dedicada, a diferencia del patrón usado en proyectos hermanos como dafiti-create-db (que sí usa un record FtpProperties).
  • Impuestos de factura siempre a cero: DecathlonInvoicesRunner.uploadInvoice fija request.setTotalTaxAmount(BigDecimal.ZERO) de forma incondicional al subir la factura a Decathlon, sin usar el taxAmount real disponible en InvoiceAxazureInfo. Conviene confirmar si es intencional o un defecto pendiente de corregir.
  • Duplicado en la lista de países premium: la propiedad decathloncreatedb.order.premiumcountrylist incluye HR dos veces (CH,HR,VA,MC,MA,LI,SM,GB,CZ,PL,HU,HR,BG,RO,DK,SE), tanto en application.properties como en application-pro.properties. No tiene efecto funcional (se usa como contains), pero indica una copia manual sin depurar.
  • CLAUDE.md verificado: 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.