Skip to main content

logistics-change-status

1. Descripción general

Según el pom.xml, el proyecto se describe como "Logistics Change Status". Es una API REST que actúa como agregador de webhooks: recibe actualizaciones de estado de pedido de múltiples transportistas externos (Auro, Servientrega, 99minutos, NPF, Cubbo, Times Logistics, LogisFashion Chile, Sprint Logistics, Sarmed, LogSolutions, Sprint Logistics UK), valida el payload (de forma desigual según el proveedor, ver hallazgo de seguridad) y lo almacena como fichero JSON/XML en Google Cloud Storage para su procesamiento posterior por otros servicios. No hay base de datos ni lógica de actualización saliente de pedidos: el servicio solo recibe, valida (parcialmente) y almacena.

2. Información técnica

CampoValor
artifactIdlogistics-change-status
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, API web de larga duración)
MódulosNo aplica (proyecto de módulo único)

3. Arquitectura y diseño

  • .controllerLogisticsChangeStatusController, único controlador con 12 endpoints (1 de health check + 11 de recepción por proveedor).
  • .configGcsConfig (bean de GCS), LogisticsChangeStatusConfig (usuarios BasicAuth en memoria), App2ConfigurationAdapter (cadena de filtros de seguridad + CORS).
  • .utilsLogisticsChangeStatusUtil (validación de JSON genérica, verificación de cabeceras 99min, verificación de token Sarmed), LogisticsChangeStatusConst.
  • .model — DTOs por proveedor (StatusOrdersAuro, StatusOrderNpf, StatusOrderServientrega, StatusOrderDistricenter) — no todos los proveedores tienen DTO propio; varios endpoints trabajan directamente sobre el String JSON crudo.
flowchart TD
A[Transportista externo] -->|POST /api/change-status-order/proveedor| B[LogisticsChangeStatusController]
B -->|validación según proveedor, ver hallazgo| C{¿válido?}
C -->|sí| D[GCS pi-logistics-segment]
C -->|no| E[400 Bad Request]
B -.->|error de escritura GCS| F[Slack]

4. Dependencias principales

DependenciaPropósito
spring-boot-starter-webAPI REST
spring-boot-starter-securityBasicAuth en memoria + cadena de filtros
org.json:jsonValidación y construcción de JSON
org.apache.poi:poi / poi-ooxmlDeclaradas, sin uso aparente en el controlador actual
com.hawkersco:slack-clientNotificaciones de error
com.hawkersco:pi-function-commonsStorageUtils (escritura en GCS), DateUtils

5. API / Endpoints

Todos bajo el prefijo /api.

MétodoRutaProveedorAutenticaciónValidación de contenido
GET/checkPública
POST/change-status-order/auroAuroBasicAuth, ROLE_AUROisJSONValid (sintaxis JSON genérica)
POST/change-status-order/servientregaServientregaBasicAuth, ROLE_SERVENTREGAisJSONValid
POST/change-status-order/npfNPFPública (permitAll)Ninguna — ni siquiera sintaxis JSON
POST/change-status-order/cubboCubboPúblicaNinguna
POST/change-status-order/99min99minutosPública, pero con verificación de cabeceras (verifyHeader99min)Cabeceras user/password/flags en Base64 comparadas contra valores configurados
POST/change-status-order/times-logisticsTimes LogisticsPúblicaNinguna (solo registra las cabeceras en el log)
POST/change-status-order/logisfashion-cl/logiscore-statusLogisFashion ChileBasicAuth (no listado en permitAll, cae en anyRequest().authenticated())Ninguna
POST/change-status-order/logisfashion-cl/shipping-statusLogisFashion ChileBasicAuth (ídem)Ninguna
POST/change-status-order/sprintlogisticsSprint LogisticsPúblicaisJSONValid
POST/change-status-order/sarmedSarmedPública, pero con verificación de token (verifyStatusSarmed)isJSONValid + token en el propio payload comparado contra valor configurado
POST/change-status-order/logsolutionsLogSolutionsPúblicaisJSONValid
POST/change-status-order/sprintlogistics-ukSprint Logistics UKPúblicaisJSONValid

Todas las respuestas de éxito devuelven 200 OK con un JSON {"orderStatus": "..."}", incluso cuando el almacenamiento en GCS falla (en ese caso se notifica a Slack pero la respuesta HTTP sigue siendo 200).

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Transportistas externos (11 proveedores)HTTP POST entrante (webhook)EntranteNotificación de cambios de estado de pedido
Google Cloud Storage (bucket pi-logistics-segment)API de GCSSalienteAlmacenamiento de cada payload recibido, particionado por proveedor
SlackHTTP (SlackClient)SalienteNotificación de errores al escribir en GCS

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).

ClaveDescripción
server.portPuerto de escucha (8080 en local, 80 en producción)
logisticchangestatus.encodepass / .encodepasslogisfashionchileHashes BCrypt de las contraseñas BasicAuth (Auro/Servientrega y LogisFashion Chile)
99min.headers.user / .password / .flagsValores esperados (en claro, luego codificados a Base64 en tiempo de ejecución) para la verificación de cabeceras de 99minutos
sarmed.tokenToken esperado en el payload de Sarmed
gcs.bucket.nameBucket de GCS destino
slack.client.url / .auth.token / .channel.idConfiguración de Slack

