cidstructurable con los tests

master
dev 12 months ago
parent 549867821a
commit 9254730291

543
package-lock.json generated

@ -7,6 +7,9 @@
"": {
"name": "app-selector-acabados",
"version": "0.0.1",
"dependencies": {
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.9",
"@sveltejs/kit": "^2.43.2",
@ -24,7 +27,9 @@
"svelte-check": "^4.3.2",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.2",
"vite": "^7.1.7"
"vite": "^7.1.7",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^3.2.4"
}
},
"node_modules/@alloc/quick-lru": {
@ -1256,6 +1261,16 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
"node_modules/@types/chai": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
"integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/deep-eql": "*"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@ -1263,11 +1278,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/node": {
@ -1280,6 +1302,121 @@
"undici-types": "~7.14.0"
}
},
"node_modules/@vitest/expect": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/spy": "3.2.4",
"@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.2.4",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
"optional": true
},
"vite": {
"optional": true
}
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
"integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.2.4",
"pathe": "^2.0.3",
"strip-literal": "^3.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
"integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.2.4",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyspy": "^4.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.2.4",
"loupe": "^3.1.4",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@ -1303,6 +1440,16 @@
"node": ">= 0.4"
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
@ -1395,6 +1542,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001750",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
@ -1416,6 +1573,33 @@
],
"license": "CC-BY-4.0"
},
"node_modules/chai": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.1.1",
"deep-eql": "^5.0.1",
"loupe": "^3.1.0",
"pathval": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/check-error": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@ -1466,7 +1650,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -1480,6 +1663,16 @@
}
}
},
"node_modules/deep-eql": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@ -1528,11 +1721,18 @@
"node": ">=10.13.0"
}
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"dev": true,
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.11",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
"integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
"dev": true,
"devOptional": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@ -1597,11 +1797,31 @@
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/expect-type": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
"integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@ -1654,6 +1874,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"license": "MIT"
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -1710,6 +1936,13 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
"dev": true,
"license": "MIT"
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@ -1966,6 +2199,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/loupe": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
"dev": true,
"license": "MIT"
},
"node_modules/magic-string": {
"version": "0.30.19",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
@ -2023,14 +2263,13 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"devOptional": true,
"funding": [
{
"type": "github",
@ -2069,18 +2308,35 @@
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/pathval": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.16"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"devOptional": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -2103,7 +2359,7 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"devOptional": true,
"funding": [
{
"type": "opencollective",
@ -2351,7 +2607,7 @@
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@ -2409,6 +2665,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true,
"license": "ISC"
},
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
@ -2438,12 +2701,39 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"devOptional": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true,
"license": "MIT"
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"dev": true,
"license": "MIT"
},
"node_modules/strip-literal": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^9.0.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@ -2545,11 +2835,25 @@
"node": ">=18"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
"dev": true,
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true,
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@ -2562,6 +2866,36 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinypool": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/tinyrainbow": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tinyspy": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
"integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@ -2572,11 +2906,31 @@
"node": ">=6"
}
},
"node_modules/tsconfck": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
"integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==",
"license": "MIT",
"bin": {
"tsconfck": "bin/tsconfck.js"
},
"engines": {
"node": "^18 || >=20"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@ -2624,11 +2978,25 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"dev": true,
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/vite": {
"version": "7.1.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
@ -2699,6 +3067,61 @@
}
}
},
"node_modules/vite-node": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cac": "^6.7.14",
"debug": "^4.4.1",
"es-module-lexer": "^1.7.0",
"pathe": "^2.0.3",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"bin": {
"vite-node": "vite-node.mjs"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/vite-plugin-devtools-json": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vite-plugin-devtools-json/-/vite-plugin-devtools-json-1.0.0.tgz",
"integrity": "sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==",
"dev": true,
"license": "MIT",
"dependencies": {
"uuid": "^11.1.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/vite-tsconfig-paths": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
"integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"globrex": "^0.1.2",
"tsconfck": "^3.0.3"
},
"peerDependencies": {
"vite": "*"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
}
},
"node_modules/vitefu": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
@ -2719,6 +3142,96 @@
}
}
},
"node_modules/vitest": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
"@vitest/mocker": "3.2.4",
"@vitest/pretty-format": "^3.2.4",
"@vitest/runner": "3.2.4",
"@vitest/snapshot": "3.2.4",
"@vitest/spy": "3.2.4",
"@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"debug": "^4.4.1",
"expect-type": "^1.2.1",
"magic-string": "^0.30.17",
"pathe": "^2.0.3",
"picomatch": "^4.0.2",
"std-env": "^3.9.0",
"tinybench": "^2.9.0",
"tinyexec": "^0.3.2",
"tinyglobby": "^0.2.14",
"tinypool": "^1.1.1",
"tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
"vite-node": "3.2.4",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.2.4",
"@vitest/ui": "3.2.4",
"happy-dom": "*",
"jsdom": "*"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
"@types/debug": {
"optional": true
},
"@types/node": {
"optional": true
},
"@vitest/browser": {
"optional": true
},
"@vitest/ui": {
"optional": true
},
"happy-dom": {
"optional": true
},
"jsdom": {
"optional": true
}
}
},
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"siginfo": "^2.0.0",
"stackback": "0.0.2"
},
"bin": {
"why-is-node-running": "cli.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",

@ -11,7 +11,9 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check ."
"lint": "prettier --check .",
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.9",
@ -30,6 +32,11 @@
"svelte-check": "^4.3.2",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.2",
"vite": "^7.1.7"
"vite": "^7.1.7",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^3.2.4"
},
"dependencies": {
"vite-tsconfig-paths": "^5.1.4"
}
}

