Compare commits
	
		
			3 Commits
		
	
	
		
			12860f2406
			...
			f961f9d8e7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f961f9d8e7 | |||
| da581d9714 | |||
| 271a86b632 | 
							
								
								
									
										506
									
								
								Elecciones-Web/frontend-admin/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										506
									
								
								Elecciones-Web/frontend-admin/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -13,12 +13,14 @@ | ||||
|         "@tanstack/react-query": "^5.85.5", | ||||
|         "axios": "^1.11.0", | ||||
|         "react": "^19.1.1", | ||||
|         "react-dom": "^19.1.1" | ||||
|         "react-dom": "^19.1.1", | ||||
|         "react-select": "^5.10.2" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@eslint/js": "^9.33.0", | ||||
|         "@types/react": "^19.1.10", | ||||
|         "@types/react-dom": "^19.1.7", | ||||
|         "@types/react-select": "^5.0.0", | ||||
|         "@vitejs/plugin-react": "^5.0.0", | ||||
|         "eslint": "^9.33.0", | ||||
|         "eslint-plugin-react-hooks": "^5.2.0", | ||||
| @@ -47,7 +49,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", | ||||
|       "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-validator-identifier": "^7.27.1", | ||||
| @@ -103,7 +104,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", | ||||
|       "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/parser": "^7.28.3", | ||||
| @@ -137,7 +137,6 @@ | ||||
|       "version": "7.28.0", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", | ||||
|       "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -147,7 +146,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", | ||||
|       "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/traverse": "^7.27.1", | ||||
| @@ -189,7 +187,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", | ||||
|       "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -199,7 +196,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", | ||||
|       "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -233,7 +229,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", | ||||
|       "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/types": "^7.28.2" | ||||
| @@ -277,11 +272,19 @@ | ||||
|         "@babel/core": "^7.0.0-0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@babel/runtime": { | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", | ||||
|       "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@babel/template": { | ||||
|       "version": "7.27.2", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", | ||||
|       "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.27.1", | ||||
| @@ -296,7 +299,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", | ||||
|       "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.27.1", | ||||
| @@ -315,7 +317,6 @@ | ||||
|       "version": "7.28.2", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", | ||||
|       "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-string-parser": "^7.27.1", | ||||
| @@ -378,6 +379,126 @@ | ||||
|         "react": ">=16.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/babel-plugin": { | ||||
|       "version": "11.13.5", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", | ||||
|       "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-module-imports": "^7.16.7", | ||||
|         "@babel/runtime": "^7.18.3", | ||||
|         "@emotion/hash": "^0.9.2", | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/serialize": "^1.3.3", | ||||
|         "babel-plugin-macros": "^3.1.0", | ||||
|         "convert-source-map": "^1.5.0", | ||||
|         "escape-string-regexp": "^4.0.0", | ||||
|         "find-root": "^1.1.0", | ||||
|         "source-map": "^0.5.7", | ||||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { | ||||
|       "version": "1.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", | ||||
|       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/cache": { | ||||
|       "version": "11.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", | ||||
|       "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/sheet": "^1.4.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "@emotion/weak-memoize": "^0.4.0", | ||||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/hash": { | ||||
|       "version": "0.9.2", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", | ||||
|       "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/memoize": { | ||||
|       "version": "0.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", | ||||
|       "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/react": { | ||||
|       "version": "11.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", | ||||
|       "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.18.3", | ||||
|         "@emotion/babel-plugin": "^11.13.5", | ||||
|         "@emotion/cache": "^11.14.0", | ||||
|         "@emotion/serialize": "^1.3.3", | ||||
|         "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "@emotion/weak-memoize": "^0.4.0", | ||||
|         "hoist-non-react-statics": "^3.3.1" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.8.0" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/react": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/serialize": { | ||||
|       "version": "1.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", | ||||
|       "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@emotion/hash": "^0.9.2", | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/unitless": "^0.10.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "csstype": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/sheet": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", | ||||
|       "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/unitless": { | ||||
|       "version": "0.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", | ||||
|       "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/use-insertion-effect-with-fallbacks": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", | ||||
|       "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/utils": { | ||||
|       "version": "1.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", | ||||
|       "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/weak-memoize": { | ||||
|       "version": "0.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", | ||||
|       "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@esbuild/aix-ppc64": { | ||||
|       "version": "0.25.9", | ||||
|       "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", | ||||
| @@ -974,6 +1095,31 @@ | ||||
|         "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@floating-ui/core": { | ||||
|       "version": "1.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", | ||||
|       "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@floating-ui/utils": "^0.2.10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@floating-ui/dom": { | ||||
|       "version": "1.7.4", | ||||
|       "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", | ||||
|       "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@floating-ui/core": "^1.7.3", | ||||
|         "@floating-ui/utils": "^0.2.10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@floating-ui/utils": { | ||||
|       "version": "0.2.10", | ||||
|       "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", | ||||
|       "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@humanfs/core": { | ||||
|       "version": "0.19.1", | ||||
|       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", | ||||
| @@ -1044,7 +1190,6 @@ | ||||
|       "version": "0.3.13", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | ||||
|       "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/sourcemap-codec": "^1.5.0", | ||||
| @@ -1055,7 +1200,6 @@ | ||||
|       "version": "3.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | ||||
|       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
| @@ -1065,14 +1209,12 @@ | ||||
|       "version": "1.5.5", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | ||||
|       "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@jridgewell/trace-mapping": { | ||||
|       "version": "0.3.30", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", | ||||
|       "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/resolve-uri": "^3.1.0", | ||||
| @@ -1489,11 +1631,16 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/parse-json": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", | ||||
|       "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/react": { | ||||
|       "version": "19.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", | ||||
|       "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "csstype": "^3.0.2" | ||||
| @@ -1509,6 +1656,25 @@ | ||||
|         "@types/react": "^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/react-select": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-5.0.0.tgz", | ||||
|       "integrity": "sha512-vddLcBpzUMVpVNmnBtpC5cyZ2ajaHx/g6SHUo6lmMw0FIiOzrtmoSQ4UI6TRl+sm8TGGT+Oir8NRMZfYQtgr8Q==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "react-select": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/react-transition-group": { | ||||
|       "version": "4.4.12", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", | ||||
|       "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "@types/react": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|       "version": "8.41.0", | ||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", | ||||
| @@ -1881,6 +2047,21 @@ | ||||
|         "proxy-from-env": "^1.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/babel-plugin-macros": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", | ||||
|       "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.12.5", | ||||
|         "cosmiconfig": "^7.0.0", | ||||
|         "resolve": "^1.19.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10", | ||||
|         "npm": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
| @@ -1962,7 +2143,6 @@ | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", | ||||
|       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6" | ||||
| @@ -2052,6 +2232,31 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/cosmiconfig": { | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", | ||||
|       "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@types/parse-json": "^4.0.0", | ||||
|         "import-fresh": "^3.2.1", | ||||
|         "parse-json": "^5.0.0", | ||||
|         "path-type": "^4.0.0", | ||||
|         "yaml": "^1.10.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cosmiconfig/node_modules/yaml": { | ||||
|       "version": "1.10.2", | ||||
|       "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", | ||||
|       "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", | ||||
|       "license": "ISC", | ||||
|       "engines": { | ||||
|         "node": ">= 6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cross-spawn": { | ||||
|       "version": "7.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", | ||||
| @@ -2071,14 +2276,12 @@ | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | ||||
|       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/debug": { | ||||
|       "version": "4.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", | ||||
|       "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ms": "^2.1.3" | ||||
| @@ -2108,6 +2311,16 @@ | ||||
|         "node": ">=0.4.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dom-helpers": { | ||||
|       "version": "5.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", | ||||
|       "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.8.7", | ||||
|         "csstype": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dunder-proto": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | ||||
| @@ -2129,6 +2342,15 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/error-ex": { | ||||
|       "version": "1.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", | ||||
|       "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "is-arrayish": "^0.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/es-define-property": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | ||||
| @@ -2230,7 +2452,6 @@ | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", | ||||
|       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
| @@ -2504,6 +2725,12 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/find-root": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", | ||||
|       "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/find-up": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", | ||||
| @@ -2743,6 +2970,15 @@ | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/hoist-non-react-statics": { | ||||
|       "version": "3.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", | ||||
|       "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "react-is": "^16.7.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ignore": { | ||||
|       "version": "5.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", | ||||
| @@ -2757,7 +2993,6 @@ | ||||
|       "version": "3.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", | ||||
|       "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "parent-module": "^1.0.0", | ||||
| @@ -2780,6 +3015,27 @@ | ||||
|         "node": ">=0.8.19" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-arrayish": { | ||||
|       "version": "0.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", | ||||
|       "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/is-core-module": { | ||||
|       "version": "2.16.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", | ||||
|       "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "hasown": "^2.0.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-extglob": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | ||||
| @@ -2824,7 +3080,6 @@ | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", | ||||
|       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/js-yaml": { | ||||
| @@ -2844,7 +3099,6 @@ | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", | ||||
|       "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "jsesc": "bin/jsesc" | ||||
| @@ -2860,6 +3114,12 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/json-parse-even-better-errors": { | ||||
|       "version": "2.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", | ||||
|       "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/json-schema-traverse": { | ||||
|       "version": "0.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", | ||||
| @@ -2911,6 +3171,12 @@ | ||||
|         "node": ">= 0.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/lines-and-columns": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", | ||||
|       "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/locate-path": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", | ||||
| @@ -2934,6 +3200,18 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/loose-envify": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", | ||||
|       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "js-tokens": "^3.0.0 || ^4.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "loose-envify": "cli.js" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/lru-cache": { | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | ||||
| @@ -2953,6 +3231,12 @@ | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/memoize-one": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", | ||||
|       "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/merge2": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", | ||||
| @@ -3015,7 +3299,6 @@ | ||||
|       "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": { | ||||
| @@ -3051,6 +3334,15 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/object-assign": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | ||||
|       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/optionator": { | ||||
|       "version": "0.9.4", | ||||
|       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", | ||||
| @@ -3105,7 +3397,6 @@ | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", | ||||
|       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "callsites": "^3.0.0" | ||||
| @@ -3114,6 +3405,24 @@ | ||||
|         "node": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/parse-json": { | ||||
|       "version": "5.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", | ||||
|       "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.0.0", | ||||
|         "error-ex": "^1.3.1", | ||||
|         "json-parse-even-better-errors": "^2.3.0", | ||||
|         "lines-and-columns": "^1.1.6" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-exists": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", | ||||
| @@ -3134,11 +3443,25 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-parse": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", | ||||
|       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/path-type": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", | ||||
|       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "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, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/picomatch": { | ||||
| @@ -3193,6 +3516,17 @@ | ||||
|         "node": ">= 0.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/prop-types": { | ||||
|       "version": "15.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | ||||
|       "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "loose-envify": "^1.4.0", | ||||
|         "object-assign": "^4.1.1", | ||||
|         "react-is": "^16.13.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/proxy-from-env": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", | ||||
| @@ -3251,6 +3585,12 @@ | ||||
|         "react": "^19.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-is": { | ||||
|       "version": "16.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | ||||
|       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/react-refresh": { | ||||
|       "version": "0.17.0", | ||||
|       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", | ||||
| @@ -3261,11 +3601,67 @@ | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-select": { | ||||
|       "version": "5.10.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", | ||||
|       "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.12.0", | ||||
|         "@emotion/cache": "^11.4.0", | ||||
|         "@emotion/react": "^11.8.1", | ||||
|         "@floating-ui/dom": "^1.0.1", | ||||
|         "@types/react-transition-group": "^4.4.0", | ||||
|         "memoize-one": "^6.0.0", | ||||
|         "prop-types": "^15.6.0", | ||||
|         "react-transition-group": "^4.3.0", | ||||
|         "use-isomorphic-layout-effect": "^1.2.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", | ||||
|         "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-transition-group": { | ||||
|       "version": "4.4.5", | ||||
|       "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", | ||||
|       "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.5.5", | ||||
|         "dom-helpers": "^5.0.1", | ||||
|         "loose-envify": "^1.4.0", | ||||
|         "prop-types": "^15.6.2" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.6.0", | ||||
|         "react-dom": ">=16.6.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/resolve": { | ||||
|       "version": "1.22.10", | ||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", | ||||
|       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "is-core-module": "^2.16.0", | ||||
|         "path-parse": "^1.0.7", | ||||
|         "supports-preserve-symlinks-flag": "^1.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "resolve": "bin/resolve" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/resolve-from": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", | ||||
|       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=4" | ||||
| @@ -3385,6 +3781,15 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map": { | ||||
|       "version": "0.5.7", | ||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", | ||||
|       "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map-js": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | ||||
| @@ -3408,6 +3813,12 @@ | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/stylis": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", | ||||
|       "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/supports-color": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", | ||||
| @@ -3421,6 +3832,18 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "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", | ||||
|       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/tinyglobby": { | ||||
|       "version": "0.2.14", | ||||
|       "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", | ||||
| @@ -3593,6 +4016,20 @@ | ||||
|         "punycode": "^2.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/use-isomorphic-layout-effect": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", | ||||
|       "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/react": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/vite": { | ||||
|       "version": "7.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", | ||||
| @@ -3732,6 +4169,21 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/yaml": { | ||||
|       "version": "2.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", | ||||
|       "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", | ||||
|       "dev": true, | ||||
|       "license": "ISC", | ||||
|       "optional": true, | ||||
|       "peer": true, | ||||
|       "bin": { | ||||
|         "yaml": "bin.mjs" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 14.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yocto-queue": { | ||||
|       "version": "0.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", | ||||
|   | ||||
| @@ -15,12 +15,14 @@ | ||||
|     "@tanstack/react-query": "^5.85.5", | ||||
|     "axios": "^1.11.0", | ||||
|     "react": "^19.1.1", | ||||
|     "react-dom": "^19.1.1" | ||||
|     "react-dom": "^19.1.1", | ||||
|     "react-select": "^5.10.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.33.0", | ||||
|     "@types/react": "^19.1.10", | ||||
|     "@types/react-dom": "^19.1.7", | ||||
|     "@types/react-select": "^5.0.0", | ||||
|     "@vitejs/plugin-react": "^5.0.0", | ||||
|     "eslint": "^9.33.0", | ||||
|     "eslint-plugin-react-hooks": "^5.2.0", | ||||
|   | ||||
| @@ -52,11 +52,22 @@ export const AgrupacionesManager = () => { | ||||
|     const handleLogoChange = (agrupacionId: string, categoriaId: number, value: string) => { | ||||
|         setEditedLogos(prev => { | ||||
|             const newLogos = [...prev]; | ||||
|             const existing = newLogos.find(l => l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId); | ||||
|             const existing = newLogos.find(l => | ||||
|                 l.agrupacionPoliticaId === agrupacionId && | ||||
|                 l.categoriaId === categoriaId && | ||||
|                 l.ambitoGeograficoId == null | ||||
|             ); | ||||
|  | ||||
|             if (existing) { | ||||
|                 existing.logoUrl = value; | ||||
|             } else { | ||||
|                 newLogos.push({ id: 0, agrupacionPoliticaId: agrupacionId, categoriaId, logoUrl: value }); | ||||
|                 newLogos.push({ | ||||
|                     id: 0, | ||||
|                     agrupacionPoliticaId: agrupacionId, | ||||
|                     categoriaId, | ||||
|                     logoUrl: value, | ||||
|                     ambitoGeograficoId: null | ||||
|                 }); | ||||
|             } | ||||
|             return newLogos; | ||||
|         }); | ||||
| @@ -91,7 +102,11 @@ export const AgrupacionesManager = () => { | ||||
|     const isLoading = isLoadingAgrupaciones || isLoadingLogos; | ||||
|  | ||||
|     const getLogoUrl = (agrupacionId: string, categoriaId: number) => { | ||||
|         return editedLogos.find(l => l.agrupacionPoliticaId === agrupacionId && l.categoriaId === categoriaId)?.logoUrl || ''; | ||||
|         return editedLogos.find(l => | ||||
|             l.agrupacionPoliticaId === agrupacionId && | ||||
|             l.categoriaId === categoriaId && | ||||
|             l.ambitoGeograficoId == null | ||||
|         )?.logoUrl || ''; | ||||
|     }; | ||||
|  | ||||
|     return ( | ||||
|   | ||||
| @@ -104,11 +104,11 @@ export const ConfiguracionGeneral = () => { | ||||
|                 </p> | ||||
|             </div> | ||||
|             <div className="form-group" style={{ marginTop: '2rem' }}> | ||||
|                 <label htmlFor="ticker-cantidad">Cantidad en Ticker (Dip/Sen)</label> | ||||
|                 <label htmlFor="ticker-cantidad">Cantidad en Ticker (Dip/Sen) (Sumar 1 para "Otros")</label> | ||||
|                 <input id="ticker-cantidad" type="number" value={tickerCantidad} onChange={e => setTickerCantidad(e.target.value)} /> | ||||
|             </div> | ||||
|             <div className="form-group" style={{ marginTop: '2rem' }}> | ||||
|                 <label htmlFor="concejales-cantidad">Cantidad en Widget Concejales</label> | ||||
|                 <label htmlFor="concejales-cantidad">Cantidad en Widget Concejales (Sumar 1 para "Otros")</label> | ||||
|                 <input  | ||||
|                     id="concejales-cantidad" | ||||
|                     type="number"  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import { OrdenDiputadosManager } from './OrdenDiputadosManager'; | ||||
| import { OrdenSenadoresManager } from './OrdenSenadoresManager'; | ||||
| import { ConfiguracionGeneral } from './ConfiguracionGeneral'; | ||||
| import { BancasManager } from './BancasManager'; | ||||
| import { LogoOverridesManager } from './LogoOverridesManager'; | ||||
|  | ||||
| export const DashboardPage = () => { | ||||
|     const { logout } = useAuth(); | ||||
| @@ -17,6 +18,7 @@ export const DashboardPage = () => { | ||||
|             </header> | ||||
|             <main style={{ marginTop: '2rem' }}>    | ||||
|                 <AgrupacionesManager /> | ||||
|                 <LogoOverridesManager /> | ||||
|                 <div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', marginTop: '2rem' }}> | ||||
|                     <div style={{ flex: '1 1 400px' }}> | ||||
|                         <OrdenDiputadosManager /> | ||||
|   | ||||
| @@ -0,0 +1,83 @@ | ||||
| // src/components/LogoOverridesManager.tsx | ||||
| import { useState, useMemo, useEffect } from 'react'; | ||||
| import { useQuery, useQueryClient } from '@tanstack/react-query'; | ||||
| import Select from 'react-select'; | ||||
| import { getMunicipiosForAdmin, getAgrupaciones, getLogos, updateLogos } from '../services/apiService'; | ||||
| import type { MunicipioSimple, AgrupacionPolitica, LogoAgrupacionCategoria } from '../types'; | ||||
|  | ||||
| // --- AÑADIMOS LAS CATEGORÍAS PARA EL SELECTOR --- | ||||
| const CATEGORIAS_OPTIONS = [ | ||||
|     { value: 5, label: 'Senadores' }, | ||||
|     { value: 6, label: 'Diputados' }, | ||||
|     { value: 7, label: 'Concejales' } | ||||
| ]; | ||||
|  | ||||
| export const LogoOverridesManager = () => { | ||||
|     const queryClient = useQueryClient(); | ||||
|     const { data: municipios = [] } = useQuery<MunicipioSimple[]>({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin }); | ||||
|     const { data: agrupaciones = [] } = useQuery<AgrupacionPolitica[]>({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones }); | ||||
|     const { data: logos = [] } = useQuery<LogoAgrupacionCategoria[]>({ queryKey: ['logos'], queryFn: getLogos }); | ||||
|  | ||||
|     // --- NUEVO ESTADO PARA LA CATEGORÍA --- | ||||
|     const [selectedCategoria, setSelectedCategoria] = useState<{ value: number; label: string } | null>(null); | ||||
|     const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||
|     const [selectedAgrupacion, setSelectedAgrupacion] = useState<{ value: string; label: string } | null>(null); | ||||
|     const [logoUrl, setLogoUrl] = useState(''); | ||||
|  | ||||
|     const municipioOptions = useMemo(() => municipios.map(m => ({ value: m.id, label: m.nombre })), [municipios]); | ||||
|     const agrupacionOptions = useMemo(() => agrupaciones.map(a => ({ value: a.id, label: a.nombre })), [agrupaciones]); | ||||
|  | ||||
|     const currentLogo = useMemo(() => { | ||||
|         // La búsqueda ahora depende de los 3 selectores | ||||
|         if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return ''; | ||||
|         return logos.find(l =>  | ||||
|             l.ambitoGeograficoId === parseInt(selectedMunicipio.value) &&  | ||||
|             l.agrupacionPoliticaId === selectedAgrupacion.value && | ||||
|             l.categoriaId === selectedCategoria.value | ||||
|         )?.logoUrl || ''; | ||||
|     }, [logos, selectedMunicipio, selectedAgrupacion, selectedCategoria]); | ||||
|      | ||||
|     useEffect(() => { setLogoUrl(currentLogo) }, [currentLogo]); | ||||
|  | ||||
|     const handleSave = async () => { | ||||
|         if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return; | ||||
|         const newLogoEntry: LogoAgrupacionCategoria = { | ||||
|             id: 0, | ||||
|             agrupacionPoliticaId: selectedAgrupacion.value, | ||||
|             categoriaId: selectedCategoria.value, | ||||
|             ambitoGeograficoId: parseInt(selectedMunicipio.value), | ||||
|             logoUrl: logoUrl || null | ||||
|         }; | ||||
|         try { | ||||
|             await updateLogos([newLogoEntry]); | ||||
|             queryClient.invalidateQueries({ queryKey: ['logos'] }); | ||||
|             alert('Override de logo guardado.'); | ||||
|         } catch { alert('Error al guardar.'); } | ||||
|     }; | ||||
|      | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Overrides de Logos por Municipio y Categoría</h3> | ||||
|             <p>Configure una imagen específica para un partido en un municipio y categoría determinados.</p> | ||||
|             <div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end' }}> | ||||
|                 <div style={{ flex: 1 }}> | ||||
|                     <label>Categoría</label> | ||||
|                     <Select options={CATEGORIAS_OPTIONS} value={selectedCategoria} onChange={setSelectedCategoria} isClearable placeholder="Seleccione..."/> | ||||
|                 </div> | ||||
|                 <div style={{ flex: 1 }}> | ||||
|                     <label>Municipio</label> | ||||
|                     <Select options={municipioOptions} value={selectedMunicipio} onChange={setSelectedMunicipio} isClearable placeholder="Seleccione..."/> | ||||
|                 </div> | ||||
|                 <div style={{ flex: 1 }}> | ||||
|                     <label>Agrupación</label> | ||||
|                     <Select options={agrupacionOptions} value={selectedAgrupacion} onChange={setSelectedAgrupacion} isClearable placeholder="Seleccione..."/> | ||||
|                 </div> | ||||
|                 <div style={{ flex: 2 }}> | ||||
|                     <label>URL del Logo Específico</label> | ||||
|                     <input type="text" value={logoUrl} onChange={e => setLogoUrl(e.target.value)} style={{ width: '100%' }} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria} /> | ||||
|                 </div> | ||||
|                 <button onClick={handleSave} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria}>Guardar</button> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -1,7 +1,7 @@ | ||||
| // src/services/apiService.ts | ||||
| import axios from 'axios'; | ||||
| import { triggerLogout } from '../context/authUtils'; | ||||
| import type { AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria } from '../types'; | ||||
| import type { AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, MunicipioSimple } from '../types'; | ||||
|  | ||||
| const AUTH_API_URL = 'http://localhost:5217/api/auth'; | ||||
| const ADMIN_API_URL = 'http://localhost:5217/api/admin'; | ||||
| @@ -10,7 +10,7 @@ const adminApiClient = axios.create({ | ||||
|   baseURL: ADMIN_API_URL, | ||||
| }); | ||||
|  | ||||
| // --- INTERCEPTORES (una sola vez) --- | ||||
| // --- INTERCEPTORES --- | ||||
|  | ||||
| // Interceptor de Peticiones: Añade el token JWT a cada llamada | ||||
| adminApiClient.interceptors.request.use( | ||||
| @@ -63,44 +63,51 @@ export const updateAgrupacion = async (id: string, data: UpdateAgrupacionData): | ||||
|  | ||||
| // 3. Ordenamiento de Agrupaciones | ||||
| export const updateOrden = async (camara: 'diputados' | 'senadores', ids: string[]): Promise<void> => { | ||||
|     await adminApiClient.put(`/agrupaciones/orden-${camara}`, ids); | ||||
|   await adminApiClient.put(`/agrupaciones/orden-${camara}`, ids); | ||||
| }; | ||||
|  | ||||
| // 4. Gestión de Bancas y Ocupantes | ||||
| export const getBancadas = async (camara: 'diputados' | 'senadores'): Promise<Bancada[]> => { | ||||
|     const camaraId = camara === 'diputados' ? 0 : 1; | ||||
|     const response = await adminApiClient.get(`/bancadas/${camaraId}`); | ||||
|     return response.data; | ||||
|   const camaraId = camara === 'diputados' ? 0 : 1; | ||||
|   const response = await adminApiClient.get(`/bancadas/${camaraId}`); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export interface UpdateBancadaData { | ||||
|     agrupacionPoliticaId: string | null; | ||||
|     nombreOcupante: string | null; | ||||
|     fotoUrl: string | null; | ||||
|     periodo: string | null; | ||||
|   agrupacionPoliticaId: string | null; | ||||
|   nombreOcupante: string | null; | ||||
|   fotoUrl: string | null; | ||||
|   periodo: string | null; | ||||
| } | ||||
|  | ||||
| export const updateBancada = async (bancadaId: number, data: UpdateBancadaData): Promise<void> => { | ||||
|     await adminApiClient.put(`/bancadas/${bancadaId}`, data); | ||||
|   await adminApiClient.put(`/bancadas/${bancadaId}`, data); | ||||
| }; | ||||
|  | ||||
| // 5. Configuración General | ||||
| export type ConfiguracionResponse = Record<string, string>; | ||||
|  | ||||
| export const getConfiguracion = async (): Promise<ConfiguracionResponse> => { | ||||
|     const response = await adminApiClient.get('/configuracion'); | ||||
|     return response.data; | ||||
|   const response = await adminApiClient.get('/configuracion'); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const updateConfiguracion = async (data: Record<string, string>): Promise<void> => { | ||||
|     await adminApiClient.put('/configuracion', data); | ||||
|   await adminApiClient.put('/configuracion', data); | ||||
| }; | ||||
|  | ||||
| export const getLogos = async (): Promise<LogoAgrupacionCategoria[]> => { | ||||
|     const response = await adminApiClient.get('/logos'); | ||||
|     return response.data; | ||||
|   const response = await adminApiClient.get('/logos'); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const updateLogos = async (data: LogoAgrupacionCategoria[]): Promise<void> => { | ||||
|     await adminApiClient.put('/logos', data); | ||||
|   await adminApiClient.put('/logos', data); | ||||
| }; | ||||
|  | ||||
| export const getMunicipiosForAdmin = async (): Promise<MunicipioSimple[]> => { | ||||
|     // Ahora usa adminApiClient, que apunta a /api/admin/ | ||||
|     // La URL final será /api/admin/catalogos/municipios | ||||
|     const response = await adminApiClient.get('/catalogos/municipios'); | ||||
|     return response.data; | ||||
| }; | ||||
| @@ -45,4 +45,7 @@ export interface LogoAgrupacionCategoria { | ||||
|     agrupacionPoliticaId: string; | ||||
|     categoriaId: number; | ||||
|     logoUrl: string | null; | ||||
|     ambitoGeograficoId: number | null; | ||||
| } | ||||
|  | ||||
| export interface MunicipioSimple { id: string; nombre: string; } | ||||
							
								
								
									
										445
									
								
								Elecciones-Web/frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										445
									
								
								Elecciones-Web/frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -18,6 +18,7 @@ | ||||
|         "react": "^19.1.1", | ||||
|         "react-dom": "^19.1.1", | ||||
|         "react-pdf": "^10.1.0", | ||||
|         "react-select": "^5.10.2", | ||||
|         "react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support", | ||||
|         "react-tooltip": "^5.29.1" | ||||
|       }, | ||||
| @@ -26,6 +27,7 @@ | ||||
|         "@types/geojson": "^7946.0.16", | ||||
|         "@types/react": "^19.1.10", | ||||
|         "@types/react-dom": "^19.1.7", | ||||
|         "@types/react-select": "^5.0.0", | ||||
|         "@vitejs/plugin-react": "^5.0.0", | ||||
|         "eslint": "^9.33.0", | ||||
|         "eslint-plugin-react-hooks": "^5.2.0", | ||||
| @@ -54,7 +56,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", | ||||
|       "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-validator-identifier": "^7.27.1", | ||||
| @@ -110,7 +111,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", | ||||
|       "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/parser": "^7.28.3", | ||||
| @@ -144,7 +144,6 @@ | ||||
|       "version": "7.28.0", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", | ||||
|       "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -154,7 +153,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", | ||||
|       "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/traverse": "^7.27.1", | ||||
| @@ -196,7 +194,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", | ||||
|       "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -206,7 +203,6 @@ | ||||
|       "version": "7.27.1", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", | ||||
|       "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
| @@ -240,7 +236,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", | ||||
|       "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/types": "^7.28.2" | ||||
| @@ -284,11 +279,19 @@ | ||||
|         "@babel/core": "^7.0.0-0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@babel/runtime": { | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", | ||||
|       "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.9.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@babel/template": { | ||||
|       "version": "7.27.2", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", | ||||
|       "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.27.1", | ||||
| @@ -303,7 +306,6 @@ | ||||
|       "version": "7.28.3", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", | ||||
|       "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.27.1", | ||||
| @@ -322,7 +324,6 @@ | ||||
|       "version": "7.28.2", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", | ||||
|       "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-string-parser": "^7.27.1", | ||||
| @@ -332,6 +333,126 @@ | ||||
|         "node": ">=6.9.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/babel-plugin": { | ||||
|       "version": "11.13.5", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", | ||||
|       "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/helper-module-imports": "^7.16.7", | ||||
|         "@babel/runtime": "^7.18.3", | ||||
|         "@emotion/hash": "^0.9.2", | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/serialize": "^1.3.3", | ||||
|         "babel-plugin-macros": "^3.1.0", | ||||
|         "convert-source-map": "^1.5.0", | ||||
|         "escape-string-regexp": "^4.0.0", | ||||
|         "find-root": "^1.1.0", | ||||
|         "source-map": "^0.5.7", | ||||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { | ||||
|       "version": "1.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", | ||||
|       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/cache": { | ||||
|       "version": "11.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", | ||||
|       "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/sheet": "^1.4.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "@emotion/weak-memoize": "^0.4.0", | ||||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/hash": { | ||||
|       "version": "0.9.2", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", | ||||
|       "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/memoize": { | ||||
|       "version": "0.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", | ||||
|       "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/react": { | ||||
|       "version": "11.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", | ||||
|       "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.18.3", | ||||
|         "@emotion/babel-plugin": "^11.13.5", | ||||
|         "@emotion/cache": "^11.14.0", | ||||
|         "@emotion/serialize": "^1.3.3", | ||||
|         "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "@emotion/weak-memoize": "^0.4.0", | ||||
|         "hoist-non-react-statics": "^3.3.1" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.8.0" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/react": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/serialize": { | ||||
|       "version": "1.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", | ||||
|       "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@emotion/hash": "^0.9.2", | ||||
|         "@emotion/memoize": "^0.9.0", | ||||
|         "@emotion/unitless": "^0.10.0", | ||||
|         "@emotion/utils": "^1.4.2", | ||||
|         "csstype": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/sheet": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", | ||||
|       "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/unitless": { | ||||
|       "version": "0.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", | ||||
|       "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/use-insertion-effect-with-fallbacks": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", | ||||
|       "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/utils": { | ||||
|       "version": "1.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", | ||||
|       "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@emotion/weak-memoize": { | ||||
|       "version": "0.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", | ||||
|       "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@esbuild/aix-ppc64": { | ||||
|       "version": "0.25.9", | ||||
|       "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", | ||||
| @@ -1023,7 +1144,6 @@ | ||||
|       "version": "0.3.13", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | ||||
|       "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/sourcemap-codec": "^1.5.0", | ||||
| @@ -1034,7 +1154,6 @@ | ||||
|       "version": "3.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | ||||
|       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
| @@ -1057,14 +1176,12 @@ | ||||
|       "version": "1.5.5", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | ||||
|       "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@jridgewell/trace-mapping": { | ||||
|       "version": "0.3.30", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", | ||||
|       "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/resolve-uri": "^3.1.0", | ||||
| @@ -2026,11 +2143,16 @@ | ||||
|         "undici-types": "~7.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/parse-json": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", | ||||
|       "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/react": { | ||||
|       "version": "19.1.10", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", | ||||
|       "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", | ||||
|       "devOptional": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "csstype": "^3.0.2" | ||||
| @@ -2046,6 +2168,25 @@ | ||||
|         "@types/react": "^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/react-select": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-5.0.0.tgz", | ||||
|       "integrity": "sha512-vddLcBpzUMVpVNmnBtpC5cyZ2ajaHx/g6SHUo6lmMw0FIiOzrtmoSQ4UI6TRl+sm8TGGT+Oir8NRMZfYQtgr8Q==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "react-select": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/react-transition-group": { | ||||
|       "version": "4.4.12", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", | ||||
|       "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "@types/react": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|       "version": "8.40.0", | ||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", | ||||
| @@ -2418,6 +2559,21 @@ | ||||
|         "proxy-from-env": "^1.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/babel-plugin-macros": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", | ||||
|       "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.12.5", | ||||
|         "cosmiconfig": "^7.0.0", | ||||
|         "resolve": "^1.19.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10", | ||||
|         "npm": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
| @@ -2508,7 +2664,6 @@ | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", | ||||
|       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6" | ||||
| @@ -2613,6 +2768,31 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/cosmiconfig": { | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", | ||||
|       "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@types/parse-json": "^4.0.0", | ||||
|         "import-fresh": "^3.2.1", | ||||
|         "parse-json": "^5.0.0", | ||||
|         "path-type": "^4.0.0", | ||||
|         "yaml": "^1.10.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cosmiconfig/node_modules/yaml": { | ||||
|       "version": "1.10.2", | ||||
|       "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", | ||||
|       "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", | ||||
|       "license": "ISC", | ||||
|       "engines": { | ||||
|         "node": ">= 6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cross-spawn": { | ||||
|       "version": "7.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", | ||||
| @@ -2632,7 +2812,6 @@ | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | ||||
|       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", | ||||
|       "devOptional": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/d3-array": { | ||||
| @@ -2851,7 +3030,6 @@ | ||||
|       "version": "4.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", | ||||
|       "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ms": "^2.1.3" | ||||
| @@ -2890,6 +3068,16 @@ | ||||
|         "node": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dom-helpers": { | ||||
|       "version": "5.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", | ||||
|       "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.8.7", | ||||
|         "csstype": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dunder-proto": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | ||||
| @@ -2911,6 +3099,15 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/error-ex": { | ||||
|       "version": "1.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", | ||||
|       "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "is-arrayish": "^0.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/es-define-property": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | ||||
| @@ -3012,7 +3209,6 @@ | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", | ||||
|       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
| @@ -3286,6 +3482,12 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/find-root": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", | ||||
|       "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/find-up": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", | ||||
| @@ -3525,6 +3727,15 @@ | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/hoist-non-react-statics": { | ||||
|       "version": "3.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", | ||||
|       "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "react-is": "^16.7.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ignore": { | ||||
|       "version": "5.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", | ||||
| @@ -3539,7 +3750,6 @@ | ||||
|       "version": "3.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", | ||||
|       "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "parent-module": "^1.0.0", | ||||
| @@ -3571,6 +3781,27 @@ | ||||
|         "node": ">=12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-arrayish": { | ||||
|       "version": "0.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", | ||||
|       "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/is-core-module": { | ||||
|       "version": "2.16.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", | ||||
|       "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "hasown": "^2.0.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/is-extglob": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | ||||
| @@ -3634,7 +3865,6 @@ | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", | ||||
|       "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "jsesc": "bin/jsesc" | ||||
| @@ -3650,6 +3880,12 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/json-parse-even-better-errors": { | ||||
|       "version": "2.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", | ||||
|       "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/json-schema-traverse": { | ||||
|       "version": "0.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", | ||||
| @@ -3701,6 +3937,12 @@ | ||||
|         "node": ">= 0.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/lines-and-columns": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", | ||||
|       "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/locate-path": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", | ||||
| @@ -3779,6 +4021,12 @@ | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/memoize-one": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", | ||||
|       "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/merge-refs": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz", | ||||
| @@ -3858,7 +4106,6 @@ | ||||
|       "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": { | ||||
| @@ -3899,7 +4146,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | ||||
|       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", | ||||
|       "license": "MIT", | ||||
|       "peer": true, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
| @@ -3958,7 +4204,6 @@ | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", | ||||
|       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "callsites": "^3.0.0" | ||||
| @@ -3967,6 +4212,24 @@ | ||||
|         "node": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/parse-json": { | ||||
|       "version": "5.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", | ||||
|       "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": "^7.0.0", | ||||
|         "error-ex": "^1.3.1", | ||||
|         "json-parse-even-better-errors": "^2.3.0", | ||||
|         "lines-and-columns": "^1.1.6" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-exists": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", | ||||
| @@ -3987,6 +4250,21 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-parse": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", | ||||
|       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/path-type": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", | ||||
|       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/pdfjs-dist": { | ||||
|       "version": "5.3.93", | ||||
|       "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.93.tgz", | ||||
| @@ -4003,7 +4281,6 @@ | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | ||||
|       "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/picomatch": { | ||||
| @@ -4063,7 +4340,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | ||||
|       "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", | ||||
|       "license": "MIT", | ||||
|       "peer": true, | ||||
|       "dependencies": { | ||||
|         "loose-envify": "^1.4.0", | ||||
|         "object-assign": "^4.1.1", | ||||
| @@ -4132,8 +4408,7 @@ | ||||
|       "version": "16.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | ||||
|       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", | ||||
|       "license": "MIT", | ||||
|       "peer": true | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/react-pdf": { | ||||
|       "version": "10.1.0", | ||||
| @@ -4174,6 +4449,27 @@ | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-select": { | ||||
|       "version": "5.10.2", | ||||
|       "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", | ||||
|       "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.12.0", | ||||
|         "@emotion/cache": "^11.4.0", | ||||
|         "@emotion/react": "^11.8.1", | ||||
|         "@floating-ui/dom": "^1.0.1", | ||||
|         "@types/react-transition-group": "^4.4.0", | ||||
|         "memoize-one": "^6.0.0", | ||||
|         "prop-types": "^15.6.0", | ||||
|         "react-transition-group": "^4.3.0", | ||||
|         "use-isomorphic-layout-effect": "^1.2.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", | ||||
|         "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-simple-maps": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "git+ssh://git@github.com/ozimmortal/react-simple-maps.git#f2b342ae277569b751fa1c9b6638e06ad05ab617", | ||||
| @@ -4206,6 +4502,22 @@ | ||||
|         "react-dom": ">=16.14.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-transition-group": { | ||||
|       "version": "4.4.5", | ||||
|       "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", | ||||
|       "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.5.5", | ||||
|         "dom-helpers": "^5.0.1", | ||||
|         "loose-envify": "^1.4.0", | ||||
|         "prop-types": "^15.6.2" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">=16.6.0", | ||||
|         "react-dom": ">=16.6.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-virtualized-auto-sizer": { | ||||
|       "version": "1.0.26", | ||||
|       "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz", | ||||
| @@ -4216,11 +4528,30 @@ | ||||
|         "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/resolve": { | ||||
|       "version": "1.22.10", | ||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", | ||||
|       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "is-core-module": "^2.16.0", | ||||
|         "path-parse": "^1.0.7", | ||||
|         "supports-preserve-symlinks-flag": "^1.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "resolve": "bin/resolve" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/resolve-from": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", | ||||
|       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=4" | ||||
| @@ -4340,6 +4671,15 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map": { | ||||
|       "version": "0.5.7", | ||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", | ||||
|       "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map-js": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | ||||
| @@ -4388,6 +4728,12 @@ | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/stylis": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", | ||||
|       "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/supports-color": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", | ||||
| @@ -4401,6 +4747,18 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "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", | ||||
|       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/terser": { | ||||
|       "version": "5.43.1", | ||||
|       "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", | ||||
| @@ -4644,6 +5002,20 @@ | ||||
|         "react": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/use-isomorphic-layout-effect": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", | ||||
|       "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "@types/react": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/vite": { | ||||
|       "version": "7.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", | ||||
| @@ -4792,6 +5164,21 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/yaml": { | ||||
|       "version": "2.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", | ||||
|       "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", | ||||
|       "dev": true, | ||||
|       "license": "ISC", | ||||
|       "optional": true, | ||||
|       "peer": true, | ||||
|       "bin": { | ||||
|         "yaml": "bin.mjs" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 14.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yocto-queue": { | ||||
|       "version": "0.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
|     "react": "^19.1.1", | ||||
|     "react-dom": "^19.1.1", | ||||
|     "react-pdf": "^10.1.0", | ||||
|     "react-select": "^5.10.2", | ||||
|     "react-simple-maps": "github:ozimmortal/react-simple-maps#feat/react-19-support", | ||||
|     "react-tooltip": "^5.29.1" | ||||
|   }, | ||||
| @@ -28,6 +29,7 @@ | ||||
|     "@types/geojson": "^7946.0.16", | ||||
|     "@types/react": "^19.1.10", | ||||
|     "@types/react-dom": "^19.1.7", | ||||
|     "@types/react-select": "^5.0.0", | ||||
|     "@vitejs/plugin-react": "^5.0.0", | ||||
|     "eslint": "^9.33.0", | ||||
|     "eslint-plugin-react-hooks": "^5.2.0", | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -6,6 +6,7 @@ import MapaBsAs from './components/MapaBsAs' | ||||
| import { TickerWidget } from './components/TickerWidget' | ||||
| import { TelegramaWidget } from './components/TelegramaWidget' | ||||
| import { ConcejalesWidget } from './components/ConcejalesWidget' | ||||
| import MapaBsAsSecciones from './components/MapaBsAsSecciones' | ||||
|  | ||||
| function App() { | ||||
|   return ( | ||||
| @@ -17,6 +18,7 @@ function App() { | ||||
|         <CongresoWidget /> | ||||
|         <BancasWidget /> | ||||
|         <MapaBsAs /> | ||||
|         <MapaBsAsSecciones /> | ||||
|         <TelegramaWidget /> | ||||
|       </main> | ||||
|     </> | ||||
|   | ||||
| @@ -60,6 +60,14 @@ export interface ConfiguracionPublica { | ||||
|   // ... otras claves públicas que pueda añadir en el futuro | ||||
| } | ||||
|  | ||||
| export interface ResultadoDetalleSeccion { | ||||
|   id: string; // ID de la agrupación para la key | ||||
|   nombre: string; | ||||
|   votos: number; | ||||
|   porcentaje: number; | ||||
|   color: string | null; | ||||
| } | ||||
|  | ||||
| export const getResumenProvincial = async (): Promise<CategoriaResumen[]> => { | ||||
|   const response = await apiClient.get('/resultados/provincia/02'); | ||||
|   return response.data; | ||||
| @@ -130,3 +138,20 @@ export const getResultadosConcejales = async (seccionId: string): Promise<Result | ||||
|   const response = await apiClient.get(`/resultados/concejales/${seccionId}`); | ||||
|   return response.data; | ||||
| }; | ||||
|  | ||||
| export const getDetalleSeccion = async (seccionId: string, categoriaId: number): Promise<ResultadoDetalleSeccion[]> => { | ||||
|     const response = await apiClient.get(`/resultados/seccion/${seccionId}?categoriaId=${categoriaId}`); | ||||
|     return response.data; | ||||
| }; | ||||
|  | ||||
| export const getResultadosConcejalesPorMunicipio = async (municipioId: string): Promise<ResultadoTicker[]> => { | ||||
|   // Usamos el endpoint 'partido' que, según la aclaración de la API, busca por municipio | ||||
|   const response = await apiClient.get(`/resultados/partido/${municipioId}`); | ||||
|   // La API devuelve un objeto, nosotros extraemos el array de resultados | ||||
|   return response.data.resultados; | ||||
| }; | ||||
|  | ||||
| export const getMunicipios = async (): Promise<MunicipioSimple[]> => { | ||||
|   const response = await apiClient.get('/catalogos/municipios'); | ||||
|   return response.data; | ||||
| }; | ||||
| @@ -1,102 +1,119 @@ | ||||
| // src/components/ConcejalesWidget.tsx | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { useState, useMemo, useEffect } from 'react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { getSeccionesElectorales, getResultadosConcejales, getConfiguracionPublica } from '../apiService'; | ||||
| import Select from 'react-select'; // <-- 1. Importar react-select | ||||
| import { getMunicipios, getResultadosConcejalesPorMunicipio, getConfiguracionPublica } from '../apiService'; | ||||
| import type { MunicipioSimple, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; // Reutilizamos los estilos del ticker | ||||
| import './TickerWidget.css'; | ||||
|  | ||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| export const ConcejalesWidget = () => { | ||||
|   const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||
|   const [seccionActualId, setSeccionActualId] = useState<string>(''); | ||||
| // Estilos personalizados para que el selector se vea bien | ||||
| const customSelectStyles = { | ||||
|   control: (base: any) => ({ ...base, minWidth: '220px', border: '1px solid #ced4da' }), | ||||
|   menu: (base: any) => ({ ...base, zIndex: 10 }), // Para que el menú se superponga | ||||
| }; | ||||
|  | ||||
| export const ConcejalesWidget = () => { | ||||
|   // 2. Cambiamos el estado para que se adapte a react-select | ||||
|   const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null); | ||||
|  | ||||
|   // Query para la configuración (para saber cuántos resultados mostrar) | ||||
|   const { data: configData } = useQuery({ | ||||
|     queryKey: ['configuracionPublica'], | ||||
|     queryFn: getConfiguracionPublica, | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|   // Calculamos la cantidad a mostrar desde la configuración | ||||
|   const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10) + 1; | ||||
|   // Usamos la clave de configuración correcta | ||||
|   const cantidadAMostrar = parseInt(configData?.ConcejalesResultadosCantidad || '5', 10); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     getSeccionesElectorales().then(seccionesData => { | ||||
|       if (seccionesData && seccionesData.length > 0) { | ||||
|         const orden = new Map([ | ||||
|           ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | ||||
|           ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | ||||
|         ]); | ||||
|         const getOrden = (nombre: string) => { | ||||
|           const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | ||||
|           return match ? orden.get(match[0]) ?? 99 : 99; | ||||
|         }; | ||||
|         seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | ||||
|         setSecciones(seccionesData); | ||||
|         // Al estar los datos ya ordenados, el [0] será "Sección Capital" | ||||
|         setSeccionActualId(seccionesData[0].id); | ||||
|       } | ||||
|     }); | ||||
|   }, []); // El array de dependencias vacío asegura que esto solo se ejecute una vez | ||||
|  | ||||
|   // Query para obtener los resultados de la sección seleccionada | ||||
|   const { data: resultados, isLoading } = useQuery<ResultadoTicker[]>({ | ||||
|     queryKey: ['resultadosConcejales', seccionActualId], | ||||
|     queryFn: () => getResultadosConcejales(seccionActualId), | ||||
|     enabled: !!seccionActualId, | ||||
|   // 3. Query para obtener la lista de MUNICIPIOS | ||||
|   const { data: municipios = [], isLoading: isLoadingMunicipios } = useQuery<MunicipioSimple[]>({ | ||||
|     queryKey: ['municipios'], | ||||
|     queryFn: getMunicipios, | ||||
|   }); | ||||
|  | ||||
|   // --- INICIO DE LA LÓGICA DE PROCESAMIENTO "OTROS" --- | ||||
|   // Este useEffect se encarga de establecer el valor por defecto | ||||
|   useEffect(() => { | ||||
|     // Se ejecuta solo si tenemos la lista de municipios y aún no hemos seleccionado nada | ||||
|     if (municipios.length > 0 && !selectedMunicipio) { | ||||
|       // Buscamos "LA PLATA" en la lista (insensible a mayúsculas/minúsculas) | ||||
|       const laPlata = municipios.find(m => m.nombre.toUpperCase() === 'LA PLATA'); | ||||
|  | ||||
|       // Si lo encontramos, lo establecemos como el municipio seleccionado | ||||
|       if (laPlata) { | ||||
|         setSelectedMunicipio({ value: laPlata.id, label: laPlata.nombre }); | ||||
|       } | ||||
|     } | ||||
|   }, [municipios, selectedMunicipio]); // Se ejecuta cuando 'municipios' o 'selectedMunicipio' cambian | ||||
|  | ||||
|   // 4. Transformamos los datos para react-select | ||||
|   const municipioOptions = useMemo(() => | ||||
|     municipios | ||||
|       .map(m => ({ value: m.id, label: m.nombre })) | ||||
|       .sort((a, b) => a.label.localeCompare(b.label)), // Orden alfabético | ||||
|     [municipios]); | ||||
|  | ||||
|   // 5. Query para obtener los resultados del MUNICIPIO seleccionado | ||||
|   const { data: resultados, isLoading: isLoadingResultados } = useQuery<ResultadoTicker[]>({ | ||||
|     queryKey: ['resultadosConcejalesPorMunicipio', selectedMunicipio?.value], | ||||
|     queryFn: () => getResultadosConcejalesPorMunicipio(selectedMunicipio!.value), | ||||
|     enabled: !!selectedMunicipio, | ||||
|   }); | ||||
|  | ||||
|   // 6. Lógica para "Otros" (sin cambios funcionales) | ||||
|   let displayResults: ResultadoTicker[] = resultados || []; | ||||
|   if (resultados && resultados.length > cantidadAMostrar) { | ||||
|     const topParties = resultados.slice(0, cantidadAMostrar - 1); | ||||
|     const otherParties = resultados.slice(cantidadAMostrar - 1); | ||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.votosPorcentaje || 0), 0); | ||||
|  | ||||
|     const otrosPorcentaje = otherParties.reduce((sum, party) => sum + (party.porcentaje || 0), 0); | ||||
|     const otrosEntry: ResultadoTicker = { | ||||
|       id: `otros-concejales-${seccionActualId}`, | ||||
|       id: `otros-concejales-${selectedMunicipio?.value}`, | ||||
|       nombre: 'Otros', | ||||
|       nombreCorto: 'Otros', | ||||
|       color: '#888888', | ||||
|       logoUrl: null, | ||||
|       votos: 0, // No es relevante para la visualización del porcentaje | ||||
|       votosPorcentaje: otrosPorcentaje, | ||||
|       votos: 0, | ||||
|       porcentaje: otrosPorcentaje, | ||||
|     }; | ||||
|     displayResults = [...topParties, otrosEntry]; | ||||
|   } else if (resultados) { | ||||
|     displayResults = resultados.slice(0, cantidadAMostrar); | ||||
|   } | ||||
|   // --- FIN DE LA LÓGICA DE PROCESAMIENTO "OTROS" --- | ||||
|  | ||||
|   return ( | ||||
|     <div className="ticker-card" style={{ gridColumn: '1 / -1' }}> | ||||
|       <div className="ticker-header"> | ||||
|         <h3>CONCEJALES - LA PLATA</h3> | ||||
|         <select value={seccionActualId} onChange={e => setSeccionActualId(e.target.value)} disabled={secciones.length === 0}> | ||||
|           {secciones.map(s => <option key={s.id} value={s.id}>{s.nombre}</option>)} | ||||
|         </select> | ||||
|         <h3>CONCEJALES POR MUNICIPIO</h3> | ||||
|         <Select | ||||
|           options={municipioOptions} | ||||
|           value={selectedMunicipio} | ||||
|           onChange={(option) => setSelectedMunicipio(option)} | ||||
|           isLoading={isLoadingMunicipios} | ||||
|           placeholder="Buscar y seleccionar un municipio..." | ||||
|           styles={customSelectStyles} | ||||
|         /> | ||||
|       </div> | ||||
|       <div className="ticker-results"> | ||||
|         {isLoading ? <p>Cargando...</p> : | ||||
|           displayResults.map(partido => ( | ||||
|             <div key={partido.id} className="ticker-party"> | ||||
|               <div className="party-logo"> | ||||
|                 <ImageWithFallback src={partido.logoUrl || undefined} fallbackSrc="/default-avatar.png" alt={`Logo de ${partido.nombre}`} /> | ||||
|         {(isLoadingMunicipios || (isLoadingResultados && selectedMunicipio)) && <p>Cargando...</p>} | ||||
|         {!selectedMunicipio && !isLoadingMunicipios && <p style={{textAlign: 'center', color: '#666'}}>Seleccione un municipio.</p>} | ||||
|         {displayResults.map(partido => ( | ||||
|           <div key={partido.id} className="ticker-party"> | ||||
|             <div className="party-logo"> | ||||
|               <ImageWithFallback src={partido.logoUrl || undefined} fallbackSrc="/default-avatar.png" alt={`Logo de ${partido.nombre}`} /> | ||||
|             </div> | ||||
|             <div className="party-details"> | ||||
|               <div className="party-info"> | ||||
|                 <span className="party-name">{partido.nombreCorto || partido.nombre}</span> | ||||
|                 <span className="party-percent">{formatPercent(partido.porcentaje)}</span> | ||||
|               </div> | ||||
|               <div className="party-details"> | ||||
|                 <div className="party-info"> | ||||
|                   <span className="party-name">{partido.nombreCorto || partido.nombre}</span> | ||||
|                   <span className="party-percent">{formatPercent(partido.votosPorcentaje)}</span> | ||||
|                 </div> | ||||
|                 <div className="party-bar-background"> | ||||
|                   <div className="party-bar-foreground" style={{ width: `${partido.votosPorcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|                 </div> | ||||
|               <div className="party-bar-background"> | ||||
|                 <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|               </div> | ||||
|             </div> | ||||
|           )) | ||||
|         } | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
|   | ||||
| @@ -76,7 +76,7 @@ | ||||
|   min-height: 0; | ||||
|   background-color: var(--background-panel-color); | ||||
|   border-radius: 8px; | ||||
|   padding: 1.5rem; | ||||
|   padding: 1rem; | ||||
|   border: none; | ||||
| } | ||||
|  | ||||
| @@ -195,3 +195,51 @@ | ||||
|     font-size: 1em; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* --- ESTILOS PARA EL SELECTOR DE CATEGORÍA --- */ | ||||
| .mapa-categoria-selector { | ||||
|   display: flex; | ||||
|   margin-bottom: 1.5rem; | ||||
| } | ||||
|  | ||||
| .mapa-categoria-combobox { | ||||
|   width: 100%; | ||||
|   padding: 0.75rem 1rem; | ||||
|   font-size: 1em; | ||||
|   font-weight: 500; | ||||
|   color: var(--text-color); | ||||
|   background-color: #f8f9fa; | ||||
|   border: 1px solid var(--border-color); | ||||
|   border-radius: 6px; | ||||
|   cursor: pointer; | ||||
|   appearance: none; | ||||
|   background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%230073e6%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.4-12.9z%22%2F%3E%3C%2Fsvg%3E'); | ||||
|   background-repeat: no-repeat; | ||||
|   background-position: right 1rem center; | ||||
|   background-size: 0.8em; | ||||
|   transition: all 0.2s ease-in-out; | ||||
| } | ||||
|  | ||||
| .mapa-categoria-combobox:hover { | ||||
|   border-color: var(--primary-accent-color); | ||||
|   background-color: #e9ecef; | ||||
| } | ||||
|  | ||||
| .mapa-categoria-combobox:focus { | ||||
|   outline: none; | ||||
|   border-color: var(--primary-accent-color); | ||||
|   box-shadow: 0 0 0 2px rgba(0, 115, 230, 0.25); | ||||
| } | ||||
|  | ||||
| /* --- ESTILOS PARA SECCIONES NO CLICLEABLES --- */ | ||||
| .rsm-geography.no-results { | ||||
|     pointer-events: none; /* Ignora todos los eventos del ratón (click, hover, etc.) */ | ||||
|     cursor: default;      /* Muestra el cursor por defecto en lugar de la mano */ | ||||
| } | ||||
|  | ||||
| /* Opcional pero recomendado: modificar la regla :hover para que no afecte a las secciones no clicleables */ | ||||
| .rsm-geography:not(.no-results):hover { | ||||
|     stroke: var(--primary-accent-color); | ||||
|     stroke-width: 1.5px; | ||||
|     filter: brightness(1.05); | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| // src/components/MapaBsAs.tsx | ||||
| import { useState, useMemo, useCallback, useEffect } from 'react'; | ||||
| import type { MouseEvent } from 'react'; | ||||
| import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; | ||||
| import { Tooltip } from 'react-tooltip'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| @@ -24,7 +23,7 @@ interface ResultadoDetalladoMunicipio { | ||||
|   ultimaActualizacion: string; | ||||
|   porcentajeEscrutado: number; | ||||
|   porcentajeParticipacion: number; | ||||
|   resultados: { nombre: string; votos: number; porcentaje: number }[]; | ||||
|   resultados: { id: string; nombre: string; votos: number; porcentaje: number; color: string | null; }[]; | ||||
|   votosAdicionales: { enBlanco: number; nulos: number; recurridos: number }; | ||||
| } | ||||
|  | ||||
| @@ -33,11 +32,13 @@ interface Agrupacion { | ||||
|   nombre: string; | ||||
| } | ||||
|  | ||||
| interface Categoria { | ||||
|   id: number; | ||||
|   nombre: string; | ||||
| } | ||||
|  | ||||
| interface PartidoProperties { | ||||
|   id: string; | ||||
|   departamento: string; | ||||
|   cabecera: string; | ||||
|   provincia: string; | ||||
| } | ||||
|  | ||||
| type PartidoGeography = Feature<Geometry, PartidoProperties> & { rsmKey: string }; | ||||
| @@ -50,52 +51,57 @@ const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -600], [1 | ||||
| const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM }; | ||||
| const DEFAULT_MAP_COLOR = '#E0E0E0'; | ||||
|  | ||||
| const CATEGORIAS: Categoria[] = [ | ||||
|   { id: 5, nombre: 'Senadores' }, | ||||
|   { id: 6, nombre: 'Diputados' }, | ||||
|   { id: 7, nombre: 'Concejales' } | ||||
| ]; | ||||
|  | ||||
| // --- Componente Principal --- | ||||
| const MapaBsAs = () => { | ||||
|   const [position, setPosition] = useState(INITIAL_POSITION); | ||||
|   const [selectedAmbitoId, setSelectedAmbitoId] = useState<number | null>(null); | ||||
|   const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6); | ||||
|   const [tooltipContent, setTooltipContent] = useState(''); | ||||
|  | ||||
|   const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapa[]>({ | ||||
|     queryKey: ['mapaResultados'], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa`)).data, | ||||
|     queryKey: ['mapaResultadosPorMunicipio', selectedCategoriaId], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-municipio?categoriaId=${selectedCategoriaId}`)).data, | ||||
|   }); | ||||
|  | ||||
|   const { data: geoData, isLoading: isLoadingGeo } = useQuery<any>({ | ||||
|     queryKey: ['mapaGeoData'], | ||||
|     queryFn: async () => (await axios.get('/partidos-bsas.topojson')).data, | ||||
|   }); | ||||
|  | ||||
|   const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({ | ||||
|     queryKey: ['catalogoAgrupaciones'], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data, | ||||
|   }); | ||||
|  | ||||
|   // --- SU SOLUCIÓN CORRECTA INTEGRADA --- | ||||
|   const { nombresAgrupaciones, resultadosPorDepartamento } = useMemo<{ | ||||
|     nombresAgrupaciones: Map<string, string>; | ||||
|     resultadosPorDepartamento: Map<string, ResultadoMapa>; | ||||
|   }>(() => { | ||||
|       const nombresMap = new Map<string, string>(); | ||||
|       const resultadosMap = new Map<string, ResultadoMapa>(); | ||||
|  | ||||
|       if (agrupacionesData) { | ||||
|           agrupacionesData.forEach((agrupacion) => { | ||||
|               nombresMap.set(agrupacion.id, agrupacion.nombre); | ||||
|           }); | ||||
|       } | ||||
|  | ||||
|       if (resultadosData) { | ||||
|           resultadosData.forEach(r => resultadosMap.set(r.departamentoNombre.toUpperCase(), r)); | ||||
|       } | ||||
|  | ||||
|       return { | ||||
|         nombresAgrupaciones: nombresMap, | ||||
|         resultadosPorDepartamento: resultadosMap | ||||
|       }; | ||||
|     const nombresMap = new Map<string, string>(); | ||||
|     const resultadosMap = new Map<string, ResultadoMapa>(); | ||||
|     if (agrupacionesData) { | ||||
|       agrupacionesData.forEach((agrupacion) => { | ||||
|         nombresMap.set(agrupacion.id, agrupacion.nombre); | ||||
|       }); | ||||
|     } | ||||
|     if (resultadosData) { | ||||
|       resultadosData.forEach(r => { | ||||
|         if (r.departamentoNombre) { | ||||
|           resultadosMap.set(r.departamentoNombre.toUpperCase(), r) | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     return { nombresAgrupaciones: nombresMap, resultadosPorDepartamento: resultadosMap }; | ||||
|   }, [agrupacionesData, resultadosData]); | ||||
|  | ||||
|   const isLoading = isLoadingResultados || isLoadingAgrupaciones || isLoadingGeo; | ||||
|  | ||||
|   // ... (el resto del componente no necesita cambios) | ||||
|    | ||||
|   const handleReset = useCallback(() => { | ||||
|     setSelectedAmbitoId(null); | ||||
|     setPosition(INITIAL_POSITION); | ||||
| @@ -142,75 +148,104 @@ const MapaBsAs = () => { | ||||
|  | ||||
|   const getPartyFillColor = (departamentoNombre: string) => { | ||||
|     const resultado = resultadosPorDepartamento.get(departamentoNombre.toUpperCase()); | ||||
|     if (!resultado || !resultado.colorGanador) { | ||||
|       return DEFAULT_MAP_COLOR; | ||||
|     } | ||||
|     return resultado.colorGanador; | ||||
|     return resultado?.colorGanador || DEFAULT_MAP_COLOR; | ||||
|   }; | ||||
|  | ||||
|   const handleMouseEnter = (e: MouseEvent<SVGPathElement>) => { | ||||
|     const path = e.target as SVGPathElement; | ||||
|     if (path.parentNode) { | ||||
|       path.parentNode.appendChild(path); | ||||
|     } | ||||
|   }; | ||||
|   // --- Helper de Renderizado --- | ||||
|   const renderGeography = (geo: PartidoGeography, isSelectedGeo: boolean = false) => { | ||||
|     const departamentoNombre = geo.properties.departamento.toUpperCase(); | ||||
|     const resultado = resultadosPorDepartamento.get(departamentoNombre); | ||||
|     const isClickable = !!resultado; | ||||
|     const isSelected = isSelectedGeo || (selectedAmbitoId !== null && selectedAmbitoId === resultado?.ambitoId); | ||||
|     const isFaded = !isSelectedGeo && selectedAmbitoId !== null && !isSelected; | ||||
|     const nombreAgrupacionGanadora = resultado ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos'; | ||||
|  | ||||
|   if (isLoading) return <div className="loading-container">Cargando datos del mapa...</div>; | ||||
|     return ( | ||||
|       <Geography | ||||
|         key={geo.rsmKey + (isSelectedGeo ? '-selected' : '')} | ||||
|         geography={geo} | ||||
|         data-tooltip-id="partido-tooltip" | ||||
|         data-tooltip-content={`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`} | ||||
|         className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`} | ||||
|         fill={getPartyFillColor(geo.properties.departamento)} | ||||
|         onClick={isClickable ? () => handleGeographyClick(geo) : undefined} | ||||
|         onMouseEnter={() => setTooltipContent(`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`)} | ||||
|         onMouseLeave={() => setTooltipContent("")} | ||||
|       /> | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className="mapa-wrapper"> | ||||
|       <div className="mapa-container"> | ||||
|         <ComposableMap projection="geoMercator" projectionConfig={{ scale: 4400, center: [-60.5, -37.2] }} className="rsm-svg"> | ||||
|           <ZoomableGroup | ||||
|             center={position.center} | ||||
|             zoom={position.zoom} | ||||
|             onMoveEnd={handleMoveEnd} | ||||
|             style={{ transition: "transform 400ms ease-in-out" }} | ||||
|             translateExtent={TRANSLATE_EXTENT} | ||||
|             minZoom={MIN_ZOOM} | ||||
|             maxZoom={MAX_ZOOM} | ||||
|             filterZoomEvent={(e: WheelEvent) => { | ||||
|               if (e.deltaY > 0) { | ||||
|                 handleReset(); | ||||
|               } else if (e.deltaY < 0) { | ||||
|                 handleZoomIn(); | ||||
|               } | ||||
|               return true; | ||||
|             }} | ||||
|         {isLoading ? <div className="spinner"></div> : ( | ||||
|           <ComposableMap | ||||
|             key={selectedCategoriaId} | ||||
|             projection="geoMercator" | ||||
|             projectionConfig={{ scale: 4400, center: [-60.5, -37.2] }} | ||||
|             className="rsm-svg" | ||||
|             data-tooltip-id="partido-tooltip" | ||||
|           > | ||||
|             {geoData && ( | ||||
|               <Geographies geography={geoData}> | ||||
|                 {({ geographies }: { geographies: PartidoGeography[] }) => | ||||
|                   geographies.map((geo) => { | ||||
|                     const departamentoNombre = geo.properties.departamento.toUpperCase(); | ||||
|                     const resultado = resultadosPorDepartamento.get(departamentoNombre); | ||||
|                     const isSelected = resultado ? selectedAmbitoId === resultado.ambitoId : false; | ||||
|                     const isFaded = selectedAmbitoId !== null && !isSelected; | ||||
|                     const nombreAgrupacionGanadora = resultado ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos'; | ||||
|             <ZoomableGroup | ||||
|               center={position.center} | ||||
|               zoom={position.zoom} | ||||
|               onMoveEnd={handleMoveEnd} | ||||
|               style={{ transition: "transform 400ms ease-in-out" }} | ||||
|               translateExtent={TRANSLATE_EXTENT} | ||||
|               minZoom={MIN_ZOOM} | ||||
|               maxZoom={MAX_ZOOM} | ||||
|               filterZoomEvent={(e: WheelEvent) => { | ||||
|                 if (e.deltaY > 0) { | ||||
|                   handleReset(); | ||||
|                 } else if (e.deltaY < 0) { | ||||
|                   handleZoomIn(); | ||||
|                 } | ||||
|                 return true; | ||||
|               }} | ||||
|             > | ||||
|               {geoData && ( | ||||
|                 <Geographies geography={geoData}> | ||||
|                   {({ geographies }: { geographies: PartidoGeography[] }) => { | ||||
|                     const selectedGeo = selectedAmbitoId | ||||
|                       ? geographies.find(geo => { | ||||
|                         const resultado = resultadosPorDepartamento.get(geo.properties.departamento.toUpperCase()); | ||||
|                         return resultado?.ambitoId === selectedAmbitoId; | ||||
|                       }) | ||||
|                       : null; | ||||
|  | ||||
|                     return ( | ||||
|                       <Geography | ||||
|                         key={geo.rsmKey} | ||||
|                         geography={geo} | ||||
|                         data-tooltip-id="partido-tooltip" | ||||
|                         data-tooltip-content={`${geo.properties.departamento}: ${nombreAgrupacionGanadora}`} | ||||
|                         className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''}`} | ||||
|                         fill={getPartyFillColor(geo.properties.departamento)} | ||||
|                         onClick={() => handleGeographyClick(geo)} | ||||
|                         onMouseEnter={handleMouseEnter} | ||||
|                       /> | ||||
|                       <> | ||||
|                         {geographies.map(geo => (!selectedGeo || geo.rsmKey !== selectedGeo.rsmKey) ? renderGeography(geo) : null)} | ||||
|                         {selectedGeo && renderGeography(selectedGeo, true)} | ||||
|                       </> | ||||
|                     ); | ||||
|                   }) | ||||
|                 } | ||||
|               </Geographies> | ||||
|             )} | ||||
|           </ZoomableGroup> | ||||
|         </ComposableMap> | ||||
|         <Tooltip id="partido-tooltip" variant="light" /> | ||||
|                   }} | ||||
|                 </Geographies> | ||||
|               )} | ||||
|             </ZoomableGroup> | ||||
|           </ComposableMap> | ||||
|         )} | ||||
|         <Tooltip id="partido-tooltip" content={tooltipContent} /> | ||||
|         {selectedAmbitoId !== null && <ControlesMapa onReset={handleReset} />} | ||||
|       </div> | ||||
|       <div className="info-panel"> | ||||
|         <DetalleMunicipio ambitoId={selectedAmbitoId} onReset={handleReset} /> | ||||
|         <div className="mapa-categoria-selector"> | ||||
|           <select | ||||
|             className="mapa-categoria-combobox" | ||||
|             value={selectedCategoriaId} | ||||
|             onChange={(e) => { | ||||
|               setSelectedCategoriaId(Number(e.target.value)); | ||||
|               handleReset(); | ||||
|             }} | ||||
|           > | ||||
|             {CATEGORIAS.map(cat => ( | ||||
|               <option key={cat.id} value={cat.id}> | ||||
|                 {cat.nombre} | ||||
|               </option> | ||||
|             ))} | ||||
|           </select> | ||||
|         </div> | ||||
|         <DetalleMunicipio ambitoId={selectedAmbitoId} onReset={handleReset} categoriaId={selectedCategoriaId} /> | ||||
|         <Legend resultados={resultadosPorDepartamento} nombresAgrupaciones={nombresAgrupaciones} /> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -224,10 +259,10 @@ const ControlesMapa = ({ onReset }: { onReset: () => void }) => ( | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| const DetalleMunicipio = ({ ambitoId, onReset }: { ambitoId: number | null; onReset: () => void }) => { | ||||
| const DetalleMunicipio = ({ ambitoId, onReset, categoriaId }: { ambitoId: number | null; onReset: () => void; categoriaId: number; }) => { | ||||
|   const { data, isLoading, error } = useQuery<ResultadoDetalladoMunicipio>({ | ||||
|     queryKey: ['municipioDetalle', ambitoId], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/municipio/${ambitoId}`)).data, | ||||
|     queryKey: ['municipioDetalle', ambitoId, categoriaId], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/municipio/${ambitoId}?categoriaId=${categoriaId}`)).data, | ||||
|     enabled: !!ambitoId, | ||||
|   }); | ||||
|  | ||||
| @@ -240,18 +275,24 @@ const DetalleMunicipio = ({ ambitoId, onReset }: { ambitoId: number | null; onRe | ||||
|       <button className="reset-button-panel" onClick={onReset}>← VOLVER</button> | ||||
|       <h3>{data?.municipioNombre}</h3> | ||||
|       <div className="detalle-metricas"> | ||||
|         <span><strong>Escrutado:</strong> {data?.porcentajeEscrutado.toFixed(2)}%</span> | ||||
|         <span><strong>Participación:</strong> {data?.porcentajeParticipacion.toFixed(2)}%</span> | ||||
|         <span><strong>Escrutado:</strong> {(data?.porcentajeEscrutado ?? 0).toFixed(2)}%</span> | ||||
|         <span><strong>Participación:</strong> {(data?.porcentajeParticipacion ?? 0).toFixed(2)}%</span> | ||||
|       </div> | ||||
|       <ul className="resultados-lista"> | ||||
|         {data?.resultados.map((r, index) => ( | ||||
|         {(data?.resultados ?? []).map((r, index) => ( | ||||
|           <li key={`${r.nombre}-${index}`}> | ||||
|             <div className="resultado-info"> | ||||
|               <span className="partido-nombre">{r.nombre}</span> | ||||
|               <span className="partido-votos">{r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)</span> | ||||
|             </div> | ||||
|             <div className="progress-bar"> | ||||
|               <div className="progress-fill" style={{ width: `${r.porcentaje}%` }}></div> | ||||
|               <div | ||||
|                 className="progress-fill" | ||||
|                 style={{ | ||||
|                   width: `${r.porcentaje}%`, | ||||
|                   backgroundColor: r.color || DEFAULT_MAP_COLOR | ||||
|                 }} | ||||
|               ></div> | ||||
|             </div> | ||||
|           </li> | ||||
|         ))} | ||||
| @@ -262,32 +303,32 @@ const DetalleMunicipio = ({ ambitoId, onReset }: { ambitoId: number | null; onRe | ||||
|  | ||||
| const Legend = ({ resultados, nombresAgrupaciones }: { resultados: Map<string, ResultadoMapa>, nombresAgrupaciones: Map<string, string> }) => { | ||||
|  | ||||
|     const legendItems = useMemo(() => { | ||||
|         const ganadoresUnicos = new Map<string, { nombre: string; color: string }>(); | ||||
|   const legendItems = useMemo(() => { | ||||
|     const ganadoresUnicos = new Map<string, { nombre: string; color: string }>(); | ||||
|  | ||||
|         resultados.forEach(resultado => { | ||||
|             if (resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) { | ||||
|                 ganadoresUnicos.set(resultado.agrupacionGanadoraId, { | ||||
|                     nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido', | ||||
|                     color: resultado.colorGanador | ||||
|                 }); | ||||
|             } | ||||
|     resultados.forEach(resultado => { | ||||
|       if (resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) { | ||||
|         ganadoresUnicos.set(resultado.agrupacionGanadoraId, { | ||||
|           nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido', | ||||
|           color: resultado.colorGanador | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|         return Array.from(ganadoresUnicos.values()); | ||||
|     }, [resultados, nombresAgrupaciones]); | ||||
|     return Array.from(ganadoresUnicos.values()); | ||||
|   }, [resultados, nombresAgrupaciones]); | ||||
|  | ||||
|     return ( | ||||
|         <div className="legend"> | ||||
|             <h4>Leyenda de Ganadores</h4> | ||||
|             {legendItems.map(item => ( | ||||
|                 <div key={item.nombre} className="legend-item"> | ||||
|                     <div className="legend-color-box" style={{ backgroundColor: item.color }} /> | ||||
|                     <span>{item.nombre}</span> | ||||
|                 </div> | ||||
|             ))} | ||||
|   return ( | ||||
|     <div className="legend"> | ||||
|       <h4>Leyenda de Ganadores</h4> | ||||
|       {legendItems.map(item => ( | ||||
|         <div key={item.nombre} className="legend-item"> | ||||
|           <div className="legend-color-box" style={{ backgroundColor: item.color }} /> | ||||
|           <span>{item.nombre}</span> | ||||
|         </div> | ||||
|     ); | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default MapaBsAs; | ||||
							
								
								
									
										303
									
								
								Elecciones-Web/frontend/src/components/MapaBsAsSecciones.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								Elecciones-Web/frontend/src/components/MapaBsAsSecciones.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
| // src/components/MapaBsAsSecciones.tsx | ||||
| import { useState, useMemo, useCallback, useEffect } from 'react'; | ||||
| import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; | ||||
| import { Tooltip } from 'react-tooltip'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import axios from 'axios'; | ||||
| import { geoCentroid } from 'd3-geo'; | ||||
| import { getDetalleSeccion } from '../apiService'; | ||||
| import type { ResultadoDetalleSeccion } from '../apiService'; | ||||
| import './MapaBsAs.css'; | ||||
|  | ||||
| // --- Interfaces y Tipos --- | ||||
| type PointTuple = [number, number]; | ||||
| interface ResultadoMapaSeccion { | ||||
|   seccionId: string; | ||||
|   seccionNombre: string; | ||||
|   agrupacionGanadoraId: string | null; | ||||
|   colorGanador: string | null; | ||||
| } | ||||
| interface Agrupacion { id: string; nombre: string; } | ||||
| interface Categoria { id: number; nombre: string; } | ||||
| type SeccionGeography = { | ||||
|   rsmKey: string; | ||||
|   properties: { seccion: string; fna: string; }; | ||||
| }; | ||||
|  | ||||
| // --- Constantes --- | ||||
| const API_BASE_URL = 'http://localhost:5217/api'; | ||||
| const DEFAULT_MAP_COLOR = '#E0E0E0'; | ||||
| const CATEGORIAS: Categoria[] = [{ id: 5, nombre: 'Senadores' }, { id: 6, nombre: 'Diputados' }]; | ||||
| const SECCION_ID_TO_ROMAN: Record<string, string> = { '1': 'I', '2': 'II', '3': 'III', '4': 'IV', '5': 'V', '6': 'VI', '7': 'VII', '8': 'VIII' }; | ||||
| const ROMAN_TO_SECCION_ID: Record<string, string> = { 'I': '1', 'II': '2', 'III': '3', 'IV': '4', 'V': '5', 'VI': '6', 'VII': '7', 'VIII': '8' }; | ||||
| // --- CORRECCIÓN 1: Mover NOMBRES_SECCIONES aquí para que sea global al archivo --- | ||||
| const NOMBRES_SECCIONES: Record<string, string> = { | ||||
|     'I': 'Sección Primera', 'II': 'Sección Segunda', 'III': 'Sección Tercera', 'IV': 'Sección Cuarta', | ||||
|     'V': 'Sección Quinta', 'VI': 'Sección Sexta', 'VII': 'Sección Séptima', 'VIII': 'Sección Capital' | ||||
| }; | ||||
| const MIN_ZOOM = 1; | ||||
| const MAX_ZOOM = 5; | ||||
| const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -600], [1100, 300]]; | ||||
| const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM }; | ||||
|  | ||||
|  | ||||
| // --- Componente de Detalle --- | ||||
| const DetalleSeccion = ({ seccion, categoriaId, onReset }: { seccion: SeccionGeography | null, categoriaId: number, onReset: () => void }) => { | ||||
|   const seccionId = seccion ? ROMAN_TO_SECCION_ID[seccion.properties.seccion] : null; | ||||
|  | ||||
|   const { data: resultadosDetalle, isLoading, error } = useQuery<ResultadoDetalleSeccion[]>({ | ||||
|     queryKey: ['detalleSeccion', seccionId, categoriaId], | ||||
|     queryFn: () => getDetalleSeccion(seccionId!, categoriaId), | ||||
|     enabled: !!seccionId, | ||||
|   }); | ||||
|  | ||||
|   if (!seccion) { | ||||
|     return ( | ||||
|       <div className="detalle-placeholder"> | ||||
|         <h3>Resultados por Sección</h3> | ||||
|         <p>Haga clic en una sección del mapa para ver los resultados detallados.</p> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|    | ||||
|   if (isLoading) return (<div className="detalle-loading"><div className="spinner"></div><p>Cargando resultados de la sección...</p></div>); | ||||
|   if (error) return <div className="detalle-error">Error al cargar los datos de la sección.</div>; | ||||
|  | ||||
|   const nombreSeccionLegible = NOMBRES_SECCIONES[seccion.properties.seccion] || "Sección Desconocida"; | ||||
|  | ||||
|   return ( | ||||
|     <div className="detalle-content"> | ||||
|       <button className="reset-button-panel" onClick={onReset}>← VOLVER</button> | ||||
|       <h3>{nombreSeccionLegible}</h3> | ||||
|       <ul className="resultados-lista"> | ||||
|         {resultadosDetalle?.map((r) => ( | ||||
|           <li key={r.id}> | ||||
|             <div className="resultado-info"> | ||||
|               <span className="partido-nombre">{r.nombre}</span> | ||||
|               <span className="partido-votos">{r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)</span> | ||||
|             </div> | ||||
|             <div className="progress-bar"> | ||||
|               {/* --- CORRECCIÓN 2: Usar el color de la API --- */} | ||||
|               <div className="progress-fill" style={{ width: `${r.porcentaje}%`, backgroundColor: r.color || DEFAULT_MAP_COLOR }}></div> | ||||
|             </div> | ||||
|           </li> | ||||
|         ))} | ||||
|       </ul> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| // --- Componente de Controles del Mapa --- | ||||
| const ControlesMapa = ({ onReset }: { onReset: () => void }) => ( | ||||
|   <div className="map-controls"> | ||||
|     <button onClick={onReset}>← VOLVER</button> | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| // --- Componente Principal --- | ||||
| const MapaBsAsSecciones = () => { | ||||
|   const [position, setPosition] = useState(INITIAL_POSITION); | ||||
|   const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6); | ||||
|   const [clickedSeccion, setClickedSeccion] = useState<SeccionGeography | null>(null); | ||||
|   const [tooltipContent, setTooltipContent] = useState(''); | ||||
|  | ||||
|   const { data: geoData, isLoading: isLoadingGeo } = useQuery<any>({ | ||||
|     queryKey: ['mapaGeoDataSecciones'], | ||||
|     queryFn: async () => (await axios.get('./secciones-electorales-pba.topojson')).data, | ||||
|   }); | ||||
|  | ||||
|   const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapaSeccion[]>({ | ||||
|     queryKey: ['mapaResultadosPorSeccion', selectedCategoriaId], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-seccion?categoriaId=${selectedCategoriaId}`)).data, | ||||
|   }); | ||||
|  | ||||
|   const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({ | ||||
|     queryKey: ['catalogoAgrupaciones'], | ||||
|     queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data, | ||||
|   }); | ||||
|  | ||||
|   const { nombresAgrupaciones, resultadosPorSeccionRomana } = useMemo<{ | ||||
|     nombresAgrupaciones: Map<string, string>; | ||||
|     resultadosPorSeccionRomana: Map<string, ResultadoMapaSeccion>; | ||||
|   }>(() => { | ||||
|     const nombresMap = new Map<string, string>(); | ||||
|     const resultadosMap = new Map<string, ResultadoMapaSeccion>(); | ||||
|  | ||||
|     if (agrupacionesData) { | ||||
|       agrupacionesData.forEach(a => nombresMap.set(a.id, a.nombre)); | ||||
|     } | ||||
|     if (resultadosData) { | ||||
|       resultadosData.forEach(r => { | ||||
|         const roman = SECCION_ID_TO_ROMAN[r.seccionId]; | ||||
|         if (roman) resultadosMap.set(roman, r); | ||||
|       }); | ||||
|     } | ||||
|     return { nombresAgrupaciones: nombresMap, resultadosPorSeccionRomana: resultadosMap }; | ||||
|   }, [agrupacionesData, resultadosData]); | ||||
|  | ||||
|   const isLoading = isLoadingGeo || isLoadingResultados || isLoadingAgrupaciones; | ||||
|  | ||||
|   const handleReset = useCallback(() => { | ||||
|     setClickedSeccion(null); | ||||
|     setPosition(INITIAL_POSITION); | ||||
|   }, []); | ||||
|  | ||||
|   const handleGeographyClick = useCallback((geo: SeccionGeography) => { | ||||
|     if (clickedSeccion?.rsmKey === geo.rsmKey) { | ||||
|       handleReset(); | ||||
|     } else { | ||||
|       const centroid = geoCentroid(geo as any) as PointTuple; | ||||
|       setPosition({ center: centroid, zoom: 2 }); | ||||
|       setClickedSeccion(geo); | ||||
|     } | ||||
|   }, [clickedSeccion, handleReset]); | ||||
|  | ||||
|   const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => { | ||||
|     if (newPosition.zoom <= MIN_ZOOM) { | ||||
|       if (position.zoom > MIN_ZOOM || clickedSeccion !== null) { | ||||
|         handleReset(); | ||||
|       } | ||||
|       return; | ||||
|     } | ||||
|     if (newPosition.zoom < position.zoom && clickedSeccion !== null) { | ||||
|       setClickedSeccion(null); | ||||
|     } | ||||
|     setPosition({ center: newPosition.coordinates, zoom: newPosition.zoom }); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const handleKeyDown = (e: KeyboardEvent) => e.key === 'Escape' && handleReset(); | ||||
|     window.addEventListener('keydown', handleKeyDown); | ||||
|     return () => window.removeEventListener('keydown', handleKeyDown); | ||||
|   }, [handleReset]); | ||||
|  | ||||
|   const getSectionFillColor = (seccionRomana: string) => { | ||||
|     return resultadosPorSeccionRomana.get(seccionRomana)?.colorGanador || DEFAULT_MAP_COLOR; | ||||
|   }; | ||||
|  | ||||
|   const handleZoomIn = () => { | ||||
|     if (position.zoom < MAX_ZOOM) { | ||||
|       setPosition(p => ({ ...p, zoom: Math.min(p.zoom * 1.5, MAX_ZOOM) })); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className="mapa-wrapper"> | ||||
|       <div className="mapa-container"> | ||||
|         {isLoading ? <div className="spinner"></div> : ( | ||||
|           <ComposableMap | ||||
|             key={selectedCategoriaId} | ||||
|             projection="geoMercator" | ||||
|             projectionConfig={{ scale: 4400, center: [-60.5, -37.2] }} | ||||
|             className="rsm-svg" | ||||
|             data-tooltip-id="seccion-tooltip" | ||||
|           > | ||||
|             <ZoomableGroup | ||||
|               center={position.center} | ||||
|               zoom={position.zoom} | ||||
|               onMoveEnd={handleMoveEnd} | ||||
|               minZoom={MIN_ZOOM} | ||||
|               maxZoom={MAX_ZOOM} | ||||
|               translateExtent={TRANSLATE_EXTENT} | ||||
|               style={{ transition: "transform 400ms ease-in-out" }} | ||||
|               filterZoomEvent={(e: WheelEvent) => { | ||||
|                 if (e.deltaY > 0) { | ||||
|                   handleReset(); | ||||
|                 } else if (e.deltaY < 0) { | ||||
|                   handleZoomIn(); | ||||
|                 } | ||||
|                 return true; | ||||
|               }} | ||||
|             > | ||||
|               {geoData && ( | ||||
|                 <Geographies geography={geoData}> | ||||
|                   {({ geographies }: { geographies: SeccionGeography[] }) => | ||||
|                     geographies.map((geo) => { | ||||
|                       const seccionRomana = geo.properties.seccion; | ||||
|                       const resultado = resultadosPorSeccionRomana.get(seccionRomana); | ||||
|                       const nombreGanador = resultado?.agrupacionGanadoraId ? nombresAgrupaciones.get(resultado.agrupacionGanadoraId) : 'Sin datos'; | ||||
|                       const isSelected = clickedSeccion?.rsmKey === geo.rsmKey; | ||||
|                       const isFaded = clickedSeccion && !isSelected; | ||||
|                       const isClickable = !!resultado; | ||||
|  | ||||
|                       return ( | ||||
|                         <Geography | ||||
|                           key={geo.rsmKey + (isSelected ? '-selected' : '')} | ||||
|                           geography={geo as any} | ||||
|                           data-tooltip-id="seccion-tooltip" | ||||
|                           onClick={isClickable ? () => handleGeographyClick(geo) : undefined} | ||||
|                           onMouseEnter={() => { | ||||
|                             if (isClickable) { | ||||
|                               // --- CORRECCIÓN 3: Tooltip con nombre de sección --- | ||||
|                               const nombreSeccionLegible = NOMBRES_SECCIONES[geo.properties.seccion] || "Sección Desconocida"; | ||||
|                               setTooltipContent(`${nombreSeccionLegible}: ${nombreGanador}`); | ||||
|                             } | ||||
|                           }} | ||||
|                           onMouseLeave={() => setTooltipContent("")} | ||||
|                           className={`rsm-geography ${isSelected ? 'selected' : ''} ${isFaded ? 'faded' : ''} ${!isClickable ? 'no-results' : ''}`} | ||||
|                           fill={getSectionFillColor(seccionRomana)} | ||||
|                         /> | ||||
|                       ); | ||||
|                     }) | ||||
|                   } | ||||
|                 </Geographies> | ||||
|               )} | ||||
|             </ZoomableGroup> | ||||
|           </ComposableMap> | ||||
|         )} | ||||
|         {clickedSeccion && <ControlesMapa onReset={handleReset} />} | ||||
|         <Tooltip id="seccion-tooltip" content={tooltipContent} /> | ||||
|       </div> | ||||
|       <div className="info-panel"> | ||||
|         <div className="mapa-categoria-selector"> | ||||
|           <select | ||||
|             className="mapa-categoria-combobox" | ||||
|             value={selectedCategoriaId} | ||||
|             onChange={(e) => { | ||||
|               setSelectedCategoriaId(Number(e.target.value)); | ||||
|               handleReset(); | ||||
|             }} | ||||
|           > | ||||
|             {CATEGORIAS.map(cat => ( | ||||
|               <option key={cat.id} value={cat.id}> | ||||
|                 {cat.nombre} | ||||
|               </option> | ||||
|             ))} | ||||
|           </select> | ||||
|         </div> | ||||
|         <DetalleSeccion seccion={clickedSeccion} categoriaId={selectedCategoriaId} onReset={handleReset} /> | ||||
|         <LegendSecciones resultados={resultadosPorSeccionRomana} nombresAgrupaciones={nombresAgrupaciones} /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| // --- Sub-componente para la Leyenda --- | ||||
| const LegendSecciones = ({ resultados, nombresAgrupaciones }: { resultados: Map<string, ResultadoMapaSeccion>, nombresAgrupaciones: Map<string, string> }) => { | ||||
|   const legendItems = useMemo(() => { | ||||
|     const ganadoresUnicos = new Map<string, { nombre: string; color: string }>(); | ||||
|     resultados.forEach(resultado => { | ||||
|       if (resultado.agrupacionGanadoraId && resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) { | ||||
|         ganadoresUnicos.set(resultado.agrupacionGanadoraId, { | ||||
|           nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido', | ||||
|           color: resultado.colorGanador | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     return Array.from(ganadoresUnicos.values()); | ||||
|   }, [resultados, nombresAgrupaciones]); | ||||
|  | ||||
|   return ( | ||||
|     <div className="legend"> | ||||
|       <h4>Ganadores por Sección</h4> | ||||
|       {legendItems.map(item => ( | ||||
|         <div key={item.nombre} className="legend-item"> | ||||
|           <div className="legend-color-box" style={{ backgroundColor: item.color }} /> | ||||
|           <span>{item.nombre}</span> | ||||
|         </div> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default MapaBsAsSecciones; | ||||
| @@ -96,8 +96,8 @@ | ||||
| } | ||||
| .party-logo { | ||||
|     flex-shrink: 0; | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
|     width: 75px; | ||||
|     height: 75px; | ||||
| } | ||||
| .party-logo img { | ||||
|     width: 100%; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { getResumenProvincial, getConfiguracionPublica } from '../apiService'; | ||||
| import type { CategoriaResumen, ResultadoTicker } from '../types/types'; | ||||
| import { ImageWithFallback } from './ImageWithFallback'; | ||||
| import './TickerWidget.css'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| @@ -20,7 +21,9 @@ export const TickerWidget = () => { | ||||
|     staleTime: 0, | ||||
|   }); | ||||
|  | ||||
|   const cantidadAMostrar = parseInt(configData?.TickerResultadosCantidad || '5', 10) + 1; | ||||
|   const cantidadAMostrar = useMemo(() => { | ||||
|     return parseInt(configData?.TickerResultadosCantidad || '5', 10); | ||||
|   }, [configData]); | ||||
|  | ||||
|   if (isLoading) return <div className="ticker-wrapper loading">Cargando resumen...</div>; | ||||
|   if (error || !categorias) return <div className="ticker-wrapper error">No hay datos disponibles.</div>; | ||||
| @@ -36,7 +39,7 @@ export const TickerWidget = () => { | ||||
|         if (categoria.resultados.length > cantidadAMostrar) { | ||||
|           const topParties = categoria.resultados.slice(0, cantidadAMostrar - 1); | ||||
|           const otherParties = categoria.resultados.slice(cantidadAMostrar - 1); | ||||
|           const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.votosPorcentaje, 0); | ||||
|           const otrosPorcentaje = otherParties.reduce((sum, party) => sum + party.porcentaje, 0); | ||||
|  | ||||
|           const otrosEntry: ResultadoTicker = { | ||||
|             id: `otros-${categoria.categoriaId}`, | ||||
| @@ -45,7 +48,7 @@ export const TickerWidget = () => { | ||||
|             color: '#888888', | ||||
|             logoUrl: null, | ||||
|             votos: 0, | ||||
|             votosPorcentaje: otrosPorcentaje, | ||||
|             porcentaje: otrosPorcentaje, | ||||
|           }; | ||||
|  | ||||
|           displayResults = [...topParties, otrosEntry]; | ||||
| @@ -75,10 +78,10 @@ export const TickerWidget = () => { | ||||
|                   <div className="party-details"> | ||||
|                     <div className="party-info"> | ||||
|                       <span className="party-name">{partido.nombreCorto || partido.nombre}</span> | ||||
|                       <span className="party-percent">{formatPercent(partido.votosPorcentaje)}</span> | ||||
|                       <span className="party-percent">{formatPercent(partido.porcentaje)}</span> | ||||
|                     </div> | ||||
|                     <div className="party-bar-background"> | ||||
|                       <div className="party-bar-foreground" style={{ width: `${partido.votosPorcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|                       <div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }}></div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|   | ||||
| @@ -48,7 +48,7 @@ export interface ResultadoTicker { | ||||
|   color: string | null; | ||||
|   logoUrl: string | null; | ||||
|   votos: number; | ||||
|   votosPorcentaje: number; | ||||
|   porcentaje: number; | ||||
| } | ||||
|  | ||||
| export interface EstadoRecuentoTicker { | ||||
|   | ||||
| @@ -187,22 +187,52 @@ public class AdminController : ControllerBase | ||||
|   [HttpPut("logos")] | ||||
|   public async Task<IActionResult> UpdateLogos([FromBody] List<LogoAgrupacionCategoria> logos) | ||||
|   { | ||||
|     // Lógica de "Upsert" | ||||
|     foreach (var logo in logos) | ||||
|     { | ||||
|       var logoExistente = await _dbContext.LogosAgrupacionesCategorias | ||||
|           .FirstOrDefaultAsync(l => l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId && l.CategoriaId == logo.CategoriaId); | ||||
|           .FirstOrDefaultAsync(l => | ||||
|               l.AgrupacionPoliticaId == logo.AgrupacionPoliticaId && | ||||
|               l.CategoriaId == logo.CategoriaId && | ||||
|               l.AmbitoGeograficoId == logo.AmbitoGeograficoId); | ||||
|  | ||||
|       if (logoExistente != null) | ||||
|       { | ||||
|         // Si encontramos el registro exacto, solo actualizamos su URL. | ||||
|         logoExistente.LogoUrl = logo.LogoUrl; | ||||
|       } | ||||
|       else if (!string.IsNullOrEmpty(logo.LogoUrl)) | ||||
|       { | ||||
|         _dbContext.LogosAgrupacionesCategorias.Add(logo); | ||||
|         // Si no se encontró un registro exacto (es un override nuevo), | ||||
|         // lo añadimos a la base de datos. | ||||
|         _dbContext.LogosAgrupacionesCategorias.Add(new LogoAgrupacionCategoria | ||||
|         { | ||||
|           AgrupacionPoliticaId = logo.AgrupacionPoliticaId, | ||||
|           CategoriaId = logo.CategoriaId, | ||||
|           AmbitoGeograficoId = logo.AmbitoGeograficoId, | ||||
|           LogoUrl = logo.LogoUrl | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     await _dbContext.SaveChangesAsync(); | ||||
|     return NoContent(); | ||||
|   } | ||||
|  | ||||
|   [HttpGet("catalogos/municipios")] | ||||
|   public async Task<IActionResult> GetMunicipiosForAdmin() | ||||
|   { | ||||
|     var municipios = await _dbContext.AmbitosGeograficos | ||||
|         .AsNoTracking() | ||||
|         .Where(a => a.NivelId == 30) // Nivel 30 = Municipio | ||||
|         .OrderBy(a => a.Nombre) | ||||
|         .Select(a => new | ||||
|         { | ||||
|           // Devolvemos el ID de la base de datos como un string, | ||||
|           // que es lo que el componente Select espera. | ||||
|           Id = a.Id.ToString(), | ||||
|           Nombre = a.Nombre | ||||
|         }) | ||||
|         .ToListAsync(); | ||||
|  | ||||
|     return Ok(municipios); | ||||
|   } | ||||
| } | ||||
| @@ -22,13 +22,11 @@ public class ResultadosController : ControllerBase | ||||
|         _configuration = configuration; | ||||
|     } | ||||
|  | ||||
|     [HttpGet("partido/{seccionId}")] | ||||
|     [HttpGet("partido/{seccionId}")] // 'seccionId' es el ID del municipio | ||||
|     public async Task<IActionResult> GetResultadosPorPartido(string seccionId) | ||||
|     { | ||||
|         // 1. Buscamos el ámbito geográfico correspondiente al PARTIDO (Nivel 30) | ||||
|         var ambito = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             // CAMBIO CLAVE: Buscamos por SeccionId y NivelId para ser precisos | ||||
|             .FirstOrDefaultAsync(a => a.SeccionId == seccionId && a.NivelId == 30); | ||||
|  | ||||
|         if (ambito == null) | ||||
| @@ -36,14 +34,12 @@ public class ResultadosController : ControllerBase | ||||
|             return NotFound(new { message = $"No se encontró el partido con ID {seccionId}" }); | ||||
|         } | ||||
|  | ||||
|         // 2. Buscamos el estado del recuento para ese ámbito | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentos | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id); | ||||
|  | ||||
|         if (estadoRecuento == null) | ||||
|         { | ||||
|             // Devolvemos una respuesta vacía pero válida para el frontend | ||||
|             return Ok(new MunicipioResultadosDto | ||||
|             { | ||||
|                 MunicipioNombre = ambito.Nombre, | ||||
| @@ -55,28 +51,57 @@ public class ResultadosController : ControllerBase | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // 3. Buscamos todos los votos para ese ámbito | ||||
|         // 1. Obtenemos los IDs de las agrupaciones que tienen resultados en este municipio | ||||
|         var agrupacionIds = await _dbContext.ResultadosVotos | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambito.Id) | ||||
|             .Select(rv => rv.AgrupacionPoliticaId) | ||||
|             .Distinct() | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // 2. Buscamos TODOS los logos relevantes en una sola consulta: | ||||
|         //    - Los que son para la categoría Concejales (7) | ||||
|         //    - Y pertenecen a los partidos que compiten aquí | ||||
|         //    - Y son genéricos (sin ámbito) O específicos para ESTE municipio | ||||
|         var logosRelevantes = await _dbContext.LogosAgrupacionesCategorias | ||||
|             .AsNoTracking() | ||||
|             .Where(l => l.CategoriaId == 7 && | ||||
|                         agrupacionIds.Contains(l.AgrupacionPoliticaId) && | ||||
|                         (l.AmbitoGeograficoId == null || l.AmbitoGeograficoId == ambito.Id)) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var resultadosVotos = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(rv => rv.AgrupacionPolitica) | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambito.Id) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // 4. Calculamos el total de votos positivos | ||||
|         long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos); | ||||
|  | ||||
|         // 5. Mapeamos al DTO de respuesta | ||||
|         var respuestaDto = new MunicipioResultadosDto | ||||
|         { | ||||
|             MunicipioNombre = ambito.Nombre, | ||||
|             UltimaActualizacion = estadoRecuento.FechaTotalizacion, | ||||
|             PorcentajeEscrutado = estadoRecuento.MesasTotalizadasPorcentaje, | ||||
|             PorcentajeParticipacion = estadoRecuento.ParticipacionPorcentaje, | ||||
|             Resultados = resultadosVotos.Select(rv => new AgrupacionResultadoDto | ||||
|             Resultados = resultadosVotos.Select(rv => | ||||
|             { | ||||
|                 Nombre = rv.AgrupacionPolitica.Nombre, | ||||
|                 Votos = rv.CantidadVotos, | ||||
|                 Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 | ||||
|                 // --- LÓGICA DE FALLBACK --- | ||||
|                 var logoUrl = | ||||
|                     // 1. Buscamos primero el logo específico para este municipio. | ||||
|                     logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == ambito.Id)?.LogoUrl | ||||
|                     // 2. Si no lo encontramos, buscamos el logo genérico (sin ámbito). | ||||
|                     ?? logosRelevantes.FirstOrDefault(l => l.AgrupacionPoliticaId == rv.AgrupacionPoliticaId && l.AmbitoGeograficoId == null)?.LogoUrl; | ||||
|  | ||||
|                 return new AgrupacionResultadoDto | ||||
|                 { | ||||
|                     Id = rv.AgrupacionPolitica.Id, | ||||
|                     Nombre = rv.AgrupacionPolitica.Nombre, | ||||
|                     NombreCorto = rv.AgrupacionPolitica.NombreCorto, | ||||
|                     Color = rv.AgrupacionPolitica.Color, | ||||
|                     LogoUrl = logoUrl, // Asignamos el logo encontrado | ||||
|                     Votos = rv.CantidadVotos, | ||||
|                     Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 | ||||
|                 }; | ||||
|             }).OrderByDescending(r => r.Votos).ToList(), | ||||
|             VotosAdicionales = new VotosAdicionalesDto | ||||
|             { | ||||
| @@ -95,15 +120,10 @@ public class ResultadosController : ControllerBase | ||||
|         var provincia = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.DistritoId == distritoId && a.NivelId == 10); | ||||
|  | ||||
|         var todosLosResumenes = await _dbContext.ResumenesVotos.AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // OBTENER TODOS LOS LOGOS EN UNA SOLA CONSULTA | ||||
|         var logosLookup = (await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync()) | ||||
|             .ToLookup(l => $"{l.AgrupacionPoliticaId}-{l.CategoriaId}"); | ||||
|  | ||||
|         if (provincia == null) return NotFound($"No se encontró la provincia con distritoId {distritoId}"); | ||||
|         if (provincia == null) | ||||
|         { | ||||
|             return NotFound($"No se encontró la provincia con distritoId {distritoId}"); | ||||
|         } | ||||
|  | ||||
|         var estadosPorCategoria = await _dbContext.EstadosRecuentosGenerales.AsNoTracking() | ||||
|             .Include(e => e.CategoriaElectoral) | ||||
| @@ -113,43 +133,50 @@ public class ResultadosController : ControllerBase | ||||
|         var resultadosPorMunicipio = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.AmbitoGeografico.NivelId == 30) | ||||
|             .Where(r => r.AmbitoGeografico.NivelId == 30) // Nivel 30 = Municipio | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var logos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().ToListAsync(); | ||||
|  | ||||
|         var resultadosAgrupados = resultadosPorMunicipio | ||||
|             .GroupBy(r => r.CategoriaId) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 CategoriaId = g.Key, | ||||
|                 CategoriaNombre = estadosPorCategoria.ContainsKey(g.Key) ? estadosPorCategoria[g.Key].CategoriaElectoral.Nombre : "Desconocido", | ||||
|                 EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.Key), | ||||
|                 TotalVotosCategoria = g.Sum(r => r.CantidadVotos), | ||||
|                 // Agrupamos por el ID de la agrupación, no por el objeto | ||||
|                 Resultados = g.GroupBy(r => r.AgrupacionPoliticaId) | ||||
|                               .Select(partidoGroup => new | ||||
|                               { | ||||
|                                   // El objeto Agrupacion lo tomamos del primer elemento del grupo | ||||
|                                   Agrupacion = partidoGroup.First().AgrupacionPolitica, | ||||
|                                   Votos = partidoGroup.Sum(r => r.CantidadVotos) | ||||
|                               }) | ||||
|                               .ToList() | ||||
|                 Resultados = g | ||||
|                     .GroupBy(r => r.AgrupacionPolitica) | ||||
|                     .Select(partidoGroup => new | ||||
|                     { | ||||
|                         Agrupacion = partidoGroup.Key, | ||||
|                         Votos = partidoGroup.Sum(r => r.CantidadVotos) | ||||
|                     }) | ||||
|                     .OrderByDescending(r => r.Votos) | ||||
|                     .ToList() | ||||
|             }) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 g.CategoriaId, | ||||
|                 CategoriaNombre = estadosPorCategoria.ContainsKey(g.CategoriaId) ? estadosPorCategoria[g.CategoriaId].CategoriaElectoral.Nombre : "Desconocido", | ||||
|                 EstadoRecuento = estadosPorCategoria.GetValueOrDefault(g.CategoriaId), | ||||
|                 Resultados = g.Resultados | ||||
|                               .Select(r => new | ||||
|                               { | ||||
|                                   r.Agrupacion.Id, | ||||
|                                   r.Agrupacion.Nombre, | ||||
|                                   r.Agrupacion.NombreCorto, | ||||
|                                   r.Agrupacion.Color, | ||||
|                                   LogoUrl = logosLookup[$"{r.Agrupacion.Id}-{g.CategoriaId}"].FirstOrDefault()?.LogoUrl, | ||||
|                                   r.Votos, | ||||
|                                   VotosPorcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0 | ||||
|                               }) | ||||
|                               .OrderByDescending(r => r.Votos) | ||||
|                               .ToList() | ||||
|                 g.CategoriaNombre, | ||||
|                 g.EstadoRecuento, | ||||
|                 Resultados = g.Resultados.Select(r => | ||||
|                 { | ||||
|                     var logoUrl = logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId != null)?.LogoUrl | ||||
|                                ?? logos.FirstOrDefault(l => l.AgrupacionPoliticaId == r.Agrupacion.Id && l.CategoriaId == g.CategoriaId && l.AmbitoGeograficoId == null)?.LogoUrl; | ||||
|  | ||||
|                     return new | ||||
|                     { | ||||
|                         Id = r.Agrupacion.Id, | ||||
|                         r.Agrupacion.Nombre, | ||||
|                         r.Agrupacion.NombreCorto, | ||||
|                         r.Agrupacion.Color, | ||||
|                         LogoUrl = logoUrl, | ||||
|                         r.Votos, | ||||
|                         Porcentaje = g.TotalVotosCategoria > 0 ? ((decimal)r.Votos * 100 / g.TotalVotosCategoria) : 0 | ||||
|                     }; | ||||
|                 }).ToList() | ||||
|             }) | ||||
|             .OrderBy(c => c.CategoriaId) | ||||
|             .ToList(); | ||||
| @@ -235,63 +262,60 @@ public class ResultadosController : ControllerBase | ||||
|         return Ok(resultadosGanadores); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("municipio/{ambitoId}")] // Cambiamos el nombre del parámetro de ruta | ||||
|     public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId) // Cambiamos el tipo de string a int | ||||
|     [HttpGet("municipio/{ambitoId}")] | ||||
|     public async Task<IActionResult> GetResultadosPorMunicipio(int ambitoId, [FromQuery] int categoriaId) | ||||
|     { | ||||
|         _logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}", ambitoId); | ||||
|  | ||||
|         // PASO 1: Buscar el Ámbito Geográfico directamente por su CLAVE PRIMARIA (AmbitoGeograficoId). | ||||
|         var ambito = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30); // Usamos a.Id == ambitoId | ||||
|         _logger.LogInformation("Buscando resultados para AmbitoGeograficoId: {AmbitoId}, CategoriaId: {CategoriaId}", ambitoId, categoriaId); | ||||
|  | ||||
|         // Validamos que el ámbito exista | ||||
|         var ambito = await _dbContext.AmbitosGeograficos.AsNoTracking() | ||||
|             .FirstOrDefaultAsync(a => a.Id == ambitoId && a.NivelId == 30); | ||||
|         if (ambito == null) | ||||
|         { | ||||
|             _logger.LogWarning("No se encontró el ámbito para el ID interno: {AmbitoId} o no es Nivel 30.", ambitoId); | ||||
|             return NotFound(new { message = $"No se encontró el municipio con ID interno {ambitoId}" }); | ||||
|             _logger.LogWarning("No se encontró el municipio con ID: {AmbitoId}", ambitoId); | ||||
|             return NotFound($"No se encontró el municipio con ID {ambitoId}"); | ||||
|         } | ||||
|         _logger.LogInformation("Ámbito encontrado: Id={AmbitoId}, Nombre={AmbitoNombre}", ambito.Id, ambito.Nombre); | ||||
|  | ||||
|         // PASO 2: Usar la CLAVE PRIMARIA (ambito.Id) para buscar el estado del recuento. | ||||
|         // Obtenemos el estado del recuento para el ámbito | ||||
|         var estadoRecuento = await _dbContext.EstadosRecuentos | ||||
|             .AsNoTracking() | ||||
|             .FirstOrDefaultAsync(e => e.AmbitoGeograficoId == ambito.Id); | ||||
|  | ||||
|         if (estadoRecuento == null) | ||||
|         { | ||||
|             _logger.LogWarning("No se encontró EstadoRecuento para AmbitoGeograficoId: {AmbitoId}", ambito.Id); | ||||
|             return NotFound(new { message = $"No se han encontrado resultados de recuento para el municipio {ambito.Nombre}" }); | ||||
|         } | ||||
|  | ||||
|         // PASO 3: Usar la CLAVE PRIMARIA (ambito.Id) para buscar los votos. | ||||
|         // Obtenemos los votos para ESE municipio y ESA categoría | ||||
|         var resultadosVotos = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(rv => rv.AgrupacionPolitica) // Incluimos el nombre del partido | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambito.Id) | ||||
|             .OrderByDescending(rv => rv.CantidadVotos) | ||||
|             .Include(rv => rv.AgrupacionPolitica) | ||||
|             .Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // PASO 4: Calcular el total de votos positivos para el porcentaje. | ||||
|         long totalVotosPositivos = resultadosVotos.Sum(r => r.CantidadVotos); | ||||
|         // Calculamos el total de votos solo para esta selección | ||||
|         var totalVotosPositivos = (decimal)resultadosVotos.Sum(r => r.CantidadVotos); | ||||
|  | ||||
|         // PASO 5: Mapear todo al DTO de respuesta que el frontend espera. | ||||
|         // Mapeamos los resultados de los partidos | ||||
|         var resultadosPartidosDto = resultadosVotos | ||||
|             .OrderByDescending(r => r.CantidadVotos) | ||||
|             .Select(rv => new AgrupacionResultadoDto // Assuming AgrupacionResultadoDto is the correct DTO for individual party results | ||||
|             { | ||||
|                 Id = rv.AgrupacionPolitica.Id, | ||||
|                 Nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre, | ||||
|                 Color = rv.AgrupacionPolitica.Color, | ||||
|                 Votos = rv.CantidadVotos, | ||||
|                 Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos / totalVotosPositivos) * 100 : 0 | ||||
|             }).ToList(); | ||||
|  | ||||
|         // Construimos la respuesta completa del DTO | ||||
|         var respuestaDto = new MunicipioResultadosDto | ||||
|         { | ||||
|             MunicipioNombre = ambito.Nombre, | ||||
|             UltimaActualizacion = estadoRecuento.FechaTotalizacion, | ||||
|             PorcentajeEscrutado = estadoRecuento.MesasTotalizadasPorcentaje, | ||||
|             PorcentajeParticipacion = estadoRecuento.ParticipacionPorcentaje, | ||||
|             Resultados = resultadosVotos.Select(rv => new AgrupacionResultadoDto | ||||
|             UltimaActualizacion = estadoRecuento?.FechaTotalizacion ?? DateTime.UtcNow, // Use null-conditional operator | ||||
|             PorcentajeEscrutado = estadoRecuento?.MesasTotalizadasPorcentaje ?? 0, | ||||
|             PorcentajeParticipacion = estadoRecuento?.ParticipacionPorcentaje ?? 0, | ||||
|             Resultados = resultadosPartidosDto, | ||||
|             VotosAdicionales = new VotosAdicionalesDto // Assuming default constructor is fine | ||||
|             { | ||||
|                 Nombre = rv.AgrupacionPolitica.Nombre, | ||||
|                 Votos = rv.CantidadVotos, | ||||
|                 Porcentaje = totalVotosPositivos > 0 ? (rv.CantidadVotos * 100.0m / totalVotosPositivos) : 0 | ||||
|             }).ToList(), | ||||
|             VotosAdicionales = new VotosAdicionalesDto | ||||
|             { | ||||
|                 EnBlanco = estadoRecuento.VotosEnBlanco, | ||||
|                 Nulos = estadoRecuento.VotosNulos, | ||||
|                 Recurridos = estadoRecuento.VotosRecurridos | ||||
|                 EnBlanco = estadoRecuento?.VotosEnBlanco ?? 0, | ||||
|                 Nulos = estadoRecuento?.VotosNulos ?? 0, | ||||
|                 Recurridos = estadoRecuento?.VotosRecurridos ?? 0 | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -537,11 +561,10 @@ public class ResultadosController : ControllerBase | ||||
|     [HttpGet("concejales/{seccionId}")] | ||||
|     public async Task<IActionResult> GetResultadosConcejalesPorSeccion(string seccionId) | ||||
|     { | ||||
|         // 1. Encontrar todos los municipios (Nivel 30) que pertenecen a la sección dada (Nivel 20) | ||||
|         var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | ||||
|             .Select(a => a.Id) // Solo necesitamos sus IDs | ||||
|             .Select(a => a.Id) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!municipiosDeLaSeccion.Any()) | ||||
| @@ -549,7 +572,6 @@ public class ResultadosController : ControllerBase | ||||
|             return Ok(new List<object>()); | ||||
|         } | ||||
|  | ||||
|         // 2. Obtener todos los resultados de la categoría Concejales (ID 7) para esos municipios | ||||
|         var resultadosMunicipales = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
| @@ -561,29 +583,159 @@ public class ResultadosController : ControllerBase | ||||
|             .Where(l => l.CategoriaId == 7) | ||||
|             .ToDictionaryAsync(l => l.AgrupacionPoliticaId); | ||||
|  | ||||
|         // 3. Agrupar y sumar en memoria para obtener el total por partido para la sección | ||||
|         var totalVotosSeccion = resultadosMunicipales.Sum(r => r.CantidadVotos); | ||||
|         var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos); | ||||
|  | ||||
|         var resultadosFinales = resultadosMunicipales | ||||
|             .GroupBy(r => r.AgrupacionPolitica) | ||||
|             // 1. Agrupamos por el ID del partido para evitar duplicados. | ||||
|             .GroupBy(r => r.AgrupacionPoliticaId) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 Agrupacion = g.Key, | ||||
|                 // 2. Obtenemos la entidad completa del primer elemento del grupo. | ||||
|                 Agrupacion = g.First().AgrupacionPolitica, | ||||
|                 Votos = g.Sum(r => r.CantidadVotos) | ||||
|             }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .Select(r => new | ||||
|             { | ||||
|                 r.Agrupacion.Id, | ||||
|                 Id = r.Agrupacion.Id, // Aseguramos que el Id esté en el objeto final | ||||
|                 r.Agrupacion.Nombre, | ||||
|                 r.Agrupacion.NombreCorto, | ||||
|                 r.Agrupacion.Color, | ||||
|                 LogoUrl = logosConcejales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl, | ||||
|                 r.Votos, | ||||
|                 votosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0 | ||||
|                 Votos = r.Votos, | ||||
|                 // --- CORRECCIÓN CLAVE --- | ||||
|                 // 3. Usamos el nombre de propiedad correcto que el frontend espera: 'votosPorcentaje' | ||||
|                 VotosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0 | ||||
|             }) | ||||
|             .ToList(); | ||||
|  | ||||
|         return Ok(resultadosFinales); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("mapa-por-seccion")] | ||||
|     public async Task<IActionResult> GetResultadosMapaPorSeccion([FromQuery] int categoriaId) | ||||
|     { | ||||
|         // 1. Obtenemos todos los resultados a nivel de MUNICIPIO para la categoría dada. | ||||
|         var resultadosMunicipales = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AmbitoGeografico) | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // 2. Agrupamos en memoria por Sección Electoral y sumamos los votos. | ||||
|         var ganadoresPorSeccion = resultadosMunicipales | ||||
|             .GroupBy(r => r.AmbitoGeografico.SeccionProvincialId) | ||||
|             .Select(g => | ||||
|             { | ||||
|                 // Para cada sección, encontramos al partido con más votos. | ||||
|                 var ganador = g | ||||
|                     .GroupBy(r => r.AgrupacionPolitica) | ||||
|                     .Select(pg => new { Agrupacion = pg.Key, TotalVotos = pg.Sum(r => r.CantidadVotos) }) | ||||
|                     .OrderByDescending(x => x.TotalVotos) | ||||
|                     .FirstOrDefault(); | ||||
|  | ||||
|                 // Buscamos el nombre de la sección | ||||
|                 var seccionInfo = _dbContext.AmbitosGeograficos | ||||
|                     .FirstOrDefault(a => a.SeccionProvincialId == g.Key && a.NivelId == 20); | ||||
|  | ||||
|                 return new | ||||
|                 { | ||||
|                     SeccionId = g.Key, | ||||
|                     SeccionNombre = seccionInfo?.Nombre, | ||||
|                     AgrupacionGanadoraId = ganador?.Agrupacion.Id, | ||||
|                     ColorGanador = ganador?.Agrupacion.Color | ||||
|                 }; | ||||
|             }) | ||||
|             .Where(r => r.SeccionId != null) // Filtramos cualquier posible nulo | ||||
|             .ToList(); | ||||
|  | ||||
|         return Ok(ganadoresPorSeccion); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("seccion/{seccionId}")] | ||||
|     public async Task<IActionResult> GetResultadosDetallePorSeccion(string seccionId, [FromQuery] int categoriaId) | ||||
|     { | ||||
|         var municipiosDeLaSeccion = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .Where(a => a.NivelId == 30 && a.SeccionProvincialId == seccionId) | ||||
|             .Select(a => a.Id) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         if (!municipiosDeLaSeccion.Any()) return Ok(new List<object>()); | ||||
|  | ||||
|         var resultadosMunicipales = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Include(r => r.AgrupacionPolitica) | ||||
|             .Where(r => r.CategoriaId == categoriaId && municipiosDeLaSeccion.Contains(r.AmbitoGeograficoId)) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var totalVotosSeccion = (decimal)resultadosMunicipales.Sum(r => r.CantidadVotos); | ||||
|  | ||||
|         var resultadosFinales = resultadosMunicipales | ||||
|             // 1. Agrupamos por el ID del partido para evitar duplicados. | ||||
|             .GroupBy(r => r.AgrupacionPoliticaId) | ||||
|             .Select(g => new | ||||
|             { | ||||
|                 // 2. Tomamos la entidad completa del primer elemento del grupo. | ||||
|                 Agrupacion = g.First().AgrupacionPolitica, | ||||
|                 Votos = g.Sum(r => r.CantidadVotos) | ||||
|             }) | ||||
|             .OrderByDescending(r => r.Votos) | ||||
|             .Select(r => new | ||||
|             { | ||||
|                 id = r.Agrupacion.Id, | ||||
|                 nombre = r.Agrupacion.NombreCorto ?? r.Agrupacion.Nombre, | ||||
|                 votos = r.Votos, | ||||
|                 porcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0, | ||||
|                 // 3. Añadimos el color a la respuesta. | ||||
|                 color = r.Agrupacion.Color | ||||
|             }) | ||||
|             .ToList(); | ||||
|  | ||||
|         return Ok(resultadosFinales); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("mapa-por-municipio")] | ||||
|     public async Task<IActionResult> GetResultadosMapaPorMunicipio([FromQuery] int categoriaId) | ||||
|     { | ||||
|         // Obtenemos los votos primero | ||||
|         var votosPorMunicipio = await _dbContext.ResultadosVotos | ||||
|             .AsNoTracking() | ||||
|             .Where(r => r.CategoriaId == categoriaId && r.AmbitoGeografico.NivelId == 30) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         // Luego, los agrupamos en memoria | ||||
|         var ganadores = votosPorMunicipio | ||||
|             .GroupBy(r => r.AmbitoGeograficoId) | ||||
|             .Select(g => g.OrderByDescending(r => r.CantidadVotos).First()) | ||||
|             .ToList(); | ||||
|  | ||||
|         // Ahora, obtenemos los detalles necesarios en una sola consulta adicional | ||||
|         var idsAgrupacionesGanadoras = ganadores.Select(g => g.AgrupacionPoliticaId).ToList(); | ||||
|         var idsAmbitosGanadores = ganadores.Select(g => g.AmbitoGeograficoId).ToList(); | ||||
|  | ||||
|         var agrupacionesInfo = await _dbContext.AgrupacionesPoliticas | ||||
|             .AsNoTracking() | ||||
|             .Where(a => idsAgrupacionesGanadoras.Contains(a.Id)) | ||||
|             .ToDictionaryAsync(a => a.Id); | ||||
|  | ||||
|         var ambitosInfo = await _dbContext.AmbitosGeograficos | ||||
|             .AsNoTracking() | ||||
|             .Where(a => idsAmbitosGanadores.Contains(a.Id)) | ||||
|             .ToDictionaryAsync(a => a.Id); | ||||
|  | ||||
|         // Finalmente, unimos todo en memoria | ||||
|         var resultadoFinal = ganadores.Select(g => new | ||||
|         { | ||||
|             AmbitoId = g.AmbitoGeograficoId, | ||||
|             DepartamentoNombre = ambitosInfo.GetValueOrDefault(g.AmbitoGeograficoId)?.Nombre, | ||||
|             AgrupacionGanadoraId = g.AgrupacionPoliticaId, | ||||
|             ColorGanador = agrupacionesInfo.GetValueOrDefault(g.AgrupacionPoliticaId)?.Color | ||||
|         }) | ||||
|         .Where(r => r.DepartamentoNombre != null) // Filtramos por si acaso | ||||
|         .ToList(); | ||||
|  | ||||
|         return Ok(resultadoFinal); | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| @Elecciones.Api_HostAddress = http://localhost:5217 | ||||
|  | ||||
| GET {{Elecciones.Api_HostAddress}}/weatherforecast/ | ||||
| Accept: application/json | ||||
|  | ||||
| ### | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using System.Reflection; | ||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] | ||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+271a86b63211dff88fb7188d1db7b390e6c53efb")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","khGrM2Rl22MsVh9N6\u002B7todRrMuJ6o3ljuHxZF/aubqE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","6xYke/2SzNspypSwIgizeNUH7b\u002Bfoz3wYfKk6z1tMsw="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","PmrFmFQepAv9NhtsO0l45PPk3BWugccwJ3ysTKcw7bM=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","\u002BgTea2HWyPcSQvQyVe0ghcDwqBGkKZIDd2\u002BlGKdxL2U="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| @@ -1 +1 @@ | ||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","ywKm3DCyXg4YCbZAIx3JUlT8N4Irff3GswYUVDST\u002BjQ=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","khGrM2Rl22MsVh9N6\u002B7todRrMuJ6o3ljuHxZF/aubqE=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","6xYke/2SzNspypSwIgizeNUH7b\u002Bfoz3wYfKk6z1tMsw="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| {"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["BNGxWTPjjFD1Fj56FltRDUvsBzgMlQvuqV\u002BraH2IhwQ=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","PmrFmFQepAv9NhtsO0l45PPk3BWugccwJ3ysTKcw7bM=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","\u002BgTea2HWyPcSQvQyVe0ghcDwqBGkKZIDd2\u002BlGKdxL2U="],"CachedAssets":{},"CachedCopyCandidates":{}} | ||||
| @@ -0,0 +1,28 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
|   <Target Name="GetEFProjectMetadata"> | ||||
|     <MSBuild Condition=" '$(TargetFramework)' == '' " | ||||
|              Projects="$(MSBuildProjectFile)" | ||||
|              Targets="GetEFProjectMetadata" | ||||
|              Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);EFProjectMetadataFile=$(EFProjectMetadataFile)" /> | ||||
|     <ItemGroup Condition=" '$(TargetFramework)' != '' "> | ||||
|       <EFProjectMetadata Include="AssemblyName: $(AssemblyName)" /> | ||||
|       <EFProjectMetadata Include="Language: $(Language)" /> | ||||
|       <EFProjectMetadata Include="OutputPath: $(OutputPath)" /> | ||||
|       <EFProjectMetadata Include="Platform: $(Platform)" /> | ||||
|       <EFProjectMetadata Include="PlatformTarget: $(PlatformTarget)" /> | ||||
|       <EFProjectMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" /> | ||||
|       <EFProjectMetadata Include="ProjectDir: $(ProjectDir)" /> | ||||
|       <EFProjectMetadata Include="RootNamespace: $(RootNamespace)" /> | ||||
|       <EFProjectMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" /> | ||||
|       <EFProjectMetadata Include="TargetFileName: $(TargetFileName)" /> | ||||
|       <EFProjectMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" /> | ||||
|       <EFProjectMetadata Include="Nullable: $(Nullable)" /> | ||||
|       <EFProjectMetadata Include="TargetFramework: $(TargetFramework)" /> | ||||
|       <EFProjectMetadata Include="TargetPlatformIdentifier: $(TargetPlatformIdentifier)" /> | ||||
|     </ItemGroup> | ||||
|     <WriteLinesToFile Condition=" '$(TargetFramework)' != '' " | ||||
|                       File="$(EFProjectMetadataFile)" | ||||
|                       Lines="@(EFProjectMetadata)" /> | ||||
|   </Target> | ||||
| </Project> | ||||
| @@ -3,7 +3,11 @@ namespace Elecciones.Core.DTOs.ApiResponses; | ||||
|  | ||||
| public class AgrupacionResultadoDto | ||||
| { | ||||
|     public required string? Id { get; set; } | ||||
|     public string Nombre { get; set; } = null!; | ||||
|     public string? NombreCorto { get; set; } = null!; | ||||
|     public string? Color { get; set; } = null!; | ||||
|     public string? LogoUrl { get; set; } = null!; | ||||
|     public long Votos { get; set; } | ||||
|     public decimal Porcentaje { get; set; } | ||||
| } | ||||
| @@ -13,7 +13,7 @@ using System.Reflection; | ||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+271a86b63211dff88fb7188d1db7b390e6c53efb")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -78,7 +78,8 @@ public class EleccionesDbContext(DbContextOptions<EleccionesDbContext> options) | ||||
|         }); | ||||
|         modelBuilder.Entity<LogoAgrupacionCategoria>(entity => | ||||
|         { | ||||
|             entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId }).IsUnique(); | ||||
|             // La combinación de las tres columnas debe ser única. | ||||
|             entity.HasIndex(l => new { l.AgrupacionPoliticaId, l.CategoriaId, l.AmbitoGeograficoId }).IsUnique(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -7,12 +7,10 @@ public class LogoAgrupacionCategoria | ||||
| { | ||||
|     [Key] | ||||
|     public int Id { get; set; } | ||||
|  | ||||
|     [Required] | ||||
|     public string AgrupacionPoliticaId { get; set; } = null!; | ||||
|      | ||||
|     [Required] | ||||
|     public int CategoriaId { get; set; } | ||||
|  | ||||
|     public string? LogoUrl { get; set; } | ||||
|     public int? AmbitoGeograficoId { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,556 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using Elecciones.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Metadata; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     [DbContext(typeof(EleccionesDbContext))] | ||||
|     [Migration("20250902155740_AddAmbitoToLogosWithCorrectIndex")] | ||||
|     partial class AddAmbitoToLogosWithCorrectIndex | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .UseCollation("Modern_Spanish_CI_AS") | ||||
|                 .HasAnnotation("ProductVersion", "9.0.8") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 128); | ||||
|  | ||||
|             SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AdminUser", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("PasswordHash") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("PasswordSalt") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Username") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AdminUsers"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AgrupacionPolitica", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<string>("Color") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("IdTelegrama") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreCorto") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenDiputados") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int?>("OrdenSenadores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AgrupacionesPoliticas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.AmbitoGeografico", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("CircuitoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("DistritoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("EstablecimientoId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MesaId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("MunicipioId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("NivelId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("SeccionProvincialId") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("AmbitosGeograficos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("Camara") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("NumeroBanca") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("Bancadas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.CategoriaElectoral", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("Nombre") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<int>("Orden") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("CategoriasElectorales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Configuracion", b => | ||||
|                 { | ||||
|                     b.Property<string>("Clave") | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.Property<string>("Valor") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(100) | ||||
|                         .HasColumnType("nvarchar(100)"); | ||||
|  | ||||
|                     b.HasKey("Clave"); | ||||
|  | ||||
|                     b.ToTable("Configuraciones"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<long>("VotosEnBlanco") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosEnBlancoPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosNulos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosNulosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.Property<long>("VotosRecurridos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosRecurridosPorcentaje") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadElectores") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CantidadVotantes") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("MesasEsperadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("MesasTotalizadas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("MesasTotalizadasPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.Property<decimal>("ParticipacionPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("AmbitoGeograficoId", "CategoriaId"); | ||||
|  | ||||
|                     b.HasIndex("CategoriaId"); | ||||
|  | ||||
|                     b.ToTable("EstadosRecuentosGenerales"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.LogoAgrupacionCategoria", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int?>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("LogoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId") | ||||
|                         .IsUnique() | ||||
|                         .HasFilter("[AmbitoGeograficoId] IS NOT NULL"); | ||||
|  | ||||
|                     b.ToTable("LogosAgrupacionesCategorias"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<int>("BancadaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("FotoUrl") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("NombreOcupante") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<string>("Periodo") | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("BancadaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("OcupantesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<int>("NroBancas") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ProyeccionesBancas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("CantidadVotos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<decimal>("PorcentajeVotos") | ||||
|                         .HasPrecision(18, 4) | ||||
|                         .HasColumnType("decimal(18,4)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.HasIndex("AmbitoGeograficoId", "CategoriaId", "AgrupacionPoliticaId") | ||||
|                         .IsUnique(); | ||||
|  | ||||
|                     b.ToTable("ResultadosVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); | ||||
|  | ||||
|                     b.Property<string>("AgrupacionPoliticaId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<long>("Votos") | ||||
|                         .HasColumnType("bigint"); | ||||
|  | ||||
|                     b.Property<decimal>("VotosPorcentaje") | ||||
|                         .HasPrecision(5, 2) | ||||
|                         .HasColumnType("decimal(5,2)"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.ToTable("ResumenesVotos"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Telegrama", b => | ||||
|                 { | ||||
|                     b.Property<string>("Id") | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<string>("ContenidoBase64") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(max)"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaEscaneo") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.Property<DateTime>("FechaTotalizacion") | ||||
|                         .HasColumnType("datetime2"); | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.ToTable("Telegramas"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId"); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuento", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.EstadoRecuentoGeneral", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.CategoriaElectoral", "CategoriaElectoral") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("CategoriaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("CategoriaElectoral"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.OcupanteBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.Bancada", "Bancada") | ||||
|                         .WithOne("Ocupante") | ||||
|                         .HasForeignKey("Elecciones.Database.Entities.OcupanteBanca", "BancadaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("Bancada"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ProyeccionBanca", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResultadoVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.HasOne("Elecciones.Database.Entities.AmbitoGeografico", "AmbitoGeografico") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AmbitoGeograficoId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|  | ||||
|                     b.Navigation("AmbitoGeografico"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.ResumenVoto", b => | ||||
|                 { | ||||
|                     b.HasOne("Elecciones.Database.Entities.AgrupacionPolitica", "AgrupacionPolitica") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AgrupacionPoliticaId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired(); | ||||
|  | ||||
|                     b.Navigation("AgrupacionPolitica"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("Elecciones.Database.Entities.Bancada", b => | ||||
|                 { | ||||
|                     b.Navigation("Ocupante"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,49 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace Elecciones.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddAmbitoToLogosWithCorrectIndex : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropIndex( | ||||
|                 name: "IX_LogosAgrupacionesCategorias_AgrupacionPoliticaId_CategoriaId", | ||||
|                 table: "LogosAgrupacionesCategorias"); | ||||
|  | ||||
|             migrationBuilder.AddColumn<int>( | ||||
|                 name: "AmbitoGeograficoId", | ||||
|                 table: "LogosAgrupacionesCategorias", | ||||
|                 type: "int", | ||||
|                 nullable: true); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "IX_LogosAgrupacionesCategorias_AgrupacionPoliticaId_CategoriaId_AmbitoGeograficoId", | ||||
|                 table: "LogosAgrupacionesCategorias", | ||||
|                 columns: new[] { "AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId" }, | ||||
|                 unique: true, | ||||
|                 filter: "[AmbitoGeograficoId] IS NOT NULL"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropIndex( | ||||
|                 name: "IX_LogosAgrupacionesCategorias_AgrupacionPoliticaId_CategoriaId_AmbitoGeograficoId", | ||||
|                 table: "LogosAgrupacionesCategorias"); | ||||
|  | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "AmbitoGeograficoId", | ||||
|                 table: "LogosAgrupacionesCategorias"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "IX_LogosAgrupacionesCategorias_AgrupacionPoliticaId_CategoriaId", | ||||
|                 table: "LogosAgrupacionesCategorias", | ||||
|                 columns: new[] { "AgrupacionPoliticaId", "CategoriaId" }, | ||||
|                 unique: true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -284,6 +284,9 @@ namespace Elecciones.Database.Migrations | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("nvarchar(450)"); | ||||
|  | ||||
|                     b.Property<int?>("AmbitoGeograficoId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
|                     b.Property<int>("CategoriaId") | ||||
|                         .HasColumnType("int"); | ||||
|  | ||||
| @@ -292,8 +295,9 @@ namespace Elecciones.Database.Migrations | ||||
|  | ||||
|                     b.HasKey("Id"); | ||||
|  | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId") | ||||
|                         .IsUnique(); | ||||
|                     b.HasIndex("AgrupacionPoliticaId", "CategoriaId", "AmbitoGeograficoId") | ||||
|                         .IsUnique() | ||||
|                         .HasFilter("[AmbitoGeograficoId] IS NOT NULL"); | ||||
|  | ||||
|                     b.ToTable("LogosAgrupacionesCategorias"); | ||||
|                 }); | ||||
|   | ||||
| @@ -13,7 +13,7 @@ using System.Reflection; | ||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")] | ||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3b8c6bf754cff6ace486ae8fe850ed4d69233280")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+271a86b63211dff88fb7188d1db7b390e6c53efb")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
| @@ -13,7 +13,7 @@ using System.Reflection; | ||||
| [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")] | ||||
| [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] | ||||
| [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+608ae655bedf6c59be5fec1e14fc308871d2fd62")] | ||||
| [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+271a86b63211dff88fb7188d1db7b390e6c53efb")] | ||||
| [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")] | ||||
| [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")] | ||||
| [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user