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
| Variable | Descripción | Valor de referencia |
|---|---|---|
API_URL | URL base de robo-api | https://robo-api.hawkersco.net |
AUTH0_DOMAIN | Tenant de Auth0 (URL completa, sin /oauth/token) | https://robo-hawkers.eu.auth0.com |
AUTH0_AUDIENCE | Audience del API en Auth0 | https://robo-api.hawkersco.net |
AUTH0_CLIENT_ID | Client ID de la aplicación | secreto |
AUTH0_CLIENT_SECRET | Client secret | secreto |
AUTH0_USERNAME | Usuario de servicio (grant password, scope customer) | secreto |
AUTH0_PASSWORD | Contraseña del usuario de servicio | secreto |
Marketing Cloud
| Variable | Descripción |
|---|---|
MC_AUTH_URL | Endpoint /v2/token del tenant de Marketing Cloud |
MC_API_URL | Endpoint REST base del tenant |
MC_CLIENT_ID | Client ID del paquete instalado (grant client_credentials) |
MC_CLIENT_SECRET | Client secret |
MC_EVENT_KEY | EventDefinitionKey del journey de confirmación de devolución |
PDFs, acceso y pruebas
| Variable | Descripción |
|---|---|
PDF_SIGNATURE_SECRET | Secreto 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_USER | Usuario del Basic Auth |
BASIC_AUTH_PASS | Contraseña del Basic Auth |
TEST_EMAIL | Email que reemplaza al del cliente en los journeys cuando NODE_ENV !== 'production' |
API_TEST_HEADER | Cabecera de modo de pruebas de robo-api — X-Test-Mode |
API_TEST_HEADER_VALUE | Valor de esa cabecera (true). Solo se envía si NODE_ENV !== 'production' |
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: Basiccomparando contraBASIC_AUTH_USER/BASIC_AUTH_PASS; si no coincide devuelve401conWWW-Authenticate: Basic realm="Protected Area". - Quedan siempre excluidos:
/_next,/test,/favicon.ico,/robots.txte/images(tanto en la listapublicPathscomo en elmatcher).
/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
devystartfijan el puerto 80, así que la app queda enhttp://localhostsin puerto. En macOS/Linux escuchar en el 80 puede requerir permisos elevados; si molesta, se puede lanzarnpx next dev -p 3000puntualmente. - Se necesita un
.env.localcon, como mínimo,API_URL, las cincoAUTH0_*yPDF_SIGNATURE_SECRET. Sin lasMC_*, 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-ModeyAPI_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, unHWRET…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:
- builder (
node:20-alpine):npm ciynpm run build. - runner (
node:20-alpine): copiapublic,.next,node_modulesypackage.json, crea el usuario no-rootappuser:appgroup, hacechown -Rsobre/appy arranca conCMD ["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:
| Etapa | Acción |
|---|---|
| Checkout | checkout scm |
| Build & Push | gcloud builds submit --tag <REGISTRY>/customer-returns-portal:<BUILD_NUMBER> (Cloud Build → Artifact Registry) |
| Deploy | Credenciales del clúster, borrado del deployment anterior, sed del tag en k8s/deployment.yaml, kubectl apply y kubectl rollout status |
Entorno del pipeline:
| Variable | Valor |
|---|---|
GCP_PROJECT | pi-saldum |
GKE_CLUSTER | pi-cluster-hw |
GKE_ZONE | europe-west3-a |
REGISTRY | europe-west3-docker.pkg.dev/pi-saldum/pi-repo |
IMAGE_NAME | customer-returns-portal |
GKE_NAMESPACE | pi |
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)
| Propiedad | Valor |
|---|---|
| Namespace | pi |
| Réplicas | 1 |
| Estrategia | Recreate |
| Imagen | …/pi-repo/customer-returns-portal:latest (placeholder; sed lo sustituye por el tag del build) |
imagePullPolicy | IfNotPresent |
containerPort | 3000 |
| Configuración | envFrom.secretRef: customer-returns-portal |
automountServiceAccountToken | false |
| Requests | 256 Mi memoria / 100m CPU / 256 Mi ephemeral-storage |
| Limits | 512 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 componenteErrorcon 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.tsxninot-found.tsxpropios en el App Router, así que se usan las pantallas por defecto de Next.js.