Skip to main content

trustpilot-invitation-sender

1. Descripción general

Según el pom.xml, el proyecto se describe como "Trustpilot Invitation Sender". Es un microservicio batch (runner) que envía invitaciones de reseña de Trustpilot (de servicio y de producto) a clientes cuyo pedido fue entregado hace exactamente 3 días, consultando el datawarehouse corporativo en Snowflake y excluyendo clientes con incidencias de soporte abiertas o direcciones de correo internas.

2. Información técnica

CampoValor
artifactIdtrustpilot-invitation-sender
groupIdcom.hawkersco
version1.0.25
Java25
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 2 CommandLineRunner, ambos activos.

OrdenRunnerMarcaTiendas (id_shop)
1TrustpilotInvitationSenderHwRunnerHawkers Co1, 2, 3, 4, 5, 16, 33, 34, 35, 36
2TrustpilotInvitationSenderNwRunnerNorthweek13
  • .configSnowflakeConfig (DataSource con autenticación RSA por clave privada), SnowflakeJdbcTemplateConfig (JdbcTemplate sobre Snowflake), SnowflakeProperties.
  • .utilsTrustpilotInvitationSenderUtils (consulta SQL a Snowflake + mapeo de filas), SnowflakeKeyLoader (decodifica Base64 y descifra la clave privada PKCS8).
  • .modelsInvitationRow (record con los datos de la fila de resultado).
flowchart TD
A["1. TrustpilotInvitationSenderHwRunner"] -->|getInvitations, hoy-3 días| B[(Snowflake DWH)]
C["2. TrustpilotInvitationSenderNwRunner"] --> B
A -->|createInvitationRow, filtra emails internos| D[TrustpilotInvitationsClient]
C --> D
D -->|createInvitation, service+product review| E[Trustpilot API]

Flujo (idéntico en ambos runners, solo cambia el filtro de tiendas y la plantilla/reply-to por marca): ejecuta una consulta SQL sobre Snowflake que obtiene, para las tiendas de la marca, los pedidos entregados (ENTREGADO) exactamente 3 días atrás (o, si la tienda no tuvo entregas confirmadas en los últimos 30 días, los pedidos cuyo último pago fue hace 3 días), excluyendo clientes con casos de soporte abiertos por NOT_RECEIVED_ORDER, RECEIVED_PRODUCT_DEFECT o RECEIVED_ORDER_INCOMPLETE, y excluyendo SKUs con prefijo DN. Por cada fila, filtra los correos internos (@saldum.com o que contengan hawkers), resuelve el idioma/URL de redirección según el país (con casos especiales para países hispanohablantes: AD, MX, CO) y la plantilla según un mapa país→plantilla cargado desde JSON; construye una invitación combinada de reseña de servicio y de producto, y la envía a la API de Trustpilot.

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
spring-jdbcJdbcTemplate/DataSource para Snowflake
net.snowflake:snowflake-jdbcDriver JDBC de Snowflake
org.bouncycastle:bcprov-jdk18on / bcpkix-jdk18onDescifrado de la clave privada RSA (PKCS8) usada para autenticación en Snowflake
com.hawkersco.connectors:api-connectorsTrustpilotInvitationsClient y modelos (Invitation, ServiceReviewInvitation, ProductReviewInvitation)
commons-ioLectura de los ficheros JSON de recursos
com.fasterxml.jackson.core:jackson-databindParseo del mapa país→plantilla
Lombok (annotation processor)Generación de código boilerplate

5. API / Endpoints

No aplica a este proyecto. Es un batch/runner sin capa REST.

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Snowflake (DWH)JDBC con autenticación por clave RSAEntranteConsulta de pedidos entregados elegibles para invitación
Trustpilot APIHTTP (TrustpilotInvitationsClient, librería api-connectors)SalienteEnvío de invitaciones de reseña de servicio y de producto

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
trustpilot.username / .apikey / .password / .apisecretCredenciales de la API de Trustpilot
trustpilot.businessunitid.hw / .nwID de business unit de Trustpilot por marca
bi.filtershopid.hw / .nwIDs de tienda incluidos en la consulta, por marca
bi.db.server / .name / .schema / .snowflakewarehouse / .role / .user / .portConexión a Snowflake
bi.db.private-key / .passphraseClave privada RSA (Base64/PKCS8 cifrada) y su contraseña, para autenticación en Snowflake
trustpilot.default.template.hw / .nwPlantilla de invitación por defecto por marca
trustpilot.default.replyto.hw / .nwDirección de respuesta por marca
Recursos JSON (trustpilotlocales.json, trustpilotcountrytemplatemap_hw.json, trustpilotcountrytemplatemap_nw.json)Mapas de país → locale/plantilla/URL de redirección

