eci-create-db
1. Descripción general
Según el pom.xml, el proyecto se describe como "ECI create db". Es un microservicio batch (runner) que sincroniza los pedidos del marketplace El Corte Inglés (ECI) con la base de datos interna de logística: acepta los pedidos pendientes en ECI, importa los pedidos en estado de envío como entidades internas (Order, OrderLine, Customer, Shipment...) y genera las etiquetas de envío en PDF a partir de las etiquetas ZPL/PNG proporcionadas por ECI.
Forma parte de la familia de runners de creación de pedidos de marketplace del ecosistema Hawkers (mismo patrón que decathlon-create-db, coppel-create-db, dafiti-create-db), con una particularidad: es el único de esta familia que incluye un paso de conversión y generación de etiqueta de envío (EciLabelRunner) usando el servicio externo Labelary.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | eci-create-db |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 (maven.compiler.release=25) |
| Spring Boot | 4.0.6 |
| 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 3 CommandLineRunner que implementan Ordered y se ejecutan de forma secuencial.
Paquetes principales:
com.hawkersco.ecicreatedb— clase principal (EciCreateDbApplication) y los tres runners..config—EciCreateDbConfig(declaración manual de beans delogistics-commons)..utils—EciCreateDbUtils, componente compartido de transformación de datos y aceptación de pedidos.
flowchart TD
A["1. EciAcceptOrdersRunner<br/>Acepta pedidos en waiting"] --> B["2. EciCreateDbRunner<br/>Importa pedidos SHIPPING (últimos 5 días)"]
B --> C["3. EciLabelRunner<br/>Genera etiquetas PDF y cierra la JVM"]
B -.->|usa| U[EciCreateDbUtils]
A -.->|usa| U
C -->|descarga ZIP| ECI[API ECI]
C -->|PDF→PNG| PDF[PdfUtils]
C -->|PNG→PDF| LAB[Labelary API]
EciCreateDbConfig declara manualmente todos los servicios de logistics-commons como @Bean (no son @Component en la librería externa), junto con el bean PersistenceManagedTypes (sustituto de @EntityScan, eliminado en Spring Boot 4) que escanea 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 |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | Utilidades comunes: DateUtils, PdfUtils, ZipUtils, DirectoryUtils |
com.hawkersco:eci-client:1.0.25-SNAPSHOT | Cliente @HttpExchange (EciClient) para la API de El Corte Inglés |
com.hawkersco:slack-client:1.0.25-SNAPSHOT | Notificaciones de error a Slack |
com.hawkersco:labelary-client:1.0.25-SNAPSHOT | Cliente @HttpExchange (LabelaryClient) para convertir imágenes de etiqueta a PDF |
lombok | Generación de código boilerplate |
spring-boot-starter-test (test) | JUnit 5 + Spring Test |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
Marketplace ECI (marketplace.elcorteingles.es) | HTTP (@HttpExchange vía EciClient) | Entrante/Saliente | Lectura de pedidos en espera/envío, aceptación de pedidos, descarga del ZIP de etiquetas |
Labelary (api.labelary.com) | HTTP (@HttpExchange vía LabelaryClient) | Saliente | Conversión de la imagen PNG de la etiqueta a PDF |
| Slack | HTTP (SlackClient/SlackUtils) | Saliente | Alertas de errores operativos (fuente de pedido no registrada, fallo al guardar Shipment) |
PostgreSQL (logistics) | JDBC | Entrante/Saliente | Persistencia de pedidos vía logistics-commons |
7. Configuración
En producción (application-pro.properties) las credenciales 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).
| 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} |
eci.auth.client.url | URL base de la API de ECI | ${eciUrl} |
eci.credentials.key | API key de autenticación con ECI | ${eciCredentialsKey} |
slack.client.url | URL API Slack | ${slackClientUrl} |
slack.auth.token | Token bot de Slack | ${slackAuthToken} |
slack.channel.id | Canal de notificaciones | ${slackChannelId} |
labelary.client.url | URL base del servicio Labelary | http://api.labelary.com |
⚠️ Alerta de seguridad
El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: contraseña de la base de datos PostgreSQL, API key de ECI y token de bot de Slack (xoxb-...). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar la contraseña de BD, la API key de ECI y el token de Slack.
- Sustituir los valores hardcodeados de
application.propertiespor credenciales de un entorno de desarrollo aislado. - Revisar el historial de control de versiones, ya que estas credenciales pueden seguir expuestas en commits anteriores.
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. Entidades relevantes: Order, OrderLine, OrderMarketplace, Customer, CustomerSource, Shipment, ShipmentLine, Source. No hay Flyway/Liquibase en este repositorio. El runner de etiquetas también gestiona directorios locales temporales zip/ e image/, que borra y recrea en cada ejecución.
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 5 minutos (schedule: "0/5 * * * *", concurrencyPolicy: Forbid). Al arrancar, se ejecutan en orden los 3 runners:
| Orden | Runner | Función |
|---|---|---|
| 1 | EciAcceptOrdersRunner | Obtiene pedidos de ECI en estado "waiting" y los marca como aceptados vía eciClient.acceptOrder |
| 2 | EciCreateDbRunner | Importa pedidos de ECI en estado SHIPPING de los últimos 5 días (paginado de 100), crea Order, OrderLine, Customer, Shipment |
| 3 | EciLabelRunner | Descarga y genera las etiquetas PDF de los pedidos sin etiqueta, y cierra la JVM (System.exit(SpringApplication.exit(context))) |
Detalles relevantes:
EciAcceptOrdersRunner: no hace ninguna comprobación de idempotencia explícita más allá de lo que gestione la propia API de ECI; se limita a recorrer todos los pedidos "waiting" devueltos y aceptarlos uno a uno.EciCreateDbRunner: pagina conThread.sleep(10_000)entre páginas; antes de crear un pedido compruebaorderService.findBySourceAndCdOrderExternal(SOURCE_ID, orderId); si ya existía unOrderMarketplacecon el mismodsOrderMarketplace/fuente, lo borra y lo recrea. El nombre interno del pedido sigue el patrónMKECI+ secuencia numérica de 7 caracteres basada en el últimoidOrderMarketplace+ 1 (no en elidOrder). Los pedidos cuyoshippingTypeLabelcontiene"Entrega en centro comercial"reciben el estadoPENDING_LABELen lugar dePENDING_SHIPMENT, a la espera de queEciLabelRunnercomplete el flujo.EciLabelRunner: para cadaOrdersin etiqueta (orderService.findByOrdersEciNoLabel()), descarga el ZIP de documentos de ECI, lo descomprime, localiza el PDF de la etiqueta, lo convierte a PNG (PdfUtils.convertPdfToPng, 203 DPI), sube el PNG a Labelary para reconvertirlo a PDF, codifica el resultado en base64 y lo guarda enOrderMarketplace.label, avanzando el pedido aPENDING_SHIPMENT.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a la BD logistics y credenciales válidas de ECI, Slack y Labelary en un application.properties local.
# Compilar sin tests (igual que en CI)
./mvnw -B -DskipTests clean install
# Ejecutar tests
./mvnw test
# Ejecutar un test concreto
./mvnw test -Dtest=EciCreateDbApplicationTests
# 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 o consultar el estado (cdProcessingStatus) de la tabla orders 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/eci-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 5 minutos. - 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). - Las variables sensibles se inyectan en el pod mediante un
Secretde Kubernetes llamado igual que la app (eci-create-db).
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/eci-create-db/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). EciAcceptOrdersRunner y EciCreateDbRunner envuelven todo su bucle principal en un try/catch genérico que registra el error con Level.SEVERE y continúa (no propagan, por lo que un fallo en una página no detiene necesariamente las páginas siguientes salvo que rompa el propio bucle). EciCreateDbUtils.saveShipment captura ParseException y notifica a Slack. EciLabelRunner.processOrderLabel captura cualquier Exception por pedido y continúa con el siguiente. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
- Inconsistencia en el email de cliente:
EciCreateDbUtils.transformToOrderLogisticasigna aorder.setDsCustomer(email)el valororderEci.getOrderID() + "@eci.com"(sin prefijo), mientras quesaveCustomerbusca/crea elCustomerusando"ECIEU" + orderEci.getOrderID() + "@eci.com"(con el prefijoCUSTOMER_EMAIL_PREFIX). Son dos cadenas de email distintas para el mismo pedido: el campodsCustomerdel pedido no coincide con eldsEmailreal delCustomerasociado. Aunque la relaciónOrder↔Customerse resuelve poridCustomery no por email, esta discrepancia puede confundir cualquier proceso o consulta que usedsCustomercomo referencia de contacto. - Posible desalineación de rutas en
EciLabelRunner:findPdfNamebusca el PDF en el directoriozip/<dsOrderMarketplace>, pero la extracción posterior (ZipUtils.searchPdf) y la ruta de salida usanzip/<cdOrderExternal>. En la práctica,dsOrderMarketplaceycdOrderExternalprovienen del mismoorderIdde ECI, por lo que hoy coinciden, pero el código no lo garantiza explícitamente: si en algún momento estos dos campos se llenaran con valores distintos, el runner dejaría de encontrar el PDF esperado sin ningún aviso claro del motivo. - Reintento de páginas con
Thread.sleepfijo:EciCreateDbRunnerespera 10 segundos entre cada página de resultados, independientemente de si la página devuelta está vacía o llena; en escenarios con muchas páginas esto puede alargar significativamente la ejecución dentro de la ventana de 5 minutos entre invocaciones delCronJob. CLAUDE.mdverificado: la descripción del flujo (3 runners, source ID 62, formato de nombreMKECI..., dependencias de configuración) coincide con el código actual; no se han encontrado discrepancias relevantes más allá de los hallazgos anteriores, que no estaban documentados enCLAUDE.md.- Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en
application.properties.