diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json
new file mode 100644
index 00000000..09efbb05
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json
@@ -0,0 +1,14 @@
+{
+ "apps/server/dist/index.js": "391a1d3f4ae0d5f4231ac487931a93c81b0cc0eb221f33f04ce785983a925507",
+ "apps/server/dist/index.js.map": "bddd81d81d134df7ef35ab4da1dca82d61f0a3bbd59bbc5751a08527affcf315",
+ "apps/web/dist/assets/geist-mono-latin-ext-variable-Bwz-egvJ.woff2": "1a189eb997c3e2ece68373e387afaec9e8617424186c4b1ab3cff7c54ba6223b",
+ "apps/web/dist/assets/geist-mono-latin-variable-XN7g48iV.woff2": "684ad5b531f81d43c1e8c7038262d5db7cdc1f68006e04d6c7769efa8d33c8cc",
+ "apps/web/dist/assets/index-DsAXQkUa.js": "9e9d770b82597b1a7f8c2aa812d9874aa3e84f8a35ddba9400a941b6e1a73f5b",
+ "apps/web/dist/assets/index-ZFzBcHiV.css": "c8a8acc5a0ef0f1891efb7fcab2ca6fa100458ad27df938dd374aee081e7200b",
+ "apps/web/dist/assets/inter-variable-DiVDrmQJ.woff2": "693b77d4f32ee9b8bfc995589b5fad5e99adf2832738661f5402f9978429a8e3",
+ "apps/web/dist/assets/roboto-serif-latin-ext-variable-B9BEn9Tg.woff2": "90626de112742b75e72b27865650de397eccf6845b22b577b0283b9cbfda83fc",
+ "apps/web/dist/assets/roboto-serif-latin-variable-CusprKnR.woff2": "a32deb82e55d4bcb083e09dbb4da3198011ce0e9f919877179e8c2bca23a9042",
+ "apps/web/dist/assets/workplace-map-canvas-D7df2W27.js": "e74b72073a0149f70e3700809214b929bd88841577b63266199849b160ee9859",
+ "apps/web/dist/assets/workplace-map-canvas-DLioOiRN.css": "b0cb7bd6fec5951c9c8f5808821438206604b1629a912c96009ac427f38e7d69",
+ "apps/web/dist/index.html": "a34046a30a628b0f6a7d9d44d6951885ed130daced14c2de7dd683dffee3daf0"
+}
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json
new file mode 100644
index 00000000..b2683729
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json
@@ -0,0 +1,115 @@
+{
+ "recordedAtUtc": "2026-10-26T11:01:50.867076+00:00",
+ "candidateCommit": "6992a4ac745acf6b474a0f22d1a859b861119452",
+ "candidateBranch": "main",
+ "initialWorkingTree": "clean",
+ "verdict": "NO PREPARADO",
+ "blockers": [
+ "B-01: compiled server duplicate createRequire",
+ "B-02: no Secure cookie in tested TLS-to-HTTP scenario"
+ ],
+ "newFrontendVisualValidation": "pending",
+ "newResourcesTests": {
+ "pass": 124,
+ "fail": 0
+ },
+ "newTableTests": {
+ "pass": 9,
+ "fail": 0
+ },
+ "goSourcesIdentical": false,
+ "candidateChangesComparedUsingGitObjects": true,
+ "concurrentChangesNotCertified": true,
+ "productionConnected": false,
+ "databaseConnected": false,
+ "migrationsExecuted": false,
+ "productionActivated": false,
+ "commands": [
+ {
+ "command": "pnpm exec turbo run build --force",
+ "exitCode": 0,
+ "log": "technical/build.log"
+ },
+ {
+ "command": "node --check apps/server/dist/index.js",
+ "exitCode": 1,
+ "log": "technical/node-check.log"
+ },
+ {
+ "command": "Diagnóstico proxy-session-diagnostic.mjs en loopback, sin BD",
+ "exitCode": 0,
+ "log": "technical/proxy.log"
+ },
+ {
+ "command": "pnpm --filter @metis/resources test",
+ "exitCode": 0,
+ "log": "technical/resources.log"
+ },
+ {
+ "command": "Pruebas entity-column-visibility.test.tsx con aislamiento none",
+ "exitCode": 0,
+ "log": "technical/table.log"
+ },
+ {
+ "command": "pnpm --filter @metis/resources typecheck",
+ "exitCode": 0,
+ "log": "technical/resources-types.log"
+ },
+ {
+ "command": "pnpm --filter @metis/web typecheck",
+ "exitCode": 0,
+ "log": "technical/web-types.log"
+ },
+ {
+ "command": "pnpm --filter @repo/rdx-ui typecheck",
+ "exitCode": 0,
+ "log": "technical/rdx-ui-types.log"
+ },
+ {
+ "command": "Biome dirigido, 15 fuentes",
+ "exitCode": 0,
+ "log": "technical/biome.log"
+ },
+ {
+ "command": "ESLint dirigido, 15 fuentes",
+ "exitCode": 0,
+ "log": "technical/eslint.log"
+ },
+ {
+ "command": "pnpm typecheck",
+ "exitCode": 2,
+ "log": "technical/types.log"
+ },
+ {
+ "command": "pnpm lint",
+ "exitCode": 1,
+ "log": "technical/lint.log"
+ },
+ {
+ "command": "pnpm check",
+ "exitCode": 1,
+ "log": "technical/check.log"
+ }
+ ],
+ "files": {
+ "builds.json": "f3c2db9da0f6d88c312c9111d37f8380b66ae19eb5fdd92e18f851e9f32aad66",
+ "technical/biome.log": "39f4d8fe1255abee2e51cadc52a3613b308d03c4e646cd271c7ed6df407fbab6",
+ "technical/build.log": "3ccaef5a89dc97a321913bb345a2c70b2e8ac36a679e9bff08c50caa952adfce",
+ "technical/check.log": "15ad425f82d431d4a39aa0adf303c90a45a3f983b4c723eb10d26952cc5217d3",
+ "technical/eslint.log": "2bd73ff0eb7555f4a8aac3ac9f64061e3b9f5bc50c0560cf8afd987ecf0fd00b",
+ "technical/lint.log": "90b5583555e49b2cd54f6b284dd47df4086dcb0e85d50a688b65c7a0afb5c4b2",
+ "technical/node-check.log": "8257d56c85b4657dfaaedc60781e41646a526c3bfc5b7e9ecc351f321f6eae8e",
+ "technical/proxy.log": "b70d5360741b61f26b7f9a1366481b22acb55704f8a0d567b438686b614ccc69",
+ "technical/rdx-ui-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
+ "technical/resources-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
+ "technical/resources.log": "1aa100050d30b4a19d43044fb5bf0b2b4e14fec26914cdd824bdd58e10bd663f",
+ "technical/table.log": "25cd0182b29d78b97e8f35842e9a6ea1724f70bd2da65d058719f291b5d097ca",
+ "technical/types.log": "6d534229b49c3f6329c466d76fd6cbce8bbf3afe5f22d35a67656ba3648d4fba",
+ "technical/web-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
+ "traceability.json": "4a006707c2705419377a2ea59f71a7c31e27a478b97785fc31793df56e28ab20"
+ },
+ "report": {
+ "path": "docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md",
+ "sha256": "a9318a2168479b94c7f33f56c84543eb0baa7539b2d44d2eaea4a55f96c59cf6"
+ }
+}
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log
new file mode 100644
index 00000000..5ba833af
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log
@@ -0,0 +1 @@
+Checked 15 files in 15ms. No fixes applied.
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log
new file mode 100644
index 00000000..7b40a1d6
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log
@@ -0,0 +1,47 @@
+• turbo 2.9.14
+
+ • Packages in scope: @metis/assets, @metis/companies, @metis/core, @metis/identity, @metis/relationship-contracts, @metis/resources, @metis/server, @metis/web, @repo/rdx-ddd, @repo/rdx-ui, @repo/rdx-utils, @repo/shadcn-ui, @repo/typescript-config
+ • Running build in 13 packages
+ • Remote caching disabled
+
+@metis/server:build: cache bypass, force executing cd9cb21b866f866f
+@metis/web:build: cache bypass, force executing ca8ca295b02da9d1
+@metis/web:build: $ vite build
+@metis/server:build: $ tsup --config tsup.config.ts
+@metis/web:build: vite v8.3.0 building client environment for production...
+@metis/web:build: transforming...
+@metis/server:build: CLI Building entry: src/index.ts
+@metis/server:build: CLI Using tsconfig: tsconfig.json
+@metis/server:build: CLI tsup v8.5.1
+@metis/server:build: CLI Using tsup config: /home/david/dev/metis-desk/apps/server/tsup.config.ts
+@metis/server:build: CLI Target: node24
+@metis/server:build: CLI Cleaning output folder
+@metis/server:build: ESM Build start
+@metis/server:build: ESM dist/index.js 1.70 MB
+@metis/server:build: ESM dist/index.js.map 3.22 MB
+@metis/server:build: ESM ⚡️ Build success in 431ms
+@metis/web:build: ✓ 10068 modules transformed.
+@metis/web:build: rendering chunks...
+@metis/web:build: computing gzip size...
+@metis/web:build: dist/index.html 0.54 kB │ gzip: 0.35 kB
+@metis/web:build: dist/assets/geist-mono-latin-ext-variable-Bwz-egvJ.woff2 14.69 kB
+@metis/web:build: dist/assets/geist-mono-latin-variable-XN7g48iV.woff2 23.12 kB
+@metis/web:build: dist/assets/roboto-serif-latin-ext-variable-B9BEn9Tg.woff2 49.84 kB
+@metis/web:build: dist/assets/roboto-serif-latin-variable-CusprKnR.woff2 66.10 kB
+@metis/web:build: dist/assets/inter-variable-DiVDrmQJ.woff2 352.24 kB
+@metis/web:build: dist/assets/workplace-map-canvas-DLioOiRN.css 15.41 kB │ gzip: 2.56 kB
+@metis/web:build: dist/assets/index-ZFzBcHiV.css 253.08 kB │ gzip: 38.86 kB
+@metis/web:build: dist/assets/workplace-map-canvas-D7df2W27.js 227.75 kB │ gzip: 74.26 kB
+@metis/web:build: dist/assets/index-DsAXQkUa.js 1,820.59 kB │ gzip: 510.82 kB
+@metis/web:build:
+@metis/web:build: [plugin builtin:vite-reporter]
+@metis/web:build: (!) Some chunks are larger than 500 kB after minification. Consider:
+@metis/web:build: - Using dynamic import() to code-split the application
+@metis/web:build: - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
+@metis/web:build: - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
+@metis/web:build: ✓ built in 1.05s
+
+ Tasks: 2 successful, 2 total
+Cached: 0 cached, 2 total
+ Time: 1.788s
+
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log
new file mode 100644
index 00000000..d800073a
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log
@@ -0,0 +1,1379 @@
+$ biome check .
+.agents/skills/shadcn/assets/showcase/color-pair.tsx:81:17 lint/style/noNegationElse FIXABLE ━━━━━━━━━━
+
+ ! Invert blocks when performing a negation test.
+
+ 79 │ }, [])
+ 80 │
+ > 81 │ const level = !info
+ │ ^^^^^
+ > 82 │ ? ""
+ ...
+ > 88 │ ? "AA Large"
+ > 89 │ : "Fail"
+ │ ^^^^^^^^
+ 90 │
+ 91 │ return (
+
+ i Safe fix: Invert the condition and the blocks.
+
+ 79 79 │ }, [])
+ 80 80 │
+ 81 │ - ··const·level·=·!info
+ 82 │ - ····?·""
+ 83 │ - ····:·info.ratio·>=·7
+ 81 │ + ··const·level·=·info
+ 82 │ + ····?·info.ratio·>=·7
+ 84 83 │ ? "AAA"
+ 85 84 │ : info.ratio >= 4.5
+ ······· │
+ 87 86 │ : info.ratio >= 3
+ 88 87 │ ? "AA Large"
+ 89 │ - ··········:·"Fail"
+ 88 │ + ··········:·"Fail"
+ 89 │ + ····:·""
+ 90 90 │
+ 91 91 │ return (
+
+
+.agents/skills/shadcn/assets/showcase/color-pair.tsx:98:7 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━
+
+ × The attributes are not sorted.
+
+ 96 │ )}
+ 97 │ >
+ > 98 │
99 │ ref={ref}
+ ...
+ > 104 │ className="flex h-24 items-end justify-between p-4"
+ > 105 │ >
+ │ ^
+ 106 │
Aa
+ 107 │ {info && (
+
+ i Safe fix: Sort the JSX props.
+
+ 97 97 │ >
+ 98 98 │
+ 106 106 │
Aa
+
+
+.agents/skills/shadcn/assets/showcase/color-pair.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 1 │ - import·*·as·React·from·"react"
+ 1 │ + import·*·as·React·from·"react";
+ 2 2 │
+ 3 │ - import·{·cn·}·from·"@/lib/utils"
+ 3 │ + import·{·cn·}·from·"@/lib/utils";
+ 4 4 │
+ 5 5 │ // Resolves any CSS color (hex, rgb, oklch, color-mix) to sRGB by painting one pixel.
+ 6 6 │ // Translucent colors are composited over the backdrop, as the browser would.
+ 7 7 │ function toRgb(color: string, backdrop = "#fff") {
+ 8 │ - ··const·canvas·=·document.createElement("canvas")
+ 9 │ - ··canvas.width·=·canvas.height·=·1
+ 10 │ - ··const·context·=·canvas.getContext("2d",·{·willReadFrequently:·true·})
+ 8 │ + ··const·canvas·=·document.createElement("canvas");
+ 9 │ + ··canvas.width·=·canvas.height·=·1;
+ 10 │ + ··const·context·=·canvas.getContext("2d",·{·willReadFrequently:·true·});
+ 11 11 │ if (!context) {
+ 12 │ - ····return·null
+ 12 │ + ····return·null;
+ 13 13 │ }
+ 14 │ - ··context.fillStyle·=·backdrop
+ 15 │ - ··context.fillRect(0,·0,·1,·1)
+ 16 │ - ··context.fillStyle·=·color
+ 17 │ - ··context.fillRect(0,·0,·1,·1)
+ 18 │ - ··const·[r,·g,·b]·=·context.getImageData(0,·0,·1,·1).data
+ 19 │ - ··return·[r,·g,·b]
+ 14 │ + ··context.fillStyle·=·backdrop;
+ 15 │ + ··context.fillRect(0,·0,·1,·1);
+ 16 │ + ··context.fillStyle·=·color;
+ 17 │ + ··context.fillRect(0,·0,·1,·1);
+ 18 │ + ··const·[r,·g,·b]·=·context.getImageData(0,·0,·1,·1).data;
+ 19 │ + ··return·[r,·g,·b];
+ 20 20 │ }
+ 21 21 │
+ 22 22 │ function toHex(rgb: number[]) {
+ 23 │ - ··return·`#${rgb.map((value)·=>·value.toString(16).padStart(2,·"0")).join("")}`
+ 23 │ + ··return·`#${rgb.map((value)·=>·value.toString(16).padStart(2,·"0")).join("")}`;
+ 24 24 │ }
+ 25 25 │
+ 26 26 │ function luminance([r, g, b]: number[]) {
+ 27 27 │ const [R, G, B] = [r, g, b].map((value) => {
+ 28 │ - ····const·channel·=·value·/·255
+ 29 │ - ····return·channel·<=·0.03928
+ 30 │ - ······?·channel·/·12.92
+ 31 │ - ······:·((channel·+·0.055)·/·1.055)·**·2.4
+ 32 │ - ··})
+ 33 │ - ··return·0.2126·*·R·+·0.7152·*·G·+·0.0722·*·B
+ 28 │ + ····const·channel·=·value·/·255;
+ 29 │ + ····return·channel·<=·0.03928·?·channel·/·12.92·:·((channel·+·0.055)·/·1.055)·**·2.4;
+ 30 │ + ··});
+ 31 │ + ··return·0.2126·*·R·+·0.7152·*·G·+·0.0722·*·B;
+ 34 32 │ }
+ 35 33 │
+ 36 34 │ function contrast(a: number[], b: number[]) {
+ 37 │ - ··const·[light,·dark]·=·[luminance(a),·luminance(b)].sort((x,·y)·=>·y·-·x)
+ 38 │ - ··return·(light·+·0.05)·/·(dark·+·0.05)
+ 35 │ + ··const·[light,·dark]·=·[luminance(a),·luminance(b)].sort((x,·y)·=>·y·-·x);
+ 36 │ + ··return·(light·+·0.05)·/·(dark·+·0.05);
+ 39 37 │ }
+ 40 38 │
+ ······· │
+ 45 43 │ className,
+ 46 44 │ }: {
+ 47 │ - ··name:·string
+ 48 │ - ··background:·string
+ 49 │ - ··foreground:·string
+ 50 │ - ··className?:·string
+ 45 │ + ··name:·string;
+ 46 │ + ··background:·string;
+ 47 │ + ··foreground:·string;
+ 48 │ + ··className?:·string;
+ 51 49 │ }) {
+ 52 │ - ··const·ref·=·React.useRef
(null)
+ 53 │ - ··const·[info,·setInfo]·=·React.useState<{·ratio:·number;·bg:·string·}·|·null>(
+ 54 │ - ····null
+ 55 │ - ··)
+ 50 │ + ··const·ref·=·React.useRef(null);
+ 51 │ + ··const·[info,·setInfo]·=·React.useState<{·ratio:·number;·bg:·string·}·|·null>(null);
+ 56 52 │
+ 57 53 │ React.useEffect(() => {
+ 58 │ - ····const·element·=·ref.current
+ 54 │ + ····const·element·=·ref.current;
+ 59 55 │ if (!element) {
+ 60 │ - ······return
+ 56 │ + ······return;
+ 61 57 │ }
+ 62 58 │ // Re-measure when the theme class on changes.
+ 63 59 │ const measure = () => {
+ 64 │ - ······const·style·=·getComputedStyle(element)
+ 65 │ - ······const·page·=·getComputedStyle(document.body).backgroundColor
+ 66 │ - ······const·bg·=·toRgb(style.backgroundColor,·page)
+ 60 │ + ······const·style·=·getComputedStyle(element);
+ 61 │ + ······const·page·=·getComputedStyle(document.body).backgroundColor;
+ 62 │ + ······const·bg·=·toRgb(style.backgroundColor,·page);
+ 67 63 │ if (!bg) {
+ 68 │ - ········return
+ 64 │ + ········return;
+ 69 65 │ }
+ 70 │ - ······const·fg·=·toRgb(style.color,·toHex(bg))
+ 66 │ + ······const·fg·=·toRgb(style.color,·toHex(bg));
+ 71 67 │ if (fg) {
+ 72 │ - ········setInfo({·ratio:·contrast(bg,·fg),·bg:·toHex(bg)·})
+ 68 │ + ········setInfo({·ratio:·contrast(bg,·fg),·bg:·toHex(bg)·});
+ 73 69 │ }
+ 74 │ - ····}
+ 75 │ - ····measure()
+ 76 │ - ····const·observer·=·new·MutationObserver(measure)
+ 77 │ - ····observer.observe(document.documentElement,·{·attributes:·true·})
+ 78 │ - ····return·()·=>·observer.disconnect()
+ 79 │ - ··},·[])
+ 70 │ + ····};
+ 71 │ + ····measure();
+ 72 │ + ····const·observer·=·new·MutationObserver(measure);
+ 73 │ + ····observer.observe(document.documentElement,·{·attributes:·true·});
+ 74 │ + ····return·()·=>·observer.disconnect();
+ 75 │ + ··},·[]);
+ 80 76 │
+ 81 77 │ const level = !info
+ ······· │
+ 87 83 │ : info.ratio >= 3
+ 88 84 │ ? "AA Large"
+ 89 │ - ··········:·"Fail"
+ 85 │ + ··········:·"Fail";
+ 90 86 │
+ 91 87 │ return (
+ 92 │ - ····
+ 88 │ + ····
+ 98 89 │
+ 118 │ - ········{info·&&·(
+ 119 │ - ··········
+ 120 │ - ············{info.bg}
+ 121 │ - ··········
+ 122 │ - ········)}
+ 109 │ + ········{info·&&·{info.bg}}
+ 123 110 │
+ 124 111 │
+ 125 │ - ··)
+ 112 │ + ··);
+ 126 113 │ }
+ 127 114 │
+
+
+.agents/skills/shadcn/assets/showcase/preview-states.css format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 11 11 │ }
+ 12 12 │ }
+ 13 │ - @custom-variant·focus-visible·(&:focus-visible,·&[data-preview~="focus"]);
+ 14 │ - @custom-variant·active·(&:active,·&[data-preview~="active"]);
+ 13 │ + @custom-variant·focus-visible·(&:focus-visible,
+ 14 │ + &[data-preview~="focus"]);
+ 15 │ + @custom-variant·active·(&:active,
+ 16 │ + &[data-preview~="active"]);
+ 15 17 │
+
+
+.agents/skills/shadcn/assets/showcase/state-matrix.tsx:31:15 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━
+
+ × The attributes are not sorted.
+
+ 29 │
|
+ 30 │ {states.map((state) => (
+ > 31 │
32 │ key={state}
+ > 33 │ scope="col"
+ > 34 │ className="text-left font-mono text-xs font-normal text-muted-foreground"
+ > 35 │ >
+ │ ^
+ 36 │ {state}
+ 37 │ |
+
+ i Safe fix: Sort the JSX props.
+
+ 30 30 │ {states.map((state) => (
+ 31 31 │
+ 36 36 │ {state}
+
+
+.agents/skills/shadcn/assets/showcase/state-matrix.tsx:44:15 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━
+
+ × The attributes are not sorted.
+
+ 42 │ {rows.map((row) => (
+ 43 │ |
+ > 44 │ | 45 │ scope="row"
+ > 46 │ className="pr-4 text-left font-mono text-xs font-normal text-muted-foreground"
+ > 47 │ >
+ │ ^
+ 48 │ {row}
+ 49 │ |
+
+ i Safe fix: Sort the JSX props.
+
+ 43 43 │
+ 44 44 │
+ 48 48 │ {row}
+
+
+.agents/skills/shadcn/assets/showcase/state-matrix.tsx:1:8 lint/style/useImportType FIXABLE ━━━━━━━━━━
+
+ × All these imports are only used as types.
+
+ > 1 │ import * as React from "react"
+ │ ^^^^^^^^^^^^^^^^^^^^^^^
+ 2 │
+ 3 │ import { cn } from "@/lib/utils"
+
+ i Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
+
+ i Safe fix: Use import type.
+
+ 1 │ import·type·*·as·React·from·"react"
+ │ +++++
+
+.agents/skills/shadcn/assets/showcase/state-matrix.tsx format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 1 │ - import·*·as·React·from·"react"
+ 1 │ + import·*·as·React·from·"react";
+ 2 2 │
+ 3 │ - import·{·cn·}·from·"@/lib/utils"
+ 3 │ + import·{·cn·}·from·"@/lib/utils";
+ 4 4 │
+ 5 │ - const·defaultStates·=·[
+ 6 │ - ··"default",
+ 7 │ - ··"hover",
+ 8 │ - ··"focus",
+ 9 │ - ··"active",
+ 10 │ - ··"disabled",
+ 11 │ - ]·as·const
+ 5 │ + const·defaultStates·=·["default",·"hover",·"focus",·"active",·"disabled"]·as·const;
+ 12 6 │
+ 13 7 │ export function StateMatrix({
+ ····· │
+ 17 11 │ className,
+ 18 12 │ }: {
+ 19 │ - ··rows:·readonly·Row[]
+ 20 │ - ··states?:·readonly·string[]
+ 21 │ - ··render:·(row:·Row,·state:·string)·=>·React.ReactNode
+ 22 │ - ··className?:·string
+ 13 │ + ··rows:·readonly·Row[];
+ 14 │ + ··states?:·readonly·string[];
+ 15 │ + ··render:·(row:·Row,·state:·string)·=>·React.ReactNode;
+ 16 │ + ··className?:·string;
+ 23 17 │ }) {
+ 24 18 │ return (
+ ····· │
+ 56 50 │
+ 57 51 │
+ 58 │ - ··)
+ 52 │ + ··);
+ 59 53 │ }
+ 60 54 │
+ ····· │
+ 62 56 │ export function previewState(state: string) {
+ 63 57 │ if (state === "disabled") {
+ 64 │ - ····return·{·disabled:·true·}
+ 58 │ + ····return·{·disabled:·true·};
+ 65 59 │ }
+ 66 60 │
+ 67 61 │ if (["hover", "focus", "active"].includes(state)) {
+ 68 │ - ····return·{·"data-preview":·state·}
+ 62 │ + ····return·{·"data-preview":·state·};
+ 69 63 │ }
+ 70 64 │
+ 71 │ - ··return·{}
+ 65 │ + ··return·{};
+ 72 66 │ }
+ 73 67 │
+
+
+.agents/skills/shadcn/scripts/showcase-coverage.mjs:4:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━
+
+ × The imports and exports are not sorted.
+
+ 2 │ // Lists installed UI components that no file in the showcase imports.
+ 3 │ // Usage: node showcase-coverage.mjs
+ > 4 │ import { readdirSync, readFileSync, statSync } from "node:fs"
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 5 │ import { basename, extname, join, resolve } from "node:path"
+ 6 │
+
+ i Safe fix: Organize Imports (Biome)
+
+ 1 1 │ #!/usr/bin/env node
+ 2 │ +
+ 2 3 │ // Lists installed UI components that no file in the showcase imports.
+ 3 4 │ // Usage: node showcase-coverage.mjs
+ 4 │ - import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs"
+ 5 │ + import·{·readFileSync,·readdirSync,·statSync·}·from·"node:fs"
+ 5 6 │ import { basename, extname, join, resolve } from "node:path"
+ 6 7 │
+
+
+.agents/skills/shadcn/scripts/showcase-coverage.mjs format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 2 2 │ // Lists installed UI components that no file in the showcase imports.
+ 3 3 │ // Usage: node showcase-coverage.mjs
+ 4 │ - import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs"
+ 5 │ - import·{·basename,·extname,·join,·resolve·}·from·"node:path"
+ 4 │ + import·{·readdirSync,·readFileSync,·statSync·}·from·"node:fs";
+ 5 │ + import·{·basename,·extname,·join,·resolve·}·from·"node:path";
+ 6 6 │
+ 7 │ - const·[uiArg,·...roots]·=·process.argv.slice(2)
+ 7 │ + const·[uiArg,·...roots]·=·process.argv.slice(2);
+ 8 8 │
+ 9 9 │ if (!uiArg || roots.length === 0) {
+ 10 │ - ··console.error("Usage:·node·showcase-coverage.mjs··")
+ 11 │ - ··process.exit(1)
+ 10 │ + ··console.error("Usage:·node·showcase-coverage.mjs··");
+ 11 │ + ··process.exit(1);
+ 12 12 │ }
+ 13 13 │
+ 14 │ - const·uiDir·=·resolve(uiArg)
+ 14 │ + const·uiDir·=·resolve(uiArg);
+ 15 15 │
+ 16 16 │ const components = readdirSync(uiDir)
+ 17 17 │ .filter((file) => [".tsx", ".ts"].includes(extname(file)))
+ 18 │ - ··.map((file)·=>·basename(file,·extname(file)))
+ 18 │ + ··.map((file)·=>·basename(file,·extname(file)));
+ 19 19 │
+ 20 20 │ function walk(path) {
+ 21 21 │ if (statSync(path).isFile()) {
+ 22 │ - ····return·[path]
+ 22 │ + ····return·[path];
+ 23 23 │ }
+ 24 24 │
+ 25 25 │ return readdirSync(path).flatMap((entry) => {
+ 26 │ - ····const·child·=·join(path,·entry)
+ 26 │ + ····const·child·=·join(path,·entry);
+ 27 27 │ // Skip the ui directory so components don't count as using each other.
+ 28 28 │ if (entry === "node_modules" || child === uiDir) {
+ 29 │ - ······return·[]
+ 29 │ + ······return·[];
+ 30 30 │ }
+ 31 │ - ····return·walk(child)
+ 32 │ - ··})
+ 31 │ + ····return·walk(child);
+ 32 │ + ··});
+ 33 33 │ }
+ 34 34 │
+ ····· │
+ 38 38 │ .filter((file) => /\.(tsx?|jsx?)$/.test(file))
+ 39 39 │ .map((file) => readFileSync(file, "utf8"))
+ 40 │ - ··.join("\n")
+ 40 │ + ··.join("\n");
+ 41 41 │
+ 42 │ - const·missing·=·components.filter(
+ 43 │ - ··(name)·=>·!new·RegExp(`/ui/${name}["']`).test(source)
+ 44 │ - )
+ 42 │ + const·missing·=·components.filter((name)·=>·!new·RegExp(`/ui/${name}["']`).test(source));
+ 45 43 │
+ 46 │ - console.log(
+ 47 │ - ··`${components.length·-·missing.length}/${components.length}·components·shown.`
+ 48 │ - )
+ 44 │ + console.log(`${components.length·-·missing.length}/${components.length}·components·shown.`);
+ 49 45 │
+ 50 46 │ if (missing.length > 0) {
+ 51 │ - ··console.log(`Missing:·${missing.join(",·")}`)
+ 52 │ - ··process.exit(1)
+ 47 │ + ··console.log(`Missing:·${missing.join(",·")}`);
+ 48 │ + ··process.exit(1);
+ 53 49 │ }
+ 54 50 │
+
+
+.agents/skills/ui-ux-pro-max/data/data-provenance.json format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 17 17 │ "verifiedAt": "2026-08-12",
+ 18 18 │ "sla": "needs-review",
+ 19 │ - ······"appliesTo":·[
+ 20 │ - ········"design-system"
+ 21 │ - ······],
+ 19 │ + ······"appliesTo":·["design-system"],
+ 22 20 │ "confidence": 0.88,
+ 23 21 │ "sources": [
+ ········· │
+ 42 40 │ "verifiedAt": "2026-08-12",
+ 43 41 │ "sla": "needs-review",
+ 44 │ - ······"appliesTo":·[
+ 45 │ - ········"design-system"
+ 46 │ - ······],
+ 42 │ + ······"appliesTo":·["design-system"],
+ 47 43 │ "confidence": 0.92,
+ 48 44 │ "sources": [
+ ········· │
+ 67 63 │ "verifiedAt": "2026-08-12",
+ 68 64 │ "sla": "needs-review",
+ 69 │ - ······"appliesTo":·[
+ 70 │ - ········"design-system"
+ 71 │ - ······],
+ 65 │ + ······"appliesTo":·["design-system"],
+ 72 66 │ "confidence": 0.93,
+ 73 67 │ "sources": [
+ ········· │
+ 92 86 │ "verifiedAt": "2026-08-12",
+ 93 87 │ "sla": "needs-review",
+ 94 │ - ······"appliesTo":·[
+ 95 │ - ········"design-system"
+ 96 │ - ······],
+ 88 │ + ······"appliesTo":·["design-system"],
+ 97 89 │ "confidence": 0.93,
+ 98 90 │ "sources": [
+ ········· │
+ 117 109 │ "verifiedAt": "2026-08-12",
+ 118 110 │ "sla": "needs-review",
+ 119 │ - ······"appliesTo":·[
+ 120 │ - ········"design-system"
+ 121 │ - ······],
+ 111 │ + ······"appliesTo":·["design-system"],
+ 122 112 │ "confidence": 0.94,
+ 123 113 │ "sources": [
+ ········· │
+ 142 132 │ "verifiedAt": "2026-08-12",
+ 143 133 │ "sla": "needs-review",
+ 144 │ - ······"appliesTo":·[
+ 145 │ - ········"design-system"
+ 146 │ - ······],
+ 134 │ + ······"appliesTo":·["design-system"],
+ 147 135 │ "confidence": 0.91,
+ 148 136 │ "sources": [
+ ········· │
+ 167 155 │ "verifiedAt": "2026-08-12",
+ 168 156 │ "sla": "needs-review",
+ 169 │ - ······"appliesTo":·[
+ 170 │ - ········"design-system"
+ 171 │ - ······],
+ 157 │ + ······"appliesTo":·["design-system"],
+ 172 158 │ "confidence": 0.93,
+ 173 159 │ "sources": [
+ ········· │
+ 192 178 │ "verifiedAt": "2026-08-12",
+ 193 179 │ "sla": "needs-review",
+ 194 │ - ······"appliesTo":·[
+ 195 │ - ········"design-system"
+ 196 │ - ······],
+ 180 │ + ······"appliesTo":·["design-system"],
+ 197 181 │ "confidence": 0.9,
+ 198 182 │ "sources": [
+ ········· │
+ 217 201 │ "verifiedAt": "2026-08-12",
+ 218 202 │ "sla": "needs-review",
+ 219 │ - ······"appliesTo":·[
+ 220 │ - ········"design-system"
+ 221 │ - ······],
+ 203 │ + ······"appliesTo":·["design-system"],
+ 222 204 │ "confidence": 0.89,
+ 223 205 │ "sources": [
+ ········· │
+ 242 224 │ "verifiedAt": "2026-08-12",
+ 243 225 │ "sla": "needs-review",
+ 244 │ - ······"appliesTo":·[
+ 245 │ - ········"design-system"
+ 246 │ - ······],
+ 226 │ + ······"appliesTo":·["design-system"],
+ 247 227 │ "confidence": 0.93,
+ 248 228 │ "sources": [
+ ········· │
+ 267 247 │ "verifiedAt": "2026-08-12",
+ 268 248 │ "sla": "needs-review",
+ 269 │ - ······"appliesTo":·[
+ 270 │ - ········"design-system"
+ 271 │ - ······],
+ 249 │ + ······"appliesTo":·["design-system"],
+ 272 250 │ "confidence": 0.9,
+ 273 251 │ "sources": [
+ ········· │
+ 292 270 │ "verifiedAt": "2026-08-12",
+ 293 271 │ "sla": "needs-review",
+ 294 │ - ······"appliesTo":·[
+ 295 │ - ········"design-system"
+ 296 │ - ······],
+ 272 │ + ······"appliesTo":·["design-system"],
+ 297 273 │ "confidence": 0.92,
+ 298 274 │ "sources": [
+ ········· │
+ 317 293 │ "verifiedAt": "2026-08-12",
+ 318 294 │ "sla": "needs-review",
+ 319 │ - ······"appliesTo":·[
+ 320 │ - ········"design-system"
+ 321 │ - ······],
+ 295 │ + ······"appliesTo":·["design-system"],
+ 322 296 │ "confidence": 0.88,
+ 323 297 │ "sources": [
+ ········· │
+ 342 316 │ "verifiedAt": "2026-08-12",
+ 343 317 │ "sla": "needs-review",
+ 344 │ - ······"appliesTo":·[
+ 345 │ - ········"design-system"
+ 346 │ - ······],
+ 318 │ + ······"appliesTo":·["design-system"],
+ 347 319 │ "confidence": 0.93,
+ 348 320 │ "sources": [
+ ········· │
+ 367 339 │ "verifiedAt": "2026-08-12",
+ 368 340 │ "sla": "needs-review",
+ 369 │ - ······"appliesTo":·[
+ 370 │ - ········"design-system"
+ 371 │ - ······],
+ 341 │ + ······"appliesTo":·["design-system"],
+ 372 342 │ "confidence": 0.91,
+ 373 343 │ "sources": [
+ ········· │
+ 392 362 │ "verifiedAt": "2026-08-12",
+ 393 363 │ "sla": "needs-review",
+ 394 │ - ······"appliesTo":·[
+ 395 │ - ········"design-system"
+ 396 │ - ······],
+ 364 │ + ······"appliesTo":·["design-system"],
+ 397 365 │ "confidence": 0.95,
+ 398 366 │ "sources": [
+ ········· │
+ 417 385 │ "verifiedAt": "2026-08-12",
+ 418 386 │ "sla": "needs-review",
+ 419 │ - ······"appliesTo":·[
+ 420 │ - ········"design-system"
+ 421 │ - ······],
+ 387 │ + ······"appliesTo":·["design-system"],
+ 422 388 │ "confidence": 0.92,
+ 423 389 │ "sources": [
+ ········· │
+ 442 408 │ "verifiedAt": "2026-08-12",
+ 443 409 │ "sla": "needs-review",
+ 444 │ - ······"appliesTo":·[
+ 445 │ - ········"design-system"
+ 446 │ - ······],
+ 410 │ + ······"appliesTo":·["design-system"],
+ 447 411 │ "confidence": 0.93,
+ 448 412 │ "sources": [
+ ········· │
+ 467 431 │ "verifiedAt": "2026-08-12",
+ 468 432 │ "sla": "needs-review",
+ 469 │ - ······"appliesTo":·[
+ 470 │ - ········"design-system"
+ 471 │ - ······],
+ 433 │ + ······"appliesTo":·["design-system"],
+ 278 more lines truncated
+
+
+.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-api.json format ━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 29 29 │ "category": "sans-serif",
+ 30 30 │ "kind": "webfonts#webfont",
+ 31 │ - ······"axes":·[
+ 32 │ - ········{"tag":·"wght",·"start":·100,·"end":·900}
+ 33 │ - ······]
+ 31 │ + ······"axes":·[{·"tag":·"wght",·"start":·100,·"end":·900·}]
+ 34 32 │ }
+ 35 33 │ ]
+
+
+.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-catalog.json format ━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 3 3 │ "familyMetadataList": [
+ 4 4 │ {
+ 5 │ - ······"family":·"Zeta·Serif",·"displayName":·null,·"category":·"Serif",·"stroke":·"Serif",
+ 6 │ - ······"classifications":·["Display"],·"size":·1000,·"subsets":·["menu",·"latin-ext",·"latin"],
+ 5 │ + ······"family":·"Zeta·Serif",
+ 6 │ + ······"displayName":·null,
+ 7 │ + ······"category":·"Serif",
+ 8 │ + ······"stroke":·"Serif",
+ 9 │ + ······"classifications":·["Display"],
+ 10 │ + ······"size":·1000,
+ 11 │ + ······"subsets":·["menu",·"latin-ext",·"latin"],
+ 7 12 │ "fonts": {
+ 8 │ - ········"400":·{"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2},
+ 9 │ - ········"700":·{"thickness":·7,·"slant":·1,·"width":·7,·"lineHeight":·1.2},
+ 10 │ - ········"700i":·{"thickness":·7,·"slant":·4,·"width":·7,·"lineHeight":·1.2}
+ 11 │ - ······},·"axes":·[],·"designers":·["Zeta·Studio"],
+ 12 │ - ······"lastModified":·"2025-06-02",·"dateAdded":·"2018-04-09",·"popularity":·90,
+ 13 │ - ······"trending":·11,·"defaultSort":·9,·"androidFragment":·null,·"isNoto":·false,
+ 14 │ - ······"colorCapabilities":·[],·"primaryScript":·"",·"primaryLanguage":·"",·"isOpenSource":·true,
+ 15 │ - ······"isBrandFont":·false,·"languages":·[]
+ 13 │ + ········"400":·{·"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2·},
+ 14 │ + ········"700":·{·"thickness":·7,·"slant":·1,·"width":·7,·"lineHeight":·1.2·},
+ 15 │ + ········"700i":·{·"thickness":·7,·"slant":·4,·"width":·7,·"lineHeight":·1.2·}
+ 16 │ + ······},
+ 17 │ + ······"axes":·[],
+ 18 │ + ······"designers":·["Zeta·Studio"],
+ 19 │ + ······"lastModified":·"2025-06-02",
+ 20 │ + ······"dateAdded":·"2018-04-09",
+ 21 │ + ······"popularity":·90,
+ 22 │ + ······"trending":·11,
+ 23 │ + ······"defaultSort":·9,
+ 24 │ + ······"androidFragment":·null,
+ 25 │ + ······"isNoto":·false,
+ 26 │ + ······"colorCapabilities":·[],
+ 27 │ + ······"primaryScript":·"",
+ 28 │ + ······"primaryLanguage":·"",
+ 29 │ + ······"isOpenSource":·true,
+ 30 │ + ······"isBrandFont":·false,
+ 31 │ + ······"languages":·[]
+ 16 32 │ },
+ 17 33 │ {
+ 18 │ - ······"family":·"Alpha·Sans",·"displayName":·null,·"category":·"Sans·Serif",·"stroke":·"Sans·Serif",
+ 19 │ - ······"classifications":·["Geometric"],·"size":·2000,·"subsets":·["menu",·"vietnamese",·"latin"],
+ 34 │ + ······"family":·"Alpha·Sans",
+ 35 │ + ······"displayName":·null,
+ 36 │ + ······"category":·"Sans·Serif",
+ 37 │ + ······"stroke":·"Sans·Serif",
+ 38 │ + ······"classifications":·["Geometric"],
+ 39 │ + ······"size":·2000,
+ 40 │ + ······"subsets":·["menu",·"vietnamese",·"latin"],
+ 20 41 │ "fonts": {
+ 21 │ - ········"400":·{"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2},
+ 22 │ - ········"400i":·{"thickness":·5,·"slant":·4,·"width":·7,·"lineHeight":·1.2},
+ 23 │ - ········"500":·{"thickness":·6,·"slant":·1,·"width":·7,·"lineHeight":·1.2}
+ 42 │ + ········"400":·{·"thickness":·5,·"slant":·1,·"width":·7,·"lineHeight":·1.2·},
+ 43 │ + ········"400i":·{·"thickness":·5,·"slant":·4,·"width":·7,·"lineHeight":·1.2·},
+ 44 │ + ········"500":·{·"thickness":·6,·"slant":·1,·"width":·7,·"lineHeight":·1.2·}
+ 24 45 │ },
+ 25 │ - ······"axes":·[{"tag":·"wght",·"min":·100.0,·"max":·900.0,·"defaultValue":·400.0}],
+ 26 │ - ······"designers":·["Ada·Type",·"Binh·Fonts"],·"lastModified":·"2025-01-03",
+ 27 │ - ······"dateAdded":·"2020-02-20",·"popularity":·42,·"trending":·7,·"defaultSort":·4,
+ 28 │ - ······"androidFragment":·null,·"isNoto":·false,·"colorCapabilities":·[],·"primaryScript":·"",
+ 29 │ - ······"primaryLanguage":·"",·"isOpenSource":·true,·"isBrandFont":·false,·"languages":·[]
+ 46 │ + ······"axes":·[{·"tag":·"wght",·"min":·100.0,·"max":·900.0,·"defaultValue":·400.0·}],
+ 47 │ + ······"designers":·["Ada·Type",·"Binh·Fonts"],
+ 48 │ + ······"lastModified":·"2025-01-03",
+ 49 │ + ······"dateAdded":·"2020-02-20",
+ 50 │ + ······"popularity":·42,
+ 51 │ + ······"trending":·7,
+ 52 │ + ······"defaultSort":·4,
+ 53 │ + ······"androidFragment":·null,
+ 54 │ + ······"isNoto":·false,
+ 55 │ + ······"colorCapabilities":·[],
+ 56 │ + ······"primaryScript":·"",
+ 57 │ + ······"primaryLanguage":·"",
+ 58 │ + ······"isOpenSource":·true,
+ 59 │ + ······"isBrandFont":·false,
+ 60 │ + ······"languages":·[]
+ 30 61 │ }
+ 31 62 │ ],
+
+
+.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-core.json format ━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 15 │ ····"alias":·{·"name":·"back-arrow",·"pascal_name":·"BackArrow"·},
+ │ + +
+
+.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-cases.json format ━━━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 9 9 │ "globalNegativeApplicability": {
+ 10 10 │ "tag": "hard-negative",
+ 11 │ - ····"domains":·["style",·"color",·"chart",·"landing",·"product",·"ux",·"typography",·"icons",·"gsap",·"react",·"web",·"google-fonts"],
+ 12 │ - ····"stacks":·["react",·"nextjs",·"vue",·"svelte",·"astro",·"swiftui",·"react-native",·"flutter",·"nuxtjs",·"nuxt-ui",·"html-tailwind",·"shadcn",·"jetpack-compose",·"threejs",·"angular",·"laravel",·"javafx",·"wpf",·"winui",·"avalonia",·"uno",·"uwp"],
+ 11 │ + ····"domains":·[
+ 12 │ + ······"style",
+ 13 │ + ······"color",
+ 14 │ + ······"chart",
+ 15 │ + ······"landing",
+ 16 │ + ······"product",
+ 17 │ + ······"ux",
+ 18 │ + ······"typography",
+ 19 │ + ······"icons",
+ 20 │ + ······"gsap",
+ 21 │ + ······"react",
+ 22 │ + ······"web",
+ 23 │ + ······"google-fonts"
+ 24 │ + ····],
+ 25 │ + ····"stacks":·[
+ 26 │ + ······"react",
+ 27 │ + ······"nextjs",
+ 28 │ + ······"vue",
+ 29 │ + ······"svelte",
+ 30 │ + ······"astro",
+ 31 │ + ······"swiftui",
+ 32 │ + ······"react-native",
+ 33 │ + ······"flutter",
+ 34 │ + ······"nuxtjs",
+ 35 │ + ······"nuxt-ui",
+ 36 │ + ······"html-tailwind",
+ 37 │ + ······"shadcn",
+ 38 │ + ······"jetpack-compose",
+ 39 │ + ······"threejs",
+ 40 │ + ······"angular",
+ 41 │ + ······"laravel",
+ 42 │ + ······"javafx",
+ 43 │ + ······"wpf",
+ 44 │ + ······"winui",
+ 45 │ + ······"avalonia",
+ 46 │ + ······"uno",
+ 47 │ + ······"uwp"
+ 48 │ + ····],
+ 13 49 │ "interpretation": "Run every hard-negative case against all registered domains and stacks. Any returned row is an implicit grade-0 false positive; aggregate reporting may cap this cross-product separately."
+ 14 50 │ },
+ 15 51 │ "cases": [
+ 16 │ - ····{"id":"domain-style-glassmorphism","split":"calibration","mode":"domain","query":"frosted·glass·transparent·blurred·layered·UI","domain":"style","judgments":[{"identity":{"Style·Category":"Glassmorphism"},"grade":2}],"tags":["exact-intent","domain-positive"],"notes":"Canonical·visual·treatment,·expressed·through·its·descriptive·attributes."},
+ 17 │ - ····{"id":"domain-style-accessible-paraphrase","split":"held_out","mode":"domain","query":"inclusive·high·contrast·interface·with·keyboard·nav·and·screen·readers","domain":"style","judgments":[{"identity":{"Style·Category":"Accessible·&·Ethical"},"grade":2},{"identity":{"Style·Category":"Inclusive·Design"},"grade":1}],"tags":["paraphrase","domain-positive"],"notes":"A·style·request,·not·an·individual·UX·rule."},
+ 18 │ - ····{"id":"domain-color-spa","split":"calibration","mode":"domain","query":"beauty·spa·wellness·soft·pink·lavender·palette","domain":"color","judgments":[{"identity":{"Product·Type":"Beauty/Spa/Wellness·Service"},"grade":2}],"tags":["product-palette","domain-positive"],"notes":"Uses·the·public·Product·Type·as·the·stable·palette·identity."},
+ 19 │ - ····{"id":"domain-color-cyber-typo","split":"held_out","mode":"domain","query":"cybersecurty·threat·dashboard·matrix·green·dark·pallete","domain":"color","judgments":[{"identity":{"Product·Type":"Cybersecurity·Platform"},"grade":2}],"tags":["typo","domain-positive"],"notes":"Deliberately·misspells·cybersecurity·and·palette."},
+ 20 │ - ····{"id":"domain-chart-time-series","split":"calibration","mode":"domain","query":"trend·over·time·growth·timeline·line·chart","domain":"chart","judgments":[{"identity":{"Data·Type":"Trend·Over·Time"},"grade":2},{"identity":{"Data·Type":"Time-Series·Forecast"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"Forecasting·is·acceptable·only·as·a·secondary·interpretation."},
+ 21 │ - ····{"id":"domain-chart-correlation-paraphrase","split":"held_out","mode":"domain","query":"show·whether·two·measures·move·together·using·dots·and·bubbles","domain":"chart","judgments":[{"identity":{"Data·Type":"Correlation·/·Distribution"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·canonical·words·scatter·and·correlation."},
+ 22 │ - ····{"id":"domain-landing-pricing","split":"calibration","mode":"domain","query":"pricing·plans·tiers·comparison·landing·CTA","domain":"landing","judgments":[{"identity":{"Pattern·Name":"Pricing·Page·+·CTA"},"grade":2},{"identity":{"Pattern·Name":"Pricing-Focused·Landing"},"grade":2},{"identity":{"Pattern·Name":"Comparison·Table·+·CTA"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Both·dedicated·pricing·patterns·are·equally·valid."},
+ 23 │ - ····{"id":"domain-landing-enterprise-typo","split":"held_out","mode":"domain","query":"enterprise·credibilty·trust·authority·conversion·page","domain":"landing","judgments":[{"identity":{"Pattern·Name":"Trust·&·Authority·+·Conversion"},"grade":2},{"identity":{"Pattern·Name":"Enterprise·Gateway"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·credibility·while·preserving·the·enterprise·intent."},
+ 24 │ - ····{"id":"domain-product-spa","split":"calibration","mode":"domain","query":"salon·massage·skincare·booking·and·wellness·service","domain":"product","judgments":[{"identity":{"Product·Type":"Beauty/Spa/Wellness·Service"},"grade":2},{"identity":{"Product·Type":"Booking·&·Appointment·App"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"The·industry·is·primary;·booking·is·a·supporting·capability."},
+ 25 │ - ····{"id":"domain-product-security-paraphrase","split":"held_out","mode":"domain","query":"platform·for·monitoring·digital·threats·and·protecting·systems","domain":"product","judgments":[{"identity":{"Product·Type":"Cybersecurity·Platform"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·exact·keyword·cyber."},
+ 26 │ - ····{"id":"domain-ux-keyboard-focus","split":"calibration","mode":"domain","query":"visible·focus·and·complete·keyboard·navigation·for·web·users","domain":"ux","judgments":[{"identity":{"Category":"Accessibility","Issue":"Keyboard·Navigation","Platform":"Web"},"grade":2},{"identity":{"Category":"Interaction","Issue":"Focus·States","Platform":"All"},"grade":2}],"tags":["multi-acceptable","domain-positive"],"notes":"Both·navigation·coverage·and·visible·focus·are·explicitly·requested."},
+ 27 │ - ····{"id":"domain-ux-motion-paraphrase","split":"held_out","mode":"domain","query":"stop·animations·making·people·sick·and·honor·their·motion·preference","domain":"ux","judgments":[{"identity":{"Category":"Animation","Issue":"Reduced·Motion","Platform":"All"},"grade":2},{"identity":{"Category":"Animation","Issue":"Excessive·Motion","Platform":"All"},"grade":1}],"tags":["paraphrase","accessibility","domain-positive"],"notes":"Preference·support·is·stronger·than·generic·excessive-motion·advice."},
+ 28 │ - ····{"id":"domain-typography-luxury","split":"calibration","mode":"domain","query":"elegant·luxury·serif·heading·with·clean·readable·body·font","domain":"typography","judgments":[{"identity":{"Font·Pairing·Name":"Classic·Elegant"},"grade":2},{"identity":{"Font·Pairing·Name":"Luxury·Serif"},"grade":2},{"identity":{"Font·Pairing·Name":"Luxury·Minimalist"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Several·curated·pairs·directly·satisfy·this·intentionally·broad·mood."},
+ 29 │ - ····{"id":"domain-typography-accessible-typo","split":"held_out","mode":"domain","query":"dyslexia·frendly·hyperlegible·inclusive·type·pairing","domain":"typography","judgments":[{"identity":{"Font·Pairing·Name":"Accessibility·First"},"grade":2},{"identity":{"Font·Pairing·Name":"Academic/Research"},"grade":1}],"tags":["typo","accessibility","domain-positive"],"notes":"Misspells·friendly;·the·all-Atkinson·pairing·is·the·direct·answer."},
+ 30 │ - ····{"id":"domain-icons-search","split":"calibration","mode":"domain","query":"find·lookup·search·icon·for·a·query·field","domain":"icons","judgments":[{"identity":{"Category":"Action","Icon·Name":"magnifying-glass","Library":"Phosphor"},"grade":2},{"identity":{"Category":"Action","Icon·Name":"funnel","Library":"Phosphor"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"Filter·is·useful·but·not·equivalent·to·search."},
+ 31 │ - ····{"id":"domain-icons-warning-typo","split":"held_out","mode":"domain","query":"warnng·caution·danger·status·symbol","domain":"icons","judgments":[{"identity":{"Category":"Status","Icon·Name":"warning","Library":"Phosphor"},"grade":2},{"identity":{"Category":"Status","Icon·Name":"warning-circle","Library":"Phosphor"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·warning·and·allows·the·circled·variant·as·secondary."},
+ 32 │ - ····{"id":"domain-gsap-scroll-reveal","split":"calibration","mode":"domain","query":"GSAP·reveal·elements·when·they·enter·the·viewport·on·scroll","domain":"gsap","judgments":[{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Subtle","Trigger":"scroll·(viewport·enter)"},"grade":2},{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Standard","Trigger":"scroll·(viewport·enter)"},"grade":2},{"identity":{"Category":"Scroll·Reveal","Intensity·Tier":"Complex","Trigger":"scroll·(continuous·scrub)"},"grade":1}],"tags":["multi-acceptable","domain-positive"],"notes":"Viewport-entry·variants·are·direct;·continuous·scrub·is·related·but·stronger."},
+ 33 │ - ····{"id":"domain-gsap-skeleton-typo","split":"held_out","mode":"domain","query":"skeletn·shimmer·loader·while·async·content·waits","domain":"gsap","judgments":[{"identity":{"Category":"Loading·/·Skeleton","Intensity·Tier":"Subtle","Trigger":"on·mount·/·async·wait"},"grade":2},{"identity":{"Category":"Loading·/·Skeleton","Intensity·Tier":"Standard","Trigger":"on·mount·/·async·wait"},"grade":1}],"tags":["typo","domain-positive"],"notes":"Misspells·skeleton;·subtle·shimmer·is·the·most·exact·row."},
+ 34 │ - ····{"id":"domain-react-parallel-promises","split":"calibration","mode":"domain","query":"React·Promise.all·parallel·concurrent·requests·instead·of·waterfall","domain":"react","judgments":[{"identity":{"Category":"Async·Waterfall","Issue":"Promise.all·Parallel","Platform":"React/Next.js"},"grade":2},{"identity":{"Category":"Async·Waterfall","Issue":"Dependency·Parallelization","Platform":"React/Next.js"},"grade":1}],"tags":["exact-intent","domain-positive"],"notes":"The·first·row·exactly·names·Promise.all."},
+ 35 │ - ····{"id":"domain-react-dynamic-import-paraphrase","split":"held_out","mode":"domain","query":"load·a·heavy·JavaScript·chunk·only·when·the·component·is·needed","domain":"react","judgments":[{"identity":{"Category":"Bundle·Size","Issue":"Dynamic·Imports","Platform":"React/Next.js"},"grade":2},{"identity":{"Category":"Bundle·Size","Issue":"Conditional·Loading","Platform":"React/Next.js"},"grade":1}],"tags":["paraphrase","domain-positive"],"notes":"Avoids·the·canonical·term·lazy·in·the·main·clause."},
+ 36 │ - ····{"id":"domain-web-icon-label","split":"calibration","mode":"domain","query":"accessible·name·for·an·icon-only·mobile·button","domain":"web","judgments":[{"identity":{"Category":"Accessibility","Issue":"Icon·Button·Labels","Platform":"iOS/Android/React·Native"},"grade":2}],"tags":["accessibility","domain-positive"],"notes":"App-interface·guidance·uses·the·public·web·domain·key."},
+ 37 │ - ····{"id":"domain-web-virtual-list-paraphrase","split":"held_out","mode":"domain","query":"keep·a·very·long·mobile·list·smooth·without·rendering·every·row","domain":"web","judgments":[{"identity":{"Category":"Performance","Issue":"Virtualize·Long·Lists","Platform":"iOS/Android/React·Native"},"grade":2}],"tags":["paraphrase","domain-positive"],"notes":"Describes·virtualization·without·naming·FlatList."},
+ 38 │ - ····{"id":"domain-google-fonts-inter","split":"calibration","mode":"domain","query":"Inter·variable·sans·serif·Google·font·family","domain":"google-fonts","judgments":[{"identity":{"Family":"Inter"},"grade":2},{"identity":{"Family":"Inter·Tight"},"grade":1}],"tags":["exact-entity","domain-positive"],"notes":"Exact·family·is·preferred·over·its·related·Tight·family."},
+ 39 │ - ····{"id":"domain-google-fonts-atkinson-paraphrase","split":"held_out","mode":"domain","query":"hyperlegible·accessible·sans·typeface·for·easier·reading","domain":"google-fonts","judgments":[{"identity":{"Family":"Atkinson·Hyperlegible"},"grade":2},{"identity":{"Family":"Atkinson·Hyperlegible·Next"},"grade":1}],"tags":["paraphrase","accessibility","domain-positive"],"notes":"The·established·family·is·locked;·its·newer·related·family·is·acceptable."},
+ 52 │ + ····{
+ 53 │ + ······"id":·"domain-style-glassmorphism",
+ 54 │ + ······"split":·"calibration",
+ 55 │ + ······"mode":·"domain",
+ 56 │ + ······"query":·"frosted·glass·transparent·blurred·layered·UI",
+ 57 │ + ······"domain":·"style",
+ 58 │ + ······"judgments":·[{·"identity":·{·"Style·Category":·"Glassmorphism"·},·"grade":·2·}],
+ 59 │ + ······"tags":·["exact-intent",·"domain-positive"],
+ 60 │ + ······"notes":·"Canonical·visual·treatment,·expressed·through·its·descriptive·attributes."
+ 61 │ + ····},
+ 62 │ + ····{
+ 63 │ + ······"id":·"domain-style-accessible-paraphrase",
+ 64 │ + ······"split":·"held_out",
+ 65 │ + ······"mode":·"domain",
+ 66 │ + ······"query":·"inclusive·high·contrast·interface·with·keyboard·nav·and·screen·readers",
+ 67 │ + ······"domain":·"style",
+ 68 │ + ······"judgments":·[
+ 69 │ + ········{·"identity":·{·"Style·Category":·"Accessible·&·Ethical"·},·"grade":·2·},
+ 70 │ + ········{·"identity":·{·"Style·Category":·"Inclusive·Design"·},·"grade":·1·}
+ 71 │ + ······],
+ 72 │ + ······"tags":·["paraphrase",·"domain-positive"],
+ 73 │ + ······"notes":·"A·style·request,·not·an·individual·UX·rule."
+ 74 │ + ····},
+ 75 │ + ····{
+ 76 │ + ······"id":·"domain-color-spa",
+ 77 │ + ······"split":·"calibration",
+ 78 │ + ······"mode":·"domain",
+ 79 │ + ······"query":·"beauty·spa·wellness·soft·pink·lavender·palette",
+ 80 │ + ······"domain":·"color",
+ 81 │ + ······"judgments":·[{·"identity":·{·"Product·Type":·"Beauty/Spa/Wellness·Service"·},·"grade":·2·}],
+ 82 │ + ······"tags":·["product-palette",·"domain-positive"],
+ 83 │ + ······"notes":·"Uses·the·public·Product·Type·as·the·stable·palette·identity."
+ 84 │ + ····},
+ 85 │ + ····{
+ 86 │ + ······"id":·"domain-color-cyber-typo",
+ 87 │ + ······"split":·"held_out",
+ 88 │ + ······"mode":·"domain",
+ 89 │ + ······"query":·"cybersecurty·threat·dashboard·matrix·green·dark·pallete",
+ 90 │ + ······"domain":·"color",
+ 91 │ + ······"judgments":·[{·"identity":·{·"Product·Type":·"Cybersecurity·Platform"·},·"grade":·2·}],
+ 92 │ + ······"tags":·["typo",·"domain-positive"],
+ 93 │ + ······"notes":·"Deliberately·misspells·cybersecurity·and·palette."
+ 94 │ + ····},
+ 95 │ + ····{
+ 96 │ + ······"id":·"domain-chart-time-series",
+ 97 │ + ······"split":·"calibration",
+ 98 │ + ······"mode":·"domain",
+ 99 │ + ······"query":·"trend·over·time·growth·timeline·line·chart",
+ 100 │ + ······"domain":·"chart",
+ 101 │ + ······"judgments":·[
+ 102 │ + ········{·"identity":·{·"Data·Type":·"Trend·Over·Time"·},·"grade":·2·},
+ 103 │ + ········{·"identity":·{·"Data·Type":·"Time-Series·Forecast"·},·"grade":·1·}
+ 104 │ + ······],
+ 105 │ + ······"tags":·["exact-intent",·"domain-positive"],
+ 106 │ + ······"notes":·"Forecasting·is·acceptable·only·as·a·secondary·interpretation."
+ 107 │ + ····},
+ 108 │ + ····{
+ 109 │ + ······"id":·"domain-chart-correlation-paraphrase",
+ 110 │ + ······"split":·"held_out",
+ 111 │ + ······"mode":·"domain",
+ 112 │ + ······"query":·"show·whether·two·measures·move·together·using·dots·and·bubbles",
+ 113 │ + ······"domain":·"chart",
+ 114 │ + ······"judgments":·[{·"identity":·{·"Data·Type":·"Correlation·/·Distribution"·},·"grade":·2·}],
+ 115 │ + ······"tags":·["paraphrase",·"domain-positive"],
+ 116 │ + ······"notes":·"Avoids·the·canonical·words·scatter·and·correlation."
+ 117 │ + ····},
+ 118 │ + ····{
+ 119 │ + ······"id":·"domain-landing-pricing",
+ 120 │ + ······"split":·"calibration",
+ 121 │ + ······"mode":·"domain",
+ 122 │ + ······"query":·"pricing·plans·tiers·comparison·landing·CTA",
+ 123 │ + ······"domain":·"landing",
+ 124 │ + ······"judgments":·[
+ 125 │ + ········{·"identity":·{·"Pattern·Name":·"Pricing·Page·+·CTA"·},·"grade":·2·},
+ 126 │ + ········{·"identity":·{·"Pattern·Name":·"Pricing-Focused·Landing"·},·"grade":·2·},
+ 127 │ + ········{·"identity":·{·"Pattern·Name":·"Comparison·Table·+·CTA"·},·"grade":·1·}
+ 128 │ + ······],
+ 129 │ + ······"tags":·["multi-acceptable",·"domain-positive"],
+ 130 │ + ······"notes":·"Both·dedicated·pricing·patterns·are·equally·valid."
+ 131 │ + ····},
+ 132 │ + ····{
+ 1440 more lines truncated
+
+
+.agents/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-thresholds.json format ━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 14 14 │ "tolerancePolicy": "A 1e-12 numeric tolerance permits floating-point representation only. Current known failures remain visible in relevance-baseline.json; the gate prevents regressions and does not claim final quality targets are met.",
+ 15 15 │ "metrics": {
+ 16 │ - ····"routingAccuracy":·{"floor":·0.875,·"tolerance":·1e-12},
+ 17 │ - ····"precisionAt1":·{"floor":·0.7631578947368421,·"tolerance":·1e-12},
+ 18 │ - ····"precisionAt3":·{"floor":·0.3815789473684211,·"tolerance":·1e-12},
+ 19 │ - ····"mrrAt3":·{"floor":·0.8355263157894737,·"tolerance":·1e-12},
+ 20 │ - ····"ndcgAt3":·{"floor":·0.8366350964266653,·"tolerance":·1e-12},
+ 21 │ - ····"negativeAbstention":·{"floor":·0.9117647058823529,·"tolerance":·1e-12},
+ 22 │ - ····"typoRecoveryAt3":·{"floor":·1.0,·"tolerance":·1e-12},
+ 23 │ - ····"designSystemCoherence":·{"floor":·0.71875,·"tolerance":·1e-12}
+ 16 │ + ····"routingAccuracy":·{·"floor":·0.875,·"tolerance":·1e-12·},
+ 17 │ + ····"precisionAt1":·{·"floor":·0.7631578947368421,·"tolerance":·1e-12·},
+ 18 │ + ····"precisionAt3":·{·"floor":·0.3815789473684211,·"tolerance":·1e-12·},
+ 19 │ + ····"mrrAt3":·{·"floor":·0.8355263157894737,·"tolerance":·1e-12·},
+ 20 │ + ····"ndcgAt3":·{·"floor":·0.8366350964266653,·"tolerance":·1e-12·},
+ 21 │ + ····"negativeAbstention":·{·"floor":·0.9117647058823529,·"tolerance":·1e-12·},
+ 22 │ + ····"typoRecoveryAt3":·{·"floor":·1.0,·"tolerance":·1e-12·},
+ 23 │ + ····"designSystemCoherence":·{·"floor":·0.71875,·"tolerance":·1e-12·}
+ 24 24 │ },
+ 25 25 │ "sampleMinimums": {
+ ······· │
+ 46 46 │ "calibration": {
+ 47 47 │ "metrics": {
+ 48 │ - ········"routingAccuracy":·{"floor":·0.8,·"tolerance":·1e-12},
+ 49 │ - ········"precisionAt1":·{"floor":·0.8974358974358975,·"tolerance":·1e-12},
+ 50 │ - ········"precisionAt3":·{"floor":·0.40170940170940167,·"tolerance":·1e-12},
+ 51 │ - ········"mrrAt3":·{"floor":·0.9230769230769231,·"tolerance":·1e-12},
+ 52 │ - ········"ndcgAt3":·{"floor":·0.9103072109232644,·"tolerance":·1e-12},
+ 53 │ - ········"negativeAbstention":·{"floor":·0.9411764705882353,·"tolerance":·1e-12},
+ 54 │ - ········"typoRecoveryAt3":·{"floor":·0.0,·"tolerance":·1e-12},
+ 55 │ - ········"designSystemCoherence":·{"floor":·0.6875,·"tolerance":·1e-12}
+ 48 │ + ········"routingAccuracy":·{·"floor":·0.8,·"tolerance":·1e-12·},
+ 49 │ + ········"precisionAt1":·{·"floor":·0.8974358974358975,·"tolerance":·1e-12·},
+ 50 │ + ········"precisionAt3":·{·"floor":·0.40170940170940167,·"tolerance":·1e-12·},
+ 51 │ + ········"mrrAt3":·{·"floor":·0.9230769230769231,·"tolerance":·1e-12·},
+ 52 │ + ········"ndcgAt3":·{·"floor":·0.9103072109232644,·"tolerance":·1e-12·},
+ 53 │ + ········"negativeAbstention":·{·"floor":·0.9411764705882353,·"tolerance":·1e-12·},
+ 54 │ + ········"typoRecoveryAt3":·{·"floor":·0.0,·"tolerance":·1e-12·},
+ 55 │ + ········"designSystemCoherence":·{·"floor":·0.6875,·"tolerance":·1e-12·}
+ 56 56 │ },
+ 57 │ - ······"sampleMinimums":·{"cases":·46,·"retrieval":·39,·"routing":·5,·"negativeChecks":·102,·"typo":·0,·"designSystem":·4}
+ 57 │ + ······"sampleMinimums":·{
+ 58 │ + ········"cases":·46,
+ 59 │ + ········"retrieval":·39,
+ 60 │ + ········"routing":·5,
+ 61 │ + ········"negativeChecks":·102,
+ 62 │ + ········"typo":·0,
+ 63 │ + ········"designSystem":·4
+ 64 │ + ······}
+ 58 65 │ },
+ 59 66 │ "held_out": {
+ 60 67 │ "metrics": {
+ 61 │ - ········"routingAccuracy":·{"floor":·1.0,·"tolerance":·1e-12},
+ 62 │ - ········"precisionAt1":·{"floor":·0.6216216216216216,·"tolerance":·1e-12},
+ 63 │ - ········"precisionAt3":·{"floor":·0.36036036036036034,·"tolerance":·1e-12},
+ 64 │ - ········"mrrAt3":·{"floor":·0.7432432432432432,·"tolerance":·1e-12},
+ 65 │ - ········"ndcgAt3":·{"floor":·0.7589807054707906,·"tolerance":·1e-12},
+ 66 │ - ········"negativeAbstention":·{"floor":·0.8823529411764706,·"tolerance":·1e-12},
+ 67 │ - ········"typoRecoveryAt3":·{"floor":·1.0,·"tolerance":·1e-12},
+ 68 │ - ········"designSystemCoherence":·{"floor":·0.75,·"tolerance":·1e-12}
+ 68 │ + ········"routingAccuracy":·{·"floor":·1.0,·"tolerance":·1e-12·},
+ 69 │ + ········"precisionAt1":·{·"floor":·0.6216216216216216,·"tolerance":·1e-12·},
+ 70 │ + ········"precisionAt3":·{·"floor":·0.36036036036036034,·"tolerance":·1e-12·},
+ 71 │ + ········"mrrAt3":·{·"floor":·0.7432432432432432,·"tolerance":·1e-12·},
+ 72 │ + ········"ndcgAt3":·{·"floor":·0.7589807054707906,·"tolerance":·1e-12·},
+ 73 │ + ········"negativeAbstention":·{·"floor":·0.8823529411764706,·"tolerance":·1e-12·},
+ 74 │ + ········"typoRecoveryAt3":·{·"floor":·1.0,·"tolerance":·1e-12·},
+ 75 │ + ········"designSystemCoherence":·{·"floor":·0.75,·"tolerance":·1e-12·}
+ 69 76 │ },
+ 70 │ - ······"sampleMinimums":·{"cases":·44,·"retrieval":·37,·"routing":·3,·"negativeChecks":·102,·"typo":·5,·"designSystem":·4}
+ 77 │ + ······"sampleMinimums":·{
+ 78 │ + ········"cases":·44,
+ 79 │ + ········"retrieval":·37,
+ 80 │ + ········"routing":·3,
+ 81 │ + ········"negativeChecks":·102,
+ 82 │ + ········"typo":·5,
+ 83 │ + ········"designSystem":·4
+ 84 │ + ······}
+ 71 85 │ }
+ 72 86 │ },
+ 73 87 │ "lockedCases": {
+ 74 │ - ····"domain-style-glassmorphism":·{"withinTop":·1,·"minimumGrade":·2},
+ 75 │ - ····"domain-color-spa":·{"withinTop":·1,·"minimumGrade":·2},
+ 76 │ - ····"domain-chart-time-series":·{"withinTop":·1,·"minimumGrade":·2},
+ 77 │ - ····"domain-landing-pricing":·{"withinTop":·1,·"minimumGrade":·2},
+ 78 │ - ····"domain-product-spa":·{"withinTop":·1,·"minimumGrade":·2},
+ 79 │ - ····"domain-ux-keyboard-focus":·{"withinTop":·1,·"minimumGrade":·2},
+ 80 │ - ····"domain-typography-luxury":·{"withinTop":·1,·"minimumGrade":·2},
+ 81 │ - ····"domain-icons-search":·{"withinTop":·1,·"minimumGrade":·2},
+ 82 │ - ····"domain-gsap-scroll-reveal":·{"withinTop":·1,·"minimumGrade":·2},
+ 83 │ - ····"domain-react-parallel-promises":·{"withinTop":·1,·"minimumGrade":·2},
+ 84 │ - ····"domain-web-icon-label":·{"withinTop":·1,·"minimumGrade":·2},
+ 85 │ - ····"domain-google-fonts-inter":·{"withinTop":·1,·"minimumGrade":·2},
+ 86 │ - ····"stack-swiftui-navigation":·{"withinTop":·1,·"minimumGrade":·2},
+ 87 │ - ····"stack-threejs-orbitcontrols":·{"withinTop":·1,·"minimumGrade":·2},
+ 88 │ - ····"stack-uwp-migration":·{"withinTop":·1,·"minimumGrade":·2},
+ 89 │ - ····"stack-winui-dispatcherqueue":·{"withinTop":·1,·"minimumGrade":·2}
+ 88 │ + ····"domain-style-glassmorphism":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 89 │ + ····"domain-color-spa":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 90 │ + ····"domain-chart-time-series":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 91 │ + ····"domain-landing-pricing":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 92 │ + ····"domain-product-spa":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 93 │ + ····"domain-ux-keyboard-focus":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 94 │ + ····"domain-typography-luxury":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 95 │ + ····"domain-icons-search":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 96 │ + ····"domain-gsap-scroll-reveal":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 97 │ + ····"domain-react-parallel-promises":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 98 │ + ····"domain-web-icon-label":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 99 │ + ····"domain-google-fonts-inter":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 100 │ + ····"stack-swiftui-navigation":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 101 │ + ····"stack-threejs-orbitcontrols":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 102 │ + ····"stack-uwp-migration":·{·"withinTop":·1,·"minimumGrade":·2·},
+ 103 │ + ····"stack-winui-dispatcherqueue":·{·"withinTop":·1,·"minimumGrade":·2·}
+ 90 104 │ },
+ 91 105 │ "proposedFinalTargets": {
+
+
+.artifacts/resources-global-operating-systems-closure/closure-results.json format ━━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 8 8 │ "id": "DB-SO-01",
+ 9 9 │ "result": "pass",
+ 10 │ - ······"http":·[
+ 11 │ - ········201
+ 12 │ - ······]
+ 10 │ + ······"http":·[201]
+ 13 11 │ },
+ 14 12 │ {
+ 15 13 │ "id": "DB-SO-02",
+ 16 14 │ "result": "pass",
+ 17 │ - ······"http":·[
+ 18 │ - ········201
+ 19 │ - ······]
+ 15 │ + ······"http":·[201]
+ 20 16 │ },
+ 21 17 │ {
+ 22 18 │ "id": "DB-SO-03",
+ 23 19 │ "result": "pass",
+ 24 │ - ······"http":·[
+ 25 │ - ········409
+ 26 │ - ······]
+ 20 │ + ······"http":·[409]
+ 27 21 │ },
+ 28 22 │ {
+ 29 23 │ "id": "DB-SO-04",
+ 30 24 │ "result": "pass",
+ 31 │ - ······"http":·[
+ 32 │ - ········201
+ 33 │ - ······]
+ 25 │ + ······"http":·[201]
+ 34 26 │ },
+ 35 27 │ {
+ 36 28 │ "id": "DB-SO-05",
+ 37 29 │ "result": "pass",
+ 38 │ - ······"http":·[
+ 39 │ - ········201
+ 40 │ - ······]
+ 30 │ + ······"http":·[201]
+ 41 31 │ },
+ 42 32 │ {
+ 43 33 │ "id": "DB-SO-06",
+ 44 34 │ "result": "pass",
+ 45 │ - ······"http":·[
+ 46 │ - ········201
+ 47 │ - ······]
+ 35 │ + ······"http":·[201]
+ 48 36 │ },
+ 49 37 │ {
+ 50 38 │ "id": "DB-SO-07",
+ 51 39 │ "result": "pass",
+ 52 │ - ······"http":·[
+ 53 │ - ········201,
+ 54 │ - ········200
+ 55 │ - ······]
+ 40 │ + ······"http":·[201,·200]
+ 56 41 │ },
+ 57 42 │ {
+ 58 43 │ "id": "DB-SO-08",
+ 59 44 │ "result": "pass",
+ 60 │ - ······"http":·[
+ 61 │ - ········200
+ 62 │ - ······]
+ 45 │ + ······"http":·[200]
+ 63 46 │ },
+ 64 47 │ {
+ 65 48 │ "id": "DB-SO-09",
+ 66 49 │ "result": "pass",
+ 67 │ - ······"http":·[
+ 68 │ - ········404
+ 69 │ - ······]
+ 50 │ + ······"http":·[404]
+ 70 51 │ },
+ 71 52 │ {
+ 72 53 │ "id": "DB-SO-10",
+ 73 54 │ "result": "pass",
+ 74 │ - ······"http":·[
+ 75 │ - ········404
+ 76 │ - ······]
+ 55 │ + ······"http":·[404]
+ 77 56 │ },
+ 78 57 │ {
+ 79 58 │ "id": "DB-SO-11",
+ 80 59 │ "result": "pass",
+ 81 │ - ······"http":·[
+ 82 │ - ········200
+ 83 │ - ······]
+ 60 │ + ······"http":·[200]
+ 84 61 │ },
+ 85 62 │ {
+ 86 63 │ "id": "DB-SO-12",
+ 87 64 │ "result": "pass",
+ 88 │ - ······"http":·[
+ 89 │ - ········409
+ 90 │ - ······]
+ 65 │ + ······"http":·[409]
+ 91 66 │ },
+ 92 67 │ {
+ 93 68 │ "id": "DB-SO-13",
+ 94 69 │ "result": "pass",
+ 95 │ - ······"http":·[
+ 96 │ - ········200,
+ 97 │ - ········200
+ 98 │ - ······]
+ 70 │ + ······"http":·[200,·200]
+ 99 71 │ },
+ 100 72 │ {
+ 101 73 │ "id": "DB-SO-14",
+ 102 74 │ "result": "pass",
+ 103 │ - ······"http":·[
+ 104 │ - ········200,
+ 105 │ - ········200
+ 106 │ - ······]
+ 75 │ + ······"http":·[200,·200]
+ 107 76 │ },
+ 108 77 │ {
+ 109 78 │ "id": "DB-SO-15",
+ 110 79 │ "result": "pass",
+ 111 │ - ······"http":·[
+ 112 │ - ········409
+ 113 │ - ······]
+ 80 │ + ······"http":·[409]
+ 114 81 │ },
+ 115 82 │ {
+ 116 83 │ "id": "DB-SO-16",
+ 117 84 │ "result": "pass",
+ 118 │ - ······"http":·[
+ 119 │ - ········200,
+ 120 │ - ········200
+ 121 │ - ······]
+ 85 │ + ······"http":·[200,·200]
+ 122 86 │ },
+ 123 87 │ {
+ 124 88 │ "id": "DB-SO-17",
+ 125 89 │ "result": "pass",
+ 126 │ - ······"http":·[
+ 127 │ - ········200
+ 128 │ - ······]
+ 90 │ + ······"http":·[200]
+ 129 91 │ },
+ 130 92 │ {
+ 131 93 │ "id": "DB-SO-18",
+ 132 94 │ "result": "pass",
+ 133 │ - ······"http":·[
+ 134 │ - ········200,
+ 135 │ - ········200
+ 136 │ - ······]
+ 95 │ + ······"http":·[200,·200]
+ 137 96 │ },
+ 138 97 │ {
+ 139 98 │ "id": "DB-SO-19",
+ 87 more lines truncated
+
+
+.artifacts/resources-global-operating-systems-closure/legacy-evolution.json format ━━━━━━━━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 99 99 │ "customizationPreserved": true,
+ 100 100 │ "availabilityPreserved": true
+ 101 │ - }
+ 101 │ + }
+ 102 │ +
+
+
+.artifacts/resources-global-operating-systems-unblock/mariadb/closure-results.json format ━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 8 8 │ "id": "DB-SO-01",
+ 9 9 │ "result": "pass",
+ 10 │ - ······"http":·[
+ 11 │ - ········201
+ 12 │ - ······]
+ 10 │ + ······"http":·[201]
+ 13 11 │ },
+ 14 12 │ {
+ 15 13 │ "id": "DB-SO-02",
+ 16 14 │ "result": "pass",
+ 17 │ - ······"http":·[
+ 18 │ - ········201
+ 19 │ - ······]
+ 15 │ + ······"http":·[201]
+ 20 16 │ },
+ 21 17 │ {
+ 22 18 │ "id": "DB-SO-03",
+ 23 19 │ "result": "pass",
+ 24 │ - ······"http":·[
+ 25 │ - ········409
+ 26 │ - ······]
+ 20 │ + ······"http":·[409]
+ 27 21 │ },
+ 28 22 │ {
+ 29 23 │ "id": "DB-SO-04",
+ 30 24 │ "result": "pass",
+ 31 │ - ······"http":·[
+ 32 │ - ········201
+ 33 │ - ······]
+ 25 │ + ······"http":·[201]
+ 34 26 │ },
+ 35 27 │ {
+ 36 28 │ "id": "DB-SO-05",
+ 37 29 │ "result": "pass",
+ 38 │ - ······"http":·[
+ 39 │ - ········201
+ 40 │ - ······]
+ 30 │ + ······"http":·[201]
+ 41 31 │ },
+ 42 32 │ {
+ 43 33 │ "id": "DB-SO-06",
+ 44 34 │ "result": "pass",
+ 45 │ - ······"http":·[
+ 46 │ - ········201
+ 47 │ - ······]
+ 35 │ + ······"http":·[201]
+ 48 36 │ },
+ 49 37 │ {
+ 50 38 │ "id": "DB-SO-07",
+ 51 39 │ "result": "pass",
+ 52 │ - ······"http":·[
+ 53 │ - ········201,
+ 54 │ - ········200
+ 55 │ - ······]
+ 40 │ + ······"http":·[201,·200]
+ 56 41 │ },
+ 57 42 │ {
+ 58 43 │ "id": "DB-SO-08",
+ 59 44 │ "result": "pass",
+ 60 │ - ······"http":·[
+ 61 │ - ········200
+ 62 │ - ······]
+ 45 │ + ······"http":·[200]
+ 63 46 │ },
+ 64 47 │ {
+ 65 48 │ "id": "DB-SO-09",
+ 66 49 │ "result": "pass",
+ 67 │ - ······"http":·[
+ 68 │ - ········404
+ 69 │ - ······]
+ 50 │ + ······"http":·[404]
+ 70 51 │ },
+ 71 52 │ {
+ 72 53 │ "id": "DB-SO-10",
+ 73 54 │ "result": "pass",
+ 74 │ - ······"http":·[
+ 75 │ - ········404
+ 76 │ - ······]
+ 55 │ + ······"http":·[404]
+ 77 56 │ },
+ 78 57 │ {
+ 79 58 │ "id": "DB-SO-11",
+ 80 59 │ "result": "pass",
+ 81 │ - ······"http":·[
+ 82 │ - ········200
+ 83 │ - ······]
+ 60 │ + ······"http":·[200]
+ 84 61 │ },
+ 85 62 │ {
+ 86 63 │ "id": "DB-SO-12",
+ 87 64 │ "result": "pass",
+ 88 │ - ······"http":·[
+ 89 │ - ········409
+ 90 │ - ······]
+ 65 │ + ······"http":·[409]
+ 91 66 │ },
+ 92 67 │ {
+ 93 68 │ "id": "DB-SO-13",
+ 94 69 │ "result": "pass",
+ 95 │ - ······"http":·[
+ 96 │ - ········200,
+ 97 │ - ········200
+ 98 │ - ······]
+ 70 │ + ······"http":·[200,·200]
+ 99 71 │ },
+ 100 72 │ {
+ 101 73 │ "id": "DB-SO-14",
+ 102 74 │ "result": "pass",
+ 103 │ - ······"http":·[
+ 104 │ - ········200,
+ 105 │ - ········200
+ 106 │ - ······]
+ 75 │ + ······"http":·[200,·200]
+ 107 76 │ },
+ 108 77 │ {
+ 109 78 │ "id": "DB-SO-15",
+ 110 79 │ "result": "pass",
+ 111 │ - ······"http":·[
+ 112 │ - ········409
+ 113 │ - ······]
+ 80 │ + ······"http":·[409]
+ 114 81 │ },
+ 115 82 │ {
+ 116 83 │ "id": "DB-SO-16",
+ 117 84 │ "result": "pass",
+ 118 │ - ······"http":·[
+ 119 │ - ········200,
+ 120 │ - ········200
+ 121 │ - ······]
+ 85 │ + ······"http":·[200,·200]
+ 122 86 │ },
+ 123 87 │ {
+ 124 88 │ "id": "DB-SO-17",
+ 125 89 │ "result": "pass",
+ 126 │ - ······"http":·[
+ 127 │ - ········200
+ 128 │ - ······]
+ 90 │ + ······"http":·[200]
+ 129 91 │ },
+ 130 92 │ {
+ 131 93 │ "id": "DB-SO-18",
+ 132 94 │ "result": "pass",
+ 133 │ - ······"http":·[
+ 134 │ - ········200,
+ 135 │ - ········200
+ 136 │ - ······]
+ 95 │ + ······"http":·[200,·200]
+ 137 96 │ },
+ 138 97 │ {
+ 139 98 │ "id": "DB-SO-19",
+ 87 more lines truncated
+
+
+.artifacts/resources-global-operating-systems/global-operating-systems-results.json format ━━━━━━━━━━
+
+ × Formatter would have printed the following content:
+
+ 10 10 │ "contract": 3,
+ 11 11 │ "gatesEnabledOnlyInDisposableTest": true
+ 12 │ - }
+ 12 │ + }
+ 13 │ +
+
+
+The number of diagnostics exceeds the limit allowed. Use --max-diagnostics to increase it.
+Diagnostics not shown: 159.
+Checked 2231 files in 1168ms. No fixes applied.
+Found 173 errors.
+Found 1 warning.
+Found 5 infos.
+check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Some errors were emitted while running checks.
+
+
+[ELIFECYCLE] Command failed with exit code 1.
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log
new file mode 100644
index 00000000..7cc3cb9d
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log
@@ -0,0 +1,9 @@
+
+/home/david/dev/metis-desk/modules/resources/src/common/locales/en.json
+ 0:0 warning File ignored because no matching configuration was supplied
+
+/home/david/dev/metis-desk/modules/resources/src/common/locales/es.json
+ 0:0 warning File ignored because no matching configuration was supplied
+
+✖ 2 problems (0 errors, 2 warnings)
+
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log
new file mode 100644
index 00000000..c1183f8e
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log
@@ -0,0 +1,471 @@
+$ biome lint .
+docs/auditorias/ficha-puesto-trabajo/implementacion-v3/mediciones.json lint ━━━━━━━━━━━━━━━━━━━━━━━━
+
+ i The size of the file is 2.5 MiB, which exceeds the configured maximum of 1.0 MiB for this project.
+ Use the `files.maxSize` configuration to change the maximum size of files processed, or `files.includes` to ignore the file.
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:33:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━
+
+ i Logical expression contains unnecessary complexity.
+
+ 32 │ async save(companyId: string, personId: string, data: Uint8Array) {
+ > 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+
+ i Safe fix: Reduce the complexity of the logical expression.
+
+ 31 31 │
+ 32 32 │ async save(companyId: string, personId: string, data: Uint8Array) {
+ 33 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)
+ 33 │ + ····if·(!(z.uuid().safeParse(companyId).success·&&·z.uuid().safeParse(personId).success))
+ 34 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ 35 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:49:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━
+
+ i Logical expression contains unnecessary complexity.
+
+ 48 │ async remove(companyId: string, personId: string) {
+ > 49 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ 51 │
+
+ i Safe fix: Reduce the complexity of the logical expression.
+
+ 47 47 │
+ 48 48 │ async remove(companyId: string, personId: string) {
+ 49 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)
+ 49 │ + ····if·(!(z.uuid().safeParse(companyId).success·&&·z.uuid().safeParse(personId).success))
+ 50 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ 51 51 │
+
+
+modules/companies/src/api/infrastructure/person-photo-processor.ts:18:9 lint/complexity/useSimplifiedLogicExpression FIXABLE ━━━━━━━━━━
+
+ i Logical expression contains unnecessary complexity.
+
+ 17 │ if (
+ > 18 │ !metadata.width ||
+ │ ^^^^^^^^^^^^^^^^^^
+ > 19 │ !metadata.height ||
+ │ ^^^^^^^^^^^^^^^^
+ 20 │ metadata.width > PERSON_PHOTO_LIMITS.inputDimension ||
+ 21 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension ||
+
+ i Safe fix: Reduce the complexity of the logical expression.
+
+ 16 16 │
+ 17 17 │ if (
+ 18 │ - ········!metadata.width·||
+ 19 │ - ········!metadata.height·||
+ 18 │ + ········!(metadata.width·&&metadata.height·)·||
+ 20 19 │ metadata.width > PERSON_PHOTO_LIMITS.inputDimension ||
+ 21 20 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension ||
+
+
+.agents/skills/shadcn/assets/showcase/color-pair.tsx:81:17 lint/style/noNegationElse FIXABLE ━━━━━━━━━━
+
+ ! Invert blocks when performing a negation test.
+
+ 79 │ }, [])
+ 80 │
+ > 81 │ const level = !info
+ │ ^^^^^
+ > 82 │ ? ""
+ ...
+ > 88 │ ? "AA Large"
+ > 89 │ : "Fail"
+ │ ^^^^^^^^
+ 90 │
+ 91 │ return (
+
+ i Safe fix: Invert the condition and the blocks.
+
+ 79 79 │ }, [])
+ 80 80 │
+ 81 │ - ··const·level·=·!info
+ 82 │ - ····?·""
+ 83 │ - ····:·info.ratio·>=·7
+ 81 │ + ··const·level·=·info
+ 82 │ + ····?·info.ratio·>=·7
+ 84 83 │ ? "AAA"
+ 85 84 │ : info.ratio >= 4.5
+ ······· │
+ 87 86 │ : info.ratio >= 3
+ 88 87 │ ? "AA Large"
+ 89 │ - ··········:·"Fail"
+ 88 │ + ··········:·"Fail"
+ 89 │ + ····:·""
+ 90 90 │
+ 91 91 │ return (
+
+
+.agents/skills/shadcn/assets/showcase/state-matrix.tsx:1:8 lint/style/useImportType FIXABLE ━━━━━━━━━━
+
+ × All these imports are only used as types.
+
+ > 1 │ import * as React from "react"
+ │ ^^^^^^^^^^^^^^^^^^^^^^^
+ 2 │
+ 3 │ import { cn } from "@/lib/utils"
+
+ i Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
+
+ i Safe fix: Use import type.
+
+ 1 │ import·type·*·as·React·from·"react"
+ │ +++++
+
+modules/companies/src/api/application/person-photo.use-cases.ts:25:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 24 │ async get(personId: string) {
+ > 25 │ if (!z.uuid().safeParse(personId).success)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 26 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 27 │ const photo = await this.repository.get(personId);
+ 28 │
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 23 23 │
+ 24 24 │ async get(personId: string) {
+ 25 │ - ····if·(!z.uuid().safeParse(personId).success)
+ 26 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 25 │ + ····if·(!z.uuid().safeParse(personId).success)·{
+ 26 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 27 │ + ····}
+ 27 28 │ const photo = await this.repository.get(personId);
+ 28 29 │
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:33:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 32 │ async save(companyId: string, personId: string, data: Uint8Array) {
+ > 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+ 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE"));
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 31 31 │
+ 32 32 │ async save(companyId: string, personId: string, data: Uint8Array) {
+ 33 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)
+ 34 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 33 │ + ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)·{
+ 34 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 35 │ + ····}
+ 35 36 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+ 36 37 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE"));
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:35:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ > 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 37 │ if (!(await this.repository.exists(companyId, personId)))
+ 38 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND"));
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 33 33 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ 34 34 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ 35 │ - ····if·(data.length·>·PERSON_PHOTO_LIMITS.inputBytes)
+ 36 │ - ······return·Result.fail(new·PersonPhotoError("PHOTO_TOO_LARGE"));
+ 35 │ + ····if·(data.length·>·PERSON_PHOTO_LIMITS.inputBytes)·{
+ 36 │ + ······return·Result.fail(new·PersonPhotoError("PHOTO_TOO_LARGE"));
+ 37 │ + ····}
+ 37 38 │ if (!(await this.repository.exists(companyId, personId)))
+ 38 39 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND"));
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:37:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+ 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE"));
+ > 37 │ if (!(await this.repository.exists(companyId, personId)))
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 38 │ return Result.fail(new PersonPhotoError("PERSON_NOT_FOUND"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 39 │ const processed = await this.processor.optimize(data);
+ 40 │
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 35 35 │ if (data.length > PERSON_PHOTO_LIMITS.inputBytes)
+ 36 36 │ return Result.fail(new PersonPhotoError("PHOTO_TOO_LARGE"));
+ 37 │ - ····if·(!(await·this.repository.exists(companyId,·personId)))
+ 38 │ - ······return·Result.fail(new·PersonPhotoError("PERSON_NOT_FOUND"));
+ 37 │ + ····if·(!(await·this.repository.exists(companyId,·personId)))·{
+ 38 │ + ······return·Result.fail(new·PersonPhotoError("PERSON_NOT_FOUND"));
+ 39 │ + ····}
+ 39 40 │ const processed = await this.processor.optimize(data);
+ 40 41 │
+
+
+modules/companies/src/api/application/person-photo.use-cases.ts:41:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 39 │ const processed = await this.processor.optimize(data);
+ 40 │
+ > 41 │ if (!processed.isSuccess) return Result.fail(processed.error);
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 42 │
+ 43 │ return (await this.repository.replace(companyId, personId, processed.data))
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 41 │ ····if·(!processed.isSuccess)·{·return·Result.fail(processed.error);·}
+ │ ++ ++
+
+modules/companies/src/api/application/person-photo.use-cases.ts:49:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 48 │ async remove(companyId: string, personId: string) {
+ > 49 │ if (!z.uuid().safeParse(companyId).success || !z.uuid().safeParse(personId).success)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 50 │ return Result.fail(new PersonPhotoError("INVALID_REQUEST"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 51 │
+ 52 │ return (await this.repository.replace(companyId, personId, null))
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 47 47 │
+ 48 48 │ async remove(companyId: string, personId: string) {
+ 49 │ - ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)
+ 50 │ - ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 49 │ + ····if·(!z.uuid().safeParse(companyId).success·||·!z.uuid().safeParse(personId).success)·{
+ 50 │ + ······return·Result.fail(new·PersonPhotoError("INVALID_REQUEST"));
+ 51 │ + ····}
+ 51 52 │
+ 52 53 │ return (await this.repository.replace(companyId, personId, null))
+
+
+modules/companies/src/api/application/process.use-cases.ts:127:5 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 125 │ const process = UniqueID.create(id);
+ 126 │
+ > 127 │ if (!(company.isSuccess && process.isSuccess))
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 128 │ return Promise.resolve(Result.fail(new ProcessError("INVALID_REQUEST")));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 129 │
+ 130 │ return this.repository.remove(company.data, process.data);
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 125 125 │ const process = UniqueID.create(id);
+ 126 126 │
+ 127 │ - ····if·(!(company.isSuccess·&&·process.isSuccess))
+ 128 │ - ······return·Promise.resolve(Result.fail(new·ProcessError("INVALID_REQUEST")));
+ 127 │ + ····if·(!(company.isSuccess·&&·process.isSuccess))·{
+ 128 │ + ······return·Promise.resolve(Result.fail(new·ProcessError("INVALID_REQUEST")));
+ 129 │ + ····}
+ 129 130 │
+ 130 131 │ return this.repository.remove(company.data, process.data);
+
+
+modules/companies/src/api/infrastructure/persistence/migrate-people.ts:11:3 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 9 │ const columns = await query.describeTable("company_contacts");
+ 10 │
+ > 11 │ if (!columns.photoVersion)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 12 │ await query.addColumn("company_contacts", "photoVersion", {
+ > 13 │ type: DataTypes.STRING(64),
+ > 14 │ allowNull: true,
+ > 15 │ });
+ │ ^^^
+ 16 │ for (const [name, length] of [
+ 17 │ ["position", 100],
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 9 9 │ const columns = await query.describeTable("company_contacts");
+ 10 10 │
+ 11 │ - ··if·(!columns.photoVersion)
+ 11 │ + ··if·(!columns.photoVersion)·{
+ 12 12 │ await query.addColumn("company_contacts", "photoVersion", {
+ 13 13 │ type: DataTypes.STRING(64),
+ 14 14 │ allowNull: true,
+ 15 │ - ····});
+ 15 │ + ····});
+ 16 │ + ··}
+ 16 17 │ for (const [name, length] of [
+ 17 18 │ ["position", 100],
+
+
+modules/companies/src/api/infrastructure/persistence/person-photo-migration.ts:10:3 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 8 │ );
+ 9 │
+ > 10 │ if (rows.some((row) => row.name === "person_photos")) return;
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 11 │ await database
+ 12 │ .getQueryInterface()
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 10 │ ··if·(rows.some((row)·=>·row.name·===·"person_photos"))·{·return;·}
+ │ ++ ++
+
+modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:49:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 47 │ });
+ 48 │
+ > 49 │ if (!person) return false;
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 50 │ if (photo)
+ 51 │ await PersonPhotoModel.upsert(
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 49 │ ······if·(!person)·{·return·false;·}
+ │ ++ ++
+
+modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:50:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 49 │ if (!person) return false;
+ > 50 │ if (photo)
+ │ ^^^^^^^^^^
+ > 51 │ await PersonPhotoModel.upsert(
+ > 52 │ { personId, companyId, data: Buffer.from(photo.data), version: photo.version },
+ > 53 │ { transaction }
+ > 54 │ );
+ > 55 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction });
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 56 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction });
+ 57 │
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 48 48 │
+ 49 49 │ if (!person) return false;
+ 50 │ - ······if·(photo)
+ 50 │ + ······if·(photo)·{
+ 51 51 │ await PersonPhotoModel.upsert(
+ 52 52 │ { personId, companyId, data: Buffer.from(photo.data), version: photo.version },
+ 53 53 │ { transaction }
+ 54 │ - ········);
+ 54 │ + ········);
+ 55 │ + ······}
+ 55 56 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction });
+ 56 57 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction });
+
+
+modules/companies/src/api/infrastructure/persistence/sequelize-person-photo.repository.ts:55:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 53 │ { transaction }
+ 54 │ );
+ > 55 │ else await PersonPhotoModel.destroy({ where: { personId }, transaction });
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 56 │ await person.update({ photoVersion: photo?.version ?? null }, { transaction });
+ 57 │
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 55 │ ······else·{·await·PersonPhotoModel.destroy({·where:·{·personId·},·transaction·});·}
+ │ ++ ++
+
+modules/companies/src/api/infrastructure/person-photo-processor.ts:17:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 15 │ }).metadata();
+ 16 │
+ > 17 │ if (
+ │ ^^^^
+ > 18 │ !metadata.width ||
+ ...
+ > 23 │ )
+ > 24 │ return Result.fail(new PersonPhotoError("INVALID_PHOTO"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 25 │ const output = await sharp(input)
+ 26 │ .rotate()
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 21 21 │ metadata.height > PERSON_PHOTO_LIMITS.inputDimension ||
+ 22 22 │ !["jpeg", "png", "webp"].includes(metadata.format ?? "")
+ 23 │ - ······)
+ 24 │ - ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO"));
+ 23 │ + ······)·{
+ 24 │ + ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO"));
+ 25 │ + ······}
+ 25 26 │ const output = await sharp(input)
+ 26 27 │ .rotate()
+
+
+modules/companies/src/api/infrastructure/person-photo-processor.ts:34:7 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
+
+ × Block statements are preferred in this position.
+
+ 32 │ .toBuffer();
+ 33 │
+ > 34 │ if (output.length > PERSON_PHOTO_LIMITS.outputBytes)
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ > 35 │ return Result.fail(new PersonPhotoError("INVALID_PHOTO"));
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 36 │
+ 37 │ return Result.ok({
+
+ i Unsafe fix: Wrap the statement with a `JsBlockStatement`
+
+ 32 32 │ .toBuffer();
+ 33 33 │
+ 34 │ - ······if·(output.length·>·PERSON_PHOTO_LIMITS.outputBytes)
+ 35 │ - ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO"));
+ 34 │ + ······if·(output.length·>·PERSON_PHOTO_LIMITS.outputBytes)·{
+ 35 │ + ········return·Result.fail(new·PersonPhotoError("INVALID_PHOTO"));
+ 36 │ + ······}
+ 36 37 │
+ 37 38 │ return Result.ok({
+
+
+The number of diagnostics exceeds the limit allowed. Use --max-diagnostics to increase it.
+Diagnostics not shown: 1.
+Checked 2231 files in 995ms. No fixes applied.
+Found 15 errors.
+Found 1 warning.
+Found 5 infos.
+lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Some errors were emitted while running checks.
+
+
+[ELIFECYCLE] Command failed with exit code 1.
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log
new file mode 100644
index 00000000..25c77392
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log
@@ -0,0 +1,8 @@
+/home/david/dev/metis-desk/apps/server/dist/index.js:23345
+import { createRequire } from "module";
+ ^^^^^^^^^^^^^
+
+SyntaxError: Identifier 'createRequire' has already been declared
+ at checkSyntax (node:internal/main/check_syntax:72:5)
+
+Node.js v24.20.0
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log
new file mode 100644
index 00000000..92b6297b
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log
@@ -0,0 +1 @@
+{"diagnostic":"TLS termination followed by HTTP to backend","trustProxy":false,"http":200,"secure":false,"sessionCookieIssued":false,"databaseUsed":false}
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log
new file mode 100644
index 00000000..f13e293d
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log
@@ -0,0 +1 @@
+$ tsc -p tsconfig.json
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log
new file mode 100644
index 00000000..f13e293d
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log
@@ -0,0 +1 @@
+$ tsc -p tsconfig.json
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log
new file mode 100644
index 00000000..353ad63c
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log
@@ -0,0 +1,133 @@
+$ TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/api/application/application-coincidence.test.ts src/api/domain/resource-catalog.test.ts src/api/domain/resource.test.ts src/api/domain/resource-template-propagation.test.ts src/api/application/resource-lifecycle.test.ts src/web/catalog/shared/catalog.test.tsx src/web/resources/shared/resources.test.tsx src/web/resources/list/helpers/resource-columns.test.tsx src/web/resources/list/helpers/resource-list-preferences.test.tsx src/web/resources/shared/resource-values.test.ts src/api/domain/template-sections.test.ts src/web/catalog-fields/select/catalog-field-selection.test.tsx src/api/domain/directory-domain.test.ts src/api/domain/network-share.test.ts src/api/domain/resource-scope.test.ts src/api/infrastructure/persistence/resource-scope-reader.test.ts src/api/application/resource-scope.test.ts src/api/application/resource-reference-contracts.test.ts src/api/domain/global-resource.test.ts src/web/global-applications/shared/global-applications.test.tsx src/api/domain/operating-system-identity.test.ts src/api/application/global-operating-system-contracts.test.ts src/api/application/global-operating-system-policy.test.ts src/web/resources/shared/ui/resource-characteristic-control.test.tsx
+✔ R2.6: nombre y proveedor son señales, nunca equivalencia confirmada (1.231616ms)
+✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.376204ms)
+✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (22.305088ms)
+✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (8.075287ms)
+✔ F2: elegibilidad de SO exige stock real y campos globales (0.322503ms)
+✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.299603ms)
+✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (34.291168ms)
+✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (12.676836ms)
+✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (2.464926ms)
+✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (2.204824ms)
+✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (6.674072ms)
+✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (1.905821ms)
+✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (12.243932ms)
+✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (4.950553ms)
+✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (5.450259ms)
+✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.405605ms)
+✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.437904ms)
+✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.317003ms)
+✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (17.228284ms)
+✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (396.325645ms)
+✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (2.207924ms)
+✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (2.272924ms)
+✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (6.923375ms)
+✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (15.121263ms)
+✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (0.825809ms)
+✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (12.355032ms)
+✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (3.562338ms)
+✔ SMB acepta \\NAS-01\Finanzas (2.271825ms)
+✔ SMB acepta \\files.example.local\Docs (0.293204ms)
+✔ SMB acepta \\192.168.5.10\C$ (0.164602ms)
+✔ SMB acepta \\samba\Diseño\Año 2026 (0.422204ms)
+✔ SMB acepta \\NAS\Docs\ (0.330904ms)
+✔ SMB rechaza "" (0.228603ms)
+✔ SMB rechaza "\\\\" (0.250303ms)
+✔ SMB rechaza "\\\\host" (0.133901ms)
+✔ SMB rechaza "\\\\\\share" (2.418226ms)
+✔ SMB rechaza "\\\\host\\\\" (0.550606ms)
+✔ SMB rechaza "C:\\Datos" (0.152901ms)
+✔ SMB rechaza "https://host/share" (0.100001ms)
+✔ SMB rechaza "\\\\host\\share\\.." (0.321703ms)
+✔ SMB rechaza "\\\\host\\share\\." (0.099201ms)
+✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.164002ms)
+✔ SMB rechaza "\\\\host\\share\\\\child" (0.210603ms)
+✔ SMB rechaza "\\\\host\\share." (0.262203ms)
+✔ SMB rechaza "\\\\host\\ share" (0.093101ms)
+✔ SMB rechaza "\\\\ho st\\share" (0.0716ms)
+✔ SMB rechaza "\\\\host\\bad\u0000" (0.092901ms)
+✔ SMB rechaza "\\\\host\\bad\ud800" (0.118101ms)
+✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.0877ms)
+✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (1.280214ms)
+✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (19.661911ms)
+✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (17.875892ms)
+✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (8.36469ms)
+✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (4.554549ms)
+✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.822309ms)
+✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (32.804952ms)
+✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (14.087251ms)
+✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (37.378401ms)
+✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (8.182888ms)
+✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (19.636311ms)
+✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (12.740137ms)
+✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (11.259221ms)
+✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (8.40179ms)
+✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (18.962003ms)
+✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (11.14542ms)
+✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (7.788183ms)
+✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (14.326653ms)
+✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (5.966463ms)
+✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (2.87503ms)
+✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (29.87312ms)
+✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (5.910163ms)
+✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.721708ms)
+✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (11.846427ms)
+✔ recurso: identidad estable, historial, desactivación e idempotencia (4.007143ms)
+✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (3.488938ms)
+✔ secciones: nombres, identidades, referencias y un solo nivel (11.626625ms)
+✔ secciones: igualdad, orden, identidad y persistencia JSON (6.419668ms)
+✔ secciones: plantillas globales y locales conservan separación y UUID al editar (6.713372ms)
+✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (21.021825ms)
+✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (13.471245ms)
+✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (18.6734ms)
+✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (6.083365ms)
+✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (99.064659ms)
+✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (1.365715ms)
+✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (4.082543ms)
+✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.319414ms)
+✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (2.85603ms)
+✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (108.042052ms)
+✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (167.398384ms)
+✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (115.143825ms)
+✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (50.526938ms)
+✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (229.571942ms)
+✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (36.045985ms)
+✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (3.423236ms)
+✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (1.158612ms)
+✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (11.661124ms)
+✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.589217ms)
+✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (23.45335ms)
+✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (73.833287ms)
+✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (2.829631ms)
+✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (31.745039ms)
+✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (16.513977ms)
+✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (1.108812ms)
+✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.174612ms)
+✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.422305ms)
+✔ la integración de buzón conserva su configuración explícita y no inventa referencias (3.047833ms)
+✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (1.86692ms)
+✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (19.457508ms)
+✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (1.662218ms)
+✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (0.523206ms)
+✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (13.524045ms)
+✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (127.894465ms)
+✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (421.335414ms)
+✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (17.78119ms)
+✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (32.084143ms)
+✔ asistencia: alta de recurso con plantilla específica y características opcionales (6.850473ms)
+✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (14.455054ms)
+✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (12.246331ms)
+✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (24.829866ms)
+✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (18.940503ms)
+✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (23.082547ms)
+✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (19.031704ms)
+✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (9.3907ms)
+✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (33.183154ms)
+ℹ tests 124
+ℹ suites 0
+ℹ pass 124
+ℹ fail 0
+ℹ cancelled 0
+ℹ skipped 0
+ℹ todo 0
+ℹ duration_ms 5080.204188
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log
new file mode 100644
index 00000000..2e50a094
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log
@@ -0,0 +1,17 @@
+✔ las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después (63.643378ms)
+✔ las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia (10.891116ms)
+✔ datos locales inválidos no rompen el listado y solo se aceptan valores booleanos (0.372704ms)
+✔ almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local (0.403304ms)
+✔ buscar columnas ignora tildes y mayúsculas, conserva ñ y no modifica visibilidad (1.039011ms)
+✔ los grupos conservan orden, omiten vacíos y deduplican IDs sin perder columnas sin grupo (0.485705ms)
+✔ restaurar preferencias vacías recupera defaults actuales sin tocar otras tablas (9.4554ms)
+✔ los contadores distinguen visibilidad, total y coincidencias en es/en (0.262903ms)
+✔ listado remoto: recupera columnas por ámbito y protege nombre y acciones (20.791321ms)
+ℹ tests 9
+ℹ suites 0
+ℹ pass 9
+ℹ fail 0
+ℹ cancelled 0
+ℹ skipped 0
+ℹ todo 0
+ℹ duration_ms 1969.971224
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log
new file mode 100644
index 00000000..0a3452b2
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log
@@ -0,0 +1,29 @@
+$ turbo run typecheck
+• turbo 2.9.14
+
+ • Packages in scope: @metis/assets, @metis/companies, @metis/core, @metis/identity, @metis/relationship-contracts, @metis/resources, @metis/server, @metis/web, @repo/rdx-ddd, @repo/rdx-ui, @repo/rdx-utils, @repo/shadcn-ui, @repo/typescript-config
+ • Running typecheck in 13 packages
+ • Remote caching disabled
+
+@repo/rdx-utils:typecheck: cache hit, replaying logs e660b2f14d16d88c
+@repo/rdx-utils:typecheck: $ tsc -p tsconfig.json
+@repo/shadcn-ui:typecheck: cache miss, executing 3a02880bf260fd89
+@repo/rdx-ddd:typecheck: cache hit, replaying logs 315b0dccfb6d76d3
+@repo/rdx-ddd:typecheck: $ tsc -p tsconfig.json
+@metis/relationship-contracts:typecheck: cache hit, replaying logs 6ecbe82d1a36ae07
+@metis/relationship-contracts:typecheck: $ tsc -p tsconfig.json
+@repo/shadcn-ui:typecheck: $ tsc -p tsconfig.json
+@repo/shadcn-ui:typecheck: src/components/calendar.tsx(165,12): error TS2375: Type '{ day: CalendarDay; modifiers: Modifiers; disabled?: boolean | undefined; form?: string | undefined; formAction?: string | ((formData: FormData) => void | Promise<...>) | undefined; ... 285 more ...; locale: Partial<...> | undefined; }' is not assignable to type '{ locale?: Partial; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
+@repo/shadcn-ui:typecheck: Types of property 'locale' are incompatible.
+@repo/shadcn-ui:typecheck: Type 'Partial | undefined' is not assignable to type 'Partial'.
+@repo/shadcn-ui:typecheck: Type 'undefined' is not assignable to type 'Partial'.
+@repo/shadcn-ui:typecheck: [ELIFECYCLE] Command failed with exit code 2.
+ ERROR @repo/shadcn-ui#typecheck: command (/home/david/dev/metis-desk/packages/shadcn-ui) /home/david/.nvm/versions/node/v24.20.0/bin/pnpm run typecheck exited (2)
+
+ Tasks: 3 successful, 4 total
+Cached: 3 cached, 4 total
+ Time: 4.958s
+Failed: @repo/shadcn-ui#typecheck
+
+ ERROR run failed: command exited (2)
+[ELIFECYCLE] Command failed with exit code 2.
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log
new file mode 100644
index 00000000..f13e293d
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log
@@ -0,0 +1 @@
+$ tsc -p tsconfig.json
diff --git a/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json
new file mode 100644
index 00000000..7e4ad4be
--- /dev/null
+++ b/.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json
@@ -0,0 +1,112 @@
+{
+ "head": "6992a4ac745acf6b474a0f22d1a859b861119452",
+ "branch": "main",
+ "initialWorkingTree": "clean",
+ "baseGoHead": "d2bf856c973419277f0b30f6437242cd82f7d720",
+ "unchangedRecordedSources": 1875,
+ "changedRecordedSources": [
+ {
+ "path": "modules/resources/src/common/locales/en.json",
+ "before": "a1dd1e416af87cd94ffa2c80855e9e809e3b62b1ddb679a4688d2adbd67ccc9e",
+ "candidate": "38b1298bdee2c8f4f547669f5d766faaceb6d22412479da301fd96165f17f93b"
+ },
+ {
+ "path": "modules/resources/src/common/locales/es.json",
+ "before": "edc6151d0bfac31da3a1fed555231aa7eae5b7cb71779533ea503dbe38eb986c",
+ "candidate": "f95606f21c1a25b58696a9d10e4853190af43894ff536971d59f31ece3ba008e"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/controllers/use-company-applications-controller.ts",
+ "before": "050abdeb0f351ba85ea001ad94a450fc174d0d6d0c871da41949fee1732620a3",
+ "candidate": "7ce972f6a5f0542656b096f886ae058bd92dc9aef7beee21e1e327a4348cc28f"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/controllers/use-resources-page-controller.ts",
+ "before": "10d3101bbad439537af0fb6195e199484641d2a9bc59d50a1ddc29044ebe285c",
+ "candidate": "72a55dedce077c6372272ef95b05575f9efced513b4e6ea912c4af423cf3cc11"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/ui/company-applications-list.tsx",
+ "before": "73dced13ad6d1adaff9d10a1b8ad78a402e3b69fd54230d7c13c2a2353123660",
+ "candidate": "e9290d45c77ed97f28c205e64358465a4e1cd6674bad393b2d0e1d11696b9162"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/ui/resource-inventory-table.tsx",
+ "before": "de3d16f1df8dd869a1e692be3e35a23927475c5efdd5ec88aa1a651783052400",
+ "candidate": "b3e42461e88e9e1941fedab69b6c2e49ca539fb9cb3d90ccd3cb529587da156b"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/ui/resources-page.tsx",
+ "before": "d87fa2506625f88da5fdb42b79d046f06620e1e98cf278c28f55d87eae06d528",
+ "candidate": "c9419e1661531f363a949473b0a41b5b345afdad3b8c0b9c70973e5c23379e2a"
+ },
+ {
+ "path": "packages/rdx-ui/src/components/cursor-entity-table.tsx",
+ "before": "6505c4f913b639038a71b41b169cd2382583359585795ac32fbbcfb4c92f25e7",
+ "candidate": "9a5398dc82dd3935682c39e59e8e6a6e1e023f5fa60496afa6b2ee851fa335b2"
+ },
+ {
+ "path": "packages/rdx-ui/src/entity-column-visibility.test.tsx",
+ "before": "95aa7c35e5f70f8d61cd1ae5e08edfb591d24c3d85466d7eba4cdf2a4f3e3145",
+ "candidate": "5ead7a4b1dd78a73a6ed54d1f7fa1238d33822496cc41391215edc1799aedc6d"
+ }
+ ],
+ "addedSources": [
+ {
+ "path": "modules/resources/src/web/resources/list/controllers/use-resource-list-navigation.ts",
+ "sha256": "7d773755a67618becca56290322668112fac7ec0a82373a23b5b904c589cc707"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/helpers/resource-additional-columns.tsx",
+ "sha256": "21a6391388f44a486165dce21e98d0eec5a84414682d9a586751479a68c7078e"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/helpers/resource-characteristic-columns.ts",
+ "sha256": "8865cbc2a88a235b17100d276be8a98d005cf948102b5816a73c1b711f75c6b0"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/helpers/resource-columns.test.tsx",
+ "sha256": "38ef794093816780b13973f2a3d8444a73b0f2159bc89d5d1d50bb36aff54487"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/helpers/resource-list-preferences.test.tsx",
+ "sha256": "5cddfa7c3dd91484d42e504e8d95898c3df2b8753dce2ce869a669a023b55f22"
+ },
+ {
+ "path": "modules/resources/src/web/resources/list/helpers/resource-list-preferences.ts",
+ "sha256": "e7bd6e27facdf032d18e1a5260b0e56cbe3950ccadd1e7bcee52836a5b5a7633"
+ }
+ ],
+ "workingGoDifferences": ["docs/componentes-de-interfaz.md", "modules/assets/src/web/index.ts"],
+ "lockfileSha256": "bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17",
+ "functionalBackendChanges": false,
+ "newVisualValidationRequired": true,
+ "concurrentUnvalidatedSources": [
+ {
+ "path": "modules/assets/src/web/index.ts",
+ "sha256": "d13dfe0b910d694643a878dff9d2439e7b68bc83045e6c3521f8a79e29453929"
+ },
+ {
+ "path": "modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx",
+ "sha256": "e50db875ea633ceecc6763f4d4f4c683efadd3be2e85d5249d3f1fdc23e79198"
+ },
+ {
+ "path": "modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx",
+ "sha256": "d73da6731f6de334f216fa901c65b5127b791916a93ab8c001153610025ddacf"
+ },
+ {
+ "path": "modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts",
+ "sha256": "b774f445c5b97f31e42251df9a642b06661dc57ad26361eb8b9e8a5d0c54ef8c"
+ },
+ {
+ "path": "modules/companies/src/web/companies/shared/companies-page.test.tsx",
+ "sha256": "2db363fb74b145aed6a6749f62e1e0c77a6a1f77d410a1714aef82d79bb4a10f"
+ },
+ {
+ "path": "modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts",
+ "sha256": "e5455d36a326e82fb23ea68a74578dcd4938fa745b74cf81867522615e3b01e6"
+ }
+ ],
+ "recordedAtUtc": "2026-10-26T11:00:51.729096+00:00",
+ "comparisonMethod": "GO hashes compared to git archive of candidate HEAD; subsequent workspace changes recorded separately"
+}
diff --git a/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md b/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md
index db34db82..a35ce6c7 100644
--- a/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md
+++ b/docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md
@@ -2,6 +2,8 @@
Fecha: **26 de octubre de 2026**, Europe/Madrid. Inspección iniciada el día 25. Ámbito: repositorio local y diagnósticos locales sin BD. **No se ha conectado a producción, leído `.env`, ejecutado migraciones, modificado datos reales, cambiado gates ni desplegado.**
+**Reevaluación vigente: commit `6992a4ac745acf6b474a0f22d1a859b861119452`, árbol inicialmente limpio, NO PREPARADO.** Los apartados 1–14 conservan el preflight del candidato anterior; el apartado 15 identifica las diferencias, las ejecuciones nuevas y el alcance actual de sus evidencias.
+
## 1. Resumen y dictamen
**NO PREPARADO para desplegar el artefacto de producción evaluado.** Se conserva la correspondencia de fuentes con el GO técnico, pero el preflight descubre dos bloqueos de ejecución/configuración que ese GO no había ensayado:
@@ -236,3 +238,76 @@ Responsables requeridos: mantenedor del build para B-01; backend y operación pa
Condiciones antes de autorizar: artefacto compilado válido y arranque aislado comprobado; sesiones Secure a través de la topología aprobada; identidad de release congelada; destino identificado; inventario de clientes; metadata/datos verificados con autorización; backup/restauración aprobados; smoke y responsables acordados; tratamiento explícito de CI no verde. No cambiar flags como sustituto de cumplir estas condiciones.
**Dictamen final: NO PREPARADO**, referido al snapshot reproducido y al artefacto productivo generado localmente. El entorno real sigue pendiente. El informe prepara las decisiones y acciones necesarias; no solicita, concede ni ejecuta autorización de despliegue o activación.
+
+## 15. Reevaluación del candidato confirmado en Git
+
+Fecha: 26 de octubre de 2026. Solicitud reiterada de preflight; no autoriza corregir funcionalidad, migrar, desplegar ni activar. Se utilizó `rdx-solid` para limitar las comprobaciones a diferencias reales y a los bloqueos productivos ya detectados.
+
+### 15.1. Identificación y diferencias respecto al GO
+
+| Requisito | Estado actual | Evidencia |
+| --- | --- | --- |
+| Candidato actual | VERIFICADO | `main`, `6992a4ac745acf6b474a0f22d1a859b861119452`; `git status --short` vacío antes de intervenir |
+| Trazabilidad del código candidato | VERIFICADO | Ahora está confirmado en Git; reproducible mediante checkout del commit y dependencias fijadas. No necesita el overlay anterior para identificar sus fuentes |
+| Igualdad completa con el GO | NO CUMPLE | De 1884 fuentes registradas: **1875 iguales y 9 modificadas**; hay **6 fuentes adicionales** en los mismos ámbitos |
+| Archivos de trabajo del GO | VERIFICADO con diferencia documental | De los 40 registrados, 39 coinciden; cambió `docs/componentes-de-interfaz.md` |
+| Node/PNPM y dependencias | VERIFICADO en instalación/manifests | 24.20.0 / 11.25.0; lockfile conserva SHA-256 `bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17` |
+| Instalación limpia de dependencias del destino | PENDIENTE DE VERIFICACIÓN | No se ejecuta install ni se cambia configuración privada |
+
+Los commits `07082001` y `6992a4ac` incorporan el trabajo previamente sin commit y cambios adicionales. Se compararon contenidos con los manifiestos del GO; no se atribuye todo `git diff d2bf856c..HEAD` a una diferencia funcional posterior, porque también contiene aquel trabajo ya validado.
+
+Durante la reevaluación aparecieron cambios concurrentes sin commit en `modules/assets/src/web/index.ts`, `infrastructure-page.tsx`, `use-company-detail-controller.ts`, el nuevo helper `infrastructure-items.ts` y pruebas `infrastructure.test.tsx` / `companies-page.test.tsx`. Se preservaron. **El candidato de esta tabla es el commit, no el árbol concurrente final**: sus diferencias se verificaron leyendo los objetos de `git archive HEAD`. Los hashes del trabajo concurrente se registran aparte como no validados en la fecha de captura; las ejecuciones de esta intervención no certifican esas modificaciones posteriores. Una release que las incorpore necesita nueva identificación y análisis de impacto.
+
+| Diferencia | Clasificación | Impacto y validación |
+| --- | --- | --- |
+| Columnas adicionales de características/recursos y sus pruebas | Frontend | Cambia el contenido configurable del inventario; Resources reejecutado |
+| Preferencias por compañía/plantilla, precedencia de URL y persistencia de orden | Frontend | Afecta navegación y contexto de listados; pruebas nuevas incluidas en Resources |
+| `CursorEntityTable` y prueba de visibilidad | Frontend compartido | Puede afectar también listados globales; reejecutada la suite específica de columnas/tablas. Validación visual actual pendiente |
+| Traducciones es/en de Resources | Frontend/textos | Fuentes distintas del GO; no certificar con capturas anteriores |
+| Guía de listados y componentes | Documentación | Revisada como patrón vigente; no cambia esquema/contrato |
+| Script de test en `modules/resources/package.json` | Configuración de validación | Añade nuevas pruebas; no cambia dependencias runtime ni lockfile |
+| DTO HTTP, lógica backend, persistencia, seguridad, gates y configuración de build | Sin diferencia funcional identificada respecto al GO | Las diferencias de `common` son locales es/en; los nuevos archivos server son las fixtures/tests del cierre ya registrados en el GO |
+
+Las nueve fuentes modificadas y seis nuevas, con hashes, están enumeradas en [traceability.json](../../.artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json). No hay bloqueo de identificación del candidato. **No se traslada el GO íntegro al frontend cambiado**: corresponde validar visualmente los nuevos listados/componente compartido antes de una futura aprobación de release.
+
+### 15.2. Artefactos y bloqueos reproducidos
+
+Se ejecutó `pnpm exec turbo run build --force`: servidor y web compilan, dos tareas correctas. Se calcularon hashes de los 12 archivos dist. **El dist del servidor coincide byte a byte con el del preflight anterior; el dist web es distinto**. Por tanto, la correspondencia del frontend antiguo con MCP descrita en el apartado 3 no se aplica al candidato actual.
+
+- **B-01 sigue NO CUMPLE:** nuevo `node --check apps/server/dist/index.js` devuelve 1, `Identifier 'createRequire' has already been declared`. `tsup.config.ts` conserva el banner y el bundle incluye el segundo import procedente de sharp. La compilación correcta no acredita ejecución; no se arrancó ese servidor ni se abrió BD.
+- **B-02 sigue NO CUMPLE para TLS→HTTP:** el diagnóstico conservado se ejecutó con el `createApp` actual. Resultado nuevo: `trustProxy=false`, `http=200`, `secure=false`, `sessionCookieIssued=false`, `databaseUsed=false`. Salida cero del diagnóstico significa que terminó el experimento, no que la sesión de producción funcione.
+
+No se ha configurado confianza en proxy, relajado Secure, cambiado `NODE_ENV`, editado el bundle ni modificado sharp para eludir los bloqueos. La solución de TLS/proxy debe ser aprobada y acotada al proxy real, sin confiar indiscriminadamente en cabeceras reenviadas.
+
+### 15.3. Validaciones dirigidas nuevas y deuda actual
+
+| Comando / ámbito | Resultado nuevo |
+| --- | --- |
+| `pnpm --filter @metis/resources test`, con loopback permitido | **124/124**, incluido caché, contratos y pruebas nuevas de columnas/preferencias |
+| `node --import tsx --test --test-isolation=none src/entity-column-visibility.test.tsx`, desde rdx-ui con TSX_TSCONFIG_PATH del paquete | **9/9** |
+| Typecheck directo Resources, Web y rdx-ui | Los tres correctos |
+| Biome y ESLint sobre las 15 fuentes modificadas/nuevas | Ambos correctos |
+| Build forzado | Dos tareas correctas; advertencia de tamaño de chunk web |
+| Parse del servidor compilado | **Fallo bloqueante B-01**, salida 1 |
+| Diagnóstico de sesión local | Experimento completado; demuestra **B-02** |
+| `pnpm typecheck` raíz | Falla en calendario vendor por `locale` opcional, salida 2 |
+| `pnpm lint` raíz | 15 errores, 1 warning, 5 infos; salida 1 |
+| `pnpm check` raíz | 173 errores, 1 warning, 5 infos; salida 1 |
+
+No se han corregido incidencias ajenas. Los propietarios y clasificación de deuda del apartado 12 siguen aplicándose: el parse inválido es bloqueante, no una excepción aceptable por antigüedad; los fallos generales requieren tratamiento explícito de CI. Los dos fallos previos de ayuda de Companies son antecedentes, no una ejecución nueva. No se repitió la suite completa; posteriormente se modificó concurrentemente `companies-page.test.tsx`, por lo que su estado en ese trabajo posterior queda pendiente, sin afirmar que sus dos fallos persistan o estén resueltos allí.
+
+No se repitieron MariaDB/HTTP-SQL con fixtures ni Playwright con preparación persistente: no cambió el backend/contrato/persistencia y este preflight no autoriza escrituras de negocio o migraciones. Las 29/29 + 50/50 y las 35 comprobaciones/69 capturas siguen siendo antecedentes con su alcance anterior. Para el nuevo frontend se requiere validación visual dirigida de columnas, preferencias, contexto y listados globales, en entorno aislado autorizado para preparar datos; las pruebas de componente no sustituyen esa comprobación.
+
+### 15.4. Entorno, gates, rollback y autorización
+
+No hay nueva evidencia autorizada del destino. Motor/versión/esquema aplicado, plantilla real, FKs/CHECK/índices, clientes desplegados, proxy, sesiones, supervisor, backup/restore y valores efectivos de gates continúan **PENDIENTES DE VERIFICACIÓN**. No se infieren de defaults ni del compose de desarrollo. El SQL del apartado 6 sigue preparado y **no ejecutado**; sus tablas/migraciones no cambiaron.
+
+Se verificó que los seis nombres de flags y sus dependencias siguen iguales. El plan de los apartados 8–11 sigue vigente como propuesta: persistencia compatible → lectores v3 → frontend coordinado → OS_CATALOG administrativo → OS_SOFTWARE, que abre **selección y asociación juntas**. No hay gate por compañía ni se añaden flags para simularlo. Mantener los cuatro flags application según su autorización independiente.
+
+El rollback propuesto conserva lectores v3, UUID, productos, instalaciones e historiales; cierra nuevas dependencias y altas, permite las retiradas legítimas ensayadas y no activa D08.2. No es un procedimiento productivo ya ejecutado. Los smoke tests de lectura y escritura siguen separados; ninguna escritura, conexión al destino o activación se ejecutó en esta reevaluación.
+
+### 15.5. Evidencias y dictamen vigente
+
+Ejecuciones nuevas, diferencias y hashes: [manifiesto 6992a4ac](../../.artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json). El preflight anterior y sus evidencias se conservan sin sobrescribirlos. Esta intervención modifica únicamente este informe y añade evidencias diagnósticas; los cambios concurrentes ajenos permanecen sin certificar.
+
+**NO PREPARADO**, para `6992a4ac745acf6b474a0f22d1a859b861119452` y sus artefactos generados localmente. Primero deben resolverse B-01 y la configuración aplicable de B-02, verificarse el artefacto ejecutable y revalidarse el frontend cambiado. Después faltan las comprobaciones y autorizaciones del destino enumeradas arriba. El código está identificado, pero eso no acredita una release operativa ni autoriza publicarla.
diff --git a/docs/componentes-de-interfaz.md b/docs/componentes-de-interfaz.md
index 8376dea2..03f488f0 100644
--- a/docs/componentes-de-interfaz.md
+++ b/docs/componentes-de-interfaz.md
@@ -1,5 +1,29 @@
# Componentes de interfaz
+## Submenú de Personas en la navegación de compañía
+
+`CompanySectionNavigation` compone `SidebarMenu` y `SidebarMenuItem` de shadcn/ui
+dentro del contenedor contextual existente. `CompanyPeopleNavigation` utiliza
+`SidebarMenuSub`, `SidebarMenuSubItem` y `SidebarMenuSubButton` con `BrowserLink`
+mediante `render`. Mantiene las tres vistas de Personas en el panel fijo y en el Sheet,
+con selección por URL, listas anidadas semánticas y expansión al entrar en la sección.
+No requiere otro SidebarProvider ni cambia el controlador responsive del shell.
+Los contadores de las entradas principales conservan `CountBadge` de rdx-ui.
+
+Verificado el 26 de octubre de 2026 mediante Playwright MCP en una instalación
+desechable de `metis_desk_test`: 15 compañías y una principal con 50 personas,
+51 activos y 66 recursos. Las vistas presentan 12 contactos, 43 activas y 7 inactivas.
+Se comprobaron 1440×900, 390×844 y 320×640, teclado, selección, cierre del Sheet y
+retorno del foco, recarga, vuelta de ficha, búsqueda/orden, reinicio de página,
+vacío y limpieza de filtros. Evidencias locales en
+`.artifacts/playwright/authenticated-6t1tczdv/people-navigation*.{png,json}`;
+las capturas de escritorio y móvil se abrieron e inspeccionaron. No se declara
+una auditoría completa de accesibilidad ni de estados de error de red.
+
+Skills consultadas: `ux-navigation` para jerarquía y orientación, `shadcn` para la
+composición mediante `render`, `vercel-react-best-practices` para derivar selección
+de la URL y `rdx-solid` para responsabilidades y pruebas. No se modificó vendor.
+
Metis Desk usa shadcn/ui, con Base UI y el estilo base-vega, siguiendo la organización de factuges-erp.
`packages/shadcn-ui` contiene únicamente el código del registro oficial y no se adapta ni se
extiende desde este proyecto. Se consume mediante `@repo/shadcn-ui`.
diff --git a/docs/rutas-del-cliente.md b/docs/rutas-del-cliente.md
index ca2e7305..3970b262 100644
--- a/docs/rutas-del-cliente.md
+++ b/docs/rutas-del-cliente.md
@@ -23,6 +23,14 @@ autenticar se recupera ese destino. No se aceptan redirecciones a otros orígene
## Espacio de compañía
+En Personas, `/companies/:companyId/people?contactsOnly=true` abre los contactos de la
+compañía; `?active=true` y `?active=false` muestran las personas activas e inactivas.
+Sin estos filtros se muestran todas. El menú despliega estas tres subentradas al entrar
+en Personas, tanto en el panel fijo como en el Sheet. Cada enlace conserva búsqueda y
+orden, reinicia la página y reemplaza los filtros de la vista anterior. El filtro de estado
+del listado comparte la misma URL; puede combinarse con Solo contactos. La ficha y el
+retorno conservan ambos criterios. `active` no habilita cursor ni pageSize en Personas.
+
Todas las secciones siguientes cuelgan de `/companies/:companyId`:
```text
diff --git a/modules/assets/src/web/index.ts b/modules/assets/src/web/index.ts
index 64924e2e..fe63915a 100644
--- a/modules/assets/src/web/index.ts
+++ b/modules/assets/src/web/index.ts
@@ -14,6 +14,7 @@ export {
infrastructureApi,
infrastructureKeys,
} from "./infrastructure/shared/api/infrastructure-api";
+export { getInfrastructureItems } from "./infrastructure/shared/helpers/infrastructure-items";
export { NetworkSharePublications } from "./network-share-publications/detail/ui/network-share-publications";
export { PublicationPicker } from "./network-share-publications/select/ui/publication-picker";
export { assetsEnglish, assetsSpanish } from "./shared";
diff --git a/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx b/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx
index 4dbf86e2..c5bdec6f 100644
--- a/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx
+++ b/modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx
@@ -17,6 +17,7 @@ import { InventoryFeedback } from "../../../shared";
import { inventoryApi } from "../../../shared/api";
import { inventoryKeys } from "../../../shared/queries";
import { infrastructureApi, infrastructureKeys } from "../../shared/api/infrastructure-api";
+import { getInfrastructureItems } from "../../shared/helpers/infrastructure-items";
import { infrastructureRoles } from "../../shared/helpers/infrastructure-roles";
import { InfrastructureEditorDialog } from "../../shared/ui/infrastructure-editor-dialog";
@@ -39,16 +40,11 @@ export function InfrastructurePage({ companyId }: { companyId: string }) {
});
const assets = inventory.data?.assets ?? [];
const editing = assets.find((asset) => asset.id === editingId);
- const items = assets.flatMap((asset) => {
- const config = network.data?.items.find((item) => item.assetId === asset.id);
-
- const roles = infrastructureRoles(asset.values.templateId, catalog.data?.items ?? [], config);
-
- return (roles.length || config?.values.interfaces.length) &&
- (!role || roles.some((value) => value === role))
- ? [{ ...asset, config: { roles, interfaces: config?.values.interfaces ?? [] } }]
- : [];
- });
+ const items = getInfrastructureItems(
+ assets,
+ catalog.data?.items ?? [],
+ network.data?.items ?? []
+ ).filter((asset) => !role || asset.config.roles.some((value) => value === role));
return (
diff --git a/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts b/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts
new file mode 100644
index 00000000..571d3a7b
--- /dev/null
+++ b/modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts
@@ -0,0 +1,21 @@
+import type { CatalogItemDTO } from "../../../../common";
+import type { InfrastructureDTO } from "../../../../common/infrastructure";
+import type { AssetDTO } from "../../../../common/inventory";
+
+import { infrastructureRoles } from "./infrastructure-roles";
+
+/** Activos que pertenecen a infraestructura, con su clasificación e interfaces efectivas. */
+export function getInfrastructureItems(
+ assets: readonly AssetDTO[],
+ catalog: readonly CatalogItemDTO[],
+ configurations: readonly InfrastructureDTO[]
+) {
+ return assets.flatMap((asset) => {
+ const config = configurations.find((item) => item.assetId === asset.id);
+ const roles = infrastructureRoles(asset.values.templateId, catalog, config);
+
+ return roles.length || config?.values.interfaces.length
+ ? [{ ...asset, config: { roles, interfaces: config?.values.interfaces ?? [] } }]
+ : [];
+ });
+}
diff --git a/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx b/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx
index b24986cb..2da3767b 100644
--- a/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx
+++ b/modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx
@@ -20,6 +20,7 @@ import { inventoryKeys } from "../../shared/queries";
import { InfrastructurePage } from "../list/ui/infrastructure-page";
import { infrastructureKeys } from "./api/infrastructure-api";
+import { getInfrastructureItems } from "./helpers/infrastructure-items";
import { infrastructureRoles } from "./helpers/infrastructure-roles";
import { AssetInfrastructurePanel } from "./ui/asset-infrastructure-panel";
@@ -117,6 +118,19 @@ test("infraestructura: clasificación de serie independiente del nombre/icono y
assert.deepEqual(infrastructureRoles(templateId, [{ ...renamed, builtinKey: null }]), []);
});
+test("infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos", () => {
+ assert.equal(getInfrastructureItems([], catalog.items, []).length, 0);
+ assert.equal(getInfrastructureItems([asset], catalog.items, []).length, 1);
+ assert.equal(getInfrastructureItems([asset], catalog.items, [config]).length, 1);
+ assert.equal(getInfrastructureItems([asset], [], []).length, 0);
+ const interfacesOnly = { ...config, values: { ...config.values, roles: [] } };
+ const excluded = { ...config, values: { ...config.values, roles: [], interfaces: [] } };
+
+ assert.equal(getInfrastructureItems([asset], [], [interfacesOnly]).length, 1);
+ assert.equal(getInfrastructureItems([asset], catalog.items, [excluded]).length, 0);
+ assert.equal(getInfrastructureItems([asset], [], [{ ...config, assetId: companyId }]).length, 0);
+});
+
test("infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos", async () => {
for (const [language, title, edit] of [
["es", "Infraestructura", "Editar infraestructura"],
diff --git a/modules/companies/src/common/locales/en.json b/modules/companies/src/common/locales/en.json
index 753068ee..110cf99e 100644
--- a/modules/companies/src/common/locales/en.json
+++ b/modules/companies/src/common/locales/en.json
@@ -26,6 +26,12 @@
"close": "Close navigation",
"skip": "Skip to content"
},
+ "peopleNavigation": {
+ "contacts": "Company contacts",
+ "active": "Active",
+ "inactive": "Inactive",
+ "all": "All people"
+ },
"navigationCounts": {
"loading": "Loading total…",
"error": "The total could not be refreshed."
diff --git a/modules/companies/src/common/locales/es.json b/modules/companies/src/common/locales/es.json
index cc63e889..9babb9ee 100644
--- a/modules/companies/src/common/locales/es.json
+++ b/modules/companies/src/common/locales/es.json
@@ -26,6 +26,12 @@
"close": "Cerrar navegación",
"skip": "Saltar al contenido"
},
+ "peopleNavigation": {
+ "contacts": "Contactos de la compañía",
+ "active": "Activas",
+ "inactive": "Inactivas",
+ "all": "Todas las personas"
+ },
"navigationCounts": {
"loading": "Cargando total…",
"error": "No se ha podido actualizar el total."
diff --git a/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts b/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts
index 774f0e65..e7edee14 100644
--- a/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts
+++ b/modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts
@@ -1,6 +1,11 @@
import {
+ catalogApi as assetCatalogApi,
+ catalogKeys as assetCatalogKeys,
inventoryApi as assetInventoryApi,
inventoryKeys as assetInventoryKeys,
+ getInfrastructureItems,
+ infrastructureApi,
+ infrastructureKeys,
} from "@metis/assets/client";
import { useListQuery } from "@metis/core/client";
import {
@@ -41,6 +46,16 @@ export function useCompanyDetailController({
queryFn: () => resourceInventoryApi.get(id),
enabled: Boolean(company),
});
+ const infrastructure = useListQuery({
+ queryKey: infrastructureKeys.company(id),
+ queryFn: () => infrastructureApi.get(id),
+ enabled: Boolean(company),
+ });
+ const assetCatalog = useListQuery({
+ queryKey: assetCatalogKeys.scope(id),
+ queryFn: () => assetCatalogApi.get(id),
+ enabled: Boolean(company),
+ });
const profiles = useListQuery({
queryKey: profileKeys.company(id),
queryFn: () => profileApi.get(id),
@@ -51,6 +66,17 @@ export function useCompanyDetailController({
departments: { count: company?.departments.length, failed: false },
sites: { count: company?.sites.length, failed: false },
inventory: { count: inventory.data?.assets.length, failed: inventory.isError },
+ infrastructure: {
+ count:
+ inventory.data && infrastructure.data && assetCatalog.data
+ ? getInfrastructureItems(
+ inventory.data.assets,
+ assetCatalog.data.items,
+ infrastructure.data.items
+ ).length
+ : undefined,
+ failed: inventory.isError || infrastructure.isError || assetCatalog.isError,
+ },
workstations: { count: inventory.data?.workstations.length, failed: inventory.isError },
resources: { count: resources.data?.resources.length, failed: resources.isError },
profiles: { count: profiles.data?.profiles.length, failed: profiles.isError },
diff --git a/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx b/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx
new file mode 100644
index 00000000..d96efb24
--- /dev/null
+++ b/modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx
@@ -0,0 +1,69 @@
+import { BrowserLink } from "@metis/core/client";
+import {
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+} from "@repo/shadcn-ui/components/sidebar";
+import type { MouseEventHandler } from "react";
+import { useTranslation } from "react-i18next";
+
+import { companySectionHref } from "../../../workspace/shared/helpers";
+
+/** Vistas del listado de Personas; cada destino conserva búsqueda y orden, y reinicia página. */
+export function CompanyPeopleNavigation({
+ companyId,
+ searchParams,
+ onNavigate,
+}: {
+ companyId: string;
+ searchParams: URLSearchParams;
+ onNavigate?: MouseEventHandler | undefined;
+}) {
+ const { t } = useTranslation("companies");
+ const contactsOnly = searchParams.get("contactsOnly") === "true";
+ const active = searchParams.get("active");
+ const entries = [
+ { key: "contacts", label: t("peopleNavigation.contacts"), selected: contactsOnly },
+ {
+ key: "active",
+ label: t("peopleNavigation.active"),
+ selected: !contactsOnly && active === "true",
+ },
+ {
+ key: "inactive",
+ label: t("peopleNavigation.inactive"),
+ selected: !contactsOnly && active === "false",
+ },
+ ] as const;
+
+ return (
+
+ {entries.map((entry) => {
+ const params = new URLSearchParams(searchParams);
+
+ params.delete("page");
+ params.delete("contactsOnly");
+ params.delete("active");
+ if (entry.key === "contacts") {
+ params.set("contactsOnly", "true");
+ } else {
+ params.set("active", String(entry.key === "active"));
+ }
+ const href = `${companySectionHref(companyId, "people")}?${params.toString()}`;
+
+ return (
+
+ }
+ >
+ {entry.label}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx b/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx
index 42dd05c8..e1a0e19d 100644
--- a/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx
+++ b/modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx
@@ -1,6 +1,8 @@
-import { BrowserLink } from "@metis/core/client";
+import { BrowserLink, useBrowserSearchParams } from "@metis/core/client";
import { CountBadge } from "@repo/rdx-ui/components/count-badge";
+import { ChevronDownIcon, ChevronRightIcon } from "@repo/rdx-ui/lib/icons";
import { Button, buttonVariants } from "@repo/shadcn-ui/components/button";
+import { SidebarMenu, SidebarMenuItem } from "@repo/shadcn-ui/components/sidebar";
import type { MouseEventHandler } from "react";
import { useTranslation } from "react-i18next";
@@ -8,6 +10,8 @@ import { companySectionHref } from "../../../workspace/shared/helpers";
import type { CompanyTab } from "../../shared/types";
import type { useCompanyNavigationEntries } from "../helpers/use-company-navigation-entries";
+import { CompanyPeopleNavigation } from "./company-people-navigation";
+
export function CompanySectionNavigation({
companyId,
activeSection,
@@ -26,6 +30,14 @@ export function CompanySectionNavigation({
onSectionChange?: (section: CompanyTab) => void;
}) {
const { t, i18n } = useTranslation("companies");
+ const { searchParams } = useBrowserSearchParams();
+ const peopleExpanded = activeSection === "people";
+ const PeopleChevron = peopleExpanded ? ChevronDownIcon : ChevronRightIcon;
+ const itemClassName =
+ layout === "sidebar"
+ ? "group/company-section cursor-pointer text-foreground hover:text-primary h-auto min-h-10 w-full justify-start whitespace-normal text-left text-[13px] leading-[1.4285714286] data-[active=true]:bg-accent data-[active=true]:hover:bg-accent dark:data-[active=true]:hover:bg-accent data-[active=true]:text-accent-foreground shell:text-sm"
+ : "group/company-section cursor-pointer text-foreground hover:text-primary";
+ const peopleFiltered = searchParams.get("contactsOnly") === "true" || searchParams.has("active");
return (
);
}
diff --git a/modules/companies/src/web/companies/shared/companies-page.test.tsx b/modules/companies/src/web/companies/shared/companies-page.test.tsx
index c7195da6..49dc9385 100644
--- a/modules/companies/src/web/companies/shared/companies-page.test.tsx
+++ b/modules/companies/src/web/companies/shared/companies-page.test.tsx
@@ -1,7 +1,13 @@
import assert from "node:assert/strict";
import { test } from "node:test";
-import { assetsEnglish, assetsSpanish } from "@metis/assets/client";
+import {
+ catalogKeys as assetCatalogKeys,
+ inventoryKeys as assetInventoryKeys,
+ assetsEnglish,
+ assetsSpanish,
+ infrastructureKeys,
+} from "@metis/assets/client";
import { resourcesEnglish, resourcesSpanish } from "@metis/resources/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createInstance } from "i18next";
@@ -18,12 +24,55 @@ import { SiteSection } from "../../sites/list";
import { CatalogOverview } from "../../workspace/catalogs/ui/catalog-overview";
import { CompanyDetail } from "../detail";
import { CompanyOverview } from "../detail/ui/company-overview";
+import { CompanyPeopleNavigation } from "../detail/ui/company-people-navigation";
import { CompaniesPage } from "../list";
import { companyKeys } from "./queries";
const companyId = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4";
+test("las subentradas de Personas conservan contexto y reemplazan el filtro de la vista", async () => {
+ for (const language of ["es", "en"]) {
+ const html = await renderPage(
+ language,
+ ,
+ () => undefined
+ );
+ const links = Array.from(
+ html.matchAll(/href="([^"]+)"/g),
+ (match) => new URL((match[1] ?? "").replaceAll("&", "&"), "http://metis.invalid")
+ );
+
+ assert.equal(links.length, 3);
+ for (const link of links) {
+ assert.equal(link.pathname, `/companies/${companyId}/people`);
+ assert.equal(link.searchParams.get("q"), "Ana");
+ assert.equal(link.searchParams.get("sort"), "name");
+ assert.equal(link.searchParams.get("direction"), "desc");
+ assert.equal(link.searchParams.has("page"), false);
+ }
+ assert.equal(links[0]?.searchParams.get("contactsOnly"), "true");
+ assert.equal(links[0]?.searchParams.has("active"), false);
+ assert.equal(links[1]?.searchParams.get("active"), "true");
+ assert.equal(links[1]?.searchParams.has("contactsOnly"), false);
+ assert.equal(links[2]?.searchParams.get("active"), "false");
+ assert.equal(links[2]?.searchParams.has("contactsOnly"), false);
+ assert.equal((html.match(/aria-current="page"/g) ?? []).length, 1);
+ const translations = language === "es" ? companiesSpanish : companiesEnglish;
+
+ assert.ok(html.includes(translations.peopleNavigation.contacts));
+ assert.ok(html.includes(translations.peopleNavigation.active));
+ assert.ok(html.includes(translations.peopleNavigation.inactive));
+ }
+});
+
async function renderPage(
language: string,
children: ReactNode,
@@ -343,6 +392,72 @@ test("el menú de compañía ordena trabajo y personas por grupos sin repetir la
assert.ok(html.includes('data-slot="avatar-badge"'));
});
+for (const state of ["ready", "pending", "error"] as const) {
+ test(`el menú muestra el contador de infraestructura: ${state}`, async () => {
+ for (const language of ["es", "en"]) {
+ const html = await renderPage(
+ language,
+ undefined}
+ onSectionChange={() => undefined}
+ />,
+ (client) => {
+ client.setQueryData(companyKeys.detail(companyId), {
+ company: {
+ id: companyId,
+ name: "Empresa",
+ active: true,
+ people: [],
+ contacts: [],
+ departments: [],
+ assignments: [],
+ sites: [],
+ },
+ });
+ client.setQueryData(assetInventoryKeys.company(companyId), {
+ assets: [],
+ workstations: [],
+ relationships: [],
+ });
+ client.setQueryData(assetCatalogKeys.scope(companyId), { items: [] });
+ if (state === "ready") {
+ client.setQueryData(infrastructureKeys.company(companyId), { items: [] });
+ } else if (state === "error") {
+ client.setQueryDefaults(infrastructureKeys.company(companyId), { retryOnMount: false });
+ client
+ .getQueryCache()
+ .build(client, {
+ queryKey: infrastructureKeys.company(companyId),
+ })
+ .setState({ status: "error", error: new Error("NETWORK_ERROR") });
+ }
+ }
+ );
+ const navigation = html.match(/ |