hecha tarea

This commit is contained in:
2025-10-28 18:05:15 -03:00
parent bf99cdfe35
commit ab7a2452e7
15 changed files with 282 additions and 64 deletions

View File

@@ -4,9 +4,11 @@
"": { "": {
"name": "test", "name": "test",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.9.2",
"@tailwindcss/vite": "^4.1.15", "@tailwindcss/vite": "^4.1.15",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-redux": "^9.2.0",
"react-router": "^7.9.4", "react-router": "^7.9.4",
"react-router-dom": "^7.9.4", "react-router-dom": "^7.9.4",
"tailwindcss": "^4.1.15", "tailwindcss": "^4.1.15",
@@ -124,6 +126,8 @@
"@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="], "@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="],
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.9.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="],
@@ -154,6 +158,10 @@
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.15", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.15" } }, "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.15", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.15" } }, "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.15", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.15", "@tailwindcss/oxide-darwin-arm64": "4.1.15", "@tailwindcss/oxide-darwin-x64": "4.1.15", "@tailwindcss/oxide-freebsd-x64": "4.1.15", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.15", "@tailwindcss/oxide-linux-arm64-musl": "4.1.15", "@tailwindcss/oxide-linux-x64-gnu": "4.1.15", "@tailwindcss/oxide-linux-x64-musl": "4.1.15", "@tailwindcss/oxide-wasm32-wasi": "4.1.15", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.15", "@tailwindcss/oxide-win32-x64-msvc": "4.1.15" } }, "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.15", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.15", "@tailwindcss/oxide-darwin-arm64": "4.1.15", "@tailwindcss/oxide-darwin-x64": "4.1.15", "@tailwindcss/oxide-freebsd-x64": "4.1.15", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.15", "@tailwindcss/oxide-linux-arm64-musl": "4.1.15", "@tailwindcss/oxide-linux-x64-gnu": "4.1.15", "@tailwindcss/oxide-linux-x64-musl": "4.1.15", "@tailwindcss/oxide-wasm32-wasi": "4.1.15", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.15", "@tailwindcss/oxide-win32-x64-msvc": "4.1.15" } }, "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ=="],
@@ -204,6 +212,8 @@
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/type-utils": "8.46.1", "@typescript-eslint/utils": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/type-utils": "8.46.1", "@typescript-eslint/utils": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", "@typescript-eslint/typescript-estree": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", "@typescript-eslint/typescript-estree": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA=="],
@@ -342,6 +352,8 @@
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
@@ -448,12 +460,20 @@
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"react-router": ["react-router@7.9.4", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA=="], "react-router": ["react-router@7.9.4", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA=="],
"react-router-dom": ["react-router-dom@7.9.4", "", { "dependencies": { "react-router": "7.9.4" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA=="], "react-router-dom": ["react-router-dom@7.9.4", "", { "dependencies": { "react-router": "7.9.4" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA=="],
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
"redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
@@ -502,6 +522,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.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", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="], "vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.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", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],

View File

@@ -10,9 +10,11 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.9.2",
"@tailwindcss/vite": "^4.1.15", "@tailwindcss/vite": "^4.1.15",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-redux": "^9.2.0",
"react-router": "^7.9.4", "react-router": "^7.9.4",
"react-router-dom": "^7.9.4", "react-router-dom": "^7.9.4",
"tailwindcss": "^4.1.15" "tailwindcss": "^4.1.15"

View File

@@ -7,7 +7,7 @@ import { Seccion } from "./Components/Seccion";
import type { PermissionLevel } from "./types/usuario"; import type { PermissionLevel } from "./types/usuario";
function App() { function App() {
const { usuario, setPermissionLevel, setUsuario } = useUsuario(); const { usuario, setPermissionLevel } = useUsuario();
return ( return (
<> <>
<Navbar> <Navbar>
@@ -17,7 +17,9 @@ function App() {
<Link to="/admin">Admin</Link> <Link to="/admin">Admin</Link>
)} )}
<Link to="/productos">productos</Link> <Link to="/productos">productos</Link>
<Link to="/counter">contador</Link>
<Link to="/about">About</Link> <Link to="/about">About</Link>
{usuario !== null && <Link to="/logout">Cerrar Sesion</Link>}
</Seccion> </Seccion>
<Seccion> <Seccion>
El permiso el usuario es: El permiso el usuario es:
@@ -33,7 +35,7 @@ function App() {
</select> </select>
</Seccion> </Seccion>
</Navbar> </Navbar>
<AppRouter usuario={usuario} setUsuario={setUsuario} /> <AppRouter />
</> </>
); );
} }

View File

