feat: add unpublished share component and publication operation summary
- Introduced `UnpublishedShare` component for displaying empty state with actions for assigning and viewing history. - Added `PublicationOperationSummary` component to summarize publication context and actions based on the current state. - Implemented `CompanyNetworkShareDetail` to manage and display network share details for companies. - Created `NetworkShareResourceTitle` for collapsible display of resource names with accessibility features. - Developed `ResourceDetailActions` for managing resource actions like edit, activate/deactivate, and delete. - Added `ResourceRevisionHistory` to display the history of resource revisions with detailed information. - Implemented a comprehensive audit script for network share detail UI, including accessibility checks and error handling.
This commit is contained in:
parent
a72d6a2ec8
commit
c863cc6d96
120
.artifacts/auditoria-so-estado-actual/manifest.json
Normal file
120
.artifacts/auditoria-so-estado-actual/manifest.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"recordedAtUtc": "2026-10-26T16:11:26.960694+00:00",
|
||||
"reportDateEuropeMadrid": "2026-10-26",
|
||||
"head": "a72d6a2ec8985e1d51f74db68a1b3aabb3693030",
|
||||
"branch": "main",
|
||||
"scope": "Static architectural research and documentation only",
|
||||
"sourceOfTruth": "Inspected working tree; concurrent modifications preserved",
|
||||
"evidenceFiles": {
|
||||
"docs/architecture/auditoria-sistemas-operativos-activos-y-puestos.md": "d8fc09b2eff51aaf99f1dc4b94b601109b7db699183393fcaa788d95f766a8d8",
|
||||
"docs/architecture/decision-catalogo-global-sistemas-operativos.md": "e6db0003a7a259849a95e615776fc8ed32176ce1276d38bda8919889e258e062",
|
||||
"docs/architecture/decision-so-actual-en-activos-y-puestos.md": "9dc8eaaaf5111c70c87a31a617a6c602ef0b7445bd2aa323da89202ed3f7cb0a",
|
||||
"docs/architecture/cierre-tecnico-catalogo-global-sistemas-operativos.md": "6f13d080cacda801606b8edf0d4f6ee70715c0f1b56e4b792567b550f89582f8",
|
||||
"docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md": "7d9e72143f907304f1880ab59ab4fb719d8b327907fdea6a5b3bfe569ecceb54",
|
||||
"modules/assets/src/api/domain/asset-catalog-seed.ts": "140ac2a6585a7edf24bab1c093b1b0be262b2bbd0bfda18e73fada8c043a023b",
|
||||
"modules/assets/src/api/infrastructure/persistence/asset.model.ts": "da7ee7f593948d55748534d1100ac6386b49318d711166c3cb544056b7f81aed",
|
||||
"modules/assets/src/api/infrastructure/persistence/asset-relational-schema.ts": "16ca683da98a345d7d6dc77e8d565ba9c1f3881ddb2158b5f699fcb354bd96e0",
|
||||
"modules/assets/src/api/domain/asset.aggregate.ts": "2022bd458488f084540bfc74c4321664b08f96ff89ee6fbc43ffa222fc2be8a6",
|
||||
"modules/assets/src/api/infrastructure/http/asset.routes.ts": "3014d08796df2d112edab4be39b635821ceae45b84bb08f6b33c7ff42dde9d9f",
|
||||
"modules/resources/src/api/infrastructure/persistence/resource.model.ts": "9ebcf283218d4ceeace0faeca74bc048ae805b7817bd4c1297b044082cd69822",
|
||||
"modules/resources/src/api/infrastructure/persistence/resource-scope-schema.ts": "a95ae7704e6c499f525645f25d933985872fde51d6c933f7b26528d8f472477c",
|
||||
"modules/resources/src/api/infrastructure/persistence/resource-scope-migration.ts": "e3637a5e6606bab9637dc59f4b6e810543845187c26b1418e07504ec2c806a42",
|
||||
"modules/companies/src/api/domain/software-installation-values.ts": "e59fdbae3514e9c3f1c04106db83f7ba493371e387b81986341451e2c1c8341e",
|
||||
"modules/companies/src/api/domain/value-objects/software-installation-values.vo.ts": "1ec49e47ce4469792bda081e72c66c059b30e5b8d37638e9592b9625c5779a76",
|
||||
"modules/companies/src/api/domain/software-installation.aggregate.ts": "69ec13f822576a1ee58a349613c0c4baaa2cf641eaf2b8849f5f41056befe721",
|
||||
"modules/companies/src/api/infrastructure/persistence/software-installation-migration.ts": "0f38b7cc6f7ea8ec4e38089765a68b1ba051339ecdac666d1f291cc17fa986b8",
|
||||
"modules/companies/src/api/infrastructure/persistence/software-installation-persistence.mapper.ts": "31eb5f27d5b94d80dd806bb51197cb3e222c04b802cfcfbf22fa9b8b195eb133",
|
||||
"modules/companies/src/api/infrastructure/persistence/software-installation-snapshot.ts": "9020cc790d238401a52de8c63d9d2765c218b8f3dbb32ab79a24b00b26edabab",
|
||||
"modules/companies/src/api/application/software-installation-policy.ts": "71ebd911a1ff649e46ca88b0852b96f430f7029bc049276c81873d9abdf70ac7",
|
||||
"modules/companies/src/api/application/software-installation-input.mapper.ts": "09125c484beec6a6b6a62b237eb271c9aa83eb83792af75176fa4376dadae4f4",
|
||||
"modules/companies/src/api/infrastructure/http/software-installation.routes.ts": "f6261167a74f2050611169287e32800bc14b8c73dab8ad97fd8215b98bc86920",
|
||||
"modules/companies/src/api/application/software-installation.use-cases.ts": "bad84001338d9d39860a45afac4b50165ee4cba1b775a4ea9cbdfb9da396723e",
|
||||
"modules/companies/src/api/infrastructure/persistence/sequelize-software-installation.repository.ts": "873fd3f16ebd4b28e48006ae49f6efb51d28cdcd0960137fdd6e95384fa64111",
|
||||
"modules/companies/src/api/application/software-installation-response.builder.ts": "682f7af3b10b1fdf0d60ae36aaf6878af772390624e9009a8ec6c65f8127917a",
|
||||
"modules/resources/src/api/domain/resource-catalog-seed.ts": "ccc4d67cc3f7405620965c769806f27f93cce12a220735c0700cf1884b641ff4",
|
||||
"modules/resources/src/api/application/operating-system-identity-policy.ts": "475482c8ab6591ef4f47f43d586d67212f5a286ee1bfa4869ddbb5d3226d2aeb",
|
||||
"modules/resources/src/api/application/global-application-template-policy.ts": "35403af9de284edb5f75b351ecf138f60163251715d16fba7e98b91bebd5c9d3",
|
||||
"modules/resources/src/api/infrastructure/http/global-resource.routes.ts": "6dd210d40330a52421eb401e4825fb3529779faa6d17d567225d88a768614e4d",
|
||||
"modules/resources/src/common/operating-system-identity.ts": "15b335a243321d005e784bbc80e486256f1525d0483ad264e535cf9432b34e15",
|
||||
"modules/resources/src/api/infrastructure/persistence/resource-reference-reader.ts": "b477e796e0004220d8e27c2c656070a77821407fe9866123811beb9f1eaecbf8",
|
||||
"modules/resources/src/api/application/resource-global-gates.ts": "88d9caeb9344dbe0dec9c39ce025111e66ea737211ec2c8d7c645f1fb43fc45e",
|
||||
"modules/resources/src/api/infrastructure/persistence/verify-global-resource-enablement.ts": "509e3b9ba2919bc73ef83602bb027ec2c1c0db3fe7c0ba600351cfc551f172b2",
|
||||
"modules/assets/src/api/domain/value-objects/workstation-values.vo.ts": "c2490c322bbbad4650840f308509fa569126118db7971624f28580ae7729334f",
|
||||
"modules/assets/src/api/application/workstation-input.mapper.ts": "d5d3915c2d5f0f0af8f0ebaa24a27092ba42b4cbeb3059a88aedc75394c95c2e",
|
||||
"modules/assets/src/api/infrastructure/persistence/workstation.model.ts": "a0ba43d157d52d42185042c0beee0b37c83f90a1e4308a51d7a4278a19a5df3b",
|
||||
"modules/assets/src/api/domain/workstation.aggregate.ts": "e2ec45294c9758585abf0d77c9b1b5fdc0b6d0621423c781b4011773456f739c",
|
||||
"modules/assets/src/api/domain/asset-relationship.aggregate.ts": "90ad885ce01ef32edb66989426dbdd3ccc5aba383ede4fd6c1979a55e9c5fe38",
|
||||
"modules/companies/src/api/domain/work-profile-values.ts": "9c60f46b47d06114d5d4f075c2b15ff97007a0d003b9c4fc33a2517268da331a",
|
||||
"modules/companies/src/api/application/work-profile-input.mapper.ts": "62e1f759e381c1530ac03ffa62f0fc5a23dc6ee93357035019360d901507af4e",
|
||||
"modules/companies/src/api/domain/application-configuration-values.ts": "5dd80dc36871c0e1c53e6c991513093e70774b6445fd0a8b568ceaa3e913de94",
|
||||
"modules/companies/src/api/application/application-configuration-policy.ts": "e48acc318db59e555e18c0b64fc9df3bb315d52ebd297db05e447c51f896b87c",
|
||||
"modules/companies/src/web/software-installations/shared/helpers/current-operating-systems.ts": "7f3ed548af98657b9faaf94a48ccf2cd739067d1577fd6ef0a20a50e0714dac0",
|
||||
"modules/companies/src/web/software-installations/shared/queries/use-asset-software.ts": "753b758bd428edeb5ba52c9b8892ecf77d2a821c7d62ce0b9a4071e7d44df920",
|
||||
"modules/companies/src/web/software-installations/detail/ui/current-operating-systems.tsx": "5345a8b8d32614fe73bc5ab16a0302f4f1fe65b73681751df2113b0435e57f9f",
|
||||
"modules/companies/src/web/workstations/detail/controllers/use-workstation-detail-controller.ts": "aa513f8cf5a3d1d1adef91675d39c1d6c72891c4e745a9e0c0625f0b2bc553cb",
|
||||
"modules/companies/src/web/workstations/detail/ui/workstation-detail-content.tsx": "7b7d081b444cde82860b2f7fd22b072da1e2696300600a1d486b00d96e91a90d",
|
||||
"modules/companies/src/web/companies/detail/ui/company-inventory.tsx": "5830df6fc455b323e52a2113576ad2c34f61600241a6bfd7662ebc0600f5adce",
|
||||
"modules/companies/src/web/workstations/detail/controllers/use-workstation-software.ts": "7e4ed64ed57c476b4c2ca361d2e090cf23fbfe206176465b9eaad48bf25f54c3",
|
||||
"modules/companies/src/web/workstations/detail/helpers/workplace-map-model.ts": "bf25c7e88b8c8bf318c398986dbc08f3ec2f77eb739588555e711fe1e123cb4c",
|
||||
"apps/server/src/infrastructure/http/identity-runtime.ts": "1513d18df5b69e43cea14ac5645f9aec0d89d1bb5f4c764c153f5c5134ac0032",
|
||||
"modules/companies/src/api/module.ts": "c826d19d8b06eda39738af953c349c3d3cab63952ff2c7c2649794b22f4b05e6",
|
||||
"docs/software-del-ordenador.md": "ae1ad29b6a4c53914550aae8b0222797f5e08f769906c047f89f7e847d3564c1",
|
||||
"modules/companies/src/api/domain/software-installation.test.ts": "a95afd2fb6bf4025adc8d14098a40a374a971cb2b46c7f017c9a7fe9321ceb85",
|
||||
"modules/companies/src/web/software-installations/shared/current-operating-systems.test.tsx": "81105f06893f3016b808611e571dc0cbb0d61dc590ab7ec972150acb029b2ccf",
|
||||
"apps/server/src/global-operating-systems-closure.integration.test.ts": "771ccf735641dbd257569b4366f0ac70eb81d63cc729db2d5f4ed5e30358669a",
|
||||
"docs/arquitectura/composicion-de-modulos.md": "b0fefe4089e00f7a9cbce528610ca7e27880d5828da65cba4f6ad2288c75744a",
|
||||
"docs/architecture/resources-global-scope/19-d08-2-resolucion-ratificacion.md": "c59d4c2887a92664f27139c1deb1f885ea8faf08d01d20012eaa31a8a3e3fb48"
|
||||
},
|
||||
"report": {
|
||||
"path": "docs/architecture/auditoria-estado-actual-sistemas-operativos-activos-y-puestos.md",
|
||||
"sha256": "600a88277ebc8e27a24c6b7974e40cdbda6e55a28460742831c1d539b7bc8fdc"
|
||||
},
|
||||
"checks": {
|
||||
"referencedPathsExist": true,
|
||||
"scenarios": 16,
|
||||
"mermaidDiagrams": 2,
|
||||
"testsExecuted": false,
|
||||
"databaseAccessed": false,
|
||||
"productionAccessed": false,
|
||||
"migrationsExecuted": false,
|
||||
"productionCodeModified": false
|
||||
},
|
||||
"workingTreeStatusAtRecording": [
|
||||
" M docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md",
|
||||
" M docs/componentes-de-interfaz.md",
|
||||
" M modules/assets/src/common/locales/en.json",
|
||||
" M modules/assets/src/common/locales/es.json",
|
||||
" M modules/assets/src/web/index.ts",
|
||||
" M modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx",
|
||||
" M modules/assets/src/web/network-share-publications/shared/network-share-ui.test.tsx",
|
||||
" M modules/companies/src/common/locales/en.json",
|
||||
" M modules/companies/src/common/locales/es.json",
|
||||
" M modules/companies/src/web/resources/list/ui/company-resources.tsx",
|
||||
" M modules/resources/src/common/locales/en.json",
|
||||
" M modules/resources/src/common/locales/es.json",
|
||||
" M modules/resources/src/web/index.ts",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-detail.tsx",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-selection-panel.tsx",
|
||||
" M modules/resources/src/web/resources/list/types/resources-page-props.ts",
|
||||
" M modules/resources/src/web/resources/list/ui/resources-page.tsx",
|
||||
" M packages/rdx-ui/src/components/detail-panel.tsx",
|
||||
" M packages/rdx-ui/src/components/form-dialog.tsx",
|
||||
" M scripts/visual-audit.py",
|
||||
"?? .artifacts/resources-global-operating-systems-preflight-a72d6a2e/",
|
||||
"?? docs/architecture/auditoria-estado-actual-sistemas-operativos-activos-y-puestos.md",
|
||||
"?? docs/ficha-de-carpeta-compartida.md",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/controllers/",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/helpers/",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/network-share-resource-detail.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/publication-history.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/published-share-asset.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/resource-publication-card.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/unpublished-share.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/manage/ui/publication-operation-summary.tsx",
|
||||
"?? modules/companies/src/web/resources/detail/ui/company-network-share-detail.tsx",
|
||||
"?? modules/resources/src/web/resources/detail/ui/resource-detail-actions.tsx",
|
||||
"?? modules/resources/src/web/resources/detail/ui/resource-revision-history.tsx",
|
||||
"?? scripts/network-share-detail-ui-audit.py"
|
||||
]
|
||||
}
|
||||
@ -1,33 +1,33 @@
|
||||
▶ Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real
|
||||
✔ B3 — evolución SQL doble conserva campos, valores, personalizaciones y revisiones (0.701606ms)
|
||||
✔ DB-SO-01 crear SO global (42.871762ms)
|
||||
✔ DB-SO-02 crear aplicación global mantiene su tipo (42.436359ms)
|
||||
✔ DB-SO-03 licencia global rechazada sin raíz persistida (21.908985ms)
|
||||
✔ DB-SO-04 instalación válida desde A (121.539228ms)
|
||||
✔ DB-SO-05 mismo producto desde B, instalación y licencia independientes (162.366074ms)
|
||||
✔ DB-SO-06 dos activos de A usan el mismo SO global (315.915072ms)
|
||||
✔ DB-SO-07 particulares y globales equivalentes conservan UUID e instalaciones (137.643465ms)
|
||||
✔ DB-SO-08 licencia de A válida y particular (117.865997ms)
|
||||
✔ DB-SO-09 licencia de A no puede vincularse desde B (157.376231ms)
|
||||
✔ DB-SO-10 SO excluido de ApplicationConfiguration (126.53987ms)
|
||||
✔ DB-SO-11 desactivar conserva instalaciones usadas (50.935631ms)
|
||||
✔ DB-SO-12 inactivo bloquea nueva instalación (108.274915ms)
|
||||
✔ DB-SO-13 historial y resolver conservan SO inactivo (29.59105ms)
|
||||
✔ DB-SO-14 versión instalada genera revisión sin cambiar producto (365.216789ms)
|
||||
✔ DB-SO-15 destino de instalación inmutable (223.936693ms)
|
||||
✔ DB-SO-16 retirada de instalación de producto inactivo (570.283255ms)
|
||||
✔ DB-SO-17 replay de creación no duplica ni reactiva (33.73864ms)
|
||||
✔ DB-SO-18 activar/desactivar preserva UUID, historial y referencias (177.183762ms)
|
||||
✔ DB-SO-19 solo stock: copia y otros tipos no adquieren globalidad (315.072142ms)
|
||||
✔ DB-SO-20 particular anterior incompleto sigue siendo editable y local (145.260533ms)
|
||||
✔ C1 — equivalentes concurrentes esperan el lock de catálogo y solo uno persiste (230.848842ms)
|
||||
✔ C2 — desactivación confirma primero y asociación revalida después de esperar (468.860536ms)
|
||||
✔ C3 — identidad, instalación, metadatos y desactivación concurrentes (288.411751ms)
|
||||
✔ C4a — fallo tras escribir raíz global revierte raíz, características y revisiones (76.707246ms)
|
||||
✔ C4b — fallo en revisión de instalación revierte instalación completa (146.788844ms)
|
||||
✔ D — contratos v2/v3, propósito, capabilities y autorización (248.643069ms)
|
||||
✔ F — cerrar gates conserva lectura, historial y retirada legítima (1726.083172ms)
|
||||
✔ Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real (47759.450137ms)
|
||||
✔ B3 — evolución SQL doble conserva campos, valores, personalizaciones y revisiones (0.772304ms)
|
||||
✔ DB-SO-01 crear SO global (46.028737ms)
|
||||
✔ DB-SO-02 crear aplicación global mantiene su tipo (44.90923ms)
|
||||
✔ DB-SO-03 licencia global rechazada sin raíz persistida (23.088718ms)
|
||||
✔ DB-SO-04 instalación válida desde A (127.733456ms)
|
||||
✔ DB-SO-05 mismo producto desde B, instalación y licencia independientes (176.271701ms)
|
||||
✔ DB-SO-06 dos activos de A usan el mismo SO global (349.856578ms)
|
||||
✔ DB-SO-07 particulares y globales equivalentes conservan UUID e instalaciones (128.298352ms)
|
||||
✔ DB-SO-08 licencia de A válida y particular (106.066739ms)
|
||||
✔ DB-SO-09 licencia de A no puede vincularse desde B (157.025598ms)
|
||||
✔ DB-SO-10 SO excluido de ApplicationConfiguration (119.207306ms)
|
||||
✔ DB-SO-11 desactivar conserva instalaciones usadas (45.964733ms)
|
||||
✔ DB-SO-12 inactivo bloquea nueva instalación (99.866591ms)
|
||||
✔ DB-SO-13 historial y resolver conservan SO inactivo (30.539321ms)
|
||||
✔ DB-SO-14 versión instalada genera revisión sin cambiar producto (354.319274ms)
|
||||
✔ DB-SO-15 destino de instalación inmutable (212.150241ms)
|
||||
✔ DB-SO-16 retirada de instalación de producto inactivo (495.50569ms)
|
||||
✔ DB-SO-17 replay de creación no duplica ni reactiva (29.001209ms)
|
||||
✔ DB-SO-18 activar/desactivar preserva UUID, historial y referencias (254.702536ms)
|
||||
✔ DB-SO-19 solo stock: copia y otros tipos no adquieren globalidad (257.351156ms)
|
||||
✔ DB-SO-20 particular anterior incompleto sigue siendo editable y local (129.748936ms)
|
||||
✔ C1 — equivalentes concurrentes esperan el lock de catálogo y solo uno persiste (227.933739ms)
|
||||
✔ C2 — desactivación confirma primero y asociación revalida después de esperar (361.285186ms)
|
||||
✔ C3 — identidad, instalación, metadatos y desactivación concurrentes (254.404421ms)
|
||||
✔ C4a — fallo tras escribir raíz global revierte raíz, características y revisiones (82.213889ms)
|
||||
✔ C4b — fallo en revisión de instalación revierte instalación completa (151.297283ms)
|
||||
✔ D — contratos v2/v3, propósito, capabilities y autorización (273.826049ms)
|
||||
✔ F — cerrar gates conserva lectura, historial y retirada legítima (1512.466505ms)
|
||||
✔ Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real (35512.142313ms)
|
||||
ℹ tests 29
|
||||
ℹ suites 0
|
||||
ℹ pass 29
|
||||
@ -35,4 +35,4 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 49098.971929
|
||||
ℹ duration_ms 36837.510665
|
||||
|
||||
@ -1,93 +1,93 @@
|
||||
{
|
||||
"resourceId": "01a174ef-6735-710e-91a9-8bca64fef0dc",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"resourceId": "01a1792f-3b83-75ec-95f5-85e11090a07c",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"originalBindings": [
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-de87e373415b",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f266-714f-ab98-ef87f8982b5f",
|
||||
"id": "01a1792e-e7be-712e-84e3-f14de305adff",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-a58e30da043d",
|
||||
"required": 0,
|
||||
"position": 0,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-ee01d44d287d",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f267-762b-ae6a-91610b309fd2",
|
||||
"id": "01a1792e-e7be-712e-84e4-018276b96141",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-da9cbd4def2f",
|
||||
"required": 0,
|
||||
"position": 1,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-f37d9579e79c",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f267-762b-ae6a-6fbf417e65f2",
|
||||
"id": "01a1792e-e7be-712e-84e4-04b396a999a4",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-b576b4767131",
|
||||
"required": 0,
|
||||
"position": 2,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ef-66ee-7763-8065-815a5c68b2cf",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ef-66ae-77ec-8d19-1ef89cb4745c",
|
||||
"id": "01a1792f-3b35-77b9-a533-36b575f5730d",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792f-3af6-717f-9efa-702da2ce9c9d",
|
||||
"required": 0,
|
||||
"position": 3,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
}
|
||||
],
|
||||
"evolvedBindings": [
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-de87e373415b",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f266-714f-ab98-ef87f8982b5f",
|
||||
"id": "01a1792e-e7be-712e-84e3-f14de305adff",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-a58e30da043d",
|
||||
"required": 0,
|
||||
"position": 0,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-ee01d44d287d",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f267-762b-ae6a-91610b309fd2",
|
||||
"id": "01a1792e-e7be-712e-84e4-018276b96141",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-da9cbd4def2f",
|
||||
"required": 0,
|
||||
"position": 1,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ee-f269-72eb-9e8e-f37d9579e79c",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ee-f267-762b-ae6a-6fbf417e65f2",
|
||||
"id": "01a1792e-e7be-712e-84e4-04b396a999a4",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792e-e7bc-72da-9e19-b576b4767131",
|
||||
"required": 0,
|
||||
"position": 2,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ef-66ee-7763-8065-815a5c68b2cf",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ef-66ae-77ec-8d19-1ef89cb4745c",
|
||||
"id": "01a1792f-3b35-77b9-a533-36b575f5730d",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792f-3af6-717f-9efa-702da2ce9c9d",
|
||||
"required": 0,
|
||||
"position": 3,
|
||||
"sectionId": "01a174ef-66e7-77fa-b64c-2667d71f05a9"
|
||||
"sectionId": "01a1792f-3b2d-7245-ac3a-0655e77d9942"
|
||||
},
|
||||
{
|
||||
"id": "01a174ef-6805-739a-8977-95d3221286d2",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ef-6804-747b-b42c-b10560610789",
|
||||
"id": "01a1792f-3c06-7715-b3fd-d2a4f2f22ba9",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792f-3c05-715e-b3ad-227b0f51dbff",
|
||||
"required": 0,
|
||||
"position": 4,
|
||||
"sectionId": null
|
||||
},
|
||||
{
|
||||
"id": "01a174ef-6805-739a-8977-99e426769780",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ef-6804-747b-b42c-b4deee355760",
|
||||
"id": "01a1792f-3c06-7715-b3fd-d42646603c7d",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792f-3c05-715e-b3ad-25e3831e5e89",
|
||||
"required": 0,
|
||||
"position": 5,
|
||||
"sectionId": null
|
||||
},
|
||||
{
|
||||
"id": "01a174ef-6805-739a-8977-9d7c602e1672",
|
||||
"templateId": "01a174ee-f269-72eb-9e8e-fb11658300fc",
|
||||
"fieldId": "01a174ef-6804-747b-b42c-bb5123934ce6",
|
||||
"id": "01a1792f-3c06-7715-b3fd-da6738a79f82",
|
||||
"templateId": "01a1792e-e7be-712e-84e4-0cd98f41aa07",
|
||||
"fieldId": "01a1792f-3c05-715e-b3ad-2839cae63239",
|
||||
"required": 0,
|
||||
"position": 6,
|
||||
"sectionId": null
|
||||
|
||||
@ -1,57 +1,59 @@
|
||||
✔ HTTP real: estado, contrato, errores y trazas sin datos sensibles (64.878761ms)
|
||||
✔ HTTP real: entrada JSON inválida y errores internos conservan un contrato seguro (27.548096ms)
|
||||
✔ MariaDB y HTTP: pertenencias, controladores, transiciones, aislamiento e integridad (2213.707827ms)
|
||||
✔ el puerto usa un valor local por defecto y acepta los límites válidos (0.364602ms)
|
||||
✔ el arranque rechaza puertos inválidos sin revelar el valor recibido (0.816006ms)
|
||||
✔ MariaDB: dominios, contexto atómico, concurrencia, integridad y ciclo de vida (792.630633ms)
|
||||
✔ HTTP infraestructura: sesión, pertenencia, validación, identidad e idempotencia (30.063319ms)
|
||||
✔ diagnóstico: causas de Sequelize, ciclos y valores lanzados que no son Error (0.571405ms)
|
||||
✔ diagnóstico: mensajes multilínea, propiedades y cadenas largas no se vuelcan (0.524903ms)
|
||||
✔ contextos de servidor: sesión vigente, ámbito existente, copias y revocación (0.955607ms)
|
||||
✔ HTTP real: estado, contrato, errores y trazas sin datos sensibles (77.204597ms)
|
||||
✔ HTTP real: entrada JSON inválida y errores internos conservan un contrato seguro (29.325551ms)
|
||||
✔ MariaDB y HTTP: pertenencias, controladores, transiciones, aislamiento e integridad (2235.600821ms)
|
||||
✔ el puerto usa un valor local por defecto y acepta los límites válidos (0.614107ms)
|
||||
✔ el arranque rechaza puertos inválidos sin revelar el valor recibido (0.86331ms)
|
||||
✔ proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos (0.399705ms)
|
||||
✔ proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores (0.747308ms)
|
||||
✔ MariaDB: dominios, contexto atómico, concurrencia, integridad y ciclo de vida (852.597667ms)
|
||||
✔ HTTP infraestructura: sesión, pertenencia, validación, identidad e idempotencia (30.130744ms)
|
||||
✔ diagnóstico: causas de Sequelize, ciclos y valores lanzados que no son Error (0.607207ms)
|
||||
✔ diagnóstico: mensajes multilínea, propiedades y cadenas largas no se vuelcan (0.504706ms)
|
||||
✔ contextos de servidor: sesión vigente, ámbito existente, copias y revocación (0.996411ms)
|
||||
▶ MariaDB: publicaciones SMB, historial, proveedor y HTTP autenticado
|
||||
✔ registro y cardinalidad; publicador desconocido y distintos tipos sin AD (356.392903ms)
|
||||
✔ traslado atómico, replay estable y finalización identificada sin tocar accesos (111.981018ms)
|
||||
✔ concurrencia: altas iguales, publicadores distintos, traslados y finalización (148.241983ms)
|
||||
✔ restricciones SQL y protección de borrado también después de finalizar (119.178071ms)
|
||||
✔ fallo al insertar sucesor revierte el cierre anterior (118.745467ms)
|
||||
✔ proveedor: SQL keyset, orientación, historial y cursores ligados a consulta (28.355508ms)
|
||||
✔ resolución SMB por lotes: no confunde dominios, recursos ordinarios o compañías (165.029105ms)
|
||||
✔ estados y desactivación concurrente conservan publicaciones documentadas (514.990061ms)
|
||||
✔ UI 3.5: selector remoto filtra en SQL y pagina sin hidratar el inventario (1436.471586ms)
|
||||
✔ HTTP: sesión, CSRF, validación, idempotencia y consultas paginadas (123.198399ms)
|
||||
✔ MariaDB: publicaciones SMB, historial, proveedor y HTTP autenticado (4756.605138ms)
|
||||
✔ registro y cardinalidad; publicador desconocido y distintos tipos sin AD (365.307283ms)
|
||||
✔ traslado atómico, replay estable y finalización identificada sin tocar accesos (128.542392ms)
|
||||
✔ concurrencia: altas iguales, publicadores distintos, traslados y finalización (181.515477ms)
|
||||
✔ restricciones SQL y protección de borrado también después de finalizar (130.366302ms)
|
||||
✔ fallo al insertar sucesor revierte el cierre anterior (110.985298ms)
|
||||
✔ proveedor: SQL keyset, orientación, historial y cursores ligados a consulta (28.274853ms)
|
||||
✔ resolución SMB por lotes: no confunde dominios, recursos ordinarios o compañías (178.205559ms)
|
||||
✔ estados y desactivación concurrente conservan publicaciones documentadas (618.634608ms)
|
||||
✔ UI 3.5: selector remoto filtra en SQL y pagina sin hidratar el inventario (1472.051801ms)
|
||||
✔ HTTP: sesión, CSRF, validación, idempotencia y consultas paginadas (136.79642ms)
|
||||
✔ MariaDB: publicaciones SMB, historial, proveedor y HTTP autenticado (5029.58801ms)
|
||||
▶ MariaDB: SMB, unicidad vigente, historial, diagnóstico e integridad
|
||||
✔ alta, presentación, lector público y aislamiento (38.900183ms)
|
||||
✔ conflicto funcional, rollback, alias e inactivos (142.198937ms)
|
||||
✔ edición atómica, UUID estable, idempotencia y revisión de UNC (95.592397ms)
|
||||
✔ desactivación libera dirección y reactivación conflictiva conserva estado (163.774395ms)
|
||||
✔ concurrencia: una sola alta y ninguna raíz huérfana (82.283001ms)
|
||||
✔ restricciones SQL: compañía, 1:1, protocolo y unicidad (47.664648ms)
|
||||
✔ legado: ausente, inválido, válido, duplicado y candidato access; diagnóstico sin escritura (439.420705ms)
|
||||
✔ integración access existente y cambio de UNC conservan contexto y referencias (175.55068ms)
|
||||
✔ FolderMapping conserva UNC, letra e historial y detecta dirección desactualizada (409.768588ms)
|
||||
✔ HTTP autenticado: contrato SMB y diagnóstico acotado sin aceptar parámetros (65.420477ms)
|
||||
✔ borrado sin referencias limpia proyección; una FK externa impide borrado (261.540007ms)
|
||||
✔ MariaDB: SMB, unicidad vigente, historial, diagnóstico e integridad (2639.933755ms)
|
||||
✔ alta, presentación, lector público y aislamiento (51.896771ms)
|
||||
✔ conflicto funcional, rollback, alias e inactivos (171.434132ms)
|
||||
✔ edición atómica, UUID estable, idempotencia y revisión de UNC (107.796502ms)
|
||||
✔ desactivación libera dirección y reactivación conflictiva conserva estado (208.33645ms)
|
||||
✔ concurrencia: una sola alta y ninguna raíz huérfana (87.376051ms)
|
||||
✔ restricciones SQL: compañía, 1:1, protocolo y unicidad (66.232093ms)
|
||||
✔ legado: ausente, inválido, válido, duplicado y candidato access; diagnóstico sin escritura (505.942361ms)
|
||||
✔ integración access existente y cambio de UNC conservan contexto y referencias (205.614119ms)
|
||||
✔ FolderMapping conserva UNC, letra e historial y detecta dirección desactualizada (420.469906ms)
|
||||
✔ HTTP autenticado: contrato SMB y diagnóstico acotado sin aceptar parámetros (71.296427ms)
|
||||
✔ borrado sin referencias limpia proyección; una FK externa impide borrado (237.845557ms)
|
||||
✔ MariaDB: SMB, unicidad vigente, historial, diagnóstico e integridad (2874.428818ms)
|
||||
▶ MariaDB: nuevas carpetas SMB integradas con accesos sin adoptar históricos
|
||||
✔ alta automática: misma identidad, ninguna dependencia de dominio o publicador (12.60939ms)
|
||||
✔ renombrar el tipo de acceso conserva su UUID para las siguientes altas (55.298894ms)
|
||||
✔ el cliente no puede imponer buzón, otro tipo ni contexto; se revierte el tipo preparado (74.970834ms)
|
||||
✔ renombrado, UNC y estados sincronizan valores y UUID de todas las revisiones (133.734351ms)
|
||||
✔ necesidad y concesiones AD y NAS local: cobertura documental y borrado protegido (1191.363179ms)
|
||||
✔ fallo después de escribir acceso revierte Resource, SMB, AccessItem y tipo (38.852577ms)
|
||||
✔ carpeta anterior sin integración: lectura, edición y estado no la adoptan (158.133526ms)
|
||||
✔ MariaDB: nuevas carpetas SMB integradas con accesos sin adoptar históricos (2343.789395ms)
|
||||
✔ MariaDB: las plantillas propagan estructura en todas las compañías de forma atómica e idempotente (1585.94158ms)
|
||||
✔ alta automática: misma identidad, ninguna dependencia de dominio o publicador (11.477685ms)
|
||||
✔ renombrar el tipo de acceso conserva su UUID para las siguientes altas (65.860683ms)
|
||||
✔ el cliente no puede imponer buzón, otro tipo ni contexto; se revierte el tipo preparado (93.529686ms)
|
||||
✔ renombrado, UNC y estados sincronizan valores y UUID de todas las revisiones (136.588ms)
|
||||
✔ necesidad y concesiones AD y NAS local: cobertura documental y borrado protegido (1319.027689ms)
|
||||
✔ fallo después de escribir acceso revierte Resource, SMB, AccessItem y tipo (40.417492ms)
|
||||
✔ carpeta anterior sin integración: lectura, edición y estado no la adoptan (170.026129ms)
|
||||
✔ MariaDB: nuevas carpetas SMB integradas con accesos sin adoptar históricos (2588.791754ms)
|
||||
✔ MariaDB: las plantillas propagan estructura en todas las compañías de forma atómica e idempotente (1637.984212ms)
|
||||
▶ MariaDB: proveedor real, lotes, seguridad, SQL paginado y transiciones de pertenencias
|
||||
✔ registro estático y consultas vacías desde ambos extremos (6.754748ms)
|
||||
✔ directa, inversa, either, roles y UUID canónicos sin duplicados (40.533589ms)
|
||||
✔ keyset SQL: páginas de uno, fin y cursores ligados a consulta (26.276387ms)
|
||||
✔ lotes reales sin N+1, orden, repetidos, estados y referencias inaccesibles (19.499839ms)
|
||||
✔ traslado y fin conservan identidad, estado y rol histórico (38.136271ms)
|
||||
✔ ámbito, contextos falsificados, sesión revocada y acceso inverso (18.632432ms)
|
||||
✔ EXPLAIN real e índices disponibles para ambos extremos (6.075043ms)
|
||||
✔ MariaDB: proveedor real, lotes, seguridad, SQL paginado y transiciones de pertenencias (1486.420567ms)
|
||||
✔ registro estático y consultas vacías desde ambos extremos (6.826136ms)
|
||||
✔ directa, inversa, either, roles y UUID canónicos sin duplicados (46.840845ms)
|
||||
✔ keyset SQL: páginas de uno, fin y cursores ligados a consulta (30.512559ms)
|
||||
✔ lotes reales sin N+1, orden, repetidos, estados y referencias inaccesibles (23.04302ms)
|
||||
✔ traslado y fin conservan identidad, estado y rol histórico (42.902724ms)
|
||||
✔ ámbito, contextos falsificados, sesión revocada y acceso inverso (21.26291ms)
|
||||
✔ EXPLAIN real e índices disponibles para ambos extremos (6.220933ms)
|
||||
✔ MariaDB: proveedor real, lotes, seguridad, SQL paginado y transiciones de pertenencias (1553.285028ms)
|
||||
ℹ {"column":"assetId","includeEnded":false,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"assetId","includeEnded":false,"forcedIndexPlan":[{"key":"directory_membership_asset","Extra":"Using index condition; Using where"}]}
|
||||
ℹ {"column":"assetId","includeEnded":true,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
@ -60,11 +62,11 @@
|
||||
ℹ {"column":"domainResourceId","includeEnded":false,"forcedIndexPlan":[{"key":"directory_membership_domain_page","Extra":"Using index condition; Using where"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":true,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_fk,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":true,"forcedIndexPlan":[{"key":"directory_membership_domain_page","Extra":"Using index condition"}]}
|
||||
ℹ tests 50
|
||||
ℹ tests 52
|
||||
ℹ suites 0
|
||||
ℹ pass 50
|
||||
ℹ pass 52
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 17252.621293
|
||||
ℹ duration_ms 18273.692638
|
||||
|
||||
@ -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-DDY57tZZ.css": "f776869d2151f8dd354692bc6233d5ce5a61da3029a2ee9a105de171205521ff",
|
||||
"apps/web/dist/assets/index-DFkWetkr.js": "fc821d95d6d2519eeddab9b65af98132415d6d2e40df4d7947d52db307d9526b",
|
||||
"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-CLylSTwa.js": "3d5f231340d4b2b0192f8486a69937ed8e72409a782449647b90056041d73927",
|
||||
"apps/web/dist/assets/workplace-map-canvas-DLioOiRN.css": "b0cb7bd6fec5951c9c8f5808821438206604b1629a912c96009ac427f38e7d69",
|
||||
"apps/web/dist/index.html": "584d5eefe3f32738969db97bf4cdae57b6ab711a0ed519c0b7776fde61bf5e03"
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
{
|
||||
"recordedAtUtc": "2026-10-26T14:01:00.156013+00:00",
|
||||
"candidateCommit": "a72d6a2ec8985e1d51f74db68a1b3aabb3693030",
|
||||
"initialWorkingTree": "clean",
|
||||
"branch": "main",
|
||||
"verdict": "NO PREPARADO",
|
||||
"node": "24.20.0",
|
||||
"pnpm": "11.25.0",
|
||||
"sourceChangesByThisIntervention": [],
|
||||
"blockers": [
|
||||
"B-01: compiled server cannot parse (duplicated createRequire)",
|
||||
"B-02: no Secure cookie in tested TLS-to-HTTP scenario"
|
||||
],
|
||||
"productionConnected": false,
|
||||
"databaseConnected": false,
|
||||
"migrationsExecuted": false,
|
||||
"productionActivated": false,
|
||||
"newResults": {
|
||||
"resources": {
|
||||
"pass": 124,
|
||||
"fail": 0
|
||||
},
|
||||
"assetsDirected": {
|
||||
"pass": 9,
|
||||
"fail": 0
|
||||
},
|
||||
"companiesDirected": {
|
||||
"pass": 29,
|
||||
"fail": 2
|
||||
}
|
||||
},
|
||||
"newVisualEvidence": 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: loopback sin BD",
|
||||
"exitCode": 0,
|
||||
"log": "technical/proxy.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/resources test",
|
||||
"exitCode": 0,
|
||||
"log": "technical/resources.log"
|
||||
},
|
||||
{
|
||||
"command": "Pruebas dirigidas Companies: companies-page, people-search, workspace-routes",
|
||||
"exitCode": 1,
|
||||
"log": "technical/companies.log"
|
||||
},
|
||||
{
|
||||
"command": "Pruebas dirigidas Assets: infrastructure, network-share-ui",
|
||||
"exitCode": 0,
|
||||
"log": "technical/assets.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"
|
||||
},
|
||||
{
|
||||
"command": "Biome sobre 25 fuentes cambiadas",
|
||||
"exitCode": 0,
|
||||
"log": "technical/biome.log"
|
||||
},
|
||||
{
|
||||
"command": "ESLint sobre las mismas 25 fuentes",
|
||||
"exitCode": 0,
|
||||
"log": "technical/eslint.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/resources typecheck",
|
||||
"exitCode": 0,
|
||||
"log": "technical/resources-types.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/companies typecheck",
|
||||
"exitCode": 0,
|
||||
"log": "technical/companies-types.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/assets typecheck",
|
||||
"exitCode": 0,
|
||||
"log": "technical/assets-types.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/web typecheck",
|
||||
"exitCode": 0,
|
||||
"log": "technical/web-types.log"
|
||||
}
|
||||
],
|
||||
"files": {
|
||||
"builds.json": "6b1fa426f451ad4342f2c84a488688943ea29f8cafad67a5f493c9a79af67347",
|
||||
"technical/assets-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
|
||||
"technical/assets.log": "8e4f3c71a9ab5b69d551c91342f45e3c819142ee2c8beba1a050a8e15f5c05c4",
|
||||
"technical/biome.log": "732ef6408b59471f0e5c784629987b5cb1784215f03df842c2d7d28cecbc3afa",
|
||||
"technical/build.log": "9df2bc56aab97bdf31d54ea75265bf5b5a72a8d4edad3c4ef6ac3e1ac6c3ddf5",
|
||||
"technical/check.log": "c375893658e6b4d145af0ae0dec44118d585ddc2e1c5aee14602fd86ba689af5",
|
||||
"technical/companies-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
|
||||
"technical/companies.log": "e1b6cd20abec7cc1744afaaea2db0031e90c4a89b50eac8eb712222405e28a8d",
|
||||
"technical/eslint.log": "4d956f90d70c5ac65b4343b539607a645dfe31a5d9b00a6a9c0d0f4c86959008",
|
||||
"technical/lint.log": "f87ffc124aa5a17d60abcf020fae2857a3d22501057ef317d8db81baf62bc264",
|
||||
"technical/node-check.log": "8257d56c85b4657dfaaedc60781e41646a526c3bfc5b7e9ecc351f321f6eae8e",
|
||||
"technical/proxy.log": "b70d5360741b61f26b7f9a1366481b22acb55704f8a0d567b438686b614ccc69",
|
||||
"technical/resources-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
|
||||
"technical/resources.log": "a18d3689525a9be932abbda4581f51f9fc8dc85502762adc4d5b7d2ca550b996",
|
||||
"technical/types.log": "72909d0b3f63a30a87720cbb7691c4bbef3b4b2c0bb5d4dba05b030958aca3aa",
|
||||
"technical/web-types.log": "29c5ee0f6b0b20dbb259b99d01276198c3d28190090706394608d4deda6d2c0b",
|
||||
"traceability.json": "21277fca880e35cab2d4445d1ace17d203b291fa5c0b2b3a7c6d4837b298fd93"
|
||||
},
|
||||
"reportSha256": "7d9e72143f907304f1880ab59ab4fb719d8b327907fdea6a5b3bfe569ecceb54"
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
$ tsc -p tsconfig.json
|
||||
@ -0,0 +1,17 @@
|
||||
✔ infraestructura: clasificación de serie independiente del nombre/icono y selección manual prioritaria (2.621021ms)
|
||||
✔ infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos (0.444504ms)
|
||||
✔ infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos (122.305107ms)
|
||||
✔ SMB: relaciones paginadas, UNC Unicode y enlaces bidireccionales es/en (17.311343ms)
|
||||
✔ SMB: historial conserva periodos y actores y no ofrece modificar periodos finalizados (2.37392ms)
|
||||
✔ SMB: vacío real y error de lectura son distintos (10.554087ms)
|
||||
✔ SMB: denegación oculta datos previos y carga inicial conserva cabeceras (16.156433ms)
|
||||
✔ SMB: transferencia conserva UUID y envía un único comando propietario con CSRF (26.555319ms)
|
||||
✔ SMB: búsqueda remota y cursor opaco viajan al servidor (1.691013ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2626.924953
|
||||
@ -0,0 +1 @@
|
||||
Checked 25 files in 26ms. 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 ddaa55eb9eb1f13e
|
||||
@metis/web:build: cache bypass, force executing 8444758b1331f844
|
||||
@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 405ms
|
||||
@metis/web:build: ✓ 10072 modules transformed.
|
||||
@metis/web:build: rendering chunks...
|
||||
@metis/web:build: computing gzip size...
|
||||
@metis/web:build: dist/index.html 0.54 kB │ gzip: 0.34 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-DDY57tZZ.css 253.48 kB │ gzip: 38.88 kB
|
||||
@metis/web:build: dist/assets/workplace-map-canvas-CLylSTwa.js 227.75 kB │ gzip: 74.26 kB
|
||||
@metis/web:build: dist/assets/index-DFkWetkr.js 1,826.14 kB │ gzip: 512.39 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.03s
|
||||
|
||||
Tasks: 2 successful, 2 total
|
||||
Cached: 0 cached, 2 total
|
||||
Time: 1.752s
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
$ tsc -p tsconfig.json
|
||||
@ -0,0 +1,75 @@
|
||||
✔ las subentradas de Personas conservan contexto y reemplazan el filtro de la vista (25.937714ms)
|
||||
✔ el dashboard no ofrece selector de compañía y conserva las métricas de cada ámbito (49.547507ms)
|
||||
✔ el resumen de compañía es la entrada por defecto y ofrece destinos reales (82.401977ms)
|
||||
✔ los índices de catálogos distinguen definiciones globales y variantes de compañía (8.083466ms)
|
||||
✔ el listado presenta estado vacío y acceso al alta sin formularios visibles (44.398465ms)
|
||||
✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (53.636941ms)
|
||||
✔ el formulario de sede ofrece dirección y teléfono opcionales (8.145767ms)
|
||||
✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (10.261584ms)
|
||||
✔ asignar un departamento permite añadir el primero y conserva el selector en edición (21.754179ms)
|
||||
✔ el tablero de compañía tiene composición propia, acciones arriba y métricas limitadas a su ámbito (16.052132ms)
|
||||
✔ el menú de compañía ordena trabajo y personas por grupos sin repetir las acciones de usuario (27.868629ms)
|
||||
✔ el menú muestra el contador de infraestructura: ready (36.208298ms)
|
||||
✔ el menú muestra el contador de infraestructura: pending (41.612742ms)
|
||||
✔ el menú muestra el contador de infraestructura: error (49.576607ms)
|
||||
✔ sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto (41.406941ms)
|
||||
✔ la navegación de compañía conserva 21 enlaces reales, sección activa y contexto es/en (77.86574ms)
|
||||
✔ Personas conserva búsqueda por nombre, contactos, consulta y distinción ñ/n en es/en (195.641108ms)
|
||||
✔ Personas admite actividad en listado y ficha, con los demás criterios de contexto (7.528762ms)
|
||||
✔ el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha (1.445512ms)
|
||||
✔ las funcionalidades archivadas no tienen rutas activas (0.535804ms)
|
||||
✔ el filtro de plantilla permite abrir listados y fichas de activos y recursos (0.682706ms)
|
||||
✔ el filtro de plantilla se limita a los inventarios que conocen sus definiciones (0.323303ms)
|
||||
✔ todas las secciones de compañía tienen una ruta propia y el índice es el resumen (1.748815ms)
|
||||
✔ los índices de catálogos y los cinco catálogos globales son destinos independientes (0.356003ms)
|
||||
✔ las configuraciones conservan la compañía y el UUID del perfil (0.531005ms)
|
||||
✔ las rutas desconocidas y los parámetros inválidos no abren una sección por defecto (2.520721ms)
|
||||
✔ la ficha de puesto tiene ruta propia, conserva el UUID y rechaza identificadores inválidos (0.809107ms)
|
||||
✔ dominios: enlaces a cuentas y grupos limitados al entorno de la compañía (0.672306ms)
|
||||
✔ los parámetros de contexto llegan al consumidor sin crear rutas nuevas ni aceptar claves ajenas (0.626605ms)
|
||||
✔ catálogo de aplicaciones: UUID global y criterios propios sin compañía ficticia (0.527704ms)
|
||||
✔ catálogo SO: ruta específica y filtros, sin confundir con aplicaciones (0.401103ms)
|
||||
ℹ tests 31
|
||||
ℹ suites 0
|
||||
ℹ pass 29
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 6447.052094
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at src/web/companies/shared/companies-page.test.tsx:1:6023
|
||||
✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (53.636941ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(markup.includes("Personas (1)"))
|
||||
|
||||
at TestContext.<anonymous> (/home/david/dev/metis-desk/modules/companies/src/web/companies/shared/companies-page.test.tsx:229:10)
|
||||
at async Test.run (node:internal/test_runner/test:1404:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at src/web/companies/shared/companies-page.test.tsx:1:7439
|
||||
✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (10.261584ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(markup.includes("Esta persona no tiene acceso a Metis Desk"))
|
||||
|
||||
at TestContext.<anonymous> (/home/david/dev/metis-desk/modules/companies/src/web/companies/shared/companies-page.test.tsx:259:10)
|
||||
at async Test.run (node:internal/test_runner/test:1404:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
|
||||
/home/david/dev/metis-desk/modules/assets/src/common/locales/en.json
|
||||
0:0 warning File ignored because no matching configuration was supplied
|
||||
|
||||
/home/david/dev/metis-desk/modules/assets/src/common/locales/es.json
|
||||
0:0 warning File ignored because no matching configuration was supplied
|
||||
|
||||
/home/david/dev/metis-desk/modules/companies/src/common/locales/en.json
|
||||
0:0 warning File ignored because no matching configuration was supplied
|
||||
|
||||
/home/david/dev/metis-desk/modules/companies/src/common/locales/es.json
|
||||
0:0 warning File ignored because no matching configuration was supplied
|
||||
|
||||
✖ 4 problems (0 errors, 4 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 2237 files in 1084ms. 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,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.566811ms)
|
||||
✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.354202ms)
|
||||
✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (35.574962ms)
|
||||
✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (9.875972ms)
|
||||
✔ F2: elegibilidad de SO exige stock real y campos globales (0.322402ms)
|
||||
✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.618505ms)
|
||||
✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (28.885812ms)
|
||||
✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (10.588878ms)
|
||||
✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (2.358317ms)
|
||||
✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (1.610911ms)
|
||||
✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (1.40251ms)
|
||||
✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (1.514612ms)
|
||||
✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (10.87208ms)
|
||||
✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (5.576741ms)
|
||||
✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (5.068938ms)
|
||||
✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.389503ms)
|
||||
✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.275302ms)
|
||||
✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.287002ms)
|
||||
✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (17.512729ms)
|
||||
✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (383.032011ms)
|
||||
✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (2.519719ms)
|
||||
✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (2.319017ms)
|
||||
✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (5.623841ms)
|
||||
✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (7.099653ms)
|
||||
✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (1.206509ms)
|
||||
✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (11.798387ms)
|
||||
✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (1.620412ms)
|
||||
✔ SMB acepta \\NAS-01\Finanzas (2.329217ms)
|
||||
✔ SMB acepta \\files.example.local\Docs (0.310302ms)
|
||||
✔ SMB acepta \\192.168.5.10\C$ (0.152102ms)
|
||||
✔ SMB acepta \\samba\Diseño\Año 2026 (1.793813ms)
|
||||
✔ SMB acepta \\NAS\Docs\ (0.720605ms)
|
||||
✔ SMB rechaza "" (0.293202ms)
|
||||
✔ SMB rechaza "\\\\" (3.038023ms)
|
||||
✔ SMB rechaza "\\\\host" (0.177702ms)
|
||||
✔ SMB rechaza "\\\\\\share" (1.699012ms)
|
||||
✔ SMB rechaza "\\\\host\\\\" (0.722005ms)
|
||||
✔ SMB rechaza "C:\\Datos" (0.154902ms)
|
||||
✔ SMB rechaza "https://host/share" (0.096901ms)
|
||||
✔ SMB rechaza "\\\\host\\share\\.." (0.393103ms)
|
||||
✔ SMB rechaza "\\\\host\\share\\." (0.112101ms)
|
||||
✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.160901ms)
|
||||
✔ SMB rechaza "\\\\host\\share\\\\child" (0.362202ms)
|
||||
✔ SMB rechaza "\\\\host\\share." (0.298002ms)
|
||||
✔ SMB rechaza "\\\\host\\ share" (0.087701ms)
|
||||
✔ SMB rechaza "\\\\ho st\\share" (0.066701ms)
|
||||
✔ SMB rechaza "\\\\host\\bad\u0000" (0.0881ms)
|
||||
✔ SMB rechaza "\\\\host\\bad\ud800" (0.132401ms)
|
||||
✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.0823ms)
|
||||
✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (0.460103ms)
|
||||
✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (24.586281ms)
|
||||
✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (25.87989ms)
|
||||
✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (13.880002ms)
|
||||
✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (6.418047ms)
|
||||
✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.716405ms)
|
||||
✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (23.878276ms)
|
||||
✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (17.526229ms)
|
||||
✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (58.875533ms)
|
||||
✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (18.236334ms)
|
||||
✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (24.079477ms)
|
||||
✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (24.784282ms)
|
||||
✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (39.387589ms)
|
||||
✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (15.783916ms)
|
||||
✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (10.472577ms)
|
||||
✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (5.968244ms)
|
||||
✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (1.914414ms)
|
||||
✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (17.604329ms)
|
||||
✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (5.918643ms)
|
||||
✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (3.099623ms)
|
||||
✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (30.690025ms)
|
||||
✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (2.572819ms)
|
||||
✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.809806ms)
|
||||
✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (14.542606ms)
|
||||
✔ recurso: identidad estable, historial, desactivación e idempotencia (2.350717ms)
|
||||
✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (7.830157ms)
|
||||
✔ secciones: nombres, identidades, referencias y un solo nivel (7.249253ms)
|
||||
✔ secciones: igualdad, orden, identidad y persistencia JSON (7.518056ms)
|
||||
✔ secciones: plantillas globales y locales conservan separación y UUID al editar (10.94218ms)
|
||||
✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (31.735333ms)
|
||||
✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (20.229148ms)
|
||||
✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (22.969168ms)
|
||||
✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (4.417333ms)
|
||||
✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (139.823925ms)
|
||||
✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (1.251309ms)
|
||||
✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (5.062142ms)
|
||||
✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.499413ms)
|
||||
✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (2.088017ms)
|
||||
✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (119.402885ms)
|
||||
✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (133.635502ms)
|
||||
✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (115.453452ms)
|
||||
✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (33.94968ms)
|
||||
✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (205.106191ms)
|
||||
✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (35.980697ms)
|
||||
✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (3.817031ms)
|
||||
✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (1.17791ms)
|
||||
✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (9.457178ms)
|
||||
✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.24261ms)
|
||||
✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (22.95749ms)
|
||||
✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (89.58614ms)
|
||||
✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (3.399928ms)
|
||||
✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (35.938297ms)
|
||||
✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (13.272598ms)
|
||||
✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (1.959815ms)
|
||||
✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.25261ms)
|
||||
✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.339503ms)
|
||||
✔ la integración de buzón conserva su configuración explícita y no inventa referencias (1.534712ms)
|
||||
✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (1.006907ms)
|
||||
✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (31.196628ms)
|
||||
✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (0.351703ms)
|
||||
✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (0.527304ms)
|
||||
✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (11.208982ms)
|
||||
✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (128.795562ms)
|
||||
✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (440.713431ms)
|
||||
✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (25.640611ms)
|
||||
✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (43.700859ms)
|
||||
✔ asistencia: alta de recurso con plantilla específica y características opcionales (8.375469ms)
|
||||
✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (15.921331ms)
|
||||
✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (12.540304ms)
|
||||
✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (25.684411ms)
|
||||
✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (16.560137ms)
|
||||
✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (21.986881ms)
|
||||
✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (16.005132ms)
|
||||
✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (10.419285ms)
|
||||
✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (41.583744ms)
|
||||
ℹ tests 124
|
||||
ℹ suites 0
|
||||
ℹ pass 124
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5324.816734
|
||||
@ -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/shadcn-ui:typecheck: cache miss, executing 3a02880bf260fd89
|
||||
@repo/rdx-utils:typecheck: cache hit, replaying logs e660b2f14d16d88c
|
||||
@repo/rdx-utils:typecheck: $ tsc -p tsconfig.json
|
||||
@metis/relationship-contracts:typecheck: cache hit, replaying logs 6ecbe82d1a36ae07
|
||||
@metis/relationship-contracts:typecheck: $ tsc -p tsconfig.json
|
||||
@repo/rdx-ddd:typecheck: cache hit, replaying logs 315b0dccfb6d76d3
|
||||
@repo/rdx-ddd: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: 5.327s
|
||||
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,221 @@
|
||||
{
|
||||
"candidateCommit": "a72d6a2ec8985e1d51f74db68a1b3aabb3693030",
|
||||
"branch": "main",
|
||||
"initialWorkingTree": "clean",
|
||||
"goBase": "d2bf856c973419277f0b30f6437242cd82f7d720",
|
||||
"unchangedGoSources": 1854,
|
||||
"changedGoSources": [
|
||||
{
|
||||
"path": "modules/assets/src/common/locales/en.json",
|
||||
"go": "ea4529d517d5e64202a572eb812c68ac8f2a88816630793d6bc7bf1e9b0c27f6",
|
||||
"candidate": "51577bb9e22805ddb0fb659788ff3a36230c6b4d40cccefdf900c3500c892a64"
|
||||
},
|
||||
{
|
||||
"path": "modules/assets/src/common/locales/es.json",
|
||||
"go": "0a36b2693a5cc69d7f5ae12d8b814b34d3d59e921678522f87b98317185b5aac",
|
||||
"candidate": "9fa4681811b7ca292e52e65b352beab127227ac66fb53bc610e9a821250bb6ab"
|
||||
},
|
||||
{
|
||||
"path": "modules/assets/src/web/index.ts",
|
||||
"go": "30a8c78828a2bad2707b129bb8c96c059915680a3e075618250fd0a2dcaf2332",
|
||||
"candidate": "6ce50764e5f167df2cd09fb0f0091d358e38e95428f0f41481d9fb1837204c85"
|
||||
},
|
||||
{
|
||||
"path": "modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx",
|
||||
"go": "107547a06d24fb7b132558caee9d1cc72d72e552c8e13d5ba838220027605637",
|
||||
"candidate": "e50db875ea633ceecc6763f4d4f4c683efadd3be2e85d5249d3f1fdc23e79198"
|
||||
},
|
||||
{
|
||||
"path": "modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx",
|
||||
"go": "8754273ed7ee336eac8b1002ecf53141f997b29b6b944a59b8fb034616987199",
|
||||
"candidate": "d73da6731f6de334f216fa901c65b5127b791916a93ab8c001153610025ddacf"
|
||||
},
|
||||
{
|
||||
"path": "modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx",
|
||||
"go": "d62142789b3988d90beb5dc56fcc925667248af84e5d26e36d0e32feb39bdc39",
|
||||
"candidate": "d736a041a42c851ec9a2fa95bbf4787959f86f569f985b1222ae5fb0d8ba6956"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/common/locales/en.json",
|
||||
"go": "2321de3c7dce16aac9e5f561de72b24fc4da8298ada19ef90d17626a74471aed",
|
||||
"candidate": "1e27291f5ace663cc6963322f57de76cbc453bdb484a0faf12b5c10aab3469ee"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/common/locales/es.json",
|
||||
"go": "a63c9ca9794c1f96619d97c5434205ddb161c76485dfc5d1ad2bd553a4ffa3ba",
|
||||
"candidate": "5392229e6f563116959d2465045d19d119cecb375740da61d67d0c267d916dde"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts",
|
||||
"go": "b0eb9df2ee6530acaaaf98973c9f9260cd0f1b519833a3840e4a0881a3e8b9e9",
|
||||
"candidate": "b774f445c5b97f31e42251df9a642b06661dc57ad26361eb8b9e8a5d0c54ef8c"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx",
|
||||
"go": "4d8db05accb0a8d128bcccbb70db558b10def0549e9c4ad815b754bae494154c",
|
||||
"candidate": "24ab9e346587a5c882aabbe67d23949657e5bb1c4e401875a0e98288211830a2"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/companies/detail/ui/company-section.tsx",
|
||||
"go": "18cbe45389e8aa9af3c85ed328a2365ec3caba9988a918baaa066292adcefc27",
|
||||
"candidate": "fb307ba4b0260672e0ee3f82abc358d1df95b9ee29325488c207432e463ed8bf"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/companies/shared/companies-page.test.tsx",
|
||||
"go": "edd2b477a2c51c8460686307aa55f13c868a100684c39c92ce79d74290b105cc",
|
||||
"candidate": "7004a66eae7c393495a405aa084740592fbbe0427f00ab518fd98975f50f601f"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/people/list/controllers/use-people-list-context.ts",
|
||||
"go": "161746fe7e23145dad3d72cbc6b72bb235198f4b56ccdc0a8c8af13285ff6141",
|
||||
"candidate": "55f3b971aa1219dc6dca56df7d9737cbf33551a7c67d9af6ba6cdee1a5774af5"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/people/list/people-search.test.tsx",
|
||||
"go": "5908a4d7d9a3735aef601867ec000e8f45e9750461b5f39a01a4c1de0375579d",
|
||||
"candidate": "faa1909b06fc242552d0f17cce831d6eabb3421d4ef684e519b62e89cdabaa31"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/people/list/ui/people-section.tsx",
|
||||
"go": "101eb330610f40a14061c1bc7d98712ce999a1d550a474848e05b8762eea11b1",
|
||||
"candidate": "ef324fb990ad413dfb94bf487389903dde02d2aad4e525f78654f1d38af97b60"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts",
|
||||
"go": "f02df7b74cf2048aa155a81fc44fd6b54dba3619197f42f4b9f2875eaa9a49e9",
|
||||
"candidate": "c04a641b876f869de6b94a2d9dc5125a9860c2ceb3fef477b672ab4f30bf3576"
|
||||
},
|
||||
{
|
||||
"path": "modules/companies/src/web/workspace/shared/workspace-routes.test.ts",
|
||||
"go": "0fc0dfe2559218c5efac4fa1bf799b9a6f894a14de21144318f0ed634048d64b",
|
||||
"candidate": "2de5e705fc94b0c241b0ab8eaee13f12c999e89b4dae77935d160274f7970131"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/common/locales/en.json",
|
||||
"go": "a1dd1e416af87cd94ffa2c80855e9e809e3b62b1ddb679a4688d2adbd67ccc9e",
|
||||
"candidate": "38b1298bdee2c8f4f547669f5d766faaceb6d22412479da301fd96165f17f93b"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/common/locales/es.json",
|
||||
"go": "edc6151d0bfac31da3a1fed555231aa7eae5b7cb71779533ea503dbe38eb986c",
|
||||
"candidate": "f95606f21c1a25b58696a9d10e4853190af43894ff536971d59f31ece3ba008e"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/controllers/use-company-applications-controller.ts",
|
||||
"go": "050abdeb0f351ba85ea001ad94a450fc174d0d6d0c871da41949fee1732620a3",
|
||||
"candidate": "7ce972f6a5f0542656b096f886ae058bd92dc9aef7beee21e1e327a4348cc28f"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/controllers/use-resources-page-controller.ts",
|
||||
"go": "10d3101bbad439537af0fb6195e199484641d2a9bc59d50a1ddc29044ebe285c",
|
||||
"candidate": "72a55dedce077c6372272ef95b05575f9efced513b4e6ea912c4af423cf3cc11"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/types/resources-page-props.ts",
|
||||
"go": "9a7a59f5f93fb30b6e60df70f391c9d10082149b8480bb43e9848cf7d00da5d0",
|
||||
"candidate": "1a8e0802975c47f3b0bff4d3bd9fa05000c6ac87f93f2546303475acc86ced30"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/ui/company-applications-list.tsx",
|
||||
"go": "73dced13ad6d1adaff9d10a1b8ad78a402e3b69fd54230d7c13c2a2353123660",
|
||||
"candidate": "e9290d45c77ed97f28c205e64358465a4e1cd6674bad393b2d0e1d11696b9162"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/ui/resource-inventory-table.tsx",
|
||||
"go": "de3d16f1df8dd869a1e692be3e35a23927475c5efdd5ec88aa1a651783052400",
|
||||
"candidate": "b3e42461e88e9e1941fedab69b6c2e49ca539fb9cb3d90ccd3cb529587da156b"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/list/ui/resources-page.tsx",
|
||||
"go": "d87fa2506625f88da5fdb42b79d046f06620e1e98cf278c28f55d87eae06d528",
|
||||
"candidate": "588b96019ddd059804ad5dbd3330566780fd36ab6d7f8cc9193439ced1442273"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts",
|
||||
"go": "85f4e344a0b294276626d57824179276154dec0aa757d474f891d81b08f36680",
|
||||
"candidate": "eb666b1ad962ab2c05b462c8683ca4449fc341d72651f0f8f925f8a6769679ff"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/shared/types/resource-form-props.ts",
|
||||
"go": "66ad516a26c6785cd285cb3bfdf9d5704b5e1b3dea6dae17b231af3622c19c8c",
|
||||
"candidate": "3b78f0f1c7863a58c58fabfe4725fdc37af1b5efa6dc54701641393be3498b4c"
|
||||
},
|
||||
{
|
||||
"path": "modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx",
|
||||
"go": "624808adc57d3d97211e9bf28d463b1cad8523b4b72e79983644bced4d06b51c",
|
||||
"candidate": "0b313afa26807e2ba2e5f96117a92cc3e8d36449ce293e0592cac75405712645"
|
||||
},
|
||||
{
|
||||
"path": "packages/rdx-ui/src/components/cursor-entity-table.tsx",
|
||||
"go": "6505c4f913b639038a71b41b169cd2382583359585795ac32fbbcfb4c92f25e7",
|
||||
"candidate": "9a5398dc82dd3935682c39e59e8e6a6e1e023f5fa60496afa6b2ee851fa335b2"
|
||||
},
|
||||
{
|
||||
"path": "packages/rdx-ui/src/entity-column-visibility.test.tsx",
|
||||
"go": "95aa7c35e5f70f8d61cd1ae5e08edfb591d24c3d85466d7eba4cdf2a4f3e3145",
|
||||
"candidate": "5ead7a4b1dd78a73a6ed54d1f7fa1238d33822496cc41391215edc1799aedc6d"
|
||||
}
|
||||
],
|
||||
"addedSources": [
|
||||
"modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts",
|
||||
"modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx",
|
||||
"modules/companies/src/web/resources/list/ui/company-resources.tsx",
|
||||
"modules/resources/src/web/resources/list/controllers/use-resource-list-navigation.ts",
|
||||
"modules/resources/src/web/resources/list/helpers/resource-additional-columns.tsx",
|
||||
"modules/resources/src/web/resources/list/helpers/resource-characteristic-columns.ts",
|
||||
"modules/resources/src/web/resources/list/helpers/resource-columns.test.tsx",
|
||||
"modules/resources/src/web/resources/list/helpers/resource-list-preferences.test.tsx",
|
||||
"modules/resources/src/web/resources/list/helpers/resource-list-preferences.ts"
|
||||
],
|
||||
"lockfileSha256": "bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17",
|
||||
"changedSince6992": [
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/builds.json",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/manifest.json",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/biome.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/build.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/check.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/eslint.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/lint.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/node-check.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/proxy.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/rdx-ui-types.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources-types.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/resources.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/table.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/types.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/technical/web-types.log",
|
||||
".artifacts/resources-global-operating-systems-preflight-6992a4ac/traceability.json",
|
||||
"docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md",
|
||||
"docs/componentes-de-interfaz.md",
|
||||
"docs/integracion-visual-de-recursos-compartidos-smb.md",
|
||||
"docs/rutas-del-cliente.md",
|
||||
"modules/assets/src/common/locales/en.json",
|
||||
"modules/assets/src/common/locales/es.json",
|
||||
"modules/assets/src/web/index.ts",
|
||||
"modules/assets/src/web/infrastructure/list/ui/infrastructure-page.tsx",
|
||||
"modules/assets/src/web/infrastructure/shared/helpers/infrastructure-items.ts",
|
||||
"modules/assets/src/web/infrastructure/shared/infrastructure.test.tsx",
|
||||
"modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx",
|
||||
"modules/companies/src/common/locales/en.json",
|
||||
"modules/companies/src/common/locales/es.json",
|
||||
"modules/companies/src/web/companies/detail/controllers/use-company-detail-controller.ts",
|
||||
"modules/companies/src/web/companies/detail/ui/company-people-navigation.tsx",
|
||||
"modules/companies/src/web/companies/detail/ui/company-section-navigation.tsx",
|
||||
"modules/companies/src/web/companies/detail/ui/company-section.tsx",
|
||||
"modules/companies/src/web/companies/shared/companies-page.test.tsx",
|
||||
"modules/companies/src/web/people/list/controllers/use-people-list-context.ts",
|
||||
"modules/companies/src/web/people/list/people-search.test.tsx",
|
||||
"modules/companies/src/web/people/list/ui/people-section.tsx",
|
||||
"modules/companies/src/web/resources/list/ui/company-resources.tsx",
|
||||
"modules/companies/src/web/workspace/shared/helpers/workspace-routes.ts",
|
||||
"modules/companies/src/web/workspace/shared/workspace-routes.test.ts",
|
||||
"modules/resources/src/web/resources/list/types/resources-page-props.ts",
|
||||
"modules/resources/src/web/resources/list/ui/resources-page.tsx",
|
||||
"modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts",
|
||||
"modules/resources/src/web/resources/shared/types/resource-form-props.ts",
|
||||
"modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx",
|
||||
"scripts/network-share-publisher-ui-audit.py",
|
||||
"scripts/visual-audit.py"
|
||||
],
|
||||
"noBackendContractsPersistenceSecurityChangesSince6992": true
|
||||
}
|
||||
24
.artifacts/server-runtime-security/b01-before-build.log
Normal file
24
.artifacts/server-runtime-security/b01-before-build.log
Normal file
@ -0,0 +1,24 @@
|
||||
$ tsup --config tsup.config.ts && pnpm check:build
|
||||
CLI Building entry: src/index.ts
|
||||
CLI Using tsconfig: tsconfig.json
|
||||
CLI tsup v8.5.1
|
||||
CLI Using tsup config: /home/david/dev/metis-desk/apps/server/tsup.config.ts
|
||||
CLI Target: node24
|
||||
CLI Cleaning output folder
|
||||
ESM Build start
|
||||
ESM dist/index.js 1.70 MB
|
||||
ESM dist/index.js.map 3.22 MB
|
||||
ESM ⚡️ Build success in 228ms
|
||||
$ node --check dist/index.js
|
||||
/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
|
||||
[ELIFECYCLE] Command failed with exit code 1.
|
||||
/home/david/dev/metis-desk/apps/server:
|
||||
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @metis/server@0.1.0 build: `tsup --config tsup.config.ts && pnpm check:build`
|
||||
Exit status 1
|
||||
53
.artifacts/server-runtime-security/b02-before-config.log
Normal file
53
.artifacts/server-runtime-security/b02-before-config.log
Normal file
@ -0,0 +1,53 @@
|
||||
✔ el puerto usa un valor local por defecto y acepta los límites válidos (1.748516ms)
|
||||
✔ el arranque rechaza puertos inválidos sin revelar el valor recibido (1.708216ms)
|
||||
✖ proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos (1.780817ms)
|
||||
✖ proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores (0.356204ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 2
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 257.934663
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at apps/server/src/config/env-schema.test.ts:1:650
|
||||
✖ proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos (1.780817ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
|
||||
undefined !== []
|
||||
|
||||
at TestContext.<anonymous> (/home/david/dev/metis-desk/apps/server/src/config/env-schema.test.ts:20:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:227:14)
|
||||
at Test.run (node:internal/test_runner/test:1397:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:969:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1537:19)
|
||||
at Test.run (node:internal/test_runner/test:1462:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at apps/server/src/config/env-schema.test.ts:1:1092
|
||||
✖ proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores (0.356204ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at TestContext.<anonymous> (/home/david/dev/metis-desk/apps/server/src/config/env-schema.test.ts:36:12)
|
||||
at Test.runInAsyncScope (node:async_hooks:227:14)
|
||||
at Test.run (node:internal/test_runner/test:1397:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:969:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1537:19)
|
||||
at Test.run (node:internal/test_runner/test:1462:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: [Function (anonymous)],
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
10
.artifacts/server-runtime-security/b02-negative-control.json
Normal file
10
.artifacts/server-runtime-security/b02-negative-control.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"control": "real TLS with original default trust policy",
|
||||
"clientProtocol": "https",
|
||||
"backendProtocol": "http",
|
||||
"forwardedProto": "https",
|
||||
"trustProxy": false,
|
||||
"secure": false,
|
||||
"protocol": "http",
|
||||
"cookies": []
|
||||
}
|
||||
4
.artifacts/server-runtime-security/build-first.json
Normal file
4
.artifacts/server-runtime-security/build-first.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"apps/server/dist/index.js": "acad66829e3736c37d150b26d2da763719abaac36b23bc11d70acdc93370ee5b",
|
||||
"apps/server/dist/index.js.map": "0565dd70b8e7f03f2a65d6a8cce45174824ad398d73cffa5af3852305b6e6e59"
|
||||
}
|
||||
4
.artifacts/server-runtime-security/build-second.json
Normal file
4
.artifacts/server-runtime-security/build-second.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"apps/server/dist/index.js": "acad66829e3736c37d150b26d2da763719abaac36b23bc11d70acdc93370ee5b",
|
||||
"apps/server/dist/index.js.map": "0565dd70b8e7f03f2a65d6a8cce45174824ad398d73cffa5af3852305b6e6e59"
|
||||
}
|
||||
10
.artifacts/server-runtime-security/build.log
Normal file
10
.artifacts/server-runtime-security/build.log
Normal file
@ -0,0 +1,10 @@
|
||||
CLI Building entry: src/index.ts
|
||||
CLI Using tsconfig: tsconfig.json
|
||||
CLI tsup v8.5.1
|
||||
CLI Using tsup config: /home/david/dev/metis-desk/apps/server/tsup.config.ts
|
||||
CLI Target: node24
|
||||
CLI Cleaning output folder
|
||||
ESM Build start
|
||||
ESM dist/index.js 1.45 MB
|
||||
ESM dist/index.js.map 2.70 MB
|
||||
ESM ⚡️ Build success in 186ms
|
||||
3
.artifacts/server-runtime-security/commands.json
Normal file
3
.artifacts/server-runtime-security/commands.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"integration": 0
|
||||
}
|
||||
29
.artifacts/server-runtime-security/http-tls.log
Normal file
29
.artifacts/server-runtime-security/http-tls.log
Normal file
@ -0,0 +1,29 @@
|
||||
$ node --import tsx --test src/app.test.ts src/infrastructure.http.test.ts src/config/env-schema.test.ts src/infrastructure/http/http-error-diagnostic.test.ts src/infrastructure/http/relationship-read-access.test.ts src/proxy-sessions.http.test.ts
|
||||
✔ HTTP real: estado, contrato, errores y trazas sin datos sensibles (60.82286ms)
|
||||
✔ HTTP real: entrada JSON inválida y errores internos conservan un contrato seguro (36.786978ms)
|
||||
✔ el puerto usa un valor local por defecto y acepta los límites válidos (1.867214ms)
|
||||
✔ el arranque rechaza puertos inválidos sin revelar el valor recibido (2.324917ms)
|
||||
✔ proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos (5.117639ms)
|
||||
✔ proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores (2.403819ms)
|
||||
✔ HTTP infraestructura: sesión, pertenencia, validación, identidad e idempotencia (106.375905ms)
|
||||
✔ diagnóstico: causas de Sequelize, ciclos y valores lanzados que no son Error (4.778836ms)
|
||||
✔ diagnóstico: mensajes multilínea, propiedades y cadenas largas no se vuelcan (1.587312ms)
|
||||
✔ contextos de servidor: sesión vigente, ámbito existente, copias y revocación (3.726628ms)
|
||||
▶ TLS HTTP: política explícita, proxy real y cookies sin cabeceras falsificadas
|
||||
✔ TLS-01/04: HTTPS real por proxy confiable emite Secure/HttpOnly/SameSite/Path/Expires (20.408053ms)
|
||||
✔ TLS-02 HTTP directo (5.076938ms)
|
||||
✔ TLS-03 XFP falsificado (2.512019ms)
|
||||
✔ TLS-05: proxy TLS no confiable no eleva seguridad (7.358656ms)
|
||||
✔ TLS-06: proxy confiable informa HTTP y no emite cookie de producción (2.72192ms)
|
||||
✔ TLS-07: cabeceras duplicadas y contradictorias se rechazan antes de sesión (9.023768ms)
|
||||
✔ TLS-08: el proxy sobrescribe la cadena inyectada; no hay confianza por número de saltos (8.888367ms)
|
||||
✔ TLS-12: emisión posterior conserva atributos Secure (11.359385ms)
|
||||
✔ TLS HTTP: política explícita, proxy real y cookies sin cabeceras falsificadas (330.208494ms)
|
||||
ℹ tests 19
|
||||
ℹ suites 0
|
||||
ℹ pass 19
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 865.234945
|
||||
@ -0,0 +1 @@
|
||||
$ tsc -p tsconfig.json
|
||||
17
.artifacts/server-runtime-security/integration.log
Normal file
17
.artifacts/server-runtime-security/integration.log
Normal file
@ -0,0 +1,17 @@
|
||||
▶ B-01/B-02: bundle real, Identity SQL, TLS, imágenes y cierre ordenado
|
||||
✔ TLS-11: producción sin proxies explícitos rechaza el arranque (359.376537ms)
|
||||
✔ B-01: proceso compilado anuncia arranque y sirve /api/__health (20.308925ms)
|
||||
✔ TLS-01/04: bootstrap/login real y cookies Secure con sesión SQL (340.11469ms)
|
||||
✔ Identity: CSRF y origen siguen bloqueando escrituras (20.299625ms)
|
||||
✔ B-01: sharp procesa logo y foto a través del servidor compilado (301.553852ms)
|
||||
✔ TLS-09/12: logout invalida SQL, conserva atributos y login regenera sesión (328.198308ms)
|
||||
✔ TLS-10: desarrollo HTTP emite cookie HttpOnly/Strict sin Secure y sin proxy (944.773171ms)
|
||||
✔ B-01/B-02: bundle real, Identity SQL, TLS, imágenes y cierre ordenado (14187.656387ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 15543.335881
|
||||
234
.artifacts/server-runtime-security/manifest.json
Normal file
234
.artifacts/server-runtime-security/manifest.json
Normal file
@ -0,0 +1,234 @@
|
||||
{
|
||||
"date": "2026-10-26",
|
||||
"baseHead": "a72d6a2ec8985e1d51f74db68a1b3aabb3693030",
|
||||
"branch": "main",
|
||||
"node": "24.20.0",
|
||||
"pnpm": "11.25.0",
|
||||
"tsup": "8.5.1",
|
||||
"format": "esm",
|
||||
"target": "node24",
|
||||
"snapshot": "HEAD más diff correctivo local sin commit, con trabajo concurrente preservado",
|
||||
"status": {
|
||||
"B-01": "RESUELTO",
|
||||
"B-02": "RESUELTO EN ENTORNO REPRESENTATIVO",
|
||||
"production": "PENDIENTE",
|
||||
"operatingSystems": "GO previo preservado",
|
||||
"global": "NO PREPARADO"
|
||||
},
|
||||
"sourceHashes": {
|
||||
"apps/server/package.json": "68ac4a44efe88a1737d3146d9a653c9a7fdc0fac7553aa7156558f8f678f2027",
|
||||
"apps/server/tsup.config.ts": "3d5c599e946a3294c4d9d5c1720b53b76405f97b81cc72f79b03baffa708b251",
|
||||
"apps/server/src/app.ts": "18ada3f488f41d4b6c483ec35f76d30f7457b470cb49e4a37257e5989878cf20",
|
||||
"apps/server/src/index.ts": "127a0f0280b6b0aad84467ec34654505961b215c43022db84ce2c06d9a9064eb",
|
||||
"apps/server/src/config/env-schema.ts": "4d21ffb8f574817dff78cbe5ef8f493951d862e0efd240e9dc28aa5b084fdc9e",
|
||||
"apps/server/src/config/env-schema.test.ts": "0d821805df34b26d312f88ca56dbd8023803fe4463103eacb61da034febf0e00",
|
||||
"apps/server/src/config/trusted-proxy-cidrs.ts": "a76d80340c8b6e6fc9090383baae946f93df5708d977f95ce22bb67cee351223",
|
||||
"apps/server/src/local-tls-proxy.fixture.ts": "c765ae682c2b3f02c8bd5e2211c6c37079767c16de7b0309c374edd1a4c9989f",
|
||||
"apps/server/src/proxy-sessions.http.test.ts": "7343294ce96cb64b17a096a9ccb55570527e9a6b4f79511813cffec4f38b2482",
|
||||
"apps/server/src/server-runtime.integration.test.ts": "2edb2fd776267b13f23e57a4c820306aba0bb168014360b83cdd3b9be3465e9c",
|
||||
"modules/identity/src/api/infrastructure/http/identity.routes.ts": "6f85c6ca442883a2e1bbc29d661260bdccc7709e9d3d729161673dd2648e5927",
|
||||
"scripts/resource-scope-test.py": "0b38635e55c2340a30533cfbf3337b7f91fd846782d35e17a9bdf784d22c388b",
|
||||
"package.json": "3d8ed22ffc11450ec62b1a825082cafa1b4c6f053c3028e653658e48bed554f9",
|
||||
"pnpm-lock.yaml": "500fa7fb9035f26ef29b6f7db96762039d0e23b6311bde2f276eb7906f47d141",
|
||||
"docs/proxy-tls-y-sesiones.md": "4b7472d2c5ff7a6a47e93da9153f43d57e0d72d35426f0c886bf0cb2d6caa38a",
|
||||
"docs/puesta-en-marcha.md": "f83f0020115d1dff691abad05351cce1af6bb59a91394e344e966feaa6b3ff36",
|
||||
"docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md": "d209c7474fa17b58e6b65d2b349ece1793a1117576f0a6e96e47d690e1d8866e"
|
||||
},
|
||||
"buildHashes": {
|
||||
"apps/server/dist/index.js": "acad66829e3736c37d150b26d2da763719abaac36b23bc11d70acdc93370ee5b",
|
||||
"apps/server/dist/index.js.map": "0565dd70b8e7f03f2a65d6a8cce45174824ad398d73cffa5af3852305b6e6e59"
|
||||
},
|
||||
"repeatedBuildIdentical": true,
|
||||
"commands": [
|
||||
{
|
||||
"command": "pnpm --filter @metis/server build (guard antes de corregir)",
|
||||
"exit": 1,
|
||||
"log": "b01-before-build.log"
|
||||
},
|
||||
{
|
||||
"command": "config/env-schema.test.ts antes de corregir",
|
||||
"exit": 1,
|
||||
"log": "b02-before-config.log"
|
||||
},
|
||||
{
|
||||
"command": "control negativo TLS real con trust proxy=false, sin BD",
|
||||
"exit": 0,
|
||||
"log": "b02-negative-control.json"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/server build (limpio y repetido, incluye node --check dist/index.js)",
|
||||
"exit": 0,
|
||||
"log": "build.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/server test:http",
|
||||
"exit": 0,
|
||||
"pass": 19,
|
||||
"log": "http-tls.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm test:server-runtime",
|
||||
"exit": 0,
|
||||
"pass": 8,
|
||||
"log": "integration.log"
|
||||
},
|
||||
{
|
||||
"command": "node --import tsx --test modules/companies/src/api/infrastructure/company-logo-processor.test.ts",
|
||||
"exit": 0,
|
||||
"pass": 4,
|
||||
"log": "sharp.log"
|
||||
},
|
||||
{
|
||||
"command": "python3 scripts/resource-scope-test.py --operating-systems-closure --regressions",
|
||||
"exit": 0,
|
||||
"pass": {
|
||||
"closure": 29,
|
||||
"regressions": 52
|
||||
},
|
||||
"logs": ["so-closure.log", "so-regressions.log"]
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/server typecheck",
|
||||
"exit": 0,
|
||||
"log": "server-typecheck.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm --filter @metis/identity typecheck",
|
||||
"exit": 0,
|
||||
"log": "identity-typecheck.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm exec biome check (12 archivos propios)",
|
||||
"exit": 0,
|
||||
"log": "own-biome.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm exec eslint (10 archivos TS propios)",
|
||||
"exit": 0,
|
||||
"log": "own-eslint.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm build",
|
||||
"exit": 0,
|
||||
"log": "root-build.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm typecheck",
|
||||
"exit": 2,
|
||||
"log": "root-typecheck.log",
|
||||
"failure": "TS2375 preexistente en packages/shadcn-ui/src/components/calendar.tsx:165"
|
||||
},
|
||||
{
|
||||
"command": "pnpm lint",
|
||||
"exit": 1,
|
||||
"errors": 15,
|
||||
"warnings": 1,
|
||||
"infos": 5,
|
||||
"log": "root-lint.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm check",
|
||||
"exit": 1,
|
||||
"errors": 173,
|
||||
"warnings": 1,
|
||||
"infos": 5,
|
||||
"log": "root-check.log"
|
||||
},
|
||||
{
|
||||
"command": "pnpm exec turbo run test --filter='!@metis/server'",
|
||||
"exit": 1,
|
||||
"successfulTasks": 7,
|
||||
"failedTasks": 1,
|
||||
"log": "packages-test.log",
|
||||
"failure": "Companies: 310 correctas y 2 aserciones de presentación fallidas ya observadas; Identity 11/11, Resources 125/125, Assets 154/154"
|
||||
}
|
||||
],
|
||||
"fixtures": {
|
||||
"runtime": {
|
||||
"companies": 1,
|
||||
"people": 1
|
||||
},
|
||||
"closure": {
|
||||
"companies": 15,
|
||||
"principal": {
|
||||
"people": 50,
|
||||
"assets": 50,
|
||||
"workstations": 50,
|
||||
"profiles": 50,
|
||||
"resourcesAtLeast": 53
|
||||
}
|
||||
},
|
||||
"database": "metis_desk_test",
|
||||
"disposable": true
|
||||
},
|
||||
"topology": "Un proxy TLS local; no certifica cadena productiva de varios saltos",
|
||||
"productionTouched": false,
|
||||
"secretsArchived": false,
|
||||
"skill": "rdx-solid",
|
||||
"playwrightRun": false,
|
||||
"gitStatusAtClose": [
|
||||
" M .artifacts/resources-global-operating-systems-closure/integration.log",
|
||||
" M .artifacts/resources-global-operating-systems-closure/legacy-evolution.json",
|
||||
" M .artifacts/resources-global-operating-systems-closure/regressions.log",
|
||||
" M apps/server/package.json",
|
||||
" M apps/server/src/app.ts",
|
||||
" M apps/server/src/config/env-schema.test.ts",
|
||||
" M apps/server/src/config/env-schema.ts",
|
||||
" M apps/server/src/index.ts",
|
||||
" M apps/server/tsup.config.ts",
|
||||
" M docs/architecture/preflight-activacion-catalogo-global-sistemas-operativos.md",
|
||||
" M docs/componentes-de-interfaz.md",
|
||||
" M docs/puesta-en-marcha.md",
|
||||
" M modules/assets/src/common/locales/en.json",
|
||||
" M modules/assets/src/common/locales/es.json",
|
||||
" M modules/assets/src/web/index.ts",
|
||||
" M modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx",
|
||||
" M modules/assets/src/web/network-share-publications/shared/network-share-ui.test.tsx",
|
||||
" M modules/companies/src/common/locales/en.json",
|
||||
" M modules/companies/src/common/locales/es.json",
|
||||
" M modules/companies/src/web/access/list/ui/access-workspace.tsx",
|
||||
" M modules/companies/src/web/resources/list/ui/company-resources.tsx",
|
||||
" M modules/identity/src/api/infrastructure/http/identity.routes.ts",
|
||||
" M modules/resources/src/common/locales/en.json",
|
||||
" M modules/resources/src/common/locales/es.json",
|
||||
" M modules/resources/src/web/index.ts",
|
||||
" M modules/resources/src/web/resources/detail/ui/network-share-address.tsx",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-detail.tsx",
|
||||
" M modules/resources/src/web/resources/detail/ui/resource-selection-panel.tsx",
|
||||
" M modules/resources/src/web/resources/list/types/resources-page-props.ts",
|
||||
" M modules/resources/src/web/resources/list/ui/resources-page.tsx",
|
||||
" M modules/resources/src/web/resources/shared/resources.test.tsx",
|
||||
" M package.json",
|
||||
" M packages/rdx-ui/src/components/detail-panel.tsx",
|
||||
" M packages/rdx-ui/src/components/form-dialog.tsx",
|
||||
" M packages/rdx-ui/src/lib/icons.ts",
|
||||
" M pnpm-lock.yaml",
|
||||
" M scripts/resource-scope-test.py",
|
||||
" M scripts/visual-audit.py",
|
||||
"?? .artifacts/auditoria-so-estado-actual/",
|
||||
"?? .artifacts/resources-global-operating-systems-preflight-a72d6a2e/",
|
||||
"?? .artifacts/server-runtime-security/",
|
||||
"?? apps/server/src/config/trusted-proxy-cidrs.ts",
|
||||
"?? apps/server/src/local-tls-proxy.fixture.ts",
|
||||
"?? apps/server/src/proxy-sessions.http.test.ts",
|
||||
"?? apps/server/src/server-runtime.integration.test.ts",
|
||||
"?? docs/architecture/auditoria-estado-actual-sistemas-operativos-activos-y-puestos.md",
|
||||
"?? docs/ficha-de-carpeta-compartida.md",
|
||||
"?? docs/proxy-tls-y-sesiones.md",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/controllers/",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/helpers/",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/network-share-resource-detail.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/publication-event-details.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/publication-history.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/published-share-asset.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/resource-publication-card.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/detail/ui/unpublished-share.tsx",
|
||||
"?? modules/assets/src/web/network-share-publications/manage/ui/publication-operation-summary.tsx",
|
||||
"?? modules/companies/src/web/resources/detail/ui/company-network-share-detail.tsx",
|
||||
"?? modules/resources/src/web/resources/detail/ui/network-share-resource-title.tsx",
|
||||
"?? modules/resources/src/web/resources/detail/ui/resource-detail-actions.tsx",
|
||||
"?? modules/resources/src/web/resources/detail/ui/resource-revision-history.tsx",
|
||||
"?? scripts/network-share-detail-ui-audit.py"
|
||||
]
|
||||
}
|
||||
1
.artifacts/server-runtime-security/own-biome.log
Normal file
1
.artifacts/server-runtime-security/own-biome.log
Normal file
@ -0,0 +1 @@
|
||||
Checked 12 files in 14ms. No fixes applied.
|
||||
0
.artifacts/server-runtime-security/own-eslint.log
Normal file
0
.artifacts/server-runtime-security/own-eslint.log
Normal file
846
.artifacts/server-runtime-security/packages-test.log
Normal file
846
.artifacts/server-runtime-security/packages-test.log
Normal file
@ -0,0 +1,846 @@
|
||||
• turbo 2.9.14
|
||||
|
||||
• Packages in scope: @metis/assets, @metis/companies, @metis/core, @metis/identity, @metis/relationship-contracts, @metis/resources, @metis/web, @repo/rdx-ddd, @repo/rdx-ui, @repo/rdx-utils, @repo/shadcn-ui, @repo/typescript-config
|
||||
• Running test in 12 packages
|
||||
• Remote caching disabled
|
||||
|
||||
@metis/core:test: cache bypass, force executing 2283a7af50b4c3f8
|
||||
@repo/rdx-ddd:test: cache bypass, force executing b722cdfdd985a9bd
|
||||
@metis/relationship-contracts:test: cache bypass, force executing 97138537b462262f
|
||||
@repo/rdx-ui:test: cache bypass, force executing 08ae8a00e21c47c8
|
||||
@repo/rdx-ui:test: $ TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/select-with-action.test.tsx src/selection-components.test.tsx src/entity-list.test.tsx src/compact-collections.test.tsx src/entity-column-visibility.test.tsx src/time-line.test.tsx src/count-badge.test.tsx src/scrollable-tabs.test.tsx src/field-sections.test.tsx src/phone-link.test.tsx src/lib/reorder-items.test.ts src/lib/text-search.test.ts src/remote-option-list.test.tsx src/dialog-help.test.tsx
|
||||
@metis/core:test: $ node --import tsx --test src/common/dto/response/get-health.response.dto.test.ts src/web/navigation/shared/browser-navigation.test.ts src/web/http/shared/api/csrf-fetch.test.ts
|
||||
@repo/rdx-ddd:test: $ node --import tsx --test src/domain-model.test.ts
|
||||
@metis/relationship-contracts:test: $ node --import tsx --test --test-isolation=none src/*.test.ts
|
||||
@metis/core:test: ✔ el contrato de estado exige un timestamp UTC y un estado conocido (3.491333ms)
|
||||
@metis/core:test: ✔ el endpoint de estado no acepta parámetros de consulta (0.467504ms)
|
||||
@repo/rdx-ddd:test: ✔ UniqueID genera UUID v7 distintos, conserva UUID existentes y rechaza identificadores inválidos (16.529152ms)
|
||||
@repo/rdx-ddd:test: ✔ los Value Objects validan y comparan valores semánticos por tipo (1.795317ms)
|
||||
@repo/rdx-ddd:test: ✔ entidades y agregados comparan identidad y tipo, independientemente de sus atributos (0.901109ms)
|
||||
@repo/rdx-ddd:test: ℹ tests 3
|
||||
@repo/rdx-ddd:test: ℹ suites 0
|
||||
@repo/rdx-ddd:test: ℹ pass 3
|
||||
@repo/rdx-ddd:test: ℹ fail 0
|
||||
@repo/rdx-ddd:test: ℹ cancelled 0
|
||||
@repo/rdx-ddd:test: ℹ skipped 0
|
||||
@repo/rdx-ddd:test: ℹ todo 0
|
||||
@repo/rdx-ddd:test: ℹ duration_ms 686.598555
|
||||
@metis/core:test: ✔ las escrituras leen la sesión actual de Query y siguen su rotación y eliminación (35.987833ms)
|
||||
@metis/core:test: ✔ GET, HEAD y OPTIONS funcionan sin sesión y no envían tokens suministrados por el llamador (1.830917ms)
|
||||
@metis/core:test: ✔ mantiene cuerpos JSON, binarios, headers, cancelación y respuestas de error sin reintentar (5.538151ms)
|
||||
@metis/core:test: ✔ rechaza tokens ausentes o inválidos y destinos externos antes de enviar una petición (0.795407ms)
|
||||
@metis/relationship-contracts:test: ✔ referencias UUID estables, normalizadas, inmutables y con proveedor coherente (5.848454ms)
|
||||
@metis/relationship-contracts:test: ✔ namespaces canónicos, sin identificadores de tabla, espacios ni segmentos vacíos (0.601005ms)
|
||||
@metis/relationship-contracts:test: ✔ el catálogo valida propietarios, namespaces, duplicados y definiciones (6.202157ms)
|
||||
@metis/relationship-contracts:test: ✔ el catálogo crea una copia profundamente inmutable sin congelar la entrada (0.655106ms)
|
||||
@metis/relationship-contracts:test: ✔ relaciones dirigidas, simétricas y derivadas respetan su semántica declarativa (0.630506ms)
|
||||
@metis/relationship-contracts:test: ✔ consulta estricta: límites, cursores y ausencia de privilegios del navegador (4.238939ms)
|
||||
@metis/relationship-contracts:test: ✔ validación de ámbito, tipo y sentido de consulta sin conceder autorización (1.849017ms)
|
||||
@metis/relationship-contracts:test: ✔ orígenes y páginas conservan referencias sin mezclar compañías (2.407522ms)
|
||||
@metis/relationship-contracts:test: ✔ servicios de prueba por lotes y errores compatibles con Result de Metis (1.398613ms)
|
||||
@metis/relationship-contracts:test: ✔ el grafo de dependencias del paquete es acíclico y ajeno a módulos funcionales (46.056524ms)
|
||||
@metis/relationship-contracts:test: ✔ las importaciones internas de producción no contienen ciclos ni imports privados (2.882526ms)
|
||||
@metis/relationship-contracts:test: ✔ capacidades semánticas explícitas sin registrar proveedores ficticios (2.20472ms)
|
||||
@metis/relationship-contracts:test: ✔ rol semántico opcional conserva la compatibilidad de vistas comunes sin metadatos libres (2.559123ms)
|
||||
@metis/relationship-contracts:test: ℹ tests 13
|
||||
@metis/relationship-contracts:test: ℹ suites 0
|
||||
@metis/relationship-contracts:test: ℹ pass 13
|
||||
@metis/relationship-contracts:test: ℹ fail 0
|
||||
@metis/relationship-contracts:test: ℹ cancelled 0
|
||||
@metis/relationship-contracts:test: ℹ skipped 0
|
||||
@metis/relationship-contracts:test: ℹ todo 0
|
||||
@metis/relationship-contracts:test: ℹ duration_ms 758.843724
|
||||
@metis/core:test: ✔ estado de ficha: tablas independientes, aislamiento entre quince propietarios y metadatos ajenos conservados (11.979711ms)
|
||||
@metis/core:test: ✔ estado de ficha: metadatos malformados, ámbitos externos y valores incompatibles no se restauran (3.683334ms)
|
||||
@metis/core:test: ✔ History mantiene URL, filtros, Atrás/Adelante, cierre y suscripciones sin entradas duplicadas (1.708015ms)
|
||||
@metis/core:test: ✔ las redirecciones internas rechazan destinos externos, barras invertidas y caracteres de control (0.353103ms)
|
||||
@metis/core:test: ✔ el retorno reutiliza la entrada exacta del listado y conserva el estado ajeno (0.672406ms)
|
||||
@metis/core:test: ✔ el contexto de retorno valida versión, coordenadas, ámbito y filtros sin guardar registros (5.683653ms)
|
||||
@metis/core:test: ✔ las consultas Unicode válidas no pierden contexto al superar 2000 caracteres codificados (4.601642ms)
|
||||
@metis/core:test: ✔ retorno de detalle y listado conviven sin aceptar el ámbito de otro propietario (1.791716ms)
|
||||
@metis/core:test: ℹ tests 14
|
||||
@metis/core:test: ℹ suites 0
|
||||
@metis/core:test: ℹ pass 14
|
||||
@metis/core:test: ℹ fail 0
|
||||
@metis/core:test: ℹ cancelled 0
|
||||
@metis/core:test: ℹ skipped 0
|
||||
@metis/core:test: ℹ todo 0
|
||||
@metis/core:test: ℹ duration_ms 1011.240179
|
||||
@repo/rdx-ui:test: ✔ la tabla compacta conserva semántica y enlaces sin herramientas de exploración (20.120785ms)
|
||||
@repo/rdx-ui:test: ✔ los valores ausentes comparten criterio con EntityTable y cero permanece visible (1.09631ms)
|
||||
@repo/rdx-ui:test: ✔ carga y error sin datos no afirman vacío; caché obsoleta conserva registros (8.211876ms)
|
||||
@repo/rdx-ui:test: ✔ la ampliación identifica el contenido controlado sin añadir controles a colecciones pequeñas (4.124038ms)
|
||||
@repo/rdx-ui:test: ✔ EntityTable mantiene búsqueda, columnas y paginación con su contrato anterior (30.357779ms)
|
||||
@repo/rdx-ui:test: ✔ preview controlado: recuperar la ampliación muestra el origen con identidad estable sin perder el modo local (6.643461ms)
|
||||
@repo/rdx-ui:test: ✔ contador: conserva cero y formatea el total por idioma (33.088805ms)
|
||||
@repo/rdx-ui:test: ✔ contador: carga y error muestran ausencia de total, conservando una cifra anterior si existe (2.578524ms)
|
||||
@repo/rdx-ui:test: ✔ la ayuda breve respeta la capacidad efectiva, también con fuentes ampliadas (1.03721ms)
|
||||
@repo/rdx-ui:test: ✔ auto y expanded reservan 40rem al formulario y 20rem al panel (0.281502ms)
|
||||
@repo/rdx-ui:test: ✔ el contenido inline conserva descripción, ámbito, ejemplos, orientación e icono (11.139203ms)
|
||||
@repo/rdx-ui:test: ✔ las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después (63.243081ms)
|
||||
@repo/rdx-ui:test: ✔ las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia (11.309504ms)
|
||||
@repo/rdx-ui:test: ✔ datos locales inválidos no rompen el listado y solo se aceptan valores booleanos (0.571606ms)
|
||||
@repo/rdx-ui:test: ✔ almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local (0.428904ms)
|
||||
@repo/rdx-ui:test: ✔ buscar columnas ignora tildes y mayúsculas, conserva ñ y no modifica visibilidad (1.006309ms)
|
||||
@repo/rdx-ui:test: ✔ los grupos conservan orden, omiten vacíos y deduplican IDs sin perder columnas sin grupo (0.458504ms)
|
||||
@repo/rdx-ui:test: ✔ restaurar preferencias vacías recupera defaults actuales sin tocar otras tablas (7.992174ms)
|
||||
@repo/rdx-ui:test: ✔ los contadores distinguen visibilidad, total y coincidencias en es/en (0.171702ms)
|
||||
@repo/rdx-ui:test: ✔ listado remoto: recupera columnas por ámbito y protege nombre y acciones (14.616634ms)
|
||||
@repo/rdx-ui:test: ✔ los rangos vacíos y la última página nunca muestran índices fuera del conjunto (2.15052ms)
|
||||
@repo/rdx-ui:test: ✔ el listado pagina el conjunto y repite su rango conservando controles accesibles (50.379262ms)
|
||||
@repo/rdx-ui:test: ✔ la búsqueda local reduce el conjunto completo antes de paginar y permite limpiarla (9.939791ms)
|
||||
@repo/rdx-ui:test: ✔ el texto formado solo por espacios se puede borrar sin confundir vacío con ausencia de coincidencias (7.182766ms)
|
||||
@repo/rdx-ui:test: ✔ la búsqueda genérica ignora acentos y Unicode equivalente, manteniendo ñ y el texto escrito (34.009913ms)
|
||||
@repo/rdx-ui:test: ✔ el orden inicial ascendente se indica en la cabecera y se aplica antes de paginar (6.816263ms)
|
||||
@repo/rdx-ui:test: ✔ una columna explícita puede establecer un orden inicial descendente (8.266176ms)
|
||||
@repo/rdx-ui:test: ✔ el orden inicial ignora acciones y columnas sin valor comparable (7.65397ms)
|
||||
@repo/rdx-ui:test: ✔ las tablas sin columnas ordenables conservan el orden recibido (7.172666ms)
|
||||
@repo/rdx-ui:test: ✔ el orden inicial mantiene la estabilidad en empates y se indica con el listado vacío (9.74699ms)
|
||||
@repo/rdx-ui:test: ✔ una búsqueda controlada conserva los resultados filtrados por la pantalla (5.054447ms)
|
||||
@repo/rdx-ui:test: ✔ carga, error y búsqueda vacía se distinguen de un listado sin registros (27.20225ms)
|
||||
@repo/rdx-ui:test: ✔ el rango y la configuración también se traducen en inglés (6.992464ms)
|
||||
@repo/rdx-ui:test: ✔ acciones: disparador de solo icono con etiqueta accesible y menú contextual (5.755253ms)
|
||||
@repo/rdx-ui:test: ✔ acciones: operación principal visible con nombre accesible y secundarias en el menú (5.48605ms)
|
||||
@repo/rdx-ui:test: ✔ acciones: conserva la restricción de la principal y evita menús sin secundarias (3.623233ms)
|
||||
@repo/rdx-ui:test: ✔ el estado activo se distingue en verde del inactivo (0.570005ms)
|
||||
@repo/rdx-ui:test: ✔ acciones: siempre última, compacta y anclada a la derecha, aunque se declare primero (13.153421ms)
|
||||
@repo/rdx-ui:test: ✔ los textos comunes mantienen claves, idiomas y parámetros del rango (0.232602ms)
|
||||
@repo/rdx-ui:test: ✔ las celdas vacías muestran un guion largo en es/en y conservan cero, textos y controles (115.602762ms)
|
||||
@repo/rdx-ui:test: ✔ las columnas opcionales empiezan ocultas sin ocultar identificación ni acciones obligatorias (4.664943ms)
|
||||
@repo/rdx-ui:test: ✔ Nombre ordena sin tildes ni mayúsculas antes de paginar, en español e inglés (14.421132ms)
|
||||
@repo/rdx-ui:test: ✔ Nombre tiene prioridad inicial aunque otra columna comparable aparezca antes (5.855854ms)
|
||||
@repo/rdx-ui:test: ✔ Nombre conserva empates entre acentos, mayúsculas y Unicode compuesto al ordenar descendente (4.940946ms)
|
||||
@repo/rdx-ui:test: ✔ paginación por cursor: navegación accesible y límites sin inventar totales (0.631306ms)
|
||||
@repo/rdx-ui:test: ✔ estados presentacionales: carga, datos anteriores y recuperación son mutuamente distinguibles (0.291403ms)
|
||||
@repo/rdx-ui:test: ✔ carga inicial conserva estructura, no anuncia cero registros y no fabrica acciones (5.837154ms)
|
||||
@repo/rdx-ui:test: ✔ refetch y error posterior muestran los datos disponibles sin skeletons (8.489378ms)
|
||||
@repo/rdx-ui:test: ✔ error inicial, vacío válido y filtros sin coincidencias tienen contenidos distintos en es/en (15.19764ms)
|
||||
@repo/rdx-ui:test: ✔ el contrato de navegación rechaza páginas mal formadas y ordenaciones desconocidas (0.660006ms)
|
||||
@repo/rdx-ui:test: ✔ navegación controlada ordena y pagina realmente sin alterar el consumidor no controlado (4.539042ms)
|
||||
@repo/rdx-ui:test: ✔ quitar sección conserva todos los campos y su obligatoriedad sin mutar el origen (1.791416ms)
|
||||
@repo/rdx-ui:test: ✔ copiar remapea secciones conservando campos y reglas con identidades independientes (0.452605ms)
|
||||
@repo/rdx-ui:test: ✔ secciones visuales ordenadas, abiertas inicialmente y campos sin sección (9.347286ms)
|
||||
@repo/rdx-ui:test: ✔ consulta compacta conserva grupos, campos sin sección y avisos sin tarjetas anidadas (3.627333ms)
|
||||
@repo/rdx-ui:test: ✔ pestañas de grupos conservan controles ocultos, orden y campos sin grupo, omitiendo grupos vacíos (13.122021ms)
|
||||
@repo/rdx-ui:test: ✔ el editor ofrece asas etiquetadas para secciones y campos, deshabilitadas al guardar (77.577413ms)
|
||||
@repo/rdx-ui:test: ✔ soltar antes o después del destino conserva el orden de los demás elementos (1.820617ms)
|
||||
@repo/rdx-ui:test: ✔ destinos inválidos y posiciones sin cambio no producen una nueva ordenación (4.465141ms)
|
||||
@repo/rdx-ui:test: ✔ reordenar mantiene identidades, secciones y obligatoriedad sin modificar los objetos (0.235202ms)
|
||||
@repo/rdx-ui:test: ✔ la comparación parcial pliega vocales, caja y representaciones Unicode equivalentes (5.34385ms)
|
||||
@repo/rdx-ui:test: ✔ ñ conserva identidad compuesta y descompuesta y otras escrituras no se transliteran (0.441004ms)
|
||||
@repo/rdx-ui:test: ✔ puntuación técnica, números y espacios interiores siguen siendo significativos (3.929337ms)
|
||||
@repo/rdx-ui:test: ✔ vacíos y valores sin texto no serializan objetos arbitrarios (0.402304ms)
|
||||
@repo/rdx-ui:test: ✔ formatea números españoles y conserva extensiones para visualización y llamada (1.384012ms)
|
||||
@repo/rdx-ui:test: ✔ preserva números internacionales no españoles sin inventar agrupaciones (0.332403ms)
|
||||
@repo/rdx-ui:test: ✔ el enlace telefónico usa el número formateado y mantiene un nombre accesible (8.427978ms)
|
||||
@repo/rdx-ui:test: ✔ selector remoto: selección compacta al principio, radios etiquetados y orden recibido sin paginación local (49.757158ms)
|
||||
@repo/rdx-ui:test: ✔ selector remoto: vacío, carga y error no se confunden y errores ocultan filas obsoletas (7.105665ms)
|
||||
@repo/rdx-ui:test: ✔ pestañas desplazables: conserva identidad, selección y etiquetas largas sin añadir focos al SSR (26.301442ms)
|
||||
@repo/rdx-ui:test: ✔ el selector agrupa la acción etiquetada sin convertirla en envío de formulario (24.357124ms)
|
||||
@repo/rdx-ui:test: ✔ el selector deshabilitado protege ambas acciones y admite selección sin botón (5.921454ms)
|
||||
@repo/rdx-ui:test: ✔ La capacidad contextual admite presupuestos propios sin acoplarlos al viewport (0.857508ms)
|
||||
@repo/rdx-ui:test: ✔ La pérdida de capacidad cierra sin abrir modal y los eventos repetidos no publican estado (0.618806ms)
|
||||
@repo/rdx-ui:test: ✔ selección visual: vacío con acciones separadas y ficha elegida sin controles de envío (14.594334ms)
|
||||
@repo/rdx-ui:test: ✔ selección visual: pendiente, deshabilitado y bloqueo protegen las acciones correspondientes (5.650852ms)
|
||||
@repo/rdx-ui:test: ✔ PickerList: búsqueda etiquetada, selección única y opciones indisponibles visibles (31.267387ms)
|
||||
@repo/rdx-ui:test: ✔ PickerList: estados de carga y vacío no presentan opciones falsas (16.674153ms)
|
||||
@repo/rdx-ui:test: ✔ el rango del selector usa la entidad y su plural, nunca el título de la acción (50.190861ms)
|
||||
@repo/rdx-ui:test: ✔ acciones multipaso: requisitos bloquean avance y guardado en cada paso (5.022046ms)
|
||||
@repo/rdx-ui:test: ✔ acciones multipaso: guardar pendiente impide avanzar y retroceder (1.978018ms)
|
||||
@repo/rdx-ui:test: ✔ línea temporal: conserva el orden aportado, fechas semánticas, autor y contenido (11.536707ms)
|
||||
@repo/rdx-ui:test: ✔ línea temporal vacía: muestra el mensaje del consumidor como estado (0.589105ms)
|
||||
@repo/rdx-ui:test: ℹ tests 83
|
||||
@repo/rdx-ui:test: ℹ suites 0
|
||||
@repo/rdx-ui:test: ℹ pass 83
|
||||
@repo/rdx-ui:test: ℹ fail 0
|
||||
@repo/rdx-ui:test: ℹ cancelled 0
|
||||
@repo/rdx-ui:test: ℹ skipped 0
|
||||
@repo/rdx-ui:test: ℹ todo 0
|
||||
@repo/rdx-ui:test: ℹ duration_ms 2521.731449
|
||||
@metis/identity:test: cache bypass, force executing 420e86a3143935a0
|
||||
@metis/resources:test: cache bypass, force executing 33e88dc379e8557b
|
||||
@metis/identity:test: $ node --import tsx --test src/api/domain/user.aggregate.test.ts src/api/application/global-resource-authorization.test.ts src/api/infrastructure/persistence/user-persistence.mapper.test.ts src/api/infrastructure/security/scrypt-password-hasher.test.ts src/web/shared/api/identity-api.test.ts
|
||||
@metis/resources:test: $ 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
|
||||
@metis/resources:test: ✔ R2.6: nombre y proveedor son señales, nunca equivalencia confirmada (7.128265ms)
|
||||
@metis/resources:test: ✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.479904ms)
|
||||
@metis/resources:test: ✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (43.117395ms)
|
||||
@metis/identity:test: ✔ resources.global.manage exige sesión vigente, flag persistido e identidad bootstrap (9.83189ms)
|
||||
@metis/identity:test: ✔ el administrador inicial no se puede crear ni actualizar inactivo (7.528144ms)
|
||||
@metis/identity:test: ✔ actualizar es idempotente, conserva identidad y cambiar contraseña invalida sesiones (0.909405ms)
|
||||
@metis/identity:test: ✔ crear genera UUID v7 y rehidratar conserva el UUID original (9.253154ms)
|
||||
@metis/identity:test: ✔ el mapper reconstruye Value Objects y conserva la identidad y valores persistidos (5.292731ms)
|
||||
@metis/identity:test: ✔ una inconsistencia persistida falla sin generar un identificador sustituto ni filtrar datos (8.799152ms)
|
||||
@metis/resources:test: ✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (6.677039ms)
|
||||
@metis/resources:test: ✔ F2: elegibilidad de SO exige stock real y campos globales (0.477303ms)
|
||||
@metis/resources:test: ✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.484803ms)
|
||||
@metis/resources:test: ✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (101.309296ms)
|
||||
@metis/resources:test: ✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (13.422279ms)
|
||||
@metis/resources:test: ✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (2.046512ms)
|
||||
@metis/resources:test: ✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (1.254107ms)
|
||||
@metis/resources:test: ✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (0.848605ms)
|
||||
@metis/resources:test: ✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (2.062912ms)
|
||||
@metis/resources:test: ✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (9.993858ms)
|
||||
@metis/resources:test: ✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (4.091124ms)
|
||||
@metis/resources:test: ✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (5.955935ms)
|
||||
@metis/resources:test: ✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.582803ms)
|
||||
@metis/resources:test: ✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.408102ms)
|
||||
@metis/resources:test: ✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.238302ms)
|
||||
@metis/resources:test: ✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (60.351653ms)
|
||||
@metis/resources:test: ✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (291.560206ms)
|
||||
@metis/resources:test: ✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (3.814022ms)
|
||||
@metis/resources:test: ✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (2.189213ms)
|
||||
@metis/resources:test: ✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (10.542862ms)
|
||||
@metis/resources:test: ✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (9.913658ms)
|
||||
@metis/resources:test: ✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (1.79451ms)
|
||||
@metis/resources:test: ✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (31.564085ms)
|
||||
@metis/resources:test: ✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (1.930511ms)
|
||||
@metis/resources:test: ✔ SMB acepta \\NAS-01\Finanzas (2.852317ms)
|
||||
@metis/resources:test: ✔ SMB acepta \\files.example.local\Docs (0.324302ms)
|
||||
@metis/resources:test: ✔ SMB acepta \\192.168.5.10\C$ (0.195602ms)
|
||||
@metis/resources:test: ✔ SMB acepta \\samba\Diseño\Año 2026 (0.355002ms)
|
||||
@metis/resources:test: ✔ SMB acepta \\NAS\Docs\ (0.333302ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "" (0.245701ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\" (0.304701ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host" (0.143101ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\\\share" (1.291808ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\\\" (0.576403ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "C:\\Datos" (0.186401ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "https://host/share" (0.128301ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\.." (0.389002ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\." (0.334702ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.258802ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\\\child" (0.324602ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share." (0.293701ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\ share" (0.1201ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\ho st\\share" (0.111101ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\u0000" (0.143501ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\ud800" (0.180901ms)
|
||||
@metis/resources:test: ✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.115ms)
|
||||
@metis/resources:test: ✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (0.602804ms)
|
||||
@metis/resources:test: ✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (25.808251ms)
|
||||
@metis/resources:test: ✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (16.481996ms)
|
||||
@metis/resources:test: ✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (13.131176ms)
|
||||
@metis/resources:test: ✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (4.170324ms)
|
||||
@metis/resources:test: ✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.419903ms)
|
||||
@metis/resources:test: ✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (22.598333ms)
|
||||
@metis/resources:test: ✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (15.30999ms)
|
||||
@metis/resources:test: ✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (36.364714ms)
|
||||
@metis/resources:test: ✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (21.113023ms)
|
||||
@metis/resources:test: ✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (30.613679ms)
|
||||
@metis/resources:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (20.056317ms)
|
||||
@metis/resources:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (14.692186ms)
|
||||
@metis/resources:test: ✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (6.536639ms)
|
||||
@metis/resources:test: ✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (7.189642ms)
|
||||
@metis/resources:test: ✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (9.014952ms)
|
||||
@metis/resources:test: ✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (5.288031ms)
|
||||
@metis/resources:test: ✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (12.809475ms)
|
||||
@metis/resources:test: ✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (5.830034ms)
|
||||
@metis/resources:test: ✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (3.693922ms)
|
||||
@metis/resources:test: ✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (22.823134ms)
|
||||
@metis/resources:test: ✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (4.601727ms)
|
||||
@metis/resources:test: ✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.564403ms)
|
||||
@metis/resources:test: ✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (13.834681ms)
|
||||
@metis/resources:test: ✔ recurso: identidad estable, historial, desactivación e idempotencia (3.089518ms)
|
||||
@metis/resources:test: ✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (5.359932ms)
|
||||
@metis/resources:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (7.456744ms)
|
||||
@metis/resources:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (4.973529ms)
|
||||
@metis/resources:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (7.133841ms)
|
||||
@metis/resources:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (20.751821ms)
|
||||
@metis/resources:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (19.033011ms)
|
||||
@metis/resources:test: ✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (18.80371ms)
|
||||
@metis/resources:test: ✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (4.344226ms)
|
||||
@metis/resources:test: ✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (90.595227ms)
|
||||
@metis/resources:test: ✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (1.59121ms)
|
||||
@metis/identity:test: ✔ scrypt usa sales distintas y rechaza contraseñas y hashes incorrectos (1853.180717ms)
|
||||
@metis/identity:test: ✔ la sesión valida la respuesta y conserva el contrato público utilizado por la aplicación (55.411324ms)
|
||||
@metis/identity:test: ✔ login, configuración inicial, usuarios y contraseñas conservan rutas, body y CSRF (8.455749ms)
|
||||
@metis/identity:test: ✔ cerrar sesión espera la respuesta de la operación sin perder el token (2.226413ms)
|
||||
@metis/identity:test: ✔ los errores HTTP no filtran detalles internos y los datos de sesión inválidos se rechazan (4.645028ms)
|
||||
@metis/identity:test: ℹ tests 11
|
||||
@metis/identity:test: ℹ suites 0
|
||||
@metis/identity:test: ℹ pass 11
|
||||
@metis/identity:test: ℹ fail 0
|
||||
@metis/identity:test: ℹ cancelled 0
|
||||
@metis/identity:test: ℹ skipped 0
|
||||
@metis/identity:test: ℹ todo 0
|
||||
@metis/identity:test: ℹ duration_ms 2222.266648
|
||||
@metis/resources:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (5.371231ms)
|
||||
@metis/resources:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.200007ms)
|
||||
@metis/resources:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (1.526909ms)
|
||||
@metis/resources:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (84.925191ms)
|
||||
@metis/resources:test: ✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (108.230225ms)
|
||||
@metis/resources:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (79.818562ms)
|
||||
@metis/resources:test: ✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (28.288164ms)
|
||||
@metis/resources:test: ✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (136.908891ms)
|
||||
@metis/resources:test: ✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (25.080845ms)
|
||||
@metis/resources:test: ✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (2.405114ms)
|
||||
@metis/resources:test: ✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (0.988706ms)
|
||||
@metis/resources:test: ✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (7.207342ms)
|
||||
@metis/resources:test: ✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.065906ms)
|
||||
@metis/resources:test: ✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (18.89631ms)
|
||||
@metis/resources:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (80.404464ms)
|
||||
@metis/resources:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (2.169512ms)
|
||||
@metis/resources:test: ✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (23.036633ms)
|
||||
@metis/resources:test: ✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (12.15557ms)
|
||||
@metis/resources:test: ✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (0.963306ms)
|
||||
@metis/resources:test: ✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.311607ms)
|
||||
@metis/resources:test: ✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.358502ms)
|
||||
@metis/resources:test: ✔ la integración de buzón conserva su configuración explícita y no inventa referencias (1.074906ms)
|
||||
@metis/resources:test: ✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (0.961406ms)
|
||||
@metis/resources:test: ✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (21.688226ms)
|
||||
@metis/resources:test: ✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (0.325801ms)
|
||||
@metis/resources:test: ✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (0.511403ms)
|
||||
@metis/resources:test: ✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (9.011153ms)
|
||||
@metis/resources:test: ✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (90.267919ms)
|
||||
@metis/resources:test: ✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (300.91633ms)
|
||||
@metis/resources:test: ✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (15.70979ms)
|
||||
@metis/resources:test: ✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (16.684696ms)
|
||||
@metis/resources:test: ✔ asistencia: alta de recurso con plantilla específica y características opcionales (6.96304ms)
|
||||
@metis/resources:test: ✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (15.75829ms)
|
||||
@metis/resources:test: ✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (10.915263ms)
|
||||
@metis/resources:test: ✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (15.005886ms)
|
||||
@metis/resources:test: ✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (12.13507ms)
|
||||
@metis/resources:test: ✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (16.093892ms)
|
||||
@metis/resources:test: ✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (9.634255ms)
|
||||
@metis/resources:test: ✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (7.856946ms)
|
||||
@metis/resources:test: ✔ UNC: presentación por segmentos conserva exactamente separadores, Unicode y valores repetidos (4.874728ms)
|
||||
@metis/resources:test: ✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (31.367581ms)
|
||||
@metis/resources:test: ℹ tests 125
|
||||
@metis/resources:test: ℹ suites 0
|
||||
@metis/resources:test: ℹ pass 125
|
||||
@metis/resources:test: ℹ fail 0
|
||||
@metis/resources:test: ℹ cancelled 0
|
||||
@metis/resources:test: ℹ skipped 0
|
||||
@metis/resources:test: ℹ todo 0
|
||||
@metis/resources:test: ℹ duration_ms 4481.717598
|
||||
@metis/assets:test: cache bypass, force executing f05186512575d0f6
|
||||
@metis/assets:test: $ TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/api/domain/asset-catalog.test.ts src/api/domain/asset.test.ts src/api/domain/asset-template-propagation.test.ts src/api/domain/infrastructure.test.ts src/api/domain/workstation.test.ts src/api/domain/asset-relationship.test.ts src/web/catalog/shared/catalog.test.tsx src/web/assets/shared/inventory.test.tsx src/web/assets/shared/helpers/asset-columns.test.tsx src/web/assets/detail/ui/asset-detail.test.tsx src/web/infrastructure/shared/infrastructure.test.tsx src/api/domain/support.test.ts src/web/support-connections/shared/support.test.tsx src/api/domain/brand.test.ts src/web/brands/shared/brands.test.tsx src/api/domain/template-sections.test.ts src/web/workstations/shared/helpers/workstation-creation.test.ts src/web/workstations/shared/workstation-name.test.ts src/web/workstations/rename/api/rename-workstation.test.ts src/web/assets/list/asset-workstation.test.tsx src/web/assets/select/asset-selection.test.tsx src/web/catalog-fields/select/catalog-field-selection.test.tsx src/api/domain/directory-membership.test.ts src/api/infrastructure/persistence/directory-membership-provider.test.ts src/web/directory-memberships/shared/directory-memberships.test.tsx src/api/domain/network-share-publication.test.ts src/web/network-share-publications/shared/network-share-ui.test.tsx src/api/application/workstation-concurrency.test.ts src/web/workstations/shared/helpers/workstation-asset-availability.test.ts
|
||||
@metis/assets:test: ✔ WD04.4: revisión obsoleta rechazada antes de reemplazar composición; revisión actual conserva historial (8.181771ms)
|
||||
@metis/assets:test: ✔ el catálogo inicial es idempotente, con UUID propios y características opcionales (78.501672ms)
|
||||
@metis/assets:test: ✔ la disponibilidad por compañía conserva el global y los campos desactivados (42.827966ms)
|
||||
@metis/assets:test: ✔ las plantillas validan ámbito, campos activos e identidad estable al editar (19.171464ms)
|
||||
@metis/assets:test: ✔ los campos validan listas, igualdad semántica, duplicados y tipo fijo (1.398412ms)
|
||||
@metis/assets:test: ✔ solo elimina definiciones globales personalizadas que no usan otras plantillas (2.30532ms)
|
||||
@metis/assets:test: ✔ iconos opcionales: cambio y retirada conservan identidad y asociaciones (18.029154ms)
|
||||
@metis/assets:test: ✔ el arranque conserva un icono de serie retirado sin cambiar UUID, campos o disponibilidad (48.041211ms)
|
||||
@metis/assets:test: ✔ los iconos desconocidos o asociados a campos se rechazan (4.78664ms)
|
||||
@metis/assets:test: ✔ revisión explícita conserva identidades, copias y otras plantillas (28.459043ms)
|
||||
@metis/assets:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (32.056475ms)
|
||||
@metis/assets:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (14.685325ms)
|
||||
@metis/assets:test: ✔ Las relaciones validan sus extremos, normalizan nombres y distinguen dirección (6.409255ms)
|
||||
@metis/assets:test: ✔ Retirar conserva UUID, extremos y auditoría; repetir la retirada no modifica el historial (3.303729ms)
|
||||
@metis/assets:test: ✔ Tipos de relación de serie protegidos, ámbito de compañía y dirección fija (35.609605ms)
|
||||
@metis/assets:test: ✔ Asset: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (18.73976ms)
|
||||
@metis/assets:test: ✔ Asset: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (2.35772ms)
|
||||
@metis/assets:test: ✔ Asset: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (1.001709ms)
|
||||
@metis/assets:test: ✔ las secciones conservadas desambiguan el nombre respetando el límite de 100 caracteres (0.689206ms)
|
||||
@metis/assets:test: ✔ Características opcionales, tipos y límites; cero y falso son valores presentes (12.058603ms)
|
||||
@metis/assets:test: ✔ Activo conserva UUID y revisiones; los cambios iguales son idempotentes (1.260011ms)
|
||||
@metis/assets:test: ✔ texto largo conserva saltos de línea y rechaza tipos no textuales (0.614705ms)
|
||||
@metis/assets:test: ✔ estado técnico y uso: asignaciones calculadas, elegibilidad exclusiva/compartida e historial conservado (0.911807ms)
|
||||
@metis/assets:test: ✔ Fecha de alta opcional: calendario real, independencia de compra, revisiones e idempotencia (3.783933ms)
|
||||
@metis/assets:test: ✔ sedes archivadas: nuevas referencias rechazadas y referencias anteriores conservadas (3.866933ms)
|
||||
@metis/assets:test: ✔ Marcas: nombre normalizado, UUID v7, protección de serie, estado e idempotencia (6.603356ms)
|
||||
@metis/assets:test: ✔ Marcas: SVG estáticos con transparencia, límites y contenido activo rechazado (22.633594ms)
|
||||
@metis/assets:test: ✔ Marcas: los 23 fabricantes tienen SVG local validado; Clónico no representa una marca (4.68404ms)
|
||||
@metis/assets:test: ✔ Marcas: añadir, sustituir y quitar SVG conserva identidad, origen y actualización idempotente (3.012325ms)
|
||||
@metis/assets:test: ✔ Marcas: mapper bidireccional conserva identidad y rechaza persistencia inválida (1.949217ms)
|
||||
@metis/assets:test: ✔ Marcas: unicidad, edición y eliminación de personalizadas, con protección de serie (14.410223ms)
|
||||
@metis/assets:test: ✔ pertenencia explícita: un rol, identidad UUID v7 e idempotencia sin nuevas revisiones (4.272737ms)
|
||||
@metis/assets:test: ✔ promoción y retirada del rol conservan asociación e historial; finalización repetida es inocua (2.570822ms)
|
||||
@metis/assets:test: ✔ cambio de dominio crea sucesor único y cierre atómico planificado; admite reintentos y rechaza conflictos (1.271011ms)
|
||||
@metis/assets:test: ✔ capacidad, estado y compañía: rechaza extremos incompatibles sin inferencias de nombre (0.682506ms)
|
||||
@metis/assets:test: ✔ activos o dominios inactivos conservan vínculos y permiten cerrar o retirar el rol (2.015217ms)
|
||||
@metis/assets:test: ✔ mapper: rehidratación consistente, UUID conservados y rechazo de revisiones corruptas (2.486122ms)
|
||||
@metis/assets:test: ✔ contratos estrictos: roles, paginación, UUID, acciones y privilegios arbitrarios (4.468339ms)
|
||||
@metis/assets:test: ✔ infraestructura: interfaces independientes, varias IP, normalización y UUID v7 (8.647674ms)
|
||||
@metis/assets:test: ✔ infraestructura: mismo contenido no cambia identidades ni añade revisiones (4.305437ms)
|
||||
@metis/assets:test: ✔ infraestructura: rechaza nombres/IP duplicados, valores inválidos e identidades ajenas (3.355529ms)
|
||||
@metis/assets:test: ✔ infraestructura: límites y direcciones repetidas entre interfaces o compañías permitidas (9.370281ms)
|
||||
@metis/assets:test: ✔ publicación: alta, periodo, cierre idempotente e identidad persistida (5.895051ms)
|
||||
@metis/assets:test: ✔ publicación: cardinalidad y reintento idéntico no crean periodos (0.742507ms)
|
||||
@metis/assets:test: ✔ publicación: traslado explícito y replay estable incluso tras finalizar sucesor (1.086709ms)
|
||||
@metis/assets:test: ✔ publicación: estados, recurso no SMB, identidad y fechas inválidas (1.460512ms)
|
||||
@metis/assets:test: ✔ publicación: definición compatible con contratos comunes sin rol ni proveedor network (4.319537ms)
|
||||
@metis/assets:test: ✔ asistencia: UUID v7, historial, cambios semánticos y fechas (5.374746ms)
|
||||
@metis/assets:test: ✔ asistencia: preferido disponible y entrada estricta (1.19781ms)
|
||||
@metis/assets:test: ✔ asistencia: varios métodos, duplicados, preferido único y activo retirado (1.849715ms)
|
||||
@metis/assets:test: ✔ herramientas: protección de serie y desactivación sin perder referencias (0.667206ms)
|
||||
@metis/assets:test: ✔ asistencia: mapper bidireccional y rechazo de historial inconsistente (13.895819ms)
|
||||
@metis/assets:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (8.364172ms)
|
||||
@metis/assets:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (3.101926ms)
|
||||
@metis/assets:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (6.358554ms)
|
||||
@metis/assets:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (76.108452ms)
|
||||
@metis/assets:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (27.036531ms)
|
||||
@metis/assets:test: ✔ Puestos: composición opcional, referencias válidas y límites sin duplicados (10.48149ms)
|
||||
@metis/assets:test: ✔ Puestos: UUID estable e historial de composición, persona y estado; guardado idéntico idempotente (6.376355ms)
|
||||
@metis/assets:test: ✔ Puestos: ordenador principal exclusivo, único e independiente del responsable (1.968816ms)
|
||||
@metis/assets:test: ✔ baja del puesto: libera composición y responsable, conserva historial e identidad y es idempotente (1.511513ms)
|
||||
@metis/assets:test: ✔ definición real: proveedor, cardinalidad vigente, roles e inmutabilidad (6.101652ms)
|
||||
@metis/assets:test: ✔ cursor firmado: vinculación a consulta y operador, sin aceptar UUID libres ni alteraciones (3.989534ms)
|
||||
@metis/assets:test: ✔ extensión de Assets validada: roles y estados cerrados, UUID original y ámbito (4.991243ms)
|
||||
@metis/assets:test: ✔ ficha de activo es/en: página con dos pestañas, información actual, relaciones y contenido de compañía (135.431608ms)
|
||||
@metis/assets:test: ✔ historial del activo conserva instantáneas antiguas, interfaces retiradas y conexiones en ambos sentidos, ordenadas por fecha (14.527886ms)
|
||||
@metis/assets:test: ✔ ficha de activo distingue carga, error y activo inexistente (7.702246ms)
|
||||
@metis/assets:test: ✔ acciones: crear solo para portátiles, sobremesa y servidores disponibles (4.343426ms)
|
||||
@metis/assets:test: ✔ acciones: abrir el puesto asociado aunque el activo sea periférico, no operativo o el puesto esté inactivo (1.725911ms)
|
||||
@metis/assets:test: ✔ acciones: compañía y plantilla correctas; asociaciones solo históricas no bloquean el alta (0.619304ms)
|
||||
@metis/assets:test: ✔ alta desde activo es/en: composición y ordenador preseleccionados, sede activa y nombre propuesto; edición y compañía se respetan (39.569937ms)
|
||||
@metis/assets:test: ✔ inventario es/en: crear y ver puesto son acciones visibles fuera del menú (178.706666ms)
|
||||
@metis/assets:test: ✔ la acción de selección conserva su texto visible como nombre accesible en es/en (26.556725ms)
|
||||
@metis/assets:test: ✔ selector: comparte búsqueda, pestañas por identidad y columnas del inventario en es/en (178.620812ms)
|
||||
@metis/assets:test: ✔ selector: filtra conjuntamente por plantilla, nombre, sede y estado; distingue vacío (113.46966ms)
|
||||
@metis/assets:test: ✔ selector único: controles radio y disparador de diálogo; conserva el alta rápida y el bloqueo (46.163591ms)
|
||||
@metis/assets:test: ✔ picker: búsqueda rápida disponible durante carga y error, sin anunciar un falso vacío (91.363573ms)
|
||||
@metis/assets:test: ✔ inventario es/en: icono compacto antes del nombre, plantilla solo textual y ordenación por nombre (54.183256ms)
|
||||
@metis/assets:test: ✔ columnas de características: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (30.838961ms)
|
||||
@metis/assets:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (177.2071ms)
|
||||
@metis/assets:test: ✔ columnas generales y de red es/en: fechas y autores propios, IP/MAC por interfaz y ausencia real (59.174801ms)
|
||||
@metis/assets:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (3.657631ms)
|
||||
@metis/assets:test: ✔ La capacidad del inventario protege el presupuesto de tabla en el límite real (1.67001ms)
|
||||
@metis/assets:test: ✔ El resize conserva el contexto abierto al ganar capacidad y nunca abre un modal (1.036806ms)
|
||||
@metis/assets:test: ✔ Inventario traducido con alta de cabecera y sin formulario junto al listado (176.657254ms)
|
||||
@metis/assets:test: ✔ Las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (275.183342ms)
|
||||
@metis/assets:test: ✔ El activo ofrece altas relacionadas, ayuda inferior y no encadena una tercera alta (27.131962ms)
|
||||
@metis/assets:test: ✔ Las fichas leen ambos sentidos y conservan la auditoría de relaciones retiradas (30.722384ms)
|
||||
@metis/assets:test: ✔ El formulario de tipo de relación presenta ayuda justo antes de sus operaciones (14.860789ms)
|
||||
@metis/assets:test: ✔ Puestos de trabajo: listado traducido, alta en cabecera y formulario modal (17.666706ms)
|
||||
@metis/assets:test: ✔ Puestos de trabajo: altas rápidas y ayuda inferior; un activo anidado no ofrece terceras altas (20.642623ms)
|
||||
@metis/assets:test: ✔ alta de puesto: sede única automática, responsable opcional y cuatro pasos traducidos (19.815418ms)
|
||||
@metis/assets:test: ✔ la ficha lateral agrupa información y ofrece acciones superiores y enlaces reales (85.128108ms)
|
||||
@metis/assets:test: ✔ texto largo: área editable con ayuda accesible (5.137631ms)
|
||||
@metis/assets:test: ✔ las características marcan solo los campos obligatorios y conservan unidad y ayuda (9.321456ms)
|
||||
@metis/assets:test: ✔ el resumen usa los campos propios de la plantilla, omite vacíos y texto largo y limita a cuatro (8.734552ms)
|
||||
@metis/assets:test: ✔ alta de activo por pasos: empieza por plantilla, traduce la navegación y bloquea el avance sin selección (22.912737ms)
|
||||
@metis/assets:test: ✔ el progreso de activos conserva la plantilla y explica los pasos en es/en (22.029632ms)
|
||||
@metis/assets:test: ✔ las acciones por pasos solo ofrecen guardar al final y bloquean acciones durante el envío (36.262117ms)
|
||||
@metis/assets:test: ✔ características obligatorias: rechaza ausentes y espacios y conserva cero y falso (0.294802ms)
|
||||
@metis/assets:test: ✔ nombre propuesto: usa texto de la plantilla, omite marca y vacíos y respeta el límite (4.261525ms)
|
||||
@metis/assets:test: ✔ diseño del puesto: grupos compactos, columnas por contenedor y resumen sin textos de ausencia (47.947287ms)
|
||||
@metis/assets:test: ✔ activos del puesto: estado vacío visual traducido y acciones según disponibilidad (36.400717ms)
|
||||
@metis/assets:test: ✔ diseño del puesto: navegación traducida y acciones bloqueadas durante el guardado (17.506804ms)
|
||||
@metis/assets:test: ✔ ficha compacta de activo: comparte información, traducciones y acciones sin consultas propias (63.742479ms)
|
||||
@metis/assets:test: ✔ ordenador principal: tarjetas con selección única, etiquetas es/en y solo los activos del puesto (33.362798ms)
|
||||
@metis/assets:test: ✔ puestos: URL controla consulta, orden y página y sus enlaces conservan el contexto (26.759559ms)
|
||||
@metis/assets:test: ✔ Marcas: listado común traducido, filtros y alta modal sin formulario junto a la tabla (104.786425ms)
|
||||
@metis/assets:test: ✔ Marcas: nombre de serie protegido y ayuda inferior; alta rápida siempre activa (12.414375ms)
|
||||
@metis/assets:test: ✔ Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propaga conflictos (29.788078ms)
|
||||
@metis/assets:test: ✔ Marcas: selector mantiene valores antiguos e inactivos y respeta el límite de alta anidada (14.357486ms)
|
||||
@metis/assets:test: ✔ Marcas: el campo de serie se identifica por referencia de catálogo, no por el nombre (32.903896ms)
|
||||
@metis/assets:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (3.200827ms)
|
||||
@metis/assets:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (2.215119ms)
|
||||
@metis/assets:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (4.77634ms)
|
||||
@metis/assets:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (182.992149ms)
|
||||
@metis/assets:test: ✔ L04.2: campos del catálogo recuperan orden y página desde su URL (207.34043ms)
|
||||
@metis/assets:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (218.24671ms)
|
||||
@metis/assets:test: ✔ el catálogo busca nombres sin acentos sin cambiar la consulta ni incluir campos de otras pestañas (90.816145ms)
|
||||
@metis/assets:test: ✔ los formularios tienen ayuda inferior y reservan el alta rápida al diálogo de selección de campos (86.465119ms)
|
||||
@metis/assets:test: ✔ las plantillas ofrecen selección visual traducida y conservan el icono al copiar (418.680613ms)
|
||||
@metis/assets:test: ✔ dominio: roles estructurados, resumen solo de página y enlaces UUID, es/en (53.026918ms)
|
||||
@metis/assets:test: ✔ tablas: miembro y finalización no se confunden con controlador o pertenencia vigente (13.004678ms)
|
||||
@metis/assets:test: ✔ activo sin dominio: vacío explícito y asociación solo si el equipo es compatible (21.51993ms)
|
||||
@metis/assets:test: ✔ activo unido: dominio, rol real, navegación y acciones especializadas (17.452805ms)
|
||||
@metis/assets:test: ✔ carga y error de consulta no se muestran como dominio vacío (19.706419ms)
|
||||
@metis/assets:test: ✔ API de lectura: conserva cursor opaco, tipo y ámbito; propaga errores funcionales (40.056641ms)
|
||||
@metis/assets:test: ✔ traslado usa un único comando atómico, UUID esperado y CSRF; conflicto no se oculta (7.311944ms)
|
||||
@metis/assets:test: ✔ infraestructura: clasificación de serie independiente del nombre/icono y selección manual prioritaria (2.481815ms)
|
||||
@metis/assets:test: ✔ infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos (0.482403ms)
|
||||
@metis/assets:test: ✔ infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos (168.625412ms)
|
||||
@metis/assets:test: ✔ SMB: relaciones paginadas, UNC Unicode y enlaces bidireccionales es/en (48.444089ms)
|
||||
@metis/assets:test: ✔ SMB: historial conserva periodos y actores y no ofrece modificar periodos finalizados (21.625829ms)
|
||||
@metis/assets:test: ✔ SMB: vacío real y error de lectura son distintos (18.602311ms)
|
||||
@metis/assets:test: ✔ SMB: denegación oculta datos previos y carga inicial conserva cabeceras (17.336004ms)
|
||||
@metis/assets:test: ✔ SMB: transferencia conserva UUID y envía un único comando propietario con CSRF (26.280557ms)
|
||||
@metis/assets:test: ✔ SMB: búsqueda remota y cursor opaco viajan al servidor (1.79411ms)
|
||||
@metis/assets:test: ✔ ficha SMB: publicador destacado, datos y timeline real es/en; solo lectura conserva navegación (32.213792ms)
|
||||
@metis/assets:test: ✔ ficha SMB: vacío, finalizada, carga y denegación son estados distintos (20.941525ms)
|
||||
@metis/assets:test: ✔ timeline SMB: solo hechos persistidos, UUID estable, traslado y cierre sin sucesor en página (0.617504ms)
|
||||
@metis/assets:test: ✔ historial v2: utiliza únicamente nombres autorizados aportados y conserva UUID técnicos (9.898359ms)
|
||||
@metis/assets:test: ✔ asistencia: formulario con alta rápida y ayuda antes de guardar (32.897695ms)
|
||||
@metis/assets:test: ✔ asistencia: resumen excluye retirados, ordena preferido y conserva la gestión original (11.68357ms)
|
||||
@metis/assets:test: ✔ asistencia: traducciones y resumen de método (1.515909ms)
|
||||
@metis/assets:test: ✔ asistencia: resumen documental muestra destino, VPN y preferencia sin acciones de gestión (2.217513ms)
|
||||
@metis/assets:test: ✔ asistencia: referencia no resuelta conserva el registro con ausencia y explicación (1.243107ms)
|
||||
@metis/assets:test: ✔ renombrar: usa valores vigentes, normaliza el nombre, valida respuesta y envía CSRF (50.699404ms)
|
||||
@metis/assets:test: ✔ renombrar: rechaza vacíos, espacios y exceso de longitud antes de realizar peticiones (1.770011ms)
|
||||
@metis/assets:test: ✔ renombrar: un nombre equivalente no provoca una escritura ni otra revisión (0.996206ms)
|
||||
@metis/assets:test: ✔ renombrar: propaga puesto ausente y errores del servidor sin escritura posterior (2.868717ms)
|
||||
@metis/assets:test: ✔ WD04.4: comparte reglas de disponibilidad, retención, ocupación y puesto inactivo (1.139107ms)
|
||||
@metis/assets:test: ✔ alta: sede necesaria, responsable opcional, composición mínima y ordenador perteneciente al puesto (1.092106ms)
|
||||
@metis/assets:test: ✔ composición: asigna el activo único, conserva una elección válida y limpia el ordenador retirado (0.309402ms)
|
||||
@metis/assets:test: ✔ propone un nombre de puesto solo al conocer ordenador principal y sede (1.465709ms)
|
||||
@metis/assets:test: ℹ tests 154
|
||||
@metis/assets:test: ℹ suites 0
|
||||
@metis/assets:test: ℹ pass 154
|
||||
@metis/assets:test: ℹ fail 0
|
||||
@metis/assets:test: ℹ cancelled 0
|
||||
@metis/assets:test: ℹ skipped 0
|
||||
@metis/assets:test: ℹ todo 0
|
||||
@metis/assets:test: ℹ duration_ms 6461.234104
|
||||
@metis/companies:test: cache bypass, force executing 8ec308292708eef7
|
||||
@metis/companies:test: $ TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/api/domain/company.aggregate.test.ts src/api/domain/people.test.ts src/web/companies/shared/companies-page.test.tsx src/web/people/list/people-search.test.tsx src/web/companies/list/company-filters.test.tsx src/web/shared/helpers/company-list-query-state.test.ts src/api/domain/access/access.test.ts src/api/domain/mail-configuration.test.ts src/api/domain/mailbox.test.ts src/web/access/shared/access.test.tsx src/web/mail-configurations/shared/mail.test.tsx src/api/domain/work-profile.test.ts src/web/work-profiles/shared/profile.test.tsx src/api/domain/printer-configuration.test.ts src/web/printer-configurations/shared/printer.test.tsx src/api/domain/application-configuration.test.ts src/web/application-configurations/shared/application.test.tsx src/api/domain/certificate-configuration.test.ts src/web/certificate-configurations/shared/certificate.test.tsx src/web/work-environments/shared/work-environment.test.tsx src/api/domain/telework-authorization.test.ts src/web/telework-authorizations/shared/telework.test.tsx src/api/domain/remote-connection.test.ts src/api/domain/process.test.ts src/web/remote-connections/shared/remote.test.tsx src/web/processes/shared/processes.test.tsx src/web/shared/initials-avatar.test.tsx src/api/infrastructure/company-logo-processor.test.ts src/web/companies/shared/company-avatar.test.tsx src/web/shared/client-contracts.test.ts src/web/infrastructure/shared/helpers/printer-usage.test.ts src/web/workstations/detail/helpers/workstation-relations.test.ts src/web/workstations/detail/helpers/workplace-map-layout.test.ts src/web/workstations/detail/helpers/workstation-dashboard.test.ts src/web/workstations/detail/helpers/workstation-equipment.test.ts src/web/workstations/detail/helpers/workstation-profile-expansion.test.ts src/web/people/shared/helpers/person-organization.test.ts src/web/workstations/detail/ui/workstation-detail.test.tsx src/web/workspace/shared/workspace-routes.test.ts src/web/workspace/view/helpers/sidebar-preference.test.ts src/api/domain/software-installation.test.ts src/web/software-installations/shared/software.test.tsx src/web/software-installations/shared/current-operating-systems.test.tsx src/api/application/directory-context-plan.test.ts src/web/access/list/helpers/access-environment-filter.test.ts src/api/domain/folder-mapping-account.test.ts src/api/application/local-identity-plan.test.ts src/web/access/list/helpers/access-resource-filter.test.ts src/web/folder-mappings/shared/helpers/folder-account-options.test.ts src/web/local-identity-environments/shared/local-identities-ui.test.tsx src/web/workstations/detail/helpers/workplace-node-actions.test.tsx
|
||||
@metis/companies:test: ✔ contexto AD: creación pura con identidades propias, sin cuentas, grupos ni activos (6.296937ms)
|
||||
@metis/companies:test: ✔ clasificación independiente del nombre; no se adopta un sistema existente por llamarse AD (4.708628ms)
|
||||
@metis/companies:test: ✔ se rechazan compañías distintas, referencias inexistentes y entornos sin clasificación (1.187707ms)
|
||||
@metis/companies:test: ✔ DNS repetido no crea dos entornos; otro dominio comparte sistema y obtiene un entorno distinto (1.165707ms)
|
||||
@metis/companies:test: ✔ identidad local: vínculo por UUID, alta y retirada idempotentes con identidad e historial protegidos (10.840364ms)
|
||||
@metis/companies:test: ✔ identidad local: clasificación explícita rechaza AD por sistema o entorno, sin comparar etiquetas (1.215007ms)
|
||||
@metis/companies:test: ✔ identidad local: crear entorno utiliza AccessWorkspace y conserva cuentas y sistema existentes (2.107713ms)
|
||||
@metis/companies:test: ✔ catálogos globales no se duplican con adiciones locales y no se admiten grupos anidados (6.466738ms)
|
||||
@metis/companies:test: ✔ renombrar una cuenta conserva UUID, historial y semántica de repetición; su uso permanece fijo (4.285325ms)
|
||||
@metis/companies:test: ✔ cerrar una autorización configurada deja retirada pendiente y cerrar una pendiente no inventa un acceso (5.856735ms)
|
||||
@metis/companies:test: ✔ titulares no se solapan incluso en periodos cerrados; el relevo conserva una retirada anterior (2.467215ms)
|
||||
@metis/companies:test: ✔ responsabilidad técnica no concede acceso y se permite sobre una cuenta caducada o inactiva (1.652409ms)
|
||||
@metis/companies:test: ✔ valores reconstruyen UUID y fechas, rechazan fechas imposibles y no permiten credenciales (3.632821ms)
|
||||
@metis/companies:test: ✔ las referencias UUID se normalizan y no permiten duplicar cuentas variando mayúsculas (1.117006ms)
|
||||
@metis/companies:test: ✔ aplicaciones: contrato estricto, versión opcional y solo instalada (9.339555ms)
|
||||
@metis/companies:test: ✔ aplicaciones: UUID, idempotencia, versión auditada, destino fijo y retirada (6.011936ms)
|
||||
@metis/companies:test: ✔ aplicaciones: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (2.445614ms)
|
||||
@metis/companies:test: ✔ aplicaciones: inactividad conserva notas y permite retirada, impide reconfigurar (2.073112ms)
|
||||
@metis/companies:test: ✔ aplicaciones: persistencia preserva historial y rechaza claves y orden incoherentes (6.620639ms)
|
||||
@metis/companies:test: ✔ R2.5: global conserva perfil, delivery e historial; disponibilidad distinta de lectura histórica (1.959312ms)
|
||||
@metis/companies:test: ✔ certificados: contrato estricto, ubicación opcional y límites (7.936347ms)
|
||||
@metis/companies:test: ✔ certificados: UUID, idempotencia, ubicación auditada, destino fijo y retirada (3.899723ms)
|
||||
@metis/companies:test: ✔ certificados: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (3.47202ms)
|
||||
@metis/companies:test: ✔ certificados: inactividad conserva notas y permite retirada, impide reconfigurar (1.422508ms)
|
||||
@metis/companies:test: ✔ certificados: persistencia preserva historial y rechaza claves y orden incoherentes (11.004765ms)
|
||||
@metis/companies:test: ✔ certificados: caducidad inclusiva, revisión y retirada sin perder historial (1.536809ms)
|
||||
@metis/companies:test: ✔ compañía, contacto y sede tienen UUID v7 propios y se conserva la identidad al editar (5.317131ms)
|
||||
@metis/companies:test: ✔ la pertenencia impide asociar o editar entidades de otra compañía (1.246807ms)
|
||||
@metis/companies:test: ✔ la dirección y el teléfono son optativos y se pueden borrar (0.734704ms)
|
||||
@metis/companies:test: ✔ nombres, email, teléfono y longitud de dirección se validan en los Value Objects (1.627509ms)
|
||||
@metis/companies:test: ✔ sedes: archivar y reactivar conserva identidad y datos; repetir el estado es idempotente (0.707004ms)
|
||||
@metis/companies:test: ✔ FolderMapping: cuenta desconocida, cuenta alternativa y cobertura independiente de la necesidad (23.480739ms)
|
||||
@metis/companies:test: ✔ FolderMapping: ausencia conserva cuenta, null la retira y cada cambio real crea revisión (4.894529ms)
|
||||
@metis/companies:test: ✔ FolderMapping: rechaza cuenta ausente, otro subtipo, otra persona, caducada o desactivada (5.847235ms)
|
||||
@metis/companies:test: ✔ FolderMapping: Coverage por grupo exige pertenencia directa de la cuenta y configuración vigente (13.965083ms)
|
||||
@metis/companies:test: ✔ FolderMapping: cobertura derivada cambia al retirar concesión, necesidad, cuenta o asociación personal (22.087331ms)
|
||||
@metis/companies:test: ✔ FolderMapping: todas las autorizaciones necesarias deben proceder de la cuenta seleccionada (1.71041ms)
|
||||
@metis/companies:test: ✔ Correo: UUIDv7, normalización, no-op, historial y retirada explícita (7.329943ms)
|
||||
@metis/companies:test: ✔ Correo: buzón independiente, vigencia, perfiles, varios buzones y duplicados abiertos (7.996547ms)
|
||||
@metis/companies:test: ✔ Correo: persistencia conserva identidad e historial y rechaza corrupción (6.223436ms)
|
||||
@metis/companies:test: ✔ Correo: vinculación explícita del legado conserva UUID e historial, sin inferir propiedad (5.370332ms)
|
||||
@metis/companies:test: ✔ Buzón: identidad, dirección única, cuenta opcional, ámbito y cambios auditados (4.130724ms)
|
||||
@metis/companies:test: ✔ Uso de buzón: fechas inclusiva/exclusiva, mismo día, solapes, varias personas e historial (5.335331ms)
|
||||
@metis/companies:test: ✔ Buzones: mapper conserva identidad y revisiones, rechaza corrupción (6.373938ms)
|
||||
@metis/companies:test: ✔ personas con datos opcionales no requieren cuenta de acceso y conservan su UUID (5.257931ms)
|
||||
@metis/companies:test: ✔ departamentos únicos por compañía, ignorando mayúsculas y espacios exteriores (1.232908ms)
|
||||
@metis/companies:test: ✔ varios departamentos, un único principal y sin periodos solapados (3.37782ms)
|
||||
@metis/companies:test: ✔ ediciones conservan revisiones y una repetición semántica no añade historial (6.425638ms)
|
||||
@metis/companies:test: ✔ inactivos no reciben nuevas asignaciones; se conservan y pueden cerrarse las existentes (0.973005ms)
|
||||
@metis/companies:test: ✔ asignaciones rechazan pertenencias cruzadas y fechas inválidas (1.492209ms)
|
||||
@metis/companies:test: ✔ la persistencia rechaza periodos inválidos y revisiones que no reflejan el estado actual (2.457215ms)
|
||||
@metis/companies:test: ✔ impresoras: contratos estrictos, notas opcionales y predeterminada solo vigente (14.394984ms)
|
||||
@metis/companies:test: ✔ impresoras: UUID, historial, igualdad semántica, destino fijo y retirada terminal (3.674421ms)
|
||||
@metis/companies:test: ✔ impresoras: duplicados por perfil, predeterminada única y destinos del ámbito (1.981011ms)
|
||||
@metis/companies:test: ✔ impresoras: indisponibilidad señala revisión y permite notas y retirada (1.558709ms)
|
||||
@metis/companies:test: ✔ impresoras: rehidratación conserva identidades y detecta corrupción (10.949764ms)
|
||||
@metis/companies:test: ✔ procesos: contratos estrictos, normalización y límites (8.114347ms)
|
||||
@metis/companies:test: ✔ procesos: UUID v7, tareas con identidad, actualización idempotente e historial (3.245819ms)
|
||||
@metis/companies:test: ✔ procesos: completar exige todas resueltas; cierre terminal y sujeto/tareas fijos (3.585621ms)
|
||||
@metis/companies:test: ✔ procesos: añadir revisiones conserva las resueltas e identidades y no duplica (2.798616ms)
|
||||
@metis/companies:test: ✔ plantillas: copia de instrucciones, igualdad semántica y cambios independientes (1.214307ms)
|
||||
@metis/companies:test: ✔ procesos: rehidratación bidireccional y rechazo de raíces/historial alterados (8.623651ms)
|
||||
@metis/companies:test: ✔ revisión: personales, retirada pendiente y relaciones finalizadas aún configuradas (0.835605ms)
|
||||
@metis/companies:test: ✔ revisión: incluye configuraciones de perfiles archivados, excluye cerradas y evita duplicar (0.379402ms)
|
||||
@metis/companies:test: ✔ remotos: origen personal sin detalle, destinos tipados y contrato estricto (6.83686ms)
|
||||
@metis/companies:test: ✔ remotos: UUID e historial, destino fijo, idempotencia y cierre terminal (4.773242ms)
|
||||
@metis/companies:test: ✔ remotos: referencias de compañía y duplicados abiertos por recorrido y herramienta (3.190228ms)
|
||||
@metis/companies:test: ✔ remotos: configuración y autorización independientes; caducidad y revocación no borran acceso (4.63974ms)
|
||||
@metis/companies:test: ✔ remotos: disponibilidad de origen y destino, retirada y método personalizado (2.33172ms)
|
||||
@metis/companies:test: ✔ remotos: persistencia conserva auditoría y detecta referencias, claves e historial corruptos (8.793378ms)
|
||||
@metis/companies:test: ✔ remotos: una sede archivada conserva conexiones existentes pero rechaza nuevas (1.395113ms)
|
||||
@metis/companies:test: ✔ instalaciones: pertenecen al equipo, admiten SO/aplicaciones y rechazan recursos ajenos y destinos variables (8.228972ms)
|
||||
@metis/companies:test: ✔ instalaciones: versiones, idempotencia, retirada, revisión y persistencia de historial (19.27237ms)
|
||||
@metis/companies:test: ✔ licencia del software: opcional, misma compañía y tipo, vigencia, desvinculación e historial idempotente (11.3726ms)
|
||||
@metis/companies:test: ✔ R2.4: global activo e inactivo separan alta, reconfiguración y recuperación histórica (1.892117ms)
|
||||
@metis/companies:test: ✔ teletrabajo: ausencia, límites, fechas reales y contrato estricto (5.812651ms)
|
||||
@metis/companies:test: ✔ teletrabajo: UUID, renovación y revocación auditadas, igualdad y destino fijo (11.062497ms)
|
||||
@metis/companies:test: ✔ teletrabajo: vigencia inclusiva, programación, caducidad y persona inactiva (3.199928ms)
|
||||
@metis/companies:test: ✔ teletrabajo: ámbito, unicidad incluso revocada, disponibilidad y retirada (1.224311ms)
|
||||
@metis/companies:test: ✔ teletrabajo: rehidratación preserva identidad y detecta corrupción de raíz e historial (12.144107ms)
|
||||
@metis/companies:test: ✔ Perfil: cuenta opcional, UUID, historial, identidad fija e idempotencia (7.777668ms)
|
||||
@metis/companies:test: ✔ Perfil: desactivar conserva valores e historial, es idempotente y precede al borrado (2.634223ms)
|
||||
@metis/companies:test: ✔ Perfil: misma persona/dispositivo único vigente, múltiples dispositivos/personas y revisión sin pérdida de datos (1.664614ms)
|
||||
@metis/companies:test: ✔ Perfil: cuenta autorizada, vigencia inclusiva de caducidad, cierre de autorización y cuenta técnica (1.389712ms)
|
||||
@metis/companies:test: ✔ Perfil: persistencia conserva revisiones y rechaza raíz o cuenta histórica incoherentes (12.180407ms)
|
||||
@metis/companies:test: ✔ Perfil: solo un ordenador principal activo admite altas y reactivaciones; el historial se conserva (2.331921ms)
|
||||
@metis/companies:test: ✔ PNG, JPEG y WebP se normalizan sin recorte y sin metadatos a un avatar ligero (172.951419ms)
|
||||
@metis/companies:test: ✔ se rechazan archivos vacíos, corruptos, SVG y GIF aunque simulen un MIME válido (1.587614ms)
|
||||
@metis/companies:test: ✔ se rechazan excesos de peso y dimensión antes de almacenar (5.75085ms)
|
||||
@metis/companies:test: ✔ los PNG animados se rechazan antes de decodificarlos (3.334529ms)
|
||||
@metis/companies:test: ✔ el contexto distingue cuentas y grupos del entorno, sin incluir otros entornos o datos globales (5.279646ms)
|
||||
@metis/companies:test: ✔ recurso contextual: incluye identidades de sistemas distintos sin mezclar necesidades y concesiones ajenas (11.237699ms)
|
||||
@metis/companies:test: ✔ filtro vacío no incluye elementos ajenos y las rutas validan UUID y contexto de compañía (6.446657ms)
|
||||
@metis/companies:test: ✔ los catálogos presentan alta mediante acción y no un formulario junto al listado (141.974959ms)
|
||||
@metis/companies:test: ✔ cuentas ofrecen caducidad opcional y campos sin contraseña (40.697746ms)
|
||||
@metis/companies:test: ✔ la ayuda explica todos los elementos en ambos idiomas y distingue el ámbito (100.84921ms)
|
||||
@metis/companies:test: ✔ los ejemplos del sistema orientan sin rellenar datos del alta (7.511445ms)
|
||||
@metis/companies:test: ✔ las altas rápidas respetan el contexto, ámbito y relaciones históricas (1.66241ms)
|
||||
@metis/companies:test: ✔ los formularios ofrecen altas relacionadas y el segundo modal evita altas anidadas (163.072283ms)
|
||||
@metis/companies:test: ✔ aplicaciones: listado sin formularios, alta en cabecera y traducciones (135.824022ms)
|
||||
@metis/companies:test: ✔ aplicaciones: selectores con alta rápida y ayuda inferior antes de guardar (58.228753ms)
|
||||
@metis/companies:test: ✔ R2.5: perfil muestra nombre, origen y versión global sin ResourceDTO local ni mezcla de perfiles (70.980628ms)
|
||||
@metis/companies:test: ✔ certificados: listado sin formularios, alta en cabecera y traducciones (119.637124ms)
|
||||
@metis/companies:test: ✔ certificados: selectores con alta rápida y ayuda inferior antes de guardar (53.417523ms)
|
||||
@metis/companies:test: ✔ la compañía de origen es ámbito visible, no un filtro eliminable con Limpiar filtros (237.759723ms)
|
||||
@metis/companies:test: ✔ directorio: orden y página proceden de URL sin perder la consulta (77.757766ms)
|
||||
@metis/companies:test: ✔ las subentradas de Personas conservan contexto y reemplazan el filtro de la vista (34.344905ms)
|
||||
@metis/companies:test: ✔ el dashboard no ofrece selector de compañía y conserva las métricas de cada ámbito (90.861041ms)
|
||||
@metis/companies:test: ✔ el resumen de compañía es la entrada por defecto y ofrece destinos reales (120.920956ms)
|
||||
@metis/companies:test: ✔ los índices de catálogos distinguen definiciones globales y variantes de compañía (19.766769ms)
|
||||
@metis/companies:test: ✔ el listado presenta estado vacío y acceso al alta sin formularios visibles (69.363894ms)
|
||||
@metis/companies:test: ✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (84.738325ms)
|
||||
@metis/companies:test: ✔ el formulario de sede ofrece dirección y teléfono opcionales (15.588233ms)
|
||||
@metis/companies:test: ✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (17.906954ms)
|
||||
@metis/companies:test: ✔ asignar un departamento permite añadir el primero y conserva el selector en edición (34.704796ms)
|
||||
@metis/companies:test: ✔ el tablero de compañía tiene composición propia, acciones arriba y métricas limitadas a su ámbito (24.390909ms)
|
||||
@metis/companies:test: ✔ el menú de compañía ordena trabajo y personas por grupos sin repetir las acciones de usuario (37.899825ms)
|
||||
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: ready (61.337225ms)
|
||||
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: pending (53.569258ms)
|
||||
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: error (96.881428ms)
|
||||
@metis/companies:test: ✔ sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto (56.240281ms)
|
||||
@metis/companies:test: ✔ la navegación de compañía conserva 21 enlaces reales, sección activa y contexto es/en (122.222342ms)
|
||||
@metis/companies:test: ✔ el logo sustituye iniciales con URL versionada, dimensiones fijas y carga diferida (19.260367ms)
|
||||
@metis/companies:test: ✔ sin logotipo aparecen iniciales y las listas consultan la misma caché de compañías (7.821968ms)
|
||||
@metis/companies:test: ✔ cuentas del mapeo: exige asociación vigente y catálogos activos; no atribuye por login o por perfil (10.24139ms)
|
||||
@metis/companies:test: ✔ actualización del mapeo: omitir cuenta conserva semántica distinta de retirarla explícitamente; admite revisión capturada (6.392656ms)
|
||||
@metis/companies:test: ✔ uso de impresora: agrupa perfiles de un mismo equipo y separa pendientes e historial (25.330622ms)
|
||||
@metis/companies:test: ✔ uso de impresora: referencias sin resolver no se presentan como ausencia de uso (0.472704ms)
|
||||
@metis/companies:test: ✔ identidades locales: entorno del activo, navegación por UUID y estado vigente en es/en (55.661735ms)
|
||||
@metis/companies:test: ✔ propietario local: vínculo retirado conserva navegación y no ofrece reasignación (3.36332ms)
|
||||
@metis/companies:test: ✔ cuenta del mapeo: seleccionar perfil es una acción explícita; el render no infiere ni cambia cuenta (23.725143ms)
|
||||
@metis/companies:test: ✔ correo: listado traducido, alta en cabecera y formularios ocultos (165.947996ms)
|
||||
@metis/companies:test: ✔ correo: selectores reutilizables con altas y ayuda inmediatamente antes de guardar (38.36713ms)
|
||||
@metis/companies:test: ✔ Buzones: alta en modal, cuenta opcional y ayuda al final (112.437073ms)
|
||||
@metis/companies:test: ✔ L04.2: correo mantiene tabla y criterios mientras faltan sus referencias (39.742638ms)
|
||||
@metis/companies:test: ✔ Personas conserva búsqueda por nombre, contactos, consulta y distinción ñ/n en es/en (700.766903ms)
|
||||
@metis/companies:test: ✔ organización: resuelve departamento y cargo vigentes del responsable (1.361612ms)
|
||||
@metis/companies:test: ✔ organización: sin responsable o asignación no infiere datos desde perfiles ni cargo genérico de persona (1.022608ms)
|
||||
@metis/companies:test: ✔ organización: periodos semiabiertos, históricos y futuros (0.220902ms)
|
||||
@metis/companies:test: ✔ organización: varias vigentes conservan todas, principal primero y orden estable sin mutar (10.820695ms)
|
||||
@metis/companies:test: ✔ organización: dos principales no ocultan información seleccionando una arbitrariamente (0.396603ms)
|
||||
@metis/companies:test: ✔ organización: aislamiento de compañía y persona, y referencias incompletas (0.214902ms)
|
||||
@metis/companies:test: ✔ organización: cambios y refetch se derivan de la fuente actual (0.244402ms)
|
||||
@metis/companies:test: ✔ impresoras: listado sin formularios, alta en cabecera y traducciones (149.230494ms)
|
||||
@metis/companies:test: ✔ impresoras: selectores con alta rápida y ayuda inferior antes de guardar (82.552995ms)
|
||||
@metis/companies:test: ✔ L04.2: Procesos recupera búsqueda, cerrados, orden y página en sus enlaces (288.013815ms)
|
||||
@metis/companies:test: ✔ procesos: listados es/en sin formularios junto a los datos (95.597499ms)
|
||||
@metis/companies:test: ✔ procesos: alta rápida y ayuda inferior antes del pie (31.226167ms)
|
||||
@metis/companies:test: ✔ plantillas: instrucciones, desactivación y ayuda global es/en sin datos de compañía (27.359634ms)
|
||||
@metis/companies:test: ✔ seguimiento: origen personal, revisión actual y edición sin afirmar retirada real; cerrados sin editar (85.085528ms)
|
||||
@metis/companies:test: ✔ remotos: consulta sin formularios y traducciones (112.66666ms)
|
||||
@metis/companies:test: ✔ remotos: formulario, altas rápidas y ayuda antes del pie (84.083317ms)
|
||||
@metis/companies:test: ✔ remotos: origen personal, acceso configurado sin autorización y VPN separada (5.453747ms)
|
||||
@metis/companies:test: ✔ las consultas de compañías conservan sus rutas y rechazan respuestas inválidas (51.689309ms)
|
||||
@metis/companies:test: ✔ lectura y guardado de accesos están separados sin cambiar el contrato HTTP (3.39492ms)
|
||||
@metis/companies:test: ✔ el guardado de usos de buzón mantiene su endpoint y no se confunde con la consulta (4.589727ms)
|
||||
@metis/companies:test: ✔ los adapters mantienen la ausencia semántica sin modificar los campos del formulario (0.362302ms)
|
||||
@metis/companies:test: ✔ las instrucciones de proceso conservan el orden y excluyen líneas vacías (0.448102ms)
|
||||
@metis/companies:test: ✔ la validación de logos conserva tipos permitidos, vacío y límite exacto de tamaño (0.601403ms)
|
||||
@metis/companies:test: ✔ la tabla espera todas sus referencias y distingue actualización de disponibilidad (1.277208ms)
|
||||
@metis/companies:test: ✔ un fallo recuperable conserva datos anteriores sin presentarlos como actualizados (1.423609ms)
|
||||
@metis/companies:test: ✔ una fuente denegada impide mostrar la caché de todo el listado compuesto (0.662404ms)
|
||||
@metis/companies:test: ✔ dashboard: HTTP 401 conserva la semántica de disponibilidad (34.670707ms)
|
||||
@metis/companies:test: ✔ dashboard: HTTP 403 conserva la semántica de disponibilidad (0.529403ms)
|
||||
@metis/companies:test: ✔ dashboard: HTTP 503 conserva la semántica de disponibilidad (0.380402ms)
|
||||
@metis/companies:test: ✔ dashboard: una lectura correcta valida y conserva la página del proveedor (6.455439ms)
|
||||
@metis/companies:test: ✔ iniciales y siglas soportan nombres vacíos, Unicode y separadores (2.825424ms)
|
||||
@metis/companies:test: ✔ el color es estable con variaciones de espacios, mayúsculas y acentos (0.371803ms)
|
||||
@metis/companies:test: ✔ los colores generados mantienen contraste AA para texto pequeño (3.366729ms)
|
||||
@metis/companies:test: ✔ el avatar conserva su nombre accesible y puede ser decorativo dentro de un botón etiquetado (9.686882ms)
|
||||
@metis/companies:test: ✔ OSUI-01/02/17: SO actual desde instalación, independiente de cualquier valor de fábrica (4.73784ms)
|
||||
@metis/companies:test: ✔ OSUI-03/04/05/18: vacío, pendiente y retirado no son SO actual (35.906204ms)
|
||||
@metis/companies:test: ✔ OSUI-06/07: varios SO, orden determinista y aplicaciones excluidas (14.671024ms)
|
||||
@metis/companies:test: ✔ OSUI-08/12: cambiar de activo conserva solo su SO, sin aceptar compañías ajenas (3.189027ms)
|
||||
@metis/companies:test: ✔ OSUI-09: versión de instalación y redundancia literal sin reglas de fabricante (6.176653ms)
|
||||
@metis/companies:test: ✔ OSUI-10: referencia no resoluble no significa ausencia; inactiva resoluble conserva información (6.413454ms)
|
||||
@metis/companies:test: ✔ OSUI-11: error con datos previos, error de resolver y carga no se presentan como vacío (11.006393ms)
|
||||
@metis/companies:test: ✔ OSUI-13/14: servidor sin puesto y plantilla propia consumen la misma lectura por activo (5.298739ms)
|
||||
@metis/companies:test: ✔ OSUI-16: presentación es/en y versión opcional (11.770573ms)
|
||||
@metis/companies:test: ✔ SO global: selector conserva scope y destino SO; la versión instalada sigue separada (0.544704ms)
|
||||
@metis/companies:test: ✔ REG-SO-01/11: render global configurado, inactivo e histórico conserva nombre, versión y destino es/en (19.128019ms)
|
||||
@metis/companies:test: ✔ software del puesto es/en: SO actual, versión, enlaces y separación por ordenador sin formularios incrustados (133.23482ms)
|
||||
@metis/companies:test: ✔ formulario software: recurso fijo en edición, estados permitidos, ayuda y ausencia de persona/perfil (26.459863ms)
|
||||
@metis/companies:test: ✔ R2.4: nombre, origen y enlace global se resuelven sin ResourceDTO local; licencia continúa particular (36.459824ms)
|
||||
@metis/companies:test: ✔ teletrabajo: consulta sin formularios y traducciones (76.32017ms)
|
||||
@metis/companies:test: ✔ teletrabajo: formulario con alta rápida de persona, fechas opcionales y ayuda inferior (22.884341ms)
|
||||
@metis/companies:test: ✔ teletrabajo: sin autorización no se presupone permiso; persona inactiva requiere revisión (5.312433ms)
|
||||
@metis/companies:test: ✔ teletrabajo: alta desde un perfil fija la persona y no abre otra alta rápida (7.912649ms)
|
||||
@metis/companies:test: ✔ teletrabajo es/en: conserva estado documental, vigencia y revisión como mensajes independientes (29.928184ms)
|
||||
@metis/companies:test: ✔ entorno: reúne varios correos y configuraciones, distingue pendiente, retirada y revisión (2.326714ms)
|
||||
@metis/companies:test: ✔ entorno: excluye cerradas, otros perfiles, compañías y correo antiguo sin adivinar relaciones (0.908006ms)
|
||||
@metis/companies:test: ✔ entorno: composición vigente del puesto y datos actuales sin duplicación (0.405603ms)
|
||||
@metis/companies:test: ✔ entorno: ficha es/en con estados y acciones, sin formularios; muestra falso y varios correos (86.11883ms)
|
||||
@metis/companies:test: ✔ entorno: carga, fallo parcial sin falsa ficha vacía, perfil inexistente y aviso de archivado (49.515803ms)
|
||||
@metis/companies:test: ✔ entorno: consulta autorización por persona, común a sus perfiles y sin confundir preparación (31.718194ms)
|
||||
@metis/companies:test: ✔ entorno: accesos abiertos de la persona y sus orígenes sin confundirlos con configuraciones del perfil (13.256181ms)
|
||||
@metis/companies:test: ✔ las entidades de los selectores se pluralizan en es/en sin reutilizar el verbo de acción (7.556546ms)
|
||||
@metis/companies:test: ✔ perfiles: listado es/en, alta en cabecera y ausencia de formulario junto al listado (78.052078ms)
|
||||
@metis/companies:test: ✔ perfiles: la cabecera breve conserva el motivo completo en la celda de revisión (37.752331ms)
|
||||
@metis/companies:test: ✔ perfiles: alta en cuatro pasos, sin guardado intermedio y contenido de ayuda conservado (16.708602ms)
|
||||
@metis/companies:test: ✔ perfiles: selección de persona explícita y cuenta opcional con altas independientes (18.953016ms)
|
||||
@metis/companies:test: ✔ perfiles: el puesto fijado aporta su ordenador y no selecciona al responsable como usuario (7.437246ms)
|
||||
@metis/companies:test: ✔ perfiles: requisitos por paso, cuenta opcional y propuesta basada en persona y ordenador (0.295702ms)
|
||||
@metis/companies:test: ✔ perfiles: el nombre sugerido respeta el límite del contrato (0.249402ms)
|
||||
@metis/companies:test: ✔ perfiles: resumen con listas, ordenador único y ausencias representadas por guion (4.491827ms)
|
||||
@metis/companies:test: ✔ perfiles: edición completa con identidad fija aunque el perfil requiera revisión (10.343763ms)
|
||||
@metis/companies:test: ✔ perfiles: orden y página navegables conservan búsqueda y archivados al abrir entorno (46.514185ms)
|
||||
@metis/companies:test: ✔ Personas admite actividad en listado y ficha, con los demás criterios de contexto (12.411407ms)
|
||||
@metis/companies:test: ✔ el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha (1.992517ms)
|
||||
@metis/companies:test: ✔ las funcionalidades archivadas no tienen rutas activas (0.768906ms)
|
||||
@metis/companies:test: ✔ el filtro de plantilla permite abrir listados y fichas de activos y recursos (1.17931ms)
|
||||
@metis/companies:test: ✔ el filtro de plantilla se limita a los inventarios que conocen sus definiciones (2.33352ms)
|
||||
@metis/companies:test: ✔ todas las secciones de compañía tienen una ruta propia y el índice es el resumen (3.790232ms)
|
||||
@metis/companies:test: ✔ los índices de catálogos y los cinco catálogos globales son destinos independientes (0.605605ms)
|
||||
@metis/companies:test: ✔ las configuraciones conservan la compañía y el UUID del perfil (1.561713ms)
|
||||
@metis/companies:test: ✔ las rutas desconocidas y los parámetros inválidos no abren una sección por defecto (3.740132ms)
|
||||
@metis/companies:test: ✔ la ficha de puesto tiene ruta propia, conserva el UUID y rechaza identificadores inválidos (1.21631ms)
|
||||
@metis/companies:test: ✔ dominios: enlaces a cuentas y grupos limitados al entorno de la compañía (1.889016ms)
|
||||
@metis/companies:test: ✔ los parámetros de contexto llegan al consumidor sin crear rutas nuevas ni aceptar claves ajenas (1.980017ms)
|
||||
@metis/companies:test: ✔ catálogo de aplicaciones: UUID global y criterios propios sin compañía ficticia (1.771116ms)
|
||||
@metis/companies:test: ✔ catálogo SO: ruta específica y filtros, sin confundir con aplicaciones (0.855308ms)
|
||||
@metis/companies:test: ✔ la barra comienza desplegada sin navegador o sin una preferencia válida (1.59321ms)
|
||||
@metis/companies:test: ✔ plegar y desplegar persisten la preferencia sin modificar otras claves (5.596634ms)
|
||||
@metis/companies:test: ✔ un fallo de lectura, escritura o acceso a localStorage no bloquea la barra (0.382203ms)
|
||||
@metis/companies:test: ✔ la capacidad del shell fuerza modos sin cambiar la preferencia manual (3.417621ms)
|
||||
@metis/companies:test: ✔ un menú solo libera su modalidad si su presentación pasa a estar disponible fija (0.475902ms)
|
||||
@metis/companies:test: ✔ layout 640px: 0 asociados y 0 instalaciones (12.159604ms)
|
||||
@metis/companies:test: ✔ layout 640px: 0 asociados y 3 instalaciones (11.572099ms)
|
||||
@metis/companies:test: ✔ layout 640px: 1 asociados y 0 instalaciones (4.853941ms)
|
||||
@metis/companies:test: ✔ layout 640px: 1 asociados y 2 instalaciones (5.623348ms)
|
||||
@metis/companies:test: ✔ layout 640px: 2 asociados y 3 instalaciones (8.012269ms)
|
||||
@metis/companies:test: ✔ layout 748px: 0 asociados y 0 instalaciones (1.696314ms)
|
||||
@metis/companies:test: ✔ layout 748px: 0 asociados y 3 instalaciones (5.206144ms)
|
||||
@metis/companies:test: ✔ layout 748px: 1 asociados y 0 instalaciones (4.209536ms)
|
||||
@metis/companies:test: ✔ layout 748px: 1 asociados y 2 instalaciones (5.946551ms)
|
||||
@metis/companies:test: ✔ layout 748px: 2 asociados y 3 instalaciones (11.711ms)
|
||||
@metis/companies:test: ✔ layout 900px: 0 asociados y 0 instalaciones (0.995808ms)
|
||||
@metis/companies:test: ✔ layout 900px: 0 asociados y 3 instalaciones (4.027434ms)
|
||||
@metis/companies:test: ✔ layout 900px: 1 asociados y 0 instalaciones (2.167919ms)
|
||||
@metis/companies:test: ✔ layout 900px: 1 asociados y 2 instalaciones (5.506947ms)
|
||||
@metis/companies:test: ✔ layout 900px: 2 asociados y 3 instalaciones (9.723383ms)
|
||||
@metis/companies:test: ✔ layout 1200px: 0 asociados y 0 instalaciones (2.965225ms)
|
||||
@metis/companies:test: ✔ layout 1200px: 0 asociados y 3 instalaciones (8.680874ms)
|
||||
@metis/companies:test: ✔ layout 1200px: 1 asociados y 0 instalaciones (7.830867ms)
|
||||
@metis/companies:test: ✔ layout 1200px: 1 asociados y 2 instalaciones (9.36598ms)
|
||||
@metis/companies:test: ✔ layout 1200px: 2 asociados y 3 instalaciones (12.842509ms)
|
||||
@metis/companies:test: ✔ layout: dos asociados se reparten por capacidad; sin principal no se fabrican conexiones (8.992576ms)
|
||||
@metis/companies:test: ✔ preview: conserva límites y publica solo cobertura real de fuentes conocidas (0.489104ms)
|
||||
@metis/companies:test: ✔ WD04.4: acción estable de presentación, fuera de las relaciones y contadores, sin solapamientos (169.081042ms)
|
||||
@metis/companies:test: ✔ WD04.5: acciones ordenadas del principal y periférico sin inferir software por categoría (1.56231ms)
|
||||
@metis/companies:test: ✔ WD04.5: lectura sin permisos de edición, carga/error parcial y activos retirados (0.295002ms)
|
||||
@metis/companies:test: ✔ WD04.5: distingue instalación, producto global/inactivo y licencia particular (0.619004ms)
|
||||
@metis/companies:test: ✔ WD04.5: producto particular y gates cerrados no crean acciones administrativas (0.744405ms)
|
||||
@metis/companies:test: ✔ WD04.5: SO global utiliza la referencia pública y su etiqueta específica (0.296702ms)
|
||||
@metis/companies:test: ✔ WD04.5: estados terminales, refetch y cambio de principal o compañía no reutilizan acciones antiguas (0.239502ms)
|
||||
@metis/companies:test: ✔ WD04.5: trigger es/en separado del enlace, tarjetas sin acciones y añadir sin menú (45.090776ms)
|
||||
@metis/companies:test: ✔ dashboard: ordenador y equipamiento actuales, personas por perfiles y archivados separados (12.703509ms)
|
||||
@metis/companies:test: ✔ dashboard: sin responsable sigue mostrando usuarios y un puesto desmantelado no cuenta historia como miembros (1.050909ms)
|
||||
@metis/companies:test: ✔ dashboard: sustituir ordenador resuelve perfiles del nuevo host sin trasladar los anteriores (0.325403ms)
|
||||
@metis/companies:test: ✔ dashboard: referencias ajenas o principal fuera de composición no aportan relaciones (0.258703ms)
|
||||
@metis/companies:test: ✔ pendientes: solo pending, perfiles activos, compañía correcta y deduplicación por familia e identidad (2.28972ms)
|
||||
@metis/companies:test: ✔ último cambio: orden cronológico sin mutación, altas de miembros y campos realmente cambiados (1.290411ms)
|
||||
@metis/companies:test: ✔ equipamiento: principal válido primero, miembros únicos y secundarios en orden declarado (2.28432ms)
|
||||
@metis/companies:test: ✔ equipamiento: activos sin principal no adquieren un principal por su tipo o posición (0.327703ms)
|
||||
@metis/companies:test: ✔ equipamiento: principal fuera de composición se advierte sin añadirlo a miembros (0.257002ms)
|
||||
@metis/companies:test: ✔ equipamiento: referencia ausente o de otra compañía no inventa datos ni marca principal (0.402704ms)
|
||||
@metis/companies:test: ✔ equipamiento: vacío y desmantelado no recuperan miembros del historial (0.277903ms)
|
||||
@metis/companies:test: ✔ equipamiento: averiado sigue asignado y conserva ambos estados originales (0.231902ms)
|
||||
@metis/companies:test: ✔ expansión: todos abiertos inicialmente y plegado independiente de cada perfil (2.379721ms)
|
||||
@metis/companies:test: ✔ refetch conserva plegados y filtros individuales, incluso con error o fuentes incompletas (0.660606ms)
|
||||
@metis/companies:test: ✔ retirada o archivo limpia únicamente el estado del perfil afectado (0.353903ms)
|
||||
@metis/companies:test: ✔ cambio de compañía, puesto u ordenador restablece todos los perfiles abiertos y los filtros (0.332403ms)
|
||||
@metis/companies:test: ✔ orden estable y numérico del resumen y los pendientes, sin mutar datos remotos (8.090969ms)
|
||||
@metis/companies:test: ✔ incluye solo perfiles del ordenador principal, distingue inactivos y deduplica personas (4.241036ms)
|
||||
@metis/companies:test: ✔ puesto inexistente o sin composición no hereda perfiles de otros activos (0.670205ms)
|
||||
@metis/companies:test: ✔ persona: responsabilidad y uso son independientes y solo se consideran puestos y perfiles vigentes (0.737507ms)
|
||||
@metis/companies:test: ✔ ficha de puesto es/en: cabecera, retorno, bloques vacíos y edición modal sin formulario incrustado (75.809262ms)
|
||||
@metis/companies:test: ✔ un UUID de puesto ausente muestra no encontrado sin cabecera ni edición de otro puesto (2.947618ms)
|
||||
@metis/companies:test: ✔ ficha de persona es/en: distingue responsabilidad y uso, y enlaza los puestos (5.084331ms)
|
||||
@metis/companies:test: ✔ ficha: ordenador destacado y periféricos sin repetir, perfiles solo del ordenador y personas deduplicadas (81.550797ms)
|
||||
@metis/companies:test: ✔ personas y perfiles: carga y error no se presentan como listados vacíos (8.655552ms)
|
||||
@metis/companies:test: ✔ ficha: no permite crear perfiles si el ordenador está retirado o el puesto está desactivado (36.809924ms)
|
||||
@metis/companies:test: ✔ historial del puesto es/en: revisiones de más reciente a más antigua con autor y valores de cada momento (8.571952ms)
|
||||
@metis/companies:test: ✔ personas es/en: enlaces a todos sus puestos bajo responsabilidad, sin confundirlos con puestos usados (43.124762ms)
|
||||
@metis/companies:test: ✔ menú de compañía es/en: totales en badges alineados y cero real en secciones vacías (30.405185ms)
|
||||
@metis/companies:test: ✔ ruta de activo de compañía abre la página con información, historial, software y solo sus perfiles (89.102042ms)
|
||||
@metis/companies:test: ✔ equipamiento es/en: badges de principal, técnico y uso conservan su significado (8.098149ms)
|
||||
@metis/companies:test: ✔ equipamiento: inventario fallido no se presenta como vacío y permite reintentar (2.602116ms)
|
||||
@metis/companies:test: ✔ equipamiento: principal inconsistente se explica y no se incorpora como miembro válido (2.237114ms)
|
||||
@metis/companies:test: ✔ personas y perfiles es/en: expansión contextual, enlaces independientes y resumen cero real (7.239744ms)
|
||||
@metis/companies:test: ✔ cabecera es/en: responsable largo o ausente, contexto independiente y mapa con resumen (63.437187ms)
|
||||
@metis/companies:test: ✔ perfiles: lista completa y todos desplegados por defecto, incluso con cincuenta perfiles (71.503033ms)
|
||||
@metis/companies:test: ✔ perfiles: carga, error y datos retenidos no publican un total como actual (4.834929ms)
|
||||
@metis/companies:test: ✔ equipamiento: contador conocido cero, uno, cinco y seis; error conserva filas sin publicar total (11.895972ms)
|
||||
@metis/companies:test: ✔ configuración: cobertura desconocida, revisión y notas permanecen fuera del disclosure técnico (2.395214ms)
|
||||
@metis/companies:test: ✔ perfiles: una actualización en curso conserva el total conocido y anuncia la actividad (1.53051ms)
|
||||
@metis/companies:test: ✔ v2.2: dependencia sin principal conserva mapa y resumen y una explicación principal sin tabla vacía (12.899578ms)
|
||||
@metis/companies:test: ✔ v2.2: un vacío confirmado de perfiles conserva el alta y evita cabeceras de una tabla sin filas (1.185808ms)
|
||||
@metis/companies:test: ✔ v2.2: soporte distingue perfiles en carga, error, ausencia confirmada y persona no resuelta (2.696816ms)
|
||||
@metis/companies:test: ✔ v2.2: referencia de principal no resuelta no publica cero pendientes ni ausencia de perfiles (6.174537ms)
|
||||
@metis/companies:test: ✔ v3: Software conserva los tres estados documentales con la misma representación que las familias (30.234583ms)
|
||||
@metis/companies:test: ✔ v3: estados técnicos de equipamiento conservan principal y asignación en es/en (15.174392ms)
|
||||
@metis/companies:test: ✔ responsable es/en: avatar y nombre abren un único popup y la ausencia no ofrece acciones (4.137825ms)
|
||||
@metis/companies:test: ✔ mapa visual: aísla compañías, deduplica miembros y conserva software y estados reales (11.095368ms)
|
||||
@metis/companies:test: ✔ mapa es/en: principal solo, vacíos confirmados y error de software conservan navegación y gestión (39.040136ms)
|
||||
@metis/companies:test: ✔ mapa: composición vacía y sin principal no fabrica relaciones ni instalaciones (1.520409ms)
|
||||
@metis/companies:test: ✔ contexto de persona: rechaza datos de otra compañía y no ofrece acciones (0.661604ms)
|
||||
@metis/companies:test: ✔ mapa es/en: carga inicial y carga parcial mantienen estados accesibles (23.346741ms)
|
||||
@metis/companies:test: ✔ mapa es/en: leyenda con tres conceptos y cobertura explícita sin totales inventados (1.788511ms)
|
||||
@metis/companies:test: ✔ mapa es/en: revisión de software traducida incluso sin referencia navegable (1.095307ms)
|
||||
@metis/companies:test: ✔ WD04.4 es/en: acción autorizada, vacío accionable y lectura sin permiso (6.092637ms)
|
||||
@metis/companies:test: ✔ perfiles del puesto: configuraciones de todos los activos abiertas y destinos accesibles únicos (13.681782ms)
|
||||
@metis/companies:test: ℹ tests 312
|
||||
@metis/companies:test: ℹ suites 0
|
||||
@metis/companies:test: ℹ pass 310
|
||||
@metis/companies:test: ℹ fail 2
|
||||
@metis/companies:test: ℹ cancelled 0
|
||||
@metis/companies:test: ℹ skipped 0
|
||||
@metis/companies:test: ℹ todo 0
|
||||
@metis/companies:test: ℹ duration_ms 16304.241322
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: ✖ failing tests:
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: test at src/web/companies/shared/companies-page.test.tsx:1:6023
|
||||
@metis/companies:test: ✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (84.738325ms)
|
||||
@metis/companies:test: AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: assert.ok(markup.includes("Personas (1)"))
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: at TestContext.<anonymous> (/home/david/dev/metis-desk/modules/companies/src/web/companies/shared/companies-page.test.tsx:229:10)
|
||||
@metis/companies:test: at async Test.run (node:internal/test_runner/test:1404:7)
|
||||
@metis/companies:test: at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
@metis/companies:test: generatedMessage: true,
|
||||
@metis/companies:test: code: 'ERR_ASSERTION',
|
||||
@metis/companies:test: actual: false,
|
||||
@metis/companies:test: expected: true,
|
||||
@metis/companies:test: operator: '==',
|
||||
@metis/companies:test: diff: 'simple'
|
||||
@metis/companies:test: }
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: test at src/web/companies/shared/companies-page.test.tsx:1:7439
|
||||
@metis/companies:test: ✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (17.906954ms)
|
||||
@metis/companies:test: AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: assert.ok(markup.includes("Esta persona no tiene acceso a Metis Desk"))
|
||||
@metis/companies:test:
|
||||
@metis/companies:test: at TestContext.<anonymous> (/home/david/dev/metis-desk/modules/companies/src/web/companies/shared/companies-page.test.tsx:259:10)
|
||||
@metis/companies:test: at async Test.run (node:internal/test_runner/test:1404:7)
|
||||
@metis/companies:test: at async Test.processPendingSubtests (node:internal/test_runner/test:969:7) {
|
||||
@metis/companies:test: generatedMessage: true,
|
||||
@metis/companies:test: code: 'ERR_ASSERTION',
|
||||
@metis/companies:test: actual: false,
|
||||
@metis/companies:test: expected: true,
|
||||
@metis/companies:test: operator: '==',
|
||||
@metis/companies:test: diff: 'simple'
|
||||
@metis/companies:test: }
|
||||
@metis/companies:test: [ELIFECYCLE] Test failed. See above for more details.
|
||||
ERROR @metis/companies#test: command (/home/david/dev/metis-desk/modules/companies) /home/david/.nvm/versions/node/v24.20.0/bin/pnpm run test exited (1)
|
||||
|
||||
Tasks: 7 successful, 8 total
|
||||
Cached: 0 cached, 8 total
|
||||
Time: 31.657s
|
||||
Failed: @metis/companies#test
|
||||
|
||||
ERROR run failed: command exited (1)
|
||||
49
.artifacts/server-runtime-security/root-build.log
Normal file
49
.artifacts/server-runtime-security/root-build.log
Normal file
@ -0,0 +1,49 @@
|
||||
$ turbo run build
|
||||
• 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 miss, executing d04f9a4c860b60a6
|
||||
@metis/web:build: cache miss, executing f481d8706cf68099
|
||||
@metis/server:build: $ tsup --config tsup.config.ts && pnpm check:build
|
||||
@metis/web:build: $ vite build
|
||||
@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.45 MB
|
||||
@metis/server:build: ESM dist/index.js.map 2.70 MB
|
||||
@metis/server:build: ESM ⚡️ Build success in 712ms
|
||||
@metis/web:build: ✓ 10085 modules transformed.
|
||||
@metis/web:build: rendering chunks...
|
||||
@metis/server:build: $ node --check dist/index.js
|
||||
@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-CuGEDphm.css 256.31 kB │ gzip: 39.27 kB
|
||||
@metis/web:build: dist/assets/workplace-map-canvas-PquwpJLl.js 227.75 kB │ gzip: 74.26 kB
|
||||
@metis/web:build: dist/assets/index-BT6ZPYh6.js 1,849.83 kB │ gzip: 518.15 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 2.51s
|
||||
|
||||
Tasks: 2 successful, 2 total
|
||||
Cached: 0 cached, 2 total
|
||||
Time: 3.525s
|
||||
|
||||
1379
.artifacts/server-runtime-security/root-check.log
Normal file
1379
.artifacts/server-runtime-security/root-check.log
Normal file
File diff suppressed because it is too large
Load Diff
471
.artifacts/server-runtime-security/root-lint.log
Normal file
471
.artifacts/server-runtime-security/root-lint.log
Normal file
@ -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 2261 files in 1081ms. 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.
|
||||
29
.artifacts/server-runtime-security/root-typecheck.log
Normal file
29
.artifacts/server-runtime-security/root-typecheck.log
Normal file
@ -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/shadcn-ui:typecheck: cache miss, executing 3a02880bf260fd89
|
||||
@repo/rdx-utils:typecheck: cache hit, replaying logs e660b2f14d16d88c
|
||||
@repo/rdx-utils:typecheck: $ tsc -p tsconfig.json
|
||||
@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: 6.403s
|
||||
Failed: @repo/shadcn-ui#typecheck
|
||||
|
||||
ERROR run failed: command exited (2)
|
||||
[ELIFECYCLE] Command failed with exit code 2.
|
||||
78
.artifacts/server-runtime-security/runtime-results.json
Normal file
78
.artifacts/server-runtime-security/runtime-results.json
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"evidence": [
|
||||
{
|
||||
"check": "incomplete-production-config",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"check": "compiled-start-health",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"check": "real-bootstrap-and-session",
|
||||
"status": "pass",
|
||||
"cookies": [
|
||||
{
|
||||
"name": "metis.sid",
|
||||
"attributes": [
|
||||
"Path=/",
|
||||
"Expires=Tue, 27 Oct 2026 01:14:09 GMT",
|
||||
"HttpOnly",
|
||||
"Secure",
|
||||
"SameSite=Strict"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"check": "csrf-origin",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"check": "compiled-sharp-logo-photo-invalid",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"check": "real-logout-relogin",
|
||||
"status": "pass",
|
||||
"cookies": [
|
||||
{
|
||||
"name": "metis.sid",
|
||||
"attributes": [
|
||||
"Path=/",
|
||||
"Expires=Thu, 01 Jan 1970 00:00:00 GMT",
|
||||
"HttpOnly",
|
||||
"Secure",
|
||||
"SameSite=Strict"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"check": "compiled-graceful-production-stop",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"check": "development-http",
|
||||
"status": "pass",
|
||||
"cookies": [
|
||||
{
|
||||
"name": "metis.sid",
|
||||
"attributes": [
|
||||
"Path=/",
|
||||
"Expires=Tue, 27 Oct 2026 01:14:11 GMT",
|
||||
"HttpOnly",
|
||||
"SameSite=Strict"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database": "metis_desk_test",
|
||||
"tlsCertificateValidation": true,
|
||||
"fixtures": {
|
||||
"companies": 1,
|
||||
"people": 1
|
||||
},
|
||||
"productionTouched": false
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"output": "",
|
||||
"errors": "{\"event\":\"server_start_failed\",\"message\":\"Invalid server configuration: TRUSTED_PROXY_CIDRS requires explicit proxy IP addresses or CIDRs; mandatory in production\"}\n"
|
||||
}
|
||||
1
.artifacts/server-runtime-security/server-typecheck.log
Normal file
1
.artifacts/server-runtime-security/server-typecheck.log
Normal file
@ -0,0 +1 @@
|
||||
$ tsc -p tsconfig.json
|
||||
12
.artifacts/server-runtime-security/sharp.log
Normal file
12
.artifacts/server-runtime-security/sharp.log
Normal file
@ -0,0 +1,12 @@
|
||||
✔ PNG, JPEG y WebP se normalizan sin recorte y sin metadatos a un avatar ligero (61.948521ms)
|
||||
✔ se rechazan archivos vacíos, corruptos, SVG y GIF aunque simulen un MIME válido (0.905905ms)
|
||||
✔ se rechazan excesos de peso y dimensión antes de almacenar (2.01031ms)
|
||||
✔ los PNG animados se rechazan antes de decodificarlos (1.196706ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 403.151203
|
||||
38
.artifacts/server-runtime-security/so-closure.log
Normal file
38
.artifacts/server-runtime-security/so-closure.log
Normal file
@ -0,0 +1,38 @@
|
||||
▶ Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real
|
||||
✔ B3 — evolución SQL doble conserva campos, valores, personalizaciones y revisiones (0.772304ms)
|
||||
✔ DB-SO-01 crear SO global (46.028737ms)
|
||||
✔ DB-SO-02 crear aplicación global mantiene su tipo (44.90923ms)
|
||||
✔ DB-SO-03 licencia global rechazada sin raíz persistida (23.088718ms)
|
||||
✔ DB-SO-04 instalación válida desde A (127.733456ms)
|
||||
✔ DB-SO-05 mismo producto desde B, instalación y licencia independientes (176.271701ms)
|
||||
✔ DB-SO-06 dos activos de A usan el mismo SO global (349.856578ms)
|
||||
✔ DB-SO-07 particulares y globales equivalentes conservan UUID e instalaciones (128.298352ms)
|
||||
✔ DB-SO-08 licencia de A válida y particular (106.066739ms)
|
||||
✔ DB-SO-09 licencia de A no puede vincularse desde B (157.025598ms)
|
||||
✔ DB-SO-10 SO excluido de ApplicationConfiguration (119.207306ms)
|
||||
✔ DB-SO-11 desactivar conserva instalaciones usadas (45.964733ms)
|
||||
✔ DB-SO-12 inactivo bloquea nueva instalación (99.866591ms)
|
||||
✔ DB-SO-13 historial y resolver conservan SO inactivo (30.539321ms)
|
||||
✔ DB-SO-14 versión instalada genera revisión sin cambiar producto (354.319274ms)
|
||||
✔ DB-SO-15 destino de instalación inmutable (212.150241ms)
|
||||
✔ DB-SO-16 retirada de instalación de producto inactivo (495.50569ms)
|
||||
✔ DB-SO-17 replay de creación no duplica ni reactiva (29.001209ms)
|
||||
✔ DB-SO-18 activar/desactivar preserva UUID, historial y referencias (254.702536ms)
|
||||
✔ DB-SO-19 solo stock: copia y otros tipos no adquieren globalidad (257.351156ms)
|
||||
✔ DB-SO-20 particular anterior incompleto sigue siendo editable y local (129.748936ms)
|
||||
✔ C1 — equivalentes concurrentes esperan el lock de catálogo y solo uno persiste (227.933739ms)
|
||||
✔ C2 — desactivación confirma primero y asociación revalida después de esperar (361.285186ms)
|
||||
✔ C3 — identidad, instalación, metadatos y desactivación concurrentes (254.404421ms)
|
||||
✔ C4a — fallo tras escribir raíz global revierte raíz, características y revisiones (82.213889ms)
|
||||
✔ C4b — fallo en revisión de instalación revierte instalación completa (151.297283ms)
|
||||
✔ D — contratos v2/v3, propósito, capabilities y autorización (273.826049ms)
|
||||
✔ F — cerrar gates conserva lectura, historial y retirada legítima (1512.466505ms)
|
||||
✔ Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real (35512.142313ms)
|
||||
ℹ tests 29
|
||||
ℹ suites 0
|
||||
ℹ pass 29
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 36837.510665
|
||||
72
.artifacts/server-runtime-security/so-regressions.log
Normal file
72
.artifacts/server-runtime-security/so-regressions.log
Normal file
@ -0,0 +1,72 @@
|
||||
✔ HTTP real: estado, contrato, errores y trazas sin datos sensibles (77.204597ms)
|
||||
✔ HTTP real: entrada JSON inválida y errores internos conservan un contrato seguro (29.325551ms)
|
||||
✔ MariaDB y HTTP: pertenencias, controladores, transiciones, aislamiento e integridad (2235.600821ms)
|
||||
✔ el puerto usa un valor local por defecto y acepta los límites válidos (0.614107ms)
|
||||
✔ el arranque rechaza puertos inválidos sin revelar el valor recibido (0.86331ms)
|
||||
✔ proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos (0.399705ms)
|
||||
✔ proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores (0.747308ms)
|
||||
✔ MariaDB: dominios, contexto atómico, concurrencia, integridad y ciclo de vida (852.597667ms)
|
||||
✔ HTTP infraestructura: sesión, pertenencia, validación, identidad e idempotencia (30.130744ms)
|
||||
✔ diagnóstico: causas de Sequelize, ciclos y valores lanzados que no son Error (0.607207ms)
|
||||
✔ diagnóstico: mensajes multilínea, propiedades y cadenas largas no se vuelcan (0.504706ms)
|
||||
✔ contextos de servidor: sesión vigente, ámbito existente, copias y revocación (0.996411ms)
|
||||
▶ MariaDB: publicaciones SMB, historial, proveedor y HTTP autenticado
|
||||
✔ registro y cardinalidad; publicador desconocido y distintos tipos sin AD (365.307283ms)
|
||||
✔ traslado atómico, replay estable y finalización identificada sin tocar accesos (128.542392ms)
|
||||
✔ concurrencia: altas iguales, publicadores distintos, traslados y finalización (181.515477ms)
|
||||
✔ restricciones SQL y protección de borrado también después de finalizar (130.366302ms)
|
||||
✔ fallo al insertar sucesor revierte el cierre anterior (110.985298ms)
|
||||
✔ proveedor: SQL keyset, orientación, historial y cursores ligados a consulta (28.274853ms)
|
||||
✔ resolución SMB por lotes: no confunde dominios, recursos ordinarios o compañías (178.205559ms)
|
||||
✔ estados y desactivación concurrente conservan publicaciones documentadas (618.634608ms)
|
||||
✔ UI 3.5: selector remoto filtra en SQL y pagina sin hidratar el inventario (1472.051801ms)
|
||||
✔ HTTP: sesión, CSRF, validación, idempotencia y consultas paginadas (136.79642ms)
|
||||
✔ MariaDB: publicaciones SMB, historial, proveedor y HTTP autenticado (5029.58801ms)
|
||||
▶ MariaDB: SMB, unicidad vigente, historial, diagnóstico e integridad
|
||||
✔ alta, presentación, lector público y aislamiento (51.896771ms)
|
||||
✔ conflicto funcional, rollback, alias e inactivos (171.434132ms)
|
||||
✔ edición atómica, UUID estable, idempotencia y revisión de UNC (107.796502ms)
|
||||
✔ desactivación libera dirección y reactivación conflictiva conserva estado (208.33645ms)
|
||||
✔ concurrencia: una sola alta y ninguna raíz huérfana (87.376051ms)
|
||||
✔ restricciones SQL: compañía, 1:1, protocolo y unicidad (66.232093ms)
|
||||
✔ legado: ausente, inválido, válido, duplicado y candidato access; diagnóstico sin escritura (505.942361ms)
|
||||
✔ integración access existente y cambio de UNC conservan contexto y referencias (205.614119ms)
|
||||
✔ FolderMapping conserva UNC, letra e historial y detecta dirección desactualizada (420.469906ms)
|
||||
✔ HTTP autenticado: contrato SMB y diagnóstico acotado sin aceptar parámetros (71.296427ms)
|
||||
✔ borrado sin referencias limpia proyección; una FK externa impide borrado (237.845557ms)
|
||||
✔ MariaDB: SMB, unicidad vigente, historial, diagnóstico e integridad (2874.428818ms)
|
||||
▶ MariaDB: nuevas carpetas SMB integradas con accesos sin adoptar históricos
|
||||
✔ alta automática: misma identidad, ninguna dependencia de dominio o publicador (11.477685ms)
|
||||
✔ renombrar el tipo de acceso conserva su UUID para las siguientes altas (65.860683ms)
|
||||
✔ el cliente no puede imponer buzón, otro tipo ni contexto; se revierte el tipo preparado (93.529686ms)
|
||||
✔ renombrado, UNC y estados sincronizan valores y UUID de todas las revisiones (136.588ms)
|
||||
✔ necesidad y concesiones AD y NAS local: cobertura documental y borrado protegido (1319.027689ms)
|
||||
✔ fallo después de escribir acceso revierte Resource, SMB, AccessItem y tipo (40.417492ms)
|
||||
✔ carpeta anterior sin integración: lectura, edición y estado no la adoptan (170.026129ms)
|
||||
✔ MariaDB: nuevas carpetas SMB integradas con accesos sin adoptar históricos (2588.791754ms)
|
||||
✔ MariaDB: las plantillas propagan estructura en todas las compañías de forma atómica e idempotente (1637.984212ms)
|
||||
▶ MariaDB: proveedor real, lotes, seguridad, SQL paginado y transiciones de pertenencias
|
||||
✔ registro estático y consultas vacías desde ambos extremos (6.826136ms)
|
||||
✔ directa, inversa, either, roles y UUID canónicos sin duplicados (46.840845ms)
|
||||
✔ keyset SQL: páginas de uno, fin y cursores ligados a consulta (30.512559ms)
|
||||
✔ lotes reales sin N+1, orden, repetidos, estados y referencias inaccesibles (23.04302ms)
|
||||
✔ traslado y fin conservan identidad, estado y rol histórico (42.902724ms)
|
||||
✔ ámbito, contextos falsificados, sesión revocada y acceso inverso (21.26291ms)
|
||||
✔ EXPLAIN real e índices disponibles para ambos extremos (6.220933ms)
|
||||
✔ MariaDB: proveedor real, lotes, seguridad, SQL paginado y transiciones de pertenencias (1553.285028ms)
|
||||
ℹ {"column":"assetId","includeEnded":false,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"assetId","includeEnded":false,"forcedIndexPlan":[{"key":"directory_membership_asset","Extra":"Using index condition; Using where"}]}
|
||||
ℹ {"column":"assetId","includeEnded":true,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"assetId","includeEnded":true,"forcedIndexPlan":[{"key":"directory_membership_asset","Extra":"Using index condition"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":false,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_fk,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":false,"forcedIndexPlan":[{"key":"directory_membership_domain_page","Extra":"Using index condition; Using where"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":true,"plan":[{"id":1,"select_type":"SIMPLE","table":"asset_directory_memberships","type":"range","possible_keys":"PRIMARY,directory_membership_identity_scope,directory_membership_asset,directory_membership_domain,directory_membership_domain_fk,directory_membership_domain_page","key":"PRIMARY","key_len":"144","ref":null,"rows":"4","Extra":"Using where"}]}
|
||||
ℹ {"column":"domainResourceId","includeEnded":true,"forcedIndexPlan":[{"key":"directory_membership_domain_page","Extra":"Using index condition"}]}
|
||||
ℹ tests 52
|
||||
ℹ suites 0
|
||||
ℹ pass 52
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 18273.692638
|
||||
@ -6,8 +6,9 @@
|
||||
"scripts": {
|
||||
"dev": "node --env-file=../../.env --import tsx --watch src/index.ts",
|
||||
"typecheck": "tsc -p tsconfig.json",
|
||||
"test": "node --import tsx --test src/app.test.ts src/infrastructure.http.test.ts src/config/env-schema.test.ts src/infrastructure/http/http-error-diagnostic.test.ts src/infrastructure/http/relationship-read-access.test.ts && DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-concurrency=1 src/asset-inventory.integration.test.ts src/template-propagation.integration.test.ts src/infrastructure-inventory.integration.test.ts src/sites.integration.test.ts src/identity.test.ts src/asset-directory-memberships.integration.test.ts src/typed-relationships.integration.test.ts src/windows-domains-ui.integration.test.ts",
|
||||
"build": "tsup --config tsup.config.ts",
|
||||
"test": "pnpm test:http && DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-concurrency=1 src/asset-inventory.integration.test.ts src/template-propagation.integration.test.ts src/infrastructure-inventory.integration.test.ts src/sites.integration.test.ts src/identity.test.ts src/asset-directory-memberships.integration.test.ts src/typed-relationships.integration.test.ts src/windows-domains-ui.integration.test.ts",
|
||||
"build": "tsup --config tsup.config.ts && pnpm check:build",
|
||||
"check:build": "node --check dist/index.js",
|
||||
"start": "node --env-file=../../.env dist/index.js",
|
||||
"test:directory-domains": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/directory-domains.integration.test.ts",
|
||||
"test:directory-memberships": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/asset-directory-memberships.integration.test.ts",
|
||||
@ -16,22 +17,24 @@
|
||||
"test:network-shares": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/network-shares.integration.test.ts",
|
||||
"test:smb-access": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/smb-access.integration.test.ts",
|
||||
"test:network-share-publications": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/network-share-publications.integration.test.ts",
|
||||
"test:local-identities-folder-mapping": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/local-identities-folder-mapping.integration.test.ts"
|
||||
"test:local-identities-folder-mapping": "DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test --test-isolation=none src/local-identities-folder-mapping.integration.test.ts",
|
||||
"test:http": "node --import tsx --test src/app.test.ts src/infrastructure.http.test.ts src/config/env-schema.test.ts src/infrastructure/http/http-error-diagnostic.test.ts src/infrastructure/http/relationship-read-access.test.ts src/proxy-sessions.http.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@metis/assets": "workspace:*",
|
||||
"@metis/companies": "workspace:*",
|
||||
"@metis/core": "workspace:*",
|
||||
"@metis/identity": "workspace:*",
|
||||
"@metis/relationship-contracts": "workspace:*",
|
||||
"@metis/resources": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"express": "4.22.1",
|
||||
"express-session": "1.19.0",
|
||||
"helmet": "8.1.0",
|
||||
"mariadb": "3.5.4",
|
||||
"sequelize": "6.37.8",
|
||||
"zod": "4.3.6",
|
||||
"@metis/relationship-contracts": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*"
|
||||
"sharp": "0.35.5",
|
||||
"zod": "4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
|
||||
@ -12,10 +12,15 @@ import { logHttpRequest } from "./infrastructure/http/http-logger";
|
||||
|
||||
export function createApp(
|
||||
logger: HttpLogger = logHttpRequest,
|
||||
identity?: { middleware: RequestHandler; register: (app: Application) => void }
|
||||
identity?: { middleware: RequestHandler; register: (app: Application) => void },
|
||||
trustedProxyCidrs: readonly string[] = []
|
||||
) {
|
||||
const app = express();
|
||||
|
||||
if (trustedProxyCidrs.length > 0) {
|
||||
app.set("trust proxy", [...trustedProxyCidrs]);
|
||||
}
|
||||
|
||||
app.disable("x-powered-by");
|
||||
app.use(helmet());
|
||||
app.use((request, response, next) => {
|
||||
@ -37,6 +42,24 @@ export function createApp(
|
||||
});
|
||||
|
||||
app.use(express.json({ limit: "64kb" }));
|
||||
app.use((request, response, next) => {
|
||||
const protocol = request.headers["x-forwarded-proto"];
|
||||
const occurrences = request.rawHeaders.filter(
|
||||
(value, index) => index % 2 === 0 && value.toLowerCase() === "x-forwarded-proto"
|
||||
).length;
|
||||
|
||||
if (
|
||||
protocol !== undefined &&
|
||||
(occurrences !== 1 || (protocol !== "http" && protocol !== "https"))
|
||||
) {
|
||||
response
|
||||
.status(400)
|
||||
.json({ code: "INVALID_REQUEST", requestId: response.getHeader("x-request-id") });
|
||||
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
if (identity) {
|
||||
app.use(identity.middleware);
|
||||
identity.register(app);
|
||||
|
||||
@ -15,3 +15,43 @@ test("el arranque rechaza puertos inválidos sin revelar el valor recibido", ()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("proxy: sin configuración no se confía en cabeceras y producción exige proxies explícitos", () => {
|
||||
assert.deepEqual(readServerEnvironment({}).TRUSTED_PROXY_CIDRS, []);
|
||||
assert.throws(() => readServerEnvironment({ NODE_ENV: "production" }), /TRUSTED_PROXY_CIDRS/);
|
||||
assert.deepEqual(
|
||||
readServerEnvironment({
|
||||
NODE_ENV: "production",
|
||||
TRUSTED_PROXY_CIDRS: "192.0.2.4/32, 2001:db8::4/128",
|
||||
}).TRUSTED_PROXY_CIDRS,
|
||||
["192.0.2.4/32", "2001:db8::4/128"]
|
||||
);
|
||||
});
|
||||
|
||||
test("proxy: rechaza confianza universal, saltos, alias, hosts y redes inválidas sin revelar valores", () => {
|
||||
for (const value of [
|
||||
"true",
|
||||
"1",
|
||||
"loopback",
|
||||
"localhost",
|
||||
"0.0.0.0/0",
|
||||
"::/0",
|
||||
"::ffff:0:0/96",
|
||||
"192.0.2.4/33",
|
||||
"2001:db8::4/129",
|
||||
"192.0.2.4,",
|
||||
"192.0.2.4/NaN",
|
||||
"secret-proxy.example",
|
||||
]) {
|
||||
assert.throws(
|
||||
() => readServerEnvironment({ TRUSTED_PROXY_CIDRS: value }),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /TRUSTED_PROXY_CIDRS/);
|
||||
assert.equal(error.message.includes(value), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { TrustedProxyCidrsSchema } from "./trusted-proxy-cidrs";
|
||||
|
||||
const ServerEnvironmentSchema = z.object({
|
||||
PORT: z.coerce.number().int().min(1).max(65535),
|
||||
});
|
||||
@ -12,5 +14,13 @@ export function readServerEnvironment(environment: NodeJS.ProcessEnv) {
|
||||
throw new Error("Invalid server configuration: PORT must be between 1 and 65535");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
const proxies = TrustedProxyCidrsSchema.safeParse(environment.TRUSTED_PROXY_CIDRS ?? "");
|
||||
|
||||
if (!proxies.success || (environment.NODE_ENV === "production" && proxies.data.length === 0)) {
|
||||
throw new Error(
|
||||
"Invalid server configuration: TRUSTED_PROXY_CIDRS requires explicit proxy IP addresses or CIDRs; mandatory in production"
|
||||
);
|
||||
}
|
||||
|
||||
return { ...result.data, TRUSTED_PROXY_CIDRS: proxies.data };
|
||||
}
|
||||
|
||||
37
apps/server/src/config/trusted-proxy-cidrs.ts
Normal file
37
apps/server/src/config/trusted-proxy-cidrs.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { isIP } from "node:net";
|
||||
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/** Lista explícita; no acepta alias de proxy-addr, número de saltos ni confianza universal. */
|
||||
export const TrustedProxyCidrsSchema = z.string().transform((value, context) => {
|
||||
if (value === "") {
|
||||
return [];
|
||||
}
|
||||
const addresses = value.split(",").map((item) => item.trim());
|
||||
const valid =
|
||||
addresses.length <= 32 &&
|
||||
addresses.every((item) => {
|
||||
const [address, prefix, extra] = item.split("/");
|
||||
const version = address && !address.includes("%") ? isIP(address) : 0;
|
||||
const mappedIpv4 =
|
||||
version === 6 && new URL(`http://[${address}]`).hostname.startsWith("[::ffff:");
|
||||
|
||||
return (
|
||||
version !== 0 &&
|
||||
extra === undefined &&
|
||||
(prefix === undefined ||
|
||||
(/^\d+$/.test(prefix) &&
|
||||
Number(prefix) > 0 &&
|
||||
(!mappedIpv4 || Number(prefix) > 96) &&
|
||||
Number(prefix) <= (version === 4 ? 32 : 128)))
|
||||
);
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
context.addIssue({ code: "custom", message: "Lista de proxies inválida." });
|
||||
|
||||
return z.NEVER;
|
||||
}
|
||||
|
||||
return [...new Set(addresses)];
|
||||
});
|
||||
@ -10,9 +10,13 @@ async function start(): Promise<void> {
|
||||
|
||||
try {
|
||||
const identity = await createIdentityRuntime(database, databaseEnvironment);
|
||||
const server = createApp(undefined, identity).listen(environment.PORT, "127.0.0.1", () => {
|
||||
console.info(JSON.stringify({ event: "server_started", port: environment.PORT }));
|
||||
});
|
||||
const server = createApp(undefined, identity, environment.TRUSTED_PROXY_CIDRS).listen(
|
||||
environment.PORT,
|
||||
"127.0.0.1",
|
||||
() => {
|
||||
console.info(JSON.stringify({ event: "server_started", port: environment.PORT }));
|
||||
}
|
||||
);
|
||||
|
||||
server.on("error", (error) => {
|
||||
identity.stop();
|
||||
|
||||
173
apps/server/src/local-tls-proxy.fixture.ts
Normal file
173
apps/server/src/local-tls-proxy.fixture.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { once } from "node:events";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "node:http";
|
||||
import { request as httpRequest } from "node:http";
|
||||
import { createServer as createHttpsServer, request as httpsRequest } from "node:https";
|
||||
import type { Server } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
export async function localTlsCertificate() {
|
||||
const directory = await mkdtemp(join(tmpdir(), "metis-tls-"));
|
||||
|
||||
try {
|
||||
await promisify(execFile)(
|
||||
"openssl",
|
||||
[
|
||||
"req",
|
||||
"-x509",
|
||||
"-newkey",
|
||||
"rsa:2048",
|
||||
"-nodes",
|
||||
"-days",
|
||||
"1",
|
||||
"-subj",
|
||||
"/CN=localhost",
|
||||
"-addext",
|
||||
"subjectAltName=DNS:localhost,IP:127.0.0.1",
|
||||
"-keyout",
|
||||
join(directory, "key.pem"),
|
||||
"-out",
|
||||
join(directory, "cert.pem"),
|
||||
],
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
|
||||
return {
|
||||
key: await readFile(join(directory, "key.pem")),
|
||||
cert: await readFile(join(directory, "cert.pem")),
|
||||
close: () => rm(directory, { recursive: true, force: true }),
|
||||
};
|
||||
} catch (error) {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function listenLocal(server: Server) {
|
||||
server.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
const address = server.address();
|
||||
|
||||
if (!address || typeof address === "string") {
|
||||
throw new Error("No se obtuvo un puerto local de prueba.");
|
||||
}
|
||||
|
||||
return address.port;
|
||||
}
|
||||
|
||||
export function closeLocal(server: Server) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
|
||||
/** Proxy exclusivamente de prueba: sobrescribe los encabezados reenviados del cliente. */
|
||||
export async function localTlsProxy(
|
||||
backendPort: number,
|
||||
sourceAddress: string,
|
||||
certificate: Awaited<ReturnType<typeof localTlsCertificate>>
|
||||
) {
|
||||
const proxy = createHttpsServer(certificate, (request, response) => {
|
||||
const headers = Object.fromEntries(
|
||||
Object.entries(request.headers).filter(([key]) => key !== "forwarded")
|
||||
);
|
||||
|
||||
headers["x-forwarded-proto"] = "https";
|
||||
headers["x-forwarded-for"] = request.socket.remoteAddress ?? "";
|
||||
headers["x-forwarded-host"] = request.headers.host ?? "";
|
||||
const upstream = httpRequest(
|
||||
{
|
||||
hostname: "127.0.0.1",
|
||||
port: backendPort,
|
||||
localAddress: sourceAddress,
|
||||
path: request.url ?? "/",
|
||||
method: request.method ?? "GET",
|
||||
headers,
|
||||
agent: false,
|
||||
timeout: 10000,
|
||||
},
|
||||
(incoming) => {
|
||||
response.writeHead(incoming.statusCode ?? 502, incoming.headers);
|
||||
incoming.on("error", () => response.destroy());
|
||||
incoming.pipe(response);
|
||||
}
|
||||
);
|
||||
|
||||
upstream.on("timeout", () => upstream.destroy(new Error("Timeout del proxy local.")));
|
||||
upstream.on("error", () => {
|
||||
if (!response.headersSent) {
|
||||
response.writeHead(502);
|
||||
}
|
||||
response.end();
|
||||
});
|
||||
request.on("error", () => upstream.destroy());
|
||||
request.pipe(upstream);
|
||||
});
|
||||
const port = await listenLocal(proxy);
|
||||
|
||||
return { origin: `https://127.0.0.1:${port}`, close: () => closeLocal(proxy) };
|
||||
}
|
||||
|
||||
export function localRequest(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string;
|
||||
headers?: OutgoingHttpHeaders;
|
||||
body?: Buffer;
|
||||
localAddress?: string;
|
||||
ca?: Buffer;
|
||||
} = {}
|
||||
) {
|
||||
return new Promise<{ status: number; headers: IncomingHttpHeaders; body: Buffer }>(
|
||||
(resolve, reject) => {
|
||||
const request = (url.startsWith("https:") ? httpsRequest : httpRequest)(
|
||||
url,
|
||||
{
|
||||
method: options.method ?? "GET",
|
||||
headers: options.headers ?? {},
|
||||
...(options.localAddress ? { localAddress: options.localAddress } : {}),
|
||||
...(options.ca ? { ca: options.ca } : {}),
|
||||
agent: false,
|
||||
timeout: 10000,
|
||||
},
|
||||
(response) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
response.on("data", (chunk: unknown) => {
|
||||
if (Buffer.isBuffer(chunk)) {
|
||||
chunks.push(chunk);
|
||||
} else {
|
||||
reject(new Error("Respuesta binaria de prueba inválida."));
|
||||
}
|
||||
});
|
||||
response.on("end", () =>
|
||||
resolve({
|
||||
status: response.statusCode ?? 0,
|
||||
headers: response.headers,
|
||||
body: Buffer.concat(chunks),
|
||||
})
|
||||
);
|
||||
response.on("error", reject);
|
||||
}
|
||||
);
|
||||
|
||||
request.on("timeout", () => request.destroy(new Error("Timeout HTTP de prueba.")));
|
||||
request.on("error", reject);
|
||||
request.end(options.body);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Solo devuelve atributos: nunca incluir el valor de cookie en evidencias. */
|
||||
export function cookieAttributes(headers: IncomingHttpHeaders) {
|
||||
return (headers["set-cookie"] ?? []).map((value) => ({
|
||||
name: value.split("=")[0],
|
||||
attributes: value
|
||||
.split(";")
|
||||
.slice(1)
|
||||
.map((part) => part.trim()),
|
||||
}));
|
||||
}
|
||||
159
apps/server/src/proxy-sessions.http.test.ts
Normal file
159
apps/server/src/proxy-sessions.http.test.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { createServer } from "node:http";
|
||||
import { test } from "node:test";
|
||||
|
||||
import session from "express-session";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createApp } from "./app";
|
||||
import { readServerEnvironment } from "./config/env-schema";
|
||||
import {
|
||||
closeLocal,
|
||||
cookieAttributes,
|
||||
listenLocal,
|
||||
localRequest,
|
||||
localTlsCertificate,
|
||||
localTlsProxy,
|
||||
} from "./local-tls-proxy.fixture";
|
||||
|
||||
const StateSchema = z.object({ secure: z.boolean(), ip: z.string() });
|
||||
|
||||
test("TLS HTTP: política explícita, proxy real y cookies sin cabeceras falsificadas", async (t) => {
|
||||
const certificate = await localTlsCertificate();
|
||||
const config = readServerEnvironment({
|
||||
NODE_ENV: "production",
|
||||
TRUSTED_PROXY_CIDRS: "127.0.0.2/32",
|
||||
});
|
||||
const app = createApp(
|
||||
() => {},
|
||||
{
|
||||
middleware: session({
|
||||
name: "metis.sid",
|
||||
secret: randomBytes(48).toString("hex"),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: true, httpOnly: true, sameSite: "strict", maxAge: 8 * 60 * 60 * 1000 },
|
||||
}),
|
||||
register: (instance) =>
|
||||
instance.get("/session", (request, response) => {
|
||||
request.session.authVersion = (request.session.authVersion ?? 0) + 1;
|
||||
response.json({ secure: request.secure, ip: request.ip });
|
||||
}),
|
||||
},
|
||||
config.TRUSTED_PROXY_CIDRS
|
||||
);
|
||||
const server = createServer(app);
|
||||
const port = await listenLocal(server);
|
||||
const trusted = await localTlsProxy(port, "127.0.0.2", certificate);
|
||||
const untrusted = await localTlsProxy(port, "127.0.0.3", certificate);
|
||||
|
||||
try {
|
||||
await t.test(
|
||||
"TLS-01/04: HTTPS real por proxy confiable emite Secure/HttpOnly/SameSite/Path/Expires",
|
||||
async () => {
|
||||
const response = await localRequest(`${trusted.origin}/session`, { ca: certificate.cert });
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(StateSchema.parse(JSON.parse(response.body.toString())).secure, true);
|
||||
const [cookie] = cookieAttributes(response.headers);
|
||||
|
||||
assert.ok(cookie);
|
||||
for (const attribute of ["Secure", "HttpOnly", "SameSite=Strict", "Path=/"]) {
|
||||
assert.ok(cookie.attributes.includes(attribute));
|
||||
}
|
||||
assert.ok(cookie.attributes.some((value) => value.startsWith("Expires=")));
|
||||
const expires = cookie.attributes.find((value) => value.startsWith("Expires="));
|
||||
const date = response.headers.date;
|
||||
|
||||
assert.ok(expires && date);
|
||||
assert.ok(
|
||||
Math.abs(Date.parse(expires.slice(8)) - Date.parse(date) - 8 * 60 * 60 * 1000) <= 1000
|
||||
);
|
||||
assert.equal(
|
||||
cookie.attributes.some((value) => value.startsWith("Domain=")),
|
||||
false
|
||||
);
|
||||
}
|
||||
);
|
||||
for (const [id, headers] of [
|
||||
["TLS-02 HTTP directo", {}],
|
||||
["TLS-03 XFP falsificado", { "x-forwarded-proto": "https" }],
|
||||
] satisfies [string, Record<string, string>][]) {
|
||||
await t.test(id, async () => {
|
||||
const response = await localRequest(`http://127.0.0.1:${port}/session`, { headers });
|
||||
|
||||
assert.equal(StateSchema.parse(JSON.parse(response.body.toString())).secure, false);
|
||||
assert.deepEqual(cookieAttributes(response.headers), []);
|
||||
});
|
||||
}
|
||||
await t.test("TLS-05: proxy TLS no confiable no eleva seguridad", async () => {
|
||||
const response = await localRequest(`${untrusted.origin}/session`, { ca: certificate.cert });
|
||||
|
||||
assert.equal(StateSchema.parse(JSON.parse(response.body.toString())).secure, false);
|
||||
assert.deepEqual(cookieAttributes(response.headers), []);
|
||||
});
|
||||
await t.test(
|
||||
"TLS-06: proxy confiable informa HTTP y no emite cookie de producción",
|
||||
async () => {
|
||||
const response = await localRequest(`http://127.0.0.1:${port}/session`, {
|
||||
localAddress: "127.0.0.2",
|
||||
headers: { "x-forwarded-proto": "http" },
|
||||
});
|
||||
|
||||
assert.equal(StateSchema.parse(JSON.parse(response.body.toString())).secure, false);
|
||||
assert.deepEqual(cookieAttributes(response.headers), []);
|
||||
}
|
||||
);
|
||||
await t.test(
|
||||
"TLS-07: cabeceras duplicadas y contradictorias se rechazan antes de sesión",
|
||||
async () => {
|
||||
for (const value of [
|
||||
["https", "http"],
|
||||
["https", "https"],
|
||||
"https, http",
|
||||
"http, https",
|
||||
"ftp",
|
||||
]) {
|
||||
const response = await localRequest(`http://127.0.0.1:${port}/session`, {
|
||||
localAddress: "127.0.0.2",
|
||||
headers: { "x-forwarded-proto": value },
|
||||
});
|
||||
|
||||
assert.equal(response.status, 400);
|
||||
assert.deepEqual(cookieAttributes(response.headers), []);
|
||||
}
|
||||
}
|
||||
);
|
||||
await t.test(
|
||||
"TLS-08: el proxy sobrescribe la cadena inyectada; no hay confianza por número de saltos",
|
||||
async () => {
|
||||
const response = await localRequest(`${trusted.origin}/session`, {
|
||||
ca: certificate.cert,
|
||||
headers: { "x-forwarded-proto": "http", "x-forwarded-for": "192.0.2.8, 127.0.0.2" },
|
||||
});
|
||||
const state = StateSchema.parse(JSON.parse(response.body.toString()));
|
||||
|
||||
assert.equal(state.secure, true);
|
||||
assert.equal(state.ip, "127.0.0.1");
|
||||
}
|
||||
);
|
||||
await t.test("TLS-12: emisión posterior conserva atributos Secure", async () => {
|
||||
const first = await localRequest(`${trusted.origin}/session`, { ca: certificate.cert });
|
||||
const cookie = first.headers["set-cookie"]?.[0]?.split(";")[0];
|
||||
|
||||
assert.ok(cookie);
|
||||
const next = await localRequest(`${trusted.origin}/session`, {
|
||||
ca: certificate.cert,
|
||||
headers: { cookie },
|
||||
});
|
||||
|
||||
assert.ok(cookieAttributes(next.headers)[0]?.attributes.includes("Secure"));
|
||||
});
|
||||
} finally {
|
||||
await trusted.close();
|
||||
await untrusted.close();
|
||||
await closeLocal(server);
|
||||
await certificate.close();
|
||||
}
|
||||
});
|
||||
378
apps/server/src/server-runtime.integration.test.ts
Normal file
378
apps/server/src/server-runtime.integration.test.ts
Normal file
@ -0,0 +1,378 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { access } from "node:fs/promises";
|
||||
import { createServer } from "node:http";
|
||||
import { resolve } from "node:path";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { CompanyLogoResponseSchema, CompanyResponseSchema } from "@metis/companies/common";
|
||||
import { SessionResponseSchema } from "@metis/identity/common";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { PersonPhotoResponseSchema } from "../../../modules/companies/src/common/person-photo";
|
||||
|
||||
import {
|
||||
closeLocal,
|
||||
cookieAttributes,
|
||||
listenLocal,
|
||||
localRequest,
|
||||
localTlsCertificate,
|
||||
localTlsProxy,
|
||||
} from "./local-tls-proxy.fixture";
|
||||
import { disposableScopeEnvironment, writeScopeEvidence } from "./resource-scope.preflight";
|
||||
|
||||
async function startCompiled(environment: NodeJS.ProcessEnv, port: number) {
|
||||
const entry = resolve("apps/server/dist/index.js");
|
||||
|
||||
await access(entry);
|
||||
const child = spawn(process.execPath, [entry], {
|
||||
env: { ...environment, PORT: String(port) },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
let output = "";
|
||||
let errors = "";
|
||||
const exit = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((done) => {
|
||||
child.once("exit", (code, signal) => done({ code, signal }));
|
||||
});
|
||||
|
||||
const terminate = async () => {
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
const timer = setTimeout(() => child.kill("SIGKILL"), 10000);
|
||||
|
||||
try {
|
||||
return await exit;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await new Promise<void>((done, fail) => {
|
||||
const timer = setTimeout(
|
||||
() => fail(new Error("Timeout de arranque del servidor compilado.")),
|
||||
30000
|
||||
);
|
||||
|
||||
child.once("error", (error) => {
|
||||
clearTimeout(timer);
|
||||
fail(error);
|
||||
});
|
||||
child.once("exit", () => {
|
||||
clearTimeout(timer);
|
||||
fail(new Error("El servidor compilado terminó antes de anunciar su arranque."));
|
||||
});
|
||||
child.stderr.on("data", (chunk: Buffer) => {
|
||||
errors += chunk.toString();
|
||||
});
|
||||
child.stdout.on("data", (chunk: Buffer) => {
|
||||
output += chunk.toString();
|
||||
if (output.includes('"event":"server_started"')) {
|
||||
clearTimeout(timer);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
await terminate();
|
||||
// Solo diagnóstico de arranque; no contiene cuerpos HTTP, cookies ni entorno.
|
||||
await writeScopeEvidence("runtime-start-failure.log", { output, errors });
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
stop: async () => {
|
||||
const result = await terminate();
|
||||
|
||||
assert.deepEqual(result, { code: 0, signal: null });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("B-01/B-02: bundle real, Identity SQL, TLS, imágenes y cierre ordenado", async (t) => {
|
||||
const isolated = disposableScopeEnvironment();
|
||||
const certificate = await localTlsCertificate();
|
||||
const reservation = createServer();
|
||||
const port = await listenLocal(reservation);
|
||||
|
||||
await closeLocal(reservation);
|
||||
const proxy = await localTlsProxy(port, "127.0.0.2", certificate);
|
||||
const environment = {
|
||||
...process.env,
|
||||
...isolated,
|
||||
NODE_ENV: "production",
|
||||
APP_ORIGIN: proxy.origin,
|
||||
TRUSTED_PROXY_CIDRS: "127.0.0.2/32",
|
||||
RESOURCE_GLOBAL_CATALOG_ENABLED: "false",
|
||||
RESOURCE_GLOBAL_SOFTWARE_ENABLED: "false",
|
||||
RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED: "false",
|
||||
RESOURCE_GLOBAL_PROFILE_ENABLED: "false",
|
||||
RESOURCE_GLOBAL_OS_CATALOG_ENABLED: "false",
|
||||
RESOURCE_GLOBAL_OS_SOFTWARE_ENABLED: "false",
|
||||
};
|
||||
// Los flags se transportan como entorno, no como los booleanos normalizados del schema.
|
||||
const runtimeEnvironment = Object.fromEntries(
|
||||
Object.entries(environment).map(([key, value]) => [key, String(value)])
|
||||
);
|
||||
let running: Awaited<ReturnType<typeof startCompiled>> | undefined;
|
||||
let cookie = "";
|
||||
let csrf = "";
|
||||
const password = randomBytes(24).toString("hex");
|
||||
const evidence: {
|
||||
check: string;
|
||||
status: string;
|
||||
cookies?: ReturnType<typeof cookieAttributes>;
|
||||
}[] = [];
|
||||
|
||||
const request = async (
|
||||
path: string,
|
||||
body?: unknown,
|
||||
options: { token?: string; origin?: string; binary?: Buffer } = {}
|
||||
) => {
|
||||
const response = await localRequest(`${proxy.origin}/api${path}`, {
|
||||
ca: certificate.cert,
|
||||
method: body === undefined && !options.binary ? "GET" : "POST",
|
||||
headers: {
|
||||
cookie,
|
||||
origin: options.origin ?? proxy.origin,
|
||||
"x-csrf-token": options.token ?? csrf,
|
||||
"content-type": options.binary ? "application/octet-stream" : "application/json",
|
||||
},
|
||||
...(options.binary
|
||||
? { body: options.binary }
|
||||
: body === undefined
|
||||
? {}
|
||||
: { body: Buffer.from(JSON.stringify(body)) }),
|
||||
});
|
||||
const received = response.headers["set-cookie"]?.[0]?.split(";")[0];
|
||||
|
||||
if (received) {
|
||||
cookie = received;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
const response = await request("/auth/session");
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
const state = SessionResponseSchema.parse(JSON.parse(response.body.toString()));
|
||||
|
||||
csrf = state.csrfToken;
|
||||
|
||||
return { response, state };
|
||||
};
|
||||
|
||||
try {
|
||||
await t.test("TLS-11: producción sin proxies explícitos rechaza el arranque", async () => {
|
||||
await assert.rejects(
|
||||
startCompiled({ ...runtimeEnvironment, TRUSTED_PROXY_CIDRS: "" }, port),
|
||||
/terminó antes de anunciar su arranque/
|
||||
);
|
||||
evidence.push({ check: "incomplete-production-config", status: "pass" });
|
||||
});
|
||||
running = await startCompiled(runtimeEnvironment, port);
|
||||
await t.test("B-01: proceso compilado anuncia arranque y sirve /api/__health", async () => {
|
||||
const health = await request("/__health");
|
||||
|
||||
assert.equal(health.status, 200);
|
||||
assert.equal(
|
||||
z.object({ status: z.literal("ok") }).parse(JSON.parse(health.body.toString())).status,
|
||||
"ok"
|
||||
);
|
||||
evidence.push({ check: "compiled-start-health", status: "pass" });
|
||||
});
|
||||
await t.test("TLS-01/04: bootstrap/login real y cookies Secure con sesión SQL", async () => {
|
||||
const initial = await refresh();
|
||||
|
||||
assert.equal(initial.state.setupRequired, true);
|
||||
assert.ok(cookieAttributes(initial.response.headers)[0]?.attributes.includes("Secure"));
|
||||
assert.equal(
|
||||
(
|
||||
await request("/auth/bootstrap", {
|
||||
name: "Administrador temporal TLS",
|
||||
email: "runtime@example.invalid",
|
||||
password,
|
||||
})
|
||||
).status,
|
||||
201
|
||||
);
|
||||
assert.ok((await refresh()).state.user);
|
||||
evidence.push({
|
||||
check: "real-bootstrap-and-session",
|
||||
status: "pass",
|
||||
cookies: cookieAttributes(initial.response.headers),
|
||||
});
|
||||
});
|
||||
await t.test("Identity: CSRF y origen siguen bloqueando escrituras", async () => {
|
||||
assert.equal(
|
||||
(await request("/companies", { name: "No crear", active: true }, { token: "invalid" }))
|
||||
.status,
|
||||
403
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request(
|
||||
"/companies",
|
||||
{ name: "No crear", active: true },
|
||||
{ origin: "https://wrong.example.invalid" }
|
||||
)
|
||||
).status,
|
||||
403
|
||||
);
|
||||
evidence.push({ check: "csrf-origin", status: "pass" });
|
||||
});
|
||||
await t.test("B-01: sharp procesa logo y foto a través del servidor compilado", async () => {
|
||||
const companyResponse = await request("/companies", {
|
||||
name: "Runtime TLS — sintética",
|
||||
active: true,
|
||||
});
|
||||
|
||||
assert.equal(companyResponse.status, 201);
|
||||
const company = CompanyResponseSchema.parse(
|
||||
JSON.parse(companyResponse.body.toString())
|
||||
).company;
|
||||
const personResponse = await request(`/companies/${company.id}/people`, {
|
||||
name: "Persona sintética TLS",
|
||||
position: null,
|
||||
email: null,
|
||||
phone: null,
|
||||
active: true,
|
||||
isContact: false,
|
||||
});
|
||||
|
||||
assert.equal(personResponse.status, 201);
|
||||
const person = CompanyResponseSchema.parse(JSON.parse(personResponse.body.toString())).company
|
||||
.people[0];
|
||||
|
||||
assert.ok(person);
|
||||
for (const format of ["png", "jpeg", "webp"] as const) {
|
||||
const image = await sharp({
|
||||
create: { width: 256, height: 128, channels: 3, background: "#216e9a" },
|
||||
})
|
||||
.toFormat(format)
|
||||
.toBuffer();
|
||||
const logo = await request(`/companies/${company.id}/logo`, undefined, { binary: image });
|
||||
|
||||
assert.equal(logo.status, 200);
|
||||
const logoVersion = CompanyLogoResponseSchema.parse(
|
||||
JSON.parse(logo.body.toString())
|
||||
).logoVersion;
|
||||
|
||||
assert.ok(logoVersion);
|
||||
const savedLogo = await request(`/companies/${company.id}/logo?version=${logoVersion}`);
|
||||
|
||||
assert.equal(savedLogo.status, 200);
|
||||
const logoMetadata = await sharp(savedLogo.body).metadata();
|
||||
|
||||
assert.equal(logoMetadata.format, "webp");
|
||||
assert.equal(logoMetadata.width, 128);
|
||||
const photo = await request(
|
||||
`/companies/${company.id}/people/${person.id}/photo`,
|
||||
undefined,
|
||||
{
|
||||
binary: image,
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(photo.status, 200);
|
||||
const photoVersion = PersonPhotoResponseSchema.parse(
|
||||
JSON.parse(photo.body.toString())
|
||||
).photoVersion;
|
||||
|
||||
assert.ok(photoVersion);
|
||||
const savedPhoto = await request(
|
||||
`/companies/${company.id}/people/${person.id}/photo?version=${photoVersion}`
|
||||
);
|
||||
|
||||
assert.equal(savedPhoto.status, 200);
|
||||
assert.equal((await sharp(savedPhoto.body).metadata()).width, 128);
|
||||
}
|
||||
assert.equal(
|
||||
(
|
||||
await request(`/companies/${company.id}/logo`, undefined, {
|
||||
binary: Buffer.from("invalid image"),
|
||||
})
|
||||
).status,
|
||||
400
|
||||
);
|
||||
evidence.push({ check: "compiled-sharp-logo-photo-invalid", status: "pass" });
|
||||
});
|
||||
await t.test(
|
||||
"TLS-09/12: logout invalida SQL, conserva atributos y login regenera sesión",
|
||||
async () => {
|
||||
const oldCookie = cookie;
|
||||
const logout = await request("/auth/logout", {});
|
||||
|
||||
assert.equal(logout.status, 200);
|
||||
const cleared = cookieAttributes(logout.headers)[0];
|
||||
|
||||
assert.ok(cleared);
|
||||
for (const attribute of ["Secure", "HttpOnly", "SameSite=Strict", "Path=/"]) {
|
||||
assert.ok(cleared.attributes.includes(attribute));
|
||||
}
|
||||
const rejected = await localRequest(`${proxy.origin}/api/companies`, {
|
||||
ca: certificate.cert,
|
||||
headers: { cookie: oldCookie },
|
||||
});
|
||||
|
||||
assert.equal(rejected.status, 401);
|
||||
cookie = "";
|
||||
await refresh();
|
||||
const login = await request("/auth/login", { email: "runtime@example.invalid", password });
|
||||
|
||||
assert.equal(login.status, 200);
|
||||
assert.notEqual(cookie, oldCookie);
|
||||
assert.ok(cookieAttributes(login.headers)[0]?.attributes.includes("Secure"));
|
||||
assert.ok((await refresh()).state.user);
|
||||
evidence.push({
|
||||
check: "real-logout-relogin",
|
||||
status: "pass",
|
||||
cookies: cookieAttributes(logout.headers),
|
||||
});
|
||||
}
|
||||
);
|
||||
await running.stop();
|
||||
running = undefined;
|
||||
evidence.push({ check: "compiled-graceful-production-stop", status: "pass" });
|
||||
await t.test(
|
||||
"TLS-10: desarrollo HTTP emite cookie HttpOnly/Strict sin Secure y sin proxy",
|
||||
async () => {
|
||||
running = await startCompiled(
|
||||
{ ...runtimeEnvironment, NODE_ENV: "development", TRUSTED_PROXY_CIDRS: "" },
|
||||
port
|
||||
);
|
||||
const response = await localRequest(`http://127.0.0.1:${port}/api/auth/session`);
|
||||
const attributes = cookieAttributes(response.headers)[0];
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.ok(attributes);
|
||||
assert.equal(attributes.attributes.includes("Secure"), false);
|
||||
assert.ok(attributes.attributes.includes("HttpOnly"));
|
||||
assert.ok(attributes.attributes.includes("SameSite=Strict"));
|
||||
evidence.push({
|
||||
check: "development-http",
|
||||
status: "pass",
|
||||
cookies: cookieAttributes(response.headers),
|
||||
});
|
||||
}
|
||||
);
|
||||
await writeScopeEvidence("runtime-results.json", {
|
||||
evidence,
|
||||
database: "metis_desk_test",
|
||||
tlsCertificateValidation: true,
|
||||
fixtures: { companies: 1, people: 1 },
|
||||
productionTouched: false,
|
||||
});
|
||||
} finally {
|
||||
if (running) {
|
||||
await running.stop();
|
||||
}
|
||||
await proxy.close();
|
||||
await certificate.close();
|
||||
}
|
||||
});
|
||||
@ -6,10 +6,12 @@ export default defineConfig({
|
||||
target: "node24",
|
||||
platform: "node",
|
||||
bundle: true,
|
||||
// sharp resuelve binarios nativos desde su propio paquete, no desde el bundle.
|
||||
external: ["sharp"],
|
||||
noExternal: [/^@metis\//, "@repo/rdx-utils", "@repo/rdx-ddd"],
|
||||
// El código CommonJS de dependencias empaquetadas necesita require en la salida ESM.
|
||||
banner: {
|
||||
js: 'import { createRequire } from "node:module"; const require = createRequire(import.meta.url);',
|
||||
js: 'import { createRequire as createServerRequire } from "node:module"; const require = createServerRequire(import.meta.url);',
|
||||
},
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
|
||||
@ -0,0 +1,302 @@
|
||||
# Auditoría arquitectónica — Estado actual de los sistemas operativos de activos y puestos
|
||||
|
||||
Fecha: **26 de octubre de 2026**. Investigación estática y recomendación preliminar. **No constituye una nueva decisión aprobada ni autoriza implementación.**
|
||||
|
||||
## 1. Resumen ejecutivo
|
||||
|
||||
Metis Desk ya separa origen y operación. Assets conserva el SO suministrado como característica textual del activo; Resources representa el producto SO; Companies conserva su relación operativa con una unidad mediante `SoftwareInstallation`. El puesto consulta las instalaciones de su ordenador principal y no almacena un SO propio. El catálogo implementado admite **SO particulares y SO globales**, además de aplicaciones globales; la licencia continúa siendo particular de compañía [E01–E13, E18–E22].
|
||||
|
||||
El ejemplo ThinkPad/FreeDOS/Windows 11 Pro puede registrarse manualmente sin sustituir el dato de fábrica. Pueden existir varios productos SO configurados en una unidad. No hay detección automática, estado tipado de ausencia confirmada/desconocimiento, instancia de arranque, fecha efectiva de instalación, build/arquitectura estructurados ni procedencia/verificación. El estado `configured` es una declaración registrada, no una comprobación técnica del equipo [E06–E09, E18–E21].
|
||||
|
||||
Hallazgos principales: servidor incorpora dos campos próximos de SO de fábrica; el mismo producto solo puede tener una instalación abierta por activo; reinstalaciones carecen de evento específico; la eliminación física de una instalación borra sus revisiones; nombres/producto de las revisiones se resuelven con referencias actuales; licencias no comprueban cupos, compatibilidad comercial ni restricciones OEM. La relación genérica «Alojado en» es reutilizable documentalmente, pero no equivale a gestionar virtualización [E01, E05–E10, E16, E23].
|
||||
|
||||
Recomendación preliminar: **D acotada, apoyada en B ya implementada**. Mantener fábrica en Assets, producto en Resources, instalación en Companies y presentación derivada en puestos. Resolver primero las carencias funcionales que realmente se necesiten; no duplicar catálogo, instalación o dato SO dentro del puesto. Requisitos de compatibilidad de perfiles e inventario automático quedan condicionados a consumidores y contratos explícitos. Confianza alta en el diagnóstico estático, media en necesidades futuras; ninguna afirmación sobre datos o disponibilidad efectiva de producción.
|
||||
|
||||
## 2. Alcance y metodología
|
||||
|
||||
Base: `main`, HEAD `a72d6a2ec8985e1d51f74db68a1b3aabb3693030`. Se inspeccionó el árbol de trabajo, que ya contenía cambios concurrentes en fichas SMB, componentes visuales y documentación, incluidos archivos nuevos. Se conservaron. **HEAD no identifica por sí solo todo el árbol inspeccionado.** Las fuentes citadas se registran con SHA-256 en el [manifiesto de esta auditoría](../../.artifacts/auditoria-so-estado-actual/manifest.json); las líneas corresponden a su lectura y pueden desplazarse con cambios posteriores.
|
||||
|
||||
Se consultaron dominio, schemas, aplicación, composición, HTTP, persistencia Sequelize/migraciones y consumidores React de Assets, Resources y Companies; también pruebas y documentación normativa de capas, composición, identidad y R2.0–R2.6/D08.2. Las búsquedas incluyeron atributos dinámicos, stockKey, SO, Windows/Linux, firmware, Icecat, virtualización, instalaciones, licencias, perfiles y puestos. Una ausencia significa «no localizado en el código activo examinado», no ausencia demostrada en datos particulares o sistemas externos.
|
||||
|
||||
Se aplicó `rdx-solid` y sus referencias de arquitectura: responsabilidad por agregado, contratos públicos y distinción entre evidencia y propuesta. No se modifican interfaces ni se emite una auditoría visual; no se aplican recetas nuevas de UI/UX ni se requieren auditores visuales para esta investigación.
|
||||
|
||||
**Evidencia:** verificado estáticamente = camino/contrato presentes en código; inferencia = combinación de capacidades, identificada como tal; propuesto = todavía no implementado. Las pruebas se leyeron, **no se ejecutaron** en esta intervención. No se ejecutaron SQL, migraciones, servicios, fixtures, builds ni Playwright. No se leyeron `.env` o secretos ni se conectó a ninguna BD. Solo se crean este informe y su registro de evidencias; vendor, `archive/` y trabajo ajeno permanecen intactos.
|
||||
|
||||
## 3. Inventario verificado
|
||||
|
||||
| Elemento real | Naturaleza / propietario | Persistencia y cardinalidad | API / consumidores | Validación e historial |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Asset`, `AssetSpecification`, `AssetCatalog` | Unidad y catálogo de campos/plantillas; Assets | `assets`: `id`, `companyId`, `templateId`, `brandId`, estado, etc.; características 1:N en `asset_characteristics`, clave activo/campo | `/api/assets/:companyId`; alta/actualización y ficha mediante componentes de inventario | Pertenencia, especificación y valores tipados; `asset_revisions` y características/especificación de revisiones [E01–E03] |
|
||||
| `field:factory_os` | Campo textual de origen; valor en la unidad | `asset_definitions.stockKey` → binding de plantilla → `asset_characteristics.textValue`; un valor por activo/campo | Formulario dinámico y especificaciones de la ficha | Texto, sin fuente certificada ni enum de presencia; historial del activo [E01–E03] |
|
||||
| `field:factory_preloaded_os` | Segundo campo de origen de servidor | Mismo mecanismo; servidor también incluye `factory_os` | Formulario/ficha dinámica de servidor | No se localiza reconciliación entre ambos [E01] |
|
||||
| `Resource`, uso `os` | Producto SO; Resources | `resources`, `resource_definitions`, `resource_template_fields`, `resource_characteristics`; producto 1:N instalaciones | CRUD particular `/api/resources/:companyId`; catálogo `/api/resource-global-operating-systems` | Global solo stock elegible; particular según catálogo; `resource_revisions` y especificaciones históricas [E04, E11–E13] |
|
||||
| Identidad SO global | Valores comerciales declarativos, no entidad `OperatingSystem` separada | Campos proveedor/familia/versión comercial/estado de edición/edición, EAV relacional | DTO SO específico, formulario/revisión del catálogo global | Revisión previa, identidad fija desde primera publicación; edición conocida/no aplicable/desconocida [E11–E13] |
|
||||
| `SoftwareInstallation` | Agregado operativo por unidad; Companies | `software_installations`: UUID, compañía, activo, producto, licencia opcional, versión, estado, notas, `currentKey`; activo y producto forman N:M a través de instalaciones | `/api/software-installations/:companyId`; panel, editor, historial, SO actual y mapa | Destino activo/producto fijo; una pareja abierta; revisiones con actor/fecha; eliminación física explícita [E05–E10] |
|
||||
| Recurso `software_license`, uso `license` | Documento/licencia particular; Resources | `resources.companyId` y características de licencia; instalación 0..1 licencia, licencia 0..N instalaciones | Catálogo/recursos particulares y selector de licencia | Misma compañía/tipo, actividad/caducidad cuando procede; revisiones conservan UUID de licencia [E08, E09, E14] |
|
||||
| `Workstation` | Composición de equipamiento; Assets | `workstations.computerAssetId` 0..1 principal; `workstation_assets` N miembros, `activeKey` exclusivo; revisiones en `workstation_revisions` | `/api/assets/:companyId/workstations`; ficha y mapa de Companies | Principal debe ser miembro; coherencia de compañía/disponibilidad y exclusividad en otro puesto activo; historial de cambios [E15] |
|
||||
| `WorkProfile` | Contexto persona/dispositivo/cuenta; Companies | `work_profiles`: referencias a persona, activo y cuenta opcional; no SO ni dependencia obligatoria de UUID de puesto | `/api/work-profiles/:companyId` y vistas por persona/equipo | Validación de contexto; historial propio [E17] |
|
||||
| `ApplicationConfiguration` | Configuración de aplicación del perfil; Companies | `application_configurations`, `application_configuration_revisions`: perfil, recurso, delivery, versión, estado, notas | `/api/application-configurations/:companyId` | Solo `application`; no admite SO. No sustituye instalación por activo [E17] |
|
||||
| `AssetRelationship`, `relation:hosted` | Relación genérica entre activos; Assets | Relación con extremos/tipo, autor/fecha de alta y retirada; no es asociación a un Resource SO | Gestión de relaciones de activos | No acredita VM, hipervisor ni propagación de SO [E16] |
|
||||
|
||||
El estado scoped de Resource se introduce por migración explícita: `scope=company` con propietario o `scope=global` con `companyId=null`. No debe deducirse del `migrateInventory` legacy aislado, que aún declara el propietario obligatorio al crear la tabla inicial. El arranque y el inspector verifican compatibilidad; existencia de migraciones no demuestra aplicación en una BD real [E04, E13].
|
||||
|
||||
### Activos, modelos físicos y fábrica
|
||||
|
||||
Las plantillas stock incluyen portátil, sobremesa, monitor, impresora, router, firewall, switch, punto de acceso, NAS, servidor, smartphone y SAI. No se localizaron plantillas stock específicas de VM, mini-PC o workstation de altas prestaciones. Una plantilla propia puede describirlos; eso no añade semántica de virtualización o un nuevo subtipo de entidad [E01].
|
||||
|
||||
Existe referencia opcional a `asset_brands`; modelo, serie, código de producto/MPN, hardware, compra/proveedor/garantía se expresan mediante campos de la plantilla. No se localizó un agregado de producto físico/SKU ni integración Icecat/importador de fichas de fabricante que determine automáticamente el SO de cada unidad. Los ejemplos de las definiciones son ayuda, no datos que acrediten una configuración de fabricante. No es correcto inferir SO a partir del modelo, marca, plantilla o persona asignada [E01–E03].
|
||||
|
||||
La política de instalación comprueba que el activo exista en la compañía y no esté retirado, **no restringe el SO a plantillas laptop/desktop/server**. Esto permite servidores y plantillas propias, pero también exige criterio de inventario para evitar clasificaciones inadecuadas; no hay compatibilidad hardware/SO verificada [E08].
|
||||
|
||||
## 4. Modelo verificado
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Assets
|
||||
A["Asset: unidad y características de fábrica"]
|
||||
W["Workstation: persona, miembros, principal"]
|
||||
AH["AssetRevision / WorkstationRevision"]
|
||||
REL["AssetRelationship: Alojado en, etc."]
|
||||
W -->|"assetIds; computerAssetId opcional"| A
|
||||
A --> AH
|
||||
W --> AH
|
||||
REL -->|"extremos Asset"| A
|
||||
end
|
||||
subgraph Resources
|
||||
PROD["Resource: os/application; company/global"]
|
||||
LIC["Resource: license; company"]
|
||||
RH["ResourceRevision y especificaciones"]
|
||||
PROD --> RH
|
||||
LIC --> RH
|
||||
end
|
||||
subgraph Companies
|
||||
I["SoftwareInstallation"]
|
||||
IH["SoftwareInstallationRevision"]
|
||||
P["WorkProfile: persona/activo/cuenta"]
|
||||
C["ApplicationConfiguration: application únicamente"]
|
||||
I --> IH
|
||||
P --> C
|
||||
end
|
||||
A -->|"1 unidad por instalación; 0..N instalaciones"| I
|
||||
PROD -->|"1 producto por instalación"| I
|
||||
LIC -->|"0..1 licencia por instalación"| I
|
||||
A -->|"activo del perfil"| P
|
||||
PROD -->|"solo application"| C
|
||||
V["SO actual en ficha/puesto: consulta derivada"]
|
||||
W -.->|"selección del principal"| V
|
||||
I -.->|"configured, tipo os, referencias resueltas"| V
|
||||
```
|
||||
|
||||
`PROD` y `LIC` son papeles del mismo agregado/tabla Resource, no tablas nuevas. El gráfico no representa una FK SO–puesto ni una relación directa entre `ApplicationConfiguration` y `SoftwareInstallation`: ninguna se ha localizado. Las cardinalidades describen datos del modelo, no recuentos de una instalación desplegada.
|
||||
|
||||
## 5. Flujo de datos actual
|
||||
|
||||
1. El catálogo de Assets define campos/bindings. El usuario registra características de la unidad, incluido origen/fábrica; se guardan valores y revisiones del activo. No se crea automáticamente una instalación desde ese texto [E01–E03].
|
||||
2. Resources registra el producto SO particular o global. El global utiliza stock `template:os`, identidad comercial revisada y UUID propio; publicar otro producto exige otra identidad. La versión comercial del producto es distinta de la versión textual instalada [E11–E13].
|
||||
3. La instalación declara activo/producto/estado/versión/licencia. Companies recibe referencias públicas de Resources y Assets en la composición; valida pertenencia, disponibilidad y propósito dentro de la operación transaccional. No consulta modelos ORM ajenos desde el dominio [E05–E10, E22].
|
||||
4. `useAssetSoftware` consulta instalaciones de la compañía, filtra las del activo y resuelve productos mediante la API pública. `getCurrentOperatingSystems` selecciona únicamente `configured` + `kind=os`; no consulta el campo de fábrica [E18].
|
||||
5. La ficha del activo compone `AssetOperatingSystems`; la del puesto obtiene el miembro `computerAssetId` válido y reutiliza la misma consulta/presentación. Panel/historial/mapa ofrecen enlaces por scope [E19–E21].
|
||||
|
||||
Los estados de carga/error/referencia no disponible se separan del vacío. «No registrado» significa que la lectura no muestra un SO configurado, **no que se haya verificado un disco sin SO**. Pendiente, retirado o retirada pendiente no forman parte del resumen de SO actual. Un producto inactivo resoluble puede seguir mostrándose con aviso de revisión [E18–E21].
|
||||
|
||||
## 6. Origen de fábrica frente a instalación actual
|
||||
|
||||
| Pregunta | Respuesta verificada |
|
||||
| --- | --- |
|
||||
| ¿Existe SO suministrado originalmente? | Dos campos textuales stock: `factory_os` y `factory_preloaded_os`; valores por activo, no producto físico compartido [E01] |
|
||||
| ¿Procedencia fabricante frente a introducción manual? | Sin procedencia por valor o marca de verificación; actor/fecha de revisión indican quién registró el dato, no quién lo certificó [E02, E03] |
|
||||
| ¿Qué pasa si cambia el SO operativo? | Cambia la instalación/producto asociado; no hay camino localizado que sobrescriba automáticamente fábrica [E05–E10] |
|
||||
| ¿Se preserva fábrica? | Es independiente y tiene historial del activo, pero puede editarse manualmente; no es un dato de fabricante inmutable |
|
||||
| ¿Puede documentarse «sin SO de fábrica»? | Sí como texto; no hay estado tipado ni regla que lo relacione con presencia real actual |
|
||||
| ¿Cada unidad del modelo comparte SO? | No se impone. La plantilla define estructura; cada Asset conserva sus propios valores |
|
||||
|
||||
Portátil y sobremesa usan `factory_os`. Servidor incluye `factory_preloaded_os` **y** `factory_os` en dos grupos. La duplicidad está verificada en el seed, no contradicciones de datos reales. Decidir un campo canónico y eventual tratamiento de valores existentes requiere otra decisión; no se retira ninguno aquí [E01].
|
||||
|
||||
### Datos de una instalación efectiva
|
||||
|
||||
| Dato | Situación actual |
|
||||
| --- | --- |
|
||||
| Producto | FK `resourceId`, tipología SO/application; global o particular |
|
||||
| Edición | Pertenece al producto. No hay override de edición en la instalación; global publicado fija identidad |
|
||||
| Versión | Campo opcional `version` de hasta 100 caracteres, sin parser específico por fabricante |
|
||||
| Build y arquitectura | No tienen campos propios de instalación. Escribirlos en versión/notas no equivale a soporte estructurado |
|
||||
| Fecha efectiva de instalación | No existe. `recordedAt` es fecha de registro/revisión generada por el servidor |
|
||||
| Estado | `pending`, `configured`, `removal_pending`, `removed`, `never_configured` |
|
||||
| Origen/última verificación | No tienen contrato persistido propio |
|
||||
| Reinstalaciones | Cerrar anterior y crear nueva relación; sin comando/evento específico ni instancia de arranque |
|
||||
| SO anteriores | Instalaciones cerradas y revisiones mientras no se eliminen físicamente |
|
||||
|
||||
**«Edición desconocida» del catálogo no significa «SO de la unidad desconocido».** Tampoco `never_configured` acredita ausencia de cualquier SO: cierra la intención de instalar un producto concreto [E05–E09, E11].
|
||||
|
||||
## 7. Software, licencias e historial
|
||||
|
||||
Las aplicaciones y SO son productos Resources, no instalaciones. `SoftwareInstallation` es una declaración operativa por activo. `ApplicationConfiguration` vincula una aplicación a un perfil con delivery `installed`/`web`, versión y estado; no es por sí sola inventario de lo realmente instalado ni un motor de autorización que prohíba otros productos en el equipo. No se localizó reconciliación automática entre ambos agregados [E05, E17, E22].
|
||||
|
||||
El modelo permite varias instalaciones abiertas de **productos diferentes** por activo. Impide duplicar la pareja abierta activo/producto mediante policy y `currentKey` único; terminales liberan esa clave. No se pueden representar dos instancias simultáneas del mismo UUID de producto sobre particiones/arranques distintos. Repetir una actualización idéntica no crea revisión; repetir un alta sin identidad idempotente de instalación no debe interpretarse como un evento nuevo de reinstalación [E06–E10].
|
||||
|
||||
Las transiciones son `pending` → `configured` o `never_configured`; `configured` → `removal_pending` → `removed`; los terminales no se reabren cambiando valores. Cambiar producto o activo en una instalación existente se rechaza. Para conservar Windows 10 y Windows 11 como productos distintos, el camino disponible es cerrar la relación anterior y registrar el otro producto. La identidad global publicada impide convertir su producto en otra edición/versión comercial; un particular no tiene esa misma protección, por lo que no debe extrapolarse la garantía global a todos los Resources. Actualizar la versión instalada no transforma la identidad del producto global [E05–E09, E11].
|
||||
|
||||
Las revisiones registran UUID de instalación/revisión, actor, fecha y valores. Conservan referencias a producto y licencia, **no una copia del nombre y de toda su identidad comercial en cada revisión de instalación**. La UI resuelve los nombres actuales; no es una reconstrucción temporal completa de todas las entidades relacionadas. Particularmente, la identidad de un Resource particular no disfruta de la garantía global de inmovilidad comercial [E10–E13].
|
||||
|
||||
**Retirar no equivale a eliminar.** El endpoint `/delete` elimina primero `software_installation_revisions` y después `software_installations`, dentro de transacción. En ese camino no se observa una condición de estado que limite el borrado a un alta pendiente: no debe prometerse retención forense permanente. La documentación de software sí declara esa eliminación como corrección de un alta por error; la política funcional de retención merece ratificación explícita [E10, E23].
|
||||
|
||||
La licencia es opcional; debe ser particular de la compañía y de uso `license`. Seleccionar/cambiar a una licencia no disponible se rechaza; actividad y caducidad se verifican cuando procede crear/reconfigurar. Inactividad/caducidad derivan `needsReview`, sin retirar software automáticamente; notas, desvinculación y retirada tienen reglas que preservan operación histórica [E08–E10].
|
||||
|
||||
El catálogo de licencia ofrece modalidad `Suscripción`, `Perpetua`, `OEM`, `Otra`, métrica, cantidad, vigencia y referencia. No se localizó consumo de puestos, límite de cantidad, asignación de derechos, correspondencia comercial normalizada producto/edición ni restricción de transferencia OEM. «Volumen» puede documentarse, pero no hay política de derechos de volumen implementada. Una licencia activa de la compañía no acredita por sí sola que cubra ese producto [E08, E14].
|
||||
|
||||
## 8. Puestos, perfiles y varios ordenadores
|
||||
|
||||
**Sí: Metis Desk puede y ya muestra el SO del principal sin duplicarlo en el puesto.** `computerAssetId` debe pertenecer a `assetIds`; la ficha verifica miembro/compañía antes de llamar a `useWorkstationSoftware(companyId, computer.id, ...)`. El cambio de principal cambia la unidad consultada; no mueve ni copia instalaciones. Su historial queda en Workstation; el del software sigue ligado al activo [E15, E18–E22].
|
||||
|
||||
Al retirar un activo de la composición o descomponer el puesto, no se localiza una cascada funcional que cierre o traslade sus instalaciones. La unidad puede seguir teniendo software y consultarse desde su propia ficha. Un puesto admite varios activos; la presentación de SO actual y el software del mapa corresponden al principal, no a una agregación de SO de todos los ordenadores secundarios. Las fichas de los secundarios son el camino de consulta individual [E15, E18–E21].
|
||||
|
||||
`WorkProfile` asocia persona, activo y cuenta opcional, con reglas de anfitrión y pertenencia. No tiene un requisito SO/versión compatible. La configuración de aplicaciones de perfil excluye SO tanto local como global. No se recomienda abrirla a SO para representar instalaciones ya modeladas [E17, E22].
|
||||
|
||||
Dependencias prácticas: principal válido, inventario/resolver accesibles, consultas de compañía disponibles y contrato Resources 3 en contextos con SO global. Una referencia no resuelta no se toma como ausencia. Los permisos/gates pueden impedir nuevas asociaciones, pero no son datos de fábrica ni pruebas de presencia del SO [E12, E13, E18–E21].
|
||||
|
||||
## 9. Escenarios SO-01–SO-16
|
||||
|
||||
Clasificación **por contratos estáticamente inspeccionados**, no prueba funcional nueva de los 16 recorridos. SOPORTADO significa representable mediante la operación descrita, con sus límites expresos. **H:** revisiones conservadas al actualizar/retirar, salvo borrado físico; **I:** FK/compañía, propósito y pareja abierta, según código, no inspección de BD real.
|
||||
|
||||
| ID | Escenario / clasificación | Registro, consulta y visualización | Historial / integridad | Límite y evidencia |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| SO-01 | FreeDOS → Windows 11 Pro: **SOPORTADO** | Fábrica textual FreeDOS + Resource Windows + instalación configured; ficha y principal derivados | H activo + instalación; I | Registro manual, sin importación ni certificación del origen [E01–E09, E18–E21] |
|
||||
| SO-02 | Windows de origen sin cambios: **SOPORTADO** | Registrar origen e instalación explícitamente; no basta el texto de fábrica para el resumen | H; I | No se infiere instalación desde fabricante [E01, E18] |
|
||||
| SO-03 | Origen sin SO → Ubuntu: **PARCIALMENTE SOPORTADO** | Origen «sin SO» textual, producto Ubuntu e instalación; consulta normal de unidad | H; I | Ausencia de origen no tipada/verificada; instalación sí representable [E01, E05, E11] |
|
||||
| SO-04 | Windows 10 Pro → 11 Pro: **SOPORTADO** | Retirar relación anterior y crear otra con producto distinto; historial/panel con cerradas | H en dos identidades; I | No cambiar `resourceId` de la relación original [E05–E10] |
|
||||
| SO-05 | Reinstalación limpia del mismo Windows: **PARCIALMENTE SOPORTADO** | Cerrar instalación y crear otra del mismo producto; se consultan ambas | H si no se borra; I | Sin evento/fecha efectiva de reinstalación; repetir valores no crea evidencia [E05–E10] |
|
||||
| SO-06 | Arranque dual Windows/Linux: **PARCIALMENTE SOPORTADO** | Dos productos configured en el mismo activo; resumen plural | H independiente; I | No hay partición, cargador, arquitectura ni arranque predeterminado; mismo producto en dos instancias no cabe [E07, E18] |
|
||||
| SO-07 | Servidor físico Windows Server: **SOPORTADO** | Asset servidor + producto SO + instalación; consulta de activo sin exigir puesto | H; I | No comprueba compatibilidad hardware ni derechos por núcleo; fábrica duplicada [E01, E08, E24] |
|
||||
| SO-08 | Hipervisor y varias VM: **PARCIALMENTE SOPORTADO** | Host/huéspedes como Assets propios, producto e instalación por unidad, relación genérica hosted; consulta individual | H/relación e I básicos | Inferencia de composición; sin agregado virtualización, descubrimiento, recursos o sincronización host/guest [E01, E16] |
|
||||
| SO-09 | VM con SO propio: **PARCIALMENTE SOPORTADO** | Plantilla propia y Asset para VM; instalación normal y ficha de esa unidad | H; I | No hay tipo/plantilla stock VM ni identidad/lifecycle de plataforma verificado [E01, E08, E16] |
|
||||
| SO-10 | Cambio de principal: **SOPORTADO** | Modificar principal miembro; ficha deriva SO del nuevo Asset | H puesto, software queda en sus unidades; I | Sin traslado/reinstalación automática [E15, E18–E21] |
|
||||
| SO-11 | Activo deja el puesto: **SOPORTADO** | Cambiar composición; el activo conserva instalación y consulta propia | H puesto e instalación; I | El puesto ya no debe presentar esa unidad como principal [E15, E18] |
|
||||
| SO-12 | SO desconocido: **PARCIALMENTE SOPORTADO** | Sin instalación configurada se muestra «No registrado»; no disponible/error tienen mensajes propios | Sin evento de desconocimiento; I sobre registros existentes | No distingue desconocido confirmado de ausencia de registro [E18, E19] |
|
||||
| SO-13 | Equipo entregado sin SO: **PARCIALMENTE SOPORTADO** | Texto de origen y ausencia de instalación | H del texto; I | **NO SOPORTADO** el estado operativo tipado «sin SO, verificado» [E01, E05, E19] |
|
||||
| SO-14 | Cambio de versión o edición: **PARCIALMENTE SOPORTADO** | Versión instalada editable con revisión; otra identidad comercial exige producto/relación nuevos | H; I, destino fijo | Build/arquitectura no estructurados; no confundir edición con versión instalada [E05–E11] |
|
||||
| SO-15 | Licencia OEM/volumen/suscripción: **PARCIALMENTE SOPORTADO** | Recurso licencia particular y vínculo opcional; panel e historial enlazan | H de UUID y valores; I | Sin cupos, derechos efectivos, transferencia OEM ni cobertura comercial automática [E08–E10, E14] |
|
||||
| SO-16 | Instalado fuera del inventario/verificación: **NO SOPORTADO** como conocimiento automático | Puede declararse manualmente configured; si no se registra, no aparecerá | H solo tras registro; I del registro | No hay agente, origen de observación o última verificación; no interpretar vacío como inventario exhaustivo [E05, E18, E24] |
|
||||
|
||||
El estado efectivo de estos escenarios en una base desplegada es **NO VERIFICADO** para todos: no se consultaron sus datos. Las propiedades técnicas descritas no equivalen a ejecución, licitud de licencia ni disponibilidad productiva.
|
||||
|
||||
## 10. Deficiencias, riesgos y documentación desfasada
|
||||
|
||||
1. **Origen ambiguo:** dos campos de servidor pueden divergir; sin procedencia/certificación por unidad. La lectura del seed acredita el riesgo, no datos contradictorios existentes.
|
||||
2. **Presencia no modelada:** no hay estado observado desconocido/sin SO/instalado con fecha. El vacío de UI es honesto como «No registrado», pero no acredita ausencia.
|
||||
3. **Instancias y tiempo:** sin arranque/partición, arquitectura, build o fecha efectiva; una pareja abierta limita dos instalaciones del mismo producto; no hay evento de reinstalación.
|
||||
4. **Auditoría no permanente:** `/delete` purga instalación y revisiones. Las referencias históricas de instalación no congelan todas las características comerciales externas.
|
||||
5. **Calidad de catálogo:** particulares pueden estar incompletos o coexistir con globales equivalentes. No hay fusión/adopción. La normalización global no equipara sinónimos ni constituye UNIQUE SQL comercial.
|
||||
6. **Licencias documentales:** no hay cálculo de consumo/derechos ni política OEM. La optionalidad no equivale a validación de cumplimiento.
|
||||
7. **Asignación frente a instalación:** perfil, puesto e instalación son agregados distintos; no se detecta automáticamente discrepancia entre aplicación configurada en perfil y software declarado del activo.
|
||||
8. **Evolución e integraciones:** plantillas propias y relaciones genéricas permiten documentación, no inventario automático o gestión completa de VM. No inferir capacidades a partir de texto libre o nombres.
|
||||
|
||||
La [auditoría original](auditoria-sistemas-operativos-activos-y-puestos.md), del 25 de octubre, afirmaba que el catálogo global solo admitía aplicaciones. Esa descripción es **histórica y no describe el código actual**, ampliado por la [decisión implementada del catálogo SO](decision-catalogo-global-sistemas-operativos.md). Se conserva el documento anterior para no reescribir su evidencia. La [decisión del SO actual](decision-so-actual-en-activos-y-puestos.md) y sus ampliaciones deben leerse junto con esa extensión global.
|
||||
|
||||
El [cierre técnico](cierre-tecnico-catalogo-global-sistemas-operativos.md) y el [preflight](preflight-activacion-catalogo-global-sistemas-operativos.md) distinguen código validado de autorización/despliegue. Esta auditoría no levanta su NO PREPARADO operativo ni acredita flags o esquema de producción. No se reejecutaron aquí aquellos diagnósticos ni se atribuyen sus pruebas a esta intervención.
|
||||
|
||||
## 11. Comparación de alternativas
|
||||
|
||||
Valoración cualitativa, relativa a requisitos examinados. D significa **híbrido acotado**, no implementar sus cuatro ejes sin necesidad. Compatibilidad con plugins se interpreta como composición modular existente: no se localizó un framework dinámico de plugins para inventario.
|
||||
|
||||
| Criterio | A: atributo actual de Asset | B: instalación de software | C: SO propio del puesto | D: origen + instalación + vistas derivadas |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1. Coherencia de dominio | Media: unidad correcta, mezcla producto/operación | Alta: diferencia producto e instancia | Baja: confunde equipamiento con configuración de unidad | Alta: responsabilidades separadas ya existentes |
|
||||
| 2. Normalización | Baja si repite texto/identidad | Alta: FK a Resource | Baja: duplica dato al mover unidad | Alta; origen textual sigue necesitando política |
|
||||
| 3. Catálogo de software | Poca reutilización | Reutilización directa actual | Requeriría vínculo adicional | Directa mediante B |
|
||||
| 4. Modularidad/plugins | Cabe en Assets, pero traslada software allí | Puertos y composición ya disponibles | Acopla puesto a semántica de producto | Respeta fronteras sin framework nuevo |
|
||||
| 5. Servidores y VM | Genérico, sin lifecycle VM | Instalación por cualquier Asset elegible | Mala cobertura de activos sin puesto | Cubre unidad independiente; VM sigue acotada |
|
||||
| 6. Varios SO | Texto/lista insuficiente sin modelo adicional | Ya soporta productos diferentes | Multiplica duplicación por puesto | Usa B; no añade instancias de arranque implícitas |
|
||||
| 7. Historial | Revisiones del activo, poca semántica de instalación | Revisiones propias; limitadas por delete | Historia del puesto no reconstruye unidad | Conserva ambos historiales; debe decidir retención |
|
||||
| 8. Licencias | Necesitaría otro vínculo | Vínculo particular ya existente | Riesgo de atribuir derechos al puesto | Usa B; derechos efectivos siguen pendientes |
|
||||
| 9. Inventario automático futuro | Reconciliación de texto difícil | Buen destino operativo, faltan observaciones/idempotencia de alta | Mal receptor si cambia equipamiento | Permite separar origen/observación/operación si se justifica |
|
||||
| 10. Frontend | Simplifica un campo, empobrece estados/múltiples | Paneles y selectores ya existen | Añadiría edición y sincronización duplicadas | Ya reutiliza lectura; extensiones deben ser dirigidas |
|
||||
| 11. Backend/APIs | Otro camino para el mismo hecho actual | Reutilización máxima | Nuevas reglas de propagación y traslados | Mantener contratos; ampliar solo carencias aprobadas |
|
||||
| 12. Migración | Riesgo al convertir textos en hechos operativos | Base ya instalada; no migrar fábrica automáticamente | Traslado de responsabilidad de alto riesgo | No necesita nueva base; deduplicar origen sería decisión separada |
|
||||
| 13. Divergencia | Alta respecto a catálogo/instalaciones | Baja si se usa como fuente operativa | Alta en cambios de principal/asignación | Baja con única fuente por concepto |
|
||||
| 14. Mantenimiento | Bajo al principio, alto al crecer semántica | Bajo incremental sobre implementación | Alto por sincronización redundante | Proporcional si requisitos/perfiles no se anticipan |
|
||||
|
||||
A sigue siendo adecuada **para origen**, no como reemplazo textual del SO operativo. B es la base existente más reutilizable. C carece de justificación para el hecho instalado de una unidad. D combina las responsabilidades sin exigir producto físico, requisito de perfil ni observación automática como nuevos agregados inmediatos.
|
||||
|
||||
## 12. Modelo propuesto y recomendación preliminar
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
OR["Assets: origen de la unidad\naclarar campo canónico y procedencia"]
|
||||
PROD["Resources: producto SO\nparticular/global existente"]
|
||||
UNIT["Asset: unidad física o documentada como virtual"]
|
||||
INST["Companies: SoftwareInstallation existente"]
|
||||
WS["Workstation: composición/principal"]
|
||||
VIEW["Lectura SO actual derivada por activo"]
|
||||
DEC["A decidir: ausencia/desconocimiento,\nfecha efectiva, fuente/verificación, reinstalación"]
|
||||
REQ["Solo si hay requisito aprobado:\ncompatibilidad requerida por perfil"]
|
||||
OR --> UNIT
|
||||
UNIT --> INST
|
||||
PROD --> INST
|
||||
WS --> UNIT
|
||||
INST --> VIEW
|
||||
WS --> VIEW
|
||||
DEC -.-> INST
|
||||
DEC -.-> OR
|
||||
REQ -.-> PROD
|
||||
```
|
||||
|
||||
**Modelo propuesto, no implementado por esta auditoría.** Los nodos «A decidir» y «Solo si» representan decisiones funcionales, no tablas, estados o entidades aprobadas. La parte central ya existe. No se propone transferir propiedad de datos ni un segundo catálogo.
|
||||
|
||||
Preferencia: D acotada sobre B, con confianza alta para conservar las responsabilidades existentes. Confianza media para elegir futuras extensiones, porque no se aportó una política de observación, retención, reinstalación, virtualización o derechos de licencia. La recomendación no sustituye decisiones ratificadas del catálogo global ni justifica reimplementarlo.
|
||||
|
||||
## 13. Arquitectura modular y extensibilidad
|
||||
|
||||
Assets debe conservar unidad, origen/equipamiento e historial de asignación; Resources conserva producto e identidad/plantillas; Companies conserva instalación, perfil y configuraciones. `identity-runtime.ts` inyecta servicios de inventario y referencias por transacción en `initializeProfiles`; el repositorio de instalación consume puertos y no carga el ORM de Resource/Asset desde su dominio. La composición web aporta información relacionada desde Companies a las fichas, sin introducir un import de Companies en el dominio Assets [E22, E25].
|
||||
|
||||
Los servicios públicos existentes bastan para mostrar el SO por activo y principal. No se necesitan nuevos puntos de extensión para ese requisito. Una futura captura automática debería entrar por contratos validados, con pertenencia, fuente/fecha/conflictos definidos; no escribir directamente en tablas ajenas ni usar un framework de plugins, repositorio base o bus de eventos sin consumidores. La capacidad de evitar duplicados y no-op existente no resuelve por sí sola reconciliación de observaciones externas.
|
||||
|
||||
Un perfil con requisitos de compatibilidad necesitaría una decisión propia; no debe confundirse con `ApplicationConfiguration` ni abrir SO en el propósito application-only. El coste y propietario de una futura regla deben fijarse antes de agregar dependencias. D08.2 continúa sin adopción implementada; no autoriza convertir locales, reescribir UUID o fusionar catálogos [E17, E22, E25].
|
||||
|
||||
## 14. Decisiones pendientes
|
||||
|
||||
1. ¿Se necesita registrar explícitamente ausencia confirmada y desconocimiento? ¿Qué evidencia y vigencia los acreditan?
|
||||
2. ¿Qué campo de fábrica será canónico en servidor y qué hacer si los dos valores existentes difieren? No decidir una migración sin revisar datos autorizados.
|
||||
3. ¿Se requiere origen por valor, fecha efectiva, última verificación y resolución de conflictos manual/agente/fabricante?
|
||||
4. ¿Una reinstalación necesita identidad/evento propios y fecha efectiva, o basta cerrar/crear? ¿Se permiten instancias simultáneas del mismo producto?
|
||||
5. ¿Debe prohibirse o limitarse el borrado físico y conservar auditoría central? ¿Quién autoriza corregir un alta errónea?
|
||||
6. ¿Se requiere reproducción histórica de identidad/etiquetas del producto y licencia, además de sus UUID?
|
||||
7. ¿La necesidad de VM/hipervisor es documental o exige lifecycle, descubrimiento, instancias y recursos?
|
||||
8. ¿Licencias solo documentales o gestión de cupos, cobertura comercial, OEM, volumen y transferencias?
|
||||
9. ¿Hace falta compatibilidad SO/perfil o no existe todavía un consumidor funcional que la justifique?
|
||||
10. ¿Qué activos pueden tener SO y qué reglas de elegibilidad, además de pertenencia/no retirado, se necesitan?
|
||||
|
||||
Estas preguntas corresponden a responsables funcionales/arquitectónicos. No se resuelven creando campos, estados, migraciones o componentes en esta intervención.
|
||||
|
||||
## 15. Evidencias de código y pruebas consultadas
|
||||
|
||||
Rutas relativas al repositorio; enlaces con línea inicial y símbolos relevantes. La evidencia de persistencia son definiciones de migración/mapper y código transaccional, **no un esquema instalado inspeccionado**.
|
||||
|
||||
| ID | Ruta / símbolo y líneas relevantes |
|
||||
| --- | --- |
|
||||
| E01 | [asset-catalog-seed.ts](../../modules/assets/src/api/domain/asset-catalog-seed.ts#L52): `factory_os` 52–55; `factory_preloaded_os` 122–125; laptop/desktop 137–179; server 267–278; `relation:hosted` 357–364 |
|
||||
| E02 | [asset.model.ts](../../modules/assets/src/api/infrastructure/persistence/asset.model.ts#L21): `assets`/`asset_revisions` 21–46; [asset-relational-schema.ts](../../modules/assets/src/api/infrastructure/persistence/asset-relational-schema.ts#L75): especificaciones y características 75–108 |
|
||||
| E03 | [asset.aggregate.ts](../../modules/assets/src/api/domain/asset.aggregate.ts#L100): `update` y revisiones; [asset.routes.ts](../../modules/assets/src/api/infrastructure/http/asset.routes.ts#L90): endpoints y actor 90–127 |
|
||||
| E04 | [resource.model.ts](../../modules/resources/src/api/infrastructure/persistence/resource.model.ts#L25): tablas/revisiones; [resource-scope-schema.ts](../../modules/resources/src/api/infrastructure/persistence/resource-scope-schema.ts#L18): cláusulas/inspector; [resource-scope-migration.ts](../../modules/resources/src/api/infrastructure/persistence/resource-scope-migration.ts#L47): migración explícita |
|
||||
| E05 | [software-installation-values.ts](../../modules/companies/src/api/domain/software-installation-values.ts#L5): input 5–12, transiciones 22–28; [software-installation-values.vo.ts](../../modules/companies/src/api/domain/value-objects/software-installation-values.vo.ts#L52): terminales y destino 52–68 |
|
||||
| E06 | [software-installation.aggregate.ts](../../modules/companies/src/api/domain/software-installation.aggregate.ts#L24): create 24–45, no-op/destino/transiciones/history 64–99 |
|
||||
| E07 | [software-installation-migration.ts](../../modules/companies/src/api/infrastructure/persistence/software-installation-migration.ts#L20): tablas/FK/unique 20–54; [software-installation-persistence.mapper.ts](../../modules/companies/src/api/infrastructure/persistence/software-installation-persistence.mapper.ts#L125): `currentKey` 125–130; [software-installation-snapshot.ts](../../modules/companies/src/api/infrastructure/persistence/software-installation-snapshot.ts#L7): columnas |
|
||||
| E08 | [software-installation-policy.ts](../../modules/companies/src/api/application/software-installation-policy.ts#L6): referencias/disponibilidad/licencia; [software-installation-input.mapper.ts](../../modules/companies/src/api/application/software-installation-input.mapper.ts#L28): destino/licencia/duplicados 28–50 |
|
||||
| E09 | [software-installation.routes.ts](../../modules/companies/src/api/infrastructure/http/software-installation.routes.ts#L39): GET/POST, negociación, delete; [software-installation.use-cases.ts](../../modules/companies/src/api/application/software-installation.use-cases.ts#L38): creación/revisión y fecha 38–88 |
|
||||
| E10 | [sequelize-software-installation.repository.ts](../../modules/companies/src/api/infrastructure/persistence/sequelize-software-installation.repository.ts#L144): DELETE raíz/historial 144–150, persistencia 157–175, asociación 215–275; [software-installation-response.builder.ts](../../modules/companies/src/api/application/software-installation-response.builder.ts#L15): `needsReview` e historia |
|
||||
| E11 | [resource-catalog-seed.ts](../../modules/resources/src/api/domain/resource-catalog-seed.ts#L10): identidad, SO 170–180, evolución 214/286; [operating-system-identity-policy.ts](../../modules/resources/src/api/application/operating-system-identity-policy.ts#L40): alcance global e identidad publicada fija |
|
||||
| E12 | [global-application-template-policy.ts](../../modules/resources/src/api/application/global-application-template-policy.ts#L1): elegibilidad stock; [global-resource.routes.ts](../../modules/resources/src/api/infrastructure/http/global-resource.routes.ts#L292): superficies por tipo; [operating-system-identity.ts](../../modules/resources/src/common/operating-system-identity.ts#L3): edición/normalización |
|
||||
| E13 | [resource-reference-reader.ts](../../modules/resources/src/api/infrastructure/persistence/resource-reference-reader.ts#L71): validación asociación, filtros de propósito 195–199; [resource-global-gates.ts](../../modules/resources/src/api/application/resource-global-gates.ts#L26): dependencias/propósito; [verify-global-resource-enablement.ts](../../modules/resources/src/api/infrastructure/persistence/verify-global-resource-enablement.ts#L6): integridad antes de abrir |
|
||||
| E14 | [resource-catalog-seed.ts](../../modules/resources/src/api/domain/resource-catalog-seed.ts#L104): producto/modalidad/métrica/cantidad de licencia; [software-installation-policy.ts](../../modules/companies/src/api/application/software-installation-policy.ts#L20): vigencia UTC |
|
||||
| E15 | [workstation-values.vo.ts](../../modules/assets/src/api/domain/value-objects/workstation-values.vo.ts#L49): principal miembro; [workstation-input.mapper.ts](../../modules/assets/src/api/application/workstation-input.mapper.ts#L41): miembros/disponibilidad/exclusividad; [workstation.model.ts](../../modules/assets/src/api/infrastructure/persistence/workstation.model.ts#L32): tablas; [workstation.aggregate.ts](../../modules/assets/src/api/domain/workstation.aggregate.ts#L56): descomposición y revisiones |
|
||||
| E16 | [asset-relationship.aggregate.ts](../../modules/assets/src/api/domain/asset-relationship.aggregate.ts#L9): identidad/extremos/alta-retirada; seed en E01 |
|
||||
| E17 | [work-profile-values.ts](../../modules/companies/src/api/domain/work-profile-values.ts#L3): persona/activo/cuenta; [work-profile-input.mapper.ts](../../modules/companies/src/api/application/work-profile-input.mapper.ts#L8): contexto; [application-configuration-values.ts](../../modules/companies/src/api/domain/application-configuration-values.ts#L5): delivery/estado; [application-configuration-policy.ts](../../modules/companies/src/api/application/application-configuration-policy.ts#L6): solo application |
|
||||
| E18 | [current-operating-systems.ts](../../modules/companies/src/web/software-installations/shared/helpers/current-operating-systems.ts#L7): selección compañía/activo, estado/tipo y no resuelto; [use-asset-software.ts](../../modules/companies/src/web/software-installations/shared/queries/use-asset-software.ts#L11): consulta compartida |
|
||||
| E19 | [current-operating-systems.tsx](../../modules/companies/src/web/software-installations/detail/ui/current-operating-systems.tsx#L24): error/carga/versión/inactivo/vacío 24–89 |
|
||||
| E20 | [use-workstation-detail-controller.ts](../../modules/companies/src/web/workstations/detail/controllers/use-workstation-detail-controller.ts#L29): principal válido y lectura 29–41; [workstation-detail-content.tsx](../../modules/companies/src/web/workstations/detail/ui/workstation-detail-content.tsx#L156): resumen |
|
||||
| E21 | [company-inventory.tsx](../../modules/companies/src/web/companies/detail/ui/company-inventory.tsx#L58): composición del resumen en activo; [use-workstation-software.ts](../../modules/companies/src/web/workstations/detail/controllers/use-workstation-software.ts#L6): referencia global/local; [workplace-map-model.ts](../../modules/companies/src/web/workstations/detail/helpers/workplace-map-model.ts): nodos de software del principal |
|
||||
| E22 | [identity-runtime.ts](../../apps/server/src/infrastructure/http/identity-runtime.ts#L30): composición/gates y puertos 30–65; [companies/module.ts](../../modules/companies/src/api/module.ts#L167): `initializeProfiles`, repositorio de instalación 186–189 |
|
||||
| E23 | [software-del-ordenador.md](../software-del-ordenador.md#software-del-ordenador): declaración de eliminar alta errónea y sus revisiones; código en E10 |
|
||||
| E24 | [software-installation.test.ts](../../modules/companies/src/api/domain/software-installation.test.ts#L53): referencias/identidad/historia/licencia; [current-operating-systems.test.tsx](../../modules/companies/src/web/software-installations/shared/current-operating-systems.test.tsx#L139): OSUI-01–18, globales y estados; [global-operating-systems-closure.integration.test.ts](../../apps/server/src/global-operating-systems-closure.integration.test.ts): DB-SO-01–20 y contratos, consultados sin ejecución |
|
||||
| E25 | [composicion-de-modulos.md](../arquitectura/composicion-de-modulos.md#L3): constructor injection, servicios, prohibición ORM ajeno; [D08.2](resources-global-scope/19-d08-2-resolucion-ratificacion.md): ratificable con condiciones, sin adopción implementada |
|
||||
|
||||
Comprobaciones de esta entrega: correspondencia de rutas/símbolos, lectura de pruebas/migraciones, separación de modelos actual/propuesto y revisión del diff documental. No se han ejecutado las validaciones técnicas ni visuales de las implementaciones anteriores; sus resultados no se presentan como nuevos. La implementación de cualquier extensión queda fuera de esta auditoría.
|
||||
@ -1,8 +1,8 @@
|
||||
# Preflight de activación — Catálogo global de sistemas operativos
|
||||
|
||||
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, diagnósticos y corrección con MariaDB desechable en el apartado 17. **No se ha conectado a producción, leído `.env`, ejecutado migraciones productivas, 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.
|
||||
**Dictamen vigente: apartado 17, corrección local sobre `a72d6a2ec8985e1d51f74db68a1b3aabb3693030`: B-01 RESUELTO, B-02 RESUELTO EN ENTORNO REPRESENTATIVO; snapshot global NO PREPARADO.** Los apartados 1–16 conservan los diagnósticos históricos anteriores a esta corrección. El apartado 17 añade las pruebas con persistencia exclusivamente desechable y distingue los cambios concurrentes.
|
||||
|
||||
## 1. Resumen y dictamen
|
||||
|
||||
@ -311,3 +311,150 @@ El rollback propuesto conserva lectores v3, UUID, productos, instalaciones e his
|
||||
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.
|
||||
|
||||
## 16. Reevaluación de a72d6a2e
|
||||
|
||||
Fecha: 26 de octubre de 2026. **Candidato: `a72d6a2ec8985e1d51f74db68a1b3aabb3693030`, rama `main`; árbol limpio al comenzar.** Node 24.20.0 y PNPM 11.25.0. Se aplica `rdx-solid` para contrastar diferencias y ejecutar comprobaciones proporcionadas; no se rediseña Resources ni se modifica código funcional.
|
||||
|
||||
### Trazabilidad y alcance de las diferencias
|
||||
|
||||
El candidato está confirmado en Git y puede recuperarse mediante checkout de ese SHA; no depende del overlay histórico para identificar su código. Los objetos de `git archive` se compararon con las 1884 fuentes del GO: **1854 coinciden, 30 cambiaron y hay 9 fuentes añadidas** en los ámbitos registrados. El lockfile conserva SHA-256 `bcd468ab1f127fffeb7f37ef3bdb5f2c5e85d40c192f3bc4a7cae5b2209b7c17`. No se ejecutó instalación limpia de dependencias; sigue siendo una comprobación pendiente para el empaquetado final.
|
||||
|
||||
Desde `6992a4ac`, los commits `0a5fb282` y `a72d6a2e` incorporan las modificaciones concurrentes anteriormente pendientes y nuevas integraciones visuales. La relación completa figura en [traceability.json](../../.artifacts/resources-global-operating-systems-preflight-a72d6a2e/traceability.json).
|
||||
|
||||
| Diferencia desde la reevaluación anterior | Clasificación | Tratamiento |
|
||||
| --- | --- | --- |
|
||||
| Obtención de infraestructura desde el inventario y exportación del helper | Frontend Assets | Pruebas de infraestructura y tipos reejecutados |
|
||||
| Navegación de compañía/personas, contexto de listas y rutas | Frontend Companies | Pruebas de compañía/personas/rutas y tipos reejecutados; dos aserciones de presentación siguen fallando |
|
||||
| Creación de carpeta SMB y enlace posterior a publicación | Frontend Assets/Companies/Resources | Cambia composición de formularios y feedback; se revalidan Resources y publicaciones, sin guardar datos reales |
|
||||
| Callback `onSaved(id, resource)` y props de lista/editor | Contrato frontend entre consumidores | Tipado de los cuatro módulos/aplicación revalidado; no cambia DTO HTTP ni contrato Resources 2/3 |
|
||||
| Traducciones, documentación, scripts de auditoría y evidencias | Textos/documentación/validación | No confundir sus resultados históricos con ejecuciones de este preflight |
|
||||
| Backend, persistencia, seguridad, flags, tsup y lockfile | Sin cambios desde `6992a4ac` | Reutilizables los antecedentes técnicos de esas fronteras; se reproducen B-01/B-02 y se conserva pendiente el destino |
|
||||
|
||||
**El GO completo no se transfiere automáticamente al nuevo frontend.** La documentación SMB incorporada registra comprobaciones de su intervención; no se presentan como pruebas nuevas de este preflight ni como certificación de los 35 recorridos SO sobre este candidato. Antes de aprobar una release sigue pendiente la validación visual dirigida de los consumidores modificados y compartidos, con evidencias correspondientes al código que se publique.
|
||||
|
||||
### Resultados nuevos
|
||||
|
||||
| Comprobación | Resultado |
|
||||
| --- | --- |
|
||||
| `pnpm exec turbo run build --force` | Servidor/web compilan, 2 tareas correctas; advertencia de chunk web >500 kB |
|
||||
| Hashes de dist | 12 artefactos identificados; servidor idéntico al de `6992a4ac`, web diferente |
|
||||
| `node --check apps/server/dist/index.js` | **NO CUMPLE, B-01**: salida 1 por `createRequire` duplicado, línea 23345 del bundle generado |
|
||||
| Diagnóstico proxy local sin BD | **NO CUMPLE, B-02 en TLS→HTTP**: `trustProxy=false`, `secure=false`, `sessionCookieIssued=false`; experimento completado con salida 0 |
|
||||
| Resources completo | **124/124**, HTTP de prueba en loopback y sin BD |
|
||||
| Assets: infraestructura/publicaciones SMB | **9/9** |
|
||||
| Companies: compañía/personas/rutas | **29/31**; fallan «Personas (1)» y «Esta persona no tiene acceso a Metis Desk» |
|
||||
| Typechecks directos Resources, Companies, Assets y Web | Los cuatro correctos |
|
||||
| Biome/ESLint sobre 25 archivos cambiados TS/TSX/JSON | Ambos correctos |
|
||||
| `pnpm typecheck` raíz | Salida 2; continúa TS2375 en calendario vendor, `locale` opcional |
|
||||
| `pnpm lint` raíz | Salida 1; 15 errores, 1 warning y 5 infos |
|
||||
| `pnpm check` raíz | Salida 1; 173 errores, 1 warning y 5 infos |
|
||||
|
||||
Los dos fallos de Companies se han vuelto a ejecutar; ya no se describen aquí como estado desconocido del trabajo concurrente. Coinciden en sus expectativas con fallos previos, pero quedan pendientes de conciliar con la presentación actual; no se asume que su antigüedad justifique ignorarlos. Su responsable es Companies. No impiden compilar; sí impiden declarar satisfactoria esa validación y requieren resolución o decisión explícita de aceptación para una release. No se debilitaron asserts ni se modificaron los tests.
|
||||
|
||||
B-01 sigue siendo un bloqueo absoluto del artefacto, responsabilidad de build/servidor: éxito de tsup no garantiza que Node pueda interpretarlo. B-02 corresponde a backend/operación y a la topología TLS aprobada. No se modificaron banner, sharp, Secure ni confianza en proxy. La deuda de calendario, lint/formato y ejemplos permanece fuera de las correcciones de este encargo; su efecto sobre la política CI debe aceptarse o resolverse expresamente.
|
||||
|
||||
### Persistencia, destino y plan operativo
|
||||
|
||||
No cambian los requisitos de MariaDB, plantilla SO, FKs/CHECK/índices o migración R2.1b descritos en los apartados 6–7. **PENDIENTE DE VERIFICACIÓN** en el destino: versión/motor, esquema efectivamente aplicado, bindings y datos existentes, controles activos, clientes desplegados, proxy, sesiones, backup/restauración, supervisor y valores efectivos de configuración. No se ha obtenido acceso autorizado de lectura al destino ni se han ejecutado las consultas SQL preparadas.
|
||||
|
||||
Contratos v2/v3, propósitos, guard administrativo y seis nombres/dependencias de gates siguen sin cambios. Mantener el orden documentado: persistencia preparada → lectores v3 → frontend compatible → catálogo administrativo SO → selección/asociación SO conjuntas. No hay etapa de selección SO independiente ni gate por compañía; no se añaden para forzar un piloto. Los cuatro flags application conservan su autorización propia y los valores efectivos no se infieren de defaults.
|
||||
|
||||
El plan de smoke de lectura/escritura y los ocho escenarios de rollback siguen preparados en los apartados 10–11. Cerrar nuevas dependencias/altas no borra ni reescribe referencias: deben mantenerse backend/lectores v3, UUID, licencias, instalaciones e historial, incluyendo inactivos y retirada legítima. No es un rollback productivo probado. No se restaura un backend incapaz de leer SO global ni se implementa D08.2.
|
||||
|
||||
No se repitieron suites MariaDB ni preparación Playwright con persistencia: no cambió el backend y este preflight no autoriza migraciones ni escrituras de negocio. Los resultados anteriores 29/29 + 50/50 se conservan como evidencia de integración de las fronteras sin cambios. La validación visual pendiente debe realizarse sobre una release identificada y un entorno aislado autorizado para preparar datos.
|
||||
|
||||
### Dictamen y evidencias
|
||||
|
||||
**NO PREPARADO**, para `a72d6a2ec8985e1d51f74db68a1b3aabb3693030` y sus artefactos generados localmente. Se confirma trazabilidad, pero B-01 impide ejecutar el servidor y B-02 sigue abierto para TLS→HTTP; también faltan aceptación de las pruebas fallidas, validación visual actual y comprobaciones reales del entorno. No procede elevar este candidato a «preparado para solicitar autorización» por el mero hecho de tener GO de un snapshot anterior.
|
||||
|
||||
Evidencias nuevas y comandos: [manifiesto a72d6a2e](../../.artifacts/resources-global-operating-systems-preflight-a72d6a2e/manifest.json). Se preservan los informes anteriores. Los logs corresponden a esta ejecución; ninguna evidencia de producción ni activación se declara realizada. Esta intervención solo actualiza el informe y conserva diagnósticos; no cambia funcionalidad, configuración privada, datos reales, flags o despliegues.
|
||||
|
||||
## 17. Resolución correctiva B-01/B-02
|
||||
|
||||
### Snapshot y alcance
|
||||
|
||||
Intervención autorizada el 26-10-2026, sobre `main`, HEAD `a72d6a2ec8985e1d51f74db68a1b3aabb3693030` **más el diff local correctivo**, sin commit nuevo. El antecedente solicitado `6992a4ac` y la reevaluación de `a72d6a2e` se conservan en los apartados 15–16. Los diagnósticos anteriores describen esos snapshots sin la corrección; no son el dictamen del diff actual.
|
||||
|
||||
El árbol contiene además cambios concurrentes de interfaz SMB, documentación y auditorías, identificados en el manifiesto. Se han conservado. Esta intervención modifica build/configuración/HTTP del servidor, el borrado de cookie en Identity, scripts de prueba y documentación. No cambia el dominio de Resources, Assets o Companies, contratos v2/v3, gates, esquema, migraciones ni D08.2. No se ha accedido a producción ni cargado `.env`.
|
||||
|
||||
Skill consultada: `rdx-solid`, aplicada a la reproducción antes de corregir, responsabilidad de cada capa y pruebas de comportamiento. No se ha realizado trabajo visual ni una auditoría Playwright nueva; los cambios concurrentes de frontend siguen requiriendo validación propia.
|
||||
|
||||
### B-01: procedencia y corrección
|
||||
|
||||
La compilación anterior con tsup 8.5.1 terminaba, pero Node 24.20.0 rechazaba `apps/server/dist/index.js` con `SyntaxError: Identifier 'createRequire' has already been declared`, línea 23345. El servidor tiene un único entrypoint, formato ESM, target Node 24. El banner introducía `import { createRequire } from "node:module"` en la primera línea. El código empaquetado de `sharp/dist/sharp.mjs` aportaba otra importación de `createRequire` desde `module` en el mismo ámbito. El banner es texto externo al análisis léxico de esbuild y no participa en su renombrado de símbolos. No se ha demostrado un defecto de sharp.
|
||||
|
||||
Se externaliza `sharp`, que necesita resolver sus binarios nativos desde su paquete, y se declara como dependencia runtime directa de `@metis/server` en la versión **0.35.5 ya existente**. Antes de añadirla, la resolución desde el paquete servidor fallaba con `MODULE_NOT_FOUND`; confiar solo en la dependencia transitiva de Companies no basta con PNPM. El lockfile únicamente incorpora ese enlace en el importer del servidor, sin cambiar versiones. El banner conserva el puente `require` para las dependencias CommonJS, importando `createRequire as createServerRequire`.
|
||||
|
||||
Eliminar todo el banner podía romper dependencias CommonJS. Cambiar únicamente el alias solucionaba la colisión sintáctica pero conservaba el empaquetado de la dependencia nativa. Externalización más dependencia directa y alias evita ambos problemas con la configuración existente. No se cambia ESM/CJS ni se reescribe el bundle mediante expresiones regulares.
|
||||
|
||||
El comando oficial `build` termina ahora con `check:build` (`node --check dist/index.js`), que también falla si falta el artefacto. Se incorporó primero el guard y se observó el fallo antes de corregir. Dos builds limpios sucesivos produjeron JS y sourcemap **idénticos byte a byte**; los hashes figuran en las evidencias. El build raíz también pasa, con la advertencia ya conocida del tamaño del chunk web.
|
||||
|
||||
La prueba permanente `pnpm test:server-runtime` ejecuta el proceso **compilado**, sin tsx ni importación sustitutiva del código fuente: arranque anunciado, salud `/api/__health`, sesiones SQL, procesamiento y recuperación de logos y fotos PNG/JPEG/WebP, conversión a WebP de anchura 128 y rechazo de imagen corrupta. El proceso termina por SIGTERM con salida 0; existe un límite de cierre que haría fallar el test si necesitara SIGKILL. No es una comprobación limitada a `node --check`.
|
||||
|
||||
### B-02: causa, política y configuración
|
||||
|
||||
Un control negativo nuevo con certificado TLS validado y la política original sin proxies reproduce `secure=false`, protocolo HTTP y ausencia de cookie (véase `b02-negative-control.json` en las evidencias). No se presenta como código antiguo recompilado: conserva explícitamente su política original de confianza. En el diagnóstico anterior, Express mantenía `trust proxy=false`. Aunque el cliente usaba HTTPS hasta el proxy, Node recibía HTTP y `req.secure=false`. Identity ya configuraba `cookie.secure=true` en producción: `express-session` evitaba emitir la cookie, en lugar de emitirla sin Secure. Faltaba un contrato de confianza configurable del servidor. La corrección pertenece al servidor y al borde HTTP de Identity; no cambia autorización por compañía.
|
||||
|
||||
Se añade `TRUSTED_PROXY_CIDRS`, validada al arrancar y aplicada a Express antes de las sesiones. Solo admite IPs/CIDRs explícitas, máximo 32, sin hosts, alias, número de saltos ni confianza universal. Producción exige una lista no vacía; desarrollo conserva confianza cerrada por defecto. Se rechazan encabezados `X-Forwarded-Proto` duplicados, listas o valores no canónicos antes de sesión. Una conexión no confiable no eleva `req.secure` aunque anuncie `https`.
|
||||
|
||||
El logout tenía además un desajuste de atributos: borraba la cookie sin copiar Secure. Ahora conserva el valor de seguridad de la cookie de sesión antes de destruirla. Se mantienen HttpOnly, SameSite Strict, Path, ámbito sin Domain, duración de ocho horas y política de renovación existente. Login posterior genera otra sesión; la anterior queda inválida en SQL.
|
||||
|
||||
La topología representativa verificada es **HTTPS → un terminador TLS local → HTTP Node**. El certificado local se valida explícitamente; no se desactiva la verificación TLS. Las IPs loopback `.2`/`.3` son datos sintéticos para distinguir proxies confiables y no confiables, no recomendaciones de producción. El proxy de prueba sobrescribe protocolo, cadena de cliente y host y retira `Forwarded`. La infraestructura real, sus IPs, origen, reglas de cabeceras y accesos siguen pendientes del administrador. La confianza por IP no distingue procesos que comparten origen/NAT: restringir el acceso al backend es obligatorio.
|
||||
|
||||
Configuración por entorno y procedimiento: [Proxy TLS y sesiones](../proxy-tls-y-sesiones.md). No se ha introducido un proxy productivo nuevo ni elegido direcciones reales. No se certifica una cadena de varios terminadores; TLS-08 comprueba la sobrescritura de una cadena inyectada en la topología de un solo proxy.
|
||||
|
||||
### Matriz ejecutada
|
||||
|
||||
| ID | Resultado nuevo |
|
||||
| --- | --- |
|
||||
| TLS-01 | HTTPS real mediante proxy confiable: `req.secure=true`, cookie Secure |
|
||||
| TLS-02 | HTTP directo en producción: sin emisión de cookie de sesión insegura |
|
||||
| TLS-03 | HTTP directo con protocolo falsificado: no eleva confianza |
|
||||
| TLS-04 | Bootstrap/login y consulta posterior reales con sesión SQL válida |
|
||||
| TLS-05 | Proxy TLS de dirección no confiable: sin elevación ni cookie |
|
||||
| TLS-06 | Proxy confiable informa HTTP: `req.secure=false`, sin cookie productiva |
|
||||
| TLS-07 | Protocolo duplicado, contradictorio, lista o inválido: 400 sin cookie |
|
||||
| TLS-08 | Cadena cliente inyectada sobrescrita; IP observada correcta. Varios proxies reales: no aprobados/no verificados |
|
||||
| TLS-09 | Logout real invalida SQL; borrado con Secure/HttpOnly/Strict/Path y expiración pasada |
|
||||
| TLS-10 | Servidor compilado en `NODE_ENV=development`: HTTP, HttpOnly/Strict, sin Secure y sin confianza implícita |
|
||||
| TLS-11 | Servidor compilado en producción sin proxies: arranque rechazado antes de anunciar disponibilidad |
|
||||
| TLS-12 | Emisión posterior y relogin conservan Secure; cambia el identificador de sesión |
|
||||
|
||||
Las pruebas comprueban también ausencia de Domain y expiración de ocho horas. Se conservan en evidencias únicamente `metis.sid` y sus atributos, nunca valores de cookie, claves privadas, contraseñas ni entorno privado. MemoryStore se usa solo en la matriz HTTP de política; el recorrido compilado utiliza el almacén SQL real. Origen incorrecto y CSRF inválido mantienen 403.
|
||||
|
||||
### Validaciones y evidencias nuevas
|
||||
|
||||
| Comprobación | Resultado |
|
||||
| --- | --- |
|
||||
| Build servidor + guard de sintaxis, repetidos | Correctos; artefactos reproducibles en este entorno |
|
||||
| `pnpm build` | Correcto, servidor y web; advertencia de chunk web >500 kB |
|
||||
| `pnpm --filter @metis/server test:http` | **19/19** |
|
||||
| `pnpm test:server-runtime` | **8/8**, MariaDB desechable retirada |
|
||||
| Sharp: `company-logo-processor.test.ts` | **4/4**, formatos, inválidos, límites y animación |
|
||||
| Cierre SO con regresiones, comando oficial | **29/29 + 52/52**; dos regresiones nuevas de configuración explican el aumento desde 50 |
|
||||
| Typechecks Server e Identity | Ambos correctos |
|
||||
| Biome y ESLint del diff correctivo TS/configuración | Correctos |
|
||||
| `pnpm typecheck` raíz | Falla por TS2375 preexistente en calendario vendor (`locale` opcional) |
|
||||
| `pnpm lint` / `pnpm check` raíz | Fallan: lint 15 errores, check 173 errores; ambos con 1 warning y 5 infos. Detalles en el manifiesto |
|
||||
| Tests de paquetes sin servidor | 7 tareas correctas; Companies falla en las mismas dos aserciones de presentación del apartado 16 |
|
||||
|
||||
La prueba de runtime usa una compañía y una persona sintéticas: comprueba fronteras de autenticación e imágenes, no listados ni carga. El cierre SO utiliza sus fixtures amplias de 15 compañías y 50 personas/activos/puestos/perfiles y al menos 53 recursos en la principal. No se declara una auditoría visual con ese volumen en esta intervención. Toda persistencia se ejecutó exclusivamente sobre `metis_desk_test` en contenedores desechables.
|
||||
|
||||
No se ejecutó el `pnpm test` raíz completo porque la rama histórica de integración del servidor carga `.env`. Se ejecutaron por separado HTTP sin `.env`, runtime compilado aislado, cierre SO con regresiones y tests de los demás paquetes. Una primera ejecución de integración detectó un import no exportado en la nueva prueba; se corrigió su import sin cambiar la API de Companies y se repitió correctamente. No se modificaron asserts ajenos para ocultar fallos.
|
||||
|
||||
Comandos reproducibles y logs, hash de fuentes/artefactos, estado del árbol y resultados se recogen en [manifest.json](../../.artifacts/server-runtime-security/manifest.json). [runtime-results.json](../../.artifacts/server-runtime-security/runtime-results.json) acredita los atributos de cookie y el recorrido compilado; [integration.log](../../.artifacts/server-runtime-security/integration.log) contiene los resultados. Los builds idénticos no certifican otras plataformas nativas ni un entorno de dependencias distinto.
|
||||
|
||||
### Dictamen correctivo y condiciones restantes
|
||||
|
||||
| Ámbito | Dictamen |
|
||||
| --- | --- |
|
||||
| B-01 | **RESUELTO**: build, sintaxis, arranque compilado, imágenes y cierre verificados |
|
||||
| B-02 | **RESUELTO EN ENTORNO REPRESENTATIVO**: TLS real, confianza restringida, sesiones SQL y falsificación comprobados |
|
||||
| Validación de producción | **PENDIENTE** |
|
||||
| Catálogo global SO | **GO previo preservado**, con cierre y regresiones reejecutados para las fronteras afectadas |
|
||||
| Snapshot global | **NO PREPARADO** |
|
||||
|
||||
Antes de una release siguen pendientes: topología/IPs/origen y seguridad del proxy real, plataforma y dependencias nativas de destino, esquema/motor MariaDB, gates efectivos, supervisor y comprobación de cookies en el origen público autorizado. Además, validar visualmente el frontend actual y certificar el trabajo concurrente, y resolver o aceptar explícitamente los fallos globales y las dos aserciones de Companies. El dominio SO no pierde su cierre técnico, pero este no acredita automáticamente todo el snapshot.
|
||||
|
||||
No se desplegó, no se ejecutaron migraciones reales, no se cambiaron gates ni datos reales. La corrección de B-01/B-02 no autoriza activación productiva.
|
||||
|
||||
@ -1762,3 +1762,38 @@ reintento y foco opcionales para el acceso contextual. Las opciones del panel y
|
||||
editor contextual se calculan con `softwareInstallationOptions`, sin duplicar reglas
|
||||
de negocio. Véanse la [matriz auditada](auditorias/workplace-node-actions/02-matriz-acciones.md)
|
||||
y el [informe](mapa-visual-del-puesto.md#wd045--acciones-contextuales-en-nodos).
|
||||
|
||||
## Ficha especializada de carpeta SMB
|
||||
|
||||
`ResourcesPage.detailContent(resource, data, canManage)` permite componer una ficha específica
|
||||
sin duplicar el inventario: devolver `undefined` conserva el detalle genérico. Se aplica en
|
||||
el panel y en el diálogo, con los datos actuales ya renderizados por Resources.
|
||||
`CompanyNetworkShareDetail` une esa extensión con el bloque público
|
||||
`NetworkShareResourceDetail` de Assets y la navegación contextual a accesos.
|
||||
|
||||
La publicación usa `DetailPanel` compacto, `EntityStatus`, `EmptyState`, skeleton,
|
||||
`PublishedShareAsset` y el editor existente. Datos e historial se apilan y pasan a dos
|
||||
columnas a partir de 900 px del contenedor. `PublicationHistory` transforma periodos reales
|
||||
mediante `publicationEvents` y los presenta con `TimeLine`, conservando cursores y orden.
|
||||
El historial de valores del recurso continúa accesible mediante `ResourceRevisionHistory`,
|
||||
plegable en la ficha SMB. `NetworkShareAddress` se comparte entre datos y publicador.
|
||||
|
||||
`DetailPanel` limita su grupo de acciones al ancho disponible para que puedan envolver.
|
||||
`FormDialog` limita la altura visible del título a 25 dvh, permitiendo desplazar nombres
|
||||
largos, y acota el padding vertical de cabecera con la ventana. Esta regla mantiene espacio
|
||||
para el cuerpo cuando se amplía el texto; no recorta el título en el DOM. El editor de
|
||||
publicación alterna con el selector para evitar un tercer modal sobre la ficha de detalle.
|
||||
|
||||
En la iteración v2, `NetworkShareResourceTitle` limita la vista inicial del nombre y
|
||||
permite expandirlo por teclado cuando el `ResizeObserver` detecta recorte. Conserva el
|
||||
nombre completo en el árbol accesible. `NetworkShareAddress` ofrece un bloque completo
|
||||
y un atributo compacto, con copia literal, icono con nombre accesible y feedback de
|
||||
estado sin añadir altura. Los saltos visuales no cambian los caracteres de la UNC.
|
||||
|
||||
`PublicationEventDetails` separa nombres y enlaces del detalle técnico expandible.
|
||||
Companies puede aportar nombres ya autorizados de las revisiones del recurso mediante
|
||||
un mapa opcional; sin coincidencia se conserva el UUID, sin consultas al directorio.
|
||||
El contorno de foco opaco se aplica localmente a la ficha SMB. Se mantienen las
|
||||
primitivas compartidas y las operaciones y consultas existentes.
|
||||
|
||||
Véanse [la auditoría y las evidencias de la ficha](ficha-de-carpeta-compartida.md).
|
||||
|
||||
375
docs/ficha-de-carpeta-compartida.md
Normal file
375
docs/ficha-de-carpeta-compartida.md
Normal file
@ -0,0 +1,375 @@
|
||||
# Ficha de carpeta compartida
|
||||
|
||||
Los apartados iniciales documentan la iteración v1. La [iteración v2](#iteración-uiux-v2--refinamiento-visual-responsive-y-accesibilidad) recoge el refinamiento posterior y actualiza sus resultados y límites.
|
||||
|
||||
## Auditoría y adaptación del mockup
|
||||
|
||||
La tarea principal es reconocer y gestionar el activo que publica una carpeta SMB.
|
||||
La ficha existente usa `ResourceSelectionPanel` y `ResourceDetailDialog`; no existe
|
||||
una página independiente que justifique otro layout global. Se conserva este patrón y
|
||||
se añade una extensión específica de detalle, compuesta desde Companies. El resto de
|
||||
plantillas continúa utilizando su detalle genérico.
|
||||
|
||||
Antes del cambio, las características y revisiones del recurso precedían a una tabla de
|
||||
publicaciones. La relación principal quedaba relegada y el historial exigía desplegar
|
||||
otra tabla. El editor no mostraba el recurso y publicador actual al trasladar o cerrar.
|
||||
|
||||
Assets es propietario de las publicaciones. Se reutilizan sus consultas tipadas,
|
||||
resúmenes, selector remoto, comandos `publish` / `transfer` / `end`, periodos UUID,
|
||||
cursores e invalidaciones. Resources conserva la ficha y su dirección. La relación
|
||||
admite cero o un publicador vigente por recurso y varios periodos anteriores.
|
||||
|
||||
La estructura del mockup se adapta a Inter, tokens semánticos, iconos Tabler y acciones
|
||||
primarias rojas. Publicación a ancho completo; datos e historial en dos columnas cuando
|
||||
el contenedor alcanza 900 px; navegación contextual debajo. En el panel lateral y en
|
||||
móvil se apilan en el mismo orden. No se introduce una paleta alternativa ni vendor.
|
||||
|
||||
## Límites de los datos reales
|
||||
|
||||
- Los resúmenes exponen nombre, plantilla y estado del activo. No incluyen icono por
|
||||
plantilla ni sistema operativo: se usa un icono genérico de activo, sin inventar roles.
|
||||
- Los campos de la ficha proceden de su especificación real: UNC, servidor y descripción,
|
||||
además de campos personalizados como Ruta de acceso si ya existen. No se añade un campo
|
||||
local inexistente a todas las instalaciones ni se alteran valores.
|
||||
- No hay un contrato que certifique la correspondencia entre host UNC y activo. Se indica
|
||||
«Correspondencia no verificada», sin diagnosticar errores por comparar nombres.
|
||||
- Los actores del historial llegan como UUID, no como nombres de usuario. Se presentan
|
||||
identificados como UUID. Cada alta/traslado y cierre muestra su fecha real.
|
||||
- Se conserva el orden de registro ascendente y la paginación del contrato; no se invierte
|
||||
solo una página para aparentar un orden descendente global. El activo anterior se
|
||||
muestra cuando su periodo y resumen están disponibles en esa página.
|
||||
- Hay navegación real a necesidades, concesiones, permisos, cuentas y grupos filtrados por
|
||||
recurso. No hay consulta directa de puestos ni relación específica con accesos remotos:
|
||||
se omiten los contadores y filas ficticias del mockup. Registrar una publicación no
|
||||
configura SMB ni determina disponibilidad o permisos efectivos.
|
||||
- La política vigente permite las operaciones a operadores autenticados; no hay un rol
|
||||
independiente de solo lectura para esta relación. La composición respeta la capacidad
|
||||
de gestión recibida, oculta las acciones en solo lectura y ante denegaciones o datos
|
||||
obsoletos. No se introduce otro modelo de permisos.
|
||||
|
||||
## Composición y responsabilidades
|
||||
|
||||
`ResourcesPage.detailContent` recibe recurso, datos actuales y capacidad de gestión.
|
||||
`CompanyNetworkShareDetail` compone `NetworkShareResourceDetail`, pasando los datos y la
|
||||
navegación mediante props y children. Assets conserva la coordinación de publicaciones,
|
||||
la tarjeta del publicador, el historial y el editor contextual. No se añade dependencia
|
||||
Resources → Assets, endpoints, migraciones ni modificaciones del dominio.
|
||||
|
||||
La ausencia de publicación vigente y una publicación finalizada tienen presentaciones
|
||||
distintas cuando el historial permite distinguirlas. La carga mantiene geometría mediante
|
||||
skeleton; el error ofrece reintento y las denegaciones retiran los datos de caché. Trasladar
|
||||
y finalizar capturan el UUID del periodo iniciado; un conflicto no cambia el comando por
|
||||
otro silenciosamente. El cierre explica que solo finaliza la asociación documental.
|
||||
|
||||
## Comparación con la referencia
|
||||
|
||||
| Referencia | Adaptación implementada |
|
||||
| --- | --- |
|
||||
| Cabecera con carpeta, nombre, subtítulo y acciones | Cabecera y toolbar de la ficha existente, con menú de ciclo de vida y edición |
|
||||
| Publicación dominante a ancho completo | Tarjeta con publicador real, plantilla, estado, UNC copiable y acciones |
|
||||
| Botones azules e iconos ilustrados | Acciones rojas y SVG Tabler del sistema de Metis |
|
||||
| Datos e historial en paralelo | Dos columnas desde 900 px del contenedor; una columna en panel y móvil |
|
||||
| Indicador de coincidencia del servidor | Estado neutral: no existe verificación técnica de aliases/DFS/clústeres |
|
||||
| Timeline con nombres y fechas | Fechas y UUID de responsables reales, nombres de activos obtenidos por resúmenes autorizados |
|
||||
| Contadores de puestos y accesos remotos | Se omiten; se ofrece la navegación contextual respaldada por AccessWorkspace |
|
||||
| Estado vacío mostrado junto al ejemplo activo | Estado alternativo real, no una tarjeta de demostración simultánea |
|
||||
|
||||
## Skills y revisión
|
||||
|
||||
`frontend-design` guía la jerarquía y la comparación con el mockup, subordinada a DESIGN.md.
|
||||
`ux-design-review` estructura la revisión de usabilidad, textos, estados y accesibilidad en
|
||||
uso; sus auditores especializados y su política externa no están instalados, por lo que
|
||||
no se atribuyen auditorías automáticas. Faltan, entre otros, `ux-heuristics-audit`,
|
||||
`ux-microcopy-audit`, `ux-accessibility-audit` y `ux-mobile-responsiveness-audit`.
|
||||
`shadcn` guía composición Base UI y reutilización.
|
||||
`vercel-composition-patterns` respalda la extensión por composición y la separación de
|
||||
las interacciones modales. `vercel-react-best-practices` mantiene los datos remotos en
|
||||
TanStack Query y el avance de acciones en callbacks, sin efectos derivados. `rdx-solid`
|
||||
guía ownership, contratos e implementación proporcional. No se añaden dependencias.
|
||||
|
||||
La revisión visual detectó y corrigió recorte del grupo de acciones a 320 px y falta de
|
||||
espacio para el cuerpo con títulos largos al ampliar el texto al 400 %. Las medidas se
|
||||
aplican a las primitivas propias compartidas, sin modificar vendor ni el shell global.
|
||||
|
||||
## Verificación final — 26 de octubre de 2026
|
||||
|
||||
La ejecución final de `python3 scripts/visual-audit.py --network-share-detail --check`
|
||||
completa **28 comprobaciones**, genera **14 capturas** y ejecuta **31/31 pruebas MariaDB**
|
||||
de publicaciones, SMB y accesos en la misma instalación desechable. El proceso retira
|
||||
sus servicios y su sesión al terminar. Datos utilizados: 15 compañías; en la principal,
|
||||
50 personas, 51 activos, 50 perfiles, 50 puestos y 67 recursos. El recurso auditado llega
|
||||
a 21 periodos históricos antes de la última publicación.
|
||||
|
||||
| Control ejecutado | Resultado |
|
||||
| --- | --- |
|
||||
| Asignación, traslado y cierre reales | Correctos; se conservan datos y revisiones del recurso salvo cambios explícitos de estado de la prueba |
|
||||
| Cancelación y foco | Sin escritura; vuelve al botón Cambiar activo |
|
||||
| Conflicto concurrente | Se cierra el periodo en el servidor mientras el diálogo conserva su UUID; el traslado devuelve conflicto real y permite revisar |
|
||||
| Historial y navegación | Eventos persistidos, dos páginas reales, navegación al activo y foco al consultar historial |
|
||||
| Carga, vacío, finalizada, errores y denegación | Estados diferenciados; reintento de ambas consultas; no se muestran datos de publicación de caché tras 403 |
|
||||
| Recurso inactivo | Conserva la publicación y permite finalizarla; no ofrece un traslado |
|
||||
| Solo lectura | Prueba de render es/en con capacidad de gestión deshabilitada; conserva datos y enlaces, sin acciones de escritura |
|
||||
| Responsive | 320, 390, 768, 1024 y 1440 px, sin desbordamientos en texto y acciones de la ficha |
|
||||
| Tema y contraste | Claro y oscuro; 60 nodos de texto medidos por tema. Mínimos 4,53:1 y 4,55:1, respectivamente |
|
||||
| Texto ampliado | 200 % y 400 %; cuerpo desplazable con al menos dos líneas de altura. No equivale a zoom real del navegador |
|
||||
| Movimiento reducido | Skeleton sin animación bajo `prefers-reduced-motion: reduce` |
|
||||
| Teclado | Apertura de asignación, menú, Escape y retorno de foco; copia UNC con anuncio de éxito |
|
||||
| Typechecks dirigidos | Resources, Assets, Companies, RDX UI y web correctos |
|
||||
| Regresiones nativas con `--test-isolation=none` | Resources 124/124, Assets 153/153, RDX UI 83/83; Companies 310/312 |
|
||||
| Pruebas específicas de ficha/publicación | 9/9 |
|
||||
| `python3 scripts/visual-audit.test.py` | 10/10 |
|
||||
| Biome y ESLint dirigidos a los archivos del cambio | Correctos; sintaxis Python y `git diff --check`, correctos |
|
||||
| `pnpm build` | Correcto; aviso de tamaño de chunks existente |
|
||||
| `pnpm typecheck` global | Bloqueado por TS2375 en `packages/shadcn-ui/src/components/calendar.tsx:165`; vendor intacto |
|
||||
| `pnpm test` global | Dos fallos conocidos de textos de personas en `companies-page.test.tsx` (expectativas «Personas (1)» y texto de acceso a Metis) |
|
||||
| `pnpm lint` / `pnpm check` globales | 15 y 173 errores, respectivamente, en archivos ajenos al cambio; ejemplos de skills y código de fotos de personas, entre otros |
|
||||
|
||||
Las mediciones de contraste excluyen controles deshabilitados y elementos ocultos. No
|
||||
certifican WCAG de toda la aplicación ni contraste no textual. Se corrigieron dos acciones
|
||||
en tema oscuro utilizando `text-background` sobre el primario y `text-foreground` en la
|
||||
acción sensible, sin cambiar tokens ni vendor. El hover conserva el fondo primario y usa
|
||||
sombra para no reducir el contraste de texto.
|
||||
|
||||
### Evidencias reales
|
||||
|
||||
Directorio local excluido de Git: `.artifacts/playwright/authenticated-xmi_nn0a/`.
|
||||
`share-detail-report.json` contiene escenarios, geometría y hashes de las capturas;
|
||||
`share-detail-integration.log` contiene las 31 pruebas de integración.
|
||||
|
||||
- [Escritorio, 1440 px](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-light-1440.png).
|
||||
- [Móvil, 320 px](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-light-320.png) y
|
||||
[390 px](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-light-390.png).
|
||||
- [Datos e historial](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-data-history.png) y
|
||||
[navegación inferior](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-usage.png).
|
||||
- [Tema oscuro](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-dark.png).
|
||||
- [Carga](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-loading.png),
|
||||
[vacío](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-empty.png),
|
||||
[conflicto](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-conflict.png) y
|
||||
[confirmación de cierre](../.artifacts/playwright/authenticated-xmi_nn0a/share-detail-end-confirmation.png).
|
||||
|
||||
Las capturas se inspeccionaron frente a la estructura del mockup, sin comparación de píxeles:
|
||||
se conserva la jerarquía, con las adaptaciones de tema, modal, datos y cardinalidad indicadas.
|
||||
|
||||
## Dictamen y pendientes
|
||||
|
||||
**Implementado** dentro del modelo vigente: ficha integrada, relación visible y gestionable,
|
||||
datos, historial real, estados y navegación respaldada por los contratos existentes.
|
||||
|
||||
No se incorporan las capacidades que el dominio no proporciona: verificación técnica del
|
||||
servidor, nombres de actores, sistema operativo del publicador, contadores de puestos o
|
||||
accesos remotos y un nuevo rol de solo lectura. La Ruta de acceso se presenta si pertenece
|
||||
a la especificación real del recurso; no se crea un campo ficticio. El zoom real del navegador
|
||||
y una auditoría completa con tecnología asistiva no se han ejecutado. Se mantienen los
|
||||
fallos globales ajenos descritos. No se despliega ni se modifica infraestructura o producción.
|
||||
|
||||
## Inventario de archivos
|
||||
|
||||
- [modules/assets/src/common/locales/en.json](../modules/assets/src/common/locales/en.json).
|
||||
- [modules/assets/src/common/locales/es.json](../modules/assets/src/common/locales/es.json).
|
||||
- [modules/assets/src/web/index.ts](../modules/assets/src/web/index.ts).
|
||||
- [modules/assets/src/web/network-share-publications/detail/controllers/use-resource-publication.ts](../modules/assets/src/web/network-share-publications/detail/controllers/use-resource-publication.ts).
|
||||
- [modules/assets/src/web/network-share-publications/detail/helpers/publication-events.ts](../modules/assets/src/web/network-share-publications/detail/helpers/publication-events.ts).
|
||||
- [modules/assets/src/web/network-share-publications/detail/ui/network-share-resource-detail.tsx](../modules/assets/src/web/network-share-publications/detail/ui/network-share-resource-detail.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/detail/ui/publication-history.tsx](../modules/assets/src/web/network-share-publications/detail/ui/publication-history.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/detail/ui/published-share-asset.tsx](../modules/assets/src/web/network-share-publications/detail/ui/published-share-asset.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/detail/ui/resource-publication-card.tsx](../modules/assets/src/web/network-share-publications/detail/ui/resource-publication-card.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/detail/ui/unpublished-share.tsx](../modules/assets/src/web/network-share-publications/detail/ui/unpublished-share.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx](../modules/assets/src/web/network-share-publications/manage/ui/publication-editor.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/manage/ui/publication-operation-summary.tsx](../modules/assets/src/web/network-share-publications/manage/ui/publication-operation-summary.tsx).
|
||||
- [modules/assets/src/web/network-share-publications/shared/network-share-ui.test.tsx](../modules/assets/src/web/network-share-publications/shared/network-share-ui.test.tsx).
|
||||
- [modules/companies/src/common/locales/en.json](../modules/companies/src/common/locales/en.json).
|
||||
- [modules/companies/src/common/locales/es.json](../modules/companies/src/common/locales/es.json).
|
||||
- [modules/companies/src/web/resources/detail/ui/company-network-share-detail.tsx](../modules/companies/src/web/resources/detail/ui/company-network-share-detail.tsx).
|
||||
- [modules/companies/src/web/resources/list/ui/company-resources.tsx](../modules/companies/src/web/resources/list/ui/company-resources.tsx).
|
||||
- [modules/resources/src/common/locales/en.json](../modules/resources/src/common/locales/en.json).
|
||||
- [modules/resources/src/common/locales/es.json](../modules/resources/src/common/locales/es.json).
|
||||
- [modules/resources/src/web/index.ts](../modules/resources/src/web/index.ts).
|
||||
- [modules/resources/src/web/resources/detail/ui/resource-detail-actions.tsx](../modules/resources/src/web/resources/detail/ui/resource-detail-actions.tsx).
|
||||
- [modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx](../modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx).
|
||||
- [modules/resources/src/web/resources/detail/ui/resource-detail.tsx](../modules/resources/src/web/resources/detail/ui/resource-detail.tsx).
|
||||
- [modules/resources/src/web/resources/detail/ui/resource-revision-history.tsx](../modules/resources/src/web/resources/detail/ui/resource-revision-history.tsx).
|
||||
- [modules/resources/src/web/resources/detail/ui/resource-selection-panel.tsx](../modules/resources/src/web/resources/detail/ui/resource-selection-panel.tsx).
|
||||
- [modules/resources/src/web/resources/list/types/resources-page-props.ts](../modules/resources/src/web/resources/list/types/resources-page-props.ts).
|
||||
- [modules/resources/src/web/resources/list/ui/resources-page.tsx](../modules/resources/src/web/resources/list/ui/resources-page.tsx).
|
||||
- [packages/rdx-ui/src/components/detail-panel.tsx](../packages/rdx-ui/src/components/detail-panel.tsx).
|
||||
- [packages/rdx-ui/src/components/form-dialog.tsx](../packages/rdx-ui/src/components/form-dialog.tsx).
|
||||
- [scripts/network-share-detail-ui-audit.py](../scripts/network-share-detail-ui-audit.py).
|
||||
- [scripts/visual-audit.py](../scripts/visual-audit.py).
|
||||
|
||||
Documentación: este informe y [componentes de interfaz](componentes-de-interfaz.md).
|
||||
|
||||
## Iteración UI/UX v2 — Refinamiento visual, responsive y accesibilidad
|
||||
|
||||
### Auditoría previa y alcance
|
||||
|
||||
Se releyeron AGENTS.md, DESIGN.md, los patrones de diálogos, estructura web y pruebas,
|
||||
este informe completo y los seis SKILL.md prioritarios. Se contrastaron las fichas de
|
||||
puestos, las APIs instaladas de Button/Collapsible y las capturas v1. Se mantiene la
|
||||
composición y la semántica SMB de la primera iteración.
|
||||
|
||||
La línea base se repitió **antes de modificar el código v2**:
|
||||
`.artifacts/playwright/authenticated-qe_17tcw/` contiene 28 comprobaciones de navegador,
|
||||
14 capturas y 31/31 pruebas MariaDB. Suites: Resources 124/124, Assets 153/153,
|
||||
RDX UI 83/83 y Companies 310/312. Los dos fallos de Companies ya estaban presentes.
|
||||
|
||||
Problemas pendientes observados: cabecera de cinco líneas a 320 px, icono/publicador
|
||||
apilados con mucha altura, cuatro acciones con peso parecido, repetición prominente
|
||||
de UNC y UUID como metadato principal del historial. Ya estaban resueltos la relación
|
||||
explícita, sus comandos y estados, el conflicto concurrente, la paginación real, los
|
||||
estados vacíos y la distinción entre actividad del recurso y vigencia de publicación.
|
||||
|
||||
`frontend-design` orientó densidad y jerarquía; `ux-design-review`, la revisión de
|
||||
estados, navegación y accesibilidad; `shadcn`, la expansión con Base UI; composición
|
||||
React, las unidades de título y detalle técnico; buenas prácticas React, el uso de
|
||||
TanStack Query y del ResizeObserver solo para medir recorte real; `rdx-solid`, el
|
||||
alcance local y la preservación de contratos. Los auditores especializados ausentes
|
||||
siguen sin instalarse ni ejecutarse; se hicieron las comprobaciones disponibles con
|
||||
Playwright MCP. No se modifica vendor ni se añaden dependencias.
|
||||
|
||||
### Matriz de seguimiento
|
||||
|
||||
| ID | Problema | Cambio aplicado | Prueba y evidencia | Resultado |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| M01 | Densidad móvil y título largo | Nombre visible hasta tres líneas en móvil/dos desde sm; token corporativo `text-base` (14 px) en móvil y `text-lg` (18 px) en escritorio, completo en el árbol accesible y expandible por teclado cuando se recorta; identidad del activo en rejilla compacta | Cinco anchos; expansión con Espacio, contracción con Enter; cierre visible; capturas light-320/light-390; texto 200/400 % | Verificado en navegador; teclado virtual no reproducido |
|
||||
| M02 | Acciones con peso similar | Asignar/Cambiar como primarias; Ver ficha e Historial discretas, con nombres accesibles completos; Finalizar conserva etiqueta y variante sensible compacta | Capturas móvil/escritorio, confirmación documental, cancelación sin escritura y foco restaurado | Verificado; no se duplican operaciones ni se añade menú |
|
||||
| M03 | UUID dominante | Activo y responsable conocido primero; UUID de actor, periodo y predecesor disponibles mediante Collapsible accesible | Capturas history-expanded y history-expanded-mobile; apertura por Espacio; relación aria-controls/panel; prueba es/en de responsable no resuelto | Verificado; nombres parciales solo de revisiones ya autorizadas |
|
||||
| M04 | UNC fragmentada y redundante | Bloque principal completo y seleccionable, oportunidades de salto sin introducir caracteres; atributo secundario compacto; copia por icono con anuncio sin altura adicional | Comparación literal de DOM y portapapeles con UNC larga Unicode; error simulado y recuperación; prueba de segmentos repetidos | Verificado; copia conserva la cadena original |
|
||||
| M05 | Separación y metadatos | Padding móvil menor, datos compactos, separación uniforme y orden publicación/datos/historial/navegación | Capturas en cinco anchos; dos columnas solo con espacio de contenedor; estados vacío/carga/finalizada/inactivo y solo lectura | Verificado; sin modificación adicional de las primitivas compartidas |
|
||||
| M06 | Posibles regresiones de contraste | Tokens existentes, primario conserva fondo en hover, contornos de foco opacos locales | Mediciones de texto claro/oscuro y hover; muestras de iconos interactivos y foco; capturas dark y dark-mobile | Verificado en las muestras: texto ≥4,53:1 claro / ≥4,75:1 oscuro; foco ≥4,65:1 / ≥4,75:1; iconos ≥14,52:1 |
|
||||
| M07 | Teclado y feedback | Expansiones accesibles, nombres de iconos, anuncios de asignación/traslado/finalización y copia; error de copia visible | Enter/Espacio/Escape/Tab/Shift+Tab, menú, retorno de foco, error/reintento, conflicto real, movimiento reducido y ampliación de texto | Verificado en Chromium; lector de pantalla, zoom real y teclado virtual pendientes |
|
||||
| M08 | Riesgo de cambiar dominio o permisos | Presentación y helpers UI; se reutilizan consultas, comandos, periodos e invalidaciones | 31/31 MariaDB; 403 sin datos anteriores ni escritura; recurso inactivo, conflicto, 21 periodos con cursor, navegación real | Verificado; sin cambios de backend ni operaciones sobre servidores SMB |
|
||||
|
||||
### Identidad del responsable
|
||||
|
||||
El contrato de publicaciones sigue proporcionando UUID. El directorio de usuarios exige
|
||||
privilegios adicionales y no se utiliza. `ResourceDTO.history` ya contiene pares
|
||||
`actorId`/`actorName` autorizados al cargar esta misma ficha. Companies aporta únicamente
|
||||
los nombres que difieren del identificador; Assets recibe un `ReadonlyMap` opcional.
|
||||
No se lanzan consultas adicionales, no se supone que todos los responsables estén en las
|
||||
revisiones y no se inventan nombres. Sin coincidencia, el UUID continúa accesible en
|
||||
«Detalles técnicos». Los identificadores no sustituyen la identidad de los periodos.
|
||||
|
||||
### Comparación visual y evidencias
|
||||
|
||||
Antes: [móvil 320](../.artifacts/playwright/authenticated-qe_17tcw/share-detail-light-320.png),
|
||||
[móvil 390](../.artifacts/playwright/authenticated-qe_17tcw/share-detail-light-390.png),
|
||||
[escritorio](../.artifacts/playwright/authenticated-qe_17tcw/share-detail-light-1440.png) y
|
||||
[oscuro](../.artifacts/playwright/authenticated-qe_17tcw/share-detail-dark.png).
|
||||
|
||||
Después: [móvil 320](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-light-320.png),
|
||||
[móvil 390](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-light-390.png),
|
||||
[768](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-light-768.png),
|
||||
[1024](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-light-1024.png),
|
||||
[escritorio](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-light-1440.png),
|
||||
[oscuro](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-dark.png) y
|
||||
[oscuro móvil](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-dark-mobile.png).
|
||||
|
||||
Estados adicionales: [detalle técnico](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-history-expanded.png),
|
||||
[detalle técnico móvil](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-history-expanded-mobile.png),
|
||||
[detalle técnico oscuro](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-history-expanded-dark.png),
|
||||
[traslado](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-transfer.png),
|
||||
[traslado móvil](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-transfer-mobile.png),
|
||||
[vacío](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-empty.png),
|
||||
[carga](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-loading.png),
|
||||
[conflicto](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-conflict.png),
|
||||
[confirmación](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-end-confirmation.png),
|
||||
[texto 200 %](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-text-200.png) y
|
||||
[texto 400 %](../.artifacts/playwright/authenticated-wavl8cyb/share-detail-text-400.png).
|
||||
|
||||
La inspección visual compara la estructura con el mockup original de la conversación:
|
||||
se conservan publicador dominante, UNC, datos e historial; se mantienen los tokens,
|
||||
iconos y colores corporativos y se omiten sus contadores ficticios. A 320 px la acción
|
||||
Cambiar activo aparece en el área visible antes del pie, donde v1 todavía mostraba la
|
||||
continuación de la tarjeta. A 390 px comienza a verse Datos del recurso. En escritorio
|
||||
la tarjeta conserva identidad/UNC en horizontal y permite leer el evento sin UUID
|
||||
expuestos inicialmente. No se afirma una mejora de CLS ni rendimiento.
|
||||
|
||||
### Resultados de pruebas
|
||||
|
||||
La ejecución final `python3 scripts/visual-audit.py --network-share-detail --check`
|
||||
terminó correctamente: **33 entradas de verificación y 20 capturas**. Son **32
|
||||
comprobaciones de navegador y una entrada de integración con 31/31 pruebas MariaDB**.
|
||||
El contador histórico de 28 entradas incluía igualmente la integración; el incremento
|
||||
es de cinco comprobaciones explícitas, sin retirar las regresiones anteriores.
|
||||
Se probaron 320, 390, 768, 1024 y 1440 px, claro/oscuro, hover primario/secundario/sensible,
|
||||
texto 200/400 %, movimiento reducido, título largo y UNC Unicode. No hubo desbordamiento
|
||||
en las medidas de la ficha. Antes de medir cada ancho se esperan fotogramas y
|
||||
animaciones finitas del diálogo, sin pausas arbitrarias.
|
||||
|
||||
`share-detail-report.json` registra escenarios, medidas y hashes de las capturas;
|
||||
`share-detail-integration.log`, las pruebas SQL. Datos exclusivamente sintéticos en
|
||||
`metis_desk_test`: **15 compañías; 50 personas, 51 activos, 50 perfiles, 50 puestos y
|
||||
67 recursos** en la principal; 21 periodos antes de la publicación final. El usuario
|
||||
de navegador es ordinario. La instalación y la sesión se retiran al terminar.
|
||||
|
||||
Las ocho mediciones de texto (dos temas y tres variantes hover por tema) comprobaron
|
||||
60 nodos cada una, con mínimos **4,53:1 claro y 4,75:1 oscuro**. El contorno de foco
|
||||
medido es sólido, de 2 px: **4,65:1 y 4,75:1**. Los tres iconos interactivos muestreados
|
||||
por tema superan **14,52:1**. Las muestras se comparan con 4,5:1 para texto normal,
|
||||
3:1 para texto grande y 3:1 para las muestras pertinentes no textuales.
|
||||
|
||||
| Verificación ejecutada | Resultado v2 |
|
||||
| --- | --- |
|
||||
| Resources, suite completa con `--test-isolation=none` | 125/125; +1 prueba de conservación literal de UNC |
|
||||
| Assets, suite completa con `--test-isolation=none` | 154/154; +1 prueba de nombres autorizados y UUID no resueltos |
|
||||
| RDX UI, suite completa | 83/83 |
|
||||
| Companies, suite completa | 310/312; los mismos dos fallos de textos de la línea base |
|
||||
| Pruebas dirigidas Resources + publicación | 23/23, incluidas 10/10 de publicación |
|
||||
| TypeScript dirigido | Resources, Assets, Companies, RDX UI y apps/web correctos |
|
||||
| Biome dirigido | 34 archivos correctos |
|
||||
| ESLint dirigido | Sin errores; los JSON se verifican con Biome |
|
||||
| Pruebas del lanzador de auditoría | `python3 scripts/visual-audit.test.py`: 10/10 |
|
||||
| Sintaxis Python y `git diff --check` | Correctos |
|
||||
| `pnpm build` | Correcto; mantiene el aviso de tamaño de chunks |
|
||||
| `pnpm typecheck` global | TS2375 preexistente en calendar.tsx:165 del vendor |
|
||||
| `pnpm test` global | Fallan las dos expectativas preexistentes de Companies |
|
||||
| `pnpm lint` / `pnpm check` globales | 15/173 errores ajenos, los mismos que en v1 |
|
||||
|
||||
Los logs técnicos dirigidos se guardaron en `/tmp/metis-share-v2-final-*` y los globales
|
||||
en `/tmp/metis-share-v2-global-*`; son evidencia local temporal. El directorio de
|
||||
capturas está excluido de Git y no contiene sesión ni credenciales tras la limpieza.
|
||||
|
||||
Las primeras ejecuciones v2 detectaron tres errores del guion de auditoría: localizar
|
||||
el botón por su etiqueta anterior después de expandirlo, seleccionar dos anuncios
|
||||
como si fueran uno y referenciar una variable Python desde la evaluación del navegador.
|
||||
Se corrigieron y se repitió la instalación aislada. La medición de foco detectó además
|
||||
que `outline-none` de la primitiva anulaba el estilo del contorno: se declaró
|
||||
`outline-solid` localmente y se volvió a verificar. La etiqueta visible «Ver ficha»
|
||||
queda incluida literalmente en el nombre accesible «Ver ficha del activo». No se silencian como resultados
|
||||
satisfactorios. Las pruebas nuevas protegen conservación de caracteres y ausencia de
|
||||
nombres inventados. No se detectó una regresión funcional SMB en el recorrido final.
|
||||
|
||||
### Archivos de esta iteración
|
||||
|
||||
- Resources: nuevo `network-share-resource-title.tsx`; consumidores
|
||||
`resource-detail-dialog.tsx` y `resource-selection-panel.tsx`; `network-share-address.tsx`,
|
||||
su reinicio por dirección en `resource-detail.tsx` y `resources.test.tsx`.
|
||||
- Assets: `published-share-asset.tsx`, `resource-publication-card.tsx`,
|
||||
`network-share-resource-detail.tsx`, `publication-history.tsx`; nuevo
|
||||
`publication-event-details.tsx`; tipo derivado en `publication-events.ts`, anuncios
|
||||
en `use-resource-publication.ts` y prueba en `network-share-ui.test.tsx`.
|
||||
- Companies: `company-network-share-detail.tsx`, aportación de nombres ya autorizados.
|
||||
- Traducciones es/en de Assets y Resources; alias Tabler en `packages/rdx-ui/src/lib/icons.ts`.
|
||||
- Auditoría reproducible en `scripts/network-share-detail-ui-audit.py`; este informe y
|
||||
`docs/componentes-de-interfaz.md`.
|
||||
|
||||
Las modificaciones anteriores de `DetailPanel` y `FormDialog` pertenecen a v1: v2 no
|
||||
cambia su implementación ni contratos. Tampoco modifica backend, schemas, endpoints,
|
||||
migraciones, revisiones, transacciones, permisos ni estrategia de invalidación.
|
||||
|
||||
### Límites y dictamen v2
|
||||
|
||||
No se ejecutó un lector de pantalla real, zoom del navegador ni teclado virtual.
|
||||
La ampliación CSS del texto al 200/400 % no sustituye esos escenarios. Las medidas
|
||||
muestrales de contraste y la inspección de atributos accesibles no certifican WCAG de
|
||||
toda la aplicación. Los separadores decorativos y controles deshabilitados se distinguen
|
||||
de controles activos; no se exige a los primeros el contraste de un control interactivo.
|
||||
La copia se probó en Chromium del contenedor mediante permisos de portapapeles limitados
|
||||
al origen desechable. No hay operaciones reales sobre SMB ni producción.
|
||||
|
||||
**Implementada y verificada en el alcance solicitado.** Se mejora la densidad móvil,
|
||||
la consulta de nombres y rutas, la jerarquía de acciones y la lectura del historial,
|
||||
con trazabilidad técnica accesible. Se corrige el contorno de foco y se conserva el
|
||||
contraste de texto. Las pruebas no detectan regresiones SMB. Permanecen los fallos
|
||||
globales preexistentes y las comprobaciones con tecnología asistiva indicadas; no se
|
||||
presentan como aprobadas.
|
||||
46
docs/proxy-tls-y-sesiones.md
Normal file
46
docs/proxy-tls-y-sesiones.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Proxy TLS y sesiones del servidor
|
||||
|
||||
El entrypoint actual escucha HTTP en loopback. En producción necesita un terminador TLS autorizado delante de Node. Esta guía define el contrato de la aplicación; no acredita una instalación productiva ni incorpora un proxy de despliegue nuevo.
|
||||
|
||||
## Configuración y confianza
|
||||
|
||||
`TRUSTED_PROXY_CIDRS` contiene direcciones IP o redes CIDR explícitas, separadas por comas, con un máximo de 32 entradas. Admite IPv4 e IPv6; elimina duplicados y espacios exteriores. Rechaza hosts, alias como `loopback`, números de saltos, entradas vacías, prefijos inválidos y confianza universal (`/0`, incluida la representación IPv4 mapeada en IPv6).
|
||||
|
||||
- Con `NODE_ENV=production`, la lista es obligatoria. Su ausencia o invalidez bloquea el arranque antes de abrir el puerto.
|
||||
- En desarrollo puede omitirse: Express mantiene `trust proxy=false` y permite el recorrido HTTP de desarrollo.
|
||||
- El administrador debe proporcionar las direcciones reales desde las que Node recibe conexiones del proxy. No copiar direcciones sintéticas de los tests ni confiar en una red más amplia de lo necesario.
|
||||
- No se utiliza `trust proxy=true` ni confianza por número de saltos.
|
||||
|
||||
El terminador confiable debe sobrescribir `X-Forwarded-Proto` con un único valor canónico `https` o `http`, `X-Forwarded-For` con la dirección observada del cliente y `X-Forwarded-Host` con el host autorizado. Debe retirar cabeceras `Forwarded` aportadas por el cliente y validar el host público según la configuración del destino. La aplicación rechaza con 400 las cabeceras de protocolo repetidas, listas y valores distintos de `http`/`https`, antes del middleware de sesión. Una cabecera válida procedente de una dirección no confiable no hace que Express considere segura la conexión.
|
||||
|
||||
Restringir por red el acceso al backend. Una IP confiable no es una credencial: procesos locales o fuentes que compartan la IP/NAT del proxy no pueden distinguirse por esta política. Conservar `APP_ORIGIN` con el origen HTTPS público real para la protección de origen/CSRF. No deducirlo de cabeceras del cliente.
|
||||
|
||||
La topología verificada es **cliente HTTPS → un terminador TLS → backend HTTP**. No se ha aprobado una cadena productiva de varios proxies; antes de utilizarla deben verificarse sus direcciones, recorridos alternativos y sobrescritura de cabeceras. El test de cadena inyectada demuestra que el terminador de prueba la sobrescribe; no certifica una infraestructura de varios saltos.
|
||||
|
||||
## Cookies y autenticación
|
||||
|
||||
Identity utiliza `express-session` y su almacén SQL. En producción la cookie `metis.sid` es `Secure`, `HttpOnly`, `SameSite=Strict`, `Path=/`, sin `Domain` y con duración de ocho horas. Si el proxy no permite reconocer HTTPS, el middleware no emite una cookie de producción insegura. Comprobar el login en el destino: una lista válida de IPs no demuestra que el proxy esté enviando las cabeceras correctamente.
|
||||
|
||||
El logout destruye la sesión SQL y borra la cookie conservando los atributos de seguridad, incluido `Secure` en producción. El login posterior regenera la sesión. Se mantiene la política existente de renovación; no se ha activado `rolling`. La emisión posterior conserva `Secure`. En desarrollo HTTP la cookie mantiene `HttpOnly` y `SameSite=Strict`, sin `Secure`.
|
||||
|
||||
## Compilación y comprobación aislada
|
||||
|
||||
Desde WSL, con Node 24 y el PNPM fijado en `package.json`:
|
||||
|
||||
```bash
|
||||
pnpm --filter @metis/server build
|
||||
pnpm --filter @metis/server check:build
|
||||
pnpm --filter @metis/server test:http
|
||||
pnpm test:server-runtime
|
||||
pnpm test:global-operating-systems:closure
|
||||
```
|
||||
|
||||
El build limpia y genera `apps/server/dist/index.js` en ESM para Node 24, y ejecuta siempre `node --check`. `sharp` queda externo: instalar las dependencias runtime del servidor y los binarios nativos adecuados para la plataforma de destino. No distribuir únicamente el fichero JavaScript ni omitir las dependencias nativas opcionales necesarias. El banner conserva el puente CommonJS con un nombre de importación propio.
|
||||
|
||||
`test:http` necesita OpenSSL y puertos loopback. `test:server-runtime` necesita además Docker/MariaDB: compila, prepara un contenedor único con `metis_desk_test`, inicia **el entrypoint compilado** mediante `node`, verifica TLS con certificado local validado, sesiones SQL, autenticación, CSRF, imágenes y cierre por SIGTERM. Retira el contenedor y los certificados al terminar; no carga `.env`. Sus evidencias en `.artifacts/server-runtime-security/` solo incluyen nombres/atributos de cookies. El proxy local es exclusivamente una fixture.
|
||||
|
||||
Para ejecutar el artefacto bajo un supervisor con el entorno previamente validado e inyectado se utiliza `node apps/server/dist/index.js`. Esto requiere persistencia preparada, secreto de sesión, origen y proxies reales configurados; este documento no autoriza el arranque ni migraciones en producción.
|
||||
|
||||
## Verificación del destino pendiente
|
||||
|
||||
Antes de desplegar: identificar release y plataforma nativa, instalar dependencias runtime, verificar esquema MariaDB y gates efectivos, aprobar IPs/CIDRs y origen, restringir acceso al backend y comprobar HTTPS, login, emisión/borrado de cookie, expiración, CSRF y cierre del proceso mediante el supervisor real. Registrar atributos de cookies sin valores de sesión. El ensayo local no sustituye estas comprobaciones ni la validación visual pendiente del frontend.
|
||||
@ -90,3 +90,8 @@ pnpm --filter @metis/server start
|
||||
Hay acceso, sesiones persistentes, gestión de usuarios, compañías con personas, departamentos y sedes, inventario, recursos, puestos, perfiles y configuraciones. Inicio muestra indicadores, avisos y actividad. El [plan de desarrollo](plan-de-desarrollo.md) recoge las funciones disponibles y los pendientes; las funciones retiradas se describen en [funcionalidades archivadas](funcionalidades-archivadas.md). Las tablas nuevas se crean al arrancar la API mediante migraciones explícitas.
|
||||
|
||||
La API compilada todavía no sirve la interfaz estática. El despliegue, proxy HTTPS, backups y recuperación de acceso se definirán en una entrega posterior.
|
||||
|
||||
La compilación del servidor incluye una comprobación de sintaxis del entrypoint generado.
|
||||
Para producción con terminación TLS externa, consultar [Proxy TLS y sesiones](proxy-tls-y-sesiones.md):
|
||||
`TRUSTED_PROXY_CIDRS` debe contener los proxies reales autorizados y es obligatoria en producción.
|
||||
El ensayo compilado aislado se ejecuta mediante `pnpm test:server-runtime`.
|
||||
|
||||
@ -627,6 +627,48 @@
|
||||
"resources": "Published SMB resources",
|
||||
"publisher": "Publishing asset"
|
||||
},
|
||||
"shareDetail": {
|
||||
"viewAssetShort": "View asset",
|
||||
"historyShort": "History",
|
||||
"technicalDetails": "Technical details",
|
||||
"performedBy": "By {{name}}",
|
||||
"actorId": "Actor (UUID)",
|
||||
"periodId": "Period (UUID)",
|
||||
"predecessorId": "Previous period (UUID)",
|
||||
"assignedTo": "Publication assigned to {{name}}",
|
||||
"transferredTo": "Publication transferred to {{name}}",
|
||||
"publication": "Share publication",
|
||||
"description": "Explicit relationship between the share and its publishing asset.",
|
||||
"published": "Published",
|
||||
"unpublished": "No current publication",
|
||||
"ended": "Ended",
|
||||
"publisher": "Publishing asset",
|
||||
"address": "Share UNC path",
|
||||
"change": "Change asset",
|
||||
"viewAsset": "View asset details",
|
||||
"viewHistory": "View history",
|
||||
"assign": "Assign asset",
|
||||
"emptyTitle": "This share has no publishing asset assigned",
|
||||
"endedTitle": "The recorded publication has ended",
|
||||
"emptyDescription": "You can associate an asset from this company. The absence of this relationship does not determine actual network availability.",
|
||||
"assetUnavailable": "Asset unavailable",
|
||||
"readOnly": "Read-only view.",
|
||||
"data": "Resource details",
|
||||
"unverified": "Correspondence not verified. The UNC server may be an alias, DFS, cluster or virtual name.",
|
||||
"history": "Publication history",
|
||||
"historyOrder": "Periods in record order.",
|
||||
"emptyHistory": "No publications have been recorded.",
|
||||
"actor": "Actor (UUID): {{id}}",
|
||||
"previous": "Previous asset: {{name}}",
|
||||
"currentAsset": "Current publishing asset",
|
||||
"endExplanation": "This ends the association recorded in Metis Desk. The resource, asset and history are preserved; this action does not disable the share on its server.",
|
||||
"transferExplanation": "The current asset period will end and another will start for the selected asset. The UNC path and resource details will not change. A different name may be an alias; correspondence is not verified.",
|
||||
"events": {
|
||||
"publish": "Publication assigned",
|
||||
"transfer": "Publication transferred",
|
||||
"end": "Period ended"
|
||||
}
|
||||
},
|
||||
"shareUi": {
|
||||
"linkLater": "Link later",
|
||||
"resourceCreated": "The share “{{name}}” has been saved. Select its publishing asset. If you leave this for later, you can link it from the share details.",
|
||||
|
||||
@ -627,6 +627,48 @@
|
||||
"resources": "Recursos SMB publicados",
|
||||
"publisher": "Activo publicador"
|
||||
},
|
||||
"shareDetail": {
|
||||
"viewAssetShort": "Ver ficha",
|
||||
"historyShort": "Historial",
|
||||
"technicalDetails": "Detalles técnicos",
|
||||
"performedBy": "Por {{name}}",
|
||||
"actorId": "Responsable (UUID)",
|
||||
"periodId": "Periodo (UUID)",
|
||||
"predecessorId": "Periodo anterior (UUID)",
|
||||
"assignedTo": "Publicación asignada a {{name}}",
|
||||
"transferredTo": "Publicación trasladada a {{name}}",
|
||||
"publication": "Publicación del recurso",
|
||||
"description": "Relación explícita entre la carpeta y el activo que la publica.",
|
||||
"published": "Publicada",
|
||||
"unpublished": "Sin publicación activa",
|
||||
"ended": "Finalizada",
|
||||
"publisher": "Activo publicador",
|
||||
"address": "Ruta UNC del recurso",
|
||||
"change": "Cambiar activo",
|
||||
"viewAsset": "Ver ficha del activo",
|
||||
"viewHistory": "Consultar historial",
|
||||
"assign": "Asignar activo",
|
||||
"emptyTitle": "Esta carpeta no tiene un activo publicador asociado",
|
||||
"endedTitle": "La publicación registrada ha finalizado",
|
||||
"emptyDescription": "Puedes asociar un activo de esta compañía. La ausencia de esta relación no determina la disponibilidad real en red.",
|
||||
"assetUnavailable": "Activo no disponible",
|
||||
"readOnly": "Consulta de solo lectura.",
|
||||
"data": "Datos del recurso",
|
||||
"unverified": "Correspondencia no verificada. El servidor de la ruta UNC puede ser un alias, un nombre DFS, de clúster o virtual.",
|
||||
"history": "Historial de publicación",
|
||||
"historyOrder": "Periodos en orden de registro.",
|
||||
"emptyHistory": "No hay publicaciones registradas.",
|
||||
"actor": "Responsable (UUID): {{id}}",
|
||||
"previous": "Activo anterior: {{name}}",
|
||||
"currentAsset": "Activo publicador actual",
|
||||
"endExplanation": "Se finalizará la asociación registrada en Metis Desk. El recurso, el activo y el historial se conservan; esta acción no deshabilita la carpeta en el servidor.",
|
||||
"transferExplanation": "Se cerrará el periodo del activo actual y se abrirá otro para el seleccionado. La ruta UNC y los datos del recurso no se modificarán. Un nombre diferente puede corresponder a un alias; no se verifica la correspondencia.",
|
||||
"events": {
|
||||
"publish": "Publicación asignada",
|
||||
"transfer": "Publicación trasladada",
|
||||
"end": "Periodo finalizado"
|
||||
}
|
||||
},
|
||||
"shareUi": {
|
||||
"linkLater": "Vincular más tarde",
|
||||
"resourceCreated": "La carpeta «{{name}}» está guardada. Selecciona el activo que la publica. Si lo dejas para más tarde, podrás vincularlo desde la ficha de la carpeta.",
|
||||
|
||||
@ -16,6 +16,7 @@ export {
|
||||
} 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 { NetworkShareResourceDetail } from "./network-share-publications/detail/ui/network-share-resource-detail";
|
||||
export { PublicationEditor } from "./network-share-publications/manage/ui/publication-editor";
|
||||
export { PublicationPicker } from "./network-share-publications/select/ui/publication-picker";
|
||||
export { assetsEnglish, assetsSpanish } from "./shared";
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { useAssetCollectionState } from "../../../shared/hooks/use-asset-collection-state";
|
||||
import { usePublicationPage } from "../../shared/controllers/use-publication-page";
|
||||
|
||||
export function useResourcePublication(companyId: string, resourceId: string, canManage: boolean) {
|
||||
const anchor = { type: "resources:resource", id: resourceId } as const;
|
||||
const current = usePublicationPage(companyId, anchor, false);
|
||||
const history = usePublicationPage(companyId, anchor, true);
|
||||
const { state, denied, available } = useAssetCollectionState([current.query]);
|
||||
const data = current.query.data;
|
||||
const period = available ? data?.publications.find((item) => item.endedAt === null) : undefined;
|
||||
const itemIndex =
|
||||
data?.page.items.findIndex(
|
||||
(item) => item.origin.kind === "record" && item.origin.id === period?.id
|
||||
) ?? -1;
|
||||
const resolution = data?.summaries[itemIndex];
|
||||
const summary = resolution?.status === "resolved" ? resolution.summary : undefined;
|
||||
const hasHistory = history.query.isSuccess && !!history.query.data.publications.length;
|
||||
const writable = canManage && current.query.isSuccess && !current.query.isFetching && !denied;
|
||||
|
||||
const readOnly = available && !canManage;
|
||||
|
||||
const publicationState = period ? "published" : hasHistory ? "ended" : "unpublished";
|
||||
|
||||
const announcementKey = period
|
||||
? period.predecessorId
|
||||
? "shareDetail.transferredTo"
|
||||
: "shareDetail.assignedTo"
|
||||
: `shareDetail.${publicationState}`;
|
||||
|
||||
return {
|
||||
announcementKey,
|
||||
publicationState,
|
||||
anchor,
|
||||
current,
|
||||
state,
|
||||
denied,
|
||||
available,
|
||||
period,
|
||||
summary,
|
||||
hasHistory,
|
||||
writable,
|
||||
readOnly,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import type { NetworkSharePublicationDTO } from "../../../../common/network-share-publications";
|
||||
|
||||
/** Conserva el orden de periodos del contrato paginado; solo transforma hechos persistidos. */
|
||||
export function publicationEvents(periods: readonly NetworkSharePublicationDTO[]) {
|
||||
return periods.flatMap((period) => {
|
||||
const start = {
|
||||
id: `${period.id}:start`,
|
||||
kind: period.predecessorId ? "transfer" : "publish",
|
||||
dateTime: period.startedAt,
|
||||
actorId: period.startedBy,
|
||||
period,
|
||||
} as const;
|
||||
|
||||
return period.endedAt && period.endedBy
|
||||
? [
|
||||
start,
|
||||
{
|
||||
id: `${period.id}:end`,
|
||||
kind: "end",
|
||||
dateTime: period.endedAt,
|
||||
actorId: period.endedBy,
|
||||
period,
|
||||
} as const,
|
||||
]
|
||||
: [start];
|
||||
});
|
||||
}
|
||||
|
||||
export type PublicationEvent = ReturnType<typeof publicationEvents>[number];
|
||||
@ -0,0 +1,101 @@
|
||||
import { DetailPanel } from "@repo/rdx-ui/components/detail-panel";
|
||||
import { InfoIcon, LayersIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import type { ReactNode } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { PublicationOperation } from "../../manage/ui/publication-editor";
|
||||
import { PublicationEditor } from "../../manage/ui/publication-editor";
|
||||
|
||||
import { PublicationHistory } from "./publication-history";
|
||||
import { ResourcePublicationCard } from "./resource-publication-card";
|
||||
|
||||
/** Companies aporta los datos y los consumidores; Assets conserva la publicación y su historial. */
|
||||
export function NetworkShareResourceDetail({
|
||||
companyId,
|
||||
resourceId,
|
||||
resourceName,
|
||||
address,
|
||||
active,
|
||||
canManage,
|
||||
data,
|
||||
children,
|
||||
actorNames,
|
||||
}: {
|
||||
companyId: string;
|
||||
resourceId: string;
|
||||
resourceName: string;
|
||||
address: string;
|
||||
active: boolean;
|
||||
canManage: boolean;
|
||||
data: ReactNode;
|
||||
children: ReactNode;
|
||||
actorNames?: ReadonlyMap<string, string> | undefined;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLElement>(null);
|
||||
const historyRef = useRef<HTMLDivElement>(null);
|
||||
const [editing, setEditing] = useState<{
|
||||
operation: PublicationOperation;
|
||||
currentAssetName: string | undefined;
|
||||
}>();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="@container flex min-w-0 flex-col gap-4 rounded-lg focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-ring [&_:is(button,a):focus-visible]:outline-solid [&_:is(button,a):focus-visible]:outline-2 [&_:is(button,a):focus-visible]:outline-offset-2 [&_:is(button,a):focus-visible]:outline-ring"
|
||||
data-slot="network-share-detail"
|
||||
ref={rootRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ResourcePublicationCard
|
||||
active={active}
|
||||
address={address}
|
||||
canManage={canManage}
|
||||
companyId={companyId}
|
||||
onHistory={() => {
|
||||
historyRef.current?.focus({ preventScroll: true });
|
||||
historyRef.current?.scrollIntoView({ block: "nearest" });
|
||||
}}
|
||||
onManage={(operation, currentAssetName) => {
|
||||
triggerRef.current =
|
||||
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
setEditing({ operation, currentAssetName });
|
||||
}}
|
||||
resourceId={resourceId}
|
||||
/>
|
||||
<div className="grid min-w-0 items-start gap-4 @min-[900px]:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)]">
|
||||
<DetailPanel density="compact" icon={<LayersIcon />} title={t("shareDetail.data")}>
|
||||
{data}
|
||||
<p className="flex items-start gap-2 text-sm text-muted-foreground">
|
||||
<InfoIcon aria-hidden="true" className="size-4 shrink-0" />
|
||||
{t("shareDetail.unverified")}
|
||||
</p>
|
||||
</DetailPanel>
|
||||
<div
|
||||
className="min-w-0 rounded-lg focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-ring"
|
||||
ref={historyRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<PublicationHistory
|
||||
actorNames={actorNames}
|
||||
companyId={companyId}
|
||||
resourceId={resourceId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
{editing && (
|
||||
<PublicationEditor
|
||||
companyId={companyId}
|
||||
context={{ resourceName, address, currentAssetName: editing.currentAssetName }}
|
||||
finalFocus={() =>
|
||||
triggerRef.current?.isConnected ? triggerRef.current : rootRef.current
|
||||
}
|
||||
onClose={() => setEditing(undefined)}
|
||||
operation={editing.operation}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
import { BrowserLink } from "@metis/core/client";
|
||||
import type { EntitySummary } from "@metis/relationship-contracts";
|
||||
import { InfoIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@repo/shadcn-ui/components/collapsible";
|
||||
import { useId, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { PublicationEvent } from "../helpers/publication-events";
|
||||
|
||||
/** Identidad útil primero; los identificadores permanecen en un detalle técnico accesible. */
|
||||
export function PublicationEventDetails({
|
||||
companyId,
|
||||
event,
|
||||
summary,
|
||||
previousName,
|
||||
actorName,
|
||||
}: {
|
||||
companyId: string;
|
||||
event: PublicationEvent;
|
||||
summary: EntitySummary | undefined;
|
||||
previousName: string | undefined;
|
||||
actorName: string | undefined;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
const id = useId();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Collapsible className="flex min-w-0 flex-col gap-1" onOpenChange={setOpen} open={open}>
|
||||
<div className="flex min-w-0 items-start justify-between gap-2 text-sm">
|
||||
{summary ? (
|
||||
<BrowserLink
|
||||
className="min-w-0 break-words"
|
||||
href={`/companies/${companyId}/assets/${event.period.assetId}`}
|
||||
>
|
||||
{summary.name}
|
||||
</BrowserLink>
|
||||
) : (
|
||||
<p>{t("shareDetail.assetUnavailable")}</p>
|
||||
)}
|
||||
<CollapsibleTrigger
|
||||
aria-controls={id}
|
||||
aria-label={t("shareDetail.technicalDetails")}
|
||||
render={
|
||||
<Button
|
||||
className="focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
}
|
||||
title={t("shareDetail.technicalDetails")}
|
||||
>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
{event.kind === "transfer" && previousName && (
|
||||
<p className="break-words text-sm text-muted-foreground">
|
||||
{t("shareDetail.previous", { name: previousName })}
|
||||
</p>
|
||||
)}
|
||||
{actorName && (
|
||||
<p className="break-words text-xs text-muted-foreground">
|
||||
{t("shareDetail.performedBy", { name: actorName })}
|
||||
</p>
|
||||
)}
|
||||
<CollapsibleContent className="min-w-0 motion-reduce:animate-none" id={id} keepMounted>
|
||||
<dl className="mt-2 flex min-w-0 flex-col gap-2 rounded-md bg-muted/40 p-2 text-xs">
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareDetail.actorId")}</dt>
|
||||
<dd className="break-all font-mono">{event.actorId}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareDetail.periodId")}</dt>
|
||||
<dd className="break-all font-mono">{event.period.id}</dd>
|
||||
</div>
|
||||
{event.period.predecessorId && (
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareDetail.predecessorId")}</dt>
|
||||
<dd className="break-all font-mono">{event.period.predecessorId}</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
import { CursorPagination } from "@repo/rdx-ui/components/cursor-pagination";
|
||||
import { DetailPanel } from "@repo/rdx-ui/components/detail-panel";
|
||||
import { EntityStatus } from "@repo/rdx-ui/components/entity-list";
|
||||
import { EntityListStatus } from "@repo/rdx-ui/components/entity-list-status";
|
||||
import { TimeLine } from "@repo/rdx-ui/components/time-line";
|
||||
import { ClockIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Skeleton } from "@repo/shadcn-ui/components/skeleton";
|
||||
import { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useAssetCollectionState } from "../../../shared/hooks/use-asset-collection-state";
|
||||
import { usePublicationPage } from "../../shared/controllers/use-publication-page";
|
||||
import { publicationEvents } from "../helpers/publication-events";
|
||||
|
||||
import { PublicationEventDetails } from "./publication-event-details";
|
||||
|
||||
export function PublicationHistory({
|
||||
companyId,
|
||||
resourceId,
|
||||
actorNames,
|
||||
}: {
|
||||
companyId: string;
|
||||
resourceId: string;
|
||||
actorNames?: ReadonlyMap<string, string> | undefined;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation("assets");
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const owner = `/companies/${companyId}/resources/${resourceId}`;
|
||||
const c = usePublicationPage(
|
||||
companyId,
|
||||
{ type: "resources:resource", id: resourceId },
|
||||
true,
|
||||
owner
|
||||
);
|
||||
const { state, denied, available } = useAssetCollectionState([c.query]);
|
||||
const data = c.query.data;
|
||||
const format = new Intl.DateTimeFormat(i18n.language, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-w-0" data-slot="publication-history" ref={rootRef} tabIndex={-1}>
|
||||
<DetailPanel
|
||||
density="compact"
|
||||
description={t("shareDetail.historyOrder")}
|
||||
icon={<ClockIcon />}
|
||||
title={t("shareDetail.history")}
|
||||
>
|
||||
<EntityListStatus
|
||||
onRetry={denied ? undefined : () => c.query.refetch()}
|
||||
retryFocus={() => rootRef.current}
|
||||
retryLabel={t("retry")}
|
||||
state={
|
||||
state.status === "unavailable" && !denied
|
||||
? { ...state, message: t("shareUi.readError") }
|
||||
: state
|
||||
}
|
||||
/>
|
||||
{state.status === "loading" && (
|
||||
<Skeleton
|
||||
aria-busy="true"
|
||||
aria-label={t("loading")}
|
||||
className="h-48 w-full motion-reduce:animate-none"
|
||||
role="status"
|
||||
/>
|
||||
)}
|
||||
{available && (
|
||||
<TimeLine
|
||||
emptyMessage={t("shareDetail.emptyHistory")}
|
||||
items={publicationEvents(data?.publications ?? []).map((event) => {
|
||||
const index =
|
||||
data?.page.items.findIndex(
|
||||
(item) => item.origin.kind === "record" && item.origin.id === event.period.id
|
||||
) ?? -1;
|
||||
const resolution = data?.summaries[index];
|
||||
const summary = resolution?.status === "resolved" ? resolution.summary : undefined;
|
||||
const predecessor = data?.publications.find(
|
||||
(period) => period.id === event.period.predecessorId
|
||||
);
|
||||
const previousIndex =
|
||||
data?.page.items.findIndex(
|
||||
(item) => item.origin.kind === "record" && item.origin.id === predecessor?.id
|
||||
) ?? -1;
|
||||
const previous = data?.summaries[previousIndex];
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
dateTime: event.dateTime,
|
||||
dateLabel: format.format(new Date(event.dateTime)),
|
||||
title: (
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
{t(`shareDetail.events.${event.kind}`)}
|
||||
{event.kind !== "end" && !event.period.endedAt && (
|
||||
<EntityStatus active>{t("shareUi.current")}</EntityStatus>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
content: (
|
||||
<PublicationEventDetails
|
||||
actorName={actorNames?.get(event.actorId)}
|
||||
companyId={companyId}
|
||||
event={event}
|
||||
previousName={
|
||||
previous?.status === "resolved" ? previous.summary.name : undefined
|
||||
}
|
||||
summary={summary}
|
||||
/>
|
||||
),
|
||||
};
|
||||
})}
|
||||
label={t("shareDetail.history")}
|
||||
/>
|
||||
)}
|
||||
{!denied && (
|
||||
<CursorPagination
|
||||
canNext={!!data?.page.nextCursor && !c.query.isError}
|
||||
canPrevious={c.previous}
|
||||
nextLabel={t("directoryUi.next")}
|
||||
onNext={c.next}
|
||||
onPrevious={c.back}
|
||||
onRestart={c.restart}
|
||||
pending={c.query.isFetching}
|
||||
previousLabel={t("directoryUi.previous")}
|
||||
restartLabel={t("directoryUi.restart")}
|
||||
/>
|
||||
)}
|
||||
</DetailPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import { BrowserLink } from "@metis/core/client";
|
||||
import type { EntitySummary } from "@metis/relationship-contracts";
|
||||
import { NetworkShareAddress } from "@metis/resources/client";
|
||||
import { EntityStatus } from "@repo/rdx-ui/components/entity-list";
|
||||
import { BoxIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function PublishedShareAsset({
|
||||
companyId,
|
||||
assetId,
|
||||
address,
|
||||
summary,
|
||||
onChange,
|
||||
onHistory,
|
||||
}: {
|
||||
companyId: string;
|
||||
assetId: string;
|
||||
address: string;
|
||||
summary: EntitySummary | undefined;
|
||||
onChange: (() => void) | undefined;
|
||||
onHistory: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="@container min-w-0 rounded-lg border bg-background p-3 @min-[620px]:p-4"
|
||||
data-slot="published-share-asset"
|
||||
>
|
||||
<h3 className="sr-only @min-[620px]:not-sr-only @min-[620px]:mb-3 @min-[620px]:text-sm @min-[620px]:font-semibold">
|
||||
{t("shareDetail.publisher")}
|
||||
</h3>
|
||||
<div className="grid min-w-0 gap-3 @min-[620px]:grid-cols-2 @min-[620px]:gap-5">
|
||||
<div className="grid min-w-0 grid-cols-[auto_minmax(0,1fr)] content-start gap-x-3 gap-y-1">
|
||||
<div className="row-span-2 self-start rounded-lg bg-muted p-2 text-muted-foreground">
|
||||
<BoxIcon aria-hidden="true" className="size-6 @min-[620px]:size-10" />
|
||||
</div>
|
||||
<p className="min-w-0 break-words font-semibold @min-[620px]:text-lg">
|
||||
{summary?.name ?? t("shareDetail.assetUnavailable")}
|
||||
</p>
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-sm">
|
||||
<span className="break-words text-muted-foreground">{summary?.subtitle ?? "—"}</span>
|
||||
{summary?.state && (
|
||||
<EntityStatus
|
||||
active={["operational", "available", "in_use"].includes(summary.state.code)}
|
||||
>
|
||||
{t(summary.state.labelKey)}
|
||||
</EntityStatus>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 border-t pt-3 @min-[620px]:border-t-0 @min-[620px]:border-s @min-[620px]:ps-4 @min-[620px]:pt-0">
|
||||
<NetworkShareAddress address={address} key={address} variant="block" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{onChange && (
|
||||
<Button
|
||||
className="text-background hover:bg-primary hover:shadow-sm focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
||||
onClick={onChange}
|
||||
>
|
||||
{t("shareDetail.change")}
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{summary && (
|
||||
<Button
|
||||
aria-label={t("shareDetail.viewAsset")}
|
||||
className="focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
||||
nativeButton={false}
|
||||
render={<BrowserLink href={`/companies/${companyId}/assets/${assetId}`} />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
{t("shareDetail.viewAssetShort")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-label={t("shareDetail.viewHistory")}
|
||||
onClick={onHistory}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
{t("shareDetail.historyShort")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
import { DetailPanel } from "@repo/rdx-ui/components/detail-panel";
|
||||
import { EntityStatus } from "@repo/rdx-ui/components/entity-list";
|
||||
import { EntityListStatus } from "@repo/rdx-ui/components/entity-list-status";
|
||||
import { NetworkIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { Skeleton } from "@repo/shadcn-ui/components/skeleton";
|
||||
import { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { PublicationOperation } from "../../manage/ui/publication-editor";
|
||||
import { useResourcePublication } from "../controllers/use-resource-publication";
|
||||
|
||||
import { PublishedShareAsset } from "./published-share-asset";
|
||||
import { UnpublishedShare } from "./unpublished-share";
|
||||
|
||||
export function ResourcePublicationCard({
|
||||
companyId,
|
||||
resourceId,
|
||||
address,
|
||||
active,
|
||||
canManage,
|
||||
onManage,
|
||||
onHistory,
|
||||
}: {
|
||||
companyId: string;
|
||||
resourceId: string;
|
||||
address: string;
|
||||
active: boolean;
|
||||
canManage: boolean;
|
||||
onManage: (operation: PublicationOperation, currentAssetName?: string) => void;
|
||||
onHistory: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
announcementKey,
|
||||
publicationState,
|
||||
anchor,
|
||||
current,
|
||||
state,
|
||||
denied,
|
||||
available,
|
||||
period,
|
||||
summary,
|
||||
hasHistory,
|
||||
writable,
|
||||
readOnly,
|
||||
} = useResourcePublication(companyId, resourceId, canManage);
|
||||
const captured = period
|
||||
? { publicationId: period.id, assetId: period.assetId, resourceId }
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-w-0 rounded-lg focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-ring"
|
||||
data-slot="resource-publication"
|
||||
ref={rootRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<DetailPanel
|
||||
actions={
|
||||
<>
|
||||
{available && (
|
||||
<span>
|
||||
<EntityStatus active={!!period}>
|
||||
{t(`shareDetail.${publicationState}`)}
|
||||
</EntityStatus>
|
||||
<span className="sr-only" role="status">
|
||||
{t(announcementKey, { name: summary?.name ?? t("shareDetail.assetUnavailable") })}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{captured && writable && (
|
||||
<Button
|
||||
className="h-auto min-h-9 max-w-full whitespace-normal text-foreground"
|
||||
onClick={() => onManage({ action: "end", ...captured }, summary?.name)}
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
>
|
||||
{t("shareUi.end")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
density="compact"
|
||||
description={t("shareDetail.description")}
|
||||
icon={<NetworkIcon />}
|
||||
title={t("shareDetail.publication")}
|
||||
>
|
||||
<EntityListStatus
|
||||
onRetry={denied ? undefined : () => current.query.refetch()}
|
||||
retryFocus={() => rootRef.current}
|
||||
retryLabel={t("retry")}
|
||||
state={
|
||||
state.status === "unavailable" && !denied
|
||||
? { ...state, message: t("shareUi.readError") }
|
||||
: state
|
||||
}
|
||||
/>
|
||||
{state.status === "loading" && (
|
||||
<div
|
||||
aria-busy="true"
|
||||
aria-label={t("loading")}
|
||||
className="flex min-h-52 flex-col gap-4"
|
||||
role="status"
|
||||
>
|
||||
<Skeleton className="h-24 w-full motion-reduce:animate-none" />
|
||||
<Skeleton className="h-12 w-full motion-reduce:animate-none" />
|
||||
<Skeleton className="h-9 w-2/3 motion-reduce:animate-none" />
|
||||
</div>
|
||||
)}
|
||||
{available && period && (
|
||||
<PublishedShareAsset
|
||||
address={address}
|
||||
assetId={period.assetId}
|
||||
companyId={companyId}
|
||||
onChange={
|
||||
writable && active && captured
|
||||
? () => onManage({ action: "transfer", ...captured }, summary?.name)
|
||||
: undefined
|
||||
}
|
||||
onHistory={onHistory}
|
||||
summary={summary}
|
||||
/>
|
||||
)}
|
||||
{available && !period && (
|
||||
<UnpublishedShare
|
||||
canAssign={writable && active}
|
||||
hasHistory={hasHistory}
|
||||
onAssign={() => onManage({ action: "publish", anchor })}
|
||||
onHistory={onHistory}
|
||||
/>
|
||||
)}
|
||||
{readOnly && <p className="text-sm text-muted-foreground">{t("shareDetail.readOnly")}</p>}
|
||||
<p className="text-xs text-muted-foreground">{t("shareUi.documentary")}</p>
|
||||
</DetailPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { EmptyState } from "@repo/rdx-ui/components/empty-state";
|
||||
import { FolderIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function UnpublishedShare({
|
||||
hasHistory,
|
||||
canAssign,
|
||||
onAssign,
|
||||
onHistory,
|
||||
}: {
|
||||
hasHistory: boolean;
|
||||
canAssign: boolean;
|
||||
onAssign: () => void;
|
||||
onHistory: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
|
||||
return (
|
||||
<EmptyState
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{canAssign && (
|
||||
<Button className="text-background hover:bg-primary hover:shadow-sm" onClick={onAssign}>
|
||||
{t("shareDetail.assign")}
|
||||
</Button>
|
||||
)}
|
||||
{hasHistory && (
|
||||
<Button onClick={onHistory} variant="outline">
|
||||
{t("shareDetail.viewHistory")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
description={t("shareDetail.emptyDescription")}
|
||||
icon={<FolderIcon />}
|
||||
title={t(hasHistory ? "shareDetail.endedTitle" : "shareDetail.emptyTitle")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { FormLabel } from "@repo/rdx-ui/components/form-label";
|
||||
import type { RemotePickerOption } from "@repo/rdx-ui/components/remote-option-picker";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useId, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -12,6 +13,9 @@ import { PublicationPicker } from "../../select/ui/publication-picker";
|
||||
import type { PublicationAnchor } from "../../shared/api/network-share-publication-api";
|
||||
import { publicationApi, publicationKeys } from "../../shared/api/network-share-publication-api";
|
||||
|
||||
import type { PublicationContext } from "./publication-operation-summary";
|
||||
import { PublicationOperationSummary } from "./publication-operation-summary";
|
||||
|
||||
export type PublicationOperation =
|
||||
| { action: "publish"; anchor: PublicationAnchor }
|
||||
| { action: "transfer"; publicationId: string; assetId: string; resourceId: string }
|
||||
@ -23,10 +27,14 @@ export function PublicationEditor({
|
||||
operation,
|
||||
onClose,
|
||||
createdResourceName,
|
||||
context,
|
||||
finalFocus,
|
||||
}: {
|
||||
companyId: string;
|
||||
createdResourceName?: string;
|
||||
context?: PublicationContext;
|
||||
operation: PublicationOperation;
|
||||
finalFocus?: ComponentProps<typeof FormDialog>["finalFocus"];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
@ -96,83 +104,90 @@ export function PublicationEditor({
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormDialog
|
||||
cancelLabel={t(createdResourceName ? "shareUi.linkLater" : "cancel")}
|
||||
formActions
|
||||
layout="stable"
|
||||
onOpenChange={(open) => {
|
||||
if (!(open || mutation.isPending)) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={t(`shareUi.${operation.action}`)}
|
||||
>
|
||||
<FormBody>
|
||||
{createdResourceName && (
|
||||
<p className="text-sm" role="status">
|
||||
{t("shareUi.resourceCreated", { name: createdResourceName })}
|
||||
</p>
|
||||
)}
|
||||
{operation.action !== "end" && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<FormLabel htmlFor={id} required>
|
||||
{t(`shareUi.choose.${kind}`)}
|
||||
</FormLabel>
|
||||
<Button
|
||||
aria-expanded={picker}
|
||||
aria-haspopup="dialog"
|
||||
disabled={mutation.isPending}
|
||||
id={id}
|
||||
onClick={() => setPicker(true)}
|
||||
ref={pickerTriggerRef}
|
||||
variant="outline"
|
||||
>
|
||||
{selected?.label ?? t(`shareUi.choose.${kind}`)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{mutation.isError && (
|
||||
<div className="flex flex-col gap-2" role="alert">
|
||||
<p>
|
||||
{t(`shareUi.errors.${mutation.error.message}`, {
|
||||
defaultValue: t("shareUi.writeError"),
|
||||
})}
|
||||
{!picker && (
|
||||
<FormDialog
|
||||
cancelLabel={t(createdResourceName ? "shareUi.linkLater" : "cancel")}
|
||||
finalFocus={finalFocus}
|
||||
formActions
|
||||
layout="stable"
|
||||
onOpenChange={(open) => {
|
||||
if (!(open || mutation.isPending)) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={t(`shareUi.${operation.action}`)}
|
||||
>
|
||||
<FormBody>
|
||||
{createdResourceName && (
|
||||
<p className="text-sm" role="status">
|
||||
{t("shareUi.resourceCreated", { name: createdResourceName })}
|
||||
</p>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await client.invalidateQueries({ queryKey: publicationKeys.company(companyId) });
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
{t("retry")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormBody>
|
||||
<FormHelp
|
||||
description={t("shareUi.help")}
|
||||
examples={t("shareUi.example")}
|
||||
examplesLabel={t("shareUi.examples")}
|
||||
guidance={t(
|
||||
operation.action === "transfer"
|
||||
? "shareUi.transferHelp"
|
||||
: operation.action === "end"
|
||||
? "shareUi.endHelp"
|
||||
: "shareUi.publishHelp"
|
||||
)}
|
||||
scopeNote={t("shareUi.documentary")}
|
||||
title={t("shareUi.helpTitle")}
|
||||
/>
|
||||
<FormActions cancelDisabled={mutation.isPending}>
|
||||
<Button
|
||||
disabled={mutation.isPending || (operation.action !== "end" && !selected)}
|
||||
onClick={save}
|
||||
>
|
||||
{t(mutation.isPending ? "saving" : "shareUi.confirm")}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</FormDialog>
|
||||
)}
|
||||
<PublicationOperationSummary action={operation.action} context={context} />
|
||||
{operation.action !== "end" && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<FormLabel htmlFor={id} required>
|
||||
{t(`shareUi.choose.${kind}`)}
|
||||
</FormLabel>
|
||||
<Button
|
||||
aria-expanded={picker}
|
||||
aria-haspopup="dialog"
|
||||
className="h-auto min-h-9 whitespace-normal break-words"
|
||||
disabled={mutation.isPending}
|
||||
id={id}
|
||||
onClick={() => setPicker(true)}
|
||||
ref={pickerTriggerRef}
|
||||
variant="outline"
|
||||
>
|
||||
{selected?.label ?? t(`shareUi.choose.${kind}`)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{mutation.isError && (
|
||||
<div className="flex flex-col gap-2" role="alert">
|
||||
<p>
|
||||
{t(`shareUi.errors.${mutation.error.message}`, {
|
||||
defaultValue: t("shareUi.writeError"),
|
||||
})}
|
||||
</p>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await client.invalidateQueries({
|
||||
queryKey: publicationKeys.company(companyId),
|
||||
});
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
{t("retry")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormBody>
|
||||
<FormHelp
|
||||
description={t("shareUi.help")}
|
||||
examples={t("shareUi.example")}
|
||||
examplesLabel={t("shareUi.examples")}
|
||||
guidance={t(OPERATION_HELP_KEYS[operation.action])}
|
||||
scopeNote={t("shareUi.documentary")}
|
||||
title={t("shareUi.helpTitle")}
|
||||
/>
|
||||
<FormActions cancelDisabled={mutation.isPending}>
|
||||
<Button
|
||||
className={
|
||||
operation.action === "end"
|
||||
? "text-foreground"
|
||||
: "text-background hover:bg-primary hover:shadow-sm"
|
||||
}
|
||||
disabled={mutation.isPending || (operation.action !== "end" && !selected)}
|
||||
onClick={save}
|
||||
variant={operation.action === "end" ? "destructive" : "default"}
|
||||
>
|
||||
{t(mutation.isPending ? "saving" : "shareUi.confirm")}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</FormDialog>
|
||||
)}
|
||||
{picker && (
|
||||
<PublicationPicker
|
||||
companyId={companyId}
|
||||
@ -186,3 +201,9 @@ export function PublicationEditor({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const OPERATION_HELP_KEYS = {
|
||||
publish: "shareUi.publishHelp",
|
||||
transfer: "shareUi.transferHelp",
|
||||
end: "shareUi.endHelp",
|
||||
} as const;
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type PublicationContext = {
|
||||
resourceName: string;
|
||||
address: string;
|
||||
currentAssetName: string | undefined;
|
||||
};
|
||||
|
||||
export function PublicationOperationSummary({
|
||||
context,
|
||||
action,
|
||||
}: {
|
||||
context: PublicationContext | undefined;
|
||||
action: "publish" | "transfer" | "end";
|
||||
}) {
|
||||
const { t } = useTranslation("assets");
|
||||
|
||||
return (
|
||||
<>
|
||||
{context && (
|
||||
<dl className="flex min-w-0 flex-col gap-3 text-sm">
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareUi.resource")}</dt>
|
||||
<dd className="break-words font-medium">{context.resourceName}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareUi.address")}</dt>
|
||||
<dd className="break-all font-mono">{context.address}</dd>
|
||||
</div>
|
||||
{action !== "publish" && (
|
||||
<div>
|
||||
<dt className="text-muted-foreground">{t("shareDetail.currentAsset")}</dt>
|
||||
<dd className="break-words font-medium">{context.currentAssetName ?? "—"}</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
)}
|
||||
{action !== "publish" && (
|
||||
<p className="text-sm">
|
||||
{t(action === "end" ? "shareDetail.endExplanation" : "shareDetail.transferExplanation")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -10,7 +10,11 @@ import { I18nextProvider } from "react-i18next";
|
||||
|
||||
import { NetworkShareRelationshipResponseSchema } from "../../../common/network-share-publications";
|
||||
import { assetsEnglish, assetsSpanish } from "../../shared";
|
||||
import { publicationEvents } from "../detail/helpers/publication-events";
|
||||
import { NetworkSharePublications } from "../detail/ui/network-share-publications";
|
||||
import { NetworkShareResourceDetail } from "../detail/ui/network-share-resource-detail";
|
||||
import { PublicationHistory } from "../detail/ui/publication-history";
|
||||
import { ResourcePublicationCard } from "../detail/ui/resource-publication-card";
|
||||
|
||||
import { publicationApi, publicationKeys } from "./api/network-share-publication-api";
|
||||
import { PublicationRow } from "./ui/publication-row";
|
||||
@ -256,3 +260,165 @@ test("SMB: búsqueda remota y cursor opaco viajan al servidor", async () => {
|
||||
globalThis.fetch = previous;
|
||||
}
|
||||
});
|
||||
|
||||
function resourceFixture(ended = false) {
|
||||
const data = fixture(ended);
|
||||
|
||||
return NetworkShareRelationshipResponseSchema.parse({
|
||||
...data,
|
||||
summaries: [
|
||||
{
|
||||
status: "resolved",
|
||||
summary: {
|
||||
reference: data.page.items[0]?.source,
|
||||
name: "novasrv02",
|
||||
subtitle: "Servidor",
|
||||
state: { code: "operational", labelKey: "assets:operational" },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const resourceAnchor = { type: "resources:resource", id: resource } as const;
|
||||
|
||||
function resourceClient(current: unknown, history: unknown) {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: { queries: { staleTime: Number.POSITIVE_INFINITY } },
|
||||
});
|
||||
|
||||
client.setQueryData(publicationKeys.page(company, resourceAnchor, false, null), current);
|
||||
client.setQueryData(publicationKeys.page(company, resourceAnchor, true, null), history);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
test("ficha SMB: publicador destacado, datos y timeline real es/en; solo lectura conserva navegación", async () => {
|
||||
for (const language of ["es", "en"]) {
|
||||
const data = resourceFixture();
|
||||
const html = await render(
|
||||
language,
|
||||
<NetworkShareResourceDetail
|
||||
active
|
||||
address={String.raw`\\alias\General`}
|
||||
canManage={false}
|
||||
companyId={company}
|
||||
data={<p>Descripción real</p>}
|
||||
resourceId={resource}
|
||||
resourceName="General"
|
||||
>
|
||||
<p>Navegación real</p>
|
||||
</NetworkShareResourceDetail>,
|
||||
resourceClient(data, data)
|
||||
);
|
||||
|
||||
assert.ok(html.indexOf('data-slot="resource-publication"') < html.indexOf("Descripción real"));
|
||||
assert.ok(html.includes("novasrv02"));
|
||||
assert.ok(html.includes(`/companies/${company}/assets/${asset}`));
|
||||
assert.ok(html.includes(actor));
|
||||
assert.ok(
|
||||
html.includes(
|
||||
language === "es" ? "Correspondencia no verificada" : "Correspondence not verified"
|
||||
)
|
||||
);
|
||||
assert.ok(html.includes(language === "es" ? "Consulta de solo lectura" : "Read-only view"));
|
||||
assert.equal(html.includes(language === "es" ? "Cambiar activo" : "Change asset"), false);
|
||||
assert.equal(
|
||||
html.includes(language === "es" ? "Finalizar publicación" : "End publication"),
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("ficha SMB: vacío, finalizada, carga y denegación son estados distintos", async () => {
|
||||
const ended = resourceFixture(true);
|
||||
const empty = { page: { items: [], nextCursor: null }, summaries: [], publications: [] };
|
||||
const card = (
|
||||
<ResourcePublicationCard
|
||||
active
|
||||
address="UNC"
|
||||
canManage
|
||||
companyId={company}
|
||||
onHistory={() => undefined}
|
||||
onManage={() => undefined}
|
||||
resourceId={resource}
|
||||
/>
|
||||
);
|
||||
const vacant = await render("es", card, resourceClient(empty, empty));
|
||||
|
||||
assert.ok(vacant.includes("Esta carpeta no tiene un activo publicador asociado"));
|
||||
assert.ok(vacant.includes("Asignar activo"));
|
||||
const closed = await render("es", card, resourceClient(empty, ended));
|
||||
|
||||
assert.ok(closed.includes("La publicación registrada ha finalizado"));
|
||||
assert.equal(closed.includes("Cambiar activo"), false);
|
||||
const loading = await render("es", card);
|
||||
|
||||
assert.ok(loading.includes('aria-busy="true"'));
|
||||
assert.equal(loading.includes("Asignar activo"), false);
|
||||
const denied = resourceClient(ended, ended);
|
||||
|
||||
denied
|
||||
.getQueryCache()
|
||||
.find({ queryKey: publicationKeys.page(company, resourceAnchor, false, null) })
|
||||
?.setState({ status: "error", error: new Error("FORBIDDEN") });
|
||||
const forbidden = await render("es", card, denied);
|
||||
|
||||
assert.ok(forbidden.includes(assetsSpanish.listState.forbidden));
|
||||
assert.equal(forbidden.includes("novasrv02"), false);
|
||||
assert.equal(forbidden.includes("Asignar activo"), false);
|
||||
});
|
||||
|
||||
test("timeline SMB: solo hechos persistidos, UUID estable, traslado y cierre sin sucesor en página", () => {
|
||||
const period = fixture().publications[0];
|
||||
|
||||
assert.ok(period);
|
||||
const initial = publicationEvents([period]);
|
||||
|
||||
assert.equal(initial.length, 1);
|
||||
assert.equal(initial[0]?.kind, "publish");
|
||||
const transferred = {
|
||||
...period,
|
||||
id: "019a0000-0000-7000-8000-000000000006",
|
||||
predecessorId: period.id,
|
||||
endedAt: "2026-10-21T10:00:00.000Z",
|
||||
endedBy: actor,
|
||||
};
|
||||
const events = publicationEvents([transferred]);
|
||||
|
||||
assert.deepEqual(
|
||||
events.map((event) => event.kind),
|
||||
["transfer", "end"]
|
||||
);
|
||||
assert.equal(events[0]?.period.predecessorId, period.id);
|
||||
assert.equal(events[1]?.dateTime, transferred.endedAt);
|
||||
assert.equal(publicationEvents([]).length, 0);
|
||||
});
|
||||
|
||||
test("historial v2: utiliza únicamente nombres autorizados aportados y conserva UUID técnicos", async () => {
|
||||
const data = resourceFixture();
|
||||
const known = await render(
|
||||
"es",
|
||||
<PublicationHistory
|
||||
actorNames={new Map([[actor, "Técnica Álvarez"]])}
|
||||
companyId={company}
|
||||
resourceId={resource}
|
||||
/>,
|
||||
resourceClient(data, data)
|
||||
);
|
||||
|
||||
assert.ok(known.includes("Por Técnica Álvarez"));
|
||||
assert.ok(known.includes('aria-label="Detalles técnicos"'));
|
||||
assert.ok(known.includes(actor));
|
||||
assert.ok(known.includes(publication));
|
||||
const unresolved = await render(
|
||||
"en",
|
||||
<PublicationHistory companyId={company} resourceId={resource} />,
|
||||
resourceClient(data, data)
|
||||
);
|
||||
|
||||
assert.equal(unresolved.includes("Técnica Álvarez"), false);
|
||||
assert.equal(unresolved.includes("By "), false);
|
||||
assert.ok(unresolved.includes(actor));
|
||||
assert.ok(unresolved.includes("Technical details"));
|
||||
});
|
||||
|
||||
@ -939,6 +939,8 @@
|
||||
}
|
||||
},
|
||||
"smb": {
|
||||
"usage": "Usage and navigation",
|
||||
"usageDescription": "Requirements, grants and identities related to this share. Publication identifies who offers it; these access records document its usage.",
|
||||
"access": "Documented access",
|
||||
"need": "Needs",
|
||||
"grant": "Grants",
|
||||
|
||||
@ -939,6 +939,8 @@
|
||||
}
|
||||
},
|
||||
"smb": {
|
||||
"usage": "Uso y navegación",
|
||||
"usageDescription": "Necesidades, concesiones e identidades relacionadas con esta carpeta. La publicación identifica quién la ofrece; estos accesos documentan su uso.",
|
||||
"access": "Accesos documentados",
|
||||
"need": "Necesidades",
|
||||
"grant": "Concesiones",
|
||||
|
||||
@ -51,6 +51,34 @@ export function AccessWorkspacePage(props: AccessWorkspacePageProps) {
|
||||
const { t, i18n } = useTranslation("access");
|
||||
const tableData: AccessResponseDTO = data ?? { items: [], reviews: [], coverage: [] };
|
||||
|
||||
const groupEnvironment = (item: AccessItemDTO) => {
|
||||
if (item.values.kind !== "group") {
|
||||
return null;
|
||||
}
|
||||
const environmentId = item.values.environmentId;
|
||||
const environment = tableData.items.find((entry) => entry.id === environmentId);
|
||||
|
||||
return environment?.values.kind === "environment" ? environment.values.name : null;
|
||||
};
|
||||
|
||||
const groupIdentifier = (item: AccessItemDTO) =>
|
||||
item.values.kind === "group" ? item.values.identifier : null;
|
||||
|
||||
const groupColumns = [
|
||||
{
|
||||
id: "environmentId",
|
||||
label: t("environmentId"),
|
||||
value: groupEnvironment,
|
||||
render: groupEnvironment,
|
||||
},
|
||||
{
|
||||
id: "identifier",
|
||||
label: t("identifier"),
|
||||
value: groupIdentifier,
|
||||
render: groupIdentifier,
|
||||
},
|
||||
].filter(() => kind === "group");
|
||||
|
||||
return (
|
||||
<div className="min-w-0 space-y-6" data-slot="list-context" ref={rootRef}>
|
||||
<EntityListHeader
|
||||
@ -155,6 +183,7 @@ export function AccessWorkspacePage(props: AccessWorkspacePageProps) {
|
||||
/>
|
||||
),
|
||||
},
|
||||
...groupColumns,
|
||||
...(["account", "group", "environment"].includes(kind)
|
||||
? [
|
||||
{
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
import { NetworkShareResourceDetail } from "@metis/assets/client";
|
||||
import { BrowserLink } from "@metis/core/client";
|
||||
import type { ResourceDTO } from "@metis/resources/common";
|
||||
import { DetailPanel } from "@repo/rdx-ui/components/detail-panel";
|
||||
import { KeyRoundIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import type { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function CompanyNetworkShareDetail({
|
||||
companyId,
|
||||
resource,
|
||||
data,
|
||||
canManage,
|
||||
}: {
|
||||
companyId: string;
|
||||
resource: ResourceDTO;
|
||||
data: ReactNode;
|
||||
canManage: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation("companies");
|
||||
|
||||
if (!resource.networkShare) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const actorNames = new Map(
|
||||
resource.history
|
||||
.filter((revision) => revision.actorName !== revision.actorId)
|
||||
.map((revision) => [revision.actorId, revision.actorName])
|
||||
);
|
||||
|
||||
return (
|
||||
<NetworkShareResourceDetail
|
||||
active={resource.values.active}
|
||||
actorNames={actorNames}
|
||||
address={resource.networkShare.address}
|
||||
canManage={canManage}
|
||||
companyId={companyId}
|
||||
data={data}
|
||||
resourceId={resource.id}
|
||||
resourceName={resource.values.name}
|
||||
>
|
||||
<DetailPanel
|
||||
density="compact"
|
||||
description={t("smb.usageDescription")}
|
||||
icon={<KeyRoundIcon />}
|
||||
title={t("smb.usage")}
|
||||
>
|
||||
<nav aria-label={t("smb.access")} className="flex flex-wrap gap-3">
|
||||
{(["need", "grant", "permission", "account", "group"] as const).map((kind) => (
|
||||
<BrowserLink
|
||||
href={`/companies/${companyId}/access?kind=${kind}&resourceId=${resource.id}`}
|
||||
key={kind}
|
||||
>
|
||||
{t(`smb.${kind}`)}
|
||||
</BrowserLink>
|
||||
))}
|
||||
</nav>
|
||||
<p className="text-sm text-muted-foreground">{t("smb.documentary")}</p>
|
||||
</DetailPanel>
|
||||
</NetworkShareResourceDetail>
|
||||
);
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { useState } from "react";
|
||||
|
||||
import type { CompanyDTO } from "../../../../common";
|
||||
import { CompanyEntityContext } from "../../../companies/detail/ui/company-entity-context";
|
||||
import { CompanyNetworkShareDetail } from "../../detail/ui/company-network-share-detail";
|
||||
import { CompanyResourceRelatedContent } from "../../detail/ui/company-resource-related-content";
|
||||
|
||||
export function CompanyResources({
|
||||
@ -27,6 +28,16 @@ export function CompanyResources({
|
||||
<>
|
||||
<ResourcesPage
|
||||
companyId={company.id}
|
||||
detailContent={(resource, data, canManage) =>
|
||||
resource.networkShare?.capability === "network:smb-share" ? (
|
||||
<CompanyNetworkShareDetail
|
||||
canManage={canManage}
|
||||
companyId={company.id}
|
||||
data={data}
|
||||
resource={resource}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
initialCreate={initialCreate}
|
||||
initialId={initialId}
|
||||
onCreated={(resource, onComplete) => {
|
||||
|
||||
@ -168,10 +168,12 @@ export function registerIdentityRoutes(
|
||||
"/auth/logout",
|
||||
BootstrapAdminRequestSchema.pick({}),
|
||||
async (_dto, request, response) => {
|
||||
const secure = request.session.cookie.secure === true;
|
||||
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
request.session.destroy((error) => (error ? reject(error) : resolve()))
|
||||
);
|
||||
response.clearCookie("metis.sid", { httpOnly: true, sameSite: "strict", path: "/" });
|
||||
response.clearCookie("metis.sid", { httpOnly: true, sameSite: "strict", path: "/", secure });
|
||||
response.json({ success: true });
|
||||
}
|
||||
);
|
||||
|
||||
@ -321,6 +321,11 @@
|
||||
"smbAccessConfigured": "Manage access in Accounts and access. Edit the address and status in this resource.",
|
||||
"smbAccessHistorical": "This existing resource retains its access configuration. Saving does not convert it or add an integration.",
|
||||
"smb": {
|
||||
"showName": "Show full name",
|
||||
"hideName": "Collapse name",
|
||||
"path": "UNC path",
|
||||
"resourceHistory": "Resource history",
|
||||
"subtitle": "Network resource · SMB",
|
||||
"copy": "Copy UNC address",
|
||||
"copied": "Address copied",
|
||||
"error": "Copy failed. Select the address and copy it manually."
|
||||
|
||||
@ -321,6 +321,11 @@
|
||||
"smbAccessConfigured": "Los accesos se gestionan en Cuentas y accesos. La dirección y el estado se editan en esta ficha.",
|
||||
"smbAccessHistorical": "Este recurso anterior conserva su configuración de accesos. Guardarlo no lo convierte ni añade una integración.",
|
||||
"smb": {
|
||||
"showName": "Mostrar nombre completo",
|
||||
"hideName": "Contraer nombre",
|
||||
"path": "Ruta UNC",
|
||||
"resourceHistory": "Historial del recurso",
|
||||
"subtitle": "Recurso de red · SMB",
|
||||
"copy": "Copiar dirección UNC",
|
||||
"copied": "Dirección copiada",
|
||||
"error": "No se ha podido copiar. Selecciona la dirección y cópiala manualmente."
|
||||
|
||||
@ -11,5 +11,6 @@ export { catalogApi, catalogKeys } from "./catalog/shared/api";
|
||||
export { GlobalApplicationsPage } from "./global-applications/list/ui/global-applications-page";
|
||||
export { GlobalOperatingSystemsPage } from "./global-operating-systems/list/ui/global-operating-systems-page";
|
||||
export { ResourceEditor, ResourcesPage } from "./resources";
|
||||
export { NetworkShareAddress } from "./resources/detail/ui/network-share-address";
|
||||
export { inventoryApi, inventoryKeys } from "./resources/shared/api";
|
||||
export { resourcesEnglish, resourcesSpanish } from "./shared";
|
||||
|
||||
@ -1,31 +1,97 @@
|
||||
import { CheckIcon, CopyIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useState } from "react";
|
||||
import { Fragment, useId, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function NetworkShareAddress({ address }: { address: string }) {
|
||||
export function NetworkShareAddress({
|
||||
address,
|
||||
variant = "compact",
|
||||
}: {
|
||||
address: string;
|
||||
variant?: "block" | "compact";
|
||||
}) {
|
||||
const { t } = useTranslation("resources");
|
||||
const id = useId();
|
||||
const [state, setState] = useState<"idle" | "copied" | "error">("idle");
|
||||
const parts: { id: string; text: string; breakBefore: boolean }[] = [];
|
||||
let prefix = "";
|
||||
|
||||
for (const segment of address.split(/(?<!\\)\\(?!\\)/u)) {
|
||||
const breakBefore = parts.length > 0;
|
||||
const text = breakBefore ? `\\${segment}` : segment;
|
||||
|
||||
prefix += text;
|
||||
parts.push({ id: prefix, text, breakBefore });
|
||||
}
|
||||
const copy = (
|
||||
<Button
|
||||
aria-describedby={id}
|
||||
aria-label={t("smb.copy")}
|
||||
className="shrink-0 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
setState("copied");
|
||||
} catch {
|
||||
setState("error");
|
||||
}
|
||||
}}
|
||||
size="icon-sm"
|
||||
title={t(state === "copied" ? "smb.copied" : "smb.copy")}
|
||||
variant="ghost"
|
||||
>
|
||||
{state === "copied" ? <CheckIcon aria-hidden="true" /> : <CopyIcon aria-hidden="true" />}
|
||||
</Button>
|
||||
);
|
||||
const value = (
|
||||
<code
|
||||
className={
|
||||
variant === "block"
|
||||
? "min-w-0 select-all whitespace-pre-wrap break-normal text-sm [overflow-wrap:anywhere]"
|
||||
: "line-clamp-2 min-w-0 flex-1 select-all whitespace-pre-wrap break-normal text-xs [overflow-wrap:anywhere]"
|
||||
}
|
||||
id={id}
|
||||
title={variant === "compact" ? address : undefined}
|
||||
>
|
||||
{parts.map((part) => (
|
||||
<Fragment key={part.id}>
|
||||
{part.breakBefore && <wbr />}
|
||||
{part.text}
|
||||
</Fragment>
|
||||
))}
|
||||
</code>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<code className="min-w-0 select-all break-all">{address}</code>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
setState("copied");
|
||||
} catch {
|
||||
setState("error");
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
{t("smb.copy")}
|
||||
</Button>
|
||||
</div>
|
||||
{state !== "idle" && <p role={state === "error" ? "alert" : "status"}>{t(`smb.${state}`)}</p>}
|
||||
<div
|
||||
className="flex min-w-0 flex-col gap-1"
|
||||
data-slot="network-share-address"
|
||||
data-variant={variant}
|
||||
>
|
||||
{variant === "block" ? (
|
||||
<div className="flex min-w-0 flex-col gap-1 rounded-md bg-muted/40 p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs text-muted-foreground">{t("smb.path")}</span>
|
||||
{copy}
|
||||
</div>
|
||||
{value}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 items-start gap-1">
|
||||
{value}
|
||||
{copy}
|
||||
</div>
|
||||
)}
|
||||
{state === "copied" && (
|
||||
<span className="sr-only" role="status">
|
||||
{t("smb.copied")}
|
||||
</span>
|
||||
)}
|
||||
{state === "error" && (
|
||||
<p className="text-sm" role="alert">
|
||||
{t("smb.error")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
import { ChevronDownIcon, ChevronUpIcon, FolderIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@repo/shadcn-ui/components/collapsible";
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/** Limita la altura inicial; el nombre completo sigue disponible por lectura y expansión explícita. */
|
||||
export function NetworkShareResourceTitle({ name }: { name: string }) {
|
||||
const { t } = useTranslation("resources");
|
||||
const id = useId();
|
||||
const previewRef = useRef<HTMLSpanElement>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [truncated, setTruncated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const preview = previewRef.current;
|
||||
|
||||
if (!preview || open) {
|
||||
return;
|
||||
}
|
||||
|
||||
const measure = () => {
|
||||
if (preview.textContent !== name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clipped = preview.scrollHeight > preview.clientHeight + 1;
|
||||
|
||||
if (!clipped && document.activeElement === triggerRef.current) {
|
||||
preview.focus({ preventScroll: true });
|
||||
}
|
||||
setTruncated(clipped);
|
||||
};
|
||||
|
||||
measure();
|
||||
const observer = new ResizeObserver(measure);
|
||||
|
||||
observer.observe(preview);
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [name, open]);
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
className="flex min-w-0 flex-1 items-start gap-2 text-base leading-snug sm:text-lg sm:leading-7"
|
||||
onOpenChange={setOpen}
|
||||
open={open}
|
||||
render={<span />}
|
||||
>
|
||||
<FolderIcon aria-hidden="true" className="mt-1 size-5 shrink-0 text-primary" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span
|
||||
className={open ? "hidden" : "line-clamp-3 break-words sm:line-clamp-2"}
|
||||
hidden={open}
|
||||
ref={previewRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<CollapsibleContent
|
||||
className="block break-words motion-reduce:animate-none"
|
||||
id={id}
|
||||
keepMounted
|
||||
render={<span />}
|
||||
>
|
||||
{name}
|
||||
</CollapsibleContent>
|
||||
</span>
|
||||
{(truncated || open) && (
|
||||
<CollapsibleTrigger
|
||||
aria-controls={id}
|
||||
aria-label={t(open ? "smb.hideName" : "smb.showName")}
|
||||
ref={triggerRef}
|
||||
render={
|
||||
<Button
|
||||
className="focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
/>
|
||||
}
|
||||
title={t(open ? "smb.hideName" : "smb.showName")}
|
||||
>
|
||||
{open ? <ChevronUpIcon aria-hidden="true" /> : <ChevronDownIcon aria-hidden="true" />}
|
||||
</CollapsibleTrigger>
|
||||
)}
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import { EntityAction, EntityRowActions } from "@repo/rdx-ui/components/entity-list";
|
||||
import { PencilIcon } from "@repo/rdx-ui/lib/icons";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { ResourceDTO } from "../../../../common/inventory";
|
||||
|
||||
export function ResourceDetailActions({
|
||||
resource,
|
||||
canEdit,
|
||||
canChangeStatus,
|
||||
onEdit,
|
||||
onStatus,
|
||||
onDelete,
|
||||
}: {
|
||||
resource: ResourceDTO | undefined;
|
||||
canEdit: boolean;
|
||||
canChangeStatus: boolean;
|
||||
onEdit: (id: string) => void;
|
||||
onStatus: (id: string) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("resources");
|
||||
|
||||
if (!resource?.networkShare) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<EntityRowActions label={t("actions")}>
|
||||
<EntityAction disabled={!canChangeStatus} onClick={() => onStatus(resource.id)}>
|
||||
{t(
|
||||
resource.values.active ? "resourceLifecycle.deactivate" : "resourceLifecycle.activate"
|
||||
)}
|
||||
</EntityAction>
|
||||
<EntityAction disabled={!canEdit} onClick={() => onDelete(resource.id)}>
|
||||
{t("resourceLifecycle.delete")}
|
||||
</EntityAction>
|
||||
</EntityRowActions>
|
||||
<Button disabled={!canEdit} onClick={() => onEdit(resource.id)} variant="outline">
|
||||
<PencilIcon aria-hidden="true" />
|
||||
{t("edit")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
import { EntityStatus } from "@repo/rdx-ui/components/entity-list";
|
||||
import { FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ApplicationCoincidencesAction } from "../../../application-coincidences/review/ui/application-coincidences-action";
|
||||
import type { ResourcesPageProps } from "../../list/types/resources-page-props";
|
||||
|
||||
import { NetworkShareResourceTitle } from "./network-share-resource-title";
|
||||
import { ResourceDetail } from "./resource-detail";
|
||||
|
||||
export function ResourceDetailDialog({
|
||||
@ -11,6 +14,9 @@ export function ResourceDetailDialog({
|
||||
onReviewCoincidences,
|
||||
references,
|
||||
relatedContent,
|
||||
detailContent,
|
||||
actions,
|
||||
canManage = false,
|
||||
onClose,
|
||||
finalFocus,
|
||||
}: {
|
||||
@ -20,6 +26,9 @@ export function ResourceDetailDialog({
|
||||
relatedContent:
|
||||
| ((resource: ComponentProps<typeof ResourceDetail>["resource"]) => ReactNode)
|
||||
| undefined;
|
||||
detailContent?: ResourcesPageProps["detailContent"];
|
||||
actions?: ReactNode;
|
||||
canManage?: boolean;
|
||||
onClose: () => void;
|
||||
finalFocus?: ComponentProps<typeof FormDialog>["finalFocus"];
|
||||
}) {
|
||||
@ -29,6 +38,12 @@ export function ResourceDetailDialog({
|
||||
return null;
|
||||
}
|
||||
|
||||
const custom = detailContent?.(
|
||||
resource,
|
||||
<ResourceDetail compact references={references} resource={resource} />,
|
||||
canManage
|
||||
);
|
||||
|
||||
return (
|
||||
<FormDialog
|
||||
cancelLabel={t("close")}
|
||||
@ -41,18 +56,35 @@ export function ResourceDetailDialog({
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={resource.values.name}
|
||||
title={
|
||||
resource.networkShare ? (
|
||||
<NetworkShareResourceTitle key={resource.id} name={resource.values.name} />
|
||||
) : (
|
||||
resource.values.name
|
||||
)
|
||||
}
|
||||
wide
|
||||
>
|
||||
<FormBody>
|
||||
<ResourceDetail references={references} resource={resource} />
|
||||
{resource.networkShare && (
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<p className="text-sm text-muted-foreground">{t("smb.subtitle")}</p>
|
||||
<EntityStatus active={resource.values.active}>
|
||||
{t(resource.values.active ? "active" : "inactive")}
|
||||
</EntityStatus>
|
||||
</div>
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
{custom ?? <ResourceDetail references={references} resource={resource} />}
|
||||
{onReviewCoincidences && (
|
||||
<ApplicationCoincidencesAction
|
||||
onReview={(trigger) => onReviewCoincidences(trigger, resource.id)}
|
||||
resource={resource}
|
||||
/>
|
||||
)}
|
||||
{relatedContent?.(resource)}
|
||||
{!custom && relatedContent?.(resource)}
|
||||
</FormBody>
|
||||
</FormDialog>
|
||||
);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
||||
|
||||
import { IntegrationSummary } from "./integration-summary";
|
||||
import { NetworkShareAddress } from "./network-share-address";
|
||||
import { ResourceRevisionHistory } from "./resource-revision-history";
|
||||
|
||||
export function ResourceDetail({
|
||||
resource,
|
||||
@ -17,7 +17,7 @@ export function ResourceDetail({
|
||||
compact?: boolean;
|
||||
references?: readonly ResourceReferenceDTO[];
|
||||
}) {
|
||||
const { t, i18n } = useTranslation("resources");
|
||||
const { t } = useTranslation("resources");
|
||||
|
||||
const display = (value: string | number | boolean | undefined) =>
|
||||
value === undefined
|
||||
@ -56,7 +56,10 @@ export function ResourceDetail({
|
||||
>
|
||||
{resource.networkShare?.capability === "network:smb-share" &&
|
||||
resource.values.characteristics[field.fieldId] === resource.networkShare.address ? (
|
||||
<NetworkShareAddress address={resource.networkShare.address} />
|
||||
<NetworkShareAddress
|
||||
address={resource.networkShare.address}
|
||||
key={resource.networkShare.address}
|
||||
/>
|
||||
) : (
|
||||
display(resource.values.characteristics[field.fieldId])
|
||||
)}
|
||||
@ -71,48 +74,21 @@ export function ResourceDetail({
|
||||
<p className="whitespace-pre-wrap break-words">{resource.values.notes}</p>
|
||||
)}
|
||||
{!compact && (
|
||||
<>
|
||||
<FormSectionTitle>
|
||||
{t("history")} ({resource.history.length})
|
||||
</FormSectionTitle>
|
||||
<ol className="space-y-4">
|
||||
{[...resource.history].reverse().map((revision) => (
|
||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||
<p className="font-medium">
|
||||
{new Intl.DateTimeFormat(i18n.language, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(new Date(revision.recordedAt))}{" "}
|
||||
· {t("recordedBy", { name: revision.actorName })}
|
||||
</p>
|
||||
<p>
|
||||
{revision.values.name} · {t(revision.values.active ? "active" : "inactive")}
|
||||
</p>
|
||||
<FieldSections
|
||||
items={(revision.specification ?? resource.specification).fields}
|
||||
renderItem={(field) => (
|
||||
<div key={field.fieldId}>
|
||||
<dt className="text-muted-foreground">{field.name}</dt>
|
||||
<dd className="whitespace-pre-wrap break-words">
|
||||
{display(revision.values.characteristics[field.fieldId])}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
sections={(revision.specification ?? resource.specification).sections}
|
||||
ungroupedLabel={t("sections.ungrouped")}
|
||||
values
|
||||
/>
|
||||
<IntegrationSummary
|
||||
integration={revision.values.integration}
|
||||
references={references}
|
||||
/>
|
||||
{revision.values.notes && (
|
||||
<p className="whitespace-pre-wrap break-words">{revision.values.notes}</p>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</>
|
||||
<ResourceRevisionHistory display={display} references={references} resource={resource} />
|
||||
)}
|
||||
{compact && resource.networkShare && (
|
||||
<details className="min-w-0 text-sm">
|
||||
<summary className="cursor-pointer rounded-sm font-medium focus-visible:outline-2 focus-visible:outline-ring">
|
||||
{t("smb.resourceHistory")} ({resource.history.length})
|
||||
</summary>
|
||||
<div className="mt-4">
|
||||
<ResourceRevisionHistory
|
||||
display={display}
|
||||
references={references}
|
||||
resource={resource}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type {
|
||||
ResourceDTO,
|
||||
ResourceInput,
|
||||
ResourceReferenceDTO,
|
||||
} from "../../../../common/inventory";
|
||||
|
||||
import { IntegrationSummary } from "./integration-summary";
|
||||
|
||||
export function ResourceRevisionHistory({
|
||||
resource,
|
||||
references,
|
||||
display,
|
||||
}: {
|
||||
resource: ResourceDTO;
|
||||
references: readonly ResourceReferenceDTO[];
|
||||
display: (value: ResourceInput["characteristics"][string] | undefined) => string;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation("resources");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormSectionTitle>
|
||||
{t("history")} ({resource.history.length})
|
||||
</FormSectionTitle>
|
||||
<ol className="space-y-4">
|
||||
{[...resource.history].reverse().map((revision) => (
|
||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||
<p className="font-medium">
|
||||
{new Intl.DateTimeFormat(i18n.language, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(new Date(revision.recordedAt))}{" "}
|
||||
· {t("recordedBy", { name: revision.actorName })}
|
||||
</p>
|
||||
<p>
|
||||
{revision.values.name} · {t(revision.values.active ? "active" : "inactive")}
|
||||
</p>
|
||||
<FieldSections
|
||||
items={(revision.specification ?? resource.specification).fields}
|
||||
renderItem={(field) => (
|
||||
<div key={field.fieldId}>
|
||||
<dt className="text-muted-foreground">{field.name}</dt>
|
||||
<dd className="whitespace-pre-wrap break-words">
|
||||
{display(revision.values.characteristics[field.fieldId])}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
sections={(revision.specification ?? resource.specification).sections}
|
||||
ungroupedLabel={t("sections.ungrouped")}
|
||||
values
|
||||
/>
|
||||
<IntegrationSummary integration={revision.values.integration} references={references} />
|
||||
{revision.values.notes && (
|
||||
<p className="whitespace-pre-wrap break-words">{revision.values.notes}</p>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -8,7 +8,9 @@ import type { CatalogItemDTO } from "../../../../common";
|
||||
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
||||
import { ApplicationCoincidencesAction } from "../../../application-coincidences/review/ui/application-coincidences-action";
|
||||
import { ResourceTemplateIcon } from "../../../catalog-templates/shared/ui/resource-template-icon";
|
||||
import type { ResourcesPageProps } from "../../list/types/resources-page-props";
|
||||
|
||||
import { NetworkShareResourceTitle } from "./network-share-resource-title";
|
||||
import { ResourceDetail } from "./resource-detail";
|
||||
|
||||
export function ResourceSelectionPanel({
|
||||
@ -17,6 +19,8 @@ export function ResourceSelectionPanel({
|
||||
template,
|
||||
references,
|
||||
relatedContent,
|
||||
detailContent,
|
||||
actions,
|
||||
onClose,
|
||||
onView,
|
||||
onEdit,
|
||||
@ -27,6 +31,8 @@ export function ResourceSelectionPanel({
|
||||
template?: CatalogItemDTO | undefined;
|
||||
references: readonly ResourceReferenceDTO[];
|
||||
relatedContent: ((resource: ResourceDTO) => ReactNode) | undefined;
|
||||
detailContent?: ResourcesPageProps["detailContent"];
|
||||
actions?: ReactNode;
|
||||
onClose: () => void;
|
||||
onView: () => void;
|
||||
onEdit: () => void;
|
||||
@ -34,6 +40,9 @@ export function ResourceSelectionPanel({
|
||||
}) {
|
||||
const { t } = useTranslation("resources");
|
||||
|
||||
const data = <ResourceDetail compact references={references} resource={resource} />;
|
||||
const custom = detailContent?.(resource, data, canEdit);
|
||||
|
||||
return (
|
||||
<SelectionPanel
|
||||
actions={
|
||||
@ -41,9 +50,13 @@ export function ResourceSelectionPanel({
|
||||
<Button onClick={onView} size="sm" variant="outline">
|
||||
{t("viewResource")}
|
||||
</Button>
|
||||
<Button disabled={!canEdit} onClick={onEdit} size="sm" variant="outline">
|
||||
{t("edit")}
|
||||
</Button>
|
||||
{resource.networkShare && actions ? (
|
||||
actions
|
||||
) : (
|
||||
<Button disabled={!canEdit} onClick={onEdit} size="sm" variant="outline">
|
||||
{t("edit")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
closeLabel={t("closeResourcePanel")}
|
||||
@ -52,27 +65,33 @@ export function ResourceSelectionPanel({
|
||||
onClose={onClose}
|
||||
summary={
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span>{resource.specification.templateName}</span>
|
||||
<span>
|
||||
{resource.networkShare ? t("smb.subtitle") : resource.specification.templateName}
|
||||
</span>
|
||||
<EntityStatus active={resource.values.active}>
|
||||
{t(resource.values.active ? "active" : "inactive")}
|
||||
</EntityStatus>
|
||||
</div>
|
||||
}
|
||||
title={
|
||||
<>
|
||||
<ResourceTemplateIcon
|
||||
className="size-5 shrink-0 text-muted-foreground"
|
||||
icon={template?.values.kind === "template" ? template.values.icon : undefined}
|
||||
/>
|
||||
{resource.values.name}
|
||||
</>
|
||||
resource.networkShare ? (
|
||||
<NetworkShareResourceTitle key={resource.id} name={resource.values.name} />
|
||||
) : (
|
||||
<>
|
||||
<ResourceTemplateIcon
|
||||
className="size-5 shrink-0 text-muted-foreground"
|
||||
icon={template?.values.kind === "template" ? template.values.icon : undefined}
|
||||
/>
|
||||
{resource.values.name}
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<ResourceDetail compact references={references} resource={resource} />
|
||||
{custom ?? data}
|
||||
{onReviewCoincidences && (
|
||||
<ApplicationCoincidencesAction onReview={onReviewCoincidences} resource={resource} />
|
||||
)}
|
||||
{relatedContent?.(resource)}
|
||||
{!custom && relatedContent?.(resource)}
|
||||
</SelectionPanel>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ export type ResourcesPageProps = {
|
||||
companyId: string;
|
||||
/** Completa el flujo posterior al alta antes de abrir la ficha del recurso guardado. */
|
||||
onCreated?: (resource: ResourceDTO, onComplete: () => void) => void;
|
||||
/** Extensión de ficha: recibe los datos actuales y conserva la presentación genérica si no devuelve contenido. */
|
||||
detailContent?: (resource: ResourceDTO, data: ReactNode, canManage: boolean) => ReactNode;
|
||||
relatedContent?: (resource: ResourceDTO) => ReactNode;
|
||||
initialId?: string | undefined;
|
||||
initialCreate?: boolean | undefined;
|
||||
|
||||
@ -14,6 +14,7 @@ import { ApplicationCoincidencesDialog } from "../../../application-coincidences
|
||||
import { useApplicationCoincidenceReview } from "../../../application-coincidences/shared/use-application-coincidence-review";
|
||||
import { InventoryFeedback } from "../../../shared";
|
||||
import { ResourceDetailDialog, ResourceSelectionPanel } from "../../detail";
|
||||
import { ResourceDetailActions } from "../../detail/ui/resource-detail-actions";
|
||||
import { DeleteResourceDialog } from "../../lifecycle/ui/delete-resource-dialog";
|
||||
import { ResourceEditorDialog } from "../../shared";
|
||||
import { useResourcesPageController } from "../controllers";
|
||||
@ -27,7 +28,7 @@ import { ResourceInventoryTable } from "./resource-inventory-table";
|
||||
|
||||
export function ResourcesPage(props: ResourcesPageProps) {
|
||||
const controller = useResourcesPageController(props);
|
||||
const { companyId, relatedContent, onCreated } = props;
|
||||
const { companyId, relatedContent, detailContent, onCreated } = props;
|
||||
const {
|
||||
editor,
|
||||
setEditor,
|
||||
@ -193,7 +194,18 @@ export function ResourcesPage(props: ResourcesPageProps) {
|
||||
</div>
|
||||
{selected && detailOpen && detail.mode === "inline" && (
|
||||
<ResourceSelectionPanel
|
||||
actions={
|
||||
<ResourceDetailActions
|
||||
canChangeStatus={controller.canChangeStatus}
|
||||
canEdit={canMutate}
|
||||
onDelete={() => setDeleteId(selected.id)}
|
||||
onEdit={() => openEditor(selected.id)}
|
||||
onStatus={() => controller.toggleResourceStatus(selected.id)}
|
||||
resource={selected}
|
||||
/>
|
||||
}
|
||||
canEdit={canMutate}
|
||||
detailContent={detailContent}
|
||||
onClose={() => detail.setVisible(false)}
|
||||
onEdit={() => openEditor(selected.id)}
|
||||
onReviewCoincidences={(trigger) => review.open(selected.id, trigger, viewed?.id)}
|
||||
@ -225,6 +237,24 @@ export function ResourcesPage(props: ResourcesPageProps) {
|
||||
/>
|
||||
)}
|
||||
<ResourceDetailDialog
|
||||
actions={
|
||||
<ResourceDetailActions
|
||||
canChangeStatus={controller.canChangeStatus}
|
||||
canEdit={canMutate}
|
||||
onDelete={(id) => {
|
||||
setViewId(undefined);
|
||||
setDeleteId(id);
|
||||
}}
|
||||
onEdit={(id) => {
|
||||
setViewId(undefined);
|
||||
openEditor(id);
|
||||
}}
|
||||
onStatus={controller.toggleResourceStatus}
|
||||
resource={coincidenceParent}
|
||||
/>
|
||||
}
|
||||
canManage={canMutate}
|
||||
detailContent={detailContent}
|
||||
finalFocus={detail.finalFocus}
|
||||
onClose={() => setViewId(undefined)}
|
||||
onReviewCoincidences={(trigger, id) => review.open(id, trigger, id)}
|
||||
|
||||
@ -18,6 +18,7 @@ import { ResourceSchema } from "../../../common/inventory";
|
||||
import { catalogKeys } from "../../catalog/shared/api";
|
||||
import { resourcesEnglish, resourcesSpanish } from "../../shared";
|
||||
import { ResourceCreateProgress } from "../create/ui/resource-create-progress";
|
||||
import { NetworkShareAddress } from "../detail/ui/network-share-address";
|
||||
import { ResourceDetail } from "../detail/ui/resource-detail";
|
||||
import { ResourceSelectionPanel } from "../detail/ui/resource-selection-panel";
|
||||
import { ResourcesPage } from "../list";
|
||||
@ -647,3 +648,17 @@ test("SMB: formulario explica acceso automático o histórico sin selectores inc
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("UNC: presentación por segmentos conserva exactamente separadores, Unicode y valores repetidos", async () => {
|
||||
const address = String.raw`\\alias-corporativo\Documentación administrativa y operaciones\Área compartida con nombres largos\Área compartida con nombres largos`;
|
||||
|
||||
for (const variant of ["block", "compact"] as const) {
|
||||
const html = await render("es", <NetworkShareAddress address={address} variant={variant} />);
|
||||
const code = html.match(/<code[^>]*>([\s\S]*?)<\/code>/)?.[1];
|
||||
|
||||
assert.ok(typeof code === "string");
|
||||
assert.equal(code.replace(/<[^>]*>/g, ""), address);
|
||||
assert.ok(html.includes('aria-label="Copiar dirección UNC"'));
|
||||
assert.equal(html.includes("<input"), false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -27,7 +27,8 @@
|
||||
"test:application-coincidences": "python3 scripts/resource-scope-test.py --coincidences --regressions",
|
||||
"test:global-profiles": "python3 scripts/resource-scope-test.py --profiles --regressions",
|
||||
"test:global-operating-systems": "python3 scripts/resource-scope-test.py --operating-systems --regressions",
|
||||
"test:global-operating-systems:closure": "python3 scripts/resource-scope-test.py --operating-systems-closure --regressions"
|
||||
"test:global-operating-systems:closure": "python3 scripts/resource-scope-test.py --operating-systems-closure --regressions",
|
||||
"test:server-runtime": "python3 scripts/resource-scope-test.py --server-runtime"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.11",
|
||||
|
||||
@ -51,7 +51,9 @@ export function DetailPanel({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{actions && <div className="flex shrink-0 flex-wrap gap-2">{actions}</div>}
|
||||
{actions && (
|
||||
<div className="flex min-w-0 max-w-full shrink-0 flex-wrap gap-2">{actions}</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent
|
||||
className={density === "compact" ? "flex min-w-0 flex-col gap-3 px-4" : "space-y-5"}
|
||||
|
||||
@ -103,8 +103,8 @@ export function FormDialog({
|
||||
triggerRef,
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="relative shrink-0 flex-row items-start justify-between gap-3 border-b px-6 py-4">
|
||||
<div className="min-w-0 flex-1 py-1">
|
||||
<DialogHeader className="relative shrink-0 flex-row items-start justify-between gap-3 border-b px-6 py-[min(1rem,2dvh)]">
|
||||
<div className="min-w-0 max-h-[25dvh] flex-1 overflow-y-auto py-1">
|
||||
<DialogTitle
|
||||
className="break-words text-lg font-bold"
|
||||
tabIndex={initialFocus === "title" ? -1 : undefined}
|
||||
|
||||
@ -9,10 +9,13 @@ export {
|
||||
IconBriefcase as BriefcaseIcon,
|
||||
IconBuildings as Building2Icon,
|
||||
IconCamera as CameraIcon,
|
||||
IconCheck as CheckIcon,
|
||||
IconChevronDown as ChevronDownIcon,
|
||||
IconChevronLeft as ChevronLeftIcon,
|
||||
IconChevronRight as ChevronRightIcon,
|
||||
IconChevronUp as ChevronUpIcon,
|
||||
IconClock as ClockIcon,
|
||||
IconCopy as CopyIcon,
|
||||
IconDatabase as DatabaseIcon,
|
||||
IconDeviceDesktop as MonitorIcon,
|
||||
IconDeviceDesktopCog as MonitorCogIcon,
|
||||
|
||||
@ -71,6 +71,9 @@ importers:
|
||||
sequelize:
|
||||
specifier: 6.37.8
|
||||
version: 6.37.8(mariadb@3.5.4)
|
||||
sharp:
|
||||
specifier: 0.35.5
|
||||
version: 0.35.5(@types/node@25.6.0)
|
||||
zod:
|
||||
specifier: 4.3.6
|
||||
version: 4.3.6
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user