🛑 Alerta de seguridad — la mayoría de endpoints "públicos" no verifican en absoluto al proveedor que dicen recibir

De los 8 endpoints marcados como públicos (permitAll) en App2ConfigurationAdapter, solo 2 (99min y sarmed) verifican realmente que la petición proviene del proveedor esperado (cabeceras Base64 y token en el payload, respectivamente). Los otros 6 —npf, cubbo, times-logistics, sprintlogistics, logsolutions, sprintlogistics-uk— o bien no aplican ninguna validación (npf, cubbo, times-logistics), o bien solo comprueban que el cuerpo sea JSON sintácticamente válido (sprintlogistics, logsolutions, sprintlogistics-uk) sin verificar ningún token, firma o cabecera específica del proveedor. Además, los dos endpoints de LogisFashion Chile (logiscore-status, shipping-status) están protegidos por BasicAuth pero, una vez autenticados, tampoco aplican ninguna validación de contenido adicional.

En la práctica, esto significa que cualquiera que conozca o adivine estas URLs puede enviar actualizaciones de estado de pedido fabricadas para 6 de los 11 proveedores integrados, y el servicio las almacenará en GCS indistinguibles de una notificación real del transportista, para su posterior consumo por otros sistemas de logística de Hawkers. El CLAUDE.md existente afirma de forma genérica que estos endpoints "rely on custom token/header validation inside LogisticsChangeStatusUtil", lo cual no es cierto salvo para 99min y Sarmed — es la desviación de seguridad más relevante detectada en este proyecto.

El fichero application.properties (perfil local) contiene además, en texto plano, las credenciales de 99minutos y el token de Sarmed, y los hashes BCrypt de las contraseñas BasicAuth, y el token de bot de Slack. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Añadir verificación de token/firma/cabecera específica a los endpoints npf, cubbo, times-logistics, sprintlogistics, logsolutions y sprintlogistics-uk, siguiendo el mismo patrón ya implementado para 99min y sarmed.
  2. Rotar las credenciales de 99minutos y el token de Sarmed dado que han estado expuestas en texto plano.
  3. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.

8. Persistencia

No aplica a este proyecto. No usa base de datos: el único almacenamiento es Google Cloud Storage, usado como buzón intermedio para el resto del ecosistema.

9. Procesos programados y mensajería

No aplica a este proyecto. No hay @Scheduled ni CronJob — se despliega como Deployment de larga duración que atiende webhooks entrantes en tiempo real.

10. Ejecución en local

Requisitos previos: JDK 25, Maven.

# Compilar sin tests
./mvnw -B -DskipTests clean install

# Ejecutar la aplicación localmente
./mvnw spring-boot:run

# Ejecutar tests
./mvnw test

# Ejecutar un test concreto
./mvnw test -Dtest=LogisticsChangeStatusApplicationTests

# Build Docker
docker build -t logistics-change-status:1.0.25 .

Verificación de que el servicio está operativo: GET /api/check (público).

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/logistics-change-status:<tag>.
  • Orquestación: Kubernetes Deployment (una réplica) en el clúster GKE pi-cluster-hw, namespace pi, con credenciales de cuenta de servicio de GCP montadas por volumen.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE.

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/logistics-change-status/

12. Manejo de errores y logging

No hay un @RestControllerAdvice centralizado. El método interno store() envuelve la escritura en GCS en un try/catch genérico: en éxito devuelve 200 con estado de éxito; en error, notifica a Slack pero igualmente devuelve 200 con un JSON de estado de error en el cuerpo (nunca un código HTTP 4xx/5xx por fallo de almacenamiento) — el llamador solo puede distinguir el fallo inspeccionando el cuerpo de la respuesta, no el código de estado HTTP. Logging vía SLF4J (@Slf4j).

13. Notas y consideraciones

  • Ver alerta de seguridad crítica en la sección 7: la mayoría de los endpoints "públicos" no verifican en absoluto la identidad del proveedor que dicen atender, contradiciendo la descripción genérica del CLAUDE.md existente.
  • Los errores de almacenamiento siempre devuelven HTTP 200: incluso cuando falla la escritura en GCS, el endpoint responde 200 OK con un campo de estado de error en el JSON, en lugar de un código de error HTTP — un consumidor automatizado que solo compruebe el código de estado nunca detectará el fallo.
  • Paquete consts/ citado en CLAUDE.md no existe como tal: LogisticsChangeStatusConst vive en el paquete utils/, no en un paquete consts/ separado.
  • El resto de la arquitectura descrita en CLAUDE.md (flujo de recepción, GCS como único almacén, patrón de configuración con dos perfiles, CORS abierto y CSRF deshabilitado) coincide con el código real, verificado directamente en LogisticsChangeStatusController y App2ConfigurationAdapter.