⚠️ Alerta de seguridad

El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: credenciales completas de la API de Trustpilot (usuario, contraseña, API key y API secret — las mismas ya señaladas como expuestas en trustpilot-catalog-updater), y la clave privada RSA completa (Base64/PKCS8 cifrada) y su contraseña de descifrado usadas para autenticar contra Snowflake. Esta última es especialmente sensible: da acceso directo al datawarehouse corporativo. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar la clave privada RSA de Snowflake y su contraseña con prioridad alta, dado el nivel de acceso que otorgan.
  2. Rotar las credenciales de Trustpilot.
  3. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.
  4. Revisar el historial de control de versiones, ya que estas credenciales pueden seguir expuestas en commits anteriores.

8. Persistencia

No hay base de datos propia gestionada por este proyecto: se consulta el datawarehouse corporativo Snowflake en modo solo lectura (tablas lkp_order, lkp_customer, lkp_cs_case, stg_pi_shipment_history, etc.), sin persistir ningún dato localmente. No hay Flyway/Liquibase.

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 11:30 (schedule: "30 11 * * *", zona horaria Europe/Madrid). Se ejecutan en orden los 2 runners de la tabla de la sección 3.

10. Ejecución en local

Requisitos previos: JDK 25, Maven, acceso a Snowflake (clave RSA válida) y credenciales de la API de Trustpilot. Requiere el flag de JVM --add-opens=java.base/java.nio=ALL-UNNAMED (necesario para el driver de Snowflake), ya configurado en el pom.xml y en el CronJob vía JAVA_TOOL_OPTIONS.

# Compilar
./mvnw clean install

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

# Ejecutar tests
./mvnw test

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

# Generar el JAR
./mvnw clean package

Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o las invitaciones reflejadas en el panel de Trustpilot Business.

11. Despliegue

  • Imagen: construida con jib-maven-plugin (base eclipse-temurin:25-jre, containerizingMode=packaged, flag JVM --add-opens incluido), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/trustpilot-invitation-sender:<tag>.
  • Orquestación: Kubernetes CronJob en el clúster GKE pi-cluster-hw, namespace pi, contenedor no privilegiado, ejecutándose diariamente a las 11:30 (Europe/Madrid).
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE. El CLAUDE.md describe un pipeline con KICS scan, SonarQube y test que no existen en el Jenkinsfile actual (mismo patrón detectado en varios proyectos hermanos de este lote).

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/trustpilot-invitation-sender/

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). getInvitations captura cualquier excepción de la consulta a Snowflake, registra Level.SEVERE y devuelve una lista vacía en lugar de propagar el error (mismo patrón de fallo silencioso detectado en trustpilot-catalog-updater). Al enviar cada invitación, un fallo de la API de Trustpilot se registra como warning con el cuerpo de la respuesta, sin interrumpir el envío del resto de invitaciones. Logging mediante java.util.logging.Logger estándar (consola).

13. Notas y consideraciones

  • Todo el código Java vive en el paquete com.hawkersco.trustpilotcatalogupdater, idéntico al de su proyecto hermano trustpilot-catalog-updater, en lugar de com.hawkersco.trustpilotinvitationsender. Es un resto evidente de haber clonado el proyecto trustpilot-catalog-updater como punto de partida sin renombrar el paquete base — no afecta al funcionamiento (Spring Boot escanea el paquete raíz de la clase @SpringBootApplication tal cual esté), pero puede inducir a confusión a cualquier desarrollador que navegue el código esperando un paquete con el nombre del proyecto.
  • Fallo silencioso en la consulta a Snowflake: igual que en trustpilot-catalog-updater, si la consulta SQL falla, getInvitations() registra el error y devuelve una lista vacía; el runner continúa sin enviar ninguna invitación ese día, sin que la ejecución falle explícitamente ni se notifique por ningún canal (no hay integración con Slack en este proyecto).
  • CLAUDE.md verificado y consistente con el código en el resto de aspectos: los 2 runners y su orden, el filtro de tiendas por marca, la lógica de elegibilidad SQL (estado ENTREGADO a los 3 días, exclusión de casos de soporte y SKUs DN), el filtrado de emails internos, y las clases de configuración de Snowflake, todos coinciden con el código real, verificado directamente en TrustpilotInvitationSenderHwRunner y TrustpilotInvitationSenderUtils.
  • Pipeline de Jenkins más simple de lo documentado: ver hallazgo en la sección 11.
  • Ver alerta de seguridad en la sección 7 sobre la clave privada de Snowflake y las credenciales de Trustpilot expuestas en application.properties.