Integraciones
1. robo-api
Toda la persistencia va contra robo-api (ROBO_API_URL), con el access token del empleado autenticado (ver Roles y control de acceso). Endpoints consumidos:
| Dominio | Endpoints |
|---|---|
| Devoluciones | GET /return/filter, GET /return/info-all/{id}, PUT /return/edit/{id}, DELETE /return/delete/{id} |
| Alta de devolución | POST /order/info, POST /order/create-return |
| Historial | GET /return-log/info/{id} |
| Métodos | GET /return-method/info, POST /return-method/search, POST /new, PUT /edit/{id}, DELETE /delete/{id} |
| Motivos | GET /return-reason/info, POST /return-reason/search, POST /new, PUT /edit/{id}, DELETE /delete/{id} |
| Estados | GET /return-status/info, GET /return-line-status/info |
| Orígenes | GET /return-origin/info |
| Métodos de pago | GET /return-payment-method/info |
| Almacenes | GET /return-store/info, PUT /return-store/edit/{id} |
| Sources | GET /source/info |
| Usuarios Auth0 | GET /return-auth0-user/info, GET /info/{id}, POST /new, PUT /edit/{id} |
Comparado con el portal del cliente, que solo usa los endpoints permitidos al scope customer, aquí se explota la superficie completa de escritura y administración de la API.
Modo de pruebas
Igual que el portal del cliente, fetchAuth añade la cabecera X-Test-Mode: true cuando API_TEST_HEADER/API_TEST_HEADER_VALUE están definidas y NODE_ENV !== "production". El TestModeFilter de robo-api redirige entonces las escrituras a las tablas sombra (ReturnTest, ReturnLineTest, ReturnLogTest).
2. Salesforce Marketing Cloud
lib/marketingCloud.tsx es el mismo cliente mínimo que en el portal del cliente: token por client_credentials cacheado en memoria y marketingCloudRequest() sobre MC_API_URL.
La diferencia está en que aquí hay tres journeys, uno por hito del ciclo de vida, cada uno con su propia EventDefinitionKey:
| Acción | Variable de entorno | Cuándo se dispara |
|---|---|---|
CREATED | MC_EVENT_KEY_CREATED | Al confirmar un alta manual, solo si el empleado marca el checkbox de enviar email |
APPROVED | MC_EVENT_KEY_APPROVED | Al guardar el detalle con rol STORE, si hay líneas recién aprobadas sin email enviado |
REFUNDED | MC_EVENT_KEY_REFUNDED | Tiene payload y clave configurada, pero ningún componente lo invoca (ver Notas técnicas) |
/api/trigger-journey resuelve la clave a partir de la acción y publica:
POST ${MC_API_URL}/interaction/v1/events
{
ContactKey: data.EmailAddress,
EventDefinitionKey: <clave según la acción>,
Data: { …payload… }
}
:::info Protección en entornos no productivos
Si NODE_ENV !== "production", el handler sobrescribe data.EmailAddress con TEST_EMAIL antes de enviar, igual que en el portal del cliente. Como el ContactKey se deriva de ese campo, todos los eventos de prueba se agrupan bajo el mismo contacto.
:::
Construcción de los payloads (lib/journeys.ts)
prepareJourneyPayload(action, ctx) es una función sobrecargada por tipos que delega en tres generadores:
generateJourneyDataForCreation — el más completo. Toma el idioma del order.locale (getLanguageFromLocale), monta el items como JSON (excluyendo la línea de gastos de envío), serializa el método de devolución con su título e instrucciones en el idioma del pedido y añade la URL absoluta del PDF:
Country__c, EmailAddress, FirstName, OrderNumber, SubscriberKey (sfsc_user_id),
Return_date, Return_number, shippingCost, showShippingCost, TotalFinal,
Return_method (JSON), items (JSON), pdfUrl
generateJourneyDataForApproval — EmailAddress, Country__c, FirstName, OrderNumber, SubscriberKey, TotalFinal e items. Aquí la línea de gastos de envío sí se incluye, pero con el nombre traducido desde constants/shipping-cost.ts (Gastos de envío, Shipping costs, Frais de port, Versandkosten, Costi di spedizione, Custos de envio, Τέλη αποστολής) en mayúsculas, según el locale del pedido.
generateJourneyDataForRefund — EmailAddress, Country__c, FirstName, OrderNumber, SubscriberKey y Amount.
Idempotencia del email de aprobación
El journey APPROVED no debe reenviarse. El mecanismo, en useReturnForm:
getValidItemsToSendApprovalMailselecciona las líneas con estadoAPPROVED(id 4),is_mail_approval_sentfalso y que no sean la línea de gastos de envío con precio 0.- Si hay alguna, se dispara el journey.
- Se vuelve a llamar a
PUT /return/edit/{id}marcandois_mail_approval_sent = trueen esas líneas.
El flag vive en robo-api, así que es persistente entre sesiones y usuarios.
3. El PDF: integración entre los dos portales
Este proyecto firma enlaces de PDF pero no renderiza ninguno: no existe ninguna ruta /doc en el repositorio.
sequenceDiagram
participant E as Empleado
participant B as Back-office
participant C as returns.hawkersco.com<br/>(portal del cliente)
participant R as robo-api
E->>B: Abre el desplegable PDF
B->>B: POST /api/get-pdf-url × 7 idiomas<br/>HMAC(PDF_SIGNATURE_SECRET)
B-->>E: /doc/<base64url>.<hmac> × 7
E->>C: GET https://returns.hawkersco.com/doc/<…>
C->>C: verifySignedPdfUrl (mismo secreto)
C->>R: GET /return/info
C-->>E: PDF renderizado
lib/crypto.tsx es idéntico al del portal del cliente salvo que aquí no existe getSignedPdfStatusUrl: firma { returnId, email, language } con HMAC-SHA256 y devuelve /doc/<base64url>.<firma>. El prefijo https://returns.hawkersco.com se añade en los dos sitios donde se usa (pdf-button.tsx y journeys.ts).
:::warning Dependencia crítica compartida
Los dos proyectos deben tener exactamente el mismo PDF_SIGNATURE_SECRET. Si se rota en uno y no en el otro, todos los enlaces que genere el back-office devolverán 400 Invalid or tampered URL en el portal del cliente, y los enlaces ya enviados por email dejarán de funcionar. Cualquier rotación debe ser coordinada entre ambos Secret de Kubernetes.
:::
Además, el dominio returns.hawkersco.com está hardcodeado en los dos proyectos, por lo que un back-office de desarrollo genera enlaces que apuntan a producción (siguen siendo válidos, porque la firma no depende del host).