@@ -0,0 +1,24 @@
import { useUsuario } from "../services/useUsuario";
export function AboutPage() {
const { usuario } = useUsuario();
return (
<>
<table>
<thead>
<tr>
<th>Email</th>
<th>Username</th>
</tr>
</thead>
<tbody>
<tr>
<td>{usuario?.email}</td>
<td>{usuario?.username}</td>
</tr>
</tbody>
</table>
<ul></ul>
</>
);
}

View File

@@ -0,0 +1,35 @@
import { useState } from "react";
import { useCounter } from "../services/useCounter";
export function CounterPage() {
const [salto, setSalto] = useState(0);
const { decrement, value, increment, decrementByAmount, incrementByAmount } =
useCounter();
return (
<>
<h1>{value}</h1>
<div className="flex gap-1 justify-center">
<button onClick={() => incrementByAmount(salto)}> + {salto}</button>
<button onClick={() => increment()}> +1 </button>
<button onClick={() => decrement()}> -1 </button>
<button onClick={() => decrementByAmount(salto)}> - {salto}</button>
</div>
<hr />
<div className="flex gap-2">
<p className="font-bold"> Aumentar por: </p>
<input
type="number"
onInput={(e) => {
e.currentTarget.value = e.currentTarget.value.replace(
/[^0-9]/g,
"",
);
}}
onChange={(e) => setSalto(Number(e.target.value))}
min={0}
/>
</div>
</>
);
}

View File

@@ -0,0 +1,7 @@
import { useUsuario } from "../services/useUsuario";
export function LogOut() {
const { logOut } = useUsuario();
logOut();
return <></>;
}

View File

