Skip to main content

eci-convert-edi-to-xlsx-sales

1. Descripción general

Según el pom.xml, el proyecto se describe como "Eci convert edi to xlsx sales". Es un microservicio batch (runner) que convierte ficheros EDI (UN/EDIFACT) de ventas de El Corte Inglés (ECI) en informes XLSX. Actualmente solo está activo el flujo de la división Sport, que lee los .edi desde una carpeta de Google Drive, genera/actualiza el XLSX correspondiente y lo vuelve a subir a Drive, marcando los ficheros de origen como procesados.

2. Información técnica

CampoValor
artifactIdeci-convert-edi-to-xlsx-sales
groupIdcom.hawkersco
version1.0.25
Java25 (maven.compiler.source/target=25)
Spring Boot4.0.6
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 con dos CommandLineRunner, de los cuales solo uno está activo.

Paquetes principales:

  • com.hawkersco.eciconverteditoxlsxsales — clase principal (EciConvertEdiToXlsxSalesApplication) y los dos runners.
  • .configGoogleDriveConfig (bean Drive autenticado con cuenta de servicio delegada), EciConvertEdiToXlsxSalesConfig (bean manual de OrderService de logistics-commons).
  • .modelsEdi y EdiSport (records inmutables con los campos extraídos de cada segmento EDI).
  • .serviceEciConvertEdiToXlsxSalesService, con toda la lógica de parseo EDI como métodos estáticos.
flowchart TD
A["EciConvertEdiToXlsxSportSalesRunner (order=0, ACTIVO)"] -->|lista carpetas no procesadas| B[Google Drive]
B -->|descarga .edi| C[parseEdiSport]
C -->|escribe/actualiza XLSX| D[Google Drive]
C -->|marca appProperties.processed=true| B

