6.8 KiB
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.
Cada marca tiene UUID v7 estable, nombre de hasta 100 caracteres, estado activo/inactivo y origen de serie/personalizado. Los nombres se normalizan sin espacios exteriores y son únicos también entre marcas inactivas, sin distinguir mayúsculas o acentos.
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.
- No hay borrado físico ni conversión de origen.
- 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
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 Nombre, Estado, Origen y Acciones. Altas y edición mediante FormDialog; BrandForm coloca ContextHelp inmediatamente antes de Guardar/Cancelar.
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 biblioteca genérica sigue independiente del dominio; consultas, DTO y reglas de marcas permanecen en el módulo assets.
Dominio, persistencia e historial
Brand es una raíz AggregateRoot, con Name como Value Object 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.
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. 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.
Migración compatible
migrateBrands crea tablas y semillas de manera idempotente, sin reactivar marcas desactivadas ni sustituir sus UUID. migrateInventory añade las referencias nullable explícitamente.
importLegacyBrands importa una única vez los nombres válidos que encuentre en el campo de serie de los activos existentes, como marcas personalizadas salvo coincidencia con una de serie. No modifica el JSON de activos, no crea revisiones y no toca el historial. Los textos anteriores permanecen visibles como Valor anterior y pueden conservarse o sustituirse por una selección explícita; no se asocian automáticamente por coincidencia de nombre. Valores antiguos que no cumplan el límite del catálogo permanecen en su ficha.
Un marcador transaccional evita que reinicios posteriores vuelvan a crear nombres de marcas personalizadas ya renombradas. Las semillas mantienen los nombres originales protegidos. El catálogo de campos/plantillas de recursos continúa separado.
HTTP
- GET /api/asset-brands: listado global.
- POST /api/asset-brands: alta con { values: { name, active } }.
- POST /api/asset-brands/:id/update: actualización del mismo contrato.
Respuestas BrandsResponseSchema y BrandResponseSchema; parámetros, cuerpo y consultas estrictos. Duplicados y protección de serie devuelven 409, entrada inválida 400 y marca ausente 404. No se incorporan endpoints de eliminación ni nuevos permisos.
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.
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.
- La revisión visual no guardó activos ni marcas de prueba en la base de desarrollo. Las migraciones incorporan las semillas y nombres previos; las mutaciones de prueba se ejecutan sobre la base dedicada.