@@ -1,32 +1,30 @@
import { useState, type FormEvent } from 'react'; import { useState, type FormEvent } from "react";
import { useNavigate } from 'react-router'; import { useNavigate } from "react-router";
import { useUsuario } from "../services/useUsuario";
const Login = ({setUsuario}:any) => { const Login = () => {
const { setUsuario } = useUsuario();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const [email, setEmail] = useState(''); const handleSubmit = async (e: FormEvent) => {
const [password, setPassword] = useState(''); e.preventDefault();
const [loading, setLoading] = useState(false); setLoading(true);
const navigate = useNavigate();
const handleSubmit = async (e:FormEvent) => { try {
e.preventDefault(); setUsuario({
setLoading(true); id: 1,
name: "Juan Pepe",
try { username: "jpepe",
setUsuario( email: email || "",
{ permissionLevel: ["ADMIN"],
id: 1, });
name: "Juan Pepe", navigate("/home");
username: "jpepe", } finally {
email: "", //simulamos latencia
permissionLevel: ["ADMIN"], setLoading(false);
}
);
navigate("/home");
} catch (error) {
} finally {
//simulamos latencia
setLoading(false);
} }
}; };
@@ -79,14 +77,10 @@ const Login = ({setUsuario}:any) => {
type="submit" type="submit"
disabled={loading} disabled={loading}
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white ${ className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white ${
loading ? 'bg-indigo-400' : 'bg-indigo-600 hover:bg-indigo-700' loading ? "bg-indigo-400" : "bg-indigo-600 hover:bg-indigo-700"
} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500`} } focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500`}
> >
{loading ? ( {loading ? <span>Enviando...</span> : <span>Iniciar sesión</span>}
<span>Enviando...</span>
) : (
<span>Iniciar sesión</span>
)}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,4 +1,3 @@
import type { Usuario } from "../types/usuario";
import { Routes } from "react-router"; import { Routes } from "react-router";
import { Route } from "react-router"; import { Route } from "react-router";
import { Navigate } from "react-router"; import { Navigate } from "react-router";
@@ -6,20 +5,26 @@ import { RutaProtegida } from "./RutaProtegida";
import { ComponenteAdmin } from "../Components/ComponenteAdmin"; import { ComponenteAdmin } from "../Components/ComponenteAdmin";
import { Dashboard } from "../Components/Dashboard"; import { Dashboard } from "../Components/Dashboard";
import Login from "../Components/Login"; import Login from "../Components/Login";
import { AboutPage } from "../Components/AboutPage";
import { useUsuario } from "../services/useUsuario";
import { LogOut } from "../Components/LogOut";
import { CounterPage } from "../Components/CounterPage";
interface prop { export function AppRouter() {
usuario: Usuario | null const { usuario } = useUsuario();
setUsuario: any
}
export function AppRouter({usuario, setUsuario}: prop) {
return ( return (
<Routes> <Routes>
<Route path="/login" element={<Login setUsuario={setUsuario} />} /> <Route path="/login" element={<Login />} />
<Route element={<RutaProtegida estaPermitido={!!usuario} redirectTo="/login"/>}> <Route path="/counter" element={<CounterPage />}></Route>
<Route
element={
<RutaProtegida estaPermitido={!!usuario} redirectTo="/login" />
}
>
<Route path="/home" element={<Dashboard />} /> <Route path="/home" element={<Dashboard />} />
<Route path="/about" element={<h1>About</h1>}></Route> <Route path="/about" element={<AboutPage />}></Route>
<Route path="/productos" element={<h1>Productos</h1>}></Route> <Route path="/productos" element={<h1>Productos</h1>}></Route>
<Route path="/logout" element={<LogOut />}></Route>
</Route> </Route>
<Route <Route
path="/admin" path="/admin"

6
src/hooks.ts Normal file
View File

@@ -0,0 +1,6 @@
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@@ -3,11 +3,15 @@ import { createRoot } from "react-dom/client";
import "./index.css"; import "./index.css";
import App from "./App.tsx"; import App from "./App.tsx";
import { BrowserRouter } from "react-router"; import { BrowserRouter } from "react-router";
import { Provider } from "react-redux";
import { store } from "./store.ts";
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<BrowserRouter> <Provider store={store}>
<App /> <BrowserRouter>
</BrowserRouter> <App />
</BrowserRouter>
</Provider>
</StrictMode>, </StrictMode>,
); );

View File

@@ -0,0 +1,36 @@
import { useAppSelector, useAppDispatch } from "../hooks";
import {
increment,
decrement,
incrementByAmount,
decrementByAmount,
} from "../types/slices/counter.slice";
export const useCounter = () => {
const value = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
const incrementAction = () => {
dispatch(increment());
};
const decrementAction = () => {
dispatch(decrement());
};
const incrementByAction = (amount: number) => {
dispatch(incrementByAmount(amount));
};
const decrementByAction = (amount: number) => {
dispatch(decrementByAmount(amount));
};
return {
value,
increment: incrementAction,
decrement: decrementAction,
incrementByAmount: incrementByAction,
decrementByAmount: decrementByAction,
};
};

View File

@@ -1,26 +1,30 @@
import { useState } from "react"; import { useAppSelector, useAppDispatch } from "../hooks";
import {
setUsuario,
setPermissionLevel,
removeUsuario,
} from "../types/slices/usuario.slice";
import type { PermissionLevel, Usuario } from "../types/usuario"; import type { PermissionLevel, Usuario } from "../types/usuario";
export const useUsuario = () => { export const useUsuario = () => {
const [usuario, setUsuario] = useState<Usuario | null>( null const usuario = useAppSelector((state) => state.usuario.usuario);
/* const dispatch = useAppDispatch();
{
id: 1,
name: "Juan Pepe",
username: "jpepe",
email: "",
permissionLevel: ["GUEST"],
}
*/
);
function setPermissionLevel(arg: PermissionLevel) { const setUsuarioAction = (user: Usuario | null) => {
setUsuario({ ...usuario!, permissionLevel: [arg] }); dispatch(setUsuario(user));
} };
const setPermissionLevelAction = (level: PermissionLevel) => {
dispatch(setPermissionLevel(level));
};
const logOut = () => {
dispatch(removeUsuario());
};
return { return {
usuario, usuario,
setUsuario, setUsuario: setUsuarioAction,
setPermissionLevel, setPermissionLevel: setPermissionLevelAction,
logOut: logOut,
}; };
}; };

13
src/store.ts Normal file
View File

@@ -0,0 +1,13 @@
import { configureStore } from "@reduxjs/toolkit";
import usuarioReducer from "./types/slices/usuario.slice";
import counterReducer from "./types/slices/counter.slice";
export const store = configureStore({
reducer: {
usuario: usuarioReducer,
counter: counterReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

View File

@@ -0,0 +1,32 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
decrement: (state) => {
state.value -= 1;
},
decrementByAmount: (state, action: PayloadAction<number>) => {
state.value -= action.payload;
},
},
});
export const { increment, decrement, incrementByAmount, decrementByAmount } =
counterSlice.actions;
export default counterSlice.reducer;

View File

@@ -0,0 +1,32 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
import type { PermissionLevel, Usuario } from "../usuario";
interface UsuarioState {
usuario: Usuario | null;
}
const initialState: UsuarioState = {
usuario: null,
};
const usuarioSlice = createSlice({
name: "usuario",
initialState,
reducers: {
setUsuario: (state, action: PayloadAction<Usuario | null>) => {
state.usuario = action.payload;
},
setPermissionLevel: (state, action: PayloadAction<PermissionLevel>) => {
if (state.usuario) {
state.usuario.permissionLevel = [action.payload];
}
},
removeUsuario: (state) => {
state.usuario = null;
},
},
});
export const { setUsuario, setPermissionLevel, removeUsuario } =
usuarioSlice.actions;
export default usuarioSlice.reducer;