E["EciConvertEdiToXlsxSalesRunner (order=1, DESACTIVADO)"] -.->|lee edis/*.edi local| F[parseEdi]
F -.->|enriquece con| G[(logistics · OrderService)]
F -.->|genera| H[edis/content.xlsx local]

GoogleDriveConfig construye el cliente Drive autenticado vía GoogleCredentials.getApplicationDefault() con delegación a la cuenta de servicio gcs-pi-kafka@pi-saldum.iam.gserviceaccount.com. EciConvertEdiToXlsxSalesConfig declara manualmente el bean OrderService (no es @Component en la librería externa logistics-commons) y el PersistenceManagedTypes para el escaneo de com.hawkersco.logisticscommons.dao.

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
org.apache.poi:poi-ooxml:5.5.0Generación de ficheros XLSX
com.google.apis:google-api-services-driveCliente de la API de Google Drive
com.google.apis:google-api-services-sheetsCliente de la API de Google Sheets (declarada, sin uso detectado en el código actual)
com.google.code.gson:gson:2.13.2Utilidad JSON (uso puntual)
tools.jackson.core:jackson-databind:3.0.3Deserialización de stores.json en el runner desactivado
com.hawkersco:logistics-commons:1.0.25-SNAPSHOTOrderService y entidades JPA (Order) para enriquecer el flujo local desactivado
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTUtilidades comunes (no se ha detectado uso directo en el código actual)
lombokGeneració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

SistemaProtocoloDirecciónDetalle
Google Drive (carpeta 0AFAvqksVA3npUk9PVA)HTTP (API de Google Drive)Entrante/SalienteDescarga de .edi pendientes, subida/actualización del XLSX resultante, marcado de ficheros procesados
PostgreSQL (logistics)JDBCEntranteEnriquecimiento de líneas EDI con datos de Order (solo usado por el runner local, actualmente desactivado)

7. Configuración

Este proyecto es una excepción dentro de la familia de runners: no dispone de un perfil application-pro.properties independiente (ver hallazgo en la sección 13); solo existe src/main/resources/application.properties, con valores de base de datos hardcodeados que, en producción, son sobrescritos por variables de entorno estándar de Spring Boot definidas directamente en el CronJob.

ClaveDescripciónEjemplo
spring.datasource.urlURL JDBC de la BD logisticsjdbc:postgresql://<host>:5432/logistics
spring.datasource.usernameUsuario de BD${SPRING_DATASOURCE_USERNAME} (producción)
spring.datasource.passwordContraseña de BD${SPRING_DATASOURCE_PASSWORD} (producción)
spring.output.ansi.enabledColores ANSI en el log de consolaALWAYS

La autenticación contra Google Drive no usa una propiedad de configuración: se resuelve en tiempo de ejecución mediante GoogleCredentials.getApplicationDefault(), que en el CronJob toma las credenciales del fichero montado en /etc/gcp/sa_credentials.json (variable GOOGLE_APPLICATION_CREDENTIALS, procedente del Secret pi-saldum-gcp-credentials).

⚠️ Alerta de seguridad

El fichero src/main/resources/application.properties contiene actualmente la contraseña real en texto plano de la base de datos PostgreSQL (pi-noctua-dbuser). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar la contraseña de la base de datos expuesta.
  2. Sustituir el valor hardcodeado por una variable de entorno o un perfil separado, siguiendo el patrón de perfiles duales (application.properties/application-pro.properties) usado en el resto de runners del ecosistema, ya que en este proyecto en producción los valores se sobrescriben vía variables de entorno estándar de Spring (SPRING_DATASOURCE_*) inyectadas por Kubernetes, pero el fichero local sigue conteniendo la credencial real.
  3. Revisar el historial de control de versiones, ya que esta credencial puede seguir expuesta 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. Solo se usa desde el runner local desactivado (OrderService.findOrdersByCdOrderExternalList). El runner activo (Sport, vía Drive) no accede a base de datos. 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 una vez al día a las 06:00 (schedule: "0 6 * * *", concurrencyPolicy: Forbid).

  • EciConvertEdiToXlsxSportSalesRunner (order=0, activo): lista las subcarpetas de la carpeta raíz de Drive que no empiecen por "[PROCESSED] "; para cada una, descarga el XLSX existente (si lo hay) o crea uno nuevo con la hoja "Desglose EDI Sport"; recorre los ficheros .edi no marcados como procesados (appProperties.processed != true), los parsea con parseEdiSport, añade las filas al XLSX, sube el resultado a Drive (actualiza si ya existía, crea si no) y marca cada .edi procesado renombrándolo con el prefijo "[PROCESSED] " y estableciendo appProperties.processed=true / processedAt=<timestamp>.
  • EciConvertEdiToXlsxSalesRunner (order=1, desactivado — la anotación @Component está comentada): lee todos los .edi del directorio local edis/, filtra por el mes de venta hardcodeado SALE_MONTH_FILTER = "06/2026", descarta líneas de devolución/anulación (QTY+212:-), enriquece cada línea con el Order correspondiente (vía logistics-commons) usando el número de pedido truncado, resuelve el nombre de la tienda desde stores.json y genera edis/content.xlsx localmente. Termina la JVM (System.exit(SpringApplication.exit(context))).

10. Ejecución en local

Requisitos previos: JDK 25, Maven, credenciales de aplicación por defecto de Google (GOOGLE_APPLICATION_CREDENTIALS apuntando a una cuenta de servicio con permiso de delegación sobre gcs-pi-kafka@pi-saldum.iam.gserviceaccount.com) y, si se reactiva el runner local, acceso a la BD logistics y ficheros .EDI en el directorio edis/.

# Compilar
./mvnw clean package

# Ejecutar
./mvnw spring-boot:run

# Ejecutar el JAR directamente
java -jar target/eci-convert-edi-to-xlsx-sales-1.0.25.jar

No hay tests automatizados en este proyecto. Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o comprobando en Drive que las carpetas se renombraron con el prefijo "[PROCESSED] ".

Para reactivar EciConvertEdiToXlsxSalesRunner: descomentar @Component en la cabecera de la clase y colocar los ficheros .EDI en edis/ antes de ejecutar; el resultado se genera en edis/content.xlsx. Importante: actualizar primero SALE_MONTH_FILTER (ver sección 13).

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/eci-convert-edi-to-xlsx-sales:<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 diariamente a las 06:00. restartPolicy: Never (a diferencia de OnFailure, usado en el resto de runners de la familia).
  • CI/CD (Jenkins): pipeline con 3 etapas — CheckoutBuild & PushDeploy to GKE. Ver hallazgo crítico en la sección 13 sobre el paso mv application-pro.properties application.properties, que referencia un fichero inexistente en este repositorio.
  • Las credenciales de base de datos se inyectan como variables de entorno estándar de Spring (SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD) desde el Secret eci-convert-edi-to-xlsx-sales-secret (nombre distinto al patrón ${APP_NAME} usado en el resto de runners). Las credenciales de Google Drive se inyectan vía el volumen pi-saldum-gcp-credentials común a otros proyectos.

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/eci-convert-edi-to-xlsx-sales/

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). EciConvertEdiToXlsxSalesService.parseEdi lanza IllegalArgumentException si un segmento QTY+ contiene un valor 0 o no numérico, deteniendo el procesamiento completo del fichero. El runner local envuelve la carga de stores.json en un try/catch que relanza como RuntimeException. El runner de Drive no captura excepciones explícitamente: cualquier error de la API de Drive o del parseo detiene la ejecución y el Job de Kubernetes queda marcado como fallido (sin reintento automático, dado restartPolicy: Never). Logging mediante java.util.logging.Logger estándar (consola), sin notificación a Slack ni otro canal.

13. Notas y consideraciones

  • Jenkinsfile referencia un fichero inexistente: el paso Build & Push ejecuta mv src/main/resources/application-pro.properties src/main/resources/application.properties, pero este repositorio no contiene ningún application-pro.properties (a diferencia de todos los demás runners de la familia *-create-db/*-update-stock). Si el Jenkinsfile no se ha actualizado tras eliminar ese perfil, el mv fallaría el build; si el build actual pasa, es porque Jenkins usa una copia distinta del repositorio con ese fichero presente, o porque el paso falla silenciosamente sin que se haya detectado. Se recomienda verificar el estado real en Jenkins y limpiar el Jenkinsfile para que sea coherente con el repositorio.
  • Filtro de mes hardcodeado en el runner desactivado: EciConvertEdiToXlsxSalesRunner.SALE_MONTH_FILTER = "06/2026" es un valor fijo en código; si se reactiva sin actualizarlo primero, el runner filtrará silenciosamente todas las líneas de venta que no correspondan a junio de 2026, produciendo un XLSX vacío o incompleto sin ningún aviso de error.
  • Posible fuga de estado entre registros en parseEdiSport: al construir cada EdiSport en el bloque NAD+, el código de reseteo de las variables de línea (ean, quantity, price, completionSlip8) está comentado (líneas 258-266 de EciConvertEdiToXlsxSalesService), a diferencia de parseEdi que sí resetea sus variables tras cada inserción. Si dentro de un mismo bloque LOC+162+ aparecen varios ciclos LIN+/RFF+SS+/MOA+/QTY+/NAD+ y alguno de ellos no repite todos los segmentos (p. ej. falta un MOA+), el registro resultante podría arrastrar el valor de price (u otro campo) del ítem anterior. Conviene confirmar si esto es intencional (fallback deseado) o un descuido al desactivar el reseteo.
  • CLAUDE.md con versiones de dependencias desactualizadas: menciona logistics-commons y pi-function-commons en versión 1.0.17 "pendientes de actualizar a Java 25 / Spring Boot 4", cuando el pom.xml actual ya usa la versión 1.0.25-SNAPSHOT de ambas bajo el stack Java 25 / Spring Boot 4.0.6 — la actualización mencionada como pendiente ya se ha realizado. El resto de la descripción arquitectónica de CLAUDE.md (runners, flujo Drive, flujo local desactivado, stores.json) coincide con el código actual.
  • Dependencias declaradas sin uso aparente: google-api-services-sheets y pi-function-commons están en el pom.xml pero no se ha detectado ningún uso directo en el código fuente actual; podrían ser vestigios de una versión anterior del proyecto.
  • Ver alerta de seguridad en la sección 7 sobre la contraseña real de base de datos expuesta en application.properties.