@ -0,0 +1,25 @@
// cidentificable.ts
/**
* Representa un objeto que puede ser identificado de forma única
* mediante una propiedad `id` de tipo genérico `ID`.
*
* @template ID - Tipo del identificador (por defecto `string`)
*
* @example
* interface User extends Identifiable<number> {
* name: string;
* }
*
* const user: User = { id: 123, name: 'Alice' };
*/
export interface CIdentifiable<TID = string> {
id: TID;
}

@ -0,0 +1,162 @@
// cidstructural.ts
import type { CIdentifiable } from './cidentifiable';
import { CError } from '$lib/config/cerror';
const maxLevel: number = 3;
const levelSeparator: string = '|';
const subLevelSeparator: string = '/';
// Escapar caracteres especiales de regex
const escapedSep = subLevelSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
/**
* Expresión regular que valida un nivel del identificador estructurado.
* Reglas:
* - Debe empezar con un carácter alfanumérico.
* - Puede contener separadores entre subniveles (`/`).
* - No puede terminar con el separador.
* - No puede tener separadores consecutivos.
* - Solo se permiten caracteres alfanuméricos, guiones (`-`) y guiones bajos (`_`).
*/
const subLevelRegex = new RegExp(`^[a-zA-Z0-9_-]+(?:${escapedSep}[a-zA-Z0-9_-]+)*$`);
/**
* Representa un identificador estructurado jerárquico, dividido en niveles y subniveles.
*
* Ejemplo de estructura:
* ```
* Entidad|Grupo/Subgrupo|Opcion
* ```
*
* Donde:
* - `|` separa los niveles jerárquicos.
* - `/` separa subniveles dentro de un nivel.
*
* Reglas:
* - El identificador no puede estar vacío.
* - No se permiten niveles intermedios vacíos.
* - Solo se permiten vacíos al principio si todos los anteriores lo son.
* - El número máximo de niveles (`maxLevel`) es 3.
* - Si se alcanza el nivel máximo, el último nivel solo puede tener un único valor.
*/
export class CIdStructural implements CIdentifiable<string> {
/** Identificador completo */
readonly id: string;
/** Cache interno con las partes procesadas */
private parts: string[][] | null = null;
/**
* Crea una nueva instancia de `CIdStructural`.
*
* @param {string} id - Identificador estructurado. Ejemplo: `"Entidad|Grupo/Subgrupo|Opcion"`.
* @param {boolean} [lazy=true] - Si es `true`, no valida ni analiza el ID hasta que se llame a `getParts()`.
* Si es `false`, se valida inmediatamente.
* @throws {CError} Si el identificador está vacío o no cumple las reglas de formato.
*/
constructor(id: string, lazy: boolean = true) {
if (!id) {
throw new CError('invalid_id_format', 'id cannot be empty');
}
this.id = id;
// Validación temprana opcional
if (!lazy) {
this.getParts();
}
}
/**
* Devuelve las partes jerárquicas del identificador.
*
* Ejemplo:
* ```ts
* const id = new CIdStructural('Entidad|Grupo/Subgrupo|Opcion');
* id.getParts();
* // Resultado:
* // [
* // ['Entidad'],
* // ['Grupo', 'Subgrupo'],
* // ['Opcion']
* // ]
* ```
*
* @returns {string[][]} Matriz de partes separadas por niveles y subniveles.
* @throws {CError} Si el formato del ID es inválido, tiene niveles vacíos no permitidos
* o excede la profundidad máxima.
*/
getParts(): string[][] {
if (this.parts) return this.parts;
this.parts = [];
let canBeEmpty = true;
const sparts = this.id.split(levelSeparator);
const sl = sparts.length;
if (sl > maxLevel) {
throw new CError('invalid_id', `levels ${sl} is out of range max levels ${maxLevel}`);
}
for (let i = 0; i < sl; i++) {
const s = sparts[i];
if (s.length === 0) {
if (canBeEmpty) {
this.parts.push([]);
continue;
}
throw new CError('invalid_id', 'can be empty middle parts');
}
if (!subLevelRegex.test(s)) {
throw new CError('invalid_id', `Invalid format in level ${i}: "${s}"`);
}
this.parts.push(s.split(subLevelSeparator));
canBeEmpty = false;
}
// Validar longitud del último nivel
if (sl === maxLevel && this.parts[sl - 1].length !== 1) {
throw new CError('invalid_id', 'last part length must be 1');
}
return this.parts;
}
/**
* Crea un nuevo identificador hijo (sub-id) a partir de la instancia actual.
*
* @param {string} id - Identificador hijo que se agregará.
* @param {boolean} [same=false] - Si es `true`, se usa el separador de subnivel (`/`),
* de lo contrario, se usa el separador de nivel (`|`).
* @returns {CIdStructural} Nueva instancia combinada.
*/
createSubId(id: string, same: boolean = false): CIdStructural {
return CIdStructural.createFromParent(this, id, same);
}
/**
* Crea un nuevo identificador estructural combinando un padre y un identificador propio.
*
* @param {CIdentifiable<string>} parent - Identificador padre.
* @param {string} ownId - Parte propia que se añadirá al identificador.
* @param {boolean} [same=false] - Si es `true`, se añade como subnivel (`/`),
* de lo contrario como nuevo nivel (`|`).
* @returns {CIdStructural} Nueva instancia con el ID combinado.
* @throws {CError} Si `parent.id` o `ownId` no son válidos.
*/
static createFromParent(
parent: CIdentifiable<string>,
ownId: string,
same?: boolean
): CIdStructural {
if (!parent.id || !ownId) {
throw new CError(
'invalid_param',
`${parent.id ? 'ownId' : 'parent.id'} must be provided or be valid`
);
}
return new CIdStructural(
`${parent.id}${same ? subLevelSeparator : levelSeparator}${ownId}`
);
}
}

