Notas técnicas y consideraciones
Hallazgos observados al revisar el código. No son incidencias abiertas: son puntos a tener presentes antes de tocar el proyecto.
Control de acceso
- El proxy comprueba solo el primer rol.
proxy.tsllama ahasAccess(roles[0], pathname), mientras que el sidebar (sidebar-links.tsx) recorre todos los roles del usuario. Un usuario con["ATC", "ADMIN"]vería en el menú los enlaces de administración (porque el sidebar los resuelve por unión de roles) pero el proxy lo redirigiría a/returnsal entrar, ya que evalúa únicamenteATC. Conviene unificar ambos criterios. - Los roles se leen del ID token sin verificar la firma.
getUserRoleshaceBuffer.from(idToken.split(".")[1], "base64")y parsea el payload. El token viene de una sesión ya validada por el SDK de Auth0, así que en la práctica es seguro, pero es un patrón que invita a reutilizarse en contextos donde no lo sería. /api/tokenexpone el access token de robo-api al navegador y no tiene ningún consumidor en el código. Cualquier sesión autenticada puede pedirlo con unGET, lo que amplía el impacto de un XSS: pasa de "sesión con cookie httpOnly" a "bearer token de robo-api en manos de JavaScript". Si no se usa, lo razonable es borrar la ruta.- El rol
CUSTOMERno tiene entrada enROLE_ROUTES. Un usuario cuyo único rol seaCUSTOMERpasa la comprobación de sesión pero no tiene acceso a ninguna sección; acaba en/unauthorizedo redirigido a/returnssin poder ver nada. Es coherente con el diseño, pero conviene saberlo al diagnosticar accesos.
Seguridad y datos personales
PDF_SIGNATURE_SECRETcompartido con el portal del cliente. Es una dependencia acoplada entre dos despliegues independientes: rotarla en uno rompe los enlaces del otro. Ver Integraciones.PDF_SIGNATURE_SECRETsin validación. Si falta,crypto.createHmac("sha256", "")no falla: se firman URLs con secreto vacío y todo parece funcionar (el mismo patrón que en el portal del cliente).lib/marketingCloud.tsxsí valida sus variables al arrancar.- PII en los logs.
/api/trigger-journeyhace unconsole.logdel objeto completo antes de enviarlo, incluyendoContactKeyy elEmailAddressdel cliente. Esos logs acaban en los logs del pod. - Datos de pedido en
localStorage. El alta manual persisterefundProcess(pedido, email, artículos) enlocalStorage, que sobrevive al cierre de la pestaña. En un equipo compartido de tienda queda hasta completar o reiniciar el alta. El portal del cliente usasessionStoragepara lo equivalente. - HTML de terceros renderizado sin sanear. Las descripciones e instrucciones de los métodos se editan aquí con un WYSIWYG que permite HTML crudo y se renderizan con
dangerouslySetInnerHTMLen los dos portales. Es un vector de XSS auto-infligido: quien administre métodos puede inyectar HTML arbitrario en el portal público. - El dominio
returns.hawkersco.comestá hardcodeado enpdf-button.tsxy enlib/journeys.ts, así que un back-office de desarrollo genera enlaces a producción.
Funcionalidad incompleta o inalcanzable
- El journey
REFUNDEDno se dispara nunca. ExistengenerateJourneyDataForRefund, la sobrecarga deprepareJourneyPayloady la variableMC_EVENT_KEY_REFUNDED, perouseTriggerJourneysolo se invoca con"CREATED"y"APPROVED". El email de reembolso está preparado y sin conectar. - Borrar una devolución no está expuesto.
actions/returns/delete.tsy/api/returns/deleteexisten, y la columna de acciones pasadeleteUrl, pero no pasashowDelete, así que la opción no se renderiza. Sí funciona para métodos y motivos, que sí lo pasan. - El submenú de estados de
Actionses UI muerta. El bloqueshowStatuspinta tres opciones (Reembolsado, Aprobado, Cancelado) sin ningúnonClick, y el único sitio que pasa la prop lo hace conshowStatus={false}. DialogDeleteMethodse usa también para borrar motivos. El nombre y su ubicación (components/methods/) no reflejan que es el diálogo de borrado genérico delActionscompartido.
Reglas de negocio codificadas
- SKU de gastos de envío hardcodeado.
itemIsShippingCost()compara contra"S00233"enlib/utils.ts, igual que en el portal del cliente. Aparece en el cálculo del precio final, en la matriz de editabilidad, en el alta manual, en los payloads de journey y en el PDF. - Almacén de Grecia hardcodeado.
GREECE_COUNTRY_CODE = "GR"yGREECE_WAREHOUSE_ID = 61: los pedidos griegos se asignan siempre al almacén 61, ignorando el del pedido. Si ese almacén cambia de id, hay que tocar código. - Identificadores de estado numéricos en el código.
calculateFinalPriceexcluye las líneas conid_return_line_status !== 5(CANCELLED) ygetValidItemsToSendApprovalMailfiltra por=== 4(APPROVED), en lugar de resolverlos por nombre contra el catálogo como hace el resto del proyecto. Si esos ids cambiaran en robo-api, los cálculos fallarían en silencio. formatCurrencyadmite hasta 3 decimales por defecto (maximumFractionDigits: 3), lo que puede producir importes con tres decimales en tablas y CSV.
Patrones frágiles
getFilteredLineStatusesllama a un hook sin ser un hook. La función (enlib/return-utils.ts) haceuseContext(RoleContext)en su interior pese a no llamarseuse*. Funciona porque se invoca de forma incondicional durante el render deReturnContent, pero infringe las reglas de los hooks: llamarla dentro de una condición o de un bucle rompería el render.- Inconsistencia en los nombres del filtro de fechas.
parseFiltersemitedate_from/date_tocuando hay rango completo ydateFrom/dateTo(camelCase) cuando falta una de las dos, conundefinedinterpolado en la URL. La segunda rama es casi con seguridad un residuo: robo-api solo entiende una de las dos convenciones. - La exportación a CSV no tiene tope. El bucle
while (!last)recorre el listado completo en páginas de 100. Sin filtros, una exportación puede tardar mucho y llegar a agotar el tiempo de la petición; elcatchgenérico devolvería un500con"Error al exportar CSV"sin distinguir la causa. - Doble guardado en el journey de aprobación. Enviar el email de aprobación implica un
PUTextra para marcaris_mail_approval_sent. Si ese segundo guardado falla, el email ya se envió y el flag no queda escrito, por lo que en la siguiente edición se reenviaría. editReturn(data: any)es la única acción sin tipar, precisamente la del objeto más complejo del dominio.
Calidad y proceso
- No hay tests en el repositorio (ni unitarios ni E2E). El markup expone atributos
data-test-iden los puntos clave, pensados para automatización externa; conviene mantenerlos al refactorizar. @typescript-eslint/no-unused-varsestá desactivado eneslint.config.mjs, así que imports y variables muertas no se detectan.- El pipeline no ejecuta
lintni tests. Un error de TypeScript sí detiene el build de la imagen, pero nada más. next.config.tsconserva comentados los bloqueseslint.ignoreDuringBuildsytypescript.ignoreBuildErrors. Están desactivados, que es lo correcto; se dejan como recordatorio de que no deben activarse.- La imagen no usa
output: "standalone", así que la etaparunnercopianode_modulescompleto. Activarlo reduciría bastante el tamaño de la imagen. npm ci --legacy-peer-depses necesario para instalar: hay conflictos de peer dependencies sin resolver en el árbol actual.- El
README.mdes el decreate-next-app, sin adaptar: habla de Vercel y delocalhost:3000cuando el proyecto corre en el puerto 80 y se despliega en GKE. - No hay
.env.example. Las 19 variables hay que deducirlas del código..env.localestá correctamente ignorado por git. - El despliegue tiene corte de servicio: el pipeline borra el
Deploymenty espera a que mueran los pods (estrategiaRecreate, 1 réplica). - La UI está en español fijo.
constants/dictionary.tses un diccionario plano sin framework de i18n; internacionalizar el back-office implicaría reescribir esa capa. No confundirlo con el contenido que administra, que sí es multiidioma.