shapshot
This commit is contained in:
32
bun.lock
32
bun.lock
@@ -1,22 +1,24 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "redmine-api-administracion",
|
"name": "redmine-api-administracion",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"marked": "^16.4.1",
|
"marked": "^16.4.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@tsconfig/svelte": "^5.0.5",
|
"@tsconfig/svelte": "^5.0.6",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.10.3",
|
||||||
"svelte": "^5.39.6",
|
"svelte": "^5.46.0",
|
||||||
"svelte-check": "^4.3.2",
|
"svelte-check": "^4.3.4",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.2.7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -137,11 +139,11 @@
|
|||||||
|
|
||||||
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
|
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
|
||||||
|
|
||||||
"@tsconfig/svelte": ["@tsconfig/svelte@5.0.5", "", {}, "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ=="],
|
"@tsconfig/svelte": ["@tsconfig/svelte@5.0.6", "", {}, "sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
|
"@types/node": ["@types/node@24.10.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
@@ -161,15 +163,19 @@
|
|||||||
|
|
||||||
"css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
|
"css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
|
||||||
|
|
||||||
|
"csv-parse": ["csv-parse@6.1.0", "", {}, "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
|
"devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
|
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
|
||||||
|
|
||||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||||
|
|
||||||
"esrap": ["esrap@2.1.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg=="],
|
"esrap": ["esrap@2.2.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
@@ -183,7 +189,7 @@
|
|||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
"marked": ["marked@16.4.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg=="],
|
"marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
|
||||||
|
|
||||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
|
|
||||||
@@ -205,9 +211,9 @@
|
|||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"svelte": ["svelte@5.43.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA=="],
|
"svelte": ["svelte@5.46.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw=="],
|
||||||
|
|
||||||
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
|
"svelte-check": ["svelte-check@4.3.4", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw=="],
|
||||||
|
|
||||||
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
|
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
|
||||||
|
|
||||||
@@ -219,7 +225,7 @@
|
|||||||
|
|
||||||
"utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
|
"utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
|
||||||
|
|
||||||
"vite": ["vite@7.1.12", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug=="],
|
"vite": ["vite@7.2.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ=="],
|
||||||
|
|
||||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>redmine-api-administracion</title>
|
<title>redmine-api-administracion</title>
|
||||||
</head>
|
</head>
|
||||||
<body data-bs-theme="light">
|
<body data-bs-theme="dark">
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -11,17 +11,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
"@tsconfig/svelte": "^5.0.5",
|
"@tsconfig/svelte": "^5.0.6",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.10.3",
|
||||||
"svelte": "^5.39.6",
|
"svelte": "^5.46.0",
|
||||||
"svelte-check": "^4.3.2",
|
"svelte-check": "^4.3.4",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.7"
|
"vite": "^7.2.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"marked": "^16.4.1"
|
"marked": "^16.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
public/nodos.csv
Normal file
52
public/nodos.csv
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Descripcion Dias Dependencias
|
||||||
|
0 inicio 0 _
|
||||||
|
78 Modelo de Datos y Estructura del Sistema 73 0
|
||||||
|
79 Organización de entidades 73 78
|
||||||
|
80 Definición de tipos de información 12 78
|
||||||
|
81 Manejo de formatos de exportación 12 78
|
||||||
|
82 Identificación y trazabilidad interna 17 78
|
||||||
|
83 Reglas estructurales del sistema 12 78
|
||||||
|
84 Gestión de estados 10 78
|
||||||
|
85 Propiedades 57 78
|
||||||
|
86 Administración del ciclo de vida de una propiedad 17 78,79,83
|
||||||
|
88 Visualización de información de cada propiedad 18 85,84
|
||||||
|
89 Organización y listado de propiedades 23 85
|
||||||
|
90 Duración y condiciones 10 85
|
||||||
|
91 Contratos 54 85
|
||||||
|
92 Parámetros del contrato 14 85,84
|
||||||
|
93 Actualización y ajustes contractuales 9 91
|
||||||
|
94 Categorización y búsqueda 10 91,92
|
||||||
|
96 Notificaciones relacionadas 10 85
|
||||||
|
97 Registro y seguimiento de pagos 19 91
|
||||||
|
98 Herramientas específicas por tipo de usuario 30 99
|
||||||
|
99 Usuarios y Seguridad 84 78
|
||||||
|
100 Manejo de perfiles 15 99
|
||||||
|
101 Control de permisos y grupos 11 99
|
||||||
|
102 Autenticación y gestión de credenciales 9 99
|
||||||
|
103 Administración interna de usuarios 10 99
|
||||||
|
104 Control de sesiones 20 102
|
||||||
|
105 Herramientas de búsqueda rápida 11 94
|
||||||
|
106 Relación con Clientes 77 99
|
||||||
|
108 Gestion de clientes 23 106
|
||||||
|
109 Gestión de garantes 12 106
|
||||||
|
110 Funciones propias del propietario e inquilino 10 106
|
||||||
|
111 Navegación 24 106,99
|
||||||
|
112 Búsquedas y Organización del Contenido 81 94,111
|
||||||
|
113 Filtros 14 94
|
||||||
|
114 Paginación 13 94
|
||||||
|
115 Clasificación de propiedades y contratos 10 85,91
|
||||||
|
117 Integraciones internas del sistema 11 78
|
||||||
|
118 Estadísticas y Reportes 65 82,97,106
|
||||||
|
119 Métricas del sistema 19 118
|
||||||
|
120 Informes financieros y contractuales 7 118
|
||||||
|
121 Seguimiento histórico 12 118
|
||||||
|
122 Indicadores de ventas y alquileres 31 118
|
||||||
|
123 Interfaz y Experiencia de Usuario 96 99,85
|
||||||
|
124 Diseño de páginas específicas por rol 13 123,100
|
||||||
|
126 Pantallas de administración 16 123,103
|
||||||
|
127 Presentación visual del sistema 36 123
|
||||||
|
128 Elementos Generales y Complementarios 69 123
|
||||||
|
129 Manejo de divisas 19 97
|
||||||
|
130 Auditoría 21 99,121
|
||||||
|
131 Registros de actividad 5 130
|
||||||
|
999 Fin 0 80,81,86,88,89,90,93,96,98,101,104,105,108,109,110,112,113,114,115,117,119,120,122,124,126,127,128,129,131
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from "./componentes/header.svelte";
|
import Header from "./componentes/header.svelte";
|
||||||
import CalcularDias from "./componentes/paginas/CalcularDias.svelte";
|
import CalcularDias from "./componentes/paginas/CalcularDias.svelte";
|
||||||
|
import CaminoCritico from "./componentes/paginas/CaminoCritico.svelte";
|
||||||
import IndiceDeControl from "./componentes/paginas/IndiceDeControl.svelte";
|
import IndiceDeControl from "./componentes/paginas/IndiceDeControl.svelte";
|
||||||
|
import RelacionesNodos from "./componentes/paginas/RelacionesNodos.svelte";
|
||||||
|
import { TablaNodos } from "./componentes/paginas/TablaNodos.svelte";
|
||||||
|
import TablaProyect from "./componentes/paginas/TablaProyect.svelte";
|
||||||
import Tarjeta from "./componentes/tarjeta.svelte";
|
import Tarjeta from "./componentes/tarjeta.svelte";
|
||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
|
|
||||||
@@ -78,8 +82,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if pagina === 1}
|
{:else if pagina === 1}
|
||||||
<CalcularDias {issues} />
|
<CalcularDias issuesp={issues} />
|
||||||
{:else if pagina === 2}
|
{:else if pagina === 2}
|
||||||
<IndiceDeControl {issues} />
|
<IndiceDeControl {issues} />
|
||||||
|
{:else if pagina === 3}
|
||||||
|
<TablaProyect {issues} />
|
||||||
|
{:else if pagina === 4}
|
||||||
|
<RelacionesNodos />
|
||||||
|
{:else if pagina === 5}
|
||||||
|
<CaminoCritico />
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
import pandas as pd
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
# === CONFIGURACIÓN ===
|
|
||||||
archivo_issues = "issues.json" # tu archivo exportado
|
|
||||||
presupuesto_total = 41980.0 # presupuesto del proyecto
|
|
||||||
|
|
||||||
# === FUNCIONES AUXILIARES ===
|
|
||||||
def parse_date(valor):
|
|
||||||
if not valor:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return datetime.fromisoformat(valor.replace("Z", "+00:00")).date()
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
return datetime.strptime(valor.split("T")[0], "%Y-%m-%d").date()
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# === CARGA DE DATOS ===
|
|
||||||
with open(archivo_issues, "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
issues = data.get("issues", [])
|
|
||||||
for it in issues:
|
|
||||||
it["_start"] = parse_date(it.get("start_date"))
|
|
||||||
it["_due"] = parse_date(it.get("due_date"))
|
|
||||||
it["_closed"] = parse_date(it.get("closed_on"))
|
|
||||||
it["_done"] = float(it.get("done_ratio", 0)) / 100.0
|
|
||||||
|
|
||||||
# === GENERAR TIMELINE ===
|
|
||||||
fechas = []
|
|
||||||
for it in issues:
|
|
||||||
for f in [it["_start"], it["_due"], it["_closed"]]:
|
|
||||||
if f:
|
|
||||||
fechas.append(f)
|
|
||||||
|
|
||||||
min_fecha, max_fecha = min(fechas), max(fechas)
|
|
||||||
timeline = pd.date_range(start=min_fecha, end=max_fecha, freq="D")
|
|
||||||
|
|
||||||
# === CALCULAR PV y EV ===
|
|
||||||
total = len(issues)
|
|
||||||
pv_vals, ev_vals, fechas_plot = [], [], []
|
|
||||||
|
|
||||||
for d in timeline:
|
|
||||||
fecha = d.date()
|
|
||||||
pv = sum(1 for it in issues if it["_due"] and it["_due"] <= fecha) / total
|
|
||||||
ev = sum(it["_done"] for it in issues if it["_closed"] and it["_closed"] <= fecha)
|
|
||||||
ev += sum(it["_done"] for it in issues if (not it["_closed"]) and it["_due"] and it["_due"] <= fecha)
|
|
||||||
ev = min(ev, total)
|
|
||||||
pv_vals.append(pv * presupuesto_total)
|
|
||||||
ev_vals.append(ev / total * presupuesto_total)
|
|
||||||
fechas_plot.append(fecha)
|
|
||||||
|
|
||||||
# === GRÁFICO CURVA S ===
|
|
||||||
plt.figure(figsize=(10, 5))
|
|
||||||
plt.plot(fechas_plot, pv_vals, label="PV (Valor planificado)", color="blue")
|
|
||||||
plt.plot(fechas_plot, ev_vals, label="EV (Valor ganado)", color="green")
|
|
||||||
plt.title("Curva S — Proyecto AlquilaFacil")
|
|
||||||
plt.xlabel("Fecha")
|
|
||||||
plt.ylabel("Valor acumulado (USD)")
|
|
||||||
plt.legend()
|
|
||||||
plt.grid(True)
|
|
||||||
plt.tight_layout()
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
# === RESUMEN FINAL ===
|
|
||||||
print("----- RESUMEN CURVA S -----")
|
|
||||||
print(f"Fecha inicio: {min_fecha}")
|
|
||||||
print(f"Fecha fin: {max_fecha}")
|
|
||||||
print(f"Presupuesto total: {presupuesto_total}")
|
|
||||||
print(f"PV final: {pv_vals[-1]:.2f}")
|
|
||||||
print(f"EV final: {ev_vals[-1]:.2f}")
|
|
||||||
print(f"Issues totales: {total}")
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,15 @@
|
|||||||
<button class="btn btn-primary" onclick={() => (pagina = 2)}
|
<button class="btn btn-primary" onclick={() => (pagina = 2)}
|
||||||
>Indice de Control</button
|
>Indice de Control</button
|
||||||
>
|
>
|
||||||
|
<button class="btn btn-primary" onclick={() => (pagina = 3)}
|
||||||
|
>Tabla</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary" onclick={() => (pagina = 4)}
|
||||||
|
>Nodos</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary" onclick={() => (pagina = 5)}
|
||||||
|
>Camino Critico</button
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="btn"
|
class="btn"
|
||||||
aria-label="Cambiar tema"
|
aria-label="Cambiar tema"
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
import type { Issue } from "../../types";
|
import type { Issue } from "../../types";
|
||||||
|
|
||||||
let { issues }: { issues: Issue[] } = $props();
|
let { issuesp }: { issuesp: Issue[] } = $props();
|
||||||
|
|
||||||
|
let issues = $state(issuesp);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!orden) {
|
||||||
|
issues = issues.sort((a, b) => a.id - b.id);
|
||||||
|
} else {
|
||||||
|
issues = issues.sort((a, b) => b.id - a.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
calcularDiasPorIssue();
|
calcularDiasPorIssue();
|
||||||
@@ -9,6 +20,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
let diasCalculados = $state(0);
|
let diasCalculados = $state(0);
|
||||||
|
let offsetDias = $state(0);
|
||||||
|
let orden = $state(false);
|
||||||
|
|
||||||
|
function sortIssues() {
|
||||||
|
if (!orden) {
|
||||||
|
issues = issues.sort((a, b) => a.id - b.id);
|
||||||
|
} else {
|
||||||
|
issues = issues.sort((a, b) => b.id - a.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calcularDiasTotales() {
|
function calcularDiasTotales() {
|
||||||
if (!issues || issues.length === 0) {
|
if (!issues || issues.length === 0) {
|
||||||
@@ -80,6 +101,21 @@
|
|||||||
<h5 class="mb-0">Calculadora de Días</h5>
|
<h5 class="mb-0">Calculadora de Días</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="mb-1 flex gap-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
onclick={() => {
|
||||||
|
const textToCopy = issues
|
||||||
|
.map(
|
||||||
|
(issue) =>
|
||||||
|
`${issue.subject}\t${issue.cantDias || 0}`,
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
navigator.clipboard.writeText(textToCopy);
|
||||||
|
}}>Copiar datos</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if diasCalculados > 0}
|
{#if diasCalculados > 0}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>Resultado:</strong>
|
<strong>Resultado:</strong>
|
||||||
@@ -88,16 +124,55 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h6>Issues:</h6>
|
<h6>
|
||||||
|
Issues:
|
||||||
|
<input type="number" bind:value={offsetDias} /> Offset
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="ms-2"
|
||||||
|
bind:checked={orden}
|
||||||
|
onchange={sortIssues}
|
||||||
|
/>
|
||||||
|
{#if orden}
|
||||||
|
9..1
|
||||||
|
{:else}
|
||||||
|
1..9
|
||||||
|
{/if}
|
||||||
|
</h6>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{#each issues as issue}
|
{#each issues as issue}
|
||||||
<li
|
<li
|
||||||
class="list-group-item d-flex justify-content-between align-items-center"
|
class="list-group-item d-flex justify-content-between align-items-center"
|
||||||
>
|
>
|
||||||
{`${issue.id} | ${issue.subject}`}
|
{`${issue.id} | ${issue.subject}`}
|
||||||
<span class="badge bg-secondary"
|
<button
|
||||||
>{issue.cantDias || 0} días</span
|
class="btn btn-secondary p-0.5"
|
||||||
|
onclick={() => {
|
||||||
|
const textToCopy = `${issue.subject}\t${issue.cantDias || 0}`;
|
||||||
|
navigator.clipboard.writeText(textToCopy);
|
||||||
|
}}>copy</button
|
||||||
>
|
>
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-secondary"
|
||||||
|
>Inicio {new Date(
|
||||||
|
new Date(issue.start_date).getTime() +
|
||||||
|
offsetDias * 24 * 60 * 60 * 1000,
|
||||||
|
).toLocaleDateString()}</span
|
||||||
|
>
|
||||||
|
<span class="badge bg-secondary"
|
||||||
|
>{issue.cantDias || 0} días
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-secondary"
|
||||||
|
>Fin {new Date(
|
||||||
|
new Date(issue.start_date).getTime() +
|
||||||
|
(issue.cantDias + offsetDias) *
|
||||||
|
24 *
|
||||||
|
60 *
|
||||||
|
60 *
|
||||||
|
1000,
|
||||||
|
).toLocaleDateString()}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
118
src/componentes/paginas/CaminoCritico.svelte
Normal file
118
src/componentes/paginas/CaminoCritico.svelte
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Nodo, Tabla } from "../../types";
|
||||||
|
import { TablaNodos } from "./TablaNodos.svelte";
|
||||||
|
|
||||||
|
let tabla = new TablaNodos();
|
||||||
|
$effect(async () => {
|
||||||
|
if (tabla.issues.length !== 0) {
|
||||||
|
console.log(
|
||||||
|
(await tabla.nodosNoReferenciados()).map((x) => x.id).join(","),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mostrarfiltro = $state(false);
|
||||||
|
let filtro: Promise<Nodo[]> = $state(Promise.resolve([]));
|
||||||
|
$effect(() => {
|
||||||
|
if (mostrarfiltro) {
|
||||||
|
filtro = Promise.resolve(tabla.filtroNodo());
|
||||||
|
} else {
|
||||||
|
filtro = Promise.resolve([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await tabla.calcularCaminoCritico()}
|
||||||
|
<div>carga</div>
|
||||||
|
{:then nodos}
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-2">
|
||||||
|
El Camino Critico es:
|
||||||
|
{#each nodos.filter((x) => x.esCritico) as noduvis}
|
||||||
|
<kbd class="me-1">
|
||||||
|
{noduvis.id}
|
||||||
|
</kbd>
|
||||||
|
{" "}
|
||||||
|
{/each}
|
||||||
|
<input type="checkbox" bind:checked={mostrarfiltro} />
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped border shadow">
|
||||||
|
<thead>
|
||||||
|
<tr class="sticky-top">
|
||||||
|
<th>ID</th>
|
||||||
|
<th>dias</th>
|
||||||
|
<th>deps</th>
|
||||||
|
<th>ES</th>
|
||||||
|
<th>EF</th>
|
||||||
|
<th>LS</th>
|
||||||
|
<th>LF</th>
|
||||||
|
<th>Crit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#await filtro then filteredIssues}
|
||||||
|
{#if filteredIssues.length > 0}
|
||||||
|
{#each filteredIssues as nodo}
|
||||||
|
<tr>
|
||||||
|
{@render Td(nodo.id)}
|
||||||
|
{@render Td(nodo.duracion)}
|
||||||
|
{@render Td(
|
||||||
|
nodo.dependencias.length <= 3
|
||||||
|
? nodo.dependencias
|
||||||
|
: nodo.dependencias
|
||||||
|
.slice(0, 3)
|
||||||
|
.join(", ") + "...",
|
||||||
|
)}
|
||||||
|
{@render Td(nodo.ES)}
|
||||||
|
{@render Td(nodo.EF)}
|
||||||
|
{@render Td(nodo.LS)}
|
||||||
|
{@render Td(nodo.LF)}
|
||||||
|
{@render Td(nodo.esCritico)}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
{#each nodos as nodo}
|
||||||
|
<tr>
|
||||||
|
<td>{nodo.id}</td>
|
||||||
|
<td>{nodo.duracion}</td>
|
||||||
|
<td>
|
||||||
|
{#if nodo.dependencias.length <= 3}
|
||||||
|
{nodo.dependencias}
|
||||||
|
{:else}
|
||||||
|
{nodo.dependencias
|
||||||
|
.slice(0, 3)
|
||||||
|
.join(", ")}...
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td>{nodo.ES}</td>
|
||||||
|
<td>{nodo.EF}</td>
|
||||||
|
<td>{nodo.LS}</td>
|
||||||
|
<td>{nodo.LF}</td>
|
||||||
|
<td>{nodo.esCritico}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{:catch}
|
||||||
|
<p>error</p>
|
||||||
|
{/await}
|
||||||
|
{#snippet Td(a: any)}
|
||||||
|
<td
|
||||||
|
class="table-active-hover"
|
||||||
|
onclick={() => navigator.clipboard.writeText(a.toString())}>{a}</td
|
||||||
|
>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-active-hover {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 123, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
90
src/componentes/paginas/RelacionesNodos.svelte
Normal file
90
src/componentes/paginas/RelacionesNodos.svelte
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Issue, Tabla } from "../../types";
|
||||||
|
import { TablaNodos } from "./TablaNodos.svelte";
|
||||||
|
|
||||||
|
let tabla: TablaNodos = new TablaNodos();
|
||||||
|
|
||||||
|
let txtfiltro = $state("");
|
||||||
|
let filtro: Promise<Tabla[]> = $state(Promise.resolve([]));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container" style="width: 50%;">
|
||||||
|
<div class="mb-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={txtfiltro}
|
||||||
|
oninput={() => {
|
||||||
|
filtro = Promise.resolve(
|
||||||
|
tabla.filtroIssue(txtfiltro.split(" ")),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped border shadow">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Dias</th>
|
||||||
|
<th>Dep</th>
|
||||||
|
<th>Desc</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#await filtro then filteredIssues}
|
||||||
|
{#if filteredIssues.length > 0}
|
||||||
|
{#each filteredIssues as issue}
|
||||||
|
<tr>
|
||||||
|
{@render Td(issue["#"])}
|
||||||
|
{@render Td(issue.Dias)}
|
||||||
|
{#if issue["#"] == 999}
|
||||||
|
<td>...</td>
|
||||||
|
{:else}
|
||||||
|
{@render Td(issue.Dependencias)}
|
||||||
|
{/if}
|
||||||
|
{@render Td(issue.Descripcion)}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
{#await tabla.issues}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{:then issues}
|
||||||
|
{#each issues as issue}
|
||||||
|
<tr>
|
||||||
|
{@render Td(issue["#"])}
|
||||||
|
{@render Td(issue.Dias)}
|
||||||
|
{#if issue["#"] == 999}
|
||||||
|
<td>...</td>
|
||||||
|
{:else}
|
||||||
|
{@render Td(issue.Dependencias)}
|
||||||
|
{/if}
|
||||||
|
{@render Td(issue.Descripcion)}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#snippet Td(a: any)}
|
||||||
|
<td
|
||||||
|
class="table-active-hover"
|
||||||
|
onclick={() => navigator.clipboard.writeText(a.toString())}>{a}</td
|
||||||
|
>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 123, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
108
src/componentes/paginas/TablaNodos.svelte.ts
Normal file
108
src/componentes/paginas/TablaNodos.svelte.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { parse } from "csv-parse/browser/esm/sync";
|
||||||
|
import type { Nodo, Tabla } from "../../types";
|
||||||
|
|
||||||
|
export class TablaNodos {
|
||||||
|
issues: Promise<Tabla[]> = $state(this.setIssues());
|
||||||
|
|
||||||
|
async setIssues(): Promise<Tabla[]> {
|
||||||
|
const response = await fetch("/nodos.csv");
|
||||||
|
let contenido = await response.text();
|
||||||
|
return parse(contenido, {
|
||||||
|
columns: true,
|
||||||
|
delimiter: "\t",
|
||||||
|
trim: true,
|
||||||
|
relax_column_count: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async calcularCaminoCritico() {
|
||||||
|
// @ts-ignore
|
||||||
|
let nodos: Nodo[] = (await this.issues).map((x) => ({
|
||||||
|
id: x["#"],
|
||||||
|
duracion: x.Dias,
|
||||||
|
dependencias: x.Dependencias?.toString().split(",") || [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
nodos.sort((a, b) => {
|
||||||
|
if (a.dependencias.includes(b.id)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (b.dependencias.includes(a.id)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calcular Early Start y Early Finish
|
||||||
|
nodos.forEach((nodo) => {
|
||||||
|
if (nodo.dependencias.length === 0) {
|
||||||
|
nodo.ES = 0;
|
||||||
|
} else {
|
||||||
|
nodo.ES = Math.max(
|
||||||
|
...nodo.dependencias.map((dep) => {
|
||||||
|
const nodoDependencia = nodos.find((n) => n.id === dep.trim());
|
||||||
|
return nodoDependencia?.EF || 0;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodo.EF = nodo.ES + (parseInt(nodo.duracion) || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodos.reverse().forEach((nodo) => {
|
||||||
|
const esUltimo = !nodos.some((n) => n.dependencias.includes(nodo.id));
|
||||||
|
if (esUltimo) {
|
||||||
|
nodo.LF = nodo.EF;
|
||||||
|
} else {
|
||||||
|
const nodosSucesores = nodos.filter((n) =>
|
||||||
|
n.dependencias.includes(nodo.id),
|
||||||
|
);
|
||||||
|
nodo.LF = Math.min(...nodosSucesores.map((n) => n.LS));
|
||||||
|
}
|
||||||
|
|
||||||
|
nodo.LS = nodo.LF - (parseInt(nodo.duracion) || 0);
|
||||||
|
});
|
||||||
|
nodos.reverse();
|
||||||
|
|
||||||
|
nodos.forEach((nodo) => {
|
||||||
|
const slack = nodo.LS - nodo.ES;
|
||||||
|
nodo.esCritico = slack === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(nodos);
|
||||||
|
}
|
||||||
|
|
||||||
|
async nodosNoReferenciados() {
|
||||||
|
//@ts-ignore
|
||||||
|
const nodos: Nodo[] = (await this.issues).map((x) => ({
|
||||||
|
id: x["#"],
|
||||||
|
duracion: x.Dias,
|
||||||
|
dependencias: x.Dependencias?.toString().split(",") || [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const todosLosIds = new Set(nodos.map((nodo) => nodo.id));
|
||||||
|
const idsReferenciados = new Set();
|
||||||
|
|
||||||
|
nodos.forEach((nodo) => {
|
||||||
|
nodo.dependencias.forEach((dep) => {
|
||||||
|
idsReferenciados.add(dep.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const noReferenciados = nodos.filter(
|
||||||
|
(nodo) => !idsReferenciados.has(nodo.id),
|
||||||
|
);
|
||||||
|
return noReferenciados;
|
||||||
|
}
|
||||||
|
|
||||||
|
async filtroIssue(ids: string[]) {
|
||||||
|
return Promise.resolve(
|
||||||
|
(await this.issues).filter((issue) => ids.includes(issue["#"])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async filtroNodo() {
|
||||||
|
let nodos = await this.calcularCaminoCritico();
|
||||||
|
return Promise.resolve(nodos.filter((x) => x.esCritico));
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/componentes/paginas/TablaProyect.svelte
Normal file
88
src/componentes/paginas/TablaProyect.svelte
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { type Issue } from "../../types";
|
||||||
|
|
||||||
|
let { issues } = $props();
|
||||||
|
let items = $state(
|
||||||
|
issues.map((x: any) => {
|
||||||
|
if (x.tracker.id == 1) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let orden = $state(false);
|
||||||
|
function sortIssues() {
|
||||||
|
if (!orden) {
|
||||||
|
items = items.sort((a, b) => a.id - b.id);
|
||||||
|
} else {
|
||||||
|
items = items.sort((a, b) => b.id - a.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
sortIssues();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="ms-2"
|
||||||
|
bind:checked={orden}
|
||||||
|
onchange={sortIssues}
|
||||||
|
/>
|
||||||
|
{#if orden}
|
||||||
|
9..1
|
||||||
|
{:else}
|
||||||
|
1..9
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary btn-sm"
|
||||||
|
onclick={() => {
|
||||||
|
const tableContent =
|
||||||
|
'<table border="1"><tr><th>ID</th><th>Descripcion</th><th>Estado</th></tr>' +
|
||||||
|
items
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(
|
||||||
|
(issue: Issue) =>
|
||||||
|
`<tr><td>${issue.id}</td><td>${issue.subject}</td><td>${issue.status?.name || ""}</td></tr>`,
|
||||||
|
)
|
||||||
|
.join("") +
|
||||||
|
"</table>";
|
||||||
|
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(tableContent)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Contenido copiado a la papelera");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Fallo: ", err);
|
||||||
|
});
|
||||||
|
}}>a</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto" style="width: 45%;">
|
||||||
|
<table class="table table-striped table-responsive table-bordered table-sm">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Descripcion</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each items.filter(Boolean) as issue}
|
||||||
|
<tr>
|
||||||
|
<td>{issue.id}</td>
|
||||||
|
<td>{issue.subject}</td>
|
||||||
|
<td>{issue.status?.name}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
18
src/types.d.ts
vendored
18
src/types.d.ts
vendored
@@ -18,3 +18,21 @@ export interface Issue {
|
|||||||
updated_on: string;
|
updated_on: string;
|
||||||
closed_on: string;
|
closed_on: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Tabla {
|
||||||
|
"#": number;
|
||||||
|
Descripcion: string;
|
||||||
|
Dias: number;
|
||||||
|
Dependencias: number[];
|
||||||
|
}
|
||||||
|
interface Nodo {
|
||||||
|
id: number;
|
||||||
|
duracion: number;
|
||||||
|
ES: number; // Early Start
|
||||||
|
EF: number; // Early Finish
|
||||||
|
LS: number; // Late Start
|
||||||
|
LF: number; // Late Finish
|
||||||
|
holgura: number;
|
||||||
|
esCritico: boolean;
|
||||||
|
dependencias: number[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user