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.