generate-invoices-orders-co
1. Descripción general
El pom.xml describe el proyecto como "Generate invoices on orders from Mexico", pero esta descripción es incorrecta: tanto el artifactId (generate-invoices-orders-co), el CLAUDE.md del repositorio y, sobre todo, el código (moneda COP, IVA del 19%, lógica de NIT/CC colombiana, códigos municipales DANE, vendedor PLAY HAWKERS COLOMBIA SAS, zona horaria America/Bogota en el CronJob) confirman que el proyecto genera facturas para pedidos de Colombia, no de México. Se documenta aquí el comportamiento real verificado en el código, y se señala la descripción incorrecta del pom.xml como hallazgo (sección 13).
Es un microservicio batch (runner) que toma hasta 10 pedidos pendientes de facturar (is_generate_invoice = false), calcula sus totales fiscales (IVA, descuentos y envío prorrateados), genera un Excel con el formato exigido por el proveedor de facturación electrónica Fymtech y lo sube por SFTP.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | generate-invoices-orders-co |
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 con un único CommandLineRunner (GenerateInvoicesOrdersCoRunner) que ejecuta todo el flujo y cierra la JVM al finalizar.
Paquetes principales:
com.hawkersco.generateinvoicesordersco— clase principal y el runner..config—LogisticsDbConfig(datasource primariologistics),DynamicsDbConfig(segundo datasourcedynamics-pro),FtpProperties(record@ConfigurationProperties, prefijofymtech.ftp),OrderFraudulentProcessConfig(declaración manual de servicios delogistics-commons/dynamics-commons; el nombre de esta clase no guarda relación con su contenido, ver sección 13)..util—GenerateInvoicesOrdersCoUtil, con la resolución de códigos DANE.
flowchart TD
A[GenerateInvoicesOrdersCoRunner] -->|findAll barcodes| B[(dynamics-pro<br/>HWKInventItemBarcodesService)]
A -->|findOrdersToGenerateInvoiceFalse max 10| C[(logistics · Order)]
A -->|findFirst1 contador| D[(logistics · OrderInvoice)]
A -->|parseo XML SFCC crudo| E[SfccUtils]
A -->|resolución ciudad/depto| F[dane-code-alt.json]
A -->|genera XLSX| G[invoice/*.xlsx]
G -->|SFTP| H[Servidor Fymtech]
H -->|éxito| I[updateIsGenerateInvoice + nuevo contador]
Dos datasources PostgreSQL independientes, ambos con EntityManagerFactory/TransactionManager propios: LogisticsDbConfig (@Primary, prefijo spring.datasource, paquete com.hawkersco.logisticscommons.dao) y DynamicsDbConfig (prefijo dynamics.datasource, paquete com.hawkersco.dynamicscommons.dao).
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
org.apache.poi:poi-ooxml:5.4.0 | Generación del fichero Excel de factura |
com.googlecode.json-simple:json-simple:1.1.1 | Parseo de dane-code-alt.json |
org.apache.commons:commons-lang3:3.17.0 | StringUtils.stripAccents para normalizar nombres de ciudad |
com.hawkersco:logistics-commons:1.0.25-SNAPSHOT | Entidades/servicios de la BD logistics (Order, OrderInvoice, OrderLine) |
com.hawkersco:dynamics-commons:1.0.25-SNAPSHOT | HWKInventItemBarcodesService (mapeo SKU→EAN desde Dynamics) |
com.hawkersco:sfcc-commons:1.0.25-SNAPSHOT | SfccUtils, parseo del XML de pedido de Salesforce Commerce Cloud almacenado en Order.rawData |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | DateUtils, DirectoryUtils, SftpUtils |
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 |
|---|---|---|---|
PostgreSQL (logistics) | JDBC | Entrante/Saliente | Lectura de pedidos pendientes de facturar y actualización del flag/contador de factura |
PostgreSQL (dynamics-pro) | JDBC | Entrante | Lectura del mapeo SKU→EAN (HWKInventItemBarcodesService) |
Servidor SFTP Fymtech (sftp.hawkersco.com) | SFTP (SftpUtils) | Saliente | Subida del Excel de factura generado |
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.jdbc-url | URL JDBC de la BD logistics | ${dbLogisitcsUrl} |
spring.datasource.username / .password | Credenciales de BD logistics | ${dbLogisitcsUsername} / ${dbLogisitcsPassword} |
dynamics.datasource.jdbc-url | URL JDBC de la BD dynamics-pro | ${dbDynamicsProUrl} |
dynamics.datasource.username / .password | Credenciales de BD dynamics-pro | ${dbDynamicsProUsername} / ${dbDynamicsProPassword} |
fymtech.ftp.server / .port / .user / .pass | Conexión SFTP al servidor Fymtech | ${ftpServer} / ${ftpPort} / ${ftpUser} / ${ftpPass} |
fymtech.ftp.dir | Directorio remoto de subida | /src/pending/ |
⚠️ Alerta de seguridad
El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: contraseñas de ambas bases de datos PostgreSQL (logistics y dynamics-pro, ambas con el mismo usuario admin y la misma contraseña) y contraseña del servidor SFTP de Fymtech. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar las contraseñas de ambas bases de datos y la contraseña SFTP.
- 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
Dos bases de datos PostgreSQL independientes: logistics (entidades Order, OrderLine, OrderInvoice, vía logistics-commons) y dynamics-pro (entidad HWKInventItemBarcodes, vía dynamics-commons). Ambas con spring.jpa.hibernate.ddl-auto=none. OrderInvoice actúa como contador incremental de numeración de factura: el runner borra el registro de contador existente y guarda uno nuevo con el valor final tras cada ejecución exitosa. 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 medianoche (schedule: "0 0 * * *", zona horaria America/Bogota, concurrencyPolicy: Forbid). Flujo único de GenerateInvoicesOrdersCoRunner:
- Construye el mapa SKU→EAN desde
HWKInventItemBarcodesService.findAll(). - Obtiene hasta 10 pedidos con
is_generate_invoice = false(orderService.findOrdersToGenerateInvoiceFalse(10)) y el contador actual de factura (orderInvoiceService.findFirst1()). - Por cada pedido: parsea el XML crudo de SFCC (
Order.rawData) conSfccUtils, calcula descuento y envío totales del pedido y los prorratea entre las líneas de producto según su peso de precio neto, calcula el IVA (19% fijo) y determina si el cliente es persona (CC) o empresa (NIT, 10 dígitos con prefijo860/830/900-909) a partir del atributo personalizado SFCCHW_customer_identification; si no hay identificación, usa el cliente por defecto222222222/ "Cuantías Menores". - Resuelve el código municipal DANE de la ciudad de envío contra
dane-code-alt.json(normalizando tildes salvo lañ). - Escribe una fila por línea de producto en un Excel de 57 columnas (formato específico exigido por Fymtech).
- Sube el fichero por SFTP; si la subida tiene éxito, marca los pedidos como facturados (
updateIsGenerateInvoice(true, ...)) y actualiza el contador de factura (OrderInvoice).
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a las BD logistics y dynamics-pro, y credenciales SFTP válidas de Fymtech en un application.properties local.
# Compilar sin tests (igual que en CI)
./mvnw -B -DskipTests clean install
# Compilar con tests
./mvnw clean install
# Ejecutar tests
./mvnw test
# Ejecutar un test concreto
./mvnw test -Dtest=GenerateInvoicesOrdersCoApplicationTests
# 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, el contenido de invoice/*.xlsx generado localmente, o el estado is_generate_invoice de los pedidos 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/generate-invoices-orders-co:<tag>. - Orquestación: Kubernetes
CronJob(k8s/cronjob.yaml) en el clúster GKEpi-cluster-hw(zonaeurope-west3-a, proyectopi-saldum), namespacepi, ejecutándose diariamente a medianoche hora de Bogotá. - 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). ElCLAUDE.mdmenciona una imagen baseeclipse-temurin:25-jdk-alpiney etapasBuild → Test (vacío) → Push → Deployment → Clean, que no coinciden exactamente con elJenkinsfile/pom.xmlactuales (ver sección 13). - Las variables sensibles se inyectan en el pod mediante un
Secretde Kubernetes llamado igual que la app (generate-invoices-orders-co).
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/generate-invoices-orders-co/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). generateAndUploadInvoice envuelve toda la generación y subida del Excel en un único try/catch que registra el mensaje de error con Logger.severe y no relanza la excepción — el runner termina "con éxito" desde el punto de vista del proceso (sin marcar los pedidos como facturados) aunque haya fallado a mitad de camino. No hay notificación a Slack ni otro canal de alerta. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
- Descripción del
pom.xmlincorrecta: indica"Generate invoices on orders from Mexico", pero el proyecto genera facturas para Colombia (monedaCOP, IVA 19%, NIT/CC, códigos DANE, vendedorPLAY HAWKERS COLOMBIA SAS, zona horariaAmerica/Bogota). Es probable que la descripción se copiara de un proyecto homólogo para México sin actualizarla. Se recomienda corregirla en elpom.xml. - Bug real de
NullPointerExceptionenextractCustomerInfo: la condiciónif (attr.getContent().getFirst() != null || !attr.getContent().getFirst().toString().isEmpty())usa||en lugar de&&. Siattr.getContent().getFirst()esnull, el primer operando esfalse, por lo que Java evalúa el segundo operando (!attr.getContent().getFirst().toString().isEmpty()), que vuelve a invocar.getFirst()(de nuevonull) y llama.toString()sobre él, lanzando unaNullPointerException. Cualquier pedido SFCC cuyo atributoHW_customer_identificationtenga un primer valornullharía fallar el procesamiento de ese pedido y, dado que eltry/catchenvolvente no distingue el pedido que falló, aborta la generación de toda la factura del lote (los demás pedidos del lote tampoco se marcan como facturados en esa ejecución). Se recomienda corregir el operador a&&y añadir una comprobación de nulidad explícita antes de invocar.toString(). - Fallo parcial silencioso: como el único
try/catchdegenerateAndUploadInvoicecubre generación de Excel, subida SFTP y actualización de estado, un error en cualquier punto (incluida la NPE anterior) hace que la ejecución completa termine sin marcar ningún pedido como facturado, sin notificar el motivo por ningún canal salvo el log local del pod (que se recicla en cada ejecución delCronJob). No hay reintento parcial ni aislamiento por pedido. - Nombre de clase de configuración no relacionado con su contenido:
OrderFraudulentProcessConfigsolo declara beans deOrderService,OrderLineService,OrderInvoiceServiceyHWKInventItemBarcodesService— nada relacionado con "proceso fraudulento". El nombre es probablemente un vestigio de copiar la clase desde otro proyecto (posiblemente uno de detección de fraude) sin renombrarla. CLAUDE.mdcon detalles de despliegue desactualizados: menciona imagen baseeclipse-temurin:25-jdk-alpiney manifiesto enjenkins/deployment/app-deployment.yml, mientras que el proyecto real usajib-maven-plugin(imageneclipse-temurin:25-jre) yk8s/cronjob.yaml. El resto de la descripción arquitectónica (flujo, doble datasource, reglas fiscales colombianas) coincide con el código actual y es más precisa que la propia descripción delpom.xml.- Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en
application.properties.