Skip to main content

Configuración y despliegue

1. Variables de entorno​

Todas son de servidor (ninguna lleva el prefijo NEXT_PUBLIC_), por lo que nunca llegan al navegador. En local se definen en .env.local; en el clúster proceden del Secret de Kubernetes customer-returns-portal (montado con envFrom.secretRef).

robo-api y Auth0​

VariableDescripciónValor de referencia
API_URLURL base de robo-apihttps://robo-api.hawkersco.net
AUTH0_DOMAINTenant de Auth0 (URL completa, sin /oauth/token)https://robo-hawkers.eu.auth0.com
AUTH0_AUDIENCEAudience del API en Auth0https://robo-api.hawkersco.net
AUTH0_CLIENT_IDClient ID de la aplicaciónsecreto
AUTH0_CLIENT_SECRETClient secretsecreto
AUTH0_USERNAMEUsuario de servicio (grant password, scope customer)secreto
AUTH0_PASSWORDContraseña del usuario de serviciosecreto

Marketing Cloud​

VariableDescripción
MC_AUTH_URLEndpoint /v2/token del tenant de Marketing Cloud
MC_API_URLEndpoint REST base del tenant
MC_CLIENT_IDClient ID del paquete instalado (grant client_credentials)
MC_CLIENT_SECRETClient secret
MC_EVENT_KEYEventDefinitionKey del journey de confirmación de devolución

PDFs, acceso y pruebas​

VariableDescripción
PDF_SIGNATURE_SECRETSecreto HMAC-SHA256 de las URLs firmadas. Rotarlo invalida todos los enlaces de PDF ya enviados
BASIC_AUTH_ENABLED'true' activa el Basic Auth de proxy.tsx. En producción va a false
BASIC_AUTH_USERUsuario del Basic Auth
BASIC_AUTH_PASSContraseña del Basic Auth
TEST_EMAILEmail que reemplaza al del cliente en los journeys cuando NODE_ENV !== 'production'
API_TEST_HEADERCabecera de modo de pruebas de robo-api — X-Test-Mode
API_TEST_HEADER_VALUEValor de esa cabecera (true). Solo se envía si NODE_ENV !== 'production'
warning

PDF_SIGNATURE_SECRET no tiene fallback: si falta, crypto.createHmac se inicializa con cadena vacía y se generan firmas válidas pero con secreto vacío, sin ningún error visible. Conviene verificar que está presente en el Secret del clúster.

2. Basic Auth (proxy.tsx)​

En Next.js 16 el antiguo middleware.ts pasa a llamarse proxy.tsx, y su función exportada es proxy(). Aquí se usa para poder cerrar el portal en entornos no productivos:

  • Si BASIC_AUTH_ENABLED !== 'true' no hace nada (NextResponse.next()).
  • Si está activo, exige Authorization: Basic comparando contra BASIC_AUTH_USER / BASIC_AUTH_PASS; si no coincide devuelve 401 con WWW-Authenticate: Basic realm="Protected Area".
  • Quedan siempre excluidos: /_next, /test, /favicon.ico, /robots.txt e /images (tanto en la lista publicPaths como en el matcher).

/test está excluido a propósito: es una página estática que existe únicamente para validar el certificado de Google (así lo indica su comentario en el código), y debe responder sin credenciales.

3. Ejecución en local​

Requisitos: Node 22 (.nvmrc) y npm.

nvm use # Node 22
npm install
npm run dev # next dev --turbopack -p 80 → http://localhost
  • Los scripts dev y start fijan el puerto 80, así que la app queda en http://localhost sin puerto. En macOS/Linux escuchar en el 80 puede requerir permisos elevados; si molesta, se puede lanzar npx next dev -p 3000 puntualmente.
  • Se necesita un .env.local con, como mínimo, API_URL, las cinco AUTH0_* y PDF_SIGNATURE_SECRET. Sin las MC_*, todo funciona salvo el disparo del journey en el último paso (se registra el error en consola y la UI no se rompe).
  • Con API_TEST_HEADER=X-Test-Mode y API_TEST_HEADER_VALUE=true, las devoluciones creadas en local van a las tablas sombra de robo-api.
  • Verificación rápida: buscar un pedido real en el paso 1 debe devolver sus líneas; el selector del pie debe cambiar el idioma de toda la página; en /info, un HWRET… con su email debe pintar la línea temporal de estados y ofrecer los dos PDFs.

Otros comandos: npm run build (build de producción), npm start (servidor de producción), npm run lint.

4. Imagen Docker​

Dockerfile multi-stage:

  1. builder (node:20-alpine): npm ci y npm run build.
  2. runner (node:20-alpine): copia public, .next, node_modules y package.json, crea el usuario no-root appuser:appgroup, hace chown -R sobre /app y arranca con CMD ["npm", "start"].

EXPOSE 3000, pero el comando de arranque real es next start -p 80: el contenedor escucha en el 80 (ver Notas técnicas).

5. Pipeline de Jenkins​

Jenkinsfile declarativo con tres etapas:

EtapaAcción
Checkoutcheckout scm
Build & Pushgcloud builds submit --tag <REGISTRY>/customer-returns-portal:<BUILD_NUMBER> (Cloud Build → Artifact Registry)
DeployCredenciales del clúster, borrado del deployment anterior, sed del tag en k8s/deployment.yaml, kubectl apply y kubectl rollout status

Entorno del pipeline:

VariableValor
GCP_PROJECTpi-saldum
GKE_CLUSTERpi-cluster-hw
GKE_ZONEeurope-west3-a
REGISTRYeurope-west3-docker.pkg.dev/pi-saldum/pi-repo
IMAGE_NAMEcustomer-returns-portal
GKE_NAMESPACEpi
IMAGE_TAG<REGISTRY>/<IMAGE_NAME>:<BUILD_NUMBER>

El deploy elimina el deployment (kubectl delete deployment … --ignore-not-found) y espera a que desaparezcan los pods (kubectl wait --for=delete pod -l app=… --timeout=60s) antes de volver a aplicarlo. Es un despliegue con corte de servicio de unos segundos, coherente con la estrategia Recreate del manifiesto.

A diferencia de otros proyectos del ecosistema, este pipeline no inyecta ningún fichero de configuración: toda la configuración llega por variables de entorno desde el Secret de Kubernetes.

6. Kubernetes (k8s/deployment.yaml)​

PropiedadValor
Namespacepi
Réplicas1
EstrategiaRecreate
Imagen…/pi-repo/customer-returns-portal:latest (placeholder; sed lo sustituye por el tag del build)
imagePullPolicyIfNotPresent
containerPort3000
ConfiguraciónenvFrom.secretRef: customer-returns-portal
automountServiceAccountTokenfalse
Requests256 Mi memoria / 100m CPU / 256 Mi ephemeral-storage
Limits512 Mi memoria / 500m CPU / 512 Mi ephemeral-storage

El repositorio solo contiene el Deployment: el Service y el Ingress que exponen returns.hawkersco.com se gestionan fuera de este repo.

7. Manejo de errores y logging​

  • Cada route handler captura sus excepciones y responde { error: mensaje } con el status adecuado; los mensajes de cara al usuario ya vienen traducidos desde el servidor.
  • En cliente, los errores se muestran con toasts de sonner; los fallos de estado (sin pedido, sin devolución) renderizan el componente Error con un botón que reinicia el flujo.
  • No hay integración con Sentry ni con ningún servicio externo de logging: solo console.error, visible en los logs del pod.
  • No hay error.tsx ni not-found.tsx propios en el App Router, así que se usan las pantallas por defecto de Next.js.