feat: Implement account deletion functionality with UI and API integration
- Added `removeAccessAccount` function to handle account deletion in the database, ensuring no external references exist. - Created `deleteAccount` API function to facilitate account deletion requests from the client. - Developed `AccountDeleteDialog` component for user confirmation before account deletion, integrating with the delete API. - Introduced resource image handling with validation for PNG uploads, including UI components for image selection and preview. - Implemented resource icon template evolution logic to maintain icon bindings without overwriting existing fields. - Added scripts for auditing account deletion and usage checks, ensuring robust testing of the new features.
10
.artifacts/account-deletion/access-web-tests.log
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
✔ modules/companies/src/web/access/shared/access.test.tsx (4558.908724ms)
|
||||||
|
✔ modules/companies/src/web/shared/client-contracts.test.ts (1590.633921ms)
|
||||||
|
ℹ tests 2
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 2
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4570.096105
|
||||||
9
.artifacts/account-deletion/account-integration.log
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
✔ MariaDB: borrado de cuentas aislado, histórico propio y referencias externas protegidas (1624.360873ms)
|
||||||
|
ℹ tests 1
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 1
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 3315.595432
|
||||||
49
.artifacts/account-deletion/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 hit, replaying logs c70d0f44e89d3a41
|
||||||
|
@metis/server:build: $ tsup --config tsup.config.ts && pnpm check:build
|
||||||
|
@metis/web:build: cache hit, replaying logs c1a994aa56bdbedb
|
||||||
|
@metis/server:build: CLI Building entry: src/index.ts
|
||||||
|
@metis/server:build: CLI Using tsconfig: tsconfig.json
|
||||||
|
@metis/web:build: $ vite build
|
||||||
|
@metis/web:build: vite v8.3.0 building client environment for production...
|
||||||
|
@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/web:build: transforming...
|
||||||
|
@metis/web:build: ✓ 10092 modules transformed.
|
||||||
|
@metis/web:build: rendering chunks...
|
||||||
|
@metis/server:build: CLI Cleaning output folder
|
||||||
|
@metis/server:build: ESM Build start
|
||||||
|
@metis/web:build: computing gzip size...
|
||||||
|
@metis/web:build: dist/index.html 0.54 kB │ gzip: 0.35 kB
|
||||||
|
@metis/server:build: ESM dist/index.js 1.46 MB
|
||||||
|
@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/server:build: ESM dist/index.js.map 2.72 MB
|
||||||
|
@metis/server:build: ESM ⚡️ Build success in 549ms
|
||||||
|
@metis/server:build: $ node --check dist/index.js
|
||||||
|
@metis/web:build: dist/assets/workplace-map-canvas-DLioOiRN.css 15.41 kB │ gzip: 2.56 kB
|
||||||
|
@metis/web:build: dist/assets/index-D8sQAPMw.css 256.35 kB │ gzip: 39.28 kB
|
||||||
|
@metis/web:build: dist/assets/workplace-map-canvas-C96elfY2.js 227.75 kB │ gzip: 74.26 kB
|
||||||
|
@metis/web:build: dist/assets/index-DBWr2ZvO.js 1,858.63 kB │ gzip: 521.01 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.23s
|
||||||
|
|
||||||
|
Tasks: 2 successful, 2 total
|
||||||
|
Cached: 2 cached, 2 total
|
||||||
|
Time: 88ms >>> FULL TURBO
|
||||||
|
|
||||||
16
.artifacts/account-deletion/changed-check.log
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
modules/companies/src/web/access/list/ui/access-workspace.tsx:22:17 lint/complexity/noExcessiveCognitiveComplexity ━━━━━━━━━━
|
||||||
|
|
||||||
|
! Excessive complexity of 16 detected (max: 15).
|
||||||
|
|
||||||
|
20 │ import { AccessCell } from "./access-row";
|
||||||
|
21 │
|
||||||
|
> 22 │ export function AccessWorkspacePage(props: AccessWorkspacePageProps) {
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^
|
||||||
|
23 │ const { scope, people = [] } = props;
|
||||||
|
24 │ const [localOperation, setLocalOperation] = useState<LocalIdentityOperation>();
|
||||||
|
|
||||||
|
i Please refactor this function to reduce its complexity score from 16 to the max allowed complexity 15.
|
||||||
|
|
||||||
|
|
||||||
|
Checked 17 files in 28ms. No fixes applied.
|
||||||
|
Found 1 warning.
|
||||||
1394
.artifacts/account-deletion/check.log
Normal file
18
.artifacts/account-deletion/cleanup-test.mts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { createDatabase, readDatabaseEnvironment } from '../../apps/server/src/config/database';
|
||||||
|
const env=readDatabaseEnvironment(process.env);
|
||||||
|
if(env.DB_NAME!=='metis_desk_test'||env.NODE_ENV!=='test')throw Error('Solo pruebas');
|
||||||
|
const db=createDatabase(env);
|
||||||
|
const company='01a17cae-4bba-7070-a394-13f9009c5cfb';
|
||||||
|
try {
|
||||||
|
await db.transaction(async transaction=>{
|
||||||
|
const replacements={company};
|
||||||
|
const rows=await db.query('SELECT i.kind,i.id FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction,type:'SELECT'});
|
||||||
|
console.info({fixtureCompany:company,items:rows});
|
||||||
|
for(const table of ['access_references','access_person_references']) await db.query(`DELETE r FROM ${table} r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
await db.query('DELETE r FROM access_revisions r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction});
|
||||||
|
for(const table of ['access_account_values','access_system_values']) await db.query(`DELETE v FROM ${table} v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
await db.query('DELETE i FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction});
|
||||||
|
await db.query('DELETE FROM access_workspaces WHERE companyId=:company',{replacements,transaction});
|
||||||
|
await db.query('DELETE FROM companies WHERE id=:company',{replacements,transaction});
|
||||||
|
});
|
||||||
|
}finally {await db.close();}
|
||||||
1
.artifacts/account-deletion/companies-typecheck.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
29
.artifacts/account-deletion/identity-integration.log
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
✖ HTTP y MariaDB: configuración única, usuarios, sesiones y protección del administrador (2444.795646ms)
|
||||||
|
ℹ tests 1
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 0
|
||||||
|
ℹ fail 1
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4073.656643
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at src/identity.test.ts:1:2626
|
||||||
|
✖ HTTP y MariaDB: configuración única, usuarios, sesiones y protección del administrador (2444.795646ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
401 !== 404
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (/home/david/dev/metis-desk/apps/server/src/identity.test.ts:82:14)
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1404:7)
|
||||||
|
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||||
|
generatedMessage: true,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: 401,
|
||||||
|
expected: 404,
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
471
.artifacts/account-deletion/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: 2.
|
||||||
|
Checked 2279 files in 2s. No fixes applied.
|
||||||
|
Found 15 errors.
|
||||||
|
Found 2 warnings.
|
||||||
|
Found 5 infos.
|
||||||
|
lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
× Some errors were emitted while running checks.
|
||||||
|
|
||||||
|
|
||||||
|
[ELIFECYCLE] Command failed with exit code 1.
|
||||||
1
.artifacts/account-deletion/server-typecheck.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
850
.artifacts/account-deletion/test.log
Normal file
@ -0,0 +1,850 @@
|
|||||||
|
$ turbo run test
|
||||||
|
• 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 test in 13 packages
|
||||||
|
• Remote caching disabled
|
||||||
|
|
||||||
|
@metis/core:test: cache bypass, force executing 2283a7af50b4c3f8
|
||||||
|
@repo/rdx-ddd:test: cache bypass, force executing b722cdfdd985a9bd
|
||||||
|
@repo/rdx-ui:test: cache bypass, force executing 08ae8a00e21c47c8
|
||||||
|
@metis/relationship-contracts:test: cache bypass, force executing 97138537b462262f
|
||||||
|
@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
|
||||||
|
@metis/relationship-contracts:test: $ node --import tsx --test --test-isolation=none src/*.test.ts
|
||||||
|
@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
|
||||||
|
@repo/rdx-ddd:test: $ node --import tsx --test src/domain-model.test.ts
|
||||||
|
@metis/relationship-contracts:test: ✔ referencias UUID estables, normalizadas, inmutables y con proveedor coherente (10.751076ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ namespaces canónicos, sin identificadores de tabla, espacios ni segmentos vacíos (0.940807ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ el catálogo valida propietarios, namespaces, duplicados y definiciones (7.275051ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ el catálogo crea una copia profundamente inmutable sin congelar la entrada (0.695705ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ relaciones dirigidas, simétricas y derivadas respetan su semántica declarativa (3.016322ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ consulta estricta: límites, cursores y ausencia de privilegios del navegador (4.563332ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ validación de ámbito, tipo y sentido de consulta sin conceder autorización (1.278109ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ orígenes y páginas conservan referencias sin mezclar compañías (2.342216ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ servicios de prueba por lotes y errores compatibles con Result de Metis (1.34711ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ el grafo de dependencias del paquete es acíclico y ajeno a módulos funcionales (54.991288ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ las importaciones internas de producción no contienen ciclos ni imports privados (5.841041ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ capacidades semánticas explícitas sin registrar proveedores ficticios (2.374917ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ rol semántico opcional conserva la compatibilidad de vistas comunes sin metadatos libres (2.252516ms)
|
||||||
|
@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 774.233357
|
||||||
|
@repo/rdx-ddd:test: ✔ UniqueID genera UUID v7 distintos, conserva UUID existentes y rechaza identificadores inválidos (25.243278ms)
|
||||||
|
@repo/rdx-ddd:test: ✔ los Value Objects validan y comparan valores semánticos por tipo (4.19853ms)
|
||||||
|
@repo/rdx-ddd:test: ✔ entidades y agregados comparan identidad y tipo, independientemente de sus atributos (0.795806ms)
|
||||||
|
@metis/core:test: ✔ el contrato de estado exige un timestamp UTC y un estado conocido (4.047928ms)
|
||||||
|
@metis/core:test: ✔ el endpoint de estado no acepta parámetros de consulta (0.586604ms)
|
||||||
|
@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 874.151861
|
||||||
|
@metis/core:test: ✔ las escrituras leen la sesión actual de Query y siguen su rotación y eliminación (48.513742ms)
|
||||||
|
@metis/core:test: ✔ GET, HEAD y OPTIONS funcionan sin sesión y no envían tokens suministrados por el llamador (1.636811ms)
|
||||||
|
@metis/core:test: ✔ mantiene cuerpos JSON, binarios, headers, cancelación y respuestas de error sin reintentar (3.105622ms)
|
||||||
|
@metis/core:test: ✔ rechaza tokens ausentes o inválidos y destinos externos antes de enviar una petición (1.008307ms)
|
||||||
|
@metis/core:test: ✔ estado de ficha: tablas independientes, aislamiento entre quince propietarios y metadatos ajenos conservados (18.025727ms)
|
||||||
|
@metis/core:test: ✔ estado de ficha: metadatos malformados, ámbitos externos y valores incompatibles no se restauran (5.315637ms)
|
||||||
|
@metis/core:test: ✔ History mantiene URL, filtros, Atrás/Adelante, cierre y suscripciones sin entradas duplicadas (2.050114ms)
|
||||||
|
@metis/core:test: ✔ las redirecciones internas rechazan destinos externos, barras invertidas y caracteres de control (0.363502ms)
|
||||||
|
@metis/core:test: ✔ el retorno reutiliza la entrada exacta del listado y conserva el estado ajeno (1.107208ms)
|
||||||
|
@metis/core:test: ✔ el contexto de retorno valida versión, coordenadas, ámbito y filtros sin guardar registros (3.539625ms)
|
||||||
|
@metis/core:test: ✔ las consultas Unicode válidas no pierden contexto al superar 2000 caracteres codificados (10.01097ms)
|
||||||
|
@metis/core:test: ✔ retorno de detalle y listado conviven sin aceptar el ámbito de otro propietario (0.635704ms)
|
||||||
|
@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 1175.637486
|
||||||
|
@repo/rdx-ui:test: ✔ la tabla compacta conserva semántica y enlaces sin herramientas de exploración (31.342922ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los valores ausentes comparten criterio con EntityTable y cero permanece visible (1.842213ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga y error sin datos no afirman vacío; caché obsoleta conserva registros (10.859977ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la ampliación identifica el contenido controlado sin añadir controles a colecciones pequeñas (5.066836ms)
|
||||||
|
@repo/rdx-ui:test: ✔ EntityTable mantiene búsqueda, columnas y paginación con su contrato anterior (40.96499ms)
|
||||||
|
@repo/rdx-ui:test: ✔ preview controlado: recuperar la ampliación muestra el origen con identidad estable sin perder el modo local (10.919677ms)
|
||||||
|
@repo/rdx-ui:test: ✔ contador: conserva cero y formatea el total por idioma (66.789871ms)
|
||||||
|
@repo/rdx-ui:test: ✔ contador: carga y error muestran ausencia de total, conservando una cifra anterior si existe (1.592911ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la ayuda breve respeta la capacidad efectiva, también con fuentes ampliadas (3.259123ms)
|
||||||
|
@repo/rdx-ui:test: ✔ auto y expanded reservan 40rem al formulario y 20rem al panel (0.848706ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el contenido inline conserva descripción, ámbito, ejemplos, orientación e icono (24.857276ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después (57.124704ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia (9.065164ms)
|
||||||
|
@repo/rdx-ui:test: ✔ datos locales inválidos no rompen el listado y solo se aceptan valores booleanos (0.516003ms)
|
||||||
|
@repo/rdx-ui:test: ✔ almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local (0.393902ms)
|
||||||
|
@repo/rdx-ui:test: ✔ buscar columnas ignora tildes y mayúsculas, conserva ñ y no modifica visibilidad (1.021207ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los grupos conservan orden, omiten vacíos y deduplican IDs sin perder columnas sin grupo (0.385002ms)
|
||||||
|
@repo/rdx-ui:test: ✔ restaurar preferencias vacías recupera defaults actuales sin tocar otras tablas (7.459052ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los contadores distinguen visibilidad, total y coincidencias en es/en (0.314202ms)
|
||||||
|
@repo/rdx-ui:test: ✔ listado remoto: recupera columnas por ámbito y protege nombre y acciones (16.431016ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los rangos vacíos y la última página nunca muestran índices fuera del conjunto (2.441518ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el listado pagina el conjunto y repite su rango conservando controles accesibles (65.227861ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la búsqueda local reduce el conjunto completo antes de paginar y permite limpiarla (12.292687ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el texto formado solo por espacios se puede borrar sin confundir vacío con ausencia de coincidencias (8.826163ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la búsqueda genérica ignora acentos y Unicode equivalente, manteniendo ñ y el texto escrito (29.184507ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial ascendente se indica en la cabecera y se aplica antes de paginar (9.389667ms)
|
||||||
|
@repo/rdx-ui:test: ✔ una columna explícita puede establecer un orden inicial descendente (10.027871ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial ignora acciones y columnas sin valor comparable (8.131458ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las tablas sin columnas ordenables conservan el orden recibido (7.082351ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial mantiene la estabilidad en empates y se indica con el listado vacío (13.236694ms)
|
||||||
|
@repo/rdx-ui:test: ✔ una búsqueda controlada conserva los resultados filtrados por la pantalla (6.829548ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga, error y búsqueda vacía se distinguen de un listado sin registros (33.999241ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el rango y la configuración también se traducen en inglés (6.692848ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: disparador de solo icono con etiqueta accesible y menú contextual (6.555947ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: operación principal visible con nombre accesible y secundarias en el menú (5.095736ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: conserva la restricción de la principal y evita menús sin secundarias (2.884521ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el estado activo se distingue en verde del inactivo (0.456903ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: siempre última, compacta y anclada a la derecha, aunque se declare primero (9.383666ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los textos comunes mantienen claves, idiomas y parámetros del rango (0.265402ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las celdas vacías muestran un guion largo en es/en y conservan cero, textos y controles (155.730602ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las columnas opcionales empiezan ocultas sin ocultar identificación ni acciones obligatorias (6.316945ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre ordena sin tildes ni mayúsculas antes de paginar, en español e inglés (10.141872ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre tiene prioridad inicial aunque otra columna comparable aparezca antes (4.16733ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre conserva empates entre acentos, mayúsculas y Unicode compuesto al ordenar descendente (4.501932ms)
|
||||||
|
@repo/rdx-ui:test: ✔ paginación por cursor: navegación accesible y límites sin inventar totales (0.724705ms)
|
||||||
|
@repo/rdx-ui:test: ✔ estados presentacionales: carga, datos anteriores y recuperación son mutuamente distinguibles (0.307703ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga inicial conserva estructura, no anuncia cero registros y no fabrica acciones (6.377345ms)
|
||||||
|
@repo/rdx-ui:test: ✔ refetch y error posterior muestran los datos disponibles sin skeletons (10.363074ms)
|
||||||
|
@repo/rdx-ui:test: ✔ error inicial, vacío válido y filtros sin coincidencias tienen contenidos distintos en es/en (11.490481ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el contrato de navegación rechaza páginas mal formadas y ordenaciones desconocidas (0.536604ms)
|
||||||
|
@repo/rdx-ui:test: ✔ navegación controlada ordena y pagina realmente sin alterar el consumidor no controlado (5.126536ms)
|
||||||
|
@repo/rdx-ui:test: ✔ quitar sección conserva todos los campos y su obligatoriedad sin mutar el origen (1.768212ms)
|
||||||
|
@repo/rdx-ui:test: ✔ copiar remapea secciones conservando campos y reglas con identidades independientes (0.454203ms)
|
||||||
|
@repo/rdx-ui:test: ✔ secciones visuales ordenadas, abiertas inicialmente y campos sin sección (10.123371ms)
|
||||||
|
@repo/rdx-ui:test: ✔ consulta compacta conserva grupos, campos sin sección y avisos sin tarjetas anidadas (3.841827ms)
|
||||||
|
@repo/rdx-ui:test: ✔ pestañas de grupos conservan controles ocultos, orden y campos sin grupo, omitiendo grupos vacíos (15.306308ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el editor ofrece asas etiquetadas para secciones y campos, deshabilitadas al guardar (89.334932ms)
|
||||||
|
@repo/rdx-ui:test: ✔ soltar antes o después del destino conserva el orden de los demás elementos (5.62464ms)
|
||||||
|
@repo/rdx-ui:test: ✔ destinos inválidos y posiciones sin cambio no producen una nueva ordenación (0.316602ms)
|
||||||
|
@repo/rdx-ui:test: ✔ reordenar mantiene identidades, secciones y obligatoriedad sin modificar los objetos (0.216101ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la comparación parcial pliega vocales, caja y representaciones Unicode equivalentes (9.986371ms)
|
||||||
|
@repo/rdx-ui:test: ✔ ñ conserva identidad compuesta y descompuesta y otras escrituras no se transliteran (0.401703ms)
|
||||||
|
@repo/rdx-ui:test: ✔ puntuación técnica, números y espacios interiores siguen siendo significativos (0.418203ms)
|
||||||
|
@repo/rdx-ui:test: ✔ vacíos y valores sin texto no serializan objetos arbitrarios (0.271802ms)
|
||||||
|
@repo/rdx-ui:test: ✔ formatea números españoles y conserva extensiones para visualización y llamada (1.712412ms)
|
||||||
|
@repo/rdx-ui:test: ✔ preserva números internacionales no españoles sin inventar agrupaciones (3.454924ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el enlace telefónico usa el número formateado y mantiene un nombre accesible (10.479774ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selector remoto: selección compacta al principio, radios etiquetados y orden recibido sin paginación local (45.15262ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selector remoto: vacío, carga y error no se confunden y errores ocultan filas obsoletas (12.074485ms)
|
||||||
|
@repo/rdx-ui:test: ✔ pestañas desplazables: conserva identidad, selección y etiquetas largas sin añadir focos al SSR (38.432372ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el selector agrupa la acción etiquetada sin convertirla en envío de formulario (23.383066ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el selector deshabilitado protege ambas acciones y admite selección sin botón (7.740854ms)
|
||||||
|
@repo/rdx-ui:test: ✔ La capacidad contextual admite presupuestos propios sin acoplarlos al viewport (1.081008ms)
|
||||||
|
@repo/rdx-ui:test: ✔ La pérdida de capacidad cierra sin abrir modal y los eventos repetidos no publican estado (0.753806ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selección visual: vacío con acciones separadas y ficha elegida sin controles de envío (13.348695ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selección visual: pendiente, deshabilitado y bloqueo protegen las acciones correspondientes (4.989135ms)
|
||||||
|
@repo/rdx-ui:test: ✔ PickerList: búsqueda etiquetada, selección única y opciones indisponibles visibles (39.113777ms)
|
||||||
|
@repo/rdx-ui:test: ✔ PickerList: estados de carga y vacío no presentan opciones falsas (21.10215ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el rango del selector usa la entidad y su plural, nunca el título de la acción (45.15522ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones multipaso: requisitos bloquean avance y guardado en cada paso (5.58954ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones multipaso: guardar pendiente impide avanzar y retroceder (1.851714ms)
|
||||||
|
@repo/rdx-ui:test: ✔ línea temporal: conserva el orden aportado, fechas semánticas, autor y contenido (13.248293ms)
|
||||||
|
@repo/rdx-ui:test: ✔ línea temporal vacía: muestra el mensaje del consumidor como estado (0.573804ms)
|
||||||
|
@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 3207.299766
|
||||||
|
@metis/resources:test: cache bypass, force executing eecbca772cc1d2a1
|
||||||
|
@metis/identity:test: cache bypass, force executing 420e86a3143935a0
|
||||||
|
@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 (5.613539ms)
|
||||||
|
@metis/resources:test: ✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.477203ms)
|
||||||
|
@metis/resources:test: ✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (39.72538ms)
|
||||||
|
@metis/identity:test: ✔ resources.global.manage exige sesión vigente, flag persistido e identidad bootstrap (7.795755ms)
|
||||||
|
@metis/identity:test: ✔ el administrador inicial no se puede crear ni actualizar inactivo (21.745954ms)
|
||||||
|
@metis/identity:test: ✔ actualizar es idempotente, conserva identidad y cambiar contraseña invalida sesiones (5.978942ms)
|
||||||
|
@metis/identity:test: ✔ crear genera UUID v7 y rehidratar conserva el UUID original (2.870121ms)
|
||||||
|
@metis/resources:test: ✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (12.458988ms)
|
||||||
|
@metis/resources:test: ✔ F2: elegibilidad de SO exige stock real y campos globales (0.341602ms)
|
||||||
|
@metis/resources:test: ✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.368002ms)
|
||||||
|
@metis/identity:test: ✔ el mapper reconstruye Value Objects y conserva la identidad y valores persistidos (8.594461ms)
|
||||||
|
@metis/identity:test: ✔ una inconsistencia persistida falla sin generar un identificador sustituto ni filtrar datos (3.964828ms)
|
||||||
|
@metis/resources:test: ✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (41.603894ms)
|
||||||
|
@metis/resources:test: ✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (14.472202ms)
|
||||||
|
@metis/resources:test: ✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (8.49726ms)
|
||||||
|
@metis/resources:test: ✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (2.019114ms)
|
||||||
|
@metis/resources:test: ✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (1.597711ms)
|
||||||
|
@metis/resources:test: ✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (1.583411ms)
|
||||||
|
@metis/resources:test: ✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (15.344708ms)
|
||||||
|
@metis/resources:test: ✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (4.551232ms)
|
||||||
|
@metis/resources:test: ✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (6.691347ms)
|
||||||
|
@metis/resources:test: ✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.518004ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.420303ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.209901ms)
|
||||||
|
@metis/resources:test: ✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (54.672986ms)
|
||||||
|
@metis/resources:test: ✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (301.30263ms)
|
||||||
|
@metis/resources:test: ✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (2.71452ms)
|
||||||
|
@metis/resources:test: ✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (7.983956ms)
|
||||||
|
@metis/resources:test: ✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (17.516424ms)
|
||||||
|
@metis/resources:test: ✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (17.467324ms)
|
||||||
|
@metis/resources:test: ✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (0.886107ms)
|
||||||
|
@metis/resources:test: ✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (17.236722ms)
|
||||||
|
@metis/resources:test: ✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (1.812913ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\NAS-01\Finanzas (2.489418ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\files.example.local\Docs (0.339402ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\192.168.5.10\C$ (0.123501ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\samba\Diseño\Año 2026 (0.554904ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\NAS\Docs\ (0.363303ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "" (0.214102ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\" (0.213302ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host" (0.106001ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\\\share" (1.606612ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\\\" (0.611404ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "C:\\Datos" (0.196401ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "https://host/share" (0.105801ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\.." (0.385203ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\." (0.179701ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.190101ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\\\child" (0.258802ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share." (0.255902ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\ share" (0.077801ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\ho st\\share" (0.0603ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\u0000" (0.103601ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\ud800" (0.115301ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.072801ms)
|
||||||
|
@metis/resources:test: ✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (0.557504ms)
|
||||||
|
@metis/resources:test: ✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (21.862955ms)
|
||||||
|
@metis/resources:test: ✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (20.522645ms)
|
||||||
|
@metis/resources:test: ✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (14.162601ms)
|
||||||
|
@metis/resources:test: ✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (4.562432ms)
|
||||||
|
@metis/resources:test: ✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.403502ms)
|
||||||
|
@metis/resources:test: ✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (38.832674ms)
|
||||||
|
@metis/resources:test: ✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (14.833305ms)
|
||||||
|
@metis/resources:test: ✔ campo PNG Icono: evoluciona tres plantillas sin reemplazar personalizaciones ni reponer bindings retirados (69.245589ms)
|
||||||
|
@metis/resources:test: ✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (31.759324ms)
|
||||||
|
@metis/resources:test: ✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (8.007156ms)
|
||||||
|
@metis/resources:test: ✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (30.580916ms)
|
||||||
|
@metis/resources:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (20.274644ms)
|
||||||
|
@metis/resources:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (39.002076ms)
|
||||||
|
@metis/resources:test: ✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (8.46226ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (20.187243ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (19.602839ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (5.74254ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (30.841918ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (7.874355ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (2.624519ms)
|
||||||
|
@metis/resources:test: ✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (30.450016ms)
|
||||||
|
@metis/resources:test: ✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (4.065029ms)
|
||||||
|
@metis/resources:test: ✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.737806ms)
|
||||||
|
@metis/resources:test: ✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (14.505603ms)
|
||||||
|
@metis/resources:test: ✔ recurso: identidad estable, historial, desactivación e idempotencia (2.470618ms)
|
||||||
|
@metis/resources:test: ✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (3.393724ms)
|
||||||
|
@metis/resources:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (16.051813ms)
|
||||||
|
@metis/resources:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (4.986835ms)
|
||||||
|
@metis/resources:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (9.112064ms)
|
||||||
|
@metis/resources:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (28.640302ms)
|
||||||
|
@metis/resources:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (25.307379ms)
|
||||||
|
@metis/resources:test: ✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (23.473765ms)
|
||||||
|
@metis/resources:test: ✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (5.139937ms)
|
||||||
|
@metis/resources:test: ✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (118.019734ms)
|
||||||
|
@metis/resources:test: ✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (4.865534ms)
|
||||||
|
@metis/identity:test: ✔ scrypt usa sales distintas y rechaza contraseñas y hashes incorrectos (2205.191982ms)
|
||||||
|
@metis/identity:test: ✔ la sesión valida la respuesta y conserva el contrato público utilizado por la aplicación (35.595351ms)
|
||||||
|
@metis/identity:test: ✔ login, configuración inicial, usuarios y contraseñas conservan rutas, body y CSRF (7.216251ms)
|
||||||
|
@metis/identity:test: ✔ cerrar sesión espera la respuesta de la operación sin perder el token (2.311416ms)
|
||||||
|
@metis/identity:test: ✔ los errores HTTP no filtran detalles internos y los datos de sesión inválidos se rechazan (9.692469ms)
|
||||||
|
@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 2535.969422
|
||||||
|
@metis/resources:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (4.462732ms)
|
||||||
|
@metis/resources:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.37921ms)
|
||||||
|
@metis/resources:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (1.904614ms)
|
||||||
|
@metis/resources:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (90.175237ms)
|
||||||
|
@metis/resources:test: ✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (106.537252ms)
|
||||||
|
@metis/resources:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (82.186679ms)
|
||||||
|
@metis/resources:test: ✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (24.310772ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (213.782908ms)
|
||||||
|
@metis/resources:test: ✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (34.304842ms)
|
||||||
|
@metis/resources:test: ✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (3.315423ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (1.377609ms)
|
||||||
|
@metis/resources:test: ✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (10.999077ms)
|
||||||
|
@metis/resources:test: ✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.41171ms)
|
||||||
|
@metis/resources:test: ✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (19.452737ms)
|
||||||
|
@metis/resources:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (64.294553ms)
|
||||||
|
@metis/resources:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (2.248916ms)
|
||||||
|
@metis/resources:test: ✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (28.149099ms)
|
||||||
|
@metis/resources:test: ✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (14.629903ms)
|
||||||
|
@metis/resources:test: ✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (0.956207ms)
|
||||||
|
@metis/resources:test: ✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.158408ms)
|
||||||
|
@metis/resources:test: ✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.334003ms)
|
||||||
|
@metis/resources:test: ✔ la integración de buzón conserva su configuración explícita y no inventa referencias (1.264309ms)
|
||||||
|
@metis/resources:test: ✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (1.066008ms)
|
||||||
|
@metis/resources:test: ✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (31.647324ms)
|
||||||
|
@metis/resources:test: ✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (0.433603ms)
|
||||||
|
@metis/resources:test: ✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (4.652333ms)
|
||||||
|
@metis/resources:test: ✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (4.551232ms)
|
||||||
|
@metis/resources:test: ✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (100.487508ms)
|
||||||
|
@metis/resources:test: ✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (326.281401ms)
|
||||||
|
@metis/resources:test: ✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (15.018306ms)
|
||||||
|
@metis/resources:test: ✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (18.241428ms)
|
||||||
|
@metis/resources:test: ✔ asistencia: alta de recurso con plantilla específica y características opcionales (7.586953ms)
|
||||||
|
@metis/resources:test: ✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (16.068113ms)
|
||||||
|
@metis/resources:test: ✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (8.051457ms)
|
||||||
|
@metis/resources:test: ✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (15.409708ms)
|
||||||
|
@metis/resources:test: ✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (11.871583ms)
|
||||||
|
@metis/resources:test: ✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (15.327108ms)
|
||||||
|
@metis/resources:test: ✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (10.916177ms)
|
||||||
|
@metis/resources:test: ✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (7.868855ms)
|
||||||
|
@metis/resources:test: ✔ UNC: presentación por segmentos conserva exactamente separadores, Unicode y valores repetidos (4.800234ms)
|
||||||
|
@metis/resources:test: ✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (41.06259ms)
|
||||||
|
@metis/resources:test: ℹ tests 126
|
||||||
|
@metis/resources:test: ℹ suites 0
|
||||||
|
@metis/resources:test: ℹ pass 126
|
||||||
|
@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 5139.287091
|
||||||
|
@metis/assets:test: cache bypass, force executing 86c4d0036baaca5b
|
||||||
|
@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.002661ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo inicial es idempotente, con UUID propios y características opcionales (67.169313ms)
|
||||||
|
@metis/assets:test: ✔ la disponibilidad por compañía conserva el global y los campos desactivados (51.12489ms)
|
||||||
|
@metis/assets:test: ✔ las plantillas validan ámbito, campos activos e identidad estable al editar (14.512411ms)
|
||||||
|
@metis/assets:test: ✔ los campos validan listas, igualdad semántica, duplicados y tipo fijo (0.849306ms)
|
||||||
|
@metis/assets:test: ✔ solo elimina definiciones globales personalizadas que no usan otras plantillas (1.594312ms)
|
||||||
|
@metis/assets:test: ✔ iconos opcionales: cambio y retirada conservan identidad y asociaciones (26.877305ms)
|
||||||
|
@metis/assets:test: ✔ el arranque conserva un icono de serie retirado sin cambiar UUID, campos o disponibilidad (39.984005ms)
|
||||||
|
@metis/assets:test: ✔ los iconos desconocidos o asociados a campos se rechazan (3.710729ms)
|
||||||
|
@metis/assets:test: ✔ revisión explícita conserva identidades, copias y otras plantillas (40.239807ms)
|
||||||
|
@metis/assets:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (39.765303ms)
|
||||||
|
@metis/assets:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (15.621219ms)
|
||||||
|
@metis/assets:test: ✔ Las relaciones validan sus extremos, normalizan nombres y distinguen dirección (6.353948ms)
|
||||||
|
@metis/assets:test: ✔ Retirar conserva UUID, extremos y auditoría; repetir la retirada no modifica el historial (2.048216ms)
|
||||||
|
@metis/assets:test: ✔ Tipos de relación de serie protegidos, ámbito de compañía y dirección fija (51.688295ms)
|
||||||
|
@metis/assets:test: ✔ Asset: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (49.171475ms)
|
||||||
|
@metis/assets:test: ✔ Asset: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (2.330818ms)
|
||||||
|
@metis/assets:test: ✔ Asset: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (1.077908ms)
|
||||||
|
@metis/assets:test: ✔ las secciones conservadas desambiguan el nombre respetando el límite de 100 caracteres (0.715805ms)
|
||||||
|
@metis/assets:test: ✔ Características opcionales, tipos y límites; cero y falso son valores presentes (10.766982ms)
|
||||||
|
@metis/assets:test: ✔ Activo conserva UUID y revisiones; los cambios iguales son idempotentes (1.228409ms)
|
||||||
|
@metis/assets:test: ✔ texto largo conserva saltos de línea y rechaza tipos no textuales (0.580004ms)
|
||||||
|
@metis/assets:test: ✔ estado técnico y uso: asignaciones calculadas, elegibilidad exclusiva/compartida e historial conservado (0.814307ms)
|
||||||
|
@metis/assets:test: ✔ Fecha de alta opcional: calendario real, independencia de compra, revisiones e idempotencia (2.933523ms)
|
||||||
|
@metis/assets:test: ✔ sedes archivadas: nuevas referencias rechazadas y referencias anteriores conservadas (3.745429ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: nombre normalizado, UUID v7, protección de serie, estado e idempotencia (8.680066ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: SVG estáticos con transparencia, límites y contenido activo rechazado (30.101729ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: los 23 fabricantes tienen SVG local validado; Clónico no representa una marca (11.416287ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: añadir, sustituir y quitar SVG conserva identidad, origen y actualización idempotente (2.962022ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: mapper bidireccional conserva identidad y rechaza persistencia inválida (4.020431ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: unicidad, edición y eliminación de personalizadas, con protección de serie (12.729197ms)
|
||||||
|
@metis/assets:test: ✔ pertenencia explícita: un rol, identidad UUID v7 e idempotencia sin nuevas revisiones (3.804829ms)
|
||||||
|
@metis/assets:test: ✔ promoción y retirada del rol conservan asociación e historial; finalización repetida es inocua (2.986822ms)
|
||||||
|
@metis/assets:test: ✔ cambio de dominio crea sucesor único y cierre atómico planificado; admite reintentos y rechaza conflictos (3.285525ms)
|
||||||
|
@metis/assets:test: ✔ capacidad, estado y compañía: rechaza extremos incompatibles sin inferencias de nombre (0.457403ms)
|
||||||
|
@metis/assets:test: ✔ activos o dominios inactivos conservan vínculos y permiten cerrar o retirar el rol (3.085423ms)
|
||||||
|
@metis/assets:test: ✔ mapper: rehidratación consistente, UUID conservados y rechazo de revisiones corruptas (2.145716ms)
|
||||||
|
@metis/assets:test: ✔ contratos estrictos: roles, paginación, UUID, acciones y privilegios arbitrarios (9.368472ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: interfaces independientes, varias IP, normalización y UUID v7 (8.167262ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: mismo contenido no cambia identidades ni añade revisiones (5.20574ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: rechaza nombres/IP duplicados, valores inválidos e identidades ajenas (4.758636ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: límites y direcciones repetidas entre interfaces o compañías permitidas (7.962561ms)
|
||||||
|
@metis/assets:test: ✔ publicación: alta, periodo, cierre idempotente e identidad persistida (11.049284ms)
|
||||||
|
@metis/assets:test: ✔ publicación: cardinalidad y reintento idéntico no crean periodos (0.693706ms)
|
||||||
|
@metis/assets:test: ✔ publicación: traslado explícito y replay estable incluso tras finalizar sucesor (1.183409ms)
|
||||||
|
@metis/assets:test: ✔ publicación: estados, recurso no SMB, identidad y fechas inválidas (1.125308ms)
|
||||||
|
@metis/assets:test: ✔ publicación: definición compatible con contratos comunes sin rol ni proveedor network (6.583251ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: UUID v7, historial, cambios semánticos y fechas (4.755736ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: preferido disponible y entrada estricta (1.32861ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: varios métodos, duplicados, preferido único y activo retirado (2.255318ms)
|
||||||
|
@metis/assets:test: ✔ herramientas: protección de serie y desactivación sin perder referencias (0.701405ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: mapper bidireccional y rechazo de historial inconsistente (10.077877ms)
|
||||||
|
@metis/assets:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (6.233647ms)
|
||||||
|
@metis/assets:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (5.30674ms)
|
||||||
|
@metis/assets:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (6.121847ms)
|
||||||
|
@metis/assets:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (60.35106ms)
|
||||||
|
@metis/assets:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (27.326808ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: composición opcional, referencias válidas y límites sin duplicados (9.422172ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: UUID estable e historial de composición, persona y estado; guardado idéntico idempotente (8.228263ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: ordenador principal exclusivo, único e independiente del responsable (1.25431ms)
|
||||||
|
@metis/assets:test: ✔ baja del puesto: libera composición y responsable, conserva historial e identidad y es idempotente (1.427311ms)
|
||||||
|
@metis/assets:test: ✔ definición real: proveedor, cardinalidad vigente, roles e inmutabilidad (5.803745ms)
|
||||||
|
@metis/assets:test: ✔ cursor firmado: vinculación a consulta y operador, sin aceptar UUID libres ni alteraciones (4.807337ms)
|
||||||
|
@metis/assets:test: ✔ extensión de Assets validada: roles y estados cerrados, UUID original y ámbito (4.855737ms)
|
||||||
|
@metis/assets:test: ✔ ficha de activo es/en: página con dos pestañas, información actual, relaciones y contenido de compañía (168.517881ms)
|
||||||
|
@metis/assets:test: ✔ historial del activo conserva instantáneas antiguas, interfaces retiradas y conexiones en ambos sentidos, ordenadas por fecha (28.004513ms)
|
||||||
|
@metis/assets:test: ✔ ficha de activo distingue carga, error y activo inexistente (10.48098ms)
|
||||||
|
@metis/assets:test: ✔ acciones: crear solo para portátiles, sobremesa y servidores disponibles (4.178831ms)
|
||||||
|
@metis/assets:test: ✔ acciones: abrir el puesto asociado aunque el activo sea periférico, no operativo o el puesto esté inactivo (1.977015ms)
|
||||||
|
@metis/assets:test: ✔ acciones: compañía y plantilla correctas; asociaciones solo históricas no bloquean el alta (0.656705ms)
|
||||||
|
@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 (35.655471ms)
|
||||||
|
@metis/assets:test: ✔ inventario es/en: crear y ver puesto son acciones visibles fuera del menú (237.044303ms)
|
||||||
|
@metis/assets:test: ✔ la acción de selección conserva su texto visible como nombre accesible en es/en (37.739787ms)
|
||||||
|
@metis/assets:test: ✔ selector: comparte búsqueda, pestañas por identidad y columnas del inventario en es/en (194.068778ms)
|
||||||
|
@metis/assets:test: ✔ selector: filtra conjuntamente por plantilla, nombre, sede y estado; distingue vacío (127.220468ms)
|
||||||
|
@metis/assets:test: ✔ selector único: controles radio y disparador de diálogo; conserva el alta rápida y el bloqueo (36.039074ms)
|
||||||
|
@metis/assets:test: ✔ picker: búsqueda rápida disponible durante carga y error, sin anunciar un falso vacío (73.55016ms)
|
||||||
|
@metis/assets:test: ✔ inventario es/en: icono compacto antes del nombre, plantilla solo textual y ordenación por nombre (51.113489ms)
|
||||||
|
@metis/assets:test: ✔ columnas de características: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (31.417139ms)
|
||||||
|
@metis/assets:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (205.640566ms)
|
||||||
|
@metis/assets:test: ✔ columnas generales y de red es/en: fechas y autores propios, IP/MAC por interfaz y ausencia real (65.6848ms)
|
||||||
|
@metis/assets:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (5.363441ms)
|
||||||
|
@metis/assets:test: ✔ La capacidad del inventario protege el presupuesto de tabla en el límite real (2.051716ms)
|
||||||
|
@metis/assets:test: ✔ El resize conserva el contexto abierto al ganar capacidad y nunca abre un modal (1.34351ms)
|
||||||
|
@metis/assets:test: ✔ Inventario traducido con alta de cabecera y sin formulario junto al listado (173.983123ms)
|
||||||
|
@metis/assets:test: ✔ Las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (263.986295ms)
|
||||||
|
@metis/assets:test: ✔ El activo ofrece altas relacionadas, ayuda inferior y no encadena una tercera alta (29.178304ms)
|
||||||
|
@metis/assets:test: ✔ Las fichas leen ambos sentidos y conservan la auditoría de relaciones retiradas (19.080733ms)
|
||||||
|
@metis/assets:test: ✔ El formulario de tipo de relación presenta ayuda justo antes de sus operaciones (12.92439ms)
|
||||||
|
@metis/assets:test: ✔ Puestos de trabajo: listado traducido, alta en cabecera y formulario modal (18.320229ms)
|
||||||
|
@metis/assets:test: ✔ Puestos de trabajo: altas rápidas y ayuda inferior; un activo anidado no ofrece terceras altas (18.658931ms)
|
||||||
|
@metis/assets:test: ✔ alta de puesto: sede única automática, responsable opcional y cuatro pasos traducidos (17.599823ms)
|
||||||
|
@metis/assets:test: ✔ la ficha lateral agrupa información y ofrece acciones superiores y enlaces reales (76.189233ms)
|
||||||
|
@metis/assets:test: ✔ texto largo: área editable con ayuda accesible (9.717768ms)
|
||||||
|
@metis/assets:test: ✔ las características marcan solo los campos obligatorios y conservan unidad y ayuda (11.128278ms)
|
||||||
|
@metis/assets:test: ✔ el resumen usa los campos propios de la plantilla, omite vacíos y texto largo y limita a cuatro (9.100264ms)
|
||||||
|
@metis/assets:test: ✔ alta de activo por pasos: empieza por plantilla, traduce la navegación y bloquea el avance sin selección (17.289221ms)
|
||||||
|
@metis/assets:test: ✔ el progreso de activos conserva la plantilla y explica los pasos en es/en (24.915174ms)
|
||||||
|
@metis/assets:test: ✔ las acciones por pasos solo ofrecen guardar al final y bloquean acciones durante el envío (25.72638ms)
|
||||||
|
@metis/assets:test: ✔ características obligatorias: rechaza ausentes y espacios y conserva cero y falso (0.263202ms)
|
||||||
|
@metis/assets:test: ✔ nombre propuesto: usa texto de la plantilla, omite marca y vacíos y respeta el límite (4.484831ms)
|
||||||
|
@metis/assets:test: ✔ diseño del puesto: grupos compactos, columnas por contenedor y resumen sin textos de ausencia (49.489146ms)
|
||||||
|
@metis/assets:test: ✔ activos del puesto: estado vacío visual traducido y acciones según disponibilidad (26.288284ms)
|
||||||
|
@metis/assets:test: ✔ diseño del puesto: navegación traducida y acciones bloqueadas durante el guardado (16.743417ms)
|
||||||
|
@metis/assets:test: ✔ ficha compacta de activo: comparte información, traducciones y acciones sin consultas propias (61.946034ms)
|
||||||
|
@metis/assets:test: ✔ ordenador principal: tarjetas con selección única, etiquetas es/en y solo los activos del puesto (41.231888ms)
|
||||||
|
@metis/assets:test: ✔ puestos: URL controla consulta, orden y página y sus enlaces conservan el contexto (17.444722ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: listado común traducido, filtros y alta modal sin formulario junto a la tabla (153.856271ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: nombre de serie protegido y ayuda inferior; alta rápida siempre activa (11.370486ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propaga conflictos (29.633425ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: selector mantiene valores antiguos e inactivos y respeta el límite de alta anidada (16.862928ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: el campo de serie se identifica por referencia de catálogo, no por el nombre (43.887534ms)
|
||||||
|
@metis/assets:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (4.386134ms)
|
||||||
|
@metis/assets:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.355711ms)
|
||||||
|
@metis/assets:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (2.702721ms)
|
||||||
|
@metis/assets:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (208.220186ms)
|
||||||
|
@metis/assets:test: ✔ L04.2: campos del catálogo recuperan orden y página desde su URL (257.320458ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (263.895708ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo busca nombres sin acentos sin cambiar la consulta ni incluir campos de otras pestañas (69.60983ms)
|
||||||
|
@metis/assets:test: ✔ los formularios tienen ayuda inferior y reservan el alta rápida al diálogo de selección de campos (54.774517ms)
|
||||||
|
@metis/assets:test: ✔ las plantillas ofrecen selección visual traducida y conservan el icono al copiar (343.10661ms)
|
||||||
|
@metis/assets:test: ✔ dominio: roles estructurados, resumen solo de página y enlaces UUID, es/en (62.934679ms)
|
||||||
|
@metis/assets:test: ✔ tablas: miembro y finalización no se confunden con controlador o pertenencia vigente (7.080154ms)
|
||||||
|
@metis/assets:test: ✔ activo sin dominio: vacío explícito y asociación solo si el equipo es compatible (15.147815ms)
|
||||||
|
@metis/assets:test: ✔ activo unido: dominio, rol real, navegación y acciones especializadas (17.277231ms)
|
||||||
|
@metis/assets:test: ✔ carga y error de consulta no se muestran como dominio vacío (21.10926ms)
|
||||||
|
@metis/assets:test: ✔ API de lectura: conserva cursor opaco, tipo y ámbito; propaga errores funcionales (36.412777ms)
|
||||||
|
@metis/assets:test: ✔ traslado usa un único comando atómico, UUID esperado y CSRF; conflicto no se oculta (6.634051ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: clasificación de serie independiente del nombre/icono y selección manual prioritaria (2.262917ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos (0.469703ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos (149.114135ms)
|
||||||
|
@metis/assets:test: ✔ SMB: relaciones paginadas, UNC Unicode y enlaces bidireccionales es/en (48.851771ms)
|
||||||
|
@metis/assets:test: ✔ SMB: historial conserva periodos y actores y no ofrece modificar periodos finalizados (15.77632ms)
|
||||||
|
@metis/assets:test: ✔ SMB: vacío real y error de lectura son distintos (14.282809ms)
|
||||||
|
@metis/assets:test: ✔ SMB: denegación oculta datos previos y carga inicial conserva cabeceras (20.125353ms)
|
||||||
|
@metis/assets:test: ✔ SMB: transferencia conserva UUID y envía un único comando propietario con CSRF (33.772157ms)
|
||||||
|
@metis/assets:test: ✔ SMB: búsqueda remota y cursor opaco viajan al servidor (1.743513ms)
|
||||||
|
@metis/assets:test: ✔ ficha SMB: publicador destacado, datos y timeline real es/en; solo lectura conserva navegación (46.998757ms)
|
||||||
|
@metis/assets:test: ✔ ficha SMB: vacío, finalizada, carga y denegación son estados distintos (32.88595ms)
|
||||||
|
@metis/assets:test: ✔ timeline SMB: solo hechos persistidos, UUID estable, traslado y cierre sin sucesor en página (1.608512ms)
|
||||||
|
@metis/assets:test: ✔ historial v2: utiliza únicamente nombres autorizados aportados y conserva UUID técnicos (17.517333ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: formulario con alta rápida y ayuda antes de guardar (32.597628ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: resumen excluye retirados, ordena preferido y conserva la gestión original (13.336693ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: traducciones y resumen de método (1.553311ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: resumen documental muestra destino, VPN y preferencia sin acciones de gestión (1.955114ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: referencia no resuelta conserva el registro con ausencia y explicación (1.049908ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: usa valores vigentes, normaliza el nombre, valida respuesta y envía CSRF (31.449539ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: rechaza vacíos, espacios y exceso de longitud antes de realizar peticiones (1.944815ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: un nombre equivalente no provoca una escritura ni otra revisión (1.117308ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: propaga puesto ausente y errores del servidor sin escritura posterior (3.073523ms)
|
||||||
|
@metis/assets:test: ✔ WD04.4: comparte reglas de disponibilidad, retención, ocupación y puesto inactivo (1.27861ms)
|
||||||
|
@metis/assets:test: ✔ alta: sede necesaria, responsable opcional, composición mínima y ordenador perteneciente al puesto (9.300671ms)
|
||||||
|
@metis/assets:test: ✔ composición: asigna el activo único, conserva una elección válida y limpia el ordenador retirado (0.212302ms)
|
||||||
|
@metis/assets:test: ✔ propone exactamente el nombre del activo principal, sin depender de la sede (0.877607ms)
|
||||||
|
@metis/assets:test: ✔ no propone nombre cuando falta el activo principal (2.424918ms)
|
||||||
|
@metis/assets:test: ℹ tests 155
|
||||||
|
@metis/assets:test: ℹ suites 0
|
||||||
|
@metis/assets:test: ℹ pass 155
|
||||||
|
@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 6584.535776
|
||||||
|
@metis/companies:test: cache bypass, force executing dceb3932b3e86a9a
|
||||||
|
@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.316944ms)
|
||||||
|
@metis/companies:test: ✔ clasificación independiente del nombre; no se adopta un sistema existente por llamarse AD (2.966321ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan compañías distintas, referencias inexistentes y entornos sin clasificación (1.266209ms)
|
||||||
|
@metis/companies:test: ✔ DNS repetido no crea dos entornos; otro dominio comparte sistema y obtiene un entorno distinto (2.142315ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: vínculo por UUID, alta y retirada idempotentes con identidad e historial protegidos (11.648882ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: clasificación explícita rechaza AD por sistema o entorno, sin comparar etiquetas (1.019607ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: crear entorno utiliza AccessWorkspace y conserva cuentas y sistema existentes (2.014614ms)
|
||||||
|
@metis/companies:test: ✔ catálogos globales no se duplican con adiciones locales y no se admiten grupos anidados (8.809062ms)
|
||||||
|
@metis/companies:test: ✔ renombrar una cuenta conserva UUID, historial y semántica de repetición; su uso permanece fijo (5.043036ms)
|
||||||
|
@metis/companies:test: ✔ cerrar una autorización configurada deja retirada pendiente y cerrar una pendiente no inventa un acceso (4.879734ms)
|
||||||
|
@metis/companies:test: ✔ titulares no se solapan incluso en periodos cerrados; el relevo conserva una retirada anterior (7.464252ms)
|
||||||
|
@metis/companies:test: ✔ responsabilidad técnica no concede acceso y se permite sobre una cuenta caducada o inactiva (2.488917ms)
|
||||||
|
@metis/companies:test: ✔ valores reconstruyen UUID y fechas, rechazan fechas imposibles y no permiten credenciales (23.062761ms)
|
||||||
|
@metis/companies:test: ✔ las referencias UUID se normalizan y no permiten duplicar cuentas variando mayúsculas (0.892507ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: contrato estricto, versión opcional y solo instalada (12.461388ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: UUID, idempotencia, versión auditada, destino fijo y retirada (4.858534ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (2.458518ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: inactividad conserva notas y permite retirada, impide reconfigurar (2.086314ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: persistencia preserva historial y rechaza claves y orden incoherentes (10.783576ms)
|
||||||
|
@metis/companies:test: ✔ R2.5: global conserva perfil, delivery e historial; disponibilidad distinta de lectura histórica (2.538718ms)
|
||||||
|
@metis/companies:test: ✔ certificados: contrato estricto, ubicación opcional y límites (15.106007ms)
|
||||||
|
@metis/companies:test: ✔ certificados: UUID, idempotencia, ubicación auditada, destino fijo y retirada (4.099828ms)
|
||||||
|
@metis/companies:test: ✔ certificados: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (2.506018ms)
|
||||||
|
@metis/companies:test: ✔ certificados: inactividad conserva notas y permite retirada, impide reconfigurar (1.769513ms)
|
||||||
|
@metis/companies:test: ✔ certificados: persistencia preserva historial y rechaza claves y orden incoherentes (8.274558ms)
|
||||||
|
@metis/companies:test: ✔ certificados: caducidad inclusiva, revisión y retirada sin perder historial (1.552511ms)
|
||||||
|
@metis/companies:test: ✔ compañía, contacto y sede tienen UUID v7 propios y se conserva la identidad al editar (5.565739ms)
|
||||||
|
@metis/companies:test: ✔ la pertenencia impide asociar o editar entidades de otra compañía (1.087008ms)
|
||||||
|
@metis/companies:test: ✔ la dirección y el teléfono son optativos y se pueden borrar (0.984707ms)
|
||||||
|
@metis/companies:test: ✔ nombres, email, teléfono y longitud de dirección se validan en los Value Objects (1.45581ms)
|
||||||
|
@metis/companies:test: ✔ sedes: archivar y reactivar conserva identidad y datos; repetir el estado es idempotente (1.008607ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: cuenta desconocida, cuenta alternativa y cobertura independiente de la necesidad (27.264191ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: ausencia conserva cuenta, null la retira y cada cambio real crea revisión (4.324331ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: rechaza cuenta ausente, otro subtipo, otra persona, caducada o desactivada (3.714126ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: Coverage por grupo exige pertenencia directa de la cuenta y configuración vigente (10.375472ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: cobertura derivada cambia al retirar concesión, necesidad, cuenta o asociación personal (26.463585ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: todas las autorizaciones necesarias deben proceder de la cuenta seleccionada (3.464925ms)
|
||||||
|
@metis/companies:test: ✔ Correo: UUIDv7, normalización, no-op, historial y retirada explícita (5.69064ms)
|
||||||
|
@metis/companies:test: ✔ Correo: buzón independiente, vigencia, perfiles, varios buzones y duplicados abiertos (11.498081ms)
|
||||||
|
@metis/companies:test: ✔ Correo: persistencia conserva identidad e historial y rechaza corrupción (4.902435ms)
|
||||||
|
@metis/companies:test: ✔ Correo: vinculación explícita del legado conserva UUID e historial, sin inferir propiedad (8.628261ms)
|
||||||
|
@metis/companies:test: ✔ Buzón: identidad, dirección única, cuenta opcional, ámbito y cambios auditados (4.30253ms)
|
||||||
|
@metis/companies:test: ✔ Uso de buzón: fechas inclusiva/exclusiva, mismo día, solapes, varias personas e historial (5.243037ms)
|
||||||
|
@metis/companies:test: ✔ Buzones: mapper conserva identidad y revisiones, rechaza corrupción (7.748454ms)
|
||||||
|
@metis/companies:test: ✔ personas con datos opcionales no requieren cuenta de acceso y conservan su UUID (3.634826ms)
|
||||||
|
@metis/companies:test: ✔ departamentos únicos por compañía, ignorando mayúsculas y espacios exteriores (1.500311ms)
|
||||||
|
@metis/companies:test: ✔ varios departamentos, un único principal y sin periodos solapados (3.058422ms)
|
||||||
|
@metis/companies:test: ✔ ediciones conservan revisiones y una repetición semántica no añade historial (3.894828ms)
|
||||||
|
@metis/companies:test: ✔ inactivos no reciben nuevas asignaciones; se conservan y pueden cerrarse las existentes (1.253109ms)
|
||||||
|
@metis/companies:test: ✔ asignaciones rechazan pertenencias cruzadas y fechas inválidas (1.40911ms)
|
||||||
|
@metis/companies:test: ✔ la persistencia rechaza periodos inválidos y revisiones que no reflejan el estado actual (1.653612ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: contratos estrictos, notas opcionales y predeterminada solo vigente (6.106043ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: UUID, historial, igualdad semántica, destino fijo y retirada terminal (5.412138ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: duplicados por perfil, predeterminada única y destinos del ámbito (2.069815ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: indisponibilidad señala revisión y permite notas y retirada (1.681612ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: rehidratación conserva identidades y detecta corrupción (12.113385ms)
|
||||||
|
@metis/companies:test: ✔ procesos: contratos estrictos, normalización y límites (9.968171ms)
|
||||||
|
@metis/companies:test: ✔ procesos: UUID v7, tareas con identidad, actualización idempotente e historial (3.132022ms)
|
||||||
|
@metis/companies:test: ✔ procesos: completar exige todas resueltas; cierre terminal y sujeto/tareas fijos (2.994921ms)
|
||||||
|
@metis/companies:test: ✔ procesos: añadir revisiones conserva las resueltas e identidades y no duplica (2.939221ms)
|
||||||
|
@metis/companies:test: ✔ plantillas: copia de instrucciones, igualdad semántica y cambios independientes (3.874627ms)
|
||||||
|
@metis/companies:test: ✔ procesos: rehidratación bidireccional y rechazo de raíces/historial alterados (12.404087ms)
|
||||||
|
@metis/companies:test: ✔ revisión: personales, retirada pendiente y relaciones finalizadas aún configuradas (0.622205ms)
|
||||||
|
@metis/companies:test: ✔ revisión: incluye configuraciones de perfiles archivados, excluye cerradas y evita duplicar (0.324303ms)
|
||||||
|
@metis/companies:test: ✔ remotos: origen personal sin detalle, destinos tipados y contrato estricto (8.768462ms)
|
||||||
|
@metis/companies:test: ✔ remotos: UUID e historial, destino fijo, idempotencia y cierre terminal (4.050929ms)
|
||||||
|
@metis/companies:test: ✔ remotos: referencias de compañía y duplicados abiertos por recorrido y herramienta (2.91722ms)
|
||||||
|
@metis/companies:test: ✔ remotos: configuración y autorización independientes; caducidad y revocación no borran acceso (3.194822ms)
|
||||||
|
@metis/companies:test: ✔ remotos: disponibilidad de origen y destino, retirada y método personalizado (2.88302ms)
|
||||||
|
@metis/companies:test: ✔ remotos: persistencia conserva auditoría y detecta referencias, claves e historial corruptos (7.385552ms)
|
||||||
|
@metis/companies:test: ✔ remotos: una sede archivada conserva conexiones existentes pero rechaza nuevas (1.256309ms)
|
||||||
|
@metis/companies:test: ✔ instalaciones: pertenecen al equipo, admiten SO/aplicaciones y rechazan recursos ajenos y destinos variables (9.373566ms)
|
||||||
|
@metis/companies:test: ✔ instalaciones: versiones, idempotencia, retirada, revisión y persistencia de historial (10.346973ms)
|
||||||
|
@metis/companies:test: ✔ licencia del software: opcional, misma compañía y tipo, vigencia, desvinculación e historial idempotente (12.72739ms)
|
||||||
|
@metis/companies:test: ✔ R2.4: global activo e inactivo separan alta, reconfiguración y recuperación histórica (2.742719ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: ausencia, límites, fechas reales y contrato estricto (5.256137ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: UUID, renovación y revocación auditadas, igualdad y destino fijo (4.24363ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: vigencia inclusiva, programación, caducidad y persona inactiva (4.183629ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: ámbito, unicidad incluso revocada, disponibilidad y retirada (1.46941ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: rehidratación preserva identidad y detecta corrupción de raíz e historial (15.839911ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: cuenta opcional, UUID, historial, identidad fija e idempotencia (8.569961ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: desactivar conserva valores e historial, es idempotente y precede al borrado (3.882928ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: misma persona/dispositivo único vigente, múltiples dispositivos/personas y revisión sin pérdida de datos (1.964114ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: cuenta autorizada, vigencia inclusiva de caducidad, cierre de autorización y cuenta técnica (1.759112ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: persistencia conserva revisiones y rechaza raíz o cuenta histórica incoherentes (9.577167ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: solo un ordenador principal activo admite altas y reactivaciones; el historial se conserva (1.34781ms)
|
||||||
|
@metis/companies:test: ✔ PNG, JPEG y WebP se normalizan sin recorte y sin metadatos a un avatar ligero (121.480154ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan archivos vacíos, corruptos, SVG y GIF aunque simulen un MIME válido (5.095936ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan excesos de peso y dimensión antes de almacenar (4.525432ms)
|
||||||
|
@metis/companies:test: ✔ los PNG animados se rechazan antes de decodificarlos (3.482825ms)
|
||||||
|
@metis/companies:test: ✔ el contexto distingue cuentas y grupos del entorno, sin incluir otros entornos o datos globales (4.24543ms)
|
||||||
|
@metis/companies:test: ✔ recurso contextual: incluye identidades de sistemas distintos sin mezclar necesidades y concesiones ajenas (14.283601ms)
|
||||||
|
@metis/companies:test: ✔ filtro vacío no incluye elementos ajenos y las rutas validan UUID y contexto de compañía (4.537132ms)
|
||||||
|
@metis/companies:test: ✔ los catálogos presentan alta mediante acción y no un formulario junto al listado (128.685003ms)
|
||||||
|
@metis/companies:test: ✔ cuentas ofrecen caducidad opcional y campos sin contraseña (50.624955ms)
|
||||||
|
@metis/companies:test: ✔ la ayuda explica todos los elementos en ambos idiomas y distingue el ámbito (86.806809ms)
|
||||||
|
@metis/companies:test: ✔ los ejemplos del sistema orientan sin rellenar datos del alta (7.138851ms)
|
||||||
|
@metis/companies:test: ✔ las altas rápidas respetan el contexto, ámbito y relaciones históricas (1.543211ms)
|
||||||
|
@metis/companies:test: ✔ los formularios ofrecen altas relacionadas y el segundo modal evita altas anidadas (166.859271ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: listado sin formularios, alta en cabecera y traducciones (125.388881ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: selectores con alta rápida y ayuda inferior antes de guardar (89.342327ms)
|
||||||
|
@metis/companies:test: ✔ R2.5: perfil muestra nombre, origen y versión global sin ResourceDTO local ni mezcla de perfiles (68.547381ms)
|
||||||
|
@metis/companies:test: ✔ certificados: listado sin formularios, alta en cabecera y traducciones (116.91052ms)
|
||||||
|
@metis/companies:test: ✔ certificados: selectores con alta rápida y ayuda inferior antes de guardar (38.49777ms)
|
||||||
|
@metis/companies:test: ✔ la compañía de origen es ámbito visible, no un filtro eliminable con Limpiar filtros (211.830186ms)
|
||||||
|
@metis/companies:test: ✔ directorio: orden y página proceden de URL sin perder la consulta (88.54252ms)
|
||||||
|
@metis/companies:test: ✔ las subentradas de Personas conservan contexto y reemplazan el filtro de la vista (33.231232ms)
|
||||||
|
@metis/companies:test: ✔ el dashboard no ofrece selector de compañía y conserva las métricas de cada ámbito (75.923632ms)
|
||||||
|
@metis/companies:test: ✔ el resumen de compañía es la entrada por defecto y ofrece destinos reales (106.748948ms)
|
||||||
|
@metis/companies:test: ✔ los índices de catálogos distinguen definiciones globales y variantes de compañía (12.268186ms)
|
||||||
|
@metis/companies:test: ✔ el listado presenta estado vacío y acceso al alta sin formularios visibles (76.98414ms)
|
||||||
|
@metis/companies:test: ✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (95.367068ms)
|
||||||
|
@metis/companies:test: ✔ el formulario de sede ofrece dirección y teléfono opcionales (13.513295ms)
|
||||||
|
@metis/companies:test: ✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (19.146134ms)
|
||||||
|
@metis/companies:test: ✔ asignar un departamento permite añadir el primero y conserva el selector en edición (29.248005ms)
|
||||||
|
@metis/companies:test: ✔ el tablero de compañía tiene composición propia, acciones arriba y métricas limitadas a su ámbito (21.568252ms)
|
||||||
|
@metis/companies:test: ✔ el menú de compañía ordena trabajo y personas por grupos sin repetir las acciones de usuario (37.07746ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: ready (62.566638ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: pending (56.617605ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: error (119.402496ms)
|
||||||
|
@metis/companies:test: ✔ sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto (108.480513ms)
|
||||||
|
@metis/companies:test: ✔ la navegación de compañía conserva 21 enlaces reales, sección activa y contexto es/en (137.828533ms)
|
||||||
|
@metis/companies:test: ✔ el logo sustituye iniciales con URL versionada, dimensiones fijas y carga diferida (15.762511ms)
|
||||||
|
@metis/companies:test: ✔ sin logotipo aparecen iniciales y las listas consultan la misma caché de compañías (6.160443ms)
|
||||||
|
@metis/companies:test: ✔ cuentas del mapeo: exige asociación vigente y catálogos activos; no atribuye por login o por perfil (9.239665ms)
|
||||||
|
@metis/companies:test: ✔ actualización del mapeo: omitir cuenta conserva semántica distinta de retirarla explícitamente; admite revisión capturada (2.495018ms)
|
||||||
|
@metis/companies:test: ✔ uso de impresora: agrupa perfiles de un mismo equipo y separa pendientes e historial (18.638331ms)
|
||||||
|
@metis/companies:test: ✔ uso de impresora: referencias sin resolver no se presentan como ausencia de uso (0.549404ms)
|
||||||
|
@metis/companies:test: ✔ identidades locales: entorno del activo, navegación por UUID y estado vigente en es/en (52.481068ms)
|
||||||
|
@metis/companies:test: ✔ propietario local: vínculo retirado conserva navegación y no ofrece reasignación (2.623618ms)
|
||||||
|
@metis/companies:test: ✔ cuenta del mapeo: seleccionar perfil es una acción explícita; el render no infiere ni cambia cuenta (23.317064ms)
|
||||||
|
@metis/companies:test: ✔ correo: listado traducido, alta en cabecera y formularios ocultos (134.131341ms)
|
||||||
|
@metis/companies:test: ✔ correo: selectores reutilizables con altas y ayuda inmediatamente antes de guardar (28.990703ms)
|
||||||
|
@metis/companies:test: ✔ Buzones: alta en modal, cuenta opcional y ayuda al final (37.864766ms)
|
||||||
|
@metis/companies:test: ✔ L04.2: correo mantiene tabla y criterios mientras faltan sus referencias (40.668885ms)
|
||||||
|
@metis/companies:test: ✔ Personas conserva búsqueda por nombre, contactos, consulta y distinción ñ/n en es/en (614.49001ms)
|
||||||
|
@metis/companies:test: ✔ organización: resuelve departamento y cargo vigentes del responsable (1.42881ms)
|
||||||
|
@metis/companies:test: ✔ organización: sin responsable o asignación no infiere datos desde perfiles ni cargo genérico de persona (1.325109ms)
|
||||||
|
@metis/companies:test: ✔ organización: periodos semiabiertos, históricos y futuros (0.252802ms)
|
||||||
|
@metis/companies:test: ✔ organización: varias vigentes conservan todas, principal primero y orden estable sin mutar (9.918669ms)
|
||||||
|
@metis/companies:test: ✔ organización: dos principales no ocultan información seleccionando una arbitrariamente (0.674705ms)
|
||||||
|
@metis/companies:test: ✔ organización: aislamiento de compañía y persona, y referencias incompletas (0.411903ms)
|
||||||
|
@metis/companies:test: ✔ organización: cambios y refetch se derivan de la fuente actual (0.293302ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: listado sin formularios, alta en cabecera y traducciones (122.63526ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: selectores con alta rápida y ayuda inferior antes de guardar (55.794392ms)
|
||||||
|
@metis/companies:test: ✔ L04.2: Procesos recupera búsqueda, cerrados, orden y página en sus enlaces (189.580929ms)
|
||||||
|
@metis/companies:test: ✔ procesos: listados es/en sin formularios junto a los datos (83.212183ms)
|
||||||
|
@metis/companies:test: ✔ procesos: alta rápida y ayuda inferior antes del pie (29.125304ms)
|
||||||
|
@metis/companies:test: ✔ plantillas: instrucciones, desactivación y ayuda global es/en sin datos de compañía (26.960889ms)
|
||||||
|
@metis/companies:test: ✔ seguimiento: origen personal, revisión actual y edición sin afirmar retirada real; cerrados sin editar (82.569479ms)
|
||||||
|
@metis/companies:test: ✔ remotos: consulta sin formularios y traducciones (117.604882ms)
|
||||||
|
@metis/companies:test: ✔ remotos: formulario, altas rápidas y ayuda antes del pie (91.720088ms)
|
||||||
|
@metis/companies:test: ✔ remotos: origen personal, acceso configurado sin autorización y VPN separada (8.717166ms)
|
||||||
|
@metis/companies:test: ✔ las consultas de compañías conservan sus rutas y rechazan respuestas inválidas (38.957973ms)
|
||||||
|
@metis/companies:test: ✔ lectura y guardado de accesos están separados sin cambiar el contrato HTTP (3.447924ms)
|
||||||
|
@metis/companies:test: ✔ el guardado de usos de buzón mantiene su endpoint y no se confunde con la consulta (4.137329ms)
|
||||||
|
@metis/companies:test: ✔ los adapters mantienen la ausencia semántica sin modificar los campos del formulario (0.458303ms)
|
||||||
|
@metis/companies:test: ✔ las instrucciones de proceso conservan el orden y excluyen líneas vacías (0.790405ms)
|
||||||
|
@metis/companies:test: ✔ la validación de logos conserva tipos permitidos, vacío y límite exacto de tamaño (1.039508ms)
|
||||||
|
@metis/companies:test: ✔ la tabla espera todas sus referencias y distingue actualización de disponibilidad (1.184208ms)
|
||||||
|
@metis/companies:test: ✔ un fallo recuperable conserva datos anteriores sin presentarlos como actualizados (1.058707ms)
|
||||||
|
@metis/companies:test: ✔ una fuente denegada impide mostrar la caché de todo el listado compuesto (0.443503ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 401 conserva la semántica de disponibilidad (25.899282ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 403 conserva la semántica de disponibilidad (0.472703ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 503 conserva la semántica de disponibilidad (0.758505ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: una lectura correcta valida y conserva la página del proveedor (5.522738ms)
|
||||||
|
@metis/companies:test: ✔ iniciales y siglas soportan nombres vacíos, Unicode y separadores (2.60602ms)
|
||||||
|
@metis/companies:test: ✔ el color es estable con variaciones de espacios, mayúsculas y acentos (0.455103ms)
|
||||||
|
@metis/companies:test: ✔ los colores generados mantienen contraste AA para texto pequeño (1.37881ms)
|
||||||
|
@metis/companies:test: ✔ el avatar conserva su nombre accesible y puede ser decorativo dentro de un botón etiquetado (11.161384ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-01/02/17: SO actual desde instalación, independiente de cualquier valor de fábrica (11.358085ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-03/04/05/18: vacío, pendiente y retirado no son SO actual (41.147208ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-06/07: varios SO, orden determinista y aplicaciones excluidas (14.991912ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-08/12: cambiar de activo conserva solo su SO, sin aceptar compañías ajenas (3.395626ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-09: versión de instalación y redundancia literal sin reglas de fabricante (6.70545ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-10: referencia no resoluble no significa ausencia; inactiva resoluble conserva información (5.653143ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-11: error con datos previos, error de resolver y carga no se presentan como vacío (13.2769ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-13/14: servidor sin puesto y plantilla propia consumen la misma lectura por activo (6.347448ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-16: presentación es/en y versión opcional (12.323692ms)
|
||||||
|
@metis/companies:test: ✔ SO global: selector conserva scope y destino SO; la versión instalada sigue separada (0.679705ms)
|
||||||
|
@metis/companies:test: ✔ REG-SO-01/11: render global configurado, inactivo e histórico conserva nombre, versión y destino es/en (30.212926ms)
|
||||||
|
@metis/companies:test: ✔ software del puesto es/en: SO actual, versión, enlaces y separación por ordenador sin formularios incrustados (131.912988ms)
|
||||||
|
@metis/companies:test: ✔ formulario software: recurso fijo en edición, estados permitidos, ayuda y ausencia de persona/perfil (16.373823ms)
|
||||||
|
@metis/companies:test: ✔ R2.4: nombre, origen y enlace global se resuelven sin ResourceDTO local; licencia continúa particular (48.333962ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: consulta sin formularios y traducciones (73.285849ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: formulario con alta rápida de persona, fechas opcionales y ayuda inferior (21.092058ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: sin autorización no se presupone permiso; persona inactiva requiere revisión (5.678142ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: alta desde un perfil fija la persona y no abre otra alta rápida (6.409748ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo es/en: conserva estado documental, vigencia y revisión como mensajes independientes (48.803766ms)
|
||||||
|
@metis/companies:test: ✔ entorno: reúne varios correos y configuraciones, distingue pendiente, retirada y revisión (1.957415ms)
|
||||||
|
@metis/companies:test: ✔ entorno: excluye cerradas, otros perfiles, compañías y correo antiguo sin adivinar relaciones (1.159108ms)
|
||||||
|
@metis/companies:test: ✔ entorno: composición vigente del puesto y datos actuales sin duplicación (0.452003ms)
|
||||||
|
@metis/companies:test: ✔ entorno: ficha es/en con estados y acciones, sin formularios; muestra falso y varios correos (79.687697ms)
|
||||||
|
@metis/companies:test: ✔ entorno: carga, fallo parcial sin falsa ficha vacía, perfil inexistente y aviso de archivado (31.524736ms)
|
||||||
|
@metis/companies:test: ✔ entorno: consulta autorización por persona, común a sus perfiles y sin confundir preparación (26.887202ms)
|
||||||
|
@metis/companies:test: ✔ entorno: accesos abiertos de la persona y sus orígenes sin confundirlos con configuraciones del perfil (11.532086ms)
|
||||||
|
@metis/companies:test: ✔ las entidades de los selectores se pluralizan en es/en sin reutilizar el verbo de acción (5.696842ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: listado es/en, alta en cabecera y ausencia de formulario junto al listado (60.313251ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: la cabecera breve conserva el motivo completo en la celda de revisión (53.349399ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: alta en cuatro pasos, sin guardado intermedio y contenido de ayuda conservado (15.531216ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: selección de persona explícita y cuenta opcional con altas independientes (18.419438ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: el puesto fijado aporta su ordenador y no selecciona al responsable como usuario (8.138861ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: requisitos por paso, cuenta opcional y propuesta basada en persona y ordenador (0.262402ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: el nombre sugerido respeta el límite del contrato (0.255202ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: resumen con listas, ordenador único y ausencias representadas por guion (4.849337ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: edición completa con identidad fija aunque el perfil requiera revisión (9.818873ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: orden y página navegables conservan búsqueda y archivados al abrir entorno (33.702852ms)
|
||||||
|
@metis/companies:test: ✔ Personas admite actividad en listado y ficha, con los demás criterios de contexto (10.626674ms)
|
||||||
|
@metis/companies:test: ✔ el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha (1.875613ms)
|
||||||
|
@metis/companies:test: ✔ las funcionalidades archivadas no tienen rutas activas (0.806505ms)
|
||||||
|
@metis/companies:test: ✔ el filtro de plantilla permite abrir listados y fichas de activos y recursos (1.229009ms)
|
||||||
|
@metis/companies:test: ✔ el filtro de plantilla se limita a los inventarios que conocen sus definiciones (2.248116ms)
|
||||||
|
@metis/companies:test: ✔ todas las secciones de compañía tienen una ruta propia y el índice es el resumen (3.986228ms)
|
||||||
|
@metis/companies:test: ✔ los índices de catálogos y los cinco catálogos globales son destinos independientes (0.811706ms)
|
||||||
|
@metis/companies:test: ✔ las configuraciones conservan la compañía y el UUID del perfil (0.798105ms)
|
||||||
|
@metis/companies:test: ✔ las rutas desconocidas y los parámetros inválidos no abren una sección por defecto (4.041728ms)
|
||||||
|
@metis/companies:test: ✔ la ficha de puesto tiene ruta propia, conserva el UUID y rechaza identificadores inválidos (1.32341ms)
|
||||||
|
@metis/companies:test: ✔ dominios: enlaces a cuentas y grupos limitados al entorno de la compañía (1.191909ms)
|
||||||
|
@metis/companies:test: ✔ los parámetros de contexto llegan al consumidor sin crear rutas nuevas ni aceptar claves ajenas (2.476718ms)
|
||||||
|
@metis/companies:test: ✔ catálogo de aplicaciones: UUID global y criterios propios sin compañía ficticia (2.152215ms)
|
||||||
|
@metis/companies:test: ✔ catálogo SO: ruta específica y filtros, sin confundir con aplicaciones (0.721805ms)
|
||||||
|
@metis/companies:test: ✔ la barra comienza desplegada sin navegador o sin una preferencia válida (3.849727ms)
|
||||||
|
@metis/companies:test: ✔ plegar y desplegar persisten la preferencia sin modificar otras claves (0.314602ms)
|
||||||
|
@metis/companies:test: ✔ un fallo de lectura, escritura o acceso a localStorage no bloquea la barra (0.331202ms)
|
||||||
|
@metis/companies:test: ✔ la capacidad del shell fuerza modos sin cambiar la preferencia manual (1.364609ms)
|
||||||
|
@metis/companies:test: ✔ un menú solo libera su modalidad si su presentación pasa a estar disponible fija (0.284702ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 0 asociados y 0 instalaciones (14.741311ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 0 asociados y 3 instalaciones (11.293584ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 1 asociados y 0 instalaciones (5.654842ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 1 asociados y 2 instalaciones (6.399048ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 2 asociados y 3 instalaciones (9.533572ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 0 asociados y 0 instalaciones (2.845821ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 0 asociados y 3 instalaciones (15.739718ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 1 asociados y 0 instalaciones (7.435056ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 1 asociados y 2 instalaciones (13.778003ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 2 asociados y 3 instalaciones (15.351815ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 0 asociados y 0 instalaciones (3.484126ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 0 asociados y 3 instalaciones (4.870336ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 1 asociados y 0 instalaciones (2.823221ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 1 asociados y 2 instalaciones (7.89826ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 2 asociados y 3 instalaciones (21.540861ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 0 asociados y 0 instalaciones (4.372533ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 0 asociados y 3 instalaciones (13.702403ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 1 asociados y 0 instalaciones (3.740728ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 1 asociados y 2 instalaciones (10.077775ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 2 asociados y 3 instalaciones (13.223299ms)
|
||||||
|
@metis/companies:test: ✔ layout: dos asociados se reparten por capacidad; sin principal no se fabrican conexiones (13.325899ms)
|
||||||
|
@metis/companies:test: ✔ preview: conserva límites y publica solo cobertura real de fuentes conocidas (0.436403ms)
|
||||||
|
@metis/companies:test: ✔ WD04.4: acción estable de presentación, fuera de las relaciones y contadores, sin solapamientos (186.997902ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: acciones ordenadas del principal y periférico sin inferir software por categoría (2.565019ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: lectura sin permisos de edición, carga/error parcial y activos retirados (0.528004ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: distingue instalación, producto global/inactivo y licencia particular (0.783106ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: producto particular y gates cerrados no crean acciones administrativas (1.015507ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: SO global utiliza la referencia pública y su etiqueta específica (0.481604ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: estados terminales, refetch y cambio de principal o compañía no reutilizan acciones antiguas (0.396003ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: trigger es/en separado del enlace, tarjetas sin acciones y añadir sin menú (46.278846ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: ordenador y equipamiento actuales, personas por perfiles y archivados separados (9.747173ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: sin responsable sigue mostrando usuarios y un puesto desmantelado no cuenta historia como miembros (0.399703ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: sustituir ordenador resuelve perfiles del nuevo host sin trasladar los anteriores (0.307502ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: referencias ajenas o principal fuera de composición no aportan relaciones (0.277302ms)
|
||||||
|
@metis/companies:test: ✔ pendientes: solo pending, perfiles activos, compañía correcta y deduplicación por familia e identidad (2.547319ms)
|
||||||
|
@metis/companies:test: ✔ último cambio: orden cronológico sin mutación, altas de miembros y campos realmente cambiados (1.271609ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal válido primero, miembros únicos y secundarios en orden declarado (3.800529ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: activos sin principal no adquieren un principal por su tipo o posición (0.464603ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal fuera de composición se advierte sin añadirlo a miembros (0.489303ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: referencia ausente o de otra compañía no inventa datos ni marca principal (0.464603ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: vacío y desmantelado no recuperan miembros del historial (0.317703ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: averiado sigue asignado y conserva ambos estados originales (0.430503ms)
|
||||||
|
@metis/companies:test: ✔ expansión: todos abiertos inicialmente y plegado independiente de cada perfil (5.477941ms)
|
||||||
|
@metis/companies:test: ✔ refetch conserva plegados y filtros individuales, incluso con error o fuentes incompletas (0.399003ms)
|
||||||
|
@metis/companies:test: ✔ retirada o archivo limpia únicamente el estado del perfil afectado (0.477704ms)
|
||||||
|
@metis/companies:test: ✔ cambio de compañía, puesto u ordenador restablece todos los perfiles abiertos y los filtros (0.359603ms)
|
||||||
|
@metis/companies:test: ✔ orden estable y numérico del resumen y los pendientes, sin mutar datos remotos (7.384256ms)
|
||||||
|
@metis/companies:test: ✔ incluye solo perfiles del ordenador principal, distingue inactivos y deduplica personas (5.988545ms)
|
||||||
|
@metis/companies:test: ✔ puesto inexistente o sin composición no hereda perfiles de otros activos (1.020908ms)
|
||||||
|
@metis/companies:test: ✔ persona: responsabilidad y uso son independientes y solo se consideran puestos y perfiles vigentes (0.877107ms)
|
||||||
|
@metis/companies:test: ✔ ficha de puesto es/en: cabecera, retorno, bloques vacíos y edición modal sin formulario incrustado (79.547895ms)
|
||||||
|
@metis/companies:test: ✔ un UUID de puesto ausente muestra no encontrado sin cabecera ni edición de otro puesto (2.842922ms)
|
||||||
|
@metis/companies:test: ✔ ficha de persona es/en: distingue responsabilidad y uso, y enlaza los puestos (5.277339ms)
|
||||||
|
@metis/companies:test: ✔ ficha: ordenador destacado y periféricos sin repetir, perfiles solo del ordenador y personas deduplicadas (84.084128ms)
|
||||||
|
@metis/companies:test: ✔ personas y perfiles: carga y error no se presentan como listados vacíos (9.031767ms)
|
||||||
|
@metis/companies:test: ✔ ficha: no permite crear perfiles si el ordenador está retirado o el puesto está desactivado (38.186585ms)
|
||||||
|
@metis/companies:test: ✔ historial del puesto es/en: revisiones de más reciente a más antigua con autor y valores de cada momento (6.405148ms)
|
||||||
|
@metis/companies:test: ✔ personas es/en: enlaces a todos sus puestos bajo responsabilidad, sin confundirlos con puestos usados (45.929143ms)
|
||||||
|
@metis/companies:test: ✔ menú de compañía es/en: totales en badges alineados y cero real en secciones vacías (31.400235ms)
|
||||||
|
@metis/companies:test: ✔ ruta de activo de compañía abre la página con información, historial, software y solo sus perfiles (81.64301ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento es/en: badges de principal, técnico y uso conservan su significado (6.060845ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: inventario fallido no se presenta como vacío y permite reintentar (9.302969ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal inconsistente se explica y no se incorpora como miembro válido (2.154217ms)
|
||||||
|
@metis/companies:test: ✔ personas y perfiles es/en: expansión contextual, enlaces independientes y resumen cero real (6.853351ms)
|
||||||
|
@metis/companies:test: ✔ cabecera es/en: responsable largo o ausente, contexto independiente y mapa con resumen (57.574831ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: lista completa y todos desplegados por defecto, incluso con cincuenta perfiles (67.036601ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: carga, error y datos retenidos no publican un total como actual (4.819436ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: contador conocido cero, uno, cinco y seis; error conserva filas sin publicar total (10.602879ms)
|
||||||
|
@metis/companies:test: ✔ configuración: cobertura desconocida, revisión y notas permanecen fuera del disclosure técnico (2.309518ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: una actualización en curso conserva el total conocido y anuncia la actividad (1.866514ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: dependencia sin principal conserva mapa y resumen y una explicación principal sin tabla vacía (12.142091ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: un vacío confirmado de perfiles conserva el alta y evita cabeceras de una tabla sin filas (1.052608ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: soporte distingue perfiles en carga, error, ausencia confirmada y persona no resuelta (3.211824ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: referencia de principal no resuelta no publica cero pendientes ni ausencia de perfiles (5.746443ms)
|
||||||
|
@metis/companies:test: ✔ v3: Software conserva los tres estados documentales con la misma representación que las familias (33.35785ms)
|
||||||
|
@metis/companies:test: ✔ v3: estados técnicos de equipamiento conservan principal y asignación en es/en (11.798888ms)
|
||||||
|
@metis/companies:test: ✔ responsable es/en: avatar y nombre abren un único popup y la ausencia no ofrece acciones (3.218824ms)
|
||||||
|
@metis/companies:test: ✔ mapa visual: aísla compañías, deduplica miembros y conserva software y estados reales (9.803073ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: principal solo, vacíos confirmados y error de software conservan navegación y gestión (29.001717ms)
|
||||||
|
@metis/companies:test: ✔ mapa: composición vacía y sin principal no fabrica relaciones ni instalaciones (1.354311ms)
|
||||||
|
@metis/companies:test: ✔ contexto de persona: rechaza datos de otra compañía y no ofrece acciones (0.556304ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: carga inicial y carga parcial mantienen estados accesibles (16.351023ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: leyenda con tres conceptos y cobertura explícita sin totales inventados (1.627312ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: revisión de software traducida incluso sin referencia navegable (0.881407ms)
|
||||||
|
@metis/companies:test: ✔ WD04.4 es/en: acción autorizada, vacío accionable y lectura sin permiso (5.623542ms)
|
||||||
|
@metis/companies:test: ✔ perfiles del puesto: configuraciones de todos los activos abiertas y destinos accesibles únicos (11.803788ms)
|
||||||
|
@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 15483.371216
|
||||||
|
@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 (95.367068ms)
|
||||||
|
@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 (19.146134ms)
|
||||||
|
@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: 32.595s
|
||||||
|
Failed: @metis/companies#test
|
||||||
|
|
||||||
|
ERROR run failed: command exited (1)
|
||||||
|
[ELIFECYCLE] Test failed. See above for more details.
|
||||||
29
.artifacts/account-deletion/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/rdx-utils:typecheck: cache hit, replaying logs e660b2f14d16d88c
|
||||||
|
@repo/rdx-utils:typecheck: $ tsc -p tsconfig.json
|
||||||
|
@repo/shadcn-ui:typecheck: cache miss, executing 3a02880bf260fd89
|
||||||
|
@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: 8.024s
|
||||||
|
Failed: @repo/shadcn-ui#typecheck
|
||||||
|
|
||||||
|
ERROR run failed: command exited (2)
|
||||||
|
[ELIFECYCLE] Command failed with exit code 2.
|
||||||
10
.artifacts/account-deletion/usage-tests.log
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
✔ modules/companies/src/api/domain/access/access.test.ts (752.333653ms)
|
||||||
|
✔ modules/companies/src/web/access/shared/access.test.tsx (7349.070512ms)
|
||||||
|
ℹ tests 2
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 2
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 7369.666084
|
||||||
54
.artifacts/account-deletion/validacion.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Eliminación de cuentas — validación del 27/10/2026
|
||||||
|
|
||||||
|
Se añade «Eliminar cuenta» al menú de fila de las cuentas de compañía. La confirmación
|
||||||
|
identifica la cuenta y explica que el borrado incluye su historial propio y no elimina
|
||||||
|
la cuenta externa. Las referencias actuales e históricas bloquean la operación con
|
||||||
|
`ACCOUNT_IN_USE`; no se borran titulares, pertenencias, permisos ni perfiles en cascada.
|
||||||
|
La API valida compañía, UUID, cuerpo y query, usa sesión/CSRF y ejecuta el borrado
|
||||||
|
transaccional con bloqueos y comprobación de referencias entrantes por FK.
|
||||||
|
|
||||||
|
Archivos principales:
|
||||||
|
- `modules/companies/src/api/infrastructure/persistence/remove-access-account.ts`
|
||||||
|
- `modules/companies/src/api/infrastructure/http/access.routes.ts`
|
||||||
|
- `modules/companies/src/web/access/delete/ui/account-delete-dialog.tsx`
|
||||||
|
- `modules/companies/src/web/access/list/ui/access-workspace.tsx`
|
||||||
|
- `apps/server/src/account-deletion.integration.test.ts`
|
||||||
|
- `scripts/account-deletion-ui-audit.py`
|
||||||
|
|
||||||
|
## Comprobaciones correctas
|
||||||
|
|
||||||
|
- `pnpm --filter @metis/companies typecheck`.
|
||||||
|
- `pnpm --filter @metis/server typecheck`.
|
||||||
|
- `TSX_TSCONFIG_PATH=tsconfig.json node --import tsx --test modules/companies/src/web/shared/client-contracts.test.ts modules/companies/src/web/access/shared/access.test.tsx`.
|
||||||
|
- `DB_NAME=metis_desk_test NODE_ENV=test node --env-file=../../.env --import tsx --test src/account-deletion.integration.test.ts` desde `apps/server`: borrado de cuentas activas/inactivas, historial propio, unicidad liberada, aislamiento, repetición y protección de referencias externas incluso con una FK CASCADE.
|
||||||
|
- Biome dirigido a los archivos de código cambiados: sin errores; una advertencia de complejidad 16/15 en `AccessWorkspacePage`.
|
||||||
|
- `git diff --check` y ESLint dirigido a los archivos añadidos y modificados del servidor y del diálogo.
|
||||||
|
- `python3 scripts/visual-audit.py --account-deletion --check`: 17 comprobaciones correctas mediante Playwright MCP en Docker stdio; login ordinario real, HTTP, cancelación, Escape, retorno del foco al menú, reintento tras 503, listado actualizado y rechazo de cuentas con relaciones.
|
||||||
|
- Volumen real: 15 compañías; principal con 50 personas, 51 activos, 66 recursos, 50 perfiles, 50 puestos y 62 cuentas creadas durante el recorrido. Únicamente `metis_desk_test` desechable; entorno retirado y sesión eliminada.
|
||||||
|
- Capturas de 1440, 390 y 320 px; abiertas e inspeccionadas las de 320 px y el error por cuenta en uso de escritorio. Sin desbordamiento horizontal del diálogo en los tres tamaños.
|
||||||
|
- La compilación de producción pasó; conserva el aviso de tamaño de chunks.
|
||||||
|
|
||||||
|
Evidencias de la auditoría:
|
||||||
|
[Informe de navegador](../playwright/authenticated-jjxhf9iu/account-deletion-report.json),
|
||||||
|
[diálogo a 320 px](../playwright/authenticated-jjxhf9iu/account-delete-320.png),
|
||||||
|
[cuenta en uso](../playwright/authenticated-jjxhf9iu/account-delete-in-use.png).
|
||||||
|
|
||||||
|
## Comprobaciones globales con fallos ajenos al cambio
|
||||||
|
|
||||||
|
Se ejecutaron `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm check` y `pnpm build`.
|
||||||
|
Los cuatro primeros no quedaron verdes:
|
||||||
|
- Typecheck global: `packages/shadcn-ui/src/components/calendar.tsx:165`, propiedad `locale` incompatible con exactOptionalPropertyTypes. Vendor sin modificar.
|
||||||
|
- Tests globales: dos aserciones de textos de `modules/companies/src/web/companies/shared/companies-page.test.tsx`; el fichero y los componentes de personas no forman parte del cambio. El fallo de Companies detuvo Turbo antes de completar la suite global.
|
||||||
|
- Lint/check globales: diagnósticos de archivos ajenos (incluidos person-photo y evidencias JSON). No se reformatearon otros cambios del workspace.
|
||||||
|
- Integración antigua `identity.test.ts`: el primer intento requiere migración global R2.1b; con gates cerrados falla antes de llegar a los nuevos casos de cuentas porque una ruta archivada devuelve 401 y su prueba espera 404. Los casos HTTP añadidos en `access.integration.ts` no llegaron a ejecutarse en esa suite; la operación se verificó con la prueba de persistencia específica y la auditoría HTTP aislada.
|
||||||
|
|
||||||
|
## Skills y límites
|
||||||
|
|
||||||
|
Consultadas `rdx-solid` (capas, contratos y pruebas), `ux-error-recovery` (confirmación y
|
||||||
|
recuperación), `shadcn` (reutilización de las primitivas instaladas) y
|
||||||
|
`vercel-react-best-practices` (estado derivado sin efectos ni cachés remotas duplicadas).
|
||||||
|
Se reutiliza `ConfirmDeleteDialog` de rdx-ui y los tokens y patrones de DESIGN.md.
|
||||||
|
Los auditores especializados ux-*-audit de la skill no están instalados; no se invocaron
|
||||||
|
ni instalaron. No se declara una auditoría WCAG completa, lector de pantalla, contraste
|
||||||
|
global, tema oscuro ni todos los estados de carga del listado. No se borraron datos de
|
||||||
|
Abeto Design ni de ninguna compañía real durante la validación.
|
||||||
3
.artifacts/account-deletion/visual.log
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Preparando MariaDB desechable y servicios en puertos libres…
|
||||||
|
Eliminación de cuentas: 17 comprobaciones. Evidencias: /home/david/dev/metis-desk/.artifacts/playwright/authenticated-jjxhf9iu
|
||||||
|
Entorno temporal retirado; las capturas se conservan sin sesión ni credenciales.
|
||||||
49
.artifacts/account-usage/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 f20a36dd7052df4c
|
||||||
|
@metis/web:build: cache hit, replaying logs 0f53d284c7dbfe57
|
||||||
|
@metis/web:build: $ vite build
|
||||||
|
@metis/web:build: vite v8.3.0 building client environment for production...
|
||||||
|
@metis/web:build: transforming...
|
||||||
|
@metis/web:build: ✓ 10092 modules transformed.
|
||||||
|
@metis/web:build: rendering chunks...
|
||||||
|
@metis/web:build: computing gzip size...
|
||||||
|
@metis/web:build: dist/index.html 0.54 kB │ gzip: 0.35 kB
|
||||||
|
@metis/web:build: dist/assets/geist-mono-latin-ext-variable-Bwz-egvJ.woff2 14.69 kB
|
||||||
|
@metis/web:build: dist/assets/geist-mono-latin-variable-XN7g48iV.woff2 23.12 kB
|
||||||
|
@metis/web:build: dist/assets/roboto-serif-latin-ext-variable-B9BEn9Tg.woff2 49.84 kB
|
||||||
|
@metis/web:build: dist/assets/roboto-serif-latin-variable-CusprKnR.woff2 66.10 kB
|
||||||
|
@metis/web:build: dist/assets/inter-variable-DiVDrmQJ.woff2 352.24 kB
|
||||||
|
@metis/web:build: dist/assets/workplace-map-canvas-DLioOiRN.css 15.41 kB │ gzip: 2.56 kB
|
||||||
|
@metis/web:build: dist/assets/index-D8sQAPMw.css 256.35 kB │ gzip: 39.28 kB
|
||||||
|
@metis/web:build: dist/assets/workplace-map-canvas-Dk-Lnm8b.js 227.75 kB │ gzip: 74.26 kB
|
||||||
|
@metis/web:build: dist/assets/index-BK21VChv.js 1,859.58 kB │ gzip: 521.25 kB
|
||||||
|
@metis/web:build:
|
||||||
|
@metis/web:build: [plugin builtin:vite-reporter]
|
||||||
|
@metis/web:build: (!) Some chunks are larger than 500 kB after minification. Consider:
|
||||||
|
@metis/web:build: - Using dynamic import() to code-split the application
|
||||||
|
@metis/web:build: - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||||
|
@metis/web:build: - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
|
||||||
|
@metis/web:build: ✓ built in 1.05s
|
||||||
|
@metis/server:build: $ tsup --config tsup.config.ts && pnpm check:build
|
||||||
|
@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.46 MB
|
||||||
|
@metis/server:build: ESM dist/index.js.map 2.72 MB
|
||||||
|
@metis/server:build: ESM ⚡️ Build success in 683ms
|
||||||
|
@metis/server:build: $ node --check dist/index.js
|
||||||
|
|
||||||
|
Tasks: 2 successful, 2 total
|
||||||
|
Cached: 1 cached, 2 total
|
||||||
|
Time: 4.337s
|
||||||
|
|
||||||
1
.artifacts/account-usage/changed-check.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
Checked 10 files in 42ms. Fixed 1 file.
|
||||||
1004
.artifacts/account-usage/check.log
Normal file
1
.artifacts/account-usage/companies-typecheck.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
441
.artifacts/account-usage/lint.log
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
$ 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 │
|
||||||
|
|
||||||
|
|
||||||
|
.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"
|
||||||
|
│ +++++
|
||||||
|
|
||||||
|
.artifacts/account-deletion/cleanup-test.mts:3:1 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━━
|
||||||
|
|
||||||
|
× Block statements are preferred in this position.
|
||||||
|
|
||||||
|
1 │ import { createDatabase, readDatabaseEnvironment } from '../../apps/server/src/config/database';
|
||||||
|
2 │ const env=readDatabaseEnvironment(process.env);
|
||||||
|
> 3 │ if(env.DB_NAME!=='metis_desk_test'||env.NODE_ENV!=='test')throw Error('Solo pruebas');
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
4 │ const db=createDatabase(env);
|
||||||
|
5 │ const company='01a17cae-4bba-7070-a394-13f9009c5cfb';
|
||||||
|
|
||||||
|
i Unsafe fix: Wrap the statement with a `JsBlockStatement`
|
||||||
|
|
||||||
|
3 │ if(env.DB_NAME!=='metis_desk_test'||env.NODE_ENV!=='test')·{·throw·Error('Solo·pruebas');·}
|
||||||
|
│ +++ ++
|
||||||
|
|
||||||
|
.artifacts/account-deletion/cleanup-test.mts:11:2 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
|
||||||
|
|
||||||
|
× Block statements are preferred in this position.
|
||||||
|
|
||||||
|
9 │ const rows=await db.query('SELECT i.kind,i.id FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction,type:'SELECT'});
|
||||||
|
10 │ console.info({fixtureCompany:company,items:rows});
|
||||||
|
> 11 │ for(const table of ['access_references','access_person_references']) await db.query(`DELETE r FROM ${table} r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
12 │ await db.query('DELETE r FROM access_revisions r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction});
|
||||||
|
13 │ for(const table of ['access_account_values','access_system_values']) await db.query(`DELETE v FROM ${table} v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
|
||||||
|
i Unsafe fix: Wrap the statement with a `JsBlockStatement`
|
||||||
|
|
||||||
|
11 │ ·for(const·table·of·['access_references','access_person_references'])·{·await·db.query(`DELETE·r·FROM·${table}·r·JOIN·access_items·i·ON·i.id=r.itemId·JOIN·access_workspaces·w·ON·w.id=i.workspaceId·WHERE·w.companyId=:company`,{replacements,transaction});·}
|
||||||
|
│ ++ ++
|
||||||
|
|
||||||
|
.artifacts/account-deletion/cleanup-test.mts:13:2 lint/style/useBlockStatements FIXABLE ━━━━━━━━━━
|
||||||
|
|
||||||
|
× Block statements are preferred in this position.
|
||||||
|
|
||||||
|
11 │ for(const table of ['access_references','access_person_references']) await db.query(`DELETE r FROM ${table} r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
12 │ await db.query('DELETE r FROM access_revisions r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction});
|
||||||
|
> 13 │ for(const table of ['access_account_values','access_system_values']) await db.query(`DELETE v FROM ${table} v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company`,{replacements,transaction});
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
14 │ await db.query('DELETE i FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company',{replacements,transaction});
|
||||||
|
15 │ await db.query('DELETE FROM access_workspaces WHERE companyId=:company',{replacements,transaction});
|
||||||
|
|
||||||
|
i Unsafe fix: Wrap the statement with a `JsBlockStatement`
|
||||||
|
|
||||||
|
13 │ ·for(const·table·of·['access_account_values','access_system_values'])·{·await·db.query(`DELETE·v·FROM·${table}·v·JOIN·access_items·i·ON·i.id=v.ownerId·JOIN·access_workspaces·w·ON·w.id=i.workspaceId·WHERE·w.companyId=:company`,{replacements,transaction});·}
|
||||||
|
│ ++ ++
|
||||||
|
|
||||||
|
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·});·}
|
||||||
|
│ ++ ++
|
||||||
|
|
||||||
|
The number of diagnostics exceeds the limit allowed. Use --max-diagnostics to increase it.
|
||||||
|
Diagnostics not shown: 5.
|
||||||
|
Checked 2283 files in 1921ms. No fixes applied.
|
||||||
|
Found 18 errors.
|
||||||
|
Found 2 warnings.
|
||||||
|
Found 5 infos.
|
||||||
|
lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
× Some errors were emitted while running checks.
|
||||||
|
|
||||||
|
|
||||||
|
[ELIFECYCLE] Command failed with exit code 1.
|
||||||
9
.artifacts/account-usage/persistence.log
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
✔ MariaDB: borrado de cuentas aislado, histórico propio y referencias externas protegidas (3545.671457ms)
|
||||||
|
ℹ tests 1
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 1
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 8097.525165
|
||||||
1
.artifacts/account-usage/server-typecheck.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
$ tsc -p tsconfig.json
|
||||||
7
.artifacts/account-usage/spacing.log
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
/home/david/dev/metis-desk/modules/companies/src/api/domain/access/access-policy.ts
|
||||||
|
37:3 error Expected blank line before this statement @stylistic/padding-line-between-statements
|
||||||
|
|
||||||
|
✖ 1 problem (1 error, 0 warnings)
|
||||||
|
1 error and 0 warnings potentially fixable with the `--fix` option.
|
||||||
|
|
||||||
858
.artifacts/account-usage/test.log
Normal file
@ -0,0 +1,858 @@
|
|||||||
|
$ turbo run test
|
||||||
|
• 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 test in 13 packages
|
||||||
|
• Remote caching disabled
|
||||||
|
|
||||||
|
@metis/core:test: cache bypass, force executing 2283a7af50b4c3f8
|
||||||
|
@repo/rdx-ui:test: cache bypass, force executing dd1af9cec005380f
|
||||||
|
@repo/rdx-ddd:test: cache bypass, force executing b722cdfdd985a9bd
|
||||||
|
@metis/relationship-contracts:test: cache bypass, force executing 97138537b462262f
|
||||||
|
@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
|
||||||
|
@metis/relationship-contracts:test: $ node --import tsx --test --test-isolation=none src/*.test.ts
|
||||||
|
@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
|
||||||
|
@repo/rdx-ddd:test: $ node --import tsx --test src/domain-model.test.ts
|
||||||
|
@metis/core:test: ✔ el contrato de estado exige un timestamp UTC y un estado conocido (9.008758ms)
|
||||||
|
@metis/core:test: ✔ el endpoint de estado no acepta parámetros de consulta (0.506404ms)
|
||||||
|
@metis/core:test: ✔ las escrituras leen la sesión actual de Query y siguen su rotación y eliminación (122.650593ms)
|
||||||
|
@metis/core:test: ✔ GET, HEAD y OPTIONS funcionan sin sesión y no envían tokens suministrados por el llamador (1.564411ms)
|
||||||
|
@metis/core:test: ✔ mantiene cuerpos JSON, binarios, headers, cancelación y respuestas de error sin reintentar (9.27366ms)
|
||||||
|
@metis/core:test: ✔ rechaza tokens ausentes o inválidos y destinos externos antes de enviar una petición (2.616116ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ referencias UUID estables, normalizadas, inmutables y con proveedor coherente (22.284544ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ namespaces canónicos, sin identificadores de tabla, espacios ni segmentos vacíos (7.203646ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ el catálogo valida propietarios, namespaces, duplicados y definiciones (7.271947ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ el catálogo crea una copia profundamente inmutable sin congelar la entrada (2.178814ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ relaciones dirigidas, simétricas y derivadas respetan su semántica declarativa (1.816211ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ consulta estricta: límites, cursores y ausencia de privilegios del navegador (4.191027ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ validación de ámbito, tipo y sentido de consulta sin conceder autorización (3.193421ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ orígenes y páginas conservan referencias sin mezclar compañías (3.06012ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ servicios de prueba por lotes y errores compatibles con Result de Metis (1.215908ms)
|
||||||
|
@metis/core:test: ✔ estado de ficha: tablas independientes, aislamiento entre quince propietarios y metadatos ajenos conservados (11.059572ms)
|
||||||
|
@metis/core:test: ✔ estado de ficha: metadatos malformados, ámbitos externos y valores incompatibles no se restauran (6.869444ms)
|
||||||
|
@metis/core:test: ✔ History mantiene URL, filtros, Atrás/Adelante, cierre y suscripciones sin entradas duplicadas (3.986226ms)
|
||||||
|
@metis/core:test: ✔ las redirecciones internas rechazan destinos externos, barras invertidas y caracteres de control (0.368503ms)
|
||||||
|
@metis/core:test: ✔ el retorno reutiliza la entrada exacta del listado y conserva el estado ajeno (0.545304ms)
|
||||||
|
@metis/core:test: ✔ el contexto de retorno valida versión, coordenadas, ámbito y filtros sin guardar registros (5.041032ms)
|
||||||
|
@metis/core:test: ✔ las consultas Unicode válidas no pierden contexto al superar 2000 caracteres codificados (7.427448ms)
|
||||||
|
@metis/core:test: ✔ retorno de detalle y listado conviven sin aceptar el ámbito de otro propietario (0.893606ms)
|
||||||
|
@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 1078.868884
|
||||||
|
@metis/relationship-contracts:test: ✔ el grafo de dependencias del paquete es acíclico y ajeno a módulos funcionales (51.05583ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ las importaciones internas de producción no contienen ciclos ni imports privados (2.145114ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ capacidades semánticas explícitas sin registrar proveedores ficticios (1.698711ms)
|
||||||
|
@metis/relationship-contracts:test: ✔ rol semántico opcional conserva la compatibilidad de vistas comunes sin metadatos libres (3.08212ms)
|
||||||
|
@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 1051.891709
|
||||||
|
@repo/rdx-ddd:test: ✔ UniqueID genera UUID v7 distintos, conserva UUID existentes y rechaza identificadores inválidos (16.366106ms)
|
||||||
|
@repo/rdx-ddd:test: ✔ los Value Objects validan y comparan valores semánticos por tipo (4.798631ms)
|
||||||
|
@repo/rdx-ddd:test: ✔ entidades y agregados comparan identidad y tipo, independientemente de sus atributos (0.803406ms)
|
||||||
|
@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 1384.872559
|
||||||
|
@repo/rdx-ui:test: ✔ la tabla compacta conserva semántica y enlaces sin herramientas de exploración (31.515565ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los valores ausentes comparten criterio con EntityTable y cero permanece visible (1.781515ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga y error sin datos no afirman vacío; caché obsoleta conserva registros (9.561981ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la ampliación identifica el contenido controlado sin añadir controles a colecciones pequeñas (5.89255ms)
|
||||||
|
@repo/rdx-ui:test: ✔ EntityTable mantiene búsqueda, columnas y paginación con su contrato anterior (41.59285ms)
|
||||||
|
@repo/rdx-ui:test: ✔ preview controlado: recuperar la ampliación muestra el origen con identidad estable sin perder el modo local (9.788482ms)
|
||||||
|
@repo/rdx-ui:test: ✔ contador: conserva cero y formatea el total por idioma (29.299689ms)
|
||||||
|
@repo/rdx-ui:test: ✔ contador: carga y error muestran ausencia de total, conservando una cifra anterior si existe (3.664324ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la ayuda breve respeta la capacidad efectiva, también con fuentes ampliadas (1.348409ms)
|
||||||
|
@repo/rdx-ui:test: ✔ auto y expanded reservan 40rem al formulario y 20rem al panel (0.300402ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el contenido inline conserva descripción, ámbito, ejemplos, orientación e icono (11.170672ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después (75.337334ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia (15.775333ms)
|
||||||
|
@repo/rdx-ui:test: ✔ datos locales inválidos no rompen el listado y solo se aceptan valores booleanos (0.513704ms)
|
||||||
|
@repo/rdx-ui:test: ✔ almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local (0.414704ms)
|
||||||
|
@repo/rdx-ui:test: ✔ buscar columnas ignora tildes y mayúsculas, conserva ñ y no modifica visibilidad (1.18661ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los grupos conservan orden, omiten vacíos y deduplican IDs sin perder columnas sin grupo (0.553605ms)
|
||||||
|
@repo/rdx-ui:test: ✔ restaurar preferencias vacías recupera defaults actuales sin tocar otras tablas (11.717498ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los contadores distinguen visibilidad, total y coincidencias en es/en (0.350203ms)
|
||||||
|
@repo/rdx-ui:test: ✔ listado remoto: recupera columnas por ámbito y protege nombre y acciones (20.808175ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los rangos vacíos y la última página nunca muestran índices fuera del conjunto (2.641923ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el listado pagina el conjunto y repite su rango conservando controles accesibles (63.250832ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la búsqueda local reduce el conjunto completo antes de paginar y permite limpiarla (12.351804ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el texto formado solo por espacios se puede borrar sin confundir vacío con ausencia de coincidencias (10.492989ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la búsqueda genérica ignora acentos y Unicode equivalente, manteniendo ñ y el texto escrito (30.90046ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial ascendente se indica en la cabecera y se aplica antes de paginar (9.955984ms)
|
||||||
|
@repo/rdx-ui:test: ✔ una columna explícita puede establecer un orden inicial descendente (9.636781ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial ignora acciones y columnas sin valor comparable (13.761716ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las tablas sin columnas ordenables conservan el orden recibido (10.250087ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el orden inicial mantiene la estabilidad en empates y se indica con el listado vacío (13.507914ms)
|
||||||
|
@repo/rdx-ui:test: ✔ una búsqueda controlada conserva los resultados filtrados por la pantalla (5.646547ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga, error y búsqueda vacía se distinguen de un listado sin registros (32.989977ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el rango y la configuración también se traducen en inglés (5.669147ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: disparador de solo icono con etiqueta accesible y menú contextual (5.027842ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: operación principal visible con nombre accesible y secundarias en el menú (6.166651ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: conserva la restricción de la principal y evita menús sin secundarias (2.268619ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el estado activo se distingue en verde del inactivo (0.338703ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones: siempre última, compacta y anclada a la derecha, aunque se declare primero (7.291062ms)
|
||||||
|
@repo/rdx-ui:test: ✔ los textos comunes mantienen claves, idiomas y parámetros del rango (0.237002ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las celdas vacías muestran un guion largo en es/en y conservan cero, textos y controles (163.066573ms)
|
||||||
|
@repo/rdx-ui:test: ✔ las columnas opcionales empiezan ocultas sin ocultar identificación ni acciones obligatorias (5.796249ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre ordena sin tildes ni mayúsculas antes de paginar, en español e inglés (11.201695ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre tiene prioridad inicial aunque otra columna comparable aparezca antes (4.948042ms)
|
||||||
|
@repo/rdx-ui:test: ✔ Nombre conserva empates entre acentos, mayúsculas y Unicode compuesto al ordenar descendente (4.345637ms)
|
||||||
|
@repo/rdx-ui:test: ✔ paginación por cursor: navegación accesible y límites sin inventar totales (0.963908ms)
|
||||||
|
@repo/rdx-ui:test: ✔ estados presentacionales: carga, datos anteriores y recuperación son mutuamente distinguibles (0.411203ms)
|
||||||
|
@repo/rdx-ui:test: ✔ carga inicial conserva estructura, no anuncia cero registros y no fabrica acciones (6.395554ms)
|
||||||
|
@repo/rdx-ui:test: ✔ refetch y error posterior muestran los datos disponibles sin skeletons (10.391887ms)
|
||||||
|
@repo/rdx-ui:test: ✔ error inicial, vacío válido y filtros sin coincidencias tienen contenidos distintos en es/en (18.888159ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el contrato de navegación rechaza páginas mal formadas y ordenaciones desconocidas (0.569805ms)
|
||||||
|
@repo/rdx-ui:test: ✔ navegación controlada ordena y pagina realmente sin alterar el consumidor no controlado (6.487755ms)
|
||||||
|
@repo/rdx-ui:test: ✔ quitar sección conserva todos los campos y su obligatoriedad sin mutar el origen (2.973725ms)
|
||||||
|
@repo/rdx-ui:test: ✔ copiar remapea secciones conservando campos y reglas con identidades independientes (0.471404ms)
|
||||||
|
@repo/rdx-ui:test: ✔ secciones visuales ordenadas, abiertas inicialmente y campos sin sección (10.512488ms)
|
||||||
|
@repo/rdx-ui:test: ✔ consulta compacta conserva grupos, campos sin sección y avisos sin tarjetas anidadas (3.818132ms)
|
||||||
|
@repo/rdx-ui:test: ✔ pestañas de grupos conservan controles ocultos, orden y campos sin grupo, omitiendo grupos vacíos (16.088035ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el editor ofrece asas etiquetadas para secciones y campos, deshabilitadas al guardar (81.917989ms)
|
||||||
|
@repo/rdx-ui:test: ✔ soltar antes o después del destino conserva el orden de los demás elementos (1.648411ms)
|
||||||
|
@repo/rdx-ui:test: ✔ destinos inválidos y posiciones sin cambio no producen una nueva ordenación (8.598755ms)
|
||||||
|
@repo/rdx-ui:test: ✔ reordenar mantiene identidades, secciones y obligatoriedad sin modificar los objetos (0.328802ms)
|
||||||
|
@repo/rdx-ui:test: ✔ la comparación parcial pliega vocales, caja y representaciones Unicode equivalentes (12.233679ms)
|
||||||
|
@repo/rdx-ui:test: ✔ ñ conserva identidad compuesta y descompuesta y otras escrituras no se transliteran (0.417703ms)
|
||||||
|
@repo/rdx-ui:test: ✔ puntuación técnica, números y espacios interiores siguen siendo significativos (1.61611ms)
|
||||||
|
@repo/rdx-ui:test: ✔ vacíos y valores sin texto no serializan objetos arbitrarios (0.375902ms)
|
||||||
|
@repo/rdx-ui:test: ✔ formatea números españoles y conserva extensiones para visualización y llamada (1.40611ms)
|
||||||
|
@repo/rdx-ui:test: ✔ preserva números internacionales no españoles sin inventar agrupaciones (0.322902ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el enlace telefónico usa el número formateado y mantiene un nombre accesible (8.323954ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selector remoto: el contenido gráfico conserva la selección y la etiqueta textual (47.632208ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selector remoto: selección compacta al principio, radios etiquetados y orden recibido sin paginación local (6.346041ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selector remoto: vacío, carga y error no se confunden y errores ocultan filas obsoletas (9.075459ms)
|
||||||
|
@repo/rdx-ui:test: ✔ pestañas desplazables: conserva identidad, selección y etiquetas largas sin añadir focos al SSR (33.226614ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el selector agrupa la acción etiquetada sin convertirla en envío de formulario (40.836344ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el selector deshabilitado protege ambas acciones y admite selección sin botón (9.352379ms)
|
||||||
|
@repo/rdx-ui:test: ✔ La capacidad contextual admite presupuestos propios sin acoplarlos al viewport (1.270411ms)
|
||||||
|
@repo/rdx-ui:test: ✔ La pérdida de capacidad cierra sin abrir modal y los eventos repetidos no publican estado (1.20541ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selección visual: vacío con acciones separadas y ficha elegida sin controles de envío (21.35958ms)
|
||||||
|
@repo/rdx-ui:test: ✔ selección visual: pendiente, deshabilitado y bloqueo protegen las acciones correspondientes (7.883767ms)
|
||||||
|
@repo/rdx-ui:test: ✔ PickerList: búsqueda etiquetada, selección única y opciones indisponibles visibles (43.409765ms)
|
||||||
|
@repo/rdx-ui:test: ✔ PickerList: estados de carga y vacío no presentan opciones falsas (16.950842ms)
|
||||||
|
@repo/rdx-ui:test: ✔ el rango del selector usa la entidad y su plural, nunca el título de la acción (56.425375ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones multipaso: requisitos bloquean avance y guardado en cada paso (6.314353ms)
|
||||||
|
@repo/rdx-ui:test: ✔ acciones multipaso: guardar pendiente impide avanzar y retroceder (2.33052ms)
|
||||||
|
@repo/rdx-ui:test: ✔ línea temporal: conserva el orden aportado, fechas semánticas, autor y contenido (21.590039ms)
|
||||||
|
@repo/rdx-ui:test: ✔ línea temporal vacía: muestra el mensaje del consumidor como estado (0.534203ms)
|
||||||
|
@repo/rdx-ui:test: ℹ tests 84
|
||||||
|
@repo/rdx-ui:test: ℹ suites 0
|
||||||
|
@repo/rdx-ui:test: ℹ pass 84
|
||||||
|
@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 3253.419804
|
||||||
|
@metis/resources:test: cache bypass, force executing 72bdf6e922e78d3b
|
||||||
|
@metis/identity:test: cache bypass, force executing da6d5784f74ff7ef
|
||||||
|
@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 (1.382012ms)
|
||||||
|
@metis/resources:test: ✔ R2.6: preserva tildes, puntuación, abreviaturas, ediciones, versiones y números (0.956408ms)
|
||||||
|
@metis/resources:test: ✔ R2.6: homónimos genéricos se mantienen débiles y los UUID nunca se sustituyen (41.809851ms)
|
||||||
|
@metis/identity:test: ✔ resources.global.manage exige sesión vigente, flag persistido e identidad bootstrap (22.536289ms)
|
||||||
|
@metis/identity:test: ✔ el administrador inicial no se puede crear ni actualizar inactivo (5.807449ms)
|
||||||
|
@metis/identity:test: ✔ actualizar es idempotente, conserva identidad y cambiar contraseña invalida sesiones (1.490413ms)
|
||||||
|
@metis/identity:test: ✔ crear genera UUID v7 y rehidratar conserva el UUID original (1.283711ms)
|
||||||
|
@metis/identity:test: ✔ el mapper reconstruye Value Objects y conserva la identidad y valores persistidos (14.479722ms)
|
||||||
|
@metis/identity:test: ✔ una inconsistencia persistida falla sin generar un identificador sustituto ni filtrar datos (12.0213ms)
|
||||||
|
@metis/resources:test: ✔ F2: SO global público tipado, sin contaminación de aplicaciones ni perfiles (7.767365ms)
|
||||||
|
@metis/resources:test: ✔ F2: elegibilidad de SO exige stock real y campos globales (0.275903ms)
|
||||||
|
@metis/resources:test: ✔ F2: contrato 3 y gates de SO independientes; aplicaciones no abren SO (0.426803ms)
|
||||||
|
@metis/resources:test: ✔ F3: cualquier escritor valida identidad publicada, corrección documental e historial (37.822416ms)
|
||||||
|
@metis/resources:test: ✔ recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones (13.299011ms)
|
||||||
|
@metis/resources:test: ✔ recurso: valida identidades y rechaza actuar sobre recursos ajenos (3.912033ms)
|
||||||
|
@metis/resources:test: ✔ recurso: propaga el bloqueo por asociaciones y permite desactivar (1.812116ms)
|
||||||
|
@metis/resources:test: ✔ recurso: elimina y la repetición devuelve ausencia sin efectos adicionales (1.20321ms)
|
||||||
|
@metis/resources:test: ✔ elegibilidad global exige serie exacta y campos globales; las copias no elevan ámbito (2.521221ms)
|
||||||
|
@metis/resources:test: ✔ schemas discriminan owner, propósito, OS local y proyecciones privadas (10.887892ms)
|
||||||
|
@metis/resources:test: ✔ requests acotan lotes, consulta, paginación y rechazan privilegios o integración (5.87705ms)
|
||||||
|
@metis/resources:test: ✔ búsqueda literal conserva ñ, puntuación y espacios; pagina tras filtrar con desempate UUID (5.416145ms)
|
||||||
|
@metis/resources:test: ✔ R2.5 admite gates configurables; los valores predeterminados siguen cerrados (0.322903ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: software y perfil se habilitan por separado y requieren catálogo (0.319803ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: perfil no depende de software ni abre asociaciones con catálogo cerrado (0.404603ms)
|
||||||
|
@metis/resources:test: ✔ lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre (17.373246ms)
|
||||||
|
@metis/resources:test: ✔ HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial (356.289781ms)
|
||||||
|
@metis/resources:test: ✔ DNS: normalización explícita, etiqueta única heredada, punto raíz y política ASCII IDN (6.277653ms)
|
||||||
|
@metis/resources:test: ✔ capacidad estable: la plantilla renombrada sigue siendo dominio y una copia arbitraria no (1.619014ms)
|
||||||
|
@metis/resources:test: ✔ alta, edición, DNS y archivo usan Resource y conservan su identidad e historial (6.567455ms)
|
||||||
|
@metis/resources:test: ✔ catálogo: DNS obligatorio protegido, NetBIOS opcional e inicialización idempotente (5.443945ms)
|
||||||
|
@metis/resources:test: ✔ identificadores de campo: mayúsculas no cambian identidad ni permiten duplicados (1.19291ms)
|
||||||
|
@metis/resources:test: ✔ global: crear exige UUID v7 y conserva identidad y propietario en edición e idempotencia (16.422537ms)
|
||||||
|
@metis/resources:test: ✔ global: captura por stockKey, sin depender del nombre traducido de la plantilla (6.806357ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\NAS-01\Finanzas (2.025017ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\files.example.local\Docs (0.545104ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\192.168.5.10\C$ (0.166801ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\samba\Diseño\Año 2026 (0.351603ms)
|
||||||
|
@metis/resources:test: ✔ SMB acepta \\NAS\Docs\ (0.307903ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "" (0.248702ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\" (0.270702ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host" (0.133602ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\\\share" (1.17481ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\\\" (0.529905ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "C:\\Datos" (0.155102ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "https://host/share" (0.115201ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\.." (0.354103ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\." (0.1147ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\?\\UNC\\host\\share" (0.162701ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share\\\\child" (0.241902ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\share." (0.287503ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\ share" (0.094901ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\ho st\\share" (0.128801ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\u0000" (0.174402ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\bad\ud800" (0.234302ms)
|
||||||
|
@metis/resources:test: ✔ SMB rechaza "\\\\host\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (0.173902ms)
|
||||||
|
@metis/resources:test: ✔ SMB: comparación conservadora, alias y Unicode sin transformaciones destructivas (0.771007ms)
|
||||||
|
@metis/resources:test: ✔ SMB: semántica por stockKey y protección del catálogo sin bloquear etiquetas u obligatoriedad (23.960201ms)
|
||||||
|
@metis/resources:test: ✔ SMB: alta validada y diagnóstico histórico sin conversión ni revisión (18.189152ms)
|
||||||
|
@metis/resources:test: ✔ SMB: nuevas altas fijan integración access y rechazan contexto incompatible (13.216311ms)
|
||||||
|
@metis/resources:test: ✔ F1: identidad completa, edición conocida/no aplicable/desconocida e inactivos (6.170652ms)
|
||||||
|
@metis/resources:test: ✔ F1: normalización conservadora no equipara ediciones, releases ni ausencia desconocida (0.405003ms)
|
||||||
|
@metis/resources:test: ✔ F1: evolución de stock personalizada e idempotente; no elimina campos ni cambia obligatoriedad (68.17447ms)
|
||||||
|
@metis/resources:test: ✔ F1: un catálogo anterior evoluciona sin sobrescribir bindings ni recursos particulares (57.404181ms)
|
||||||
|
@metis/resources:test: ✔ campo PNG Icono: evoluciona tres plantillas sin reemplazar personalizaciones ni reponer bindings retirados (40.165236ms)
|
||||||
|
@metis/resources:test: ✔ catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección (39.625631ms)
|
||||||
|
@metis/resources:test: ✔ ámbitos, tipos, listas, campos externos y disponibilidad por compañía (9.351978ms)
|
||||||
|
@metis/resources:test: ✔ licencias: ampliar un catálogo previo conserva identidades y disponibilidad (21.382679ms)
|
||||||
|
@metis/resources:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (14.919225ms)
|
||||||
|
@metis/resources:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (10.355587ms)
|
||||||
|
@metis/resources:test: ✔ sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones (30.281553ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: todas las plantillas de serie tienen su icono y el arranque no repone los retirados (15.280628ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: cambiar y retirar el icono conserva identidad y asociaciones, con guardado idempotente (6.643756ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: claves desconocidas, cadenas vacías e iconos de campos se rechazan (5.600047ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: pares válidos y rechazo runtime de propietarios contradictorios o ausentes (19.105559ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: proyección preparada separada; HTTP local conserva exactamente su contrato (12.131401ms)
|
||||||
|
@metis/resources:test: ✔ ámbito: edición y propagación locales conservan propietario, UUID e historia; globales son solo lectura (4.580539ms)
|
||||||
|
@metis/resources:test: ✔ Resource: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (46.55159ms)
|
||||||
|
@metis/resources:test: ✔ Resource: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (2.003517ms)
|
||||||
|
@metis/resources:test: ✔ Resource: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.915508ms)
|
||||||
|
@metis/resources:test: ✔ recursos: tipos, valores opcionales y obligatorios, cero/falso y campos desconocidos (14.729523ms)
|
||||||
|
@metis/resources:test: ✔ recurso: identidad estable, historial, desactivación e idempotencia (2.807024ms)
|
||||||
|
@metis/resources:test: ✔ buzón como recurso: correo válido, cuenta opcional, cambios auditados y tipo fijo (16.77854ms)
|
||||||
|
@metis/resources:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (9.406179ms)
|
||||||
|
@metis/resources:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (3.368128ms)
|
||||||
|
@metis/resources:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (11.406895ms)
|
||||||
|
@metis/resources:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (32.051368ms)
|
||||||
|
@metis/resources:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (18.115052ms)
|
||||||
|
@metis/resources:test: ✔ lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope (20.129568ms)
|
||||||
|
@metis/resources:test: ✔ lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback (5.067842ms)
|
||||||
|
@metis/resources:test: ✔ proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección (96.472205ms)
|
||||||
|
@metis/resources:test: ✔ escritor scoped: envía company explícito, preserva la salida legacy y rechaza global (1.335411ms)
|
||||||
|
@metis/identity:test: ✔ scrypt usa sales distintas y rechaza contraseñas y hashes incorrectos (2006.595377ms)
|
||||||
|
@metis/identity:test: ✔ la sesión valida la respuesta y conserva el contrato público utilizado por la aplicación (42.98326ms)
|
||||||
|
@metis/identity:test: ✔ login, configuración inicial, usuarios y contraseñas conservan rutas, body y CSRF (7.114759ms)
|
||||||
|
@metis/identity:test: ✔ cerrar sesión espera la respuesta de la operación sin perder el token (3.389728ms)
|
||||||
|
@metis/identity:test: ✔ los errores HTTP no filtran detalles internos y los datos de sesión inválidos se rechazan (6.816557ms)
|
||||||
|
@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 2639.003782
|
||||||
|
@metis/resources:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (5.738338ms)
|
||||||
|
@metis/resources:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (1.380509ms)
|
||||||
|
@metis/resources:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (8.771057ms)
|
||||||
|
@metis/resources:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (91.952297ms)
|
||||||
|
@metis/resources:test: ✔ L04.2: campos de recursos recuperan orden y página sin heredar otra pestaña (106.265887ms)
|
||||||
|
@metis/resources:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (98.404237ms)
|
||||||
|
@metis/resources:test: ✔ los formularios tienen ayuda inferior y selección de campos en diálogo con alta rápida (29.327489ms)
|
||||||
|
@metis/resources:test: ✔ iconos de recursos: catálogo y selector traducido conservan la selección al editar y copiar (161.251243ms)
|
||||||
|
@metis/resources:test: ✔ catálogo global: lectura mínima y navegación traducidas, sin administración para ordinarios (33.038714ms)
|
||||||
|
@metis/resources:test: ✔ R2.4: mutación global invalida solo instalaciones cacheadas que usan su UUID (3.662724ms)
|
||||||
|
@metis/resources:test: ✔ R2.5: cambio global invalida configuraciones afectadas y preserva otras compañías (1.521609ms)
|
||||||
|
@metis/resources:test: ✔ SO global: lectura pública es/en conserva identidad estructurada y navegación propia (9.762463ms)
|
||||||
|
@metis/resources:test: ✔ SO global: invalida software y su catálogo sin refrescar aplicaciones ni perfiles (1.55681ms)
|
||||||
|
@metis/resources:test: ✔ columnas de características de recursos: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (23.807554ms)
|
||||||
|
@metis/resources:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (74.126881ms)
|
||||||
|
@metis/resources:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (3.729324ms)
|
||||||
|
@metis/resources:test: ✔ columnas generales e integración es/en: notas, fechas, autores y referencias semánticas (27.599878ms)
|
||||||
|
@metis/resources:test: ✔ icono de producto: identidad de serie, nombres personalizables y ausencia sin inferir otra imagen (0.838205ms)
|
||||||
|
@metis/resources:test: ✔ orden de recursos: aislamiento por compañía y pestaña, incluida Todo (14.528022ms)
|
||||||
|
@metis/resources:test: ✔ orden de recursos: una URL explícita prevalece sobre la preferencia sin sobrescribirla (0.885208ms)
|
||||||
|
@metis/resources:test: ✔ preferencias opcionales: datos inválidos y almacenamiento bloqueado no impiden listar (1.878716ms)
|
||||||
|
@metis/resources:test: ✔ cambiar pestaña limpia el orden y la paginación anteriores conservando la búsqueda (0.389803ms)
|
||||||
|
@metis/resources:test: ✔ la integración de buzón conserva su configuración explícita y no inventa referencias (1.447512ms)
|
||||||
|
@metis/resources:test: ✔ los valores iniciales toleran catálogo pendiente y limitan el nombre sin cambiar la entrada (0.994108ms)
|
||||||
|
@metis/resources:test: ✔ nombre propuesto: prioriza los identificadores de cada plantilla y limita su longitud (19.809765ms)
|
||||||
|
@metis/resources:test: ✔ nombre propuesto: contempla campos personalizados y descarta valores no identificativos (0.468503ms)
|
||||||
|
@metis/resources:test: ✔ paso de campos: exige valores obligatorios y acepta cero y respuestas negativas (1.834615ms)
|
||||||
|
@metis/resources:test: ✔ paso de campos: requiere la dirección de buzón aunque la plantilla la marque opcional (8.169368ms)
|
||||||
|
@metis/resources:test: ✔ recursos: listado con alta modal y sin formulario visible, en español e inglés (111.041919ms)
|
||||||
|
@metis/resources:test: ✔ Recursos: las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (311.398914ms)
|
||||||
|
@metis/resources:test: ✔ recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de recursos (15.260198ms)
|
||||||
|
@metis/resources:test: ✔ licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar (28.01248ms)
|
||||||
|
@metis/resources:test: ✔ asistencia: alta de recurso con plantilla específica y características opcionales (8.280053ms)
|
||||||
|
@metis/resources:test: ✔ alta por pasos: solo muestra la plantilla inicialmente y traduce su navegación (14.577594ms)
|
||||||
|
@metis/resources:test: ✔ indicador de alta: conserva la plantilla elegida y explica el paso actual en ambos idiomas (11.012271ms)
|
||||||
|
@metis/resources:test: ✔ recursos: resumen de plantilla con unidades, booleanos traducidos, cero y límite de cuatro (17.138311ms)
|
||||||
|
@metis/resources:test: ✔ recursos: ficha lateral compacta traducida con cierre, acciones y contenido relacionado (12.635481ms)
|
||||||
|
@metis/resources:test: ✔ recursos: inventario y panel usan el icono vigente de la plantilla sin cambiar la especificación (17.756314ms)
|
||||||
|
@metis/resources:test: ✔ dominio: el formulario traduce el contexto pendiente y solo presenta un entorno confirmado (12.951483ms)
|
||||||
|
@metis/resources:test: ✔ SMB: formulario explica acceso automático o histórico sin selectores incompatibles, es/en (8.430654ms)
|
||||||
|
@metis/resources:test: ✔ UNC: presentación por segmentos conserva exactamente separadores, Unicode y valores repetidos (5.939038ms)
|
||||||
|
@metis/resources:test: ✔ nombre de software: imagen compacta decorativa, texto accesible y ausencia sin base64 visible (11.054172ms)
|
||||||
|
@metis/resources:test: ✔ campo declarativo: traduce opciones propias y conserva valores que coinciden con el prototipo (27.439578ms)
|
||||||
|
@metis/resources:test: ℹ tests 128
|
||||||
|
@metis/resources:test: ℹ suites 0
|
||||||
|
@metis/resources:test: ℹ pass 128
|
||||||
|
@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 5004.02204
|
||||||
|
@metis/assets:test: cache bypass, force executing aa42a6d02368829e
|
||||||
|
@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 (7.82925ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo inicial es idempotente, con UUID propios y características opcionales (71.52736ms)
|
||||||
|
@metis/assets:test: ✔ la disponibilidad por compañía conserva el global y los campos desactivados (49.475118ms)
|
||||||
|
@metis/assets:test: ✔ las plantillas validan ámbito, campos activos e identidad estable al editar (41.96797ms)
|
||||||
|
@metis/assets:test: ✔ los campos validan listas, igualdad semántica, duplicados y tipo fijo (2.141413ms)
|
||||||
|
@metis/assets:test: ✔ solo elimina definiciones globales personalizadas que no usan otras plantillas (2.169814ms)
|
||||||
|
@metis/assets:test: ✔ iconos opcionales: cambio y retirada conservan identidad y asociaciones (26.350469ms)
|
||||||
|
@metis/assets:test: ✔ el arranque conserva un icono de serie retirado sin cambiar UUID, campos o disponibilidad (61.798697ms)
|
||||||
|
@metis/assets:test: ✔ los iconos desconocidos o asociados a campos se rechazan (5.835438ms)
|
||||||
|
@metis/assets:test: ✔ revisión explícita conserva identidades, copias y otras plantillas (28.670884ms)
|
||||||
|
@metis/assets:test: ✔ plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar (46.689ms)
|
||||||
|
@metis/assets:test: ✔ campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas (44.104483ms)
|
||||||
|
@metis/assets:test: ✔ Las relaciones validan sus extremos, normalizan nombres y distinguen dirección (10.804469ms)
|
||||||
|
@metis/assets:test: ✔ Retirar conserva UUID, extremos y auditoría; repetir la retirada no modifica el historial (2.369815ms)
|
||||||
|
@metis/assets:test: ✔ Tipos de relación de serie protegidos, ámbito de compañía y dirección fija (94.113304ms)
|
||||||
|
@metis/assets:test: ✔ Asset: propaga altas, orden y retiradas conservando cero/falso, valores e historial; repetir no genera revisiones (33.015612ms)
|
||||||
|
@metis/assets:test: ✔ Asset: los campos retirados no impiden añadir una plantilla completa y no se fusionan por nombre (4.66423ms)
|
||||||
|
@metis/assets:test: ✔ Asset: retirar secciones conserva solo las necesarias y mover o reincorporar campos aplica el destino (0.836205ms)
|
||||||
|
@metis/assets:test: ✔ las secciones conservadas desambiguan el nombre respetando el límite de 100 caracteres (0.572404ms)
|
||||||
|
@metis/assets:test: ✔ Características opcionales, tipos y límites; cero y falso son valores presentes (12.619081ms)
|
||||||
|
@metis/assets:test: ✔ Activo conserva UUID y revisiones; los cambios iguales son idempotentes (1.761611ms)
|
||||||
|
@metis/assets:test: ✔ texto largo conserva saltos de línea y rechaza tipos no textuales (0.744805ms)
|
||||||
|
@metis/assets:test: ✔ estado técnico y uso: asignaciones calculadas, elegibilidad exclusiva/compartida e historial conservado (0.886706ms)
|
||||||
|
@metis/assets:test: ✔ Fecha de alta opcional: calendario real, independencia de compra, revisiones e idempotencia (3.559922ms)
|
||||||
|
@metis/assets:test: ✔ sedes archivadas: nuevas referencias rechazadas y referencias anteriores conservadas (8.001451ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: nombre normalizado, UUID v7, protección de serie, estado e idempotencia (5.338234ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: SVG estáticos con transparencia, límites y contenido activo rechazado (70.309852ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: los 23 fabricantes tienen SVG local validado; Clónico no representa una marca (23.312049ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: añadir, sustituir y quitar SVG conserva identidad, origen y actualización idempotente (7.523948ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: mapper bidireccional conserva identidad y rechaza persistencia inválida (6.502942ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: unicidad, edición y eliminación de personalizadas, con protección de serie (20.517232ms)
|
||||||
|
@metis/assets:test: ✔ pertenencia explícita: un rol, identidad UUID v7 e idempotencia sin nuevas revisiones (2.892119ms)
|
||||||
|
@metis/assets:test: ✔ promoción y retirada del rol conservan asociación e historial; finalización repetida es inocua (1.898312ms)
|
||||||
|
@metis/assets:test: ✔ cambio de dominio crea sucesor único y cierre atómico planificado; admite reintentos y rechaza conflictos (0.984907ms)
|
||||||
|
@metis/assets:test: ✔ capacidad, estado y compañía: rechaza extremos incompatibles sin inferencias de nombre (0.559904ms)
|
||||||
|
@metis/assets:test: ✔ activos o dominios inactivos conservan vínculos y permiten cerrar o retirar el rol (1.160708ms)
|
||||||
|
@metis/assets:test: ✔ mapper: rehidratación consistente, UUID conservados y rechazo de revisiones corruptas (1.48671ms)
|
||||||
|
@metis/assets:test: ✔ contratos estrictos: roles, paginación, UUID, acciones y privilegios arbitrarios (5.006032ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: interfaces independientes, varias IP, normalización y UUID v7 (12.534081ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: mismo contenido no cambia identidades ni añade revisiones (4.860931ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: rechaza nombres/IP duplicados, valores inválidos e identidades ajenas (16.540207ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: límites y direcciones repetidas entre interfaces o compañías permitidas (33.305814ms)
|
||||||
|
@metis/assets:test: ✔ publicación: alta, periodo, cierre idempotente e identidad persistida (17.632113ms)
|
||||||
|
@metis/assets:test: ✔ publicación: cardinalidad y reintento idéntico no crean periodos (0.937306ms)
|
||||||
|
@metis/assets:test: ✔ publicación: traslado explícito y replay estable incluso tras finalizar sucesor (1.48251ms)
|
||||||
|
@metis/assets:test: ✔ publicación: estados, recurso no SMB, identidad y fechas inválidas (1.121607ms)
|
||||||
|
@metis/assets:test: ✔ publicación: definición compatible con contratos comunes sin rol ni proveedor network (8.390454ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: UUID v7, historial, cambios semánticos y fechas (5.455835ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: preferido disponible y entrada estricta (2.345515ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: varios métodos, duplicados, preferido único y activo retirado (2.213514ms)
|
||||||
|
@metis/assets:test: ✔ herramientas: protección de serie y desactivación sin perder referencias (1.45371ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: mapper bidireccional y rechazo de historial inconsistente (10.291866ms)
|
||||||
|
@metis/assets:test: ✔ secciones: nombres, identidades, referencias y un solo nivel (9.968964ms)
|
||||||
|
@metis/assets:test: ✔ secciones: igualdad, orden, identidad y persistencia JSON (3.11922ms)
|
||||||
|
@metis/assets:test: ✔ secciones: plantillas globales y locales conservan separación y UUID al editar (6.341141ms)
|
||||||
|
@metis/assets:test: ✔ secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad (118.186459ms)
|
||||||
|
@metis/assets:test: ✔ especificación: secciones capturadas, edición posterior y obligatoriedad independiente (60.64799ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: composición opcional, referencias válidas y límites sin duplicados (7.811376ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: UUID estable e historial de composición, persona y estado; guardado idéntico idempotente (8.340182ms)
|
||||||
|
@metis/assets:test: ✔ Puestos: ordenador principal exclusivo, único e independiente del responsable (1.365813ms)
|
||||||
|
@metis/assets:test: ✔ baja del puesto: libera composición y responsable, conserva historial e identidad y es idempotente (3.357033ms)
|
||||||
|
@metis/assets:test: ✔ definición real: proveedor, cardinalidad vigente, roles e inmutabilidad (3.895739ms)
|
||||||
|
@metis/assets:test: ✔ cursor firmado: vinculación a consulta y operador, sin aceptar UUID libres ni alteraciones (3.138531ms)
|
||||||
|
@metis/assets:test: ✔ extensión de Assets validada: roles y estados cerrados, UUID original y ámbito (6.259562ms)
|
||||||
|
@metis/assets:test: ✔ ficha de activo es/en: página con dos pestañas, información actual, relaciones y contenido de compañía (231.40454ms)
|
||||||
|
@metis/assets:test: ✔ historial del activo conserva instantáneas antiguas, interfaces retiradas y conexiones en ambos sentidos, ordenadas por fecha (30.653904ms)
|
||||||
|
@metis/assets:test: ✔ ficha de activo distingue carga, error y activo inexistente (37.927153ms)
|
||||||
|
@metis/assets:test: ✔ acciones: crear solo para portátiles, sobremesa y servidores disponibles (3.508124ms)
|
||||||
|
@metis/assets:test: ✔ acciones: abrir el puesto asociado aunque el activo sea periférico, no operativo o el puesto esté inactivo (1.297708ms)
|
||||||
|
@metis/assets:test: ✔ acciones: compañía y plantilla correctas; asociaciones solo históricas no bloquean el alta (0.859005ms)
|
||||||
|
@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 (28.235988ms)
|
||||||
|
@metis/assets:test: ✔ inventario es/en: crear y ver puesto son acciones visibles fuera del menú (213.113818ms)
|
||||||
|
@metis/assets:test: ✔ la acción de selección conserva su texto visible como nombre accesible en es/en (51.343844ms)
|
||||||
|
@metis/assets:test: ✔ selector: comparte búsqueda, pestañas por identidad y columnas del inventario en es/en (286.197218ms)
|
||||||
|
@metis/assets:test: ✔ selector: filtra conjuntamente por plantilla, nombre, sede y estado; distingue vacío (171.858351ms)
|
||||||
|
@metis/assets:test: ✔ selector único: controles radio y disparador de diálogo; conserva el alta rápida y el bloqueo (107.987221ms)
|
||||||
|
@metis/assets:test: ✔ picker: búsqueda rápida disponible durante carga y error, sin anunciar un falso vacío (201.409345ms)
|
||||||
|
@metis/assets:test: ✔ inventario es/en: icono compacto antes del nombre, plantilla solo textual y ordenación por nombre (82.34635ms)
|
||||||
|
@metis/assets:test: ✔ columnas de características: identidad, campos nuevos de catálogo, instantáneas y nombres repetidos (27.823787ms)
|
||||||
|
@metis/assets:test: ✔ columnas elegidas es/en: cero, falso, fecha localizada, unidades y guion para campos ajenos (284.019903ms)
|
||||||
|
@metis/assets:test: ✔ columnas generales y de red es/en: fechas y autores propios, IP/MAC por interfaz y ausencia real (57.960789ms)
|
||||||
|
@metis/assets:test: ✔ los campos compartidos conservan un único ID y todos sus contextos de catálogo e instantánea (3.110821ms)
|
||||||
|
@metis/assets:test: ✔ La capacidad del inventario protege el presupuesto de tabla en el límite real (1.763712ms)
|
||||||
|
@metis/assets:test: ✔ El resize conserva el contexto abierto al ganar capacidad y nunca abre un modal (1.019207ms)
|
||||||
|
@metis/assets:test: ✔ Inventario traducido con alta de cabecera y sin formulario junto al listado (235.264766ms)
|
||||||
|
@metis/assets:test: ✔ Las pestañas incluyen plantillas vacías e inactivas y combinan identidad y búsqueda (469.228319ms)
|
||||||
|
@metis/assets:test: ✔ El activo ofrece altas relacionadas, ayuda inferior y no encadena una tercera alta (38.252354ms)
|
||||||
|
@metis/assets:test: ✔ Las fichas leen ambos sentidos y conservan la auditoría de relaciones retiradas (36.13474ms)
|
||||||
|
@metis/assets:test: ✔ El formulario de tipo de relación presenta ayuda justo antes de sus operaciones (26.831277ms)
|
||||||
|
@metis/assets:test: ✔ Puestos de trabajo: listado traducido, alta en cabecera y formulario modal (32.910218ms)
|
||||||
|
@metis/assets:test: ✔ Puestos de trabajo: altas rápidas y ayuda inferior; un activo anidado no ofrece terceras altas (26.255574ms)
|
||||||
|
@metis/assets:test: ✔ alta de puesto: sede única automática, responsable opcional y cuatro pasos traducidos (29.377194ms)
|
||||||
|
@metis/assets:test: ✔ la ficha lateral agrupa información y ofrece acciones superiores y enlaces reales (99.284958ms)
|
||||||
|
@metis/assets:test: ✔ texto largo: área editable con ayuda accesible (7.384349ms)
|
||||||
|
@metis/assets:test: ✔ las características marcan solo los campos obligatorios y conservan unidad y ayuda (14.376895ms)
|
||||||
|
@metis/assets:test: ✔ el resumen usa los campos propios de la plantilla, omite vacíos y texto largo y limita a cuatro (9.600864ms)
|
||||||
|
@metis/assets:test: ✔ alta de activo por pasos: empieza por plantilla, traduce la navegación y bloquea el avance sin selección (36.917845ms)
|
||||||
|
@metis/assets:test: ✔ el progreso de activos conserva la plantilla y explica los pasos en es/en (27.06438ms)
|
||||||
|
@metis/assets:test: ✔ las acciones por pasos solo ofrecen guardar al final y bloquean acciones durante el envío (51.959545ms)
|
||||||
|
@metis/assets:test: ✔ características obligatorias: rechaza ausentes y espacios y conserva cero y falso (0.357702ms)
|
||||||
|
@metis/assets:test: ✔ nombre propuesto: usa texto de la plantilla, omite marca y vacíos y respeta el límite (8.358755ms)
|
||||||
|
@metis/assets:test: ✔ diseño del puesto: grupos compactos, columnas por contenedor y resumen sin textos de ausencia (122.02611ms)
|
||||||
|
@metis/assets:test: ✔ activos del puesto: estado vacío visual traducido y acciones según disponibilidad (78.722622ms)
|
||||||
|
@metis/assets:test: ✔ diseño del puesto: navegación traducida y acciones bloqueadas durante el guardado (67.793049ms)
|
||||||
|
@metis/assets:test: ✔ ficha compacta de activo: comparte información, traducciones y acciones sin consultas propias (98.295252ms)
|
||||||
|
@metis/assets:test: ✔ ordenador principal: tarjetas con selección única, etiquetas es/en y solo los activos del puesto (64.684029ms)
|
||||||
|
@metis/assets:test: ✔ puestos: URL controla consulta, orden y página y sus enlaces conservan el contexto (47.499814ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: listado común traducido, filtros y alta modal sin formulario junto a la tabla (95.727637ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: nombre de serie protegido y ayuda inferior; alta rápida siempre activa (16.389109ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: eliminar envía CSRF y cuerpo vacío, valida la respuesta y propaga conflictos (39.100361ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: selector mantiene valores antiguos e inactivos y respeta el límite de alta anidada (21.215141ms)
|
||||||
|
@metis/assets:test: ✔ Marcas: el campo de serie se identifica por referencia de catálogo, no por el nombre (56.865478ms)
|
||||||
|
@metis/assets:test: ✔ selección múltiple añade un lote sin duplicados y conserva las reglas y el orden existentes (4.952433ms)
|
||||||
|
@metis/assets:test: ✔ añadir desde una sección asigna solo los campos nuevos a su destino y conserva los existentes (3.318822ms)
|
||||||
|
@metis/assets:test: ✔ la selección respeta el límite del schema, valida identificadores y rechaza el conjunto vacío (7.199848ms)
|
||||||
|
@metis/assets:test: ✔ el listado reutiliza tabla, búsqueda y casillas traducidas y conserva las selecciones bloqueadas (269.377105ms)
|
||||||
|
@metis/assets:test: ✔ L04.2: campos del catálogo recuperan orden y página desde su URL (313.793098ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo muestra plantillas de serie y acción de alta sin formularios junto al listado (341.53858ms)
|
||||||
|
@metis/assets:test: ✔ el catálogo busca nombres sin acentos sin cambiar la consulta ni incluir campos de otras pestañas (166.801614ms)
|
||||||
|
@metis/assets:test: ✔ los formularios tienen ayuda inferior y reservan el alta rápida al diálogo de selección de campos (87.991287ms)
|
||||||
|
@metis/assets:test: ✔ las plantillas ofrecen selección visual traducida y conservan el icono al copiar (434.601697ms)
|
||||||
|
@metis/assets:test: ✔ dominio: roles estructurados, resumen solo de página y enlaces UUID, es/en (68.480756ms)
|
||||||
|
@metis/assets:test: ✔ tablas: miembro y finalización no se confunden con controlador o pertenencia vigente (15.902306ms)
|
||||||
|
@metis/assets:test: ✔ activo sin dominio: vacío explícito y asociación solo si el equipo es compatible (21.01304ms)
|
||||||
|
@metis/assets:test: ✔ activo unido: dominio, rol real, navegación y acciones especializadas (24.793265ms)
|
||||||
|
@metis/assets:test: ✔ carga y error de consulta no se muestran como dominio vacío (40.62397ms)
|
||||||
|
@metis/assets:test: ✔ API de lectura: conserva cursor opaco, tipo y ámbito; propaga errores funcionales (52.235247ms)
|
||||||
|
@metis/assets:test: ✔ traslado usa un único comando atómico, UUID esperado y CSRF; conflicto no se oculta (7.841852ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: clasificación de serie independiente del nombre/icono y selección manual prioritaria (2.295516ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: el total incluye roles e interfaces, respeta exclusiones manuales y no duplica activos (0.509204ms)
|
||||||
|
@metis/assets:test: ✔ infraestructura: listado y ficha presentan interfaces/IP en español e inglés, sin puertos (249.296761ms)
|
||||||
|
@metis/assets:test: ✔ SMB: relaciones paginadas, UNC Unicode y enlaces bidireccionales es/en (44.462196ms)
|
||||||
|
@metis/assets:test: ✔ SMB: historial conserva periodos y actores y no ofrece modificar periodos finalizados (16.189307ms)
|
||||||
|
@metis/assets:test: ✔ SMB: vacío real y error de lectura son distintos (14.817898ms)
|
||||||
|
@metis/assets:test: ✔ SMB: denegación oculta datos previos y carga inicial conserva cabeceras (16.55351ms)
|
||||||
|
@metis/assets:test: ✔ SMB: transferencia conserva UUID y envía un único comando propietario con CSRF (27.711784ms)
|
||||||
|
@metis/assets:test: ✔ SMB: búsqueda remota y cursor opaco viajan al servidor (2.002614ms)
|
||||||
|
@metis/assets:test: ✔ ficha SMB: publicador destacado, datos y timeline real es/en; solo lectura conserva navegación (42.00668ms)
|
||||||
|
@metis/assets:test: ✔ ficha SMB: vacío, finalizada, carga y denegación son estados distintos (19.337729ms)
|
||||||
|
@metis/assets:test: ✔ timeline SMB: solo hechos persistidos, UUID estable, traslado y cierre sin sucesor en página (0.766505ms)
|
||||||
|
@metis/assets:test: ✔ historial v2: utiliza únicamente nombres autorizados aportados y conserva UUID técnicos (15.439803ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: formulario con alta rápida y ayuda antes de guardar (49.722629ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: resumen excluye retirados, ordena preferido y conserva la gestión original (25.106766ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: traducciones y resumen de método (2.834319ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: resumen documental muestra destino, VPN y preferencia sin acciones de gestión (4.004027ms)
|
||||||
|
@metis/assets:test: ✔ asistencia: referencia no resuelta conserva el registro con ausencia y explicación (5.95884ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: usa valores vigentes, normaliza el nombre, valida respuesta y envía CSRF (45.570504ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: rechaza vacíos, espacios y exceso de longitud antes de realizar peticiones (5.183534ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: un nombre equivalente no provoca una escritura ni otra revisión (1.218408ms)
|
||||||
|
@metis/assets:test: ✔ renombrar: propaga puesto ausente y errores del servidor sin escritura posterior (5.814639ms)
|
||||||
|
@metis/assets:test: ✔ WD04.4: comparte reglas de disponibilidad, retención, ocupación y puesto inactivo (0.946306ms)
|
||||||
|
@metis/assets:test: ✔ alta: sede necesaria, responsable opcional, composición mínima y ordenador perteneciente al puesto (0.906406ms)
|
||||||
|
@metis/assets:test: ✔ composición: asigna el activo único, conserva una elección válida y limpia el ordenador retirado (0.208301ms)
|
||||||
|
@metis/assets:test: ✔ propone exactamente el nombre del activo principal, sin depender de la sede (1.166608ms)
|
||||||
|
@metis/assets:test: ✔ no propone nombre cuando falta el activo principal (0.169002ms)
|
||||||
|
@metis/assets:test: ℹ tests 155
|
||||||
|
@metis/assets:test: ℹ suites 0
|
||||||
|
@metis/assets:test: ℹ pass 155
|
||||||
|
@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 8917.877219
|
||||||
|
@metis/companies:test: cache bypass, force executing b3c337cf5e4acffd
|
||||||
|
@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 (10.512881ms)
|
||||||
|
@metis/companies:test: ✔ clasificación independiente del nombre; no se adopta un sistema existente por llamarse AD (3.439027ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan compañías distintas, referencias inexistentes y entornos sin clasificación (1.22351ms)
|
||||||
|
@metis/companies:test: ✔ DNS repetido no crea dos entornos; otro dominio comparte sistema y obtiene un entorno distinto (4.287433ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: vínculo por UUID, alta y retirada idempotentes con identidad e historial protegidos (14.078209ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: clasificación explícita rechaza AD por sistema o entorno, sin comparar etiquetas (1.753713ms)
|
||||||
|
@metis/companies:test: ✔ identidad local: crear entorno utiliza AccessWorkspace y conserva cuentas y sistema existentes (2.275218ms)
|
||||||
|
@metis/companies:test: ✔ catálogos globales no se duplican con adiciones locales y no se admiten grupos anidados (9.269572ms)
|
||||||
|
@metis/companies:test: ✔ renombrar y cambiar el uso de una cuenta conserva UUID, historial y semántica de repetición (5.004838ms)
|
||||||
|
@metis/companies:test: ✔ el uso puede cambiar entre todas las opciones sin relaciones y repetirlo no añade revisiones (11.969292ms)
|
||||||
|
@metis/companies:test: ✔ pasar de compartida a personal rechaza titulares solapados y admite periodos consecutivos (4.139332ms)
|
||||||
|
@metis/companies:test: ✔ el cambio entre uso técnico y acceso no reinterpreta relaciones actuales ni históricas (8.173263ms)
|
||||||
|
@metis/companies:test: ✔ personal y compartida permiten cambiar el uso conservando la relación con una persona (4.188933ms)
|
||||||
|
@metis/companies:test: ✔ cerrar una autorización configurada deja retirada pendiente y cerrar una pendiente no inventa un acceso (4.804737ms)
|
||||||
|
@metis/companies:test: ✔ titulares no se solapan incluso en periodos cerrados; el relevo conserva una retirada anterior (1.810414ms)
|
||||||
|
@metis/companies:test: ✔ responsabilidad técnica no concede acceso y se permite sobre una cuenta caducada o inactiva (5.367941ms)
|
||||||
|
@metis/companies:test: ✔ valores reconstruyen UUID y fechas, rechazan fechas imposibles y no permiten credenciales (5.868646ms)
|
||||||
|
@metis/companies:test: ✔ las referencias UUID se normalizan y no permiten duplicar cuentas variando mayúsculas (1.000207ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: contrato estricto, versión opcional y solo instalada (8.602266ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: UUID, idempotencia, versión auditada, destino fijo y retirada (3.87443ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (1.843215ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: inactividad conserva notas y permite retirada, impide reconfigurar (1.602613ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: persistencia preserva historial y rechaza claves y orden incoherentes (9.050869ms)
|
||||||
|
@metis/companies:test: ✔ R2.5: global conserva perfil, delivery e historial; disponibilidad distinta de lectura histórica (8.953169ms)
|
||||||
|
@metis/companies:test: ✔ certificados: contrato estricto, ubicación opcional y límites (12.344095ms)
|
||||||
|
@metis/companies:test: ✔ certificados: UUID, idempotencia, ubicación auditada, destino fijo y retirada (4.061731ms)
|
||||||
|
@metis/companies:test: ✔ certificados: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva (1.991615ms)
|
||||||
|
@metis/companies:test: ✔ certificados: inactividad conserva notas y permite retirada, impide reconfigurar (1.393411ms)
|
||||||
|
@metis/companies:test: ✔ certificados: persistencia preserva historial y rechaza claves y orden incoherentes (12.487097ms)
|
||||||
|
@metis/companies:test: ✔ certificados: caducidad inclusiva, revisión y retirada sin perder historial (1.435511ms)
|
||||||
|
@metis/companies:test: ✔ compañía, contacto y sede tienen UUID v7 propios y se conserva la identidad al editar (8.864269ms)
|
||||||
|
@metis/companies:test: ✔ la pertenencia impide asociar o editar entidades de otra compañía (3.962331ms)
|
||||||
|
@metis/companies:test: ✔ la dirección y el teléfono son optativos y se pueden borrar (0.798606ms)
|
||||||
|
@metis/companies:test: ✔ nombres, email, teléfono y longitud de dirección se validan en los Value Objects (1.718213ms)
|
||||||
|
@metis/companies:test: ✔ sedes: archivar y reactivar conserva identidad y datos; repetir el estado es idempotente (0.788206ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: cuenta desconocida, cuenta alternativa y cobertura independiente de la necesidad (74.644375ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: ausencia conserva cuenta, null la retira y cada cambio real crea revisión (19.52725ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: rechaza cuenta ausente, otro subtipo, otra persona, caducada o desactivada (17.875937ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: Coverage por grupo exige pertenencia directa de la cuenta y configuración vigente (32.785753ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: cobertura derivada cambia al retirar concesión, necesidad, cuenta o asociación personal (53.955416ms)
|
||||||
|
@metis/companies:test: ✔ FolderMapping: todas las autorizaciones necesarias deben proceder de la cuenta seleccionada (11.293887ms)
|
||||||
|
@metis/companies:test: ✔ Correo: UUIDv7, normalización, no-op, historial y retirada explícita (11.787791ms)
|
||||||
|
@metis/companies:test: ✔ Correo: buzón independiente, vigencia, perfiles, varios buzones y duplicados abiertos (28.654121ms)
|
||||||
|
@metis/companies:test: ✔ Correo: persistencia conserva identidad e historial y rechaza corrupción (17.859337ms)
|
||||||
|
@metis/companies:test: ✔ Correo: vinculación explícita del legado conserva UUID e historial, sin inferir propiedad (20.102854ms)
|
||||||
|
@metis/companies:test: ✔ Buzón: identidad, dirección única, cuenta opcional, ámbito y cambios auditados (7.654959ms)
|
||||||
|
@metis/companies:test: ✔ Uso de buzón: fechas inclusiva/exclusiva, mismo día, solapes, varias personas e historial (14.050807ms)
|
||||||
|
@metis/companies:test: ✔ Buzones: mapper conserva identidad y revisiones, rechaza corrupción (20.680559ms)
|
||||||
|
@metis/companies:test: ✔ personas con datos opcionales no requieren cuenta de acceso y conservan su UUID (7.407657ms)
|
||||||
|
@metis/companies:test: ✔ departamentos únicos por compañía, ignorando mayúsculas y espacios exteriores (1.150809ms)
|
||||||
|
@metis/companies:test: ✔ varios departamentos, un único principal y sin periodos solapados (4.774337ms)
|
||||||
|
@metis/companies:test: ✔ ediciones conservan revisiones y una repetición semántica no añade historial (9.743575ms)
|
||||||
|
@metis/companies:test: ✔ inactivos no reciben nuevas asignaciones; se conservan y pueden cerrarse las existentes (0.895607ms)
|
||||||
|
@metis/companies:test: ✔ asignaciones rechazan pertenencias cruzadas y fechas inválidas (1.27701ms)
|
||||||
|
@metis/companies:test: ✔ la persistencia rechaza periodos inválidos y revisiones que no reflejan el estado actual (1.996715ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: contratos estrictos, notas opcionales y predeterminada solo vigente (15.970223ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: UUID, historial, igualdad semántica, destino fijo y retirada terminal (7.77146ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: duplicados por perfil, predeterminada única y destinos del ámbito (1.675812ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: indisponibilidad señala revisión y permite notas y retirada (4.907737ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: rehidratación conserva identidades y detecta corrupción (16.643328ms)
|
||||||
|
@metis/companies:test: ✔ procesos: contratos estrictos, normalización y límites (7.566658ms)
|
||||||
|
@metis/companies:test: ✔ procesos: UUID v7, tareas con identidad, actualización idempotente e historial (3.122524ms)
|
||||||
|
@metis/companies:test: ✔ procesos: completar exige todas resueltas; cierre terminal y sujeto/tareas fijos (4.466634ms)
|
||||||
|
@metis/companies:test: ✔ procesos: añadir revisiones conserva las resueltas e identidades y no duplica (2.312317ms)
|
||||||
|
@metis/companies:test: ✔ plantillas: copia de instrucciones, igualdad semántica y cambios independientes (1.579512ms)
|
||||||
|
@metis/companies:test: ✔ procesos: rehidratación bidireccional y rechazo de raíces/historial alterados (6.996654ms)
|
||||||
|
@metis/companies:test: ✔ revisión: personales, retirada pendiente y relaciones finalizadas aún configuradas (0.817407ms)
|
||||||
|
@metis/companies:test: ✔ revisión: incluye configuraciones de perfiles archivados, excluye cerradas y evita duplicar (0.316903ms)
|
||||||
|
@metis/companies:test: ✔ remotos: origen personal sin detalle, destinos tipados y contrato estricto (6.629951ms)
|
||||||
|
@metis/companies:test: ✔ remotos: UUID e historial, destino fijo, idempotencia y cierre terminal (7.872261ms)
|
||||||
|
@metis/companies:test: ✔ remotos: referencias de compañía y duplicados abiertos por recorrido y herramienta (3.785529ms)
|
||||||
|
@metis/companies:test: ✔ remotos: configuración y autorización independientes; caducidad y revocación no borran acceso (4.650736ms)
|
||||||
|
@metis/companies:test: ✔ remotos: disponibilidad de origen y destino, retirada y método personalizado (2.962822ms)
|
||||||
|
@metis/companies:test: ✔ remotos: persistencia conserva auditoría y detecta referencias, claves e historial corruptos (15.996322ms)
|
||||||
|
@metis/companies:test: ✔ remotos: una sede archivada conserva conexiones existentes pero rechaza nuevas (1.153209ms)
|
||||||
|
@metis/companies:test: ✔ instalaciones: pertenecen al equipo, admiten SO/aplicaciones y rechazan recursos ajenos y destinos variables (8.528966ms)
|
||||||
|
@metis/companies:test: ✔ instalaciones: versiones, idempotencia, retirada, revisión y persistencia de historial (13.819606ms)
|
||||||
|
@metis/companies:test: ✔ licencia del software: opcional, misma compañía y tipo, vigencia, desvinculación e historial idempotente (12.110493ms)
|
||||||
|
@metis/companies:test: ✔ R2.4: global activo e inactivo separan alta, reconfiguración y recuperación histórica (1.930115ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: ausencia, límites, fechas reales y contrato estricto (7.385957ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: UUID, renovación y revocación auditadas, igualdad y destino fijo (4.414134ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: vigencia inclusiva, programación, caducidad y persona inactiva (4.012631ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: ámbito, unicidad incluso revocada, disponibilidad y retirada (6.294449ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: rehidratación preserva identidad y detecta corrupción de raíz e historial (37.840391ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: cuenta opcional, UUID, historial, identidad fija e idempotencia (10.951073ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: desactivar conserva valores e historial, es idempotente y precede al borrado (2.399616ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: misma persona/dispositivo único vigente, múltiples dispositivos/personas y revisión sin pérdida de datos (1.63541ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: cuenta autorizada, vigencia inclusiva de caducidad, cierre de autorización y cuenta técnica (1.50281ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: persistencia conserva revisiones y rechaza raíz o cuenta histórica incoherentes (8.568257ms)
|
||||||
|
@metis/companies:test: ✔ Perfil: solo un ordenador principal activo admite altas y reactivaciones; el historial se conserva (0.919206ms)
|
||||||
|
@metis/companies:test: ✔ PNG, JPEG y WebP se normalizan sin recorte y sin metadatos a un avatar ligero (256.040561ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan archivos vacíos, corruptos, SVG y GIF aunque simulen un MIME válido (7.79066ms)
|
||||||
|
@metis/companies:test: ✔ se rechazan excesos de peso y dimensión antes de almacenar (11.467488ms)
|
||||||
|
@metis/companies:test: ✔ los PNG animados se rechazan antes de decodificarlos (6.942753ms)
|
||||||
|
@metis/companies:test: ✔ el contexto distingue cuentas y grupos del entorno, sin incluir otros entornos o datos globales (5.683444ms)
|
||||||
|
@metis/companies:test: ✔ recurso contextual: incluye identidades de sistemas distintos sin mezclar necesidades y concesiones ajenas (14.220509ms)
|
||||||
|
@metis/companies:test: ✔ filtro vacío no incluye elementos ajenos y las rutas validan UUID y contexto de compañía (4.394233ms)
|
||||||
|
@metis/companies:test: ✔ los catálogos presentan alta mediante acción y no un formulario junto al listado (144.411842ms)
|
||||||
|
@metis/companies:test: ✔ cuentas ofrecen caducidad opcional y campos sin contraseña (40.196263ms)
|
||||||
|
@metis/companies:test: ✔ la ayuda explica todos los elementos en ambos idiomas y distingue el ámbito (90.319489ms)
|
||||||
|
@metis/companies:test: ✔ los ejemplos del sistema orientan sin rellenar datos del alta (11.522975ms)
|
||||||
|
@metis/companies:test: ✔ las altas rápidas respetan el contexto, ámbito y relaciones históricas (2.908519ms)
|
||||||
|
@metis/companies:test: ✔ los formularios ofrecen altas relacionadas y el segundo modal evita altas anidadas (207.483654ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: listado sin formularios, alta en cabecera y traducciones (197.965508ms)
|
||||||
|
@metis/companies:test: ✔ aplicaciones: selectores con alta rápida y ayuda inferior antes de guardar (72.991793ms)
|
||||||
|
@metis/companies:test: ✔ R2.5: perfil muestra nombre, origen y versión global sin ResourceDTO local ni mezcla de perfiles (118.317861ms)
|
||||||
|
@metis/companies:test: ✔ certificados: listado sin formularios, alta en cabecera y traducciones (248.775816ms)
|
||||||
|
@metis/companies:test: ✔ certificados: selectores con alta rápida y ayuda inferior antes de guardar (76.648419ms)
|
||||||
|
@metis/companies:test: ✔ la compañía de origen es ámbito visible, no un filtro eliminable con Limpiar filtros (408.875863ms)
|
||||||
|
@metis/companies:test: ✔ directorio: orden y página proceden de URL sin perder la consulta (97.610335ms)
|
||||||
|
@metis/companies:test: ✔ las subentradas de Personas conservan contexto y reemplazan el filtro de la vista (62.643507ms)
|
||||||
|
@metis/companies:test: ✔ el dashboard no ofrece selector de compañía y conserva las métricas de cada ámbito (82.235435ms)
|
||||||
|
@metis/companies:test: ✔ el resumen de compañía es la entrada por defecto y ofrece destinos reales (142.799129ms)
|
||||||
|
@metis/companies:test: ✔ los índices de catálogos distinguen definiciones globales y variantes de compañía (19.94863ms)
|
||||||
|
@metis/companies:test: ✔ el listado presenta estado vacío y acceso al alta sin formularios visibles (110.425418ms)
|
||||||
|
@metis/companies:test: ✖ la ficha presenta datos de la compañía, contactos y acceso a sedes (128.318234ms)
|
||||||
|
@metis/companies:test: ✔ el formulario de sede ofrece dirección y teléfono opcionales (49.232719ms)
|
||||||
|
@metis/companies:test: ✖ el formulario de persona distingue contactos de cuentas de acceso y permite datos opcionales (33.427217ms)
|
||||||
|
@metis/companies:test: ✔ asignar un departamento permite añadir el primero y conserva el selector en edición (32.38701ms)
|
||||||
|
@metis/companies:test: ✔ el tablero de compañía tiene composición propia, acciones arriba y métricas limitadas a su ámbito (29.772493ms)
|
||||||
|
@metis/companies:test: ✔ el menú de compañía ordena trabajo y personas por grupos sin repetir las acciones de usuario (82.291534ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: ready (78.736411ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: pending (76.481796ms)
|
||||||
|
@metis/companies:test: ✔ el menú muestra el contador de infraestructura: error (90.212485ms)
|
||||||
|
@metis/companies:test: ✔ sedes: listado traducido con filtro de archivadas y solo sedes activas por defecto (87.206266ms)
|
||||||
|
@metis/companies:test: ✔ la navegación de compañía conserva 21 enlaces reales, sección activa y contexto es/en (232.620807ms)
|
||||||
|
@metis/companies:test: ✔ el logo sustituye iniciales con URL versionada, dimensiones fijas y carga diferida (39.027555ms)
|
||||||
|
@metis/companies:test: ✔ sin logotipo aparecen iniciales y las listas consultan la misma caché de compañías (22.772649ms)
|
||||||
|
@metis/companies:test: ✔ cuentas del mapeo: exige asociación vigente y catálogos activos; no atribuye por login o por perfil (10.110367ms)
|
||||||
|
@metis/companies:test: ✔ actualización del mapeo: omitir cuenta conserva semántica distinta de retirarla explícitamente; admite revisión capturada (2.712718ms)
|
||||||
|
@metis/companies:test: ✔ uso de impresora: agrupa perfiles de un mismo equipo y separa pendientes e historial (37.900552ms)
|
||||||
|
@metis/companies:test: ✔ uso de impresora: referencias sin resolver no se presentan como ausencia de uso (13.347089ms)
|
||||||
|
@metis/companies:test: ✔ identidades locales: entorno del activo, navegación por UUID y estado vigente en es/en (56.430166ms)
|
||||||
|
@metis/companies:test: ✔ propietario local: vínculo retirado conserva navegación y no ofrece reasignación (1.668611ms)
|
||||||
|
@metis/companies:test: ✔ cuenta del mapeo: seleccionar perfil es una acción explícita; el render no infiere ni cambia cuenta (44.318987ms)
|
||||||
|
@metis/companies:test: ✔ correo: listado traducido, alta en cabecera y formularios ocultos (171.400318ms)
|
||||||
|
@metis/companies:test: ✔ correo: selectores reutilizables con altas y ayuda inmediatamente antes de guardar (35.640033ms)
|
||||||
|
@metis/companies:test: ✔ Buzones: alta en modal, cuenta opcional y ayuda al final (38.050249ms)
|
||||||
|
@metis/companies:test: ✔ L04.2: correo mantiene tabla y criterios mientras faltan sus referencias (34.120823ms)
|
||||||
|
@metis/companies:test: ✔ Personas conserva búsqueda por nombre, contactos, consulta y distinción ñ/n en es/en (949.939593ms)
|
||||||
|
@metis/companies:test: ✔ organización: resuelve departamento y cargo vigentes del responsable (1.214208ms)
|
||||||
|
@metis/companies:test: ✔ organización: sin responsable o asignación no infiere datos desde perfiles ni cargo genérico de persona (1.002407ms)
|
||||||
|
@metis/companies:test: ✔ organización: periodos semiabiertos, históricos y futuros (0.215101ms)
|
||||||
|
@metis/companies:test: ✔ organización: varias vigentes conservan todas, principal primero y orden estable sin mutar (12.802585ms)
|
||||||
|
@metis/companies:test: ✔ organización: dos principales no ocultan información seleccionando una arbitrariamente (0.586404ms)
|
||||||
|
@metis/companies:test: ✔ organización: aislamiento de compañía y persona, y referencias incompletas (0.967706ms)
|
||||||
|
@metis/companies:test: ✔ organización: cambios y refetch se derivan de la fuente actual (0.430602ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: listado sin formularios, alta en cabecera y traducciones (271.285198ms)
|
||||||
|
@metis/companies:test: ✔ impresoras: selectores con alta rápida y ayuda inferior antes de guardar (78.923739ms)
|
||||||
|
@metis/companies:test: ✔ L04.2: Procesos recupera búsqueda, cerrados, orden y página en sus enlaces (294.962904ms)
|
||||||
|
@metis/companies:test: ✔ procesos: listados es/en sin formularios junto a los datos (183.763287ms)
|
||||||
|
@metis/companies:test: ✔ procesos: alta rápida y ayuda inferior antes del pie (58.89928ms)
|
||||||
|
@metis/companies:test: ✔ plantillas: instrucciones, desactivación y ayuda global es/en sin datos de compañía (37.694643ms)
|
||||||
|
@metis/companies:test: ✔ seguimiento: origen personal, revisión actual y edición sin afirmar retirada real; cerrados sin editar (134.355067ms)
|
||||||
|
@metis/companies:test: ✔ remotos: consulta sin formularios y traducciones (233.445744ms)
|
||||||
|
@metis/companies:test: ✔ remotos: formulario, altas rápidas y ayuda antes del pie (95.388713ms)
|
||||||
|
@metis/companies:test: ✔ remotos: origen personal, acceso configurado sin autorización y VPN separada (4.477433ms)
|
||||||
|
@metis/companies:test: ✔ el borrado de cuentas valida la respuesta, envía CSRF y conserva errores de negocio (52.776944ms)
|
||||||
|
@metis/companies:test: ✔ las consultas de compañías conservan sus rutas y rechazan respuestas inválidas (3.394422ms)
|
||||||
|
@metis/companies:test: ✔ lectura y guardado de accesos están separados sin cambiar el contrato HTTP (5.819038ms)
|
||||||
|
@metis/companies:test: ✔ el guardado de usos de buzón mantiene su endpoint y no se confunde con la consulta (3.679624ms)
|
||||||
|
@metis/companies:test: ✔ los adapters mantienen la ausencia semántica sin modificar los campos del formulario (0.397903ms)
|
||||||
|
@metis/companies:test: ✔ las instrucciones de proceso conservan el orden y excluyen líneas vacías (0.300202ms)
|
||||||
|
@metis/companies:test: ✔ la validación de logos conserva tipos permitidos, vacío y límite exacto de tamaño (0.319702ms)
|
||||||
|
@metis/companies:test: ✔ la tabla espera todas sus referencias y distingue actualización de disponibilidad (1.56911ms)
|
||||||
|
@metis/companies:test: ✔ un fallo recuperable conserva datos anteriores sin presentarlos como actualizados (1.118907ms)
|
||||||
|
@metis/companies:test: ✔ una fuente denegada impide mostrar la caché de todo el listado compuesto (0.680804ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 401 conserva la semántica de disponibilidad (42.629377ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 403 conserva la semántica de disponibilidad (1.233408ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: HTTP 503 conserva la semántica de disponibilidad (0.378502ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: una lectura correcta valida y conserva la página del proveedor (5.887938ms)
|
||||||
|
@metis/companies:test: ✔ iniciales y siglas soportan nombres vacíos, Unicode y separadores (3.245324ms)
|
||||||
|
@metis/companies:test: ✔ el color es estable con variaciones de espacios, mayúsculas y acentos (0.413203ms)
|
||||||
|
@metis/companies:test: ✔ los colores generados mantienen contraste AA para texto pequeño (1.559911ms)
|
||||||
|
@metis/companies:test: ✔ el avatar conserva su nombre accesible y puede ser decorativo dentro de un botón etiquetado (21.403558ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-01/02/17: SO actual desde instalación, independiente de cualquier valor de fábrica (14.528007ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-03/04/05/18: vacío, pendiente y retirado no son SO actual (56.125714ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-06/07: varios SO, orden determinista y aplicaciones excluidas (24.38958ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-08/12: cambiar de activo conserva solo su SO, sin aceptar compañías ajenas (3.947229ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-09: versión de instalación y redundancia literal sin reglas de fabricante (10.83588ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-10: referencia no resoluble no significa ausencia; inactiva resoluble conserva información (15.784917ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-11: error con datos previos, error de resolver y carga no se presentan como vacío (18.337235ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-13/14: servidor sin puesto y plantilla propia consumen la misma lectura por activo (7.249554ms)
|
||||||
|
@metis/companies:test: ✔ OSUI-16: presentación es/en y versión opcional (37.95258ms)
|
||||||
|
@metis/companies:test: ✔ SO global: selector conserva scope y destino SO; la versión instalada sigue separada (0.777906ms)
|
||||||
|
@metis/companies:test: ✔ REG-SO-01/11: render global configurado, inactivo e histórico conserva nombre, versión y destino es/en (70.503921ms)
|
||||||
|
@metis/companies:test: ✔ software del puesto es/en: SO actual, versión, enlaces y separación por ordenador sin formularios incrustados (198.174064ms)
|
||||||
|
@metis/companies:test: ✔ formulario software: recurso fijo en edición, estados permitidos, ayuda y ausencia de persona/perfil (62.585363ms)
|
||||||
|
@metis/companies:test: ✔ R2.4: nombre, origen y enlace global se resuelven sin ResourceDTO local; licencia continúa particular (68.651807ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: consulta sin formularios y traducciones (181.803042ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: formulario con alta rápida de persona, fechas opcionales y ayuda inferior (55.117307ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: sin autorización no se presupone permiso; persona inactiva requiere revisión (15.311413ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo: alta desde un perfil fija la persona y no abre otra alta rápida (14.334905ms)
|
||||||
|
@metis/companies:test: ✔ teletrabajo es/en: conserva estado documental, vigencia y revisión como mensajes independientes (58.750134ms)
|
||||||
|
@metis/companies:test: ✔ entorno: reúne varios correos y configuraciones, distingue pendiente, retirada y revisión (3.960226ms)
|
||||||
|
@metis/companies:test: ✔ entorno: excluye cerradas, otros perfiles, compañías y correo antiguo sin adivinar relaciones (1.666011ms)
|
||||||
|
@metis/companies:test: ✔ entorno: composición vigente del puesto y datos actuales sin duplicación (0.854406ms)
|
||||||
|
@metis/companies:test: ✔ entorno: ficha es/en con estados y acciones, sin formularios; muestra falso y varios correos (110.819224ms)
|
||||||
|
@metis/companies:test: ✔ entorno: carga, fallo parcial sin falsa ficha vacía, perfil inexistente y aviso de archivado (61.103299ms)
|
||||||
|
@metis/companies:test: ✔ entorno: consulta autorización por persona, común a sus perfiles y sin confundir preparación (46.702105ms)
|
||||||
|
@metis/companies:test: ✔ entorno: accesos abiertos de la persona y sus orígenes sin confundirlos con configuraciones del perfil (24.243158ms)
|
||||||
|
@metis/companies:test: ✔ las entidades de los selectores se pluralizan en es/en sin reutilizar el verbo de acción (7.900751ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: listado es/en, alta en cabecera y ausencia de formulario junto al listado (83.592544ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: la cabecera breve conserva el motivo completo en la celda de revisión (50.314827ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: alta en cuatro pasos, sin guardado intermedio y contenido de ayuda conservado (17.792316ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: selección de persona explícita y cuenta opcional con altas independientes (27.436279ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: el puesto fijado aporta su ordenador y no selecciona al responsable como usuario (8.887558ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: requisitos por paso, cuenta opcional y propuesta basada en persona y ordenador (0.380102ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: el nombre sugerido respeta el límite del contrato (0.325602ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: resumen con listas, ordenador único y ausencias representadas por guion (4.749231ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: edición completa con identidad fija aunque el perfil requiera revisión (12.063378ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: orden y página navegables conservan búsqueda y archivados al abrir entorno (52.777244ms)
|
||||||
|
@metis/companies:test: ✔ Personas admite actividad en listado y ficha, con los demás criterios de contexto (15.179698ms)
|
||||||
|
@metis/companies:test: ✔ el catálogo de aplicaciones de compañía conserva cursor, filtros y retorno a ficha (1.54101ms)
|
||||||
|
@metis/companies:test: ✔ las funcionalidades archivadas no tienen rutas activas (11.712876ms)
|
||||||
|
@metis/companies:test: ✔ el filtro de plantilla permite abrir listados y fichas de activos y recursos (1.457309ms)
|
||||||
|
@metis/companies:test: ✔ el filtro de plantilla se limita a los inventarios que conocen sus definiciones (4.732631ms)
|
||||||
|
@metis/companies:test: ✔ todas las secciones de compañía tienen una ruta propia y el índice es el resumen (9.042758ms)
|
||||||
|
@metis/companies:test: ✔ los índices de catálogos y los cinco catálogos globales son destinos independientes (1.802712ms)
|
||||||
|
@metis/companies:test: ✔ las configuraciones conservan la compañía y el UUID del perfil (2.329215ms)
|
||||||
|
@metis/companies:test: ✔ las rutas desconocidas y los parámetros inválidos no abren una sección por defecto (17.03921ms)
|
||||||
|
@metis/companies:test: ✔ la ficha de puesto tiene ruta propia, conserva el UUID y rechaza identificadores inválidos (1.120307ms)
|
||||||
|
@metis/companies:test: ✔ dominios: enlaces a cuentas y grupos limitados al entorno de la compañía (13.88749ms)
|
||||||
|
@metis/companies:test: ✔ los parámetros de contexto llegan al consumidor sin crear rutas nuevas ni aceptar claves ajenas (6.105039ms)
|
||||||
|
@metis/companies:test: ✔ catálogo de aplicaciones: UUID global y criterios propios sin compañía ficticia (12.487781ms)
|
||||||
|
@metis/companies:test: ✔ catálogo SO: ruta específica y filtros, sin confundir con aplicaciones (0.648405ms)
|
||||||
|
@metis/companies:test: ✔ la barra comienza desplegada sin navegador o sin una preferencia válida (5.512536ms)
|
||||||
|
@metis/companies:test: ✔ plegar y desplegar persisten la preferencia sin modificar otras claves (0.375602ms)
|
||||||
|
@metis/companies:test: ✔ un fallo de lectura, escritura o acceso a localStorage no bloquea la barra (0.365102ms)
|
||||||
|
@metis/companies:test: ✔ la capacidad del shell fuerza modos sin cambiar la preferencia manual (6.06794ms)
|
||||||
|
@metis/companies:test: ✔ un menú solo libera su modalidad si su presentación pasa a estar disponible fija (0.338403ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 0 asociados y 0 instalaciones (27.94378ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 0 asociados y 3 instalaciones (28.888109ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 1 asociados y 0 instalaciones (7.681957ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 1 asociados y 2 instalaciones (10.564879ms)
|
||||||
|
@metis/companies:test: ✔ layout 640px: 2 asociados y 3 instalaciones (24.815186ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 0 asociados y 0 instalaciones (2.74802ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 0 asociados y 3 instalaciones (20.598055ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 1 asociados y 0 instalaciones (12.833195ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 1 asociados y 2 instalaciones (8.358362ms)
|
||||||
|
@metis/companies:test: ✔ layout 748px: 2 asociados y 3 instalaciones (15.931919ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 0 asociados y 0 instalaciones (4.390833ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 0 asociados y 3 instalaciones (10.285377ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 1 asociados y 0 instalaciones (2.860422ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 1 asociados y 2 instalaciones (7.388255ms)
|
||||||
|
@metis/companies:test: ✔ layout 900px: 2 asociados y 3 instalaciones (18.488638ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 0 asociados y 0 instalaciones (18.68624ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 0 asociados y 3 instalaciones (12.293492ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 1 asociados y 0 instalaciones (6.583449ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 1 asociados y 2 instalaciones (10.004175ms)
|
||||||
|
@metis/companies:test: ✔ layout 1200px: 2 asociados y 3 instalaciones (8.492564ms)
|
||||||
|
@metis/companies:test: ✔ layout: dos asociados se reparten por capacidad; sin principal no se fabrican conexiones (9.710173ms)
|
||||||
|
@metis/companies:test: ✔ preview: conserva límites y publica solo cobertura real de fuentes conocidas (2.374718ms)
|
||||||
|
@metis/companies:test: ✔ WD04.4: acción estable de presentación, fuera de las relaciones y contadores, sin solapamientos (382.145055ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: acciones ordenadas del principal y periférico sin inferir software por categoría (2.236515ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: lectura sin permisos de edición, carga/error parcial y activos retirados (0.550203ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: distingue instalación, producto global/inactivo y licencia particular (0.860106ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: producto particular y gates cerrados no crean acciones administrativas (1.53801ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: SO global utiliza la referencia pública y su etiqueta específica (0.609404ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: estados terminales, refetch y cambio de principal o compañía no reutilizan acciones antiguas (0.895506ms)
|
||||||
|
@metis/companies:test: ✔ WD04.5: trigger es/en separado del enlace, tarjetas sin acciones y añadir sin menú (58.990385ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: ordenador y equipamiento actuales, personas por perfiles y archivados separados (34.985262ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: sin responsable sigue mostrando usuarios y un puesto desmantelado no cuenta historia como miembros (0.318502ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: sustituir ordenador resuelve perfiles del nuevo host sin trasladar los anteriores (7.968059ms)
|
||||||
|
@metis/companies:test: ✔ dashboard: referencias ajenas o principal fuera de composición no aportan relaciones (0.378103ms)
|
||||||
|
@metis/companies:test: ✔ pendientes: solo pending, perfiles activos, compañía correcta y deduplicación por familia e identidad (2.287217ms)
|
||||||
|
@metis/companies:test: ✔ último cambio: orden cronológico sin mutación, altas de miembros y campos realmente cambiados (1.794414ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal válido primero, miembros únicos y secundarios en orden declarado (2.428319ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: activos sin principal no adquieren un principal por su tipo o posición (0.304503ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal fuera de composición se advierte sin añadirlo a miembros (0.232102ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: referencia ausente o de otra compañía no inventa datos ni marca principal (2.841221ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: vacío y desmantelado no recuperan miembros del historial (0.406003ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: averiado sigue asignado y conserva ambos estados originales (0.454303ms)
|
||||||
|
@metis/companies:test: ✔ expansión: todos abiertos inicialmente y plegado independiente de cada perfil (1.893214ms)
|
||||||
|
@metis/companies:test: ✔ refetch conserva plegados y filtros individuales, incluso con error o fuentes incompletas (0.401803ms)
|
||||||
|
@metis/companies:test: ✔ retirada o archivo limpia únicamente el estado del perfil afectado (0.405603ms)
|
||||||
|
@metis/companies:test: ✔ cambio de compañía, puesto u ordenador restablece todos los perfiles abiertos y los filtros (0.429203ms)
|
||||||
|
@metis/companies:test: ✔ orden estable y numérico del resumen y los pendientes, sin mutar datos remotos (10.508678ms)
|
||||||
|
@metis/companies:test: ✔ incluye solo perfiles del ordenador principal, distingue inactivos y deduplica personas (4.891236ms)
|
||||||
|
@metis/companies:test: ✔ puesto inexistente o sin composición no hereda perfiles de otros activos (0.850906ms)
|
||||||
|
@metis/companies:test: ✔ persona: responsabilidad y uso son independientes y solo se consideran puestos y perfiles vigentes (1.164508ms)
|
||||||
|
@metis/companies:test: ✔ ficha de puesto es/en: cabecera, retorno, bloques vacíos y edición modal sin formulario incrustado (109.937674ms)
|
||||||
|
@metis/companies:test: ✔ un UUID de puesto ausente muestra no encontrado sin cabecera ni edición de otro puesto (3.507828ms)
|
||||||
|
@metis/companies:test: ✔ ficha de persona es/en: distingue responsabilidad y uso, y enlaza los puestos (6.437451ms)
|
||||||
|
@metis/companies:test: ✔ ficha: ordenador destacado y periféricos sin repetir, perfiles solo del ordenador y personas deduplicadas (103.085419ms)
|
||||||
|
@metis/companies:test: ✔ personas y perfiles: carga y error no se presentan como listados vacíos (12.5875ms)
|
||||||
|
@metis/companies:test: ✔ ficha: no permite crear perfiles si el ordenador está retirado o el puesto está desactivado (48.707887ms)
|
||||||
|
@metis/companies:test: ✔ historial del puesto es/en: revisiones de más reciente a más antigua con autor y valores de cada momento (9.981779ms)
|
||||||
|
@metis/companies:test: ✔ personas es/en: enlaces a todos sus puestos bajo responsabilidad, sin confundirlos con puestos usados (56.689251ms)
|
||||||
|
@metis/companies:test: ✔ menú de compañía es/en: totales en badges alineados y cero real en secciones vacías (37.959202ms)
|
||||||
|
@metis/companies:test: ✔ ruta de activo de compañía abre la página con información, historial, software y solo sus perfiles (107.194951ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento es/en: badges de principal, técnico y uso conservan su significado (7.153357ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: inventario fallido no se presenta como vacío y permite reintentar (3.050324ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: principal inconsistente se explica y no se incorpora como miembro válido (2.375619ms)
|
||||||
|
@metis/companies:test: ✔ personas y perfiles es/en: expansión contextual, enlaces independientes y resumen cero real (9.934379ms)
|
||||||
|
@metis/companies:test: ✔ cabecera es/en: responsable largo o ausente, contexto independiente y mapa con resumen (74.884295ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: lista completa y todos desplegados por defecto, incluso con cincuenta perfiles (76.186805ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: carga, error y datos retenidos no publican un total como actual (7.775261ms)
|
||||||
|
@metis/companies:test: ✔ equipamiento: contador conocido cero, uno, cinco y seis; error conserva filas sin publicar total (12.473499ms)
|
||||||
|
@metis/companies:test: ✔ configuración: cobertura desconocida, revisión y notas permanecen fuera del disclosure técnico (2.841023ms)
|
||||||
|
@metis/companies:test: ✔ perfiles: una actualización en curso conserva el total conocido y anuncia la actividad (1.593613ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: dependencia sin principal conserva mapa y resumen y una explicación principal sin tabla vacía (17.465039ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: un vacío confirmado de perfiles conserva el alta y evita cabeceras de una tabla sin filas (1.18171ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: soporte distingue perfiles en carga, error, ausencia confirmada y persona no resuelta (3.466628ms)
|
||||||
|
@metis/companies:test: ✔ v2.2: referencia de principal no resuelta no publica cero pendientes ni ausencia de perfiles (14.662417ms)
|
||||||
|
@metis/companies:test: ✔ v3: Software conserva los tres estados documentales con la misma representación que las familias (29.893937ms)
|
||||||
|
@metis/companies:test: ✔ v3: estados técnicos de equipamiento conservan principal y asignación en es/en (13.279905ms)
|
||||||
|
@metis/companies:test: ✔ responsable es/en: avatar y nombre abren un único popup y la ausencia no ofrece acciones (3.79863ms)
|
||||||
|
@metis/companies:test: ✔ mapa visual: aísla compañías, deduplica miembros y conserva software y estados reales (15.916626ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: principal solo, vacíos confirmados y error de software conservan navegación y gestión (39.806416ms)
|
||||||
|
@metis/companies:test: ✔ mapa: composición vacía y sin principal no fabrica relaciones ni instalaciones (1.553212ms)
|
||||||
|
@metis/companies:test: ✔ contexto de persona: rechaza datos de otra compañía y no ofrece acciones (0.639105ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: carga inicial y carga parcial mantienen estados accesibles (21.41307ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: leyenda con tres conceptos y cobertura explícita sin totales inventados (2.099817ms)
|
||||||
|
@metis/companies:test: ✔ mapa es/en: revisión de software traducida incluso sin referencia navegable (1.072609ms)
|
||||||
|
@metis/companies:test: ✔ WD04.4 es/en: acción autorizada, vacío accionable y lectura sin permiso (6.187549ms)
|
||||||
|
@metis/companies:test: ✔ perfiles del puesto: configuraciones de todos los activos abiertas y destinos accesibles únicos (14.788518ms)
|
||||||
|
@metis/companies:test: ℹ tests 317
|
||||||
|
@metis/companies:test: ℹ suites 0
|
||||||
|
@metis/companies:test: ℹ pass 315
|
||||||
|
@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 24934.748373
|
||||||
|
@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 (128.318234ms)
|
||||||
|
@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 (33.427217ms)
|
||||||
|
@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: 44.756s
|
||||||
|
Failed: @metis/companies#test
|
||||||
|
|
||||||
|
ERROR run failed: command exited (1)
|
||||||
|
[ELIFECYCLE] Test failed. See above for more details.
|
||||||
10
.artifacts/account-usage/tests.log
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
✔ modules/companies/src/api/domain/access/access.test.ts (476.749877ms)
|
||||||
|
✔ modules/companies/src/web/access/shared/access.test.tsx (3623.123324ms)
|
||||||
|
ℹ tests 2
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 2
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 3636.831013
|
||||||
29
.artifacts/account-usage/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: 9.779s
|
||||||
|
Failed: @repo/shadcn-ui#typecheck
|
||||||
|
|
||||||
|
ERROR run failed: command exited (2)
|
||||||
|
[ELIFECYCLE] Command failed with exit code 2.
|
||||||
53
.artifacts/account-usage/validacion.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Edición de Uso de cuentas — 27/10/2026
|
||||||
|
|
||||||
|
En el formulario existente Editar → Uso se habilitan Personal, Compartida y Técnica.
|
||||||
|
El dominio acepta los cambios compatibles conservando UUID, estado y relaciones, y
|
||||||
|
registra la revisión con el valor previo. Repetir la edición sin cambios no añade revisiones.
|
||||||
|
Sistema y entorno siguen fijos. No se necesita migración ni cambia el contrato de guardado.
|
||||||
|
|
||||||
|
La conversión Compartida → Personal comprueba solapamientos de titulares, incluidos
|
||||||
|
los históricos, mediante la política de periodos existente. Cambiar entre Técnica y
|
||||||
|
Personal/Compartida se rechaza si hay relaciones con personas, incluso cerradas:
|
||||||
|
responsabilidad técnica y acceso tienen contratos distintos. El error traducido
|
||||||
|
ACCOUNT_USAGE_CONFLICT conserva el borrador y permite seleccionar un uso compatible.
|
||||||
|
|
||||||
|
Archivos principales:
|
||||||
|
- `modules/companies/src/web/access/shared/helpers/access-fields.ts`
|
||||||
|
- `modules/companies/src/api/domain/access/access-item.entity.ts`
|
||||||
|
- `modules/companies/src/api/domain/access/access-policy.ts`
|
||||||
|
- `modules/companies/src/api/domain/access/access-workspace.aggregate.ts`
|
||||||
|
- `modules/companies/src/api/domain/access/access.test.ts`
|
||||||
|
- `scripts/account-usage-ui-checks.py`
|
||||||
|
|
||||||
|
## Comprobaciones ejecutadas
|
||||||
|
|
||||||
|
Correctas:
|
||||||
|
- `pnpm --filter @metis/companies typecheck` y `pnpm --filter @metis/server typecheck`.
|
||||||
|
- `TSX_TSCONFIG_PATH=tsconfig.json node --import tsx --test modules/companies/src/api/domain/access/access.test.ts modules/companies/src/web/access/shared/access.test.tsx`.
|
||||||
|
- Prueba MariaDB `apps/server/src/account-deletion.integration.test.ts` con NODE_ENV=test y DB_NAME=metis_desk_test: cambio Personal → Compartida, relectura y conservación del historial, además de su cobertura de borrado.
|
||||||
|
- Biome dirigido a los diez archivos TypeScript/JSON cambiados: sin errores ni advertencias. ESLint dirigido: correcto. `git diff --check`: correcto.
|
||||||
|
- `pnpm build`: correcto, con el aviso existente de tamaño de chunks.
|
||||||
|
- `python3 scripts/visual-audit.py --account-deletion --check`: 8 comprobaciones específicas de Uso y 17 de regresión de eliminación mediante Playwright MCP Docker stdio. Edición real, selector habilitado, sistema bloqueado, selección por teclado, guardado persistido, mismo UUID/historial, cancelación sin guardar, error por relación incompatible conservando el borrador y recuperación con Compartida.
|
||||||
|
- Diálogo a 1440, 390 y 320 px sin desbordamiento horizontal. Abiertas e inspeccionadas las capturas de 320 px y error incompatible. El cuerpo mantiene desplazamiento y pie visible.
|
||||||
|
- Volumen sintético: 15 compañías; principal con 50 personas, 51 activos, 66 recursos, 50 perfiles, 50 puestos y 62 cuentas creadas durante el recorrido. Solo metis_desk_test desechable. Entorno retirado y sesión eliminada.
|
||||||
|
|
||||||
|
Se ejecutaron también `pnpm typecheck`, `pnpm lint`, `pnpm test` y `pnpm check` globales.
|
||||||
|
Mantienen fallos ajenos a este cambio: locale en el calendario vendor, dos pruebas de
|
||||||
|
textos en companies-page.test.tsx y diagnósticos de lint/formato de otros archivos.
|
||||||
|
No se modificaron vendor ni esos archivos para ocultarlos. Logs de esta ejecución en
|
||||||
|
este directorio. No se han cambiado cuentas reales durante las pruebas.
|
||||||
|
|
||||||
|
## Skills y límites
|
||||||
|
|
||||||
|
Consultadas `rdx-solid`, `ux-inputs-and-forms`, `shadcn` y las reglas React ya revisadas
|
||||||
|
para este formulario. Influyeron en la validación de relaciones en dominio, el campo
|
||||||
|
con etiqueta persistente, el uso de las primitivas existentes y la conservación del
|
||||||
|
borrador sin duplicar estado remoto. No están disponibles los auditores ux-*-audit;
|
||||||
|
no se invocaron ni instalaron. No se afirma validación WCAG completa, lector de
|
||||||
|
pantalla, contraste global o tema oscuro.
|
||||||
|
|
||||||
|
[Informe MCP](../playwright/authenticated-g5yiyswy/account-usage-report.json)
|
||||||
|
|
||||||
|
[Captura de edición a 320 px](../playwright/authenticated-g5yiyswy/account-usage-320.png)
|
||||||
|
|
||||||
|
[Error recuperable por relaciones incompatibles](../playwright/authenticated-g5yiyswy/account-usage-incompatible.png)
|
||||||
4
.artifacts/account-usage/visual.log
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Preparando MariaDB desechable y servicios en puertos libres…
|
||||||
|
Edición de uso: 8 comprobaciones. Evidencias: /home/david/dev/metis-desk/.artifacts/playwright/authenticated-g5yiyswy
|
||||||
|
Eliminación de cuentas: 17 comprobaciones. Evidencias: /home/david/dev/metis-desk/.artifacts/playwright/authenticated-g5yiyswy
|
||||||
|
Entorno temporal retirado; las capturas se conservan sin sesión ni credenciales.
|
||||||
BIN
apps/server/data/resource-icons/7zip.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
201
apps/server/data/resource-icons/LICENSE.dashboard-icons.txt
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright (c) 2024 Bjorn Lammers, Meier Lukas, Thomas Camlong and Homarr Labs
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
21
apps/server/data/resource-icons/LICENSE.powertoys.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
30
apps/server/data/resource-icons/LICENSE.simple-icons.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# CC0 1.0 Universal
|
||||||
|
|
||||||
|
## Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an “owner”) of an original work of authorship and/or a database (each, a “Work”).
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works (“Commons”) that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the “Affirmer”), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights (“Copyright and Related Rights”). Copyright and Related Rights include, but are not limited to, the following:
|
||||||
|
1. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
|
||||||
|
2. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
3. publicity and privacy rights pertaining to a person’s image or likeness depicted in a Work;
|
||||||
|
4. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(i), below;
|
||||||
|
5. rights protecting the extraction, dissemination, use and reuse of data in a Work;
|
||||||
|
6. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
|
||||||
|
7. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer’s heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer’s express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer’s Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the “License”). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
1. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
|
||||||
|
2. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
|
||||||
|
3. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person’s Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
|
||||||
|
4. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
|
||||||
|
|
||||||
|
For more information, please see <https://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
BIN
apps/server/data/resource-icons/adobe-indesign.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
apps/server/data/resource-icons/adobeacrobatreader.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
apps/server/data/resource-icons/adobeaftereffects.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/server/data/resource-icons/adobecreativecloud.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/server/data/resource-icons/adobeillustrator.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
apps/server/data/resource-icons/adobephotoshop.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/server/data/resource-icons/adobepremierepro.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
apps/server/data/resource-icons/anydesk.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
apps/server/data/resource-icons/autocad.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
apps/server/data/resource-icons/autodeskrevit.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/server/data/resource-icons/blender.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
apps/server/data/resource-icons/enscape.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/server/data/resource-icons/everything.png
Normal file
|
After Width: | Height: | Size: 930 B |
BIN
apps/server/data/resource-icons/filezilla.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/server/data/resource-icons/firefox.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
apps/server/data/resource-icons/google-chrome.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
apps/server/data/resource-icons/libreoffice.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
apps/server/data/resource-icons/macos.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
278
apps/server/data/resource-icons/manifest.json
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "7-Zip",
|
||||||
|
"file": "7zip.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/7zip.png",
|
||||||
|
"sha256": "bb6940ab0408c719c97ed82e88b0d4d41d4a536d436c747f50ae7774a88d1684"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe InDesign",
|
||||||
|
"file": "adobe-indesign.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/adobe-indesign.png",
|
||||||
|
"sha256": "9372a8284f959230784d142a78333eb0f6f5145d032bcba9fb92741c63fd9e86"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AnyDesk",
|
||||||
|
"file": "anydesk.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/anydesk.png",
|
||||||
|
"sha256": "6cf34396022303de5fbb52ed7ba14ef6d0b40e480cbf675227b95ac099fea069"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Blender",
|
||||||
|
"file": "blender.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/blender.png",
|
||||||
|
"sha256": "ab3dcaea33bb48010a33f476ac887af4629fddb1f5feb3d37770aab9884b9f12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FileZilla Client",
|
||||||
|
"file": "filezilla.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/filezilla.png",
|
||||||
|
"sha256": "8e6aa2fbf1ed6987aeb20944d05d88cb4c9010a057d77ef04de58ebd5bc34056"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Google Chrome",
|
||||||
|
"file": "google-chrome.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/google-chrome.png",
|
||||||
|
"sha256": "2cea3ac88ec832166c6932eb583151f086f19f74bd8ea65d754b68a8adbcfe98"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LibreOffice",
|
||||||
|
"file": "libreoffice.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/libreoffice.png",
|
||||||
|
"sha256": "2d4468bd042285718fa49b8875152a8c59b5cad3171781891d80ea031f9645f1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Edge",
|
||||||
|
"file": "microsoft-edge.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-edge.png",
|
||||||
|
"sha256": "d54c4f7d824c1f630278915a631bbd8899d66a1721ce43442cc731e8f276ddb5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Office",
|
||||||
|
"file": "microsoft-office.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-office.png",
|
||||||
|
"sha256": "3d97a7d40a4cfaa9accbcf1db13c19581262124131845d31ff28161b2bdeda14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft OneDrive",
|
||||||
|
"file": "microsoft-onedrive.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-onedrive.png",
|
||||||
|
"sha256": "7217923d07eb3182e60f156b9d3034ac6d0c45148085acfd719faf43ebd7e50f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Power BI Desktop",
|
||||||
|
"file": "powerbi.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/powerbi.png",
|
||||||
|
"sha256": "f22933f6c2f26ed446d68eafc3f307a801052dfe304bc756d571185d2a70546c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Project",
|
||||||
|
"file": "microsoft-project.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-project.png",
|
||||||
|
"sha256": "93c7ddb9dd0a8e0c295e6c9173fc777a5f75d81f4cf67c277724ded6f4600070"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Teams",
|
||||||
|
"file": "microsoft-teams.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-teams.png",
|
||||||
|
"sha256": "9bc11b6b79ff29e8a4f45ab04e6ccc984797dbc7cf5f32a158ac045bd1ce4793"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Visio",
|
||||||
|
"file": "microsoft-visio.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/microsoft-visio.png",
|
||||||
|
"sha256": "21d1f5359bf7339e1950323ade11923c5451cf69fa41a8469d58bd83f78927fe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mozilla Firefox",
|
||||||
|
"file": "firefox.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/firefox.png",
|
||||||
|
"sha256": "eefdd01e671eef11c585c447c6df38593de0a8da52544de87a1509597717bc04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mozilla Thunderbird",
|
||||||
|
"file": "thunderbird.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/thunderbird.png",
|
||||||
|
"sha256": "b843152c7decd1ff760d6f8664b1229a2d631fb83c24c7f1b09a41c1dd2de01f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RustDesk",
|
||||||
|
"file": "rustdesk.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/rustdesk.png",
|
||||||
|
"sha256": "d3969cd1587410dc6555d15b507b4cb9ebe1fd3c5cfeb6f31ec5f0a4027c3500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SketchUp",
|
||||||
|
"file": "sketchup-make.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/sketchup-make.png",
|
||||||
|
"sha256": "8071ed26f30afa52518b54f1d8e40968ce39947931b31f075ac5e9de3042f3a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Slack",
|
||||||
|
"file": "slack.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/slack.png",
|
||||||
|
"sha256": "9b79e3004cd164098ac9904eb96ed152cadf6fcc7af0be0bfb3c838552653e5e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Visual Studio Code",
|
||||||
|
"file": "visual-studio-code.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/visual-studio-code.png",
|
||||||
|
"sha256": "f43ac17908efd0b1d2d42fe98acddf95c7cb4d41f46bd9ced5295c22d864f071"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WhatsApp",
|
||||||
|
"file": "whatsapp.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/whatsapp.png",
|
||||||
|
"sha256": "c8df7f4c6659f8bb5c3153eb32e4804e7ebb1d1b01bed5b1a90bbcf20b2dd45e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zoom Workplace",
|
||||||
|
"file": "zoom.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/zoom.png",
|
||||||
|
"sha256": "9bfb06b38c4f94d9e77154ee7925e6702c64c3ba7f33d1331cf3367d7b74ea25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Acrobat Pro",
|
||||||
|
"file": "adobeacrobatreader.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobeacrobatreader.svg",
|
||||||
|
"color": "EC1C24",
|
||||||
|
"sha256": "400306f6cca97d148a06217c20fd8ab1cb79c9e6c04955f80b3987568dc4e157"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Acrobat Reader",
|
||||||
|
"file": "adobeacrobatreader.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobeacrobatreader.svg",
|
||||||
|
"color": "EC1C24",
|
||||||
|
"sha256": "400306f6cca97d148a06217c20fd8ab1cb79c9e6c04955f80b3987568dc4e157"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe After Effects",
|
||||||
|
"file": "adobeaftereffects.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobeaftereffects.svg",
|
||||||
|
"color": "9999FF",
|
||||||
|
"sha256": "2a0e3ddc7669ddda84923cda885fbfd29b5e33fe6c89e1c44e12b7f766edde1f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Creative Cloud",
|
||||||
|
"file": "adobecreativecloud.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobecreativecloud.svg",
|
||||||
|
"color": "DA1F26",
|
||||||
|
"sha256": "997e7b7d5df84b34260cd8d7e7fb1a47b0b4e18553afb63b4dd5140002f61a77"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Illustrator",
|
||||||
|
"file": "adobeillustrator.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobeillustrator.svg",
|
||||||
|
"color": "FF9A00",
|
||||||
|
"sha256": "2208ddcaba56c5274d78b76c8a26ad112a9d96bb15694131062b755da3f5cb4f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Photoshop",
|
||||||
|
"file": "adobephotoshop.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobephotoshop.svg",
|
||||||
|
"color": "31A8FF",
|
||||||
|
"sha256": "dfeba8244d53140b190fb7ce9018c4c40ed06437f4531829b60b2bbbd0549f86"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adobe Premiere",
|
||||||
|
"file": "adobepremierepro.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/adobepremierepro.svg",
|
||||||
|
"color": "9999FF",
|
||||||
|
"sha256": "6dc7bceebc16c8983f5aa5c632d8ac79e768880960ffb8ec16dad9fcd08ef2bd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AutoCAD",
|
||||||
|
"file": "autocad.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/autocad.svg",
|
||||||
|
"color": "E51050",
|
||||||
|
"sha256": "4506c09ca1e1e814700c49eb082b202961cf509f3e006a8816da535401c51339"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Autodesk Revit",
|
||||||
|
"file": "autodeskrevit.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/autodeskrevit.svg",
|
||||||
|
"color": "186BFF",
|
||||||
|
"sha256": "0342888385ffa42e9648bb5acd48d2bc00f56b82f5fa842af01f362047c5e643"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VLC media player",
|
||||||
|
"file": "vlcmediaplayer.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/vlcmediaplayer.svg",
|
||||||
|
"color": "FF8800",
|
||||||
|
"sha256": "16f94058eb0e5a70d7042fb7fd4f04d4c372a5e8297f962fe7ef7bfcc1ee2f28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Notepad++",
|
||||||
|
"file": "notepadplusplus.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/notepadplusplus.svg",
|
||||||
|
"color": "90E59A",
|
||||||
|
"sha256": "88bbe31ebfd46a5d12fabb1970865e76d988a43e7ff745fe84faf8a033a27ac8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Enscape",
|
||||||
|
"file": "enscape.png",
|
||||||
|
"source": "https://static.chaos.com/images/assets/000/019/272/original/svg-icon-enscape-35px.svg",
|
||||||
|
"sha256": "d295e66143d3daf02cb7fe3a68c0d5a6d57973b46112fcc479bd2d0898030091"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Everything",
|
||||||
|
"file": "everything.png",
|
||||||
|
"source": "https://www.voidtools.com/favicon.ico",
|
||||||
|
"sha256": "9bb027221fd51dbec71d112acb849eac7994a1f4b1d3484291171907a94dfca7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WinSCP",
|
||||||
|
"file": "winscp.png",
|
||||||
|
"source": "https://winscp.net/assets/images/logos/logo@240.jpg",
|
||||||
|
"sha256": "96191adef0ee1f1dbe93dbf8cc714aff809049e7e6dcaa711b3a9b99c5633f62"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WinRAR",
|
||||||
|
"file": "winrar.png",
|
||||||
|
"source": "https://www.win-rar.com/fileadmin/images/apple-touch-icon.png",
|
||||||
|
"sha256": "9482736333eeaa0fbfd029dc0bf3c11824b852b1a7bc92169c2679f9e242501c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nitro PDF Pro",
|
||||||
|
"file": "nitro-pdf-pro.png",
|
||||||
|
"source": "https://www.gonitro.com/hubfs/favicon-196x196.png",
|
||||||
|
"sha256": "775535d7ca765ac1f690b22a42fcedd156c38af25a903e44451c65c1b5476bb5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TeamViewer",
|
||||||
|
"file": "teamviewer.png",
|
||||||
|
"source": "https://www.teamviewer.com/etc.clientlibs/teamviewer/clientlibs/clientlib-resources/resources/favicon.png",
|
||||||
|
"sha256": "4a7b2794cb58bb7e2bfe1811fcaeefd15240409957822696dd2f1b79a3c28c07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft PowerToys",
|
||||||
|
"file": "microsoft-powertoys.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/microsoft/PowerToys/79ceeee01cfec217b61fe4453940cd73ec22e8fb/src/settings-ui/Settings.UI/Assets/Settings/Icons/PowerToys.png",
|
||||||
|
"sha256": "a4f79a171db4bbceb4aca7f0273314405c95beea1e60637496e44284c6283fd2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"family": "windows-7",
|
||||||
|
"file": "windows-7.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/windows-7.png",
|
||||||
|
"sha256": "082fea31d1d82d971f857f2ffab5bdbad5f67587c5946f38b78a473bbc1be8a5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"family": "windows-10",
|
||||||
|
"file": "windows-10.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/windows-10.png",
|
||||||
|
"sha256": "f50535a653652a8aa59f3b9e9238c1c44cb4b0df0cd1b65a1a65db187589cabc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"family": "windows-11",
|
||||||
|
"file": "windows-11.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/homarr-labs/dashboard-icons/83f32e08257b66fbd971ac8d7645f5b38d52e85d/png/windows-11.png",
|
||||||
|
"sha256": "fcb20944c6926ebe4d47fd1b74a842dc792cddafe7f0a7d0ea2d61923e868322"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"family": "macOS",
|
||||||
|
"file": "macos.png",
|
||||||
|
"source": "https://raw.githubusercontent.com/simple-icons/simple-icons/13.21.0/icons/macos.svg",
|
||||||
|
"color": "333333",
|
||||||
|
"sha256": "35658f6e044d87215847a28dcf67f6cd75577ccae09a678a38c76e0519cb2964"
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
apps/server/data/resource-icons/microsoft-edge.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
apps/server/data/resource-icons/microsoft-office.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/server/data/resource-icons/microsoft-onedrive.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
apps/server/data/resource-icons/microsoft-powertoys.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
apps/server/data/resource-icons/microsoft-project.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
apps/server/data/resource-icons/microsoft-teams.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
apps/server/data/resource-icons/microsoft-visio.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
apps/server/data/resource-icons/nitro-pdf-pro.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
apps/server/data/resource-icons/notepadplusplus.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
apps/server/data/resource-icons/powerbi.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
apps/server/data/resource-icons/rustdesk.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/server/data/resource-icons/sketchup-make.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/server/data/resource-icons/slack.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
apps/server/data/resource-icons/teamviewer.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
apps/server/data/resource-icons/thunderbird.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
apps/server/data/resource-icons/visual-studio-code.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
apps/server/data/resource-icons/vlcmediaplayer.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
apps/server/data/resource-icons/whatsapp.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
apps/server/data/resource-icons/windows-10.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
apps/server/data/resource-icons/windows-11.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
apps/server/data/resource-icons/windows-7.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
apps/server/data/resource-icons/winrar.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/server/data/resource-icons/winscp.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
apps/server/data/resource-icons/zoom.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node --env-file=../../.env --import tsx --watch src/index.ts",
|
"dev": "node --env-file=../../.env --import tsx --watch src/index.ts",
|
||||||
"typecheck": "tsc -p tsconfig.json",
|
"typecheck": "tsc -p tsconfig.json",
|
||||||
"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",
|
"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/account-deletion.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",
|
"build": "tsup --config tsup.config.ts && pnpm check:build",
|
||||||
"check:build": "node --check dist/index.js",
|
"check:build": "node --check dist/index.js",
|
||||||
"start": "node --env-file=../../.env dist/index.js",
|
"start": "node --env-file=../../.env dist/index.js",
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"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: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: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"
|
"test:http": "node --import tsx --test src/app.test.ts src/resource-images.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": {
|
"dependencies": {
|
||||||
"@metis/assets": "workspace:*",
|
"@metis/assets": "workspace:*",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|||||||
|
|
||||||
import type { AccessFieldsDTO, AccessItemDTO, CompanyDTO } from "@metis/companies/common";
|
import type { AccessFieldsDTO, AccessItemDTO, CompanyDTO } from "@metis/companies/common";
|
||||||
import { AccessResponseSchema } from "@metis/companies/common";
|
import { AccessResponseSchema } from "@metis/companies/common";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export async function verifyAccess(
|
export async function verifyAccess(
|
||||||
request: (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>,
|
request: (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>,
|
||||||
@ -379,4 +380,62 @@ export async function verifyAccess(
|
|||||||
(await request(`/access/${other.id}/${account.id}/update`, { values: renamed })).status,
|
(await request(`/access/${other.id}/${account.id}/update`, { values: renamed })).status,
|
||||||
404
|
404
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// El borrado conserva relaciones actuales e históricas y nunca cruza compañías.
|
||||||
|
const linkedDelete = await request(`${base}/${account.id}/delete`, {});
|
||||||
|
|
||||||
|
assert.equal(linkedDelete.status, 409);
|
||||||
|
assert.equal(
|
||||||
|
z.object({ code: z.string() }).parse(await linkedDelete.json()).code,
|
||||||
|
"ACCOUNT_IN_USE"
|
||||||
|
);
|
||||||
|
assert.equal((await request(`${base}/${system.id}/delete`, {})).status, 404);
|
||||||
|
assert.equal((await request(`/access/global/${system.id}/delete`, {})).status, 400);
|
||||||
|
const historical = await create(company.id, { ...accountValues, login: "historical-only" });
|
||||||
|
|
||||||
|
await create(company.id, {
|
||||||
|
...membershipValues,
|
||||||
|
accountId: historical.id,
|
||||||
|
startDate: "2020-01-01",
|
||||||
|
endDate: "2020-01-02",
|
||||||
|
configuration: "removed",
|
||||||
|
});
|
||||||
|
assert.equal((await request(`${base}/${historical.id}/delete`, {})).status, 409);
|
||||||
|
for (const active of [true, false]) {
|
||||||
|
const disposable = await create(company.id, {
|
||||||
|
...accountValues,
|
||||||
|
login: `delete-${active}`,
|
||||||
|
active,
|
||||||
|
});
|
||||||
|
|
||||||
|
await update(disposable, { ...accountValues, login: `delete-renamed-${active}`, active });
|
||||||
|
const path = `${base}/${disposable.id}/delete`;
|
||||||
|
|
||||||
|
assert.equal((await request(`/access/${other.id}/${disposable.id}/delete`, {})).status, 404);
|
||||||
|
assert.equal((await request(path, { unexpected: true })).status, 400);
|
||||||
|
assert.equal((await request(`${path}?unexpected=true`, {})).status, 400);
|
||||||
|
assert.equal((await request(path, {}, "https://wrong.example")).status, 403);
|
||||||
|
assert.equal((await request(path, {}, undefined, "wrong")).status, 403);
|
||||||
|
const deleted = await request(path, {});
|
||||||
|
|
||||||
|
assert.equal(deleted.status, 200);
|
||||||
|
const snapshot = AccessResponseSchema.parse(await deleted.json());
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
snapshot.items.some((item) => item.id === disposable.id),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert.ok(snapshot.items.some((item) => item.id === account.id));
|
||||||
|
assert.equal((await request(path, {})).status, 404);
|
||||||
|
// También desaparecen los valores históricos y la unicidad liberada permite repetir el alta.
|
||||||
|
const recreated = await create(company.id, {
|
||||||
|
...accountValues,
|
||||||
|
login: `delete-renamed-${active}`,
|
||||||
|
active,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.notEqual(recreated.id, disposable.id);
|
||||||
|
}
|
||||||
|
assert.equal((await request(`/access/not-a-uuid/${account.id}/delete`, {})).status, 400);
|
||||||
|
assert.equal((await request(`${base}/not-a-uuid/delete`, {})).status, 400);
|
||||||
}
|
}
|
||||||
|
|||||||
168
apps/server/src/account-deletion.integration.test.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
import type { AccessFieldsDTO } from "@metis/companies/common";
|
||||||
|
import { DataTypes, QueryTypes } from "sequelize";
|
||||||
|
|
||||||
|
import { AccessUseCases } from "../../../modules/companies/src/api/application/access.use-cases";
|
||||||
|
import {
|
||||||
|
AccessPersistenceMapper,
|
||||||
|
SequelizeAccessRepository,
|
||||||
|
} from "../../../modules/companies/src/api/infrastructure/persistence/access-persistence";
|
||||||
|
import { CompanyPersistenceMapper } from "../../../modules/companies/src/api/infrastructure/persistence/company-persistence.mapper";
|
||||||
|
import { SequelizeCompanyRepository } from "../../../modules/companies/src/api/infrastructure/persistence/sequelize-company.repository";
|
||||||
|
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
||||||
|
|
||||||
|
import {
|
||||||
|
cleanupMembershipFixture,
|
||||||
|
prepareMembershipFixture,
|
||||||
|
} from "./asset-directory-memberships.fixture";
|
||||||
|
import { createDatabase, readDatabaseEnvironment } from "./config/database";
|
||||||
|
|
||||||
|
test("MariaDB: borrado de cuentas aislado, histórico propio y referencias externas protegidas", async () => {
|
||||||
|
const environment = readDatabaseEnvironment(process.env);
|
||||||
|
|
||||||
|
assert.equal(environment.NODE_ENV, "test");
|
||||||
|
assert.equal(environment.DB_NAME, "metis_desk_test");
|
||||||
|
const database = createDatabase(environment);
|
||||||
|
const company = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const other = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const actor = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const table = `account_delete_ref_${randomUUID().replaceAll("-", "")}`;
|
||||||
|
const query = database.getQueryInterface();
|
||||||
|
let ready = false;
|
||||||
|
let externalTable = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prepareMembershipFixture(database, "http://localhost:5173", actor, [company, other]);
|
||||||
|
ready = true;
|
||||||
|
const access = new AccessUseCases(
|
||||||
|
new SequelizeAccessRepository(
|
||||||
|
database,
|
||||||
|
new SequelizeCompanyRepository(database, new CompanyPersistenceMapper()),
|
||||||
|
new AccessPersistenceMapper()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
async function add(values: AccessFieldsDTO) {
|
||||||
|
const saved = await access.save(company, undefined, values, actor);
|
||||||
|
|
||||||
|
assert.ok(saved.isSuccess, saved.isSuccess ? "" : saved.error.code);
|
||||||
|
const item = saved.data.workspace.items.at(-1);
|
||||||
|
|
||||||
|
assert.ok(item);
|
||||||
|
|
||||||
|
return item.id.toPrimitive();
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemId = await add({ kind: "system", name: "Directorio de borrado", active: true });
|
||||||
|
const values = {
|
||||||
|
kind: "account",
|
||||||
|
systemId,
|
||||||
|
environmentId: null,
|
||||||
|
login: "disposable",
|
||||||
|
usage: "personal",
|
||||||
|
active: true,
|
||||||
|
expiresOn: null,
|
||||||
|
notes: null,
|
||||||
|
} satisfies AccessFieldsDTO;
|
||||||
|
|
||||||
|
for (const active of [true, false]) {
|
||||||
|
const account = await add({ ...values, active });
|
||||||
|
const edited = await access.save(
|
||||||
|
company,
|
||||||
|
account,
|
||||||
|
{ ...values, active, login: "renamed", usage: "shared" },
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(edited.isSuccess);
|
||||||
|
const persisted = await access.get(company);
|
||||||
|
|
||||||
|
assert.ok(persisted.isSuccess);
|
||||||
|
const saved = persisted.data.workspace.items.find(
|
||||||
|
(item) => item.id.toPrimitive() === account
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(saved?.values.data.kind === "account");
|
||||||
|
assert.equal(saved.values.data.usage, "shared");
|
||||||
|
assert.equal(
|
||||||
|
saved.history[0]?.values.data.kind === "account" && saved.history[0].values.data.usage,
|
||||||
|
"personal"
|
||||||
|
);
|
||||||
|
assert.equal(saved.history.length, 2);
|
||||||
|
const wrongScope = await access.removeAccount(other, account);
|
||||||
|
|
||||||
|
assert.ok(!wrongScope.isSuccess && wrongScope.error.code === "ACCESS_NOT_FOUND");
|
||||||
|
const deleted = await access.removeAccount(company, account);
|
||||||
|
|
||||||
|
assert.ok(deleted.isSuccess);
|
||||||
|
assert.equal(
|
||||||
|
deleted.data.workspace.items.some((item) => item.id.toPrimitive() === account),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const rows = await database.query("SELECT id FROM access_revisions WHERE itemId=:account", {
|
||||||
|
replacements: { account },
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(rows.length, 0);
|
||||||
|
const repeated = await access.removeAccount(company, account);
|
||||||
|
|
||||||
|
assert.ok(!repeated.isSuccess && repeated.error.code === "ACCESS_NOT_FOUND");
|
||||||
|
// La identidad de acceso queda libre después de borrar el historial propio.
|
||||||
|
const replacement = await add({ ...values, active, login: "renamed" });
|
||||||
|
|
||||||
|
assert.ok((await access.removeAccount(company, replacement)).isSuccess);
|
||||||
|
}
|
||||||
|
const external = await add(values);
|
||||||
|
|
||||||
|
await query.createTable(table, {
|
||||||
|
accountId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
references: { model: "access_items", key: "id" },
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
externalTable = true;
|
||||||
|
await query.bulkInsert(table, [{ accountId: external }]);
|
||||||
|
const blocked = await access.removeAccount(company, external);
|
||||||
|
|
||||||
|
assert.ok(!blocked.isSuccess && blocked.error.code === "ACCOUNT_IN_USE");
|
||||||
|
assert.equal(
|
||||||
|
(
|
||||||
|
await database.query(`SELECT accountId FROM ${query.quoteIdentifier(table)}`, {
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
})
|
||||||
|
).length,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
const preserved = await access.get(company);
|
||||||
|
|
||||||
|
assert.ok(preserved.isSuccess);
|
||||||
|
assert.ok(preserved.data.workspace.items.some((item) => item.id.toPrimitive() === external));
|
||||||
|
const wrongKind = await access.removeAccount(company, systemId);
|
||||||
|
|
||||||
|
assert.ok(!wrongKind.isSuccess && wrongKind.error.code === "ACCESS_NOT_FOUND");
|
||||||
|
const invalid = await access.removeAccount("global", external);
|
||||||
|
|
||||||
|
assert.ok(!invalid.isSuccess && invalid.error.code === "INVALID_REQUEST");
|
||||||
|
await query.dropTable(table);
|
||||||
|
externalTable = false;
|
||||||
|
assert.ok((await access.removeAccount(company, external)).isSuccess);
|
||||||
|
} finally {
|
||||||
|
if (externalTable) {
|
||||||
|
await query.dropTable(table);
|
||||||
|
}
|
||||||
|
if (ready) {
|
||||||
|
await database.query(
|
||||||
|
"DELETE v FROM access_account_values v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:company",
|
||||||
|
{ replacements: { company } }
|
||||||
|
);
|
||||||
|
await cleanupMembershipFixture(database, [company, other], actor);
|
||||||
|
}
|
||||||
|
await database.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -9,6 +9,7 @@ import helmet from "helmet";
|
|||||||
import { httpErrorDiagnostics } from "./infrastructure/http/http-error-diagnostic";
|
import { httpErrorDiagnostics } from "./infrastructure/http/http-error-diagnostic";
|
||||||
import type { HttpLogger } from "./infrastructure/http/http-logger";
|
import type { HttpLogger } from "./infrastructure/http/http-logger";
|
||||||
import { logHttpRequest } from "./infrastructure/http/http-logger";
|
import { logHttpRequest } from "./infrastructure/http/http-logger";
|
||||||
|
import { validateResourceImages } from "./infrastructure/http/resource-image-validation";
|
||||||
|
|
||||||
export function createApp(
|
export function createApp(
|
||||||
logger: HttpLogger = logHttpRequest,
|
logger: HttpLogger = logHttpRequest,
|
||||||
@ -41,7 +42,15 @@ export function createApp(
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const resourcePaths = [
|
||||||
|
"/api/resources",
|
||||||
|
"/api/resource-global-applications",
|
||||||
|
"/api/resource-global-operating-systems",
|
||||||
|
];
|
||||||
|
|
||||||
|
app.use(resourcePaths, express.json({ limit: "3mb" }));
|
||||||
app.use(express.json({ limit: "64kb" }));
|
app.use(express.json({ limit: "64kb" }));
|
||||||
|
app.use(resourcePaths, validateResourceImages);
|
||||||
app.use((request, response, next) => {
|
app.use((request, response, next) => {
|
||||||
const protocol = request.headers["x-forwarded-proto"];
|
const protocol = request.headers["x-forwarded-proto"];
|
||||||
const occurrences = request.rawHeaders.filter(
|
const occurrences = request.rawHeaders.filter(
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
ReviewGlobalOperatingSystemResponseSchema,
|
ReviewGlobalOperatingSystemResponseSchema,
|
||||||
} from "@metis/resources/common";
|
} from "@metis/resources/common";
|
||||||
import { migrateResourceScope } from "@metis/resources/module";
|
import { migrateResourceScope } from "@metis/resources/module";
|
||||||
|
import sharp from "sharp";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
||||||
@ -464,13 +465,53 @@ test("SO global: identidad, contratos, administración e instalaciones HTTP/Mari
|
|||||||
"metadatos e historial, identidad publicada fija y propagación protegida",
|
"metadatos e historial, identidad publicada fija y propagación protegida",
|
||||||
async () => {
|
async () => {
|
||||||
const update = await admin.request(`${root}/${product.id}/update`, {
|
const update = await admin.request(`${root}/${product.id}/update`, {
|
||||||
values: { ...values, notes: "Corrección documental" },
|
values: {
|
||||||
|
...values,
|
||||||
|
notes: "Corrección documental",
|
||||||
|
characteristics: {
|
||||||
|
...values.characteristics,
|
||||||
|
[field("icon")]: `data:image/png;base64,${(
|
||||||
|
await sharp({
|
||||||
|
create: {
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 0, g: 100, b: 200, alpha: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.png({ compressionLevel: 0 })
|
||||||
|
.toBuffer()
|
||||||
|
).toString("base64")}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedRevisionId: product.revisionId,
|
expectedRevisionId: product.revisionId,
|
||||||
reviewedIdentity: identity,
|
reviewedIdentity: identity,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(update.status, 200);
|
assert.equal(update.status, 200);
|
||||||
product = GetGlobalApplicationAdminResponseSchema.parse(await update.json()).resource;
|
product = GetGlobalApplicationAdminResponseSchema.parse(await update.json()).resource;
|
||||||
|
assert.equal(
|
||||||
|
product.values.characteristics[field("icon")]
|
||||||
|
?.toString()
|
||||||
|
.startsWith("data:image/png;base64,"),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const imageDetail = GetGlobalOperatingSystemResponseSchema.parse(
|
||||||
|
await (await ordinary.request(`${root}/${product.id}`)).json()
|
||||||
|
).resource;
|
||||||
|
|
||||||
|
assert.equal(imageDetail.iconPng, product.values.characteristics[field("icon")]);
|
||||||
|
const repeated = await admin.request(`${root}/${product.id}/update`, {
|
||||||
|
values: product.values,
|
||||||
|
reviewedIdentity: identity,
|
||||||
|
expectedRevisionId: product.revisionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(repeated.status, 200);
|
||||||
|
assert.equal(
|
||||||
|
GetGlobalApplicationAdminResponseSchema.parse(await repeated.json()).resource.revisionId,
|
||||||
|
product.revisionId
|
||||||
|
);
|
||||||
const changed = { ...identity, productVersion: "10" };
|
const changed = { ...identity, productVersion: "10" };
|
||||||
const response = await admin.request(`${root}/${product.id}/update`, {
|
const response = await admin.request(`${root}/${product.id}/update`, {
|
||||||
values: { ...values, name: "Windows 10 Pro", characteristics: characteristics(changed) },
|
values: { ...values, name: "Windows 10 Pro", characteristics: characteristics(changed) },
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { once } from "node:events";
|
import { once } from "node:events";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -13,6 +14,7 @@ import {
|
|||||||
GlobalApplicationTemplateResponseSchema,
|
GlobalApplicationTemplateResponseSchema,
|
||||||
InventoryResponseSchema,
|
InventoryResponseSchema,
|
||||||
ListApplicationCandidatesResponseSchema,
|
ListApplicationCandidatesResponseSchema,
|
||||||
|
ListCompanyApplicationsResponseSchema,
|
||||||
ResolveApplicationReferencesResponseSchema,
|
ResolveApplicationReferencesResponseSchema,
|
||||||
} from "@metis/resources/common";
|
} from "@metis/resources/common";
|
||||||
import { migrateResourceScope } from "@metis/resources/module";
|
import { migrateResourceScope } from "@metis/resources/module";
|
||||||
@ -65,11 +67,20 @@ test("R2.4: software global, licencia local, negociación y serialización HTTP/
|
|||||||
return item.id.toPrimitive();
|
return item.id.toPrimitive();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconPng = `data:image/png;base64,${(await readFile(new URL("../data/resource-icons/windows-10.png", import.meta.url))).toString("base64")}`;
|
||||||
|
|
||||||
const local = async (owner: string, key: string, name: string) => {
|
const local = async (owner: string, key: string, name: string) => {
|
||||||
const saved = await fixture.resourceUseCases.save(
|
const saved = await fixture.resourceUseCases.save(
|
||||||
owner,
|
owner,
|
||||||
undefined,
|
undefined,
|
||||||
{ name, templateId: template(key), active: true, notes: "", characteristics: {} },
|
{
|
||||||
|
name,
|
||||||
|
templateId: template(key),
|
||||||
|
active: true,
|
||||||
|
notes: "",
|
||||||
|
characteristics:
|
||||||
|
key === "template:certificate" ? {} : { [template("field:icon")]: iconPng },
|
||||||
|
},
|
||||||
actor
|
actor
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -162,7 +173,7 @@ test("R2.4: software global, licencia local, negociación y serialización HTTP/
|
|||||||
templateId: specification.templateId,
|
templateId: specification.templateId,
|
||||||
active,
|
active,
|
||||||
notes: "Nota privada global",
|
notes: "Nota privada global",
|
||||||
characteristics: {},
|
characteristics: { [template("field:icon")]: iconPng },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,6 +183,41 @@ test("R2.4: software global, licencia local, negociación y serialización HTTP/
|
|||||||
};
|
};
|
||||||
|
|
||||||
const global = await product("Ágil global — Producto sintético");
|
const global = await product("Ágil global — Producto sintético");
|
||||||
|
|
||||||
|
await t.test(
|
||||||
|
"iconos públicos: aplicaciones globales y locales, SO local y aislamiento entre compañías",
|
||||||
|
async () => {
|
||||||
|
const own = localIds[0];
|
||||||
|
|
||||||
|
assert.ok(own);
|
||||||
|
const response = await ordinary.request(
|
||||||
|
`/resource-application-references/${company}/resolve`,
|
||||||
|
{ purpose: "software-installation", ids: [global.id, own, os, foreignId] },
|
||||||
|
{ contract: "2" }
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
const resolved = ResolveApplicationReferencesResponseSchema.parse(await response.json());
|
||||||
|
|
||||||
|
for (const id of [global.id, own, os]) {
|
||||||
|
const entry = resolved.items.find((item) => item.id === id);
|
||||||
|
|
||||||
|
assert.ok(entry?.status === "resolved");
|
||||||
|
assert.equal(entry.reference.iconPng, iconPng);
|
||||||
|
assert.equal("notes" in entry.reference, false);
|
||||||
|
}
|
||||||
|
assert.equal(resolved.items.find((item) => item.id === foreignId)?.status, "unavailable");
|
||||||
|
const catalogResponse = await ordinary.request(
|
||||||
|
`/resource-application-candidates/${company}/catalog?search=${encodeURIComponent("Ágil")}&pageSize=100`
|
||||||
|
);
|
||||||
|
const catalogItems = ListCompanyApplicationsResponseSchema.parse(
|
||||||
|
await catalogResponse.json()
|
||||||
|
).items;
|
||||||
|
|
||||||
|
assert.equal(catalogItems.find((item) => item.id === global.id)?.iconPng, iconPng);
|
||||||
|
assert.equal(catalogItems.find((item) => item.id === own)?.iconPng, iconPng);
|
||||||
|
}
|
||||||
|
);
|
||||||
const inactive = await product("Global inactivo", false);
|
const inactive = await product("Global inactivo", false);
|
||||||
const input = {
|
const input = {
|
||||||
assetId: firstAsset,
|
assetId: firstAsset,
|
||||||
|
|||||||
250
apps/server/src/import-resource-icons.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
import { QueryTypes } from "sequelize";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { GlobalResourceUseCases } from "../../../modules/resources/src/api/application/global-resource.use-cases";
|
||||||
|
import { readOperatingSystemIdentity } from "../../../modules/resources/src/api/application/operating-system-identity-policy";
|
||||||
|
import { seedResourceCatalog } from "../../../modules/resources/src/api/domain/resource-catalog-seed";
|
||||||
|
import { ResourceCatalogError } from "../../../modules/resources/src/api/domain/resource-definition";
|
||||||
|
import { ResourceSpecification } from "../../../modules/resources/src/api/domain/value-objects/resource-values.vo";
|
||||||
|
import { migrateInventory } from "../../../modules/resources/src/api/infrastructure/persistence/resource.model";
|
||||||
|
import { migrateResourceCatalog } from "../../../modules/resources/src/api/infrastructure/persistence/resource-catalog.model";
|
||||||
|
import { ResourceCatalogPersistenceMapper } from "../../../modules/resources/src/api/infrastructure/persistence/resource-catalog-persistence.mapper";
|
||||||
|
import { migrateResourceImages } from "../../../modules/resources/src/api/infrastructure/persistence/resource-image-migration";
|
||||||
|
import { migrateResourceRevisionSpecifications } from "../../../modules/resources/src/api/infrastructure/persistence/resource-revision-specifications";
|
||||||
|
import { SequelizeGlobalResourceRepository } from "../../../modules/resources/src/api/infrastructure/persistence/sequelize-global-resource.repository";
|
||||||
|
import { SequelizeResourceCatalogRepository } from "../../../modules/resources/src/api/infrastructure/persistence/sequelize-resource-catalog.repository";
|
||||||
|
import { ResourceImageSchema } from "../../../modules/resources/src/common/resource-image";
|
||||||
|
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
||||||
|
|
||||||
|
import { createDatabase, readDatabaseEnvironment } from "./config/database";
|
||||||
|
|
||||||
|
const options = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (options.some((option) => option !== "--apply")) {
|
||||||
|
throw new Error("Usa --apply para guardar o ningún argumento para previsualizar.");
|
||||||
|
}
|
||||||
|
const directory = new URL("../data/resource-icons/", import.meta.url);
|
||||||
|
const manifest = z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
family: z.string().optional(),
|
||||||
|
file: z.string().regex(/^[a-z0-9-]+\.png$/u),
|
||||||
|
source: z.url(),
|
||||||
|
color: z.string().optional(),
|
||||||
|
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.parse(JSON.parse(await readFile(new URL("manifest.json", directory), "utf8")));
|
||||||
|
const images = new Map<string, string>();
|
||||||
|
|
||||||
|
for (const entry of manifest) {
|
||||||
|
const bytes = await readFile(new URL(entry.file, directory));
|
||||||
|
|
||||||
|
if (createHash("sha256").update(bytes).digest("hex") !== entry.sha256) {
|
||||||
|
throw new Error(`Huella del icono incorrecta: ${entry.file}`);
|
||||||
|
}
|
||||||
|
const image = ResourceImageSchema.parse(`data:image/png;base64,${bytes.toString("base64")}`);
|
||||||
|
|
||||||
|
await sharp(bytes, { limitInputPixels: 512 * 512, failOn: "warning" })
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
const key = entry.name ?? entry.family;
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
throw new Error("Icono sin producto ni familia.");
|
||||||
|
}
|
||||||
|
images.set(key, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageFor(name: string, kind: "application" | "os") {
|
||||||
|
if (kind === "application") {
|
||||||
|
return images.get(name);
|
||||||
|
}
|
||||||
|
if (name.startsWith("macOS ")) {
|
||||||
|
return images.get("macOS");
|
||||||
|
}
|
||||||
|
if (/^Windows 7\b/u.test(name)) {
|
||||||
|
return images.get("windows-7");
|
||||||
|
}
|
||||||
|
if (/^Windows 11\b/u.test(name)) {
|
||||||
|
return images.get("windows-11");
|
||||||
|
}
|
||||||
|
|
||||||
|
return /^Windows (?:8\.1|10|Server)\b/u.test(name) ? images.get("windows-10") : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = createDatabase(readDatabaseEnvironment(process.env));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const products = z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
stockKey: z.enum(["template:application", "template:os"]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.parse(
|
||||||
|
await database.query(
|
||||||
|
"SELECT r.id,r.name,t.stockKey FROM resources r JOIN resource_definitions t ON t.id=r.templateId WHERE r.scope=:scope AND t.stockKey IN (:templates) ORDER BY r.id",
|
||||||
|
{
|
||||||
|
replacements: { scope: "global", templates: ["template:application", "template:os"] },
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const prepared = products.map((product) => ({
|
||||||
|
...product,
|
||||||
|
kind: product.stockKey === "template:os" ? ("os" as const) : ("application" as const),
|
||||||
|
image: imageFor(product.name, product.stockKey === "template:os" ? "os" : "application"),
|
||||||
|
}));
|
||||||
|
const missing = prepared.filter((product) => !product.image);
|
||||||
|
|
||||||
|
if (missing.length) {
|
||||||
|
throw new Error(`Faltan iconos para: ${missing.map((product) => product.name).join(", ")}`);
|
||||||
|
}
|
||||||
|
process.stdout.write(
|
||||||
|
`Productos con icono preparado: ${prepared.length}. Aplicaciones: ${prepared.filter((product) => product.kind === "application").length}. Sistemas operativos: ${prepared.filter((product) => product.kind === "os").length}.\n`
|
||||||
|
);
|
||||||
|
if (options.includes("--apply")) {
|
||||||
|
const administrators = z.array(z.object({ id: z.uuid() })).parse(
|
||||||
|
await database.query("SELECT id FROM users WHERE active=1 AND isInitialAdmin=1", {
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const administrator = administrators.length === 1 ? administrators[0] : undefined;
|
||||||
|
|
||||||
|
if (!administrator) {
|
||||||
|
throw new Error("La importación requiere un único administrador inicial activo.");
|
||||||
|
}
|
||||||
|
const actor = UniqueID.create(administrator.id);
|
||||||
|
|
||||||
|
if (!actor.isSuccess) {
|
||||||
|
throw actor.error;
|
||||||
|
}
|
||||||
|
await migrateResourceCatalog(database);
|
||||||
|
await migrateInventory(database);
|
||||||
|
await migrateResourceRevisionSpecifications(database);
|
||||||
|
await migrateResourceImages(database);
|
||||||
|
const catalogRepository = new SequelizeResourceCatalogRepository(
|
||||||
|
database,
|
||||||
|
new ResourceCatalogPersistenceMapper()
|
||||||
|
);
|
||||||
|
const seeded = await catalogRepository.update(
|
||||||
|
Maybe.none(),
|
||||||
|
(catalog) => Result.ok(seedResourceCatalog(catalog)),
|
||||||
|
actor.data
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!seeded.isSuccess) {
|
||||||
|
throw new Error(seeded.error.code);
|
||||||
|
}
|
||||||
|
const icon = seeded.data.definitions.find(
|
||||||
|
(field) => field.stockKey.getOrUndefined() === "field:icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
throw new Error("Campo de icono no disponible.");
|
||||||
|
}
|
||||||
|
const iconId = icon.id.toPrimitive();
|
||||||
|
let updated = 0;
|
||||||
|
let preserved = 0;
|
||||||
|
|
||||||
|
for (const product of prepared) {
|
||||||
|
const id = UniqueID.create(product.id);
|
||||||
|
|
||||||
|
if (!(id.isSuccess && product.image)) {
|
||||||
|
throw new Error("Producto de importación inválido.");
|
||||||
|
}
|
||||||
|
const repository = new SequelizeGlobalResourceRepository(database, product.kind);
|
||||||
|
let current = await repository.admin(id.data);
|
||||||
|
|
||||||
|
if (!current.isSuccess) {
|
||||||
|
throw new Error(current.error.code);
|
||||||
|
}
|
||||||
|
if (current.data.values.characteristics[iconId]) {
|
||||||
|
preserved++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!current.data.specification.fields.some((field) => field.fieldId === iconId)) {
|
||||||
|
const template = await repository.template({ existing: true, kind: product.kind });
|
||||||
|
const iconField = template.isSuccess
|
||||||
|
? template.data.fields.find((field) => field.fieldId === iconId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!iconField) {
|
||||||
|
throw new Error(`La plantilla no incluye Icono: ${product.name}`);
|
||||||
|
}
|
||||||
|
const synchronized = await repository.write(id.data, ({ resource }) => {
|
||||||
|
const previous = resource.getOrUndefined();
|
||||||
|
|
||||||
|
if (!previous) {
|
||||||
|
return Result.fail(new ResourceCatalogError("RESOURCE_NOT_FOUND"));
|
||||||
|
}
|
||||||
|
const specification = ResourceSpecification.create({
|
||||||
|
...previous.specification.toPrimitive(),
|
||||||
|
fields: [
|
||||||
|
...previous.specification.toPrimitive().fields,
|
||||||
|
{ ...iconField, required: false },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return specification.isSuccess
|
||||||
|
? previous.synchronizeGlobalSpecification(
|
||||||
|
specification.data,
|
||||||
|
actor.data,
|
||||||
|
new Date().toISOString()
|
||||||
|
)
|
||||||
|
: specification;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!synchronized.isSuccess) {
|
||||||
|
throw new Error(synchronized.error.code);
|
||||||
|
}
|
||||||
|
current = await repository.admin(id.data);
|
||||||
|
if (!current.isSuccess) {
|
||||||
|
throw new Error(current.error.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Habilita únicamente este comando autorizado; no altera los gates del servidor.
|
||||||
|
const useCases = new GlobalResourceUseCases(repository, true, product.kind);
|
||||||
|
const result = await useCases.update(
|
||||||
|
id.data,
|
||||||
|
{
|
||||||
|
...(product.kind === "os"
|
||||||
|
? { reviewedIdentity: readOperatingSystemIdentity(seeded.data, current.data.values) }
|
||||||
|
: {}),
|
||||||
|
expectedRevisionId: current.data.revisionId,
|
||||||
|
values: {
|
||||||
|
...current.data.values,
|
||||||
|
characteristics: { ...current.data.values.characteristics, [iconId]: product.image },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actor.data
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
throw new Error(`${product.name}: ${result.error.code}`);
|
||||||
|
}
|
||||||
|
const verified = await repository.admin(id.data);
|
||||||
|
|
||||||
|
if (!verified.isSuccess || verified.data.values.characteristics[iconId] !== product.image) {
|
||||||
|
throw new Error(`Verificación de icono fallida: ${product.name}`);
|
||||||
|
}
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
process.stdout.write(
|
||||||
|
`Iconos guardados: ${updated}. Iconos previos conservados: ${preserved}.\n`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
process.stdout.write("Previsualización: no se ha modificado ningún dato.\n");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await database.close();
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import type { RequestHandler } from "express";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import {
|
||||||
|
RESOURCE_IMAGE_MAX_DIMENSION,
|
||||||
|
ResourceImageSchema,
|
||||||
|
} from "../../../../../modules/resources/src/common/resource-image";
|
||||||
|
|
||||||
|
const input = z.object({
|
||||||
|
values: z.object({ characteristics: z.record(z.string(), z.unknown()) }),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Decodifica los PNG recibidos por las rutas de escritura de recursos antes de guardarlos. */
|
||||||
|
export const validateResourceImages: RequestHandler = (request, response, next) => {
|
||||||
|
const parsed = input.safeParse(request.body);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
next();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const images = Object.values(parsed.data.values.characteristics).filter(
|
||||||
|
(value): value is string => typeof value === "string" && value.startsWith("data:image/")
|
||||||
|
);
|
||||||
|
|
||||||
|
async function validate() {
|
||||||
|
for (const image of images) {
|
||||||
|
if (!ResourceImageSchema.safeParse(image).success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const decoder = sharp(Buffer.from(image.slice(image.indexOf(",") + 1), "base64"), {
|
||||||
|
limitInputPixels: RESOURCE_IMAGE_MAX_DIMENSION ** 2,
|
||||||
|
failOn: "warning",
|
||||||
|
});
|
||||||
|
const metadata = await decoder.metadata();
|
||||||
|
|
||||||
|
if (metadata.format !== "png" || (metadata.pages ?? 1) !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await decoder.raw().toBuffer();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate()
|
||||||
|
.then((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
.status(400)
|
||||||
|
.json({ code: "INVALID_REQUEST", requestId: response.getHeader("x-request-id") });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
};
|
||||||
29
apps/server/src/rename-workstations.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { QueryTypes } from "sequelize";
|
||||||
|
|
||||||
|
import { renameWorkstationsFromPrimaryAssets } from "../../../modules/assets/src/api/infrastructure/persistence/rename-workstations-from-primary-assets";
|
||||||
|
|
||||||
|
import { createDatabase, readDatabaseEnvironment } from "./config/database";
|
||||||
|
|
||||||
|
if (process.argv.slice(2).join(" ") !== "--apply") {
|
||||||
|
throw new Error("Corrección explícita de nombres: requiere --apply.");
|
||||||
|
}
|
||||||
|
const database = createDatabase(readDatabaseEnvironment(process.env));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const administrators = await database.query<{ id: string }>(
|
||||||
|
"SELECT id FROM users WHERE isInitialAdmin=1 AND active=1",
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
const administrator = administrators.length === 1 ? administrators[0] : undefined;
|
||||||
|
|
||||||
|
if (!administrator) {
|
||||||
|
throw new Error("La corrección requiere un único administrador inicial activo.");
|
||||||
|
}
|
||||||
|
const renamed = await renameWorkstationsFromPrimaryAssets(database, administrator.id);
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`Puestos renombrados: ${renamed}. Revisiones registradas con el administrador inicial.\n`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await database.close();
|
||||||
|
}
|
||||||
189
apps/server/src/resource-images.test.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { once } from "node:events";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { crc32 } from "node:zlib";
|
||||||
|
|
||||||
|
import sharp from "sharp";
|
||||||
|
|
||||||
|
import { ResourceInputSchema } from "../../../modules/resources/src/api/domain/resource-values";
|
||||||
|
import {
|
||||||
|
ResourceSpecification,
|
||||||
|
ResourceValues,
|
||||||
|
} from "../../../modules/resources/src/api/domain/value-objects/resource-values.vo";
|
||||||
|
import { ResourceImageSchema } from "../../../modules/resources/src/common/resource-image";
|
||||||
|
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
||||||
|
|
||||||
|
import { createApp } from "./app";
|
||||||
|
|
||||||
|
async function png(width = 512, height = 512) {
|
||||||
|
return sharp({
|
||||||
|
create: { width, height, channels: 4, background: { r: 40, g: 80, b: 160, alpha: 1 } },
|
||||||
|
})
|
||||||
|
.png({ compressionLevel: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataUrl = (bytes: Buffer) => `data:image/png;base64,${bytes.toString("base64")}`;
|
||||||
|
|
||||||
|
function chunk(type: string, data: Buffer) {
|
||||||
|
const header = Buffer.alloc(4);
|
||||||
|
|
||||||
|
header.writeUInt32BE(data.length);
|
||||||
|
const payload = Buffer.concat([Buffer.from(type), data]);
|
||||||
|
const checksum = Buffer.alloc(4);
|
||||||
|
|
||||||
|
checksum.writeUInt32BE(crc32(payload));
|
||||||
|
|
||||||
|
return Buffer.concat([header, payload, checksum]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function undecodablePng() {
|
||||||
|
const header = Buffer.alloc(13);
|
||||||
|
|
||||||
|
header.writeUInt32BE(1, 0);
|
||||||
|
header.writeUInt32BE(1, 4);
|
||||||
|
header[8] = 8;
|
||||||
|
header[9] = 6;
|
||||||
|
|
||||||
|
return Buffer.concat([
|
||||||
|
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
||||||
|
chunk("IHDR", header),
|
||||||
|
chunk("IDAT", Buffer.from("invalid compressed pixels")),
|
||||||
|
chunk("IEND", Buffer.alloc(0)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("PNG: admite los límites, rechaza otros formatos, dimensiones y contenedores corruptos", async () => {
|
||||||
|
const valid = await png();
|
||||||
|
|
||||||
|
assert.equal(ResourceImageSchema.safeParse(dataUrl(valid)).success, true);
|
||||||
|
assert.equal(ResourceImageSchema.safeParse(dataUrl(await png(1, 1))).success, true);
|
||||||
|
for (const bytes of [
|
||||||
|
await png(513, 1),
|
||||||
|
await png(1, 513),
|
||||||
|
valid.subarray(0, valid.length - 1),
|
||||||
|
Buffer.from("not an image"),
|
||||||
|
Buffer.concat([valid, Buffer.from("extra")]),
|
||||||
|
Buffer.concat([valid.subarray(0, 33), chunk("acTL", Buffer.alloc(8)), valid.subarray(33)]),
|
||||||
|
]) {
|
||||||
|
assert.equal(ResourceImageSchema.safeParse(dataUrl(bytes)).success, false);
|
||||||
|
}
|
||||||
|
const corrupt = Buffer.from(valid);
|
||||||
|
|
||||||
|
corrupt[40] = (corrupt[40] ?? 0) ^ 1;
|
||||||
|
assert.equal(ResourceImageSchema.safeParse(dataUrl(corrupt)).success, false);
|
||||||
|
assert.equal(
|
||||||
|
ResourceImageSchema.safeParse(`data:image/jpeg;base64,${valid.toString("base64")}`).success,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert.equal(ResourceImageSchema.safeParse(dataUrl(undecodablePng())).success, true);
|
||||||
|
assert.equal(
|
||||||
|
ResourceImageSchema.safeParse(dataUrl(Buffer.alloc(2 * 1024 * 1024 + 1))).success,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("característica de imagen: valida el tipo, la obligatoriedad, la retirada y la igualdad semántica", async () => {
|
||||||
|
const templateId = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const fieldId = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const specification = ResourceSpecification.create({
|
||||||
|
templateId,
|
||||||
|
templateName: "Aplicación",
|
||||||
|
fields: [{ fieldId, name: "Icono", type: "image", unit: "", choices: [], required: false }],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(specification.isSuccess);
|
||||||
|
const input = {
|
||||||
|
templateId,
|
||||||
|
name: "Producto",
|
||||||
|
notes: "",
|
||||||
|
active: true,
|
||||||
|
characteristics: { [fieldId]: dataUrl(await png(8, 8)) },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal(ResourceInputSchema.safeParse(input).success, true);
|
||||||
|
const first = ResourceValues.create(input, specification.data);
|
||||||
|
const repeated = ResourceValues.create(
|
||||||
|
{ ...input, characteristics: { ...input.characteristics } },
|
||||||
|
specification.data
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(first.isSuccess && repeated.isSuccess && first.data.equals(repeated.data));
|
||||||
|
for (const value of [true, 12, "https://example.test/icon.png", "not a png"]) {
|
||||||
|
assert.equal(
|
||||||
|
ResourceValues.create({ ...input, characteristics: { [fieldId]: value } }, specification.data)
|
||||||
|
.isSuccess,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert.equal(
|
||||||
|
ResourceValues.create({ ...input, characteristics: {} }, specification.data).isSuccess,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
ResourceValues.create({ ...input, characteristics: { [fieldId]: "" } }, specification.data)
|
||||||
|
.isSuccess,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const required = ResourceSpecification.create({
|
||||||
|
...specification.data.toPrimitive(),
|
||||||
|
fields: specification.data.toPrimitive().fields.map((field) => ({ ...field, required: true })),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(required.isSuccess);
|
||||||
|
assert.equal(
|
||||||
|
ResourceValues.create({ ...input, characteristics: {} }, required.data).isSuccess,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("HTTP: PNG de 512px superior a 64KiB, contenido inválido y límites restringidos a recursos", async () => {
|
||||||
|
const server = createApp(() => undefined, {
|
||||||
|
middleware: (_request, _response, next) => next(),
|
||||||
|
register: (app) => {
|
||||||
|
app.post("/api/resource-global-applications", (_request, response) =>
|
||||||
|
response.json({ saved: true })
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}).listen(0, "127.0.0.1");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await once(server, "listening");
|
||||||
|
const address = server.address();
|
||||||
|
|
||||||
|
assert.ok(address && typeof address !== "string");
|
||||||
|
const base = `http://127.0.0.1:${address.port}`;
|
||||||
|
|
||||||
|
for (const [value, status] of [
|
||||||
|
[dataUrl(await png()), 200],
|
||||||
|
[dataUrl(await png(513, 1)), 400],
|
||||||
|
[dataUrl(undecodablePng()), 400],
|
||||||
|
["data:image/png;base64,broken", 400],
|
||||||
|
] as const) {
|
||||||
|
const response = await fetch(`${base}/api/resource-global-applications`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ values: { characteristics: { icon: value } } }),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(response.status, status);
|
||||||
|
}
|
||||||
|
for (const [path, size] of [
|
||||||
|
["/api/__health", 70_000],
|
||||||
|
["/api/resource-global-applications", 3 * 1024 * 1024],
|
||||||
|
] as const) {
|
||||||
|
const response = await fetch(`${base}${path}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ value: "x".repeat(size) }),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(response.status, 413);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
server.close((error) => (error ? reject(error) : resolve()));
|
||||||
|
server.closeAllConnections();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
112
apps/server/src/workstation-names.integration.test.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
import { WorkstationUseCases } from "../../../modules/assets/src/api/application/workstation.use-cases";
|
||||||
|
import { renameWorkstationsFromPrimaryAssets } from "../../../modules/assets/src/api/infrastructure/persistence/rename-workstations-from-primary-assets";
|
||||||
|
import { SequelizeAssetRepository } from "../../../modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository";
|
||||||
|
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
|
||||||
|
|
||||||
|
import {
|
||||||
|
cleanupMembershipFixture,
|
||||||
|
prepareMembershipFixture,
|
||||||
|
} from "./asset-directory-memberships.fixture";
|
||||||
|
import { createDatabase, readDatabaseEnvironment } from "./config/database";
|
||||||
|
|
||||||
|
test("nombres de puestos: principal, historial, varias compañías e idempotencia", async () => {
|
||||||
|
const environment = readDatabaseEnvironment(process.env);
|
||||||
|
|
||||||
|
assert.equal(environment.DB_NAME, "metis_desk_test");
|
||||||
|
const database = createDatabase(environment);
|
||||||
|
const actor = UniqueID.generateNewID().toPrimitive();
|
||||||
|
const companies = [
|
||||||
|
UniqueID.generateNewID().toPrimitive(),
|
||||||
|
UniqueID.generateNewID().toPrimitive(),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fixture = await prepareMembershipFixture(
|
||||||
|
database,
|
||||||
|
environment.APP_ORIGIN,
|
||||||
|
actor,
|
||||||
|
companies
|
||||||
|
);
|
||||||
|
const useCases = new WorkstationUseCases(new SequelizeAssetRepository(database));
|
||||||
|
const originals = [];
|
||||||
|
|
||||||
|
for (const company of companies) {
|
||||||
|
const asset = await fixture.asset(company);
|
||||||
|
const name = asset.values.toPrimitive().name;
|
||||||
|
|
||||||
|
for (const values of [
|
||||||
|
{
|
||||||
|
name: "Nombre anterior",
|
||||||
|
active: true,
|
||||||
|
assetIds: [asset.id.toPrimitive()],
|
||||||
|
computerAssetId: asset.id.toPrimitive(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nombre anterior inactivo",
|
||||||
|
active: false,
|
||||||
|
assetIds: [asset.id.toPrimitive()],
|
||||||
|
computerAssetId: asset.id.toPrimitive(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
active: false,
|
||||||
|
assetIds: [asset.id.toPrimitive()],
|
||||||
|
computerAssetId: asset.id.toPrimitive(),
|
||||||
|
},
|
||||||
|
{ name: "Sin principal", active: false, assetIds: [], computerAssetId: null },
|
||||||
|
]) {
|
||||||
|
const saved = await useCases.save(
|
||||||
|
company,
|
||||||
|
undefined,
|
||||||
|
{ ...values, siteId: null, personId: null },
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(saved.isSuccess);
|
||||||
|
originals.push({ company, workstation: saved.data.workstation, name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await assert.rejects(() =>
|
||||||
|
renameWorkstationsFromPrimaryAssets(database, UniqueID.generateNewID().toPrimitive())
|
||||||
|
);
|
||||||
|
assert.equal(await renameWorkstationsFromPrimaryAssets(database, actor), 4);
|
||||||
|
assert.equal(await renameWorkstationsFromPrimaryAssets(database, actor), 0);
|
||||||
|
|
||||||
|
for (const { company, workstation, name } of originals) {
|
||||||
|
const inventory = await fixture.assetUseCases.get(company);
|
||||||
|
|
||||||
|
assert.ok(inventory.isSuccess);
|
||||||
|
const current = inventory.data.workstations.find((item) => item.id.equals(workstation.id));
|
||||||
|
|
||||||
|
assert.ok(current);
|
||||||
|
const previous = workstation.values.toPrimitive();
|
||||||
|
const changed = previous.computerAssetId && previous.name !== name;
|
||||||
|
|
||||||
|
assert.deepEqual(current.values.toPrimitive(), {
|
||||||
|
...previous,
|
||||||
|
name: changed ? name : previous.name,
|
||||||
|
});
|
||||||
|
assert.equal(current.history.length, workstation.history.length + (changed ? 1 : 0));
|
||||||
|
assert.deepEqual(current.history.slice(0, workstation.history.length), workstation.history);
|
||||||
|
assert.equal(current.history.at(-1)?.actorId.toPrimitive(), actor);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
for (const company of companies) {
|
||||||
|
const replacements = { company };
|
||||||
|
|
||||||
|
await database.query(
|
||||||
|
"DELETE r FROM workstation_revisions r JOIN workstations w ON w.id=r.workstationId WHERE w.companyId=:company",
|
||||||
|
{ replacements }
|
||||||
|
);
|
||||||
|
await database.query("DELETE FROM workstation_assets WHERE companyId=:company", {
|
||||||
|
replacements,
|
||||||
|
});
|
||||||
|
await database.query("DELETE FROM workstations WHERE companyId=:company", { replacements });
|
||||||
|
}
|
||||||
|
await cleanupMembershipFixture(database, companies, actor);
|
||||||
|
await database.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,5 +1,42 @@
|
|||||||
# Componentes de interfaz
|
# Componentes de interfaz
|
||||||
|
|
||||||
|
## Iconos en listados y selectores de software
|
||||||
|
|
||||||
|
`SoftwareProductName`, en Resources, mantiene el PNG de 24 px unido al nombre en
|
||||||
|
catálogos globales, listados de compañía, instalaciones, SO actuales, perfiles y
|
||||||
|
resúmenes de puestos. El nombre sigue siendo texto y conserva la etiqueta accesible
|
||||||
|
del enlace o control; el icono adyacente es decorativo. Si no existe imagen, su
|
||||||
|
espacio muestra «—». La imagen no depende de una columna que pueda quedar oculta.
|
||||||
|
|
||||||
|
Las referencias públicas de aplicaciones y SO incluyen `iconPng` opcional, tanto
|
||||||
|
globales como particulares. `resourceIcon` obtiene la característica mediante
|
||||||
|
`field:icon` cuando el consumidor dispone del inventario y catálogo; no busca por
|
||||||
|
el nombre editable del campo ni toma cualquier imagen de la ficha.
|
||||||
|
|
||||||
|
`RemotePickerOption.leading` y `SelectOption.leading` aceptan contenido React junto
|
||||||
|
a su etiqueta de texto. No conocen imágenes, recursos, DTO ni endpoints; el módulo
|
||||||
|
consumidor aporta el contenido y declara los adornos con `aria-hidden`. Se conservan
|
||||||
|
las etiquetas de selección, el orden recibido, el clic de fila y la primera columna
|
||||||
|
compacta de selección. `characteristicsSummary` omite valores de tipo `image` para
|
||||||
|
que una URI de imagen nunca aparezca como resumen textual de una fila.
|
||||||
|
|
||||||
|
## Imágenes PNG en características de recursos
|
||||||
|
|
||||||
|
`ResourceImageField` compone Input y Button de shadcn/ui: selección PNG, ayuda,
|
||||||
|
previsualización, sustitución, retirada y errores asociados. Usa el valor del
|
||||||
|
formulario como única fuente de verdad; solo conserva el estado de lectura y error.
|
||||||
|
Mantiene el valor anterior ante un archivo inválido y bloquea el envío durante
|
||||||
|
la lectura o mientras persiste el error. La retirada por teclado devuelve el foco
|
||||||
|
al selector. Los editores globales y particulares lo reciben mediante
|
||||||
|
`ResourceCharacteristicControl`; no conoce endpoints ni persistencia.
|
||||||
|
|
||||||
|
`ResourceImage` presenta el PNG validado con dimensiones reservadas, proporciones
|
||||||
|
originales, carga diferida y texto alternativo aportado por el consumidor. Las
|
||||||
|
fichas, revisiones y listados reutilizan esta presentación; la ausencia muestra «—».
|
||||||
|
El fondo blanco pertenece al soporte de la imagen para que los logos oscuros se
|
||||||
|
lean en ambos temas. Los contratos y fuentes figuran en
|
||||||
|
[iconos de recursos](iconos-de-recursos.md).
|
||||||
|
|
||||||
## Submenú de Personas en la navegación de compañía
|
## Submenú de Personas en la navegación de compañía
|
||||||
|
|
||||||
`CompanySectionNavigation` compone `SidebarMenu` y `SidebarMenuItem` de shadcn/ui
|
`CompanySectionNavigation` compone `SidebarMenu` y `SidebarMenuItem` de shadcn/ui
|
||||||
@ -1797,3 +1834,21 @@ El contorno de foco opaco se aplica localmente a la ficha SMB. Se mantienen las
|
|||||||
primitivas compartidas y las operaciones y consultas existentes.
|
primitivas compartidas y las operaciones y consultas existentes.
|
||||||
|
|
||||||
Véanse [la auditoría y las evidencias de la ficha](ficha-de-carpeta-compartida.md).
|
Véanse [la auditoría y las evidencias de la ficha](ficha-de-carpeta-compartida.md).
|
||||||
|
|
||||||
|
## Eliminación de cuentas
|
||||||
|
|
||||||
|
`AccountDeleteDialog`, en `companies/web/access/delete/ui`, reutiliza `ConfirmDeleteDialog`
|
||||||
|
de `@repo/rdx-ui` desde el menú de cuentas de `AccessWorkspacePage`. El consumidor aporta
|
||||||
|
el identificador de acceso, los textos es/en y la mutación. Solo se cierra al confirmar
|
||||||
|
el éxito; los errores permanecen en el diálogo, incluido el bloqueo por relaciones
|
||||||
|
actuales o históricas. La respuesta actualiza exclusivamente la caché de accesos de la
|
||||||
|
compañía. El componente compartido conserva cancelación, estado pendiente y teclado.
|
||||||
|
|
||||||
|
## Cambio de uso de cuentas
|
||||||
|
|
||||||
|
El formulario existente de edición de cuentas permite seleccionar Personal, Compartida o
|
||||||
|
Técnica en «Uso». Sistema y entorno conservan su bloqueo. La API guarda el cambio sobre
|
||||||
|
el mismo UUID con una revisión nueva; repetir los mismos datos no añade historial.
|
||||||
|
El servidor rechaza titulares solapados al pasar a Personal y cambios entre uso técnico
|
||||||
|
y de acceso cuando existen relaciones con personas, incluidas las cerradas. El error
|
||||||
|
es/en permanece en el formulario con el borrador y permite corregir la selección.
|
||||||
|
|||||||
@ -31,7 +31,9 @@ Desactivar una definición impide seleccionarla en nuevas altas, pero conserva l
|
|||||||
|
|
||||||
Datos: compañía, sistema, entorno opcional, identificador de acceso, uso personal/compartido/técnico, estado activo, caducidad opcional y observaciones.
|
Datos: compañía, sistema, entorno opcional, identificador de acceso, uso personal/compartido/técnico, estado activo, caducidad opcional y observaciones.
|
||||||
|
|
||||||
Compañía, sistema, entorno y uso quedan fijos. El identificador, estado, caducidad y observaciones pueden cambiar, conservando UUID e historial.
|
Compañía, sistema y entorno quedan fijos. El identificador, uso, estado, caducidad y observaciones pueden cambiar, conservando UUID e historial.
|
||||||
|
|
||||||
|
El cambio de uso debe respetar las relaciones con personas registradas: compartida a personal requiere periodos de titulares sin solapamientos, incluidos los históricos. No se permite cambiar entre uso técnico y de acceso si existen relaciones con personas, porque la responsabilidad y la autorización tienen contratos de configuración diferentes. No se convierten ni eliminan relaciones automáticamente.
|
||||||
|
|
||||||
La combinación compañía + sistema + entorno + identificador es única, incluyendo cuentas inactivas. El identificador distingue mayúsculas y minúsculas; se eliminan los espacios exteriores.
|
La combinación compañía + sistema + entorno + identificador es única, incluyendo cuentas inactivas. El identificador distingue mayúsculas y minúsculas; se eliminan los espacios exteriores.
|
||||||
|
|
||||||
@ -47,6 +49,11 @@ Una fecha vencida no desactiva automáticamente la cuenta: genera un aviso si si
|
|||||||
|
|
||||||
Las contraseñas y secretos no forman parte de los campos de las cuentas.
|
Las contraseñas y secretos no forman parte de los campos de las cuentas.
|
||||||
|
|
||||||
|
El menú de una cuenta permite eliminarla con confirmación si no tiene referencias actuales
|
||||||
|
ni históricas. Se elimina también su historial propio, sin modificar el sistema externo.
|
||||||
|
Las cuentas con relaciones se conservan y pueden desactivarse. Véase
|
||||||
|
[eliminación de registros](eliminacion-de-registros.md#cuentas-de-sistemas-externos).
|
||||||
|
|
||||||
## Grupos y plantillas
|
## Grupos y plantillas
|
||||||
|
|
||||||
Cada grupo pertenece a una compañía, sistema y entorno opcional. Tiene nombre visible, identificador real en el sistema, descripción opcional y estado activo. Compañía, sistema y entorno no cambian tras el alta.
|
Cada grupo pertenece a una compañía, sistema y entorno opcional. Tiene nombre visible, identificador real en el sistema, descripción opcional y estado activo. Compañía, sistema y entorno no cambian tras el alta.
|
||||||
|
|||||||
@ -9,3 +9,20 @@ Cada operación valida los UUID, el cuerpo vacío y la consulta vacía, exige au
|
|||||||
Las tareas de procesos que ya copiaron una referencia mantienen su descripción e historial: la eliminación del registro de origen no elimina el proceso. Al refrescar su revisión actual, ese registro deja de aparecer.
|
Las tareas de procesos que ya copiaron una referencia mantienen su descripción e historial: la eliminación del registro de origen no elimina el proceso. Al refrescar su revisión actual, ese registro deja de aparecer.
|
||||||
|
|
||||||
Las entidades con condiciones de eliminación (perfiles, puestos, activos, recursos y catálogos utilizados) quedan fuera de esta ampliación. Las eliminaciones de asignaciones a departamentos y procesos de personas ya estaban disponibles.
|
Las entidades con condiciones de eliminación (perfiles, puestos, activos, recursos y catálogos utilizados) quedan fuera de esta ampliación. Las eliminaciones de asignaciones a departamentos y procesos de personas ya estaban disponibles.
|
||||||
|
|
||||||
|
## Cuentas de sistemas externos
|
||||||
|
|
||||||
|
En Compañías → Cuentas y accesos → Cuentas, el menú de cada fila permite «Eliminar cuenta»
|
||||||
|
con confirmación. El borrado definitivo incluye el historial propio y está disponible para
|
||||||
|
cuentas activas o inactivas, siempre que no tengan referencias actuales ni históricas:
|
||||||
|
titulares, pertenencias a grupos, permisos, perfiles, correo, carpetas u otros registros.
|
||||||
|
Si la cuenta está en uso, se conserva todo y el diálogo propone desactivarla.
|
||||||
|
Esta operación solo elimina la documentación de Metis Desk; no elimina cuentas externas.
|
||||||
|
|
||||||
|
`POST /api/access/:companyId/:id/delete` requiere UUID válidos, cuerpo `{}`, consulta vacía,
|
||||||
|
sesión autenticada y CSRF. Devuelve el espacio actualizado con 200; un registro ausente,
|
||||||
|
de otra compañía o de otro tipo devuelve 404; una cuenta referenciada devuelve
|
||||||
|
409 `ACCOUNT_IN_USE`. Repetir el borrado devuelve 404 sin efectos adicionales.
|
||||||
|
La transacción bloquea catálogo, compañía, espacio y cuenta, comprueba las FK entrantes
|
||||||
|
y elimina únicamente referencias salientes, valores e historial propios. No borra
|
||||||
|
relaciones en cascada ni permite eliminar catálogos a través de esta ruta.
|
||||||
|
|||||||
133
docs/iconos-de-recursos.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# Iconos PNG de recursos
|
||||||
|
|
||||||
|
El campo global de serie `field:icon`, «Icono», es de tipo `image`. Aplicación,
|
||||||
|
Sistema operativo y Licencia de software lo incluyen como característica opcional.
|
||||||
|
Se añade una sola vez a las plantillas existentes, conservando sus UUID, campos,
|
||||||
|
orden, secciones y personalizaciones. Los reinicios no reponen un binding retirado.
|
||||||
|
El icono de producto es independiente del icono vectorial de la plantilla.
|
||||||
|
|
||||||
|
## Selección y validación
|
||||||
|
|
||||||
|
El selector compartido admite PNG estáticos de hasta 512 × 512 píxeles y 2 MiB.
|
||||||
|
Mantiene las proporciones y permite previsualizar, sustituir y quitar la imagen.
|
||||||
|
Una selección inválida conserva el valor anterior y bloquea el envío hasta
|
||||||
|
corregirla o retirarla. La lectura pendiente también bloquea el envío. La ayuda,
|
||||||
|
el estado y los errores están disponibles en español e inglés.
|
||||||
|
|
||||||
|
El contrato compartido valida firma PNG, dimensiones, integridad CRC de los chunks,
|
||||||
|
datos de imagen, final del contenedor y ausencia de animación. El servidor decodifica
|
||||||
|
además los píxeles con Sharp, con límite de dimensiones. No basta la extensión del
|
||||||
|
archivo ni el MIME declarado. Las rutas JSON de recursos admiten hasta 3 MiB;
|
||||||
|
las demás conservan su límite de 64 KiB.
|
||||||
|
|
||||||
|
Las fichas, revisiones y columnas de características muestran la imagen, sin
|
||||||
|
imprimir base64. Los listados de aplicaciones y SO muestran el icono junto al nombre.
|
||||||
|
La ausencia se representa con «—». El soporte blanco del icono mantiene visibles
|
||||||
|
los logotipos oscuros también con el tema oscuro.
|
||||||
|
|
||||||
|
## Persistencia e historial
|
||||||
|
|
||||||
|
La imagen viaja como una URI `data:image/png;base64,...`, validada, dentro de la
|
||||||
|
característica. Se conserva como valor escalar `valueType=image` en las tablas
|
||||||
|
relacionales `resource_characteristics` y `resource_revision_characteristics`.
|
||||||
|
No se almacena JSON ni se crean URLs remotas dependientes de terceros.
|
||||||
|
|
||||||
|
`migrateResourceImages` amplía explícitamente sus columnas `textValue` a MEDIUMTEXT
|
||||||
|
de forma idempotente. Las especificaciones y revisiones admiten el nuevo tipo.
|
||||||
|
Las escrituras normales conservan UUID e historial; repetir un guardado idéntico
|
||||||
|
no añade revisiones.
|
||||||
|
|
||||||
|
## Carga inicial de los catálogos locales
|
||||||
|
|
||||||
|
Las imágenes y su procedencia se conservan en
|
||||||
|
[`apps/server/data/resource-icons/manifest.json`](../apps/server/data/resource-icons/manifest.json).
|
||||||
|
Cada entrada identifica producto o familia, archivo PNG, URL de origen y SHA-256.
|
||||||
|
Se incluyen 40 aplicaciones y las familias Windows 7, Windows 8.1/10/Server,
|
||||||
|
Windows 11 y macOS. Las versiones y ediciones de una misma familia comparten logo.
|
||||||
|
Los iconos de menor tamaño mantienen su resolución original; ninguno supera 512 px
|
||||||
|
por dimensión.
|
||||||
|
|
||||||
|
Fuentes: [Dashboard Icons](https://github.com/homarr-labs/dashboard-icons), fijado al
|
||||||
|
commit `83f32e08257b66fbd971ac8d7645f5b38d52e85d`,
|
||||||
|
[Simple Icons 13.21.0](https://github.com/simple-icons/simple-icons/tree/13.21.0),
|
||||||
|
[PowerToys](https://github.com/microsoft/PowerToys) y las páginas oficiales de
|
||||||
|
Chaos/Enscape, voidtools, WinSCP, WinRAR, Nitro y TeamViewer. Se conservan las marcas
|
||||||
|
de sus titulares para identificar los productos. Dashboard Icons utiliza Apache-2.0
|
||||||
|
y Simple Icons CC0; sus licencias no sustituyen los derechos sobre las marcas.
|
||||||
|
|
||||||
|
Desde la raíz, previsualizar sin escribir:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @metis/server exec node --env-file=../../.env --import tsx src/import-resource-icons.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Para aplicar la carga solicitada, añadir `--apply` al comando. Este comando comprueba
|
||||||
|
archivos, huellas y PNG antes de escribir. Requiere un administrador inicial activo,
|
||||||
|
registra sus revisiones, prepara las plantillas y amplía las especificaciones antiguas
|
||||||
|
solo con el campo Icono, preservando sus valores anteriores. Conserva iconos existentes
|
||||||
|
y es reejecutable. No cambia flags de administración o asociación del servidor.
|
||||||
|
|
||||||
|
Las pruebas con persistencia y la auditoría Playwright utilizan exclusivamente una
|
||||||
|
instalación desechable de `metis_desk_test`; la carga de los catálogos locales es la
|
||||||
|
operación de datos solicitada, independiente de esas pruebas.
|
||||||
|
|
||||||
|
## Verificación de esta entrega
|
||||||
|
|
||||||
|
- Catálogo local: 40/40 aplicaciones y 38/38 SO con PNG; Icono opcional en las
|
||||||
|
tres plantillas. Segunda ejecución: 0 guardados y 78 iconos conservados.
|
||||||
|
- Resources: 126 pruebas correctas. PNG/HTTP: 3 pruebas correctas, incluyendo
|
||||||
|
límites, corrupción, animación y rechazo de píxeles no decodificables.
|
||||||
|
- Integración SO/HTTP/MariaDB: 12 pruebas correctas sobre base desechable; incluye
|
||||||
|
guardado y lectura de un PNG de 512 px, historial e idempotencia.
|
||||||
|
- Playwright MCP: recorridos de SO y aplicaciones correctos, con carga y retirada
|
||||||
|
por teclado, archivos inválidos, conservación del valor anterior, fichas,
|
||||||
|
búsqueda, paginación y tamaños estrechos/amplios. Cada recorrido utiliza 15
|
||||||
|
compañías y una principal con al menos 50 personas, activos, recursos, perfiles
|
||||||
|
y puestos; cada catálogo global dispone de 51 productos sintéticos.
|
||||||
|
- Compilación y typecheck dirigido de Resources, Server y Web correctos. Los
|
||||||
|
archivos modificados de esta implementación pasan Biome y las reglas de espaciado.
|
||||||
|
- `pnpm typecheck` general se detiene en `packages/shadcn-ui/src/components/calendar.tsx`
|
||||||
|
por el tipo opcional de `locale`. `pnpm lint` y `pnpm check` generales detectan
|
||||||
|
incidencias ajenas al cambio. `pnpm test`, ejecutado con MariaDB desechable,
|
||||||
|
se detiene en dos aserciones de `companies-page.test.tsx`: «Personas (1)» y
|
||||||
|
«Esta persona no tiene acceso a Metis Desk». No se han modificado esas áreas.
|
||||||
|
|
||||||
|
Skills consultadas: `rdx-solid`, `ux-inputs-and-forms`, `shadcn` y
|
||||||
|
`vercel-react-best-practices`. Han orientado el uso de contratos y persistencia
|
||||||
|
existentes, la conservación del valor ante errores, las etiquetas y ayuda
|
||||||
|
accesibles y la lectura de archivos desde el evento de selección. Los auditores
|
||||||
|
especializados mencionados por la skill de formularios no están instalados;
|
||||||
|
se han realizado las comprobaciones disponibles directamente con Playwright MCP.
|
||||||
|
|
||||||
|
## Ampliación a todos los listados — 27 de octubre de 2026
|
||||||
|
|
||||||
|
El icono se representa junto al nombre en catálogos globales y de compañía,
|
||||||
|
inventario local de SO y aplicaciones, selectores, instalaciones, SO actuales,
|
||||||
|
configuraciones e historiales de aplicaciones, entornos y resúmenes de puestos,
|
||||||
|
mapa de software y sugerencias de coincidencias. El nombre permanece como etiqueta
|
||||||
|
accesible y el adorno no interfiere con el clic de fila, el foco ni la selección.
|
||||||
|
Las referencias públicas incorporan el PNG para los dos ámbitos; no se infiere una
|
||||||
|
imagen por coincidencia de nombre y no se modifican los datos de las compañías.
|
||||||
|
|
||||||
|
Verificación de esta ampliación:
|
||||||
|
|
||||||
|
- 128 pruebas de Resources y 84 de rdx-ui correctas.
|
||||||
|
- 14 pruebas de integración HTTP/MariaDB correctas, incluyendo PNG global/local,
|
||||||
|
resolución de SO local y exclusión de referencias de otra compañía.
|
||||||
|
- Playwright MCP: PNG decodificado en el catálogo global de aplicaciones y SO,
|
||||||
|
catálogo combinado de compañía, SO local, selector mixto, instalaciones y resumen
|
||||||
|
del puesto. Se mantienen recorridos de búsqueda, paginación, teclado, selección,
|
||||||
|
responsive y gates cerrados, con 15 compañías y los volúmenes sintéticos del
|
||||||
|
procedimiento. Evidencias locales sin sesión en
|
||||||
|
`.artifacts/playwright/authenticated-9qcb5enx`, `authenticated-cpta18j4` y
|
||||||
|
`authenticated-y722zwbl`.
|
||||||
|
- Compilación y comprobaciones dirigidas de tipos y formato correctas.
|
||||||
|
- `pnpm test` mantiene dos fallos de `companies-page.test.tsx`; las otras 311
|
||||||
|
pruebas de Companies pasan. `pnpm typecheck` general mantiene el error de vendor
|
||||||
|
descrito anteriormente. `pnpm lint` y `pnpm check` siguen señalando incidencias
|
||||||
|
fuera de los archivos de esta ampliación.
|
||||||
|
|
||||||
|
Skills: `ux-tables` como principal, complementada por `shadcn`,
|
||||||
|
`vercel-react-best-practices` y `rdx-solid`: imagen compacta junto a la identidad,
|
||||||
|
composición compartida, etiquetas de texto conservadas y datos procedentes de las
|
||||||
|
consultas existentes. No se han instalado los auditores especializados ausentes.
|
||||||
@ -33,7 +33,7 @@ Personas sin acceso a Metis Desk, contactos como clasificación, puesto general
|
|||||||
|
|
||||||
## Cuentas, grupos y recursos: implementado
|
## Cuentas, grupos y recursos: implementado
|
||||||
|
|
||||||
Catálogos globales con adiciones por compañía, entornos, cuentas con uso fijo y caducidad opcional, relaciones con personas, plantillas globales de grupos, pertenencias sin anidación, recursos y permisos configurables, necesidades y cobertura calculada. Las autorizaciones y configuraciones se registran por separado, con historial, actor y avisos de revisión. Consultar [Cuentas, grupos y recursos](cuentas-grupos-y-recursos.md).
|
Catálogos globales con adiciones por compañía, entornos, cuentas con uso editable compatible con sus relaciones y caducidad opcional, relaciones con personas, plantillas globales de grupos, pertenencias sin anidación, recursos y permisos configurables, necesidades y cobertura calculada. Las autorizaciones y configuraciones se registran por separado, con historial, actor y avisos de revisión. Consultar [Cuentas, grupos y recursos](cuentas-grupos-y-recursos.md).
|
||||||
|
|
||||||
## Campos y plantillas de activos: implementado
|
## Campos y plantillas de activos: implementado
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
La sección Puestos de trabajo de cada compañía agrupa los activos utilizados habitualmente como un conjunto. Un puesto tiene UUID v7, nombre, estado activo/inactivo, sede, responsable opcional y hasta 200 activos sin duplicados. El alta exige al menos un activo y un ordenador principal. Los puestos antiguos o desactivados pueden conservar una composición vacía hasta completarlos. Una persona puede tener varios puestos. Los puestos no contienen otros puestos.
|
La sección Puestos de trabajo de cada compañía agrupa los activos utilizados habitualmente como un conjunto. Un puesto tiene UUID v7, nombre, estado activo/inactivo, sede, responsable opcional y hasta 200 activos sin duplicados. El alta exige al menos un activo y un ordenador principal. Los puestos antiguos o desactivados pueden conservar una composición vacía hasta completarlos. Una persona puede tener varios puestos. Los puestos no contienen otros puestos.
|
||||||
|
|
||||||
Nuevo puesto abre un modal desde la cabecera derecha; Editar abre otro modal con los datos existentes. Al crear, el resumen propone el nombre «{sede} · {ordenador principal}»; el técnico puede modificarlo o borrarlo antes de guardar y esa edición manual no se sobrescribe al retroceder, cambiar la sede o el ordenador y volver al resumen. Mientras el nombre no se haya editado, la propuesta se recalcula al avanzar con las selecciones actuales; el responsable no interviene en ella. El formulario ofrece selectores con «+» para dar de alta sedes, personas y activos sin perder sus datos. Guardar un elemento relacionado lo selecciona o incorpora al puesto; cancelarlo mantiene el formulario principal. Los elementos guardados sobreviven si posteriormente se cancela el puesto. El alta de activo desde un puesto usa plantillas y sedes existentes para evitar un tercer modal.
|
Nuevo puesto abre un modal desde la cabecera derecha; Editar abre otro modal con los datos existentes. Al crear, el resumen propone exactamente el nombre del activo principal, sin prefijos ni sede; el técnico puede modificarlo o borrarlo antes de guardar y esa edición manual no se sobrescribe al retroceder, cambiar la sede o el ordenador y volver al resumen. Mientras el nombre no se haya editado, la propuesta se recalcula al avanzar con las selecciones actuales; la sede y el responsable no intervienen en ella. El formulario ofrece selectores con «+» para dar de alta sedes, personas y activos sin perder sus datos. Guardar un elemento relacionado lo selecciona o incorpora al puesto; cancelarlo mantiene el formulario principal. Los elementos guardados sobreviven si posteriormente se cancela el puesto. El alta de activo desde un puesto usa plantillas y sedes existentes para evitar un tercer modal.
|
||||||
|
|
||||||
La ayuda aparece inmediatamente antes de Guardar y Cancelar. El listado permite buscar por nombre, persona o sede. Historial del puesto muestra las versiones completas, con estado, asignación y composición, fecha y responsable. La ficha de cada activo muestra los puestos vigentes en los que se utiliza.
|
La ayuda aparece inmediatamente antes de Guardar y Cancelar. El listado permite buscar por nombre, persona o sede. Historial del puesto muestra las versiones completas, con estado, asignación y composición, fecha y responsable. La ficha de cada activo muestra los puestos vigentes en los que se utiliza.
|
||||||
|
|
||||||
@ -184,3 +184,17 @@ mediante la caché compartida, con estado de carga y recuperación explícita de
|
|||||||
La ficha muestra un resumen compartido con la del activo: todas las instalaciones `configured` del principal con referencia resuelta de tipo `os`. Los estados pendientes o retirados no se presentan como SO actual. Sin registro aparece «No registrado»; carga, error recuperable y referencias no disponibles se distinguen. No se deduce el dato del SO de fábrica ni se copia en Workstation, WorkProfile o ApplicationConfiguration.
|
La ficha muestra un resumen compartido con la del activo: todas las instalaciones `configured` del principal con referencia resuelta de tipo `os`. Los estados pendientes o retirados no se presentan como SO actual. Sin registro aparece «No registrado»; carga, error recuperable y referencias no disponibles se distinguen. No se deduce el dato del SO de fábrica ni se copia en Workstation, WorkProfile o ApplicationConfiguration.
|
||||||
|
|
||||||
Cambiar el ordenador principal selecciona inmediatamente las instalaciones del nuevo activo sin transferir software, licencias o historial. La gestión existente sigue disponible, y el mapa continúa mostrando el conjunto de software abierto con sus estados. La lista completa de SO actuales está en el resumen, incluso cuando hay varios productos. Véase la [decisión y validación](architecture/decision-so-actual-en-activos-y-puestos.md).
|
Cambiar el ordenador principal selecciona inmediatamente las instalaciones del nuevo activo sin transferir software, licencias o historial. La gestión existente sigue disponible, y el mapa continúa mostrando el conjunto de software abierto con sus estados. La lista completa de SO actuales está en el resumen, incluso cuando hay varios productos. Véase la [decisión y validación](architecture/decision-so-actual-en-activos-y-puestos.md).
|
||||||
|
|
||||||
|
## Corrección de nombres existentes
|
||||||
|
|
||||||
|
`pnpm db:rename-workstations --apply` actualiza los puestos cuyo nombre difiere del de su activo principal. Es una corrección explícita sobre la base configurada localmente; no se ejecuta al arrancar. Incluye puestos activos e inactivos que conserven el principal en su composición y deja intactos los que no lo tienen.
|
||||||
|
|
||||||
|
La operación bloquea las compañías y se aplica en una única transacción. Cada cambio añade una revisión UUID v7 con el administrador inicial activo como actor, conservando las revisiones anteriores, asociaciones y reservas. Requiere un único administrador inicial activo. Repetirla sin cambios de nombres no añade revisiones. No establece una sincronización permanente: los nombres siguen siendo editables.
|
||||||
|
|
||||||
|
La regresión específica se ejecuta con `pnpm test:workstation-names`: prepara y elimina su propio contenedor MariaDB con `metis_desk_test`, sin cargar `.env` ni acceder a bases existentes.
|
||||||
|
|
||||||
|
Validación de esta corrección (26 de octubre de 2026): `pnpm test:workstation-names` pasa con dos compañías, dos activos y ocho puestos sintéticos; cubre nombres ya correctos, ausencia de principal, puestos activos/inactivos, conservación del historial, actor inválido e idempotencia. `pnpm --filter @metis/assets test` pasa sus 29 archivos; `pnpm --filter @metis/server typecheck`, las comprobaciones Biome/espaciado dirigidas y `pnpm build` pasan.
|
||||||
|
|
||||||
|
Las comprobaciones globales mantienen fallos ajenos a este cambio: `pnpm typecheck` en `packages/shadcn-ui/src/components/calendar.tsx:165`; `pnpm lint` y `pnpm check` en ficheros existentes y artefactos. El ensayo general `python3 scripts/resource-scope-test.py --monorepo` falla en una prueba HTTP de recursos antes de ejecutar el resto. No se ha ejecutado `pnpm test` completo.
|
||||||
|
|
||||||
|
Skills consultadas: `rdx-solid` para la corrección transaccional y su regresión; `ux-inputs-and-forms` para conservar la edición manual del nombre propuesto. No están disponibles sus skills especializadas `*-audit`. No se ha validado el formulario en navegador ni el volumen amplio de listados (15 compañías y 50 registros por entidad); las pruebas realizadas son específicas de esta corrección y no modifican la estructura visual.
|
||||||
|
|||||||
@ -96,6 +96,11 @@ Las plantillas permiten organizar campos en secciones ordenadas, globales o prop
|
|||||||
|
|
||||||
## Persistencia relacional
|
## Persistencia relacional
|
||||||
|
|
||||||
|
El campo opcional «Icono» admite imágenes PNG estáticas de hasta 512 × 512 píxeles.
|
||||||
|
Aplicación, Sistema operativo y Licencia de software lo incluyen de serie. Véanse
|
||||||
|
[iconos PNG de recursos](iconos-de-recursos.md) para validación, persistencia,
|
||||||
|
historial y carga de imágenes de los catálogos globales.
|
||||||
|
|
||||||
El catálogo, las especificaciones actuales e históricas y los valores actuales e históricos de recursos se almacenan en columnas y tablas relacionadas. Véase [normalización de la persistencia](normalizacion-de-la-persistencia.md).
|
El catálogo, las especificaciones actuales e históricas y los valores actuales e históricos de recursos se almacenan en columnas y tablas relacionadas. Véase [normalización de la persistencia](normalizacion-de-la-persistencia.md).
|
||||||
|
|
||||||
El arranque añade únicamente las definiciones ausentes y no sobrescribe las plantillas existentes. Las ediciones conservan UUID y clave de serie; el uso especializado de la plantilla permanece fijo. Las ediciones de plantillas sincronizan las fichas existentes sin borrar los valores de campos retirados; cada revisión conserva su especificación original.
|
El arranque añade únicamente las definiciones ausentes y no sobrescribe las plantillas existentes. Las ediciones conservan UUID y clave de serie; el uso especializado de la plantilla permanece fijo. Las ediciones de plantillas sincronizan las fichas existentes sin borrar los valores de campos retirados; cada revisión conserva su especificación original.
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
import { QueryTypes } from "sequelize";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { WorkstationInputSchema } from "../../domain/workstation-values";
|
||||||
|
|
||||||
|
const CandidateSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
name: WorkstationInputSchema.shape.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Corrección explícita de nombres; conserva las revisiones anteriores y la composición. */
|
||||||
|
export async function renameWorkstationsFromPrimaryAssets(database: Sequelize, actorId: string) {
|
||||||
|
const actor = z.uuid().parse(actorId);
|
||||||
|
|
||||||
|
return database.transaction(async (transaction) => {
|
||||||
|
const users = await database.query<{ id: string }>(
|
||||||
|
"SELECT id FROM users WHERE id=:actor AND active=1",
|
||||||
|
{ replacements: { actor }, transaction, type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (users.length !== 1) {
|
||||||
|
throw new Error("La corrección requiere un usuario activo para registrar las revisiones.");
|
||||||
|
}
|
||||||
|
// Mismo bloqueo por compañía que las escrituras ordinarias del inventario.
|
||||||
|
await database.query("SELECT id FROM companies ORDER BY id FOR UPDATE", {
|
||||||
|
transaction,
|
||||||
|
type: QueryTypes.SELECT,
|
||||||
|
});
|
||||||
|
const candidates = z.array(CandidateSchema).parse(
|
||||||
|
await database.query(
|
||||||
|
`SELECT w.id,a.name FROM workstations w
|
||||||
|
INNER JOIN assets a ON a.id=w.computerAssetId AND a.companyId=w.companyId
|
||||||
|
INNER JOIN workstation_members m ON m.workstationId=w.id AND m.assetId=a.id AND m.kind='exclusive'
|
||||||
|
WHERE BINARY w.name<>BINARY a.name FOR UPDATE`,
|
||||||
|
{ transaction, type: QueryTypes.SELECT }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const replacements = {
|
||||||
|
id: candidate.id,
|
||||||
|
name: candidate.name,
|
||||||
|
revisionId: UniqueID.generateNewID().toPrimitive(),
|
||||||
|
actor,
|
||||||
|
recordedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await database.query(
|
||||||
|
`INSERT INTO workstation_revisions
|
||||||
|
(id,workstationId,actorId,siteId,personId,computerAssetId,name,active,recordedAt,position)
|
||||||
|
SELECT :revisionId,w.id,:actor,w.siteId,w.personId,w.computerAssetId,:name,w.active,
|
||||||
|
GREATEST(:recordedAt,COALESCE(MAX(r.recordedAt),:recordedAt)),COALESCE(MAX(r.position),-1)+1
|
||||||
|
FROM workstations w LEFT JOIN workstation_revisions r ON r.workstationId=w.id
|
||||||
|
WHERE w.id=:id GROUP BY w.id,w.siteId,w.personId,w.computerAssetId,w.active`,
|
||||||
|
{ replacements, transaction }
|
||||||
|
);
|
||||||
|
await database.query(
|
||||||
|
`INSERT INTO workstation_revision_members (revisionId,assetId,kind,position)
|
||||||
|
SELECT :revisionId,assetId,kind,position FROM workstation_members WHERE workstationId=:id`,
|
||||||
|
{ replacements, transaction }
|
||||||
|
);
|
||||||
|
await database.query("UPDATE workstations SET name=:name WHERE id=:id", {
|
||||||
|
replacements,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -161,7 +161,6 @@
|
|||||||
"newWorkstation": "New workstation",
|
"newWorkstation": "New workstation",
|
||||||
"editWorkstation": "Edit workstation",
|
"editWorkstation": "Edit workstation",
|
||||||
"workstationPerson": "Workstation custodian",
|
"workstationPerson": "Workstation custodian",
|
||||||
"workstationSuggestedName": "Workstation for {{computer}} at {{site}}",
|
|
||||||
"workstationHistory": "Workstation history",
|
"workstationHistory": "Workstation history",
|
||||||
"workstationAssets": "Workstation assets",
|
"workstationAssets": "Workstation assets",
|
||||||
"noWorkstations": "This company has no workstations yet.",
|
"noWorkstations": "This company has no workstations yet.",
|
||||||
@ -441,7 +440,6 @@
|
|||||||
"technicalStatus": "Technical state",
|
"technicalStatus": "Technical state",
|
||||||
"technicalStatusHelp": "Available and In use are calculated from active workstations. Breakdowns and maintenance retain existing assignments.",
|
"technicalStatusHelp": "Available and In use are calculated from active workstations. Breakdowns and maintenance retain existing assignments.",
|
||||||
"workstationPersonOptional": "Workstation owner",
|
"workstationPersonOptional": "Workstation owner",
|
||||||
"workstationNameBySite": "Workstation at {{site}}",
|
|
||||||
"workstationSteps": {
|
"workstationSteps": {
|
||||||
"label": "New workstation steps",
|
"label": "New workstation steps",
|
||||||
"progress": "Step {{step}} of {{total}}",
|
"progress": "Step {{step}} of {{total}}",
|
||||||
|
|||||||
@ -161,7 +161,6 @@
|
|||||||
"newWorkstation": "Nuevo puesto",
|
"newWorkstation": "Nuevo puesto",
|
||||||
"editWorkstation": "Editar puesto",
|
"editWorkstation": "Editar puesto",
|
||||||
"workstationPerson": "Responsable del puesto",
|
"workstationPerson": "Responsable del puesto",
|
||||||
"workstationSuggestedName": "Puesto {{computer}} en {{site}}",
|
|
||||||
"workstationHistory": "Historial del puesto",
|
"workstationHistory": "Historial del puesto",
|
||||||
"workstationAssets": "Activos del puesto",
|
"workstationAssets": "Activos del puesto",
|
||||||
"noWorkstations": "Esta compañía todavía no tiene puestos de trabajo.",
|
"noWorkstations": "Esta compañía todavía no tiene puestos de trabajo.",
|
||||||
@ -441,7 +440,6 @@
|
|||||||
"technicalStatus": "Estado técnico",
|
"technicalStatus": "Estado técnico",
|
||||||
"technicalStatusHelp": "Disponible y En uso se calculan según los puestos activos. Una avería o mantenimiento conserva las asignaciones existentes.",
|
"technicalStatusHelp": "Disponible y En uso se calculan según los puestos activos. Una avería o mantenimiento conserva las asignaciones existentes.",
|
||||||
"workstationPersonOptional": "Responsable del puesto",
|
"workstationPersonOptional": "Responsable del puesto",
|
||||||
"workstationNameBySite": "Puesto en {{site}}",
|
|
||||||
"workstationSteps": {
|
"workstationSteps": {
|
||||||
"label": "Pasos del nuevo puesto",
|
"label": "Pasos del nuevo puesto",
|
||||||
"progress": "Paso {{step}} de {{total}}",
|
"progress": "Paso {{step}} de {{total}}",
|
||||||
|
|||||||
@ -254,13 +254,7 @@ test("alta desde activo es/en: composición y ordenador preseleccionados, sede a
|
|||||||
assert.ok(html.includes(`data-assets="${asset.id}"`));
|
assert.ok(html.includes(`data-assets="${asset.id}"`));
|
||||||
assert.ok(html.includes(`data-computer="${asset.id}"`));
|
assert.ok(html.includes(`data-computer="${asset.id}"`));
|
||||||
assert.ok(html.includes(`data-site="${asset.values.siteId}"`));
|
assert.ok(html.includes(`data-site="${asset.values.siteId}"`));
|
||||||
assert.ok(
|
assert.ok(html.includes('data-name="PC de Ana"'));
|
||||||
html.includes(
|
|
||||||
language === "es"
|
|
||||||
? 'data-name="Puesto PC de Ana en Fábrica"'
|
|
||||||
: 'data-name="Workstation for PC de Ana at Fábrica"'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert.ok(html.includes('data-wizard="true"'));
|
assert.ok(html.includes('data-wizard="true"'));
|
||||||
assert.ok(html.includes('data-can-continue="true"'));
|
assert.ok(html.includes('data-can-continue="true"'));
|
||||||
client.setQueryData(inventoryKeys.company(companyId), {
|
client.setQueryData(inventoryKeys.company(companyId), {
|
||||||
@ -294,6 +288,7 @@ test("alta desde activo es/en: composición y ordenador preseleccionados, sede a
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(inactiveSite.includes(`data-site="${id(3)}"`), false);
|
assert.equal(inactiveSite.includes(`data-site="${id(3)}"`), false);
|
||||||
|
assert.ok(inactiveSite.includes('data-name="PC de Ana"'));
|
||||||
assert.ok(inactiveSite.includes('data-can-continue="false"'));
|
assert.ok(inactiveSite.includes('data-can-continue="false"'));
|
||||||
} finally {
|
} finally {
|
||||||
client.clear();
|
client.clear();
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import type { FormEvent } from "react";
|
import type { FormEvent } from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import type { WorkstationInput } from "../../../../common";
|
import type { WorkstationInput } from "../../../../common";
|
||||||
import { WorkstationInputSchema } from "../../../../common";
|
import { WorkstationInputSchema } from "../../../../common";
|
||||||
@ -24,7 +23,6 @@ export function useWorkstationFormController({
|
|||||||
onSaved,
|
onSaved,
|
||||||
lookups,
|
lookups,
|
||||||
}: WorkstationFormProps) {
|
}: WorkstationFormProps) {
|
||||||
const { t } = useTranslation("assets");
|
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mounted = useRef(true);
|
const mounted = useRef(true);
|
||||||
const isWizard = !workstation;
|
const isWizard = !workstation;
|
||||||
@ -35,16 +33,11 @@ export function useWorkstationFormController({
|
|||||||
const initialSite =
|
const initialSite =
|
||||||
activeSites.find((site) => site.id === sourceAsset?.values.siteId) ??
|
activeSites.find((site) => site.id === sourceAsset?.values.siteId) ??
|
||||||
(activeSites.length === 1 ? activeSites[0] : undefined);
|
(activeSites.length === 1 ? activeSites[0] : undefined);
|
||||||
const initialName = suggestedWorkstationName(
|
const initialName = suggestedWorkstationName(sourceAsset?.values.name);
|
||||||
sourceAsset?.values.name,
|
|
||||||
initialSite?.name,
|
|
||||||
(computer, site) => t("workstationSuggestedName", { computer, site })
|
|
||||||
);
|
|
||||||
const form = useForm<WorkstationInput>({
|
const form = useForm<WorkstationInput>({
|
||||||
resolver: zodResolver(WorkstationInputSchema),
|
resolver: zodResolver(WorkstationInputSchema),
|
||||||
defaultValues: workstation?.values ?? {
|
defaultValues: workstation?.values ?? {
|
||||||
name:
|
name: initialName ?? "",
|
||||||
initialName ?? (initialSite ? t("workstationNameBySite", { site: initialSite.name }) : ""),
|
|
||||||
active: true,
|
active: true,
|
||||||
siteId: initialSite?.id ?? null,
|
siteId: initialSite?.id ?? null,
|
||||||
personId:
|
personId:
|
||||||
@ -116,14 +109,9 @@ export function useWorkstationFormController({
|
|||||||
if (workstation || form.getFieldState("name").isDirty) {
|
if (workstation || form.getFieldState("name").isDirty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const siteName = lookups.sites.find((item) => item.id === form.getValues("siteId"))?.name;
|
|
||||||
const computerName = assets.find((item) => item.id === form.getValues("computerAssetId"))
|
const computerName = assets.find((item) => item.id === form.getValues("computerAssetId"))
|
||||||
?.values.name;
|
?.values.name;
|
||||||
const name = suggestedWorkstationName(computerName, siteName, (computer, site) =>
|
form.setValue("name", suggestedWorkstationName(computerName) ?? "");
|
||||||
t("workstationSuggestedName", { computer, site })
|
|
||||||
);
|
|
||||||
|
|
||||||
form.setValue("name", name ?? (siteName ? t("workstationNameBySite", { site: siteName }) : ""));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
|
|||||||
@ -3,13 +3,16 @@ import { test } from "node:test";
|
|||||||
|
|
||||||
import { suggestedWorkstationName } from "./workstation-name";
|
import { suggestedWorkstationName } from "./workstation-name";
|
||||||
|
|
||||||
test("propone un nombre de puesto solo al conocer ordenador principal y sede", () => {
|
test("propone exactamente el nombre del activo principal, sin depender de la sede", () => {
|
||||||
const format = (computer: string, site: string) => `${site} · ${computer}`;
|
assert.equal(suggestedWorkstationName("PC-01"), "PC-01");
|
||||||
|
assert.equal(
|
||||||
assert.equal(suggestedWorkstationName("", "Fábrica", format), undefined);
|
suggestedWorkstationName("Portátil de recepción · Álvarez"),
|
||||||
assert.equal(suggestedWorkstationName("PC-01", "", format), undefined);
|
"Portátil de recepción · Álvarez"
|
||||||
assert.equal(suggestedWorkstationName(undefined, "Fábrica", format), undefined);
|
);
|
||||||
assert.equal(suggestedWorkstationName("PC-01", undefined, format), undefined);
|
assert.equal(suggestedWorkstationName("A".repeat(100)), "A".repeat(100));
|
||||||
assert.equal(suggestedWorkstationName("PC-01", "Uecko Fábrica", format), "Uecko Fábrica · PC-01");
|
});
|
||||||
assert.equal(suggestedWorkstationName("Portátil", "Oficina", format), "Oficina · Portátil");
|
|
||||||
|
test("no propone nombre cuando falta el activo principal", () => {
|
||||||
|
assert.equal(suggestedWorkstationName(""), undefined);
|
||||||
|
assert.equal(suggestedWorkstationName(undefined), undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,3 @@
|
|||||||
export function suggestedWorkstationName(
|
export function suggestedWorkstationName(computerName: string | undefined) {
|
||||||
computerName: string | undefined,
|
return computerName || undefined;
|
||||||
siteName: string | undefined,
|
|
||||||
format: (computer: string, site: string) => string
|
|
||||||
) {
|
|
||||||
if (!(computerName && siteName)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return format(computerName, siteName);
|
|
||||||
}
|
}
|
||||||
|
|||||||