feat: add technical logs and implement infrastructure item retrieval
- Created node-check.log to capture Node.js syntax errors. - Added proxy.log for TLS termination diagnostics. - Introduced rdx-ui-types.log and resources-types.log for TypeScript compilation logs. - Implemented resources.log to log test results for various resource-related tests. - Added table.log to log column restoration and preference isolation tests. - Created types.log to log type checking results across multiple packages. - Added web-types.log for TypeScript compilation logs specific to web components. - Introduced traceability.json to track changes in recorded sources and their states. - Implemented infrastructure-items.ts to retrieve infrastructure items with roles and interfaces. - Added company-people-navigation.tsx for navigation in company detail views.
This commit is contained in:
parent
6992a4ac74
commit
0a5fb28263
@ -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"
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
Checked 15 files in 15ms. No fixes applied.
|
||||||
@ -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
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||||
|
|
||||||
@ -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.
|
||||||
@ -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
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"diagnostic":"TLS termination followed by HTTP to backend","trustProxy":false,"http":200,"secure":false,"sessionCookieIssued":false,"databaseUsed":false}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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<DayPickerLocale>; }' 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<DayPickerLocale> | undefined' is not assignable to type 'Partial<DayPickerLocale>'.
|
||||||
|
@repo/shadcn-ui:typecheck: Type 'undefined' is not assignable to type 'Partial<DayPickerLocale>'.
|
||||||
|
@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.
|
||||||
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.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"
|
||||||
|
}
|
||||||
@ -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.**
|
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
|
## 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:
|
**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.
|
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.
|
**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.
|
||||||
|
|||||||
@ -1,5 +1,29 @@
|
|||||||
# Componentes de interfaz
|
# 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.
|
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
|
`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`.
|
extiende desde este proyecto. Se consume mediante `@repo/shadcn-ui`.
|
||||||
|
|||||||
@ -23,6 +23,14 @@ autenticar se recupera ese destino. No se aceptan redirecciones a otros orígene
|
|||||||
|
|
||||||
## Espacio de compañía
|
## 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`:
|
Todas las secciones siguientes cuelgan de `/companies/:companyId`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export {
|
|||||||
infrastructureApi,
|
infrastructureApi,
|
||||||
infrastructureKeys,
|
infrastructureKeys,
|
||||||
} from "./infrastructure/shared/api/infrastructure-api";
|
} 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 { NetworkSharePublications } from "./network-share-publications/detail/ui/network-share-publications";
|
||||||
export { PublicationPicker } from "./network-share-publications/select/ui/publication-picker";
|
export { PublicationPicker } from "./network-share-publications/select/ui/publication-picker";
|
||||||
export { assetsEnglish, assetsSpanish } from "./shared";
|
export { assetsEnglish, assetsSpanish } from "./shared";
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { InventoryFeedback } from "../../../shared";
|
|||||||
import { inventoryApi } from "../../../shared/api";
|
import { inventoryApi } from "../../../shared/api";
|
||||||
import { inventoryKeys } from "../../../shared/queries";
|
import { inventoryKeys } from "../../../shared/queries";
|
||||||
import { infrastructureApi, infrastructureKeys } from "../../shared/api/infrastructure-api";
|
import { infrastructureApi, infrastructureKeys } from "../../shared/api/infrastructure-api";
|
||||||
|
import { getInfrastructureItems } from "../../shared/helpers/infrastructure-items";
|
||||||
import { infrastructureRoles } from "../../shared/helpers/infrastructure-roles";
|
import { infrastructureRoles } from "../../shared/helpers/infrastructure-roles";
|
||||||
import { InfrastructureEditorDialog } from "../../shared/ui/infrastructure-editor-dialog";
|
import { InfrastructureEditorDialog } from "../../shared/ui/infrastructure-editor-dialog";
|
||||||
|
|
||||||
@ -39,16 +40,11 @@ export function InfrastructurePage({ companyId }: { companyId: string }) {
|
|||||||
});
|
});
|
||||||
const assets = inventory.data?.assets ?? [];
|
const assets = inventory.data?.assets ?? [];
|
||||||
const editing = assets.find((asset) => asset.id === editingId);
|
const editing = assets.find((asset) => asset.id === editingId);
|
||||||
const items = assets.flatMap((asset) => {
|
const items = getInfrastructureItems(
|
||||||
const config = network.data?.items.find((item) => item.assetId === asset.id);
|
assets,
|
||||||
|
catalog.data?.items ?? [],
|
||||||
const roles = infrastructureRoles(asset.values.templateId, catalog.data?.items ?? [], config);
|
network.data?.items ?? []
|
||||||
|
).filter((asset) => !role || asset.config.roles.some((value) => value === role));
|
||||||
return (roles.length || config?.values.interfaces.length) &&
|
|
||||||
(!role || roles.some((value) => value === role))
|
|
||||||
? [{ ...asset, config: { roles, interfaces: config?.values.interfaces ?? [] } }]
|
|
||||||
: [];
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@ -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 ?? [] } }]
|
||||||
|
: [];
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@ import { inventoryKeys } from "../../shared/queries";
|
|||||||
import { InfrastructurePage } from "../list/ui/infrastructure-page";
|
import { InfrastructurePage } from "../list/ui/infrastructure-page";
|
||||||
|
|
||||||
import { infrastructureKeys } from "./api/infrastructure-api";
|
import { infrastructureKeys } from "./api/infrastructure-api";
|
||||||
|
import { getInfrastructureItems } from "./helpers/infrastructure-items";
|
||||||
import { infrastructureRoles } from "./helpers/infrastructure-roles";
|
import { infrastructureRoles } from "./helpers/infrastructure-roles";
|
||||||
import { AssetInfrastructurePanel } from "./ui/asset-infrastructure-panel";
|
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 }]), []);
|
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 () => {
|
test("infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos", async () => {
|
||||||
for (const [language, title, edit] of [
|
for (const [language, title, edit] of [
|
||||||
["es", "Infraestructura", "Editar infraestructura"],
|
["es", "Infraestructura", "Editar infraestructura"],
|
||||||
|
|||||||
@ -26,6 +26,12 @@
|
|||||||
"close": "Close navigation",
|
"close": "Close navigation",
|
||||||
"skip": "Skip to content"
|
"skip": "Skip to content"
|
||||||
},
|
},
|
||||||
|
"peopleNavigation": {
|
||||||
|
"contacts": "Company contacts",
|
||||||
|
"active": "Active",
|
||||||
|
"inactive": "Inactive",
|
||||||
|
"all": "All people"
|
||||||
|
},
|
||||||
"navigationCounts": {
|
"navigationCounts": {
|
||||||
"loading": "Loading total…",
|
"loading": "Loading total…",
|
||||||
"error": "The total could not be refreshed."
|
"error": "The total could not be refreshed."
|
||||||
|
|||||||
@ -26,6 +26,12 @@
|
|||||||
"close": "Cerrar navegación",
|
"close": "Cerrar navegación",
|
||||||
"skip": "Saltar al contenido"
|
"skip": "Saltar al contenido"
|
||||||
},
|
},
|
||||||
|
"peopleNavigation": {
|
||||||
|
"contacts": "Contactos de la compañía",
|
||||||
|
"active": "Activas",
|
||||||
|
"inactive": "Inactivas",
|
||||||
|
"all": "Todas las personas"
|
||||||
|
},
|
||||||
"navigationCounts": {
|
"navigationCounts": {
|
||||||
"loading": "Cargando total…",
|
"loading": "Cargando total…",
|
||||||
"error": "No se ha podido actualizar el total."
|
"error": "No se ha podido actualizar el total."
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
catalogApi as assetCatalogApi,
|
||||||
|
catalogKeys as assetCatalogKeys,
|
||||||
inventoryApi as assetInventoryApi,
|
inventoryApi as assetInventoryApi,
|
||||||
inventoryKeys as assetInventoryKeys,
|
inventoryKeys as assetInventoryKeys,
|
||||||
|
getInfrastructureItems,
|
||||||
|
infrastructureApi,
|
||||||
|
infrastructureKeys,
|
||||||
} from "@metis/assets/client";
|
} from "@metis/assets/client";
|
||||||
import { useListQuery } from "@metis/core/client";
|
import { useListQuery } from "@metis/core/client";
|
||||||
import {
|
import {
|
||||||
@ -41,6 +46,16 @@ export function useCompanyDetailController({
|
|||||||
queryFn: () => resourceInventoryApi.get(id),
|
queryFn: () => resourceInventoryApi.get(id),
|
||||||
enabled: Boolean(company),
|
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({
|
const profiles = useListQuery({
|
||||||
queryKey: profileKeys.company(id),
|
queryKey: profileKeys.company(id),
|
||||||
queryFn: () => profileApi.get(id),
|
queryFn: () => profileApi.get(id),
|
||||||
@ -51,6 +66,17 @@ export function useCompanyDetailController({
|
|||||||
departments: { count: company?.departments.length, failed: false },
|
departments: { count: company?.departments.length, failed: false },
|
||||||
sites: { count: company?.sites.length, failed: false },
|
sites: { count: company?.sites.length, failed: false },
|
||||||
inventory: { count: inventory.data?.assets.length, failed: inventory.isError },
|
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 },
|
workstations: { count: inventory.data?.workstations.length, failed: inventory.isError },
|
||||||
resources: { count: resources.data?.resources.length, failed: resources.isError },
|
resources: { count: resources.data?.resources.length, failed: resources.isError },
|
||||||
profiles: { count: profiles.data?.profiles.length, failed: profiles.isError },
|
profiles: { count: profiles.data?.profiles.length, failed: profiles.isError },
|
||||||
|
|||||||
@ -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<HTMLElement> | 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 (
|
||||||
|
<SidebarMenuSub aria-label={t("people")} className="my-1 border-border">
|
||||||
|
{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 (
|
||||||
|
<SidebarMenuSubItem key={entry.key}>
|
||||||
|
<SidebarMenuSubButton
|
||||||
|
aria-current={entry.selected ? "page" : undefined}
|
||||||
|
className="h-auto min-h-10 whitespace-normal py-2 text-foreground hover:bg-accent hover:text-accent-foreground data-active:bg-accent data-active:text-accent-foreground"
|
||||||
|
isActive={entry.selected}
|
||||||
|
render={<BrowserLink href={href} onClick={onNavigate} />}
|
||||||
|
>
|
||||||
|
{entry.label}
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 { 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 { Button, buttonVariants } from "@repo/shadcn-ui/components/button";
|
||||||
|
import { SidebarMenu, SidebarMenuItem } from "@repo/shadcn-ui/components/sidebar";
|
||||||
import type { MouseEventHandler } from "react";
|
import type { MouseEventHandler } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -8,6 +10,8 @@ import { companySectionHref } from "../../../workspace/shared/helpers";
|
|||||||
import type { CompanyTab } from "../../shared/types";
|
import type { CompanyTab } from "../../shared/types";
|
||||||
import type { useCompanyNavigationEntries } from "../helpers/use-company-navigation-entries";
|
import type { useCompanyNavigationEntries } from "../helpers/use-company-navigation-entries";
|
||||||
|
|
||||||
|
import { CompanyPeopleNavigation } from "./company-people-navigation";
|
||||||
|
|
||||||
export function CompanySectionNavigation({
|
export function CompanySectionNavigation({
|
||||||
companyId,
|
companyId,
|
||||||
activeSection,
|
activeSection,
|
||||||
@ -26,6 +30,14 @@ export function CompanySectionNavigation({
|
|||||||
onSectionChange?: (section: CompanyTab) => void;
|
onSectionChange?: (section: CompanyTab) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t, i18n } = useTranslation("companies");
|
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 (
|
return (
|
||||||
<nav
|
<nav
|
||||||
@ -36,65 +48,76 @@ export function CompanySectionNavigation({
|
|||||||
: "flex flex-wrap gap-2"
|
: "flex flex-wrap gap-2"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{entries.map(([section, label, Icon]) => {
|
<SidebarMenu className={layout === "inline" ? "flex-row flex-wrap gap-2" : undefined}>
|
||||||
const count = counts[section];
|
{entries.map(([section, label, Icon]) => {
|
||||||
|
const count = counts[section];
|
||||||
|
|
||||||
const itemClassName =
|
const isPeople = section === "people";
|
||||||
layout === "sidebar"
|
const showPeople = isPeople && peopleExpanded;
|
||||||
? "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"
|
const active = activeSection === section;
|
||||||
: "group/company-section cursor-pointer text-foreground hover:text-primary";
|
const filteredParent = isPeople && peopleFiltered;
|
||||||
const content = (
|
const currentPage = active && !filteredParent ? "page" : undefined;
|
||||||
<>
|
const containerClassName = ["people", "inventory"].includes(section)
|
||||||
{" "}
|
? "basis-full border-t border-border pt-2 mt-2 shell:basis-auto"
|
||||||
<Icon aria-hidden="true" className="size-4 shrink-0" />
|
: undefined;
|
||||||
<span className="min-w-0 flex-1 text-left">{label}</span>
|
const branchProps = {
|
||||||
{count && (
|
"aria-expanded": isPeople ? peopleExpanded : undefined,
|
||||||
<CountBadge
|
"data-active": active,
|
||||||
className="ms-auto group-data-[active=true]/company-section:bg-primary/10 group-data-[active=true]/company-section:text-primary"
|
};
|
||||||
count={count.count}
|
const content = (
|
||||||
errorLabel={t("navigationCounts.error")}
|
<>
|
||||||
failed={count.failed}
|
{" "}
|
||||||
loadingLabel={t("navigationCounts.loading")}
|
<Icon aria-hidden="true" className="size-4 shrink-0" />
|
||||||
locale={i18n.language}
|
<span className="min-w-0 flex-1 text-left">{label}</span>
|
||||||
/>
|
{isPeople && <PeopleChevron aria-hidden="true" className="size-4 shrink-0" />}
|
||||||
)}{" "}
|
{count && (
|
||||||
</>
|
<CountBadge
|
||||||
);
|
className="ms-auto group-data-[active=true]/company-section:bg-primary/10 group-data-[active=true]/company-section:text-primary"
|
||||||
|
count={count.count}
|
||||||
|
errorLabel={t("navigationCounts.error")}
|
||||||
|
failed={count.failed}
|
||||||
|
loadingLabel={t("navigationCounts.loading")}
|
||||||
|
locale={i18n.language}
|
||||||
|
/>
|
||||||
|
)}{" "}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SidebarMenuItem className={containerClassName} key={section}>
|
||||||
className={
|
{onSectionChange ? (
|
||||||
section === "people" || section === "inventory"
|
<Button
|
||||||
? "basis-full border-t border-border pt-2 mt-2 shell:basis-auto"
|
{...branchProps}
|
||||||
: "contents"
|
aria-pressed={active}
|
||||||
}
|
className={itemClassName}
|
||||||
key={section}
|
onClick={() => onSectionChange(section)}
|
||||||
>
|
variant="ghost"
|
||||||
{onSectionChange ? (
|
>
|
||||||
<Button
|
{content}
|
||||||
aria-pressed={activeSection === section}
|
</Button>
|
||||||
className={itemClassName}
|
) : (
|
||||||
data-active={activeSection === section}
|
<BrowserLink
|
||||||
onClick={() => onSectionChange(section)}
|
{...branchProps}
|
||||||
variant="ghost"
|
aria-current={currentPage}
|
||||||
>
|
className={`${buttonVariants({ variant: "ghost" })} ${itemClassName}`}
|
||||||
{content}
|
data-slot="button"
|
||||||
</Button>
|
href={companySectionHref(companyId, section)}
|
||||||
) : (
|
onClick={onNavigate}
|
||||||
<BrowserLink
|
>
|
||||||
aria-current={activeSection === section ? "page" : undefined}
|
{content}
|
||||||
className={`${buttonVariants({ variant: "ghost" })} ${itemClassName}`}
|
</BrowserLink>
|
||||||
data-active={activeSection === section}
|
)}
|
||||||
data-slot="button"
|
{showPeople && (
|
||||||
href={companySectionHref(companyId, section)}
|
<CompanyPeopleNavigation
|
||||||
onClick={onNavigate}
|
companyId={companyId}
|
||||||
>
|
onNavigate={onNavigate}
|
||||||
{content}
|
searchParams={searchParams}
|
||||||
</BrowserLink>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</SidebarMenu>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
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 { resourcesEnglish, resourcesSpanish } from "@metis/resources/client";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { createInstance } from "i18next";
|
import { createInstance } from "i18next";
|
||||||
@ -18,12 +24,55 @@ import { SiteSection } from "../../sites/list";
|
|||||||
import { CatalogOverview } from "../../workspace/catalogs/ui/catalog-overview";
|
import { CatalogOverview } from "../../workspace/catalogs/ui/catalog-overview";
|
||||||
import { CompanyDetail } from "../detail";
|
import { CompanyDetail } from "../detail";
|
||||||
import { CompanyOverview } from "../detail/ui/company-overview";
|
import { CompanyOverview } from "../detail/ui/company-overview";
|
||||||
|
import { CompanyPeopleNavigation } from "../detail/ui/company-people-navigation";
|
||||||
import { CompaniesPage } from "../list";
|
import { CompaniesPage } from "../list";
|
||||||
|
|
||||||
import { companyKeys } from "./queries";
|
import { companyKeys } from "./queries";
|
||||||
|
|
||||||
const companyId = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4";
|
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,
|
||||||
|
<CompanyPeopleNavigation
|
||||||
|
companyId={companyId}
|
||||||
|
searchParams={
|
||||||
|
new URLSearchParams(
|
||||||
|
"active=false&contactsOnly=true&q=Ana&sort=name&direction=desc&page=3"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
() => 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(
|
async function renderPage(
|
||||||
language: string,
|
language: string,
|
||||||
children: ReactNode,
|
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"'));
|
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,
|
||||||
|
<CompanyDetail
|
||||||
|
embedded
|
||||||
|
id={companyId}
|
||||||
|
onBack={() => 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(/<nav[^>]*>[\s\S]*?<\/nav>/)?.[0];
|
||||||
|
const entry = navigation?.match(
|
||||||
|
new RegExp(`<a[^>]*href="/companies/${companyId}/infrastructure"[^>]*>[\\s\\S]*?</a>`)
|
||||||
|
)?.[0];
|
||||||
|
|
||||||
|
assert.ok(entry);
|
||||||
|
assert.ok(entry.includes('data-slot="badge"'));
|
||||||
|
assert.ok(entry.includes(state === "ready" ? ">0</span>" : ">—</span>"));
|
||||||
|
if (state !== "ready") {
|
||||||
|
const translations = language === "es" ? companiesSpanish : companiesEnglish;
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
entry.includes(
|
||||||
|
state === "error"
|
||||||
|
? translations.navigationCounts.error
|
||||||
|
: translations.navigationCounts.loading
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test("sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto", async () => {
|
test("sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto", async () => {
|
||||||
const company = {
|
const company = {
|
||||||
id: companyId,
|
id: companyId,
|
||||||
|
|||||||
@ -24,9 +24,15 @@ export function usePeopleListContext(
|
|||||||
} = useEntityNavigation(`/companies/${companyId}/people`, initialId);
|
} = useEntityNavigation(`/companies/${companyId}/people`, initialId);
|
||||||
const [localSearch, setLocalSearch] = useState("");
|
const [localSearch, setLocalSearch] = useState("");
|
||||||
const [localContactsOnly, setLocalContactsOnly] = useState(false);
|
const [localContactsOnly, setLocalContactsOnly] = useState(false);
|
||||||
|
const [localActive, setLocalActive] = useState<boolean | undefined>(undefined);
|
||||||
const { searchParams, setSearchParams } = useBrowserSearchParams();
|
const { searchParams, setSearchParams } = useBrowserSearchParams();
|
||||||
const search = routed ? (searchParams.get("q") ?? "") : localSearch;
|
const search = routed ? (searchParams.get("q") ?? "") : localSearch;
|
||||||
const contactsOnly = routed ? searchParams.get("contactsOnly") === "true" : localContactsOnly;
|
const contactsOnly = routed ? searchParams.get("contactsOnly") === "true" : localContactsOnly;
|
||||||
|
const active = routed
|
||||||
|
? searchParams.has("active")
|
||||||
|
? searchParams.get("active") === "true"
|
||||||
|
: undefined
|
||||||
|
: localActive;
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const navigation = useListLocation(
|
const navigation = useListLocation(
|
||||||
{ sort: "sort", direction: "direction", page: "page" },
|
{ sort: "sort", direction: "direction", page: "page" },
|
||||||
@ -75,6 +81,36 @@ export function usePeopleListContext(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setActive(value: boolean | undefined) {
|
||||||
|
if (!routed) {
|
||||||
|
setLocalActive(value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSearchParams((params) => {
|
||||||
|
params.delete("page");
|
||||||
|
if (value === undefined) {
|
||||||
|
params.delete("active");
|
||||||
|
} else {
|
||||||
|
params.set("active", String(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilters() {
|
||||||
|
if (!routed) {
|
||||||
|
setLocalContactsOnly(false);
|
||||||
|
setLocalActive(undefined);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSearchParams((params) => {
|
||||||
|
params.delete("page");
|
||||||
|
params.delete("contactsOnly");
|
||||||
|
params.delete("active");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
routed &&
|
routed &&
|
||||||
@ -112,6 +148,9 @@ export function usePeopleListContext(
|
|||||||
setSearch,
|
setSearch,
|
||||||
contactsOnly,
|
contactsOnly,
|
||||||
setContactsOnly,
|
setContactsOnly,
|
||||||
|
active,
|
||||||
|
setActive,
|
||||||
|
clearFilters,
|
||||||
rootRef,
|
rootRef,
|
||||||
navigation,
|
navigation,
|
||||||
selectPerson,
|
selectPerson,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción
|
|||||||
position: null,
|
position: null,
|
||||||
email: "exclusive@example.test",
|
email: "exclusive@example.test",
|
||||||
phone: null,
|
phone: null,
|
||||||
active: true,
|
active: index !== 3,
|
||||||
isContact: index === 0,
|
isContact: index === 0,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -48,19 +48,28 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción
|
|||||||
lng: language,
|
lng: language,
|
||||||
resources: { es: { companies: companiesSpanish }, en: { companies: companiesEnglish } },
|
resources: { es: { companies: companiesSpanish }, en: { companies: companiesEnglish } },
|
||||||
});
|
});
|
||||||
for (const [query, contactsOnly, expected] of [
|
for (const [query, contactsOnly, active, expected] of [
|
||||||
[" alvaro ", false, [0, 1, 2]],
|
[" alvaro ", false, undefined, [0, 1, 2]],
|
||||||
["Pena", false, [1]],
|
["Pena", false, undefined, [1]],
|
||||||
["PEN\u0303A", false, [0, 2]],
|
["PEN\u0303A", false, undefined, [0, 2]],
|
||||||
["alvaro", true, [0]],
|
["alvaro", true, undefined, [0]],
|
||||||
["exclusive@example.test", false, []],
|
["exclusive@example.test", false, undefined, []],
|
||||||
] satisfies [string, boolean, number[]][]) {
|
["", false, true, [0, 1, 2]],
|
||||||
|
["", false, false, [3]],
|
||||||
|
["", true, false, []],
|
||||||
|
["María", false, true, []],
|
||||||
|
] satisfies [string, boolean, boolean | undefined, number[]][]) {
|
||||||
|
const params = new URLSearchParams({ q: query, contactsOnly: String(contactsOnly) });
|
||||||
|
|
||||||
|
if (active !== undefined) {
|
||||||
|
params.set("active", String(active));
|
||||||
|
}
|
||||||
Object.defineProperty(globalThis, "window", {
|
Object.defineProperty(globalThis, "window", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
value: {
|
value: {
|
||||||
location: {
|
location: {
|
||||||
pathname: `/companies/${companyId}/people`,
|
pathname: `/companies/${companyId}/people`,
|
||||||
search: `?${new URLSearchParams({ q: query, contactsOnly: String(contactsOnly) })}`,
|
search: `?${params.toString()}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -77,7 +86,10 @@ test("Personas conserva búsqueda por nombre, contactos, consulta y distinción
|
|||||||
);
|
);
|
||||||
|
|
||||||
company.people.forEach((person, index) => {
|
company.people.forEach((person, index) => {
|
||||||
assert.equal(html.includes(`data-entity-id="${person.id}"`), expected.includes(index));
|
assert.equal(
|
||||||
|
html.includes(`data-entity-id="${person.id}"`),
|
||||||
|
expected.some((value) => value === index)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
assert.ok(html.includes(`value="${query}"`));
|
assert.ok(html.includes(`value="${query}"`));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@repo/rdx-ui/components/entity-list";
|
} from "@repo/rdx-ui/components/entity-list";
|
||||||
import { FormLabel } from "@repo/rdx-ui/components/form-label";
|
import { FormLabel } from "@repo/rdx-ui/components/form-label";
|
||||||
import { PhoneLink } from "@repo/rdx-ui/components/phone-link";
|
import { PhoneLink } from "@repo/rdx-ui/components/phone-link";
|
||||||
|
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
|
||||||
import { EyeIcon, PersonIcon } from "@repo/rdx-ui/lib/icons";
|
import { EyeIcon, PersonIcon } from "@repo/rdx-ui/lib/icons";
|
||||||
import { createTextMatcher } from "@repo/rdx-ui/lib/text-search";
|
import { createTextMatcher } from "@repo/rdx-ui/lib/text-search";
|
||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
@ -46,6 +47,9 @@ export function PeopleSection({
|
|||||||
setSearch,
|
setSearch,
|
||||||
contactsOnly,
|
contactsOnly,
|
||||||
setContactsOnly,
|
setContactsOnly,
|
||||||
|
active,
|
||||||
|
setActive,
|
||||||
|
clearFilters,
|
||||||
rootRef,
|
rootRef,
|
||||||
navigation,
|
navigation,
|
||||||
selectPerson,
|
selectPerson,
|
||||||
@ -99,7 +103,10 @@ export function PeopleSection({
|
|||||||
}
|
}
|
||||||
const matchesSearch = createTextMatcher(search);
|
const matchesSearch = createTextMatcher(search);
|
||||||
const people = company.people.filter(
|
const people = company.people.filter(
|
||||||
(person) => (!contactsOnly || person.isContact) && matchesSearch(person.name)
|
(person) =>
|
||||||
|
(!contactsOnly || person.isContact) &&
|
||||||
|
(active === undefined || person.active === active) &&
|
||||||
|
matchesSearch(person.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -277,22 +284,35 @@ export function PeopleSection({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
emptyMessage={t("noPeople")}
|
emptyMessage={t("noPeople")}
|
||||||
filterKey={String(contactsOnly)}
|
filterKey={`${contactsOnly}:${active ?? "all"}`}
|
||||||
filters={
|
filters={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<Checkbox
|
<SelectWithAction
|
||||||
checked={contactsOnly}
|
id={`${filterId}-active`}
|
||||||
id={`${filterId}-contact`}
|
label={t("status")}
|
||||||
onCheckedChange={(checked) => setContactsOnly(checked === true)}
|
onValueChange={(value) => setActive(value === "" ? undefined : value === "true")}
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: t("peopleNavigation.active") },
|
||||||
|
{ value: "false", label: t("peopleNavigation.inactive") },
|
||||||
|
]}
|
||||||
|
placeholder={t("peopleNavigation.all")}
|
||||||
|
value={active === undefined ? "" : String(active)}
|
||||||
/>
|
/>
|
||||||
<FormLabel htmlFor={`${filterId}-contact`}>{t("contactsOnly")}</FormLabel>
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={contactsOnly}
|
||||||
|
id={`${filterId}-contact`}
|
||||||
|
onCheckedChange={(checked) => setContactsOnly(checked === true)}
|
||||||
|
/>
|
||||||
|
<FormLabel htmlFor={`${filterId}-contact`}>{t("contactsOnly")}</FormLabel>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
filtersApplied={contactsOnly}
|
filtersApplied={contactsOnly || active !== undefined}
|
||||||
items={people}
|
items={people}
|
||||||
locale={i18n.language}
|
locale={i18n.language}
|
||||||
navigation={routed ? navigation : undefined}
|
navigation={routed ? navigation : undefined}
|
||||||
onClearFilters={() => setContactsOnly(false)}
|
onClearFilters={clearFilters}
|
||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
search={search}
|
search={search}
|
||||||
searchPlaceholder={t("searchPerson")}
|
searchPlaceholder={t("searchPerson")}
|
||||||
|
|||||||
@ -159,39 +159,38 @@ export function parseWorkspaceUrl(href: string): WorkspaceLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function globalApplicationQueryMatches(
|
function collectionQueryMatches(
|
||||||
location: WorkspaceLocation,
|
location: WorkspaceLocation,
|
||||||
query: z.infer<typeof RouteQuerySchema>
|
query: z.infer<typeof RouteQuerySchema>
|
||||||
): boolean {
|
): boolean {
|
||||||
const globalApplications =
|
const globalApplications =
|
||||||
location.area === "catalogs" &&
|
location.area === "catalogs" &&
|
||||||
["global-applications", "global-operating-systems"].includes(location.catalog ?? "");
|
["global-applications", "global-operating-systems"].includes(location.catalog ?? "");
|
||||||
const companyApplications =
|
const companySection = location.area === "company" ? location.section : undefined;
|
||||||
location.area === "company" && location.section === "resources" && query.template !== undefined;
|
const companyApplications = companySection === "resources" && query.template !== undefined;
|
||||||
const applicationCatalog = globalApplications || companyApplications;
|
const applicationCatalog = globalApplications || companyApplications;
|
||||||
|
|
||||||
|
const activeAllowed = applicationCatalog || companySection === "people";
|
||||||
|
const paginationRequested = query.cursor !== undefined || query.pageSize !== undefined;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(query.active !== undefined || query.cursor !== undefined || query.pageSize !== undefined) &&
|
(query.active !== undefined && !activeAllowed) ||
|
||||||
!applicationCatalog
|
(paginationRequested && !applicationCatalog)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (!applicationCatalog) {
|
||||||
applicationCatalog &&
|
return true;
|
||||||
((query.q?.length ?? 0) > 100 ||
|
|
||||||
(query.page !== undefined && !/^[1-9][0-9]{0,5}$/u.test(query.page)))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return (query.q?.length ?? 0) <= 100 && /^[1-9][0-9]{0,5}$/u.test(query.page ?? "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryMatchesLocation(
|
function queryMatchesLocation(
|
||||||
location: WorkspaceLocation,
|
location: WorkspaceLocation,
|
||||||
query: z.infer<typeof RouteQuerySchema>
|
query: z.infer<typeof RouteQuerySchema>
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!globalApplicationQueryMatches(location, query)) {
|
if (!collectionQueryMatches(location, query)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (location.area === "company" && query.companyId && query.companyId !== location.id) {
|
if (location.area === "company" && query.companyId && query.companyId !== location.id) {
|
||||||
|
|||||||
@ -12,6 +12,37 @@ const COMPANY_ID = "019b8838-44af-776d-a406-af74452b6f26";
|
|||||||
const ENTITY_ID = "019b8838-44af-776d-a406-af74452b6f27";
|
const ENTITY_ID = "019b8838-44af-776d-a406-af74452b6f27";
|
||||||
const TEMPLATE_ID = "01a1276d-2138-711e-b3d2-99258a02a80f";
|
const TEMPLATE_ID = "01a1276d-2138-711e-b3d2-99258a02a80f";
|
||||||
|
|
||||||
|
test("Personas admite actividad en listado y ficha, con los demás criterios de contexto", () => {
|
||||||
|
for (const active of ["true", "false"]) {
|
||||||
|
for (const suffix of ["", `/${ENTITY_ID}`]) {
|
||||||
|
assert.deepEqual(
|
||||||
|
parseWorkspaceUrl(
|
||||||
|
`/companies/${COMPANY_ID}/people${suffix}?active=${active}&contactsOnly=true&q=Ana&sort=name&direction=desc&page=2`
|
||||||
|
),
|
||||||
|
{
|
||||||
|
area: "company",
|
||||||
|
id: COMPANY_ID,
|
||||||
|
section: "people",
|
||||||
|
...(suffix ? { entityId: ENTITY_ID } : {}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const query of [
|
||||||
|
"active=invalid",
|
||||||
|
"active=true&active=false",
|
||||||
|
"active=true&cursor=abc",
|
||||||
|
"active=false&pageSize=25",
|
||||||
|
]) {
|
||||||
|
assert.deepEqual(parseWorkspaceUrl(`/companies/${COMPANY_ID}/people?${query}`), {
|
||||||
|
area: "invalid-route",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
assert.deepEqual(parseWorkspaceUrl(`/companies/${COMPANY_ID}/assets?active=true`), {
|
||||||
|
area: "invalid-route",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha", () => {
|
test("el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha", () => {
|
||||||
const query = `template=${TEMPLATE_ID}&q=Ágil&active=false&pageSize=25&page=2&cursor=abc`;
|
const query = `template=${TEMPLATE_ID}&q=Ágil&active=false&pageSize=25&page=2&cursor=abc`;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user