Reemplaza al addon .NET Framework (SolucionLibros). Genera Devoluciones de mercancías enlazadas a sus Entradas por compra a partir de un listado pegado desde Excel. - libs/shared (@dp/shared): modelos y lógica de dominio pura (FIFO, consolidación por proveedor, parseo del portapapeles), portada del addon. - apps/api (NestJS): cliente del Service Layer con cola por sesión, login con la sesión de SAP, metadatos (UDT/UDF/UDO) y botón Reconstruir, catálogos, planificaciones (pegar, procesar, reprocesar, marcar líneas, crear Devoluciones, cerrar), listado paginado con filtros y descarga de PDF de la Guía de despacho desde BES. Sirve también la web compilada, con HTTPS opcional. - apps/web (Angular): login, Configuración → Metadatos y Planificaciones (listado, nueva, detalle). - deploy/windows y scripts/publicar.mjs: despliegue como servicio de Windows con WinSW. Verificado contra el Service Layer de SQL Server. Sin probar aún: SAP HANA y la instalación del servicio en Windows (ver CLAUDE.md, sección "Sin verificar"). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
42 lines
1.8 KiB
TypeScript
42 lines
1.8 KiB
TypeScript
import { extname, join } from 'node:path';
|
|
import type { NestExpressApplication } from '@nestjs/platform-express';
|
|
import type { NextFunction, Request, Response } from 'express';
|
|
|
|
/** Nombres con hash de Angular (main-HFVHQMEH.js, styles-RS7YTGMB.css): no cambian, se pueden cachear un año. */
|
|
const CON_HASH = /-[A-Z0-9]{8}\.(js|css)$/;
|
|
|
|
/**
|
|
* Cabeceras de seguridad básicas. HSTS solo si la API atiende por HTTPS: con HTTP obligaría al
|
|
* navegador a usar un HTTPS que no existe.
|
|
*/
|
|
export function cabecerasSeguridad(https: boolean) {
|
|
return (_req: Request, res: Response, next: NextFunction): void => {
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
res.setHeader('Referrer-Policy', 'same-origin');
|
|
if (https) res.setHeader('Strict-Transport-Security', 'max-age=31536000');
|
|
next();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* La API sirve también la web compilada, así que todo queda en el mismo origen (sin CORS y con la
|
|
* cookie de sesión SameSite=strict). Las rutas de Angular (sin extensión y fuera de /api) devuelven
|
|
* index.html; un archivo que no existe responde 404 en lugar de index.html.
|
|
*/
|
|
export function servirWeb(app: NestExpressApplication, carpeta: string): void {
|
|
app.useStaticAssets(carpeta, {
|
|
index: false,
|
|
setHeaders: (res, ruta) => {
|
|
res.setHeader('Cache-Control', CON_HASH.test(ruta) ? 'public, max-age=31536000, immutable' : 'no-cache');
|
|
},
|
|
});
|
|
|
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
const esRutaDeLaApp = (req.method === 'GET' || req.method === 'HEAD') && !req.path.startsWith('/api') && extname(req.path) === '';
|
|
if (!esRutaDeLaApp) return next();
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.sendFile(join(carpeta, 'index.html'), (error) => error && next(error));
|
|
});
|
|
}
|