@ -0,0 +1,116 @@
// cmapids.ts
import { type CIdentifiable } from './cidentifiable';
/**
* Estructura de datos genérica basada en `Map` que asocia objetos con un `id` único .
*
* ✅ Mantiene el orden de inserción (gracias a `Map`).
* ✅ Permite acceso rápido por `id`.
* ✅ Soporta operaciones de listado, mapeo e iteración ordenada.
*
* @typeParam T - Tipo de los elementos, que deben tener un campo `id: string`.
*/
export class CMapIds<T extends CIdentifiable<ID>, ID = string > {
/**
* Mapa interno de elementos, donde la clave es el `id`
* y el valor es el objeto completo.
*/
private map = new Map<ID, T>();
/**
* Obtiene un elemento por su `id`.
*
* @param id - Identificador único del elemento.
* @returns El elemento asociado o `undefined` si no existe.
*/
get(id: ID): T | undefined { return this.map.get(id); }
/**
* Añade o reemplaza un elemento en el mapa.
*
* Si el `id` ya existe, sobrescribe el valor anterior.
*
* @param item - Elemento a insertar o actualizar.
*/
add(item: T): void { this.map.set(item.id, item); }
/**
* Reemplaza completamente el contenido del mapa con una nueva lista de elementos.
*
* @param items - Array de elementos a registrar.
*/
set(items: T[]): void {
this.map.clear();
for (const item of items) this.map.set(item.id, item);
}
/**
* Elimina un elemento del mapa según su `id`.
*
* @param id - Identificador del elemento a eliminar.
*/
del(id: ID): void { this.map.delete(id); }
/**
* Devuelve el número total de elementos en el mapa.
*/
size(): number { return this.map.size; }
/**
* Devuelve todos los elementos en un array, manteniendo
* el orden en que fueron insertados.
*/
all(): T[] { return Array.from(this.map.values()); }
/**
* Devuelve un array con todas las claves (`id`) registradas,
* manteniendo el orden de inserción.
*
* @returns Array de claves (`string[]`).
*/
keys(): ID[] { return Array.from(this.map.keys()); }
/**
* Itera sobre todos los elementos del mapa en orden de inserción,
* ejecutando un callback por cada uno.
*
* @param callback - Función llamada con `(item, index)` por cada elemento.
*/
forEach(callback: (item: T, index: number) => void): void {
let i = 0;
for (const item of this.map.values()) callback(item, i++);
}
/**
* Aplica una función de transformación a cada elemento del mapa
* y devuelve un array con los resultados.
*
* @param callback - Función que recibe `(item, index)` y devuelve un nuevo valor.
* @returns Array de resultados.
*/
mapItems<U>(callback: (item: T, index: number) => U): U[] {
const result: U[] = [];
let i = 0;
for (const item of this.map.values()) result.push(callback(item, i++));
return result;
}
/**
* Comprueba si existe un elemento con el `id` indicado.
*
* @param id - Identificador a buscar.
* @returns `true` si existe, `false` en caso contrario.
*/
has(id: ID): boolean { return this.map.has(id); }
}

