metis-desk/docs/catalogo-global-de-marcas.md

85 lines
10 KiB
Markdown
Raw Normal View History

2026-10-07 16:08:48 +00:00
# Catálogo global de marcas
## Finalidad y alcance
Marca identifica al fabricante de un activo; no es una plantilla ni un modelo comercial. El catálogo es global y común a todas las compañías. Por ahora no hay marcas particulares ni anulaciones por compañía, roles nuevos o catálogo de modelos comerciales.
2026-10-09 10:36:05 +00:00
Cada marca tiene UUID v7 estable, nombre de hasta 100 caracteres, estado activo/inactivo, origen de serie/personalizado y logotipo SVG opcional. Los nombres se normalizan sin espacios exteriores y son únicos también entre marcas inactivas, sin distinguir mayúsculas o acentos.
2026-10-07 16:08:48 +00:00
Se incluyen 24 marcas habituales: Acer, Apple, ASUS, Brother, Canon, Clónico, Dell, Epson, HP, Huawei, Intel, Kyocera, Lenovo, LG, Logitech, Microsoft, MikroTik, MSI, QNAP, Samsung, Synology, TP-Link, Ubiquiti y Zebra. Clónico sirve para equipos montados con componentes de distintos fabricantes.
## Operaciones
- Añadir marcas globales personalizadas.
- Renombrar y activar/desactivar marcas personalizadas.
- Activar/desactivar marcas de serie, conservando su nombre protegido.
2026-10-09 10:36:05 +00:00
- Añadir, sustituir o quitar el logotipo de cualquier marca, incluidas las de serie.
- Eliminar marcas personalizadas sin referencias en activos ni revisiones, previa confirmación. Las marcas de serie no se eliminan; no hay conversión de origen.
- Una referencia histórica impide borrar la marca aunque ya no la use ningún activo. En ese caso se puede desactivar, sin modificar activos ni historial.
2026-10-07 16:08:48 +00:00
- Una marca desactivada queda fuera de nuevas selecciones. Los activos que ya la tienen pueden conservarla, cambiarla o retirarla.
- Marca continúa siendo opcional, salvo que una plantilla marque expresamente este campo obligatorio.
El usuario interno autenticado gestiona el catálogo desde Catálogos globales → Marcas. Los cambios están protegidos por sesión y CSRF. Las personas de compañías no acceden a esta pantalla.
## Interfaz y componentes
2026-10-09 10:36:05 +00:00
BrandCatalogPage utiliza EntityListHeader y EntityTable: título/subtítulo y alta en cabecera, búsqueda y filtro de estado, resultados y configuración, tabla y paginación. Columnas Logotipo, Nombre, Estado, Origen y Acciones. Altas y edición mediante FormDialog; BrandForm coloca ContextHelp inmediatamente antes de Guardar/Cancelar.
BrandLogo presenta el SVG como una imagen con dimensiones reservadas y texto alternativo; nunca lo inserta como HTML. BrandLogoField permite seleccionar un archivo, previsualizarlo y quitarlo. El formulario conserva sus cambios hasta guardar, bloquea envíos durante la lectura y no guarda tras una selección inválida. Cancelar no modifica el catálogo. Los mensajes y controles mantienen español e inglés.
2026-10-07 16:08:48 +00:00
En un activo, el campo de serie Marca usa BrandSelect, adaptador funcional de SelectWithAction. El catálogo identifica ese campo por stockKey=field:brand y publica referenceCatalog=brands; no se detecta por nombre, por lo que campos personalizados homónimos y campos de recursos conservan su significado.
El botón de añadir abre el mismo BrandForm, mantiene el borrador del activo y selecciona el nuevo UUID. En este contexto la marca se crea activa. Cancelar el activo no elimina una marca que se haya guardado. Se respeta el límite existente de una sola alta anidada. Carga, error, reintento, etiqueta accesible y español/inglés siguen los componentes existentes.
La eliminación aparece únicamente en las acciones de marcas personalizadas. DeleteBrandDialog, en `web/brands/delete/ui`, confirma la marca y mantiene los errores dentro del modal. Cancelar no modifica datos; durante la petición se bloquean acciones duplicadas y cierre. Tras el éxito se actualiza e invalida únicamente la consulta de marcas, también compartida por los selectores.
2026-10-07 16:08:48 +00:00
La biblioteca genérica sigue independiente del dominio; consultas, DTO y reglas de marcas permanecen en el módulo assets.
## Dominio, persistencia e historial
2026-10-09 10:36:05 +00:00
Brand es una raíz AggregateRoot, con Name y BrandLogo como Value Objects, Maybe para la ausencia de logotipo y UniqueID como identidad. mapBrandInput valida la entrada y construye sus propiedades. BrandUseCases consulta el catálogo y aplica unicidad; el agregado protege origen/nombre y conserva identidad. Actualizaciones idénticas devuelven la misma instancia.
El SVG se almacena en asset_brands.logoSvg (TEXT nullable) y se guarda atómicamente con nombre y estado. Se admite SVG vectorial estático con viewBox y hasta 32 KiB UTF-8, 1000 elementos y 32 niveles. Se validan XML, namespace, elementos y atributos mediante una lista permitida. Se rechazan scripts, handlers, animaciones, foreignObject, imágenes incrustadas, DTD, entidades personalizadas, hojas de estilo y referencias externas. Se admiten degradados y recortes con referencias locales. El usuario debe aportar el archivo sin fondo; la validación no elimina figuras de fondo de un SVG. Los formatos que exportan metadatos o CSS deben guardarse como SVG simple.
Se añade saxes 6.0.0 para analizar XML real: Node no tiene parser XML nativo y una expresión regular no valida su estructura ni las entidades. El mismo schema se utiliza en entrada HTTP, formulario, respuesta y reconstrucción persistida. El renderizado usa una URL de datos local y no necesita descargas externas.
2026-10-07 16:08:48 +00:00
IBrandRepository define el puerto. BrandPersistenceMapper reconstruye y serializa los valores. SequelizeBrandRepository guarda en transacción, con un bloqueo de catálogo y restricción única en MariaDB. El inventario adquiere ese mismo bloqueo y lee las marcas con bloqueo de filas antes de validar nuevas asociaciones, evitando seleccionar una marca desactivada concurrentemente.
AssetValues incorpora brandId opcional como Maybe<UniqueID>. Activos y revisiones tienen clave foránea nullable con borrado restringido. El servidor comprueba disponibilidad y presencia del campo de serie en la especificación del activo, y escribe el nombre canónico de la marca como característica. Cada revisión conserva su UUID de marca y una instantánea de su nombre: renombrar el catálogo no reescribe el historial. Al guardar de nuevo el activo se captura el nombre actual.
2026-10-10 15:57:05 +00:00
## Inicialización del catálogo
2026-10-07 16:08:48 +00:00
2026-10-10 15:57:05 +00:00
migrateBrands crea tablas y semillas de manera idempotente, sin reactivar marcas desactivadas ni sustituir sus UUID. migrateInventory declara las referencias opcionales al crear las tablas.
2026-10-07 16:08:48 +00:00
2026-10-09 10:36:05 +00:00
La migración añade logoSvg y brand_catalog_lock.logosImported. Incorpora una sola vez los 23 SVG de fabricantes conocidos donde no haya un logotipo; Clónico queda sin imagen porque no es una marca. No sobrescribe logotipos existentes. El marcador transaccional evita reponer imágenes eliminadas o sustituidas después. Las altas iniciales de marcas de serie reciben su SVG local. Las fuentes se registran en [Fuentes de logotipos de marcas](fuentes-de-logotipos-de-marcas.md).
2026-10-07 16:08:48 +00:00
## HTTP
- GET /api/asset-brands: listado global.
2026-10-09 10:36:05 +00:00
- POST /api/asset-brands: alta con { values: { name, active, logoSvg } }.
2026-10-07 16:08:48 +00:00
- POST /api/asset-brands/:id/update: actualización del mismo contrato.
- POST /api/asset-brands/:id/delete: eliminación de una marca personalizada no referenciada, con cuerpo vacío `{}` y respuesta BrandResponseSchema de la marca eliminada.
2026-10-07 16:08:48 +00:00
Respuestas BrandsResponseSchema y BrandResponseSchema; parámetros, cuerpo y consultas estrictos. Duplicados, protección de serie y BRAND_IN_USE devuelven 409, entrada inválida 400 y marca ausente 404. La eliminación conserva autenticación y CSRF; no introduce nuevos permisos. Comprobar referencias y borrar se hace en una transacción, conservando las claves foráneas RESTRICT. Primero se bloquea el catálogo global de activos y después el catálogo de marcas, en el mismo orden que las escrituras del inventario, para evitar asociaciones concurrentes y lecturas con instantáneas anteriores al borrado en MariaDB. Repetir la eliminación devuelve 404 sin efectos adicionales; las migraciones no recrean marcas personalizadas eliminadas.
2026-10-07 16:08:48 +00:00
2026-10-09 10:36:05 +00:00
logoSvg contiene el SVG o null para ausencia/eliminación. Si se omite en una actualización, se conserva el logotipo: cambiar el estado o el nombre no debe borrarlo de forma implícita. En altas, la omisión significa ausencia. Las respuestas incluyen siempre logoSvg, nullable. Los permisos de sesión y CSRF existentes también protegen este campo.
2026-10-07 16:08:48 +00:00
## Verificación
Pruebas de dominio y mappers: normalización, límites, UUID v7, origen protegido, estado, idempotencia y persistencia inválida. Pruebas de interfaz: traducción, estructura común, modal, ayuda inferior, nombre de serie protegido, valores anteriores/inactivos y límite de altas anidadadas.
La integración HTTP/MariaDB cubre autenticación y CSRF, unicidad concurrente, uso global en compañías diferentes, referencias válidas, nombre canónico, conservación de marca inactiva, revisión histórica tras renombrado, importación sin cambios del historial y semillas idempotentes.
La eliminación se prueba en marcas personalizadas activas e inactivas, con entradas inválidas, CSRF, protección de serie, referencias actuales e históricas, repetición, reutilización del nombre y asociación concurrente de un activo. No se borran marcas de la base de desarrollo durante las pruebas.
2026-10-07 16:08:48 +00:00
### Resultado de la entrega (7 de octubre de 2026)
- pnpm typecheck: correcto, 10 tareas.
- pnpm test: correcto, 202 pruebas; incluye integración HTTP/MariaDB en metis_desk_test.
- pnpm build: correcto, 2 tareas; se mantiene el aviso previo del tamaño del paquete web.
- pnpm lint / pnpm check: ejecutados; conservan los 25/72 errores anteriores, fuera de los archivos modificados. Los 35 archivos de código/contratos modificados o nuevos pasan la comprobación dirigida sin avisos.
- Navegador: catálogo y marcas importadas visibles, búsqueda y limpieza, filtro de estado y vacío, edición protegida, desplegable del activo, alta relacionada y cancelación conservando selección/borrador. Verificados español/inglés y anchura móvil sin desbordamiento horizontal de página.
2026-10-10 15:57:05 +00:00
- La revisión visual no guardó activos ni marcas de prueba en la base de desarrollo. La inicialización incorpora las semillas; las mutaciones de prueba se ejecutan sobre la base dedicada.