@ -0,0 +1,502 @@
// cvalidator.ts
import type { CValue } from './cvalue';
import { CError } from '$lib/config/cerror';
/**
* Función de validación que verifica si un CValue cumple una condición.
*
* @param {CValue} v - Valor a validar
* @returns {boolean} `true` si el valor es válido, `false` en caso contrario
*
* @description
* Función pura que implementa la lógica de validación específica.
* Debe retornar boolean y no lanzar errores (la gestión de errores la hace CValidatorRegistry).
*
* @example
* // Validador de email simple
* const isEmail: CValidatorFn = (v) => {
* if (typeof v !== 'string') return false;
* return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
* };
*
* @example
* // Validador de rango numérico
* const isPositive: CValidatorFn = (v) => {
* return typeof v === 'number' && v > 0;
* };
*
* @example
* // Validador de longitud mínima
* const minLength = (min: number): CValidatorFn => (v) => {
* return typeof v === 'string' && v.length >= min;
* };
*/
export type CValidatorFn = (v: CValue) => boolean;
/**
* Definición de un validador con su función de validación y metadatos.
*
* @interface CValidator
*
* @property {string} name - Identificador único del validador
* @property {CValidatorFn} exec - Función que ejecuta la validación
* @property {string} [class] - Clase CSS opcional para estilizar mensajes de error en UI
* @property {string} [msg] - Mensaje de error descriptivo cuando la validación falla
*
* @description
* Representa un validador completo que puede ser registrado y ejecutado.
* Encapsula la lógica de validación junto con metadatos útiles para UI.
*
* @example
* // Validador básico
* const emailValidator: CValidator = {
* name: 'email',
* exec: (v) => typeof v === 'string' && /\S+@\S+\.\S+/.test(v),
* msg: 'Must be a valid email address'
* };
*
* @example
* // Validador con clase CSS para UI
* const requiredValidator: CValidator = {
* name: 'required',
* exec: (v) => {
* if (typeof v === 'string') return v.trim().length > 0;
* if (typeof v === 'number') return !isNaN(v);
* return v !== undefined && v !== null;
* },
* class: 'error-required',
* msg: 'This field is required'
* };
*
* @example
* // Factory function para validadores parametrizados
* function createMinLengthValidator(min: number): CValidator {
* return {
* name: `minLength_${min}`,
* exec: (v) => typeof v === 'string' && v.length >= min,
* msg: `Must be at least ${min} characters`
* };
* }
*/
export interface CValidator {
/** Identificador único del validador */
name: string;
/** Función que ejecuta la validación */
exec: CValidatorFn;
/** Clase de validador */
class?: string;
/** Mensaje de error cuando la validación falla */
msg?: string;
}
/**
* Resultado de ejecutar un validador sobre un valor.
*
* @interface CValidatorResult
*
* @property {string} name - Nombre del validador ejecutado
* @property {boolean} [isValid] - `true` si pasó la validación, `false` si falló, `undefined` si hubo error
* @property {string} [msg] - Mensaje de error del validador (presente si isValid es false)
* @property {Error} [error] - Error capturado durante la ejecución (validador no registrado o excepción)
*
* @description
* Representa el resultado de una validación individual.
* Puede tener tres estados:
* - **Éxito**: isValid = true, sin msg ni error
* - **Fallo**: isValid = false, con msg opcional
* - **Error**: error presente (validador no existe o lanzó excepción)
*
* @example
* // Validación exitosa
* { name: 'email', isValid: true }
*
* @example
* // Validación fallida
* { name: 'minLength', isValid: false, msg: 'Must be at least 5 characters' }
*
* @example
* // Error: validador no registrado
* {
* name: 'unknown',
* error: CError { code: 'validator_not_reg', message: '...' }
* }
*
* @example
* // Error: excepción durante ejecución
* {
* name: 'custom',
* error: TypeError { message: 'Cannot read property...' }
* }
*/
export interface CValidatorResult {
/** Nombre del validador ejecutado */
name: string;
/** true si pasó la validación, false si falló */
isValid?: boolean;
/** Mensaje de error (presente si isValid es false) */
msg?: string;
/** Error capturado (validador no registrado o excepción) */
error?: Error;
}
/**
* Registro centralizado de validadores reutilizables.
*
* @class CValidatorRegistry
*
* @description
* Gestiona un conjunto de validadores que pueden ser ejecutados por nombre.
* Permite:
* - Registrar validadores reutilizables
* - Ejecutar validaciones individuales o múltiples
* - Manejar errores de forma consistente
* - Compartir validadores entre componentes
*
* **Patrón Singleton recomendado**: Crear una única instancia global
* para compartir validadores en toda la aplicación.
*
* @example
* // Crear registro global
* export const validators = new CValidatorRegistry();
*
* // Registrar validadores comunes
* validators.reg({
* name: 'email',
* exec: (v) => typeof v === 'string' && /\S+@\S+\.\S+/.test(v),
* msg: 'Invalid email format'
* });
*
* validators.reg({
* name: 'required',
* exec: (v) => v !== '' && v !== null && v !== undefined,
* msg: 'This field is required'
* });
*
* @example
* // Validar valor único
* try {
* const isValid = validators.exec('email', 'user@example.com'); // true
* } catch (error) {
* console.error('Validator not found');
* }
*
* @example
* // Validar con múltiples validadores
* const results = validators.execAll(
* ['required', 'email', 'minLength'],
* userInput
* );
*
* const hasErrors = results.some(r => r.error || r.isValid === false);
* if (hasErrors) {
* console.error('Validation failed:', results);
* }
*
* @example
* // Uso en formularios
* const formValidators = new CValidatorRegistry();
*
* formValidators.reg({
* name: 'password',
* exec: (v) => typeof v === 'string' && v.length >= 8 && /\d/.test(v),
* msg: 'Password must be 8+ chars with at least one number',
* class: 'error-password'
* });
*
* const passwordResults = formValidators.execAll(['required', 'password'], password);
*/
export class CValidatorRegistry {
/**
* Almacén interno de validadores registrados
* @private
*/
private repo: Map<string, CValidator> = new Map<string, CValidator>();
/**
* Registra un nuevo validador en el registry.
*
* @param {CValidator} v - Validador a registrar
* @throws {CError} Con código 'validator_already_reg' si ya existe un validador con ese nombre
*
* @description
* Añade un validador al registro para que pueda ser ejecutado posteriormente por nombre.
* No permite sobrescribir validadores existentes para evitar conflictos.
*
* @example
* const registry = new CValidatorRegistry();
*
* registry.reg({
* name: 'positive',
* exec: (v) => typeof v === 'number' && v > 0,
* msg: 'Must be a positive number'
* });
*
* @example
* // Error: registrar el mismo nombre dos veces
* registry.reg({ name: 'email', exec: (v) => true });
* registry.reg({ name: 'email', exec: (v) => false }); // ✗ CError
*
* @example
* // Registrar validadores parametrizados
* for (let min of [5, 10, 20]) {
* registry.reg({
* name: `minLength_${min}`,
* exec: (v) => typeof v === 'string' && v.length >= min,
* msg: `Minimum ${min} characters required`
* });
* }
*/
reg(v: CValidator) {
if (this.repo.has(v.name)) {
throw new CError('validator_already_reg', `validator already reg(${v.name})`);
}
this.repo.set(v.name, v);
}
/**
* Obtiene un validador registrado por su nombre.
*
* @param {string} name - Nombre del validador
* @returns {CValidator | undefined} El validador si existe, undefined si no está registrado
*
* @description
* Permite acceder a la definición completa de un validador.
* Útil para inspeccionar metadatos (msg, class) antes de ejecutar.
*
* @example
* const emailValidator = registry.get('email');
* if (emailValidator) {
* console.log('Message:', emailValidator.msg);
* console.log('CSS class:', emailValidator.class);
* }
*
* @example
* // Verificar si existe antes de usar
* if (!registry.get('customValidator')) {
* console.warn('Validator not registered');
* }
*/
get(name: string): CValidator | undefined {
return this.repo.get(name);
}
/**
* Elimina un validador del registro.
*
* @param {string} name - Nombre del validador a eliminar
* @returns {boolean} `true` si se eliminó, `false` si no existía
*
* @description
* Remueve un validador del registro. Útil para testing o para
* reemplazar validadores en tiempo de ejecución.
*
* @example
* registry.del('obsoleteValidator'); // true si existía
*
* @example
* // Reemplazar un validador
* registry.del('email');
* registry.reg({
* name: 'email',
* exec: (v) => improvedEmailValidation(v),
* msg: 'Better email validation'
* });
*
* @example
* // Limpiar validadores de test
* afterEach(() => {
* registry.del('testValidator1');
* registry.del('testValidator2');
* });
*/
del(name: string): boolean {
return this.repo.delete(name);
}
/**
* Ejecuta un validador sobre un valor.
*
* @param {string} name - Nombre del validador a ejecutar
* @param {CValue} value - Valor a validar
* @returns {boolean} `true` si la validación pasa, `false` si falla
* @throws {CError} Con código 'validator_not_reg' si el validador no existe
*
* @description
* Ejecuta la función de validación de un validador registrado.
* Lanza error si el validador no existe (usar get() primero para verificar).
* No captura excepciones de la función exec - deben manejarse externamente.
*
* @example
* // Validación simple
* try {
* const isValid = registry.exec('email', 'test@example.com');
* if (!isValid) {
* console.error('Invalid email');
* }
* } catch (error) {
* console.error('Validator not found:', error);
* }
*
* @example
* // Validación condicional
* if (requiresValidation) {
* try {
* const result = registry.exec('complex', userInput);
* setFieldValid(result);
* } catch (err) {
* if (err instanceof CError && err.code === 'validator_not_reg') {
* console.warn('Validator not available, skipping validation');
* }
* }
* }
*/
exec(name: string, value: CValue): boolean {
const v = this.repo.get(name);
if (!v) throw new CError('validator_not_reg', `validator not registered(${name})`);
return v.exec(value);
}
/**
* Ejecuta múltiples validadores sobre un mismo valor.
*
* @param {string[]} names - Array de nombres de validadores a ejecutar
* @param {CValue} value - Valor a validar
* @returns {CValidatorResult[]} Array de resultados, uno por cada validador
*
* @description
* Ejecuta todos los validadores especificados y retorna sus resultados.
* **No lanza errores** - los captura y los incluye en los resultados.
* Útil para validar formularios con múltiples reglas.
*
* **Comportamiento**:
* - Ejecuta todos los validadores (no se detiene en el primer error)
* - Validador no registrado: result.error con CError
* - Validador lanza excepción: result.error con la excepción
* - Validación falla: result.isValid = false, result.msg del validador
* - Validación pasa: result.isValid = true
*
* @example
* // Validación de formulario
* const results = registry.execAll(
* ['required', 'email', 'maxLength'],
* emailInput.value
* );
*
* // Procesar resultados
* const errors = results
* .filter(r => r.error || r.isValid === false)
* .map(r => r.error?.message || r.msg);
*
* if (errors.length > 0) {
* displayErrors(errors);
* }
*
* @example
* // Renderizar errores en UI
* const results = registry.execAll(['required', 'minLength'], password);
*
* results.forEach(result => {
* if (result.isValid === false) {
* const validator = registry.get(result.name);
* showError(result.msg, validator?.class);
* }
* });
*
* @example
* // Validación compleja con reporte detallado
* const fieldValidations = {
* email: ['required', 'email'],
* password: ['required', 'minLength_8', 'hasNumber'],
* age: ['required', 'positive', 'maxValue_120']
* };
*
* const report: Record<string, CValidatorResult[]> = {};
* for (const [field, validators] of Object.entries(fieldValidations)) {
* report[field] = registry.execAll(validators, formData[field]);
* }
*
* // Verificar si hay errores
* const hasErrors = Object.values(report)
* .flat()
* .some(r => r.error || r.isValid === false);
*
* @example
* // Early exit si hay error crítico
* const results = registry.execAll(['required', 'email'], input);
* const criticalError = results.find(r =>
* r.name === 'required' && r.isValid === false
* );
*
* if (criticalError) {
* return; // No procesar validaciones adicionales
* }
*/
execAll(names: string[], value: CValue): CValidatorResult[] {
const results: CValidatorResult[] = [];
for (let i = 0; i < names.length; i++) {
const name = names[i];
const v = this.repo.get(name);
const result: CValidatorResult = { name };
if (!v) {
result.error = new CError('validator_not_reg', `Validator not registered (${name})`);
} else {
try {
const valid: boolean = v.exec(value);
result.isValid = valid;
if (!valid) result.msg = v.msg;
} catch (error: any) {
result.error = error;
}
}
results.push(result);
}
return results;
}
}
/**
* @example
* // Pattern: Validadores parametrizados con factory
* function createRangeValidator(min: number, max: number): CValidator {
* return {
* name: `range_${min}_${max}`,
* exec: (v) => typeof v === 'number' && v >= min && v <= max,
* msg: `Must be between ${min} and ${max}`
* };
* }
*
* registry.reg(createRangeValidator(18, 100));
*
* @example
* // Pattern: Validadores compuestos
* registry.reg({
* name: 'strongPassword',
* exec: (v) => {
* if (typeof v !== 'string') return false;
* return v.length >= 8 &&
* /[A-Z]/.test(v) &&
* /[a-z]/.test(v) &&
* /\d/.test(v) &&
* /[!@#$%^&*]/.test(v);
* },
* msg: 'Password must have 8+ chars, uppercase, lowercase, number and symbol'
* });
*
* @example
* // Pattern: Validadores asincrónicos (wrapper)
* // Nota: CValidatorFn es síncrono, pero puedes crear un wrapper
* const asyncValidators = new Map<string, (v: CValue) => Promise<boolean>>();
*
* asyncValidators.set('uniqueEmail', async (email) => {
* const response = await fetch(`/api/check-email?email=${email}`);
* return response.ok;
* });
*
* // Ejecutar validaciones async separadamente
* const syncResults = registry.execAll(['required', 'email'], email);
* if (syncResults.every(r => r.isValid)) {
* const isUnique = await asyncValidators.get('uniqueEmail')?.(email);
* }
*/

@ -63,7 +63,8 @@ export type CValue = string | number | boolean;
* @see {@link checkTypeCValue} para validación con type assertion
*/
export function isCValue(val: any): boolean {
return typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number';
const t = typeof val;
return t === 'string' || t === 'boolean' || t === 'number';
}
/**

@ -0,0 +1,18 @@
<script lang="ts">
import { CIdStructural } from '$lib/config/cidstructural';
const ids : CIdStructural [] = [
new CIdStructural('a/b/c|g1/g2|o1'),
new CIdStructural('|||o1'),
new CIdStructural('a//c||o1'),
]
for (let i=0; i<ids.length; i++) {
console.log('id ', ids[i].id,' partes ',ids[i].getParts() );
}
</script>

@ -0,0 +1,143 @@
import { describe, it, expect } from 'vitest';
import { CIdStructural } from '$lib/config/cidstructural';
import { CError } from '$lib/config/cerror';
describe('CIdStructural (extended)', () => {
// ------------------------------
// ✅ Casos válidos
// ------------------------------
it('permite id simple sin niveles', () => {
const id = new CIdStructural('root');
expect(id.getParts()).toEqual([['root']]);
});
it('permite id con guiones y guiones bajos', () => {
const id = new CIdStructural('empresa-1|departamento_2|producto-xyz');
expect(id.getParts()).toEqual([
['empresa-1'],
['departamento_2'],
['producto-xyz']
]);
});
it('permite subniveles en niveles intermedios', () => {
const id = new CIdStructural('empresa|departamento/subdep|producto');
expect(id.getParts()).toEqual([
['empresa'],
['departamento', 'subdep'],
['producto']
]);
});
it('permite niveles vacíos iniciales', () => {
const id = new CIdStructural('||producto');
expect(id.getParts()).toEqual([[], [], ['producto']]);
});
it('permite construir subIds encadenados', () => {
const root = new CIdStructural('empresa');
const dep = root.createSubId('ventas');
const subdep = dep.createSubId('online', true);
const prod = subdep.createSubId('producto');
expect(prod.id).toBe('empresa|ventas/online|producto');
});
// ------------------------------
// ❌ Casos inválidos
// ------------------------------
it('lanza error si se supera el maxLevel', () => {
expect(() => new CIdStructural('a|b|c|d').getParts()).toThrow(CError);
});
it('lanza error si hay separadores consecutivos en medio', () => {
expect(() => new CIdStructural('empresa||departamento|producto').getParts()).toThrow(CError);
});
it('lanza error si el último nivel tiene más de un subnivel', () => {
expect(() => new CIdStructural('empresa|departamento|producto/a').getParts()).toThrow(CError);
});
it('lanza error si un nivel es vacío y no puede serlo', () => {
expect(() => new CIdStructural('empresa||').getParts()).toThrow(CError);
});
it('lanza error si contiene caracteres no permitidos', () => {
expect(() => new CIdStructural('empresa|departamento|prod@cto').getParts()).toThrow(CError);
});
it('lanza error si el formato del nivel es inválido (doble /)', () => {
expect(() => new CIdStructural('empresa|dep//subdep|producto').getParts()).toThrow(CError);
});
// ------------------------------
// 🧠 Lazy loading
// ------------------------------
it('no valida inmediatamente si lazy=true', () => {
const instance = new CIdStructural('empresa|dep//subdep|producto', true);
// No explota aquí, pero sí al pedir las partes:
expect(() => instance.getParts()).toThrow(CError);
});
it('valida inmediatamente si lazy=false', () => {
expect(() => new CIdStructural('empresa|dep/subdep|producto', false)).not.toThrow();
});
// ------------------------------
// ⚙️ Comportamiento de createFromParent
// ------------------------------
it('createFromParent concatena con | cuando same=false', () => {
const parent = new CIdStructural('empresa|ventas');
const child = CIdStructural.createFromParent(parent, 'online', false);
expect(child.id).toBe('empresa|ventas|online');
});
it('createFromParent concatena con / cuando same=true', () => {
const parent = new CIdStructural('empresa|ventas');
const child = CIdStructural.createFromParent(parent, 'online', true);
expect(child.id).toBe('empresa|ventas/online');
});
it('createFromParent lanza error si ownId vacío', () => {
const parent = new CIdStructural('empresa');
expect(() => CIdStructural.createFromParent(parent, '')).toThrow(CError);
});
it('createFromParent lanza error si parent.id vacío', () => {
const fakeParent = { id: '' };
expect(() => CIdStructural.createFromParent(fakeParent, 'algo')).toThrow(CError);
});
// ------------------------------
// 🧩 Casos límite y mixtos
// ------------------------------
it('permite id con subniveles complejos en niveles intermedios', () => {
const id = new CIdStructural('raiz|a/b/c|final');
expect(id.getParts()).toEqual([
['raiz'],
['a', 'b', 'c'],
['final']
]);
});
it('lanza error si id termina con separador', () => {
expect(() => new CIdStructural('empresa|departamento|producto/').getParts()).toThrow(CError);
});
it('lanza error si un nivel contiene espacio', () => {
expect(() => new CIdStructural('empresa|depar tamento|producto').getParts()).toThrow(CError);
});
it('mantiene coherencia entre llamadas repetidas a getParts()', () => {
const id = new CIdStructural('empresa|departamento|producto');
const first = id.getParts();
const second = id.getParts();
expect(first).toBe(second);
});
});

@ -9,11 +9,8 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
"moduleResolution": "bundler",
}
}

@ -1,7 +1,12 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import devtoolsJson from 'vite-plugin-devtools-json';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
plugins: [tailwindcss(), sveltekit(),devtoolsJson()],
// Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node
// resolve: process.env.VITEST ? { conditions: ['browser'] } : undefined
});

@ -0,0 +1,16 @@
import { defineConfig } from 'vitest/config';
import viteConfig from './vite.config.js';
export default defineConfig({
...viteConfig,
test: {
// Carpeta donde están los tests
include: ['src/tests/**/*.test.ts'],
// Opcional: ignorar ciertos directorios
exclude: ['node_modules', 'build'],
// Opcional: correr en modo watch automáticamente
watch: true,
},
});
Loading…
Cancel
Save

Powered by TurnKey Linux.