feat: Add visual summary cards for Agro/Grains and implement 24h time format
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/eldia.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Mercadors - El Día</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
410
frontend/package-lock.json
generated
410
frontend/package-lock.json
generated
@@ -12,9 +12,12 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^7.2.0",
|
"@mui/icons-material": "^7.2.0",
|
||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
|
"@types/recharts": "^1.8.29",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"recharts": "^3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
@@ -1451,6 +1454,32 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
|
||||||
|
"integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"@standard-schema/utils": "^0.3.0",
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.19",
|
"version": "1.0.0-beta.19",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
||||||
@@ -1738,6 +1767,18 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -1783,6 +1824,69 @@
|
|||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "1.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz",
|
||||||
|
"integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "^1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -1837,6 +1941,22 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/recharts": {
|
||||||
|
"version": "1.8.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/recharts/-/recharts-1.8.29.tgz",
|
||||||
|
"integrity": "sha512-ulKklaVsnFIIhTQsQw226TnOibrddW1qUQNFVhoQEyY1Z7FRQrNecFCGt7msRuJseudzE9czVawZb17dK/aPXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-shape": "^1",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.35.1",
|
"version": "8.35.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
|
||||||
@@ -2448,6 +2568,127 @@
|
|||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
@@ -2465,6 +2706,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js-light": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -2566,6 +2813,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-toolkit": {
|
||||||
|
"version": "1.39.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.5.tgz",
|
||||||
|
"integrity": "sha512-z9V0qU4lx1TBXDNFWfAASWk6RNU6c6+TJBKE+FLIg8u0XJ6Yw58Hi0yX8ftEouj6p1QARRlXLFfHbIli93BdQQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"docs",
|
||||||
|
"benchmarks"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||||
@@ -2807,6 +3064,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -3164,6 +3427,16 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -3190,6 +3463,15 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-arrayish": {
|
"node_modules/is-arrayish": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
@@ -3760,12 +4042,44 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@@ -3792,6 +4106,54 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-eDc3ile9qJU9Dp/EekSthQPhAVPG48/uM47jk+PF7VBQngxeW3cwQpPHb/GHC1uqwyCRWXcIrDzuHRVrnRryoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"decimal.js-light": "^2.5.1",
|
||||||
|
"es-toolkit": "^1.39.3",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"immer": "^10.1.1",
|
||||||
|
"react-redux": "8.x.x || 9.x.x",
|
||||||
|
"reselect": "5.1.1",
|
||||||
|
"tiny-invariant": "^1.3.3",
|
||||||
|
"use-sync-external-store": "^1.2.2",
|
||||||
|
"victory-vendor": "^37.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@@ -3998,6 +4360,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||||
@@ -4160,6 +4528,46 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "37.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||||
|
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||||
|
"license": "MIT AND ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
@@ -14,9 +14,12 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^7.2.0",
|
"@mui/icons-material": "^7.2.0",
|
||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
|
"@types/recharts": "^1.8.29",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"recharts": "^3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
|
|||||||
6
frontend/public/eldia.svg
Normal file
6
frontend/public/eldia.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="89" height="69">
|
||||||
|
<path d="M0 0 C29.37 0 58.74 0 89 0 C89 22.77 89 45.54 89 69 C59.63 69 30.26 69 0 69 C0 46.23 0 23.46 0 0 Z " fill="#008FBD" transform="translate(0,0)"/>
|
||||||
|
<path d="M0 0 C3.3 0 6.6 0 10 0 C13.04822999 3.04822999 12.29337257 6.08805307 12.32226562 10.25390625 C12.31904297 11.32511719 12.31582031 12.39632812 12.3125 13.5 C12.32861328 14.57121094 12.34472656 15.64242187 12.36132812 16.74609375 C12.36197266 17.76832031 12.36261719 18.79054688 12.36328125 19.84375 C12.36775269 21.25430664 12.36775269 21.25430664 12.37231445 22.69335938 C12.1880188 23.83514648 12.1880188 23.83514648 12 25 C9 27 9 27 0 27 C0 18.09 0 9.18 0 0 Z " fill="#000000" transform="translate(0,21)"/>
|
||||||
|
<path d="M0 0 C5.61 0 11.22 0 17 0 C17 3.3 17 6.6 17 10 C25.58 10 34.16 10 43 10 C43 10.99 43 11.98 43 13 C34.42 13 25.84 13 17 13 C17 17.29 17 21.58 17 26 C11.39 26 5.78 26 0 26 C0 24.68 0 23.36 0 22 C4.62 22 9.24 22 14 22 C14 16.06 14 10.12 14 4 C9.38 4 4.76 4 0 4 C0 2.68 0 1.36 0 0 Z " fill="#000000" transform="translate(46,21)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -3,6 +3,9 @@ import { BolsaLocalWidget } from './components/BolsaLocalWidget';
|
|||||||
import { MercadoAgroWidget } from './components/MercadoAgroWidget';
|
import { MercadoAgroWidget } from './components/MercadoAgroWidget';
|
||||||
import { GranosWidget } from './components/GranosWidget';
|
import { GranosWidget } from './components/GranosWidget';
|
||||||
import { BolsaUsaWidget } from './components/BolsaUsaWidget';
|
import { BolsaUsaWidget } from './components/BolsaUsaWidget';
|
||||||
|
import { MercadoAgroCardWidget } from './components/MercadoAgroCardWidget';
|
||||||
|
import { GranosCardWidget } from './components/GranosCardWidget';
|
||||||
|
import { Divider } from '@mui/material';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -11,7 +14,7 @@ function App() {
|
|||||||
<AppBar position="static" sx={{ backgroundColor: '#028fbe' }}>
|
<AppBar position="static" sx={{ backgroundColor: '#028fbe' }}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||||
Mercados Modernos - Demo
|
Mercados - El Día
|
||||||
</Typography>
|
</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
@@ -27,16 +30,36 @@ function App() {
|
|||||||
<MercadoAgroWidget />
|
<MercadoAgroWidget />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* --- Sección 1: Mercado Agroganadero de Cañuelas --- */}
|
||||||
|
<Box component="section" sx={{ mb: 5 }}>
|
||||||
|
<Typography variant="h5" gutterBottom>Mercado Agroganadero de Cañuelas</Typography>
|
||||||
|
<MercadoAgroCardWidget />
|
||||||
|
<Divider sx={{ my: 3 }}>
|
||||||
|
<Typography variant="caption" color="text.secondary">Datos Detallados</Typography>
|
||||||
|
</Divider>
|
||||||
|
<MercadoAgroWidget />
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* --- Sección 2: Granos - Bolsa de Comercio de Rosario --- */}
|
{/* --- Sección 2: Granos - Bolsa de Comercio de Rosario --- */}
|
||||||
<Box component="section" sx={{ mb: 5 }}>
|
<Box component="section" sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5" gutterBottom>Granos - Bolsa de Comercio de Rosario</Typography>
|
<Typography variant="h5" gutterBottom>Granos - Bolsa de Comercio de Rosario</Typography>
|
||||||
<GranosWidget />
|
<GranosCardWidget />
|
||||||
|
<Divider sx={{ my: 3 }}>
|
||||||
|
<Typography variant="caption" color="text.secondary">Datos Detallados</Typography>
|
||||||
|
</Divider>
|
||||||
|
<GranosWidget />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* --- Sección 2: Granos - Bolsa de Comercio de Rosario --- */}
|
||||||
|
<Box component="section" sx={{ mb: 5 }}>
|
||||||
|
<Typography variant="h5" gutterBottom>Granos - Bolsa de Comercio de Rosario</Typography>
|
||||||
|
<GranosWidget />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* --- Sección 3: Mercado de Valores de Estados Unidos --- */}
|
{/* --- Sección 3: Mercado de Valores de Estados Unidos --- */}
|
||||||
<Box component="section" sx={{ mb: 5 }}>
|
<Box component="section" sx={{ mb: 5 }}>
|
||||||
<Typography variant="h5" gutterBottom>Mercado de Valores de Estados Unidos</Typography>
|
<Typography variant="h5" gutterBottom>Mercado de Valores de Estados Unidos</Typography>
|
||||||
<BolsaUsaWidget />
|
<BolsaUsaWidget />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* --- Sección 4: Mercado de Valores Local --- */}
|
{/* --- Sección 4: Mercado de Valores Local --- */}
|
||||||
@@ -47,9 +70,9 @@ function App() {
|
|||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
<Box component="footer" sx={{ p: 2, mt: 'auto', backgroundColor: '#f5f5f5', textAlign: 'center' }}>
|
<Box component="footer" sx={{ p: 2, mt: 'auto', backgroundColor: '#f5f5f5', textAlign: 'center' }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Desarrollado por El Día - {new Date().getFullYear()}
|
Desarrollado por El Día - {new Date().getFullYear()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
// Durante el desarrollo, nuestra API corre en un puerto específico (ej. 5045).
|
// Durante el desarrollo, nuestra API corre en un puerto específico (ej. 5045).
|
||||||
// En producción, esto debería apuntar a la URL real del servidor donde se despliegue la API.
|
// En producción, esto debería apuntar a la URL real del servidor donde se despliegue la API.
|
||||||
const API_BASE_URL = 'http://localhost:5045/api';
|
const API_BASE_URL = 'http://192.168.10.78:5045/api';
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
|
|||||||
@@ -1,38 +1,42 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, CircularProgress, Alert, Table, TableBody, TableCell,
|
Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer,
|
||||||
TableContainer, TableHead, TableRow, Paper, Typography, Tooltip
|
TableHead, TableRow, Paper, Typography, Dialog, DialogTitle,
|
||||||
|
DialogContent, IconButton
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import type { CotizacionBolsa } from '../models/mercadoModels';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { useApiData } from '../hooks/useApiData';
|
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
import RemoveIcon from '@mui/icons-material/Remove'; // Para cambios neutros
|
import RemoveIcon from '@mui/icons-material/Remove';
|
||||||
|
import { formatFullDateTime } from '../utils/formatters';
|
||||||
|
import type { CotizacionBolsa } from '../models/mercadoModels';
|
||||||
|
import { useApiData } from '../hooks/useApiData';
|
||||||
|
import { HistoricalChartWidget } from './HistoricalChartWidget';
|
||||||
|
|
||||||
// Función para formatear números
|
const formatNumber = (num: number) => new Intl.NumberFormat('es-AR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(num);
|
||||||
const formatNumber = (num: number) => {
|
|
||||||
return new Intl.NumberFormat('es-AR', {
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
}).format(num);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Componente para mostrar la variación con color e icono
|
|
||||||
const Variacion = ({ value }: { value: number }) => {
|
const Variacion = ({ value }: { value: number }) => {
|
||||||
const color = value > 0 ? 'success.main' : value < 0 ? 'error.main' : 'text.secondary';
|
const color = value > 0 ? 'success.main' : value < 0 ? 'error.main' : 'text.secondary';
|
||||||
const Icon = value > 0 ? ArrowUpwardIcon : value < 0 ? ArrowDownwardIcon : RemoveIcon;
|
const Icon = value > 0 ? ArrowUpwardIcon : value < 0 ? ArrowDownwardIcon : RemoveIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="span" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
|
<Box component="span" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
|
||||||
<Icon sx={{ fontSize: '1rem', mr: 0.5 }} />
|
<Icon sx={{ fontSize: '1rem', mr: 0.5 }} />
|
||||||
<Typography variant="body2" component="span" sx={{ fontWeight: 'bold' }}>
|
<Typography variant="body2" component="span" sx={{ fontWeight: 'bold' }}>{formatNumber(value)}%</Typography>
|
||||||
{formatNumber(value)}%
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BolsaLocalWidget = () => {
|
export const BolsaLocalWidget = () => {
|
||||||
const { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/local');
|
const { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/local');
|
||||||
|
const [selectedTicker, setSelectedTicker] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleRowClick = (ticker: string) => {
|
||||||
|
setSelectedTicker(ticker);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setSelectedTicker(null);
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
||||||
@@ -47,38 +51,52 @@ export const BolsaLocalWidget = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<>
|
||||||
<Table size="small" aria-label="tabla bolsa local">
|
<TableContainer component={Paper}>
|
||||||
<TableHead>
|
<Box sx={{ p: 1, m: 0 }}>
|
||||||
<TableRow>
|
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||||
<TableCell>Símbolo</TableCell>
|
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||||
<TableCell align="right">Precio Actual</TableCell>
|
</Typography>
|
||||||
<TableCell align="right">Apertura</TableCell>
|
</Box>
|
||||||
<TableCell align="right">Cierre Anterior</TableCell>
|
<Table size="small" aria-label="tabla bolsa local">
|
||||||
<TableCell align="center">% Cambio</TableCell>
|
<TableHead>
|
||||||
</TableRow>
|
<TableRow>
|
||||||
</TableHead>
|
<TableCell>Símbolo</TableCell>
|
||||||
<TableBody>
|
<TableCell align="right">Precio Actual</TableCell>
|
||||||
{data.map((row) => (
|
<TableCell align="right">Apertura</TableCell>
|
||||||
<TableRow key={row.ticker} hover>
|
<TableCell align="right">Cierre Anterior</TableCell>
|
||||||
<TableCell component="th" scope="row">
|
<TableCell align="center">% Cambio</TableCell>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">${formatNumber(row.precioActual)}</TableCell>
|
|
||||||
<TableCell align="right">${formatNumber(row.apertura)}</TableCell>
|
|
||||||
<TableCell align="right">${formatNumber(row.cierreAnterior)}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
<Variacion value={row.porcentajeCambio} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHead>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{data.map((row) => (
|
||||||
<Tooltip title={`Última actualización: ${new Date(data[0].fechaRegistro).toLocaleString('es-AR')}`}>
|
<TableRow key={row.ticker} hover sx={{ cursor: 'pointer' }} onClick={() => handleRowClick(row.ticker)}>
|
||||||
<Typography variant="caption" sx={{ p: 1, display: 'block', textAlign: 'right', color: 'text.secondary' }}>
|
<TableCell component="th" scope="row"><Typography variant="body2" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography></TableCell>
|
||||||
Fuente: Yahoo Finance
|
<TableCell align="right">${formatNumber(row.precioActual)}</TableCell>
|
||||||
</Typography>
|
<TableCell align="right">${formatNumber(row.apertura)}</TableCell>
|
||||||
</Tooltip>
|
<TableCell align="right">${formatNumber(row.cierreAnterior)}</TableCell>
|
||||||
</TableContainer>
|
<TableCell align="center"><Variacion value={row.porcentajeCambio} /></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Dialog open={Boolean(selectedTicker)} onClose={handleCloseDialog} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle sx={{ m: 0, p: 2 }}>
|
||||||
|
Historial de 30 días para: {selectedTicker}
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={handleCloseDialog}
|
||||||
|
sx={{ position: 'absolute', right: 8, top: 8, color: (theme) => theme.palette.grey[500] }}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
{selectedTicker && <HistoricalChartWidget ticker={selectedTicker} mercado="Local" />}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,36 +1,44 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, CircularProgress, Alert, Table, TableBody, TableCell,
|
Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer,
|
||||||
TableContainer, TableHead, TableRow, Paper, Typography, Tooltip
|
TableHead, TableRow, Paper, Typography, Dialog, DialogTitle,
|
||||||
|
DialogContent, IconButton
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import type { CotizacionBolsa } from '../models/mercadoModels';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { useApiData } from '../hooks/useApiData';
|
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
import RemoveIcon from '@mui/icons-material/Remove';
|
import RemoveIcon from '@mui/icons-material/Remove';
|
||||||
|
import { formatFullDateTime } from '../utils/formatters';
|
||||||
|
import type { CotizacionBolsa } from '../models/mercadoModels';
|
||||||
|
import { useApiData } from '../hooks/useApiData';
|
||||||
|
import { HistoricalChartWidget } from './HistoricalChartWidget';
|
||||||
|
|
||||||
const formatNumber = (num: number) => {
|
// Usamos el formato de EEUU para los precios en dólares
|
||||||
return new Intl.NumberFormat('en-US', { // Usamos formato de EEUU
|
const formatCurrency = (num: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num);
|
||||||
style: 'currency',
|
const formatPercentage = (num: number) => num.toFixed(2);
|
||||||
currency: 'USD',
|
|
||||||
}).format(num);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Variacion = ({ value }: { value: number }) => {
|
const Variacion = ({ value }: { value: number }) => {
|
||||||
const color = value > 0 ? 'success.main' : value < 0 ? 'error.main' : 'text.secondary';
|
const color = value > 0 ? 'success.main' : value < 0 ? 'error.main' : 'text.secondary';
|
||||||
const Icon = value > 0 ? ArrowUpwardIcon : value < 0 ? ArrowDownwardIcon : RemoveIcon;
|
const Icon = value > 0 ? ArrowUpwardIcon : value < 0 ? ArrowDownwardIcon : RemoveIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="span" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
|
<Box component="span" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
|
||||||
<Icon sx={{ fontSize: '1rem', mr: 0.5 }} />
|
<Icon sx={{ fontSize: '1rem', mr: 0.5 }} />
|
||||||
<Typography variant="body2" component="span" sx={{ fontWeight: 'bold' }}>
|
<Typography variant="body2" component="span" sx={{ fontWeight: 'bold' }}>{formatPercentage(value)}%</Typography>
|
||||||
{value.toFixed(2)}%
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BolsaUsaWidget = () => {
|
export const BolsaUsaWidget = () => {
|
||||||
const { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/eeuu');
|
const { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/eeuu');
|
||||||
|
const [selectedTicker, setSelectedTicker] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleRowClick = (ticker: string) => {
|
||||||
|
setSelectedTicker(ticker);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setSelectedTicker(null);
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
||||||
@@ -40,43 +48,58 @@ export const BolsaUsaWidget = () => {
|
|||||||
return <Alert severity="error">{error}</Alert>;
|
return <Alert severity="error">{error}</Alert>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recordatorio de que el fetcher puede estar desactivado
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
return <Alert severity="info">No hay datos disponibles para el mercado de EEUU. (El fetcher puede estar desactivado)</Alert>;
|
return <Alert severity="info">No hay datos disponibles para el mercado de EEUU. (El fetcher podría estar desactivado en el Worker).</Alert>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<>
|
||||||
<Table size="small" aria-label="tabla bolsa eeuu">
|
<TableContainer component={Paper}>
|
||||||
<TableHead>
|
<Box sx={{ p: 1, pb: 0 }}>
|
||||||
<TableRow>
|
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||||
<TableCell>Símbolo</TableCell>
|
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||||
<TableCell align="right">Precio Actual</TableCell>
|
</Typography>
|
||||||
<TableCell align="right">Apertura</TableCell>
|
</Box>
|
||||||
<TableCell align="right">Cierre Anterior</TableCell>
|
<Table size="small" aria-label="tabla bolsa eeuu">
|
||||||
<TableCell align="center">% Cambio</TableCell>
|
<TableHead>
|
||||||
</TableRow>
|
<TableRow>
|
||||||
</TableHead>
|
<TableCell>Símbolo</TableCell>
|
||||||
<TableBody>
|
<TableCell align="right">Precio Actual</TableCell>
|
||||||
{data.map((row) => (
|
<TableCell align="right">Apertura</TableCell>
|
||||||
<TableRow key={row.ticker} hover>
|
<TableCell align="right">Cierre Anterior</TableCell>
|
||||||
<TableCell component="th" scope="row">
|
<TableCell align="center">% Cambio</TableCell>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">{formatNumber(row.precioActual)}</TableCell>
|
|
||||||
<TableCell align="right">{formatNumber(row.apertura)}</TableCell>
|
|
||||||
<TableCell align="right">{formatNumber(row.cierreAnterior)}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
<Variacion value={row.porcentajeCambio} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHead>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{data.map((row) => (
|
||||||
<Tooltip title={`Última actualización: ${new Date(data[0].fechaRegistro).toLocaleString('es-AR')}`}>
|
<TableRow key={row.ticker} hover sx={{ cursor: 'pointer' }} onClick={() => handleRowClick(row.ticker)}>
|
||||||
<Typography variant="caption" sx={{ p: 1, display: 'block', textAlign: 'right', color: 'text.secondary' }}>
|
<TableCell component="th" scope="row"><Typography variant="body2" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography></TableCell>
|
||||||
Fuente: Finnhub
|
<TableCell align="right">{formatCurrency(row.precioActual)}</TableCell>
|
||||||
</Typography>
|
<TableCell align="right">{formatCurrency(row.apertura)}</TableCell>
|
||||||
</Tooltip>
|
<TableCell align="right">{formatCurrency(row.cierreAnterior)}</TableCell>
|
||||||
</TableContainer>
|
<TableCell align="center"><Variacion value={row.porcentajeCambio} /></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Dialog open={Boolean(selectedTicker)} onClose={handleCloseDialog} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle sx={{ m: 0, p: 2 }}>
|
||||||
|
Historial de 30 días para: {selectedTicker}
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={handleCloseDialog}
|
||||||
|
sx={{ position: 'absolute', right: 8, top: 8, color: (theme) => theme.palette.grey[500] }}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
{selectedTicker && <HistoricalChartWidget ticker={selectedTicker} mercado="EEUU" />}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
102
frontend/src/components/GranosCardWidget.tsx
Normal file
102
frontend/src/components/GranosCardWidget.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { Box, CircularProgress, Alert, Paper, Typography } from '@mui/material';
|
||||||
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
|
import RemoveIcon from '@mui/icons-material/Remove';
|
||||||
|
// Iconos de react-icons para cada grano
|
||||||
|
import { GiSunflower, GiWheat, GiCorn, GiGrain } from "react-icons/gi";
|
||||||
|
import { TbGrain } from "react-icons/tb";
|
||||||
|
|
||||||
|
import type { CotizacionGrano } from '../models/mercadoModels';
|
||||||
|
import { useApiData } from '../hooks/useApiData';
|
||||||
|
import { formatCurrency, formatDateOnly } from '../utils/formatters';
|
||||||
|
|
||||||
|
// Función para elegir el icono según el nombre del grano
|
||||||
|
const getGrainIcon = (nombre: string) => {
|
||||||
|
switch (nombre.toLowerCase()) {
|
||||||
|
case 'girasol':
|
||||||
|
return <GiSunflower size={28} color="#fbc02d" />;
|
||||||
|
case 'trigo':
|
||||||
|
return <GiWheat size={28} color="#fbc02d" />;
|
||||||
|
case 'sorgo':
|
||||||
|
return <TbGrain size={28} color="#fbc02d" />;
|
||||||
|
case 'maiz':
|
||||||
|
return <GiCorn size={28} color="#fbc02d" />;
|
||||||
|
default:
|
||||||
|
return <GiGrain size={28} color="#fbc02d" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subcomponente para una única tarjeta de grano
|
||||||
|
const GranoCard = ({ grano }: { grano: CotizacionGrano }) => {
|
||||||
|
const isPositive = grano.variacionPrecio > 0;
|
||||||
|
const isNegative = grano.variacionPrecio < 0;
|
||||||
|
const color = isPositive ? 'success.main' : isNegative ? 'error.main' : 'text.secondary';
|
||||||
|
const Icon = isPositive ? ArrowUpwardIcon : isNegative ? ArrowDownwardIcon : RemoveIcon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={2}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flex: '1 1 180px',
|
||||||
|
minWidth: '180px',
|
||||||
|
maxWidth: '220px',
|
||||||
|
height: '160px',
|
||||||
|
borderTop: `4px solid ${isPositive ? '#2e7d32' : isNegative ? '#d32f2f' : '#bdbdbd'}`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
|
{getGrainIcon(grano.nombre)}
|
||||||
|
<Typography variant="h6" component="h3" sx={{ fontWeight: 'bold', ml: 1 }}>
|
||||||
|
{grano.nombre}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ textAlign: 'center', my: 1 }}>
|
||||||
|
<Typography variant="h5" component="p" sx={{ fontWeight: 'bold' }}>
|
||||||
|
${formatCurrency(grano.precio)}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
por Tonelada
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
|
||||||
|
<Icon sx={{ fontSize: '1.1rem', mr: 0.5 }} />
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>
|
||||||
|
{formatCurrency(grano.variacionPrecio)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="caption" align="center" sx={{ mt: 1, color: 'text.secondary' }}>
|
||||||
|
Operación: {formatDateOnly(grano.fechaOperacion)}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GranosCardWidget = () => {
|
||||||
|
const { data, loading, error } = useApiData<CotizacionGrano[]>('/mercados/granos');
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert severity="error">{error}</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return <Alert severity="info">No hay datos de granos disponibles.</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, justifyContent: 'center' }}>
|
||||||
|
{data.map((grano) => (
|
||||||
|
<GranoCard key={grano.nombre} grano={grano} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
45
frontend/src/components/HistoricalChartWidget.tsx
Normal file
45
frontend/src/components/HistoricalChartWidget.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Box, CircularProgress, Alert } from '@mui/material';
|
||||||
|
import type { CotizacionBolsa } from '../models/mercadoModels';
|
||||||
|
import { useApiData } from '../hooks/useApiData';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
|
||||||
|
interface HistoricalChartWidgetProps {
|
||||||
|
ticker: string;
|
||||||
|
mercado: 'Local' | 'EEUU';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formateador para el eje X (muestra DD/MM)
|
||||||
|
const formatXAxis = (tickItem: string) => {
|
||||||
|
const date = new Date(tickItem);
|
||||||
|
return date.toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HistoricalChartWidget = ({ ticker, mercado }: HistoricalChartWidgetProps) => {
|
||||||
|
// Usamos el hook para obtener los datos del historial de los últimos 30 días
|
||||||
|
const { data, loading, error } = useApiData<CotizacionBolsa[]>(`/mercados/bolsa/history/${ticker}?mercado=${mercado}&dias=30`);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4, height: 300 }}><CircularProgress /></Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert severity="error" sx={{height: 300}}>{error}</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.length < 2) {
|
||||||
|
return <Alert severity="info" sx={{height: 300}}>No hay suficientes datos históricos para graficar.</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
|
<LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="fechaRegistro" tickFormatter={formatXAxis} />
|
||||||
|
<YAxis domain={['dataMin - 1', 'dataMax + 1']} tickFormatter={(tick) => `$${tick.toLocaleString('es-AR')}`} />
|
||||||
|
<Tooltip formatter={(value: number) => [`$${value.toFixed(2)}`, 'Precio']} />
|
||||||
|
<Legend />
|
||||||
|
<Line type="monotone" dataKey="precioActual" name="Precio de Cierre" stroke="#8884d8" strokeWidth={2} dot={false} />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
79
frontend/src/components/MercadoAgroCardWidget.tsx
Normal file
79
frontend/src/components/MercadoAgroCardWidget.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { Box, CircularProgress, Alert, Paper, Typography } from '@mui/material';
|
||||||
|
import { PiCow } from "react-icons/pi"; // Un icono divertido para "cabezas"
|
||||||
|
import ScaleIcon from '@mui/icons-material/Scale'; // Para kilos
|
||||||
|
|
||||||
|
import type { CotizacionGanado } from '../models/mercadoModels';
|
||||||
|
import { useApiData } from '../hooks/useApiData';
|
||||||
|
import { formatCurrency, formatInteger } from '../utils/formatters';
|
||||||
|
|
||||||
|
const AgroCard = ({ categoria }: { categoria: CotizacionGanado }) => {
|
||||||
|
return (
|
||||||
|
<Paper elevation={2} sx={{ p: 2, flex: '1 1 250px', minWidth: '250px', maxWidth: '300px' }}>
|
||||||
|
<Typography variant="h6" component="h3" sx={{ fontWeight: 'bold', borderBottom: 1, borderColor: 'divider', pb: 1, mb: 2 }}>
|
||||||
|
{categoria.categoria}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">Precio Máximo:</Typography>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 'bold', color: 'success.main' }}>${formatCurrency(categoria.maximo)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">Precio Mínimo:</Typography>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 'bold', color: 'error.main' }}>${formatCurrency(categoria.minimo)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">Precio Mediano:</Typography>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>${formatCurrency(categoria.mediano)}</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3, pt: 1, borderTop: 1, borderColor: 'divider' }}>
|
||||||
|
<Box sx={{ textAlign: 'center' }}>
|
||||||
|
<PiCow size={28}/>
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>{formatInteger(categoria.cabezas)}</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">Cabezas</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ textAlign: 'center' }}>
|
||||||
|
<ScaleIcon color="action" />
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>{formatInteger(categoria.kilosTotales)}</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">Kilos</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Este widget agrupa los datos por categoría para un resumen más limpio.
|
||||||
|
export const MercadoAgroCardWidget = () => {
|
||||||
|
const { data, loading, error } = useApiData<CotizacionGanado[]>('/mercados/agroganadero');
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return <Alert severity="error">{error}</Alert>;
|
||||||
|
}
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return <Alert severity="info">No hay datos del mercado agroganadero disponibles.</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agrupamos y sumamos los datos por categoría principal
|
||||||
|
const resumenPorCategoria = data.reduce((acc, item) => {
|
||||||
|
if (!acc[item.categoria]) {
|
||||||
|
acc[item.categoria] = { ...item };
|
||||||
|
} else {
|
||||||
|
acc[item.categoria].cabezas += item.cabezas;
|
||||||
|
acc[item.categoria].kilosTotales += item.kilosTotales;
|
||||||
|
acc[item.categoria].importeTotal += item.importeTotal;
|
||||||
|
acc[item.categoria].maximo = Math.max(acc[item.categoria].maximo, item.maximo);
|
||||||
|
acc[item.categoria].minimo = Math.min(acc[item.categoria].minimo, item.minimo);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, CotizacionGanado>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, justifyContent: 'center' }}>
|
||||||
|
{Object.values(resumenPorCategoria).map(categoria => (
|
||||||
|
<AgroCard key={categoria.categoria} categoria={categoria} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -53,7 +53,7 @@ export const MercadoAgroWidget = () => {
|
|||||||
<TableCell align="right">${formatNumber(row.minimo)}</TableCell>
|
<TableCell align="right">${formatNumber(row.minimo)}</TableCell>
|
||||||
<TableCell align="right">${formatNumber(row.mediano)}</TableCell>
|
<TableCell align="right">${formatNumber(row.mediano)}</TableCell>
|
||||||
<TableCell align="right">{formatNumber(row.cabezas, 0)}</TableCell>
|
<TableCell align="right">{formatNumber(row.cabezas, 0)}</TableCell>
|
||||||
<TableCell align="right">{formatNumber(row.kilosTotales, 0)} Kg</TableCell>
|
<TableCell align="right">{formatNumber(row.kilosTotales, 0)}</TableCell>
|
||||||
<TableCell align="right">${formatNumber(row.importeTotal)}</TableCell>
|
<TableCell align="right">${formatNumber(row.importeTotal)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
38
frontend/src/utils/formatters.ts
Normal file
38
frontend/src/utils/formatters.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Formateadores numéricos
|
||||||
|
export const formatCurrency = (num: number, currency = 'ARS') => {
|
||||||
|
const style = currency === 'USD' ? 'currency' : 'decimal';
|
||||||
|
const locale = currency === 'USD' ? 'en-US' : 'es-AR';
|
||||||
|
|
||||||
|
return new Intl.NumberFormat(locale, {
|
||||||
|
style: style,
|
||||||
|
currency: currency,
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}).format(num);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatInteger = (num: number) => {
|
||||||
|
return new Intl.NumberFormat('es-AR').format(num);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Formateadores de fecha y hora
|
||||||
|
export const formatFullDateTime = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleString('es-AR', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hourCycle: 'h23', // <--- LA CLAVE PARA EL FORMATO 24HS
|
||||||
|
timeZone: 'America/Argentina/Buenos_Aires',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDateOnly = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('es-AR', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
timeZone: 'America/Argentina/Buenos_Aires',
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -4,4 +4,8 @@ import react from '@vitejs/plugin-react'
|
|||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
host: true, // o "0.0.0.0"
|
||||||
|
port: 5173 // el puerto que uses, opcional
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ namespace Mercados.Api.Controllers
|
|||||||
private readonly ICotizacionGranoRepository _granoRepo;
|
private readonly ICotizacionGranoRepository _granoRepo;
|
||||||
private readonly ICotizacionGanadoRepository _ganadoRepo;
|
private readonly ICotizacionGanadoRepository _ganadoRepo;
|
||||||
private readonly ILogger<MercadosController> _logger;
|
private readonly ILogger<MercadosController> _logger;
|
||||||
|
|
||||||
// Inyectamos TODOS los repositorios que necesita el controlador.
|
// Inyectamos TODOS los repositorios que necesita el controlador.
|
||||||
public MercadosController(
|
public MercadosController(
|
||||||
ICotizacionBolsaRepository bolsaRepo,
|
ICotizacionBolsaRepository bolsaRepo,
|
||||||
ICotizacionGranoRepository granoRepo,
|
ICotizacionGranoRepository granoRepo,
|
||||||
ICotizacionGanadoRepository ganadoRepo,
|
ICotizacionGanadoRepository ganadoRepo,
|
||||||
ILogger<MercadosController> logger)
|
ILogger<MercadosController> logger)
|
||||||
{
|
{
|
||||||
_bolsaRepo = bolsaRepo;
|
_bolsaRepo = bolsaRepo;
|
||||||
@@ -61,7 +61,7 @@ namespace Mercados.Api.Controllers
|
|||||||
return StatusCode(500, "Ocurrió un error interno en el servidor.");
|
return StatusCode(500, "Ocurrió un error interno en el servidor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Endpoints de Bolsa ---
|
// --- Endpoints de Bolsa ---
|
||||||
[HttpGet("bolsa/eeuu")]
|
[HttpGet("bolsa/eeuu")]
|
||||||
[ProducesResponseType(typeof(IEnumerable<CotizacionBolsa>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<CotizacionBolsa>), StatusCodes.Status200OK)]
|
||||||
@@ -96,5 +96,22 @@ namespace Mercados.Api.Controllers
|
|||||||
return StatusCode(500, "Ocurrió un error interno en el servidor.");
|
return StatusCode(500, "Ocurrió un error interno en el servidor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("bolsa/history/{ticker}")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<CotizacionBolsa>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async Task<IActionResult> GetBolsaHistory(string ticker, [FromQuery] string mercado = "Local", [FromQuery] int dias = 30)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = await _bolsaRepo.ObtenerHistorialPorTickerAsync(ticker, mercado, dias);
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener historial para el ticker {Ticker}.", ticker);
|
||||||
|
return StatusCode(500, "Ocurrió un error interno en el servidor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
@Mercados.Api_HostAddress = http://localhost:5045
|
@Mercados.Api_HostAddress = http://192.168.10.78:5045
|
||||||
|
|
||||||
GET {{Mercados.Api_HostAddress}}/weatherforecast/
|
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
|
|
||||||
###
|
###
|
||||||
@@ -16,7 +16,7 @@ builder.Services.AddCors(options =>
|
|||||||
options.AddPolicy(name: MyAllowSpecificOrigins,
|
options.AddPolicy(name: MyAllowSpecificOrigins,
|
||||||
policy =>
|
policy =>
|
||||||
{
|
{
|
||||||
policy.WithOrigins("http://localhost:5173")
|
policy.WithOrigins("http://localhost:5173", "http://192.168.10.78:5173")
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowAnyMethod();
|
.AllowAnyMethod();
|
||||||
});
|
});
|
||||||
@@ -47,7 +47,6 @@ builder.Services
|
|||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://localhost:5045",
|
"applicationUrl": "http://0.0.0.0:5045",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "https://localhost:7256;http://localhost:5045",
|
"applicationUrl": "https://0.0.0.0:7256;http://0.0.0.0:5045",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ namespace Mercados.Infrastructure.Persistence.Repositories
|
|||||||
|
|
||||||
await connection.ExecuteAsync(sql, cotizaciones);
|
await connection.ExecuteAsync(sql, cotizaciones);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado)
|
public async Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado)
|
||||||
{
|
{
|
||||||
using IDbConnection connection = _connectionFactory.CreateConnection();
|
using IDbConnection connection = _connectionFactory.CreateConnection();
|
||||||
|
|
||||||
// Esta consulta SQL es un poco más avanzada. Usa una "Common Table Expression" (CTE)
|
// Esta consulta usa una "Common Table Expression" (CTE)
|
||||||
// y la función ROW_NUMBER() para obtener el registro más reciente para cada Ticker
|
// y la función ROW_NUMBER() para obtener el registro más reciente para cada Ticker
|
||||||
// dentro del mercado especificado. Es extremadamente eficiente.
|
// dentro del mercado especificado. Es extremadamente eficiente.
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
@@ -50,5 +50,24 @@ namespace Mercados.Infrastructure.Persistence.Repositories
|
|||||||
|
|
||||||
return await connection.QueryAsync<CotizacionBolsa>(sql, new { Mercado = mercado });
|
return await connection.QueryAsync<CotizacionBolsa>(sql, new { Mercado = mercado });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CotizacionBolsa>> ObtenerHistorialPorTickerAsync(string ticker, string mercado, int dias)
|
||||||
|
{
|
||||||
|
using IDbConnection connection = _connectionFactory.CreateConnection();
|
||||||
|
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id, Ticker, Mercado, PrecioActual, Apertura, CierreAnterior, PorcentajeCambio, FechaRegistro
|
||||||
|
FROM
|
||||||
|
CotizacionesBolsa
|
||||||
|
WHERE
|
||||||
|
Ticker = @Ticker
|
||||||
|
AND Mercado = @Mercado
|
||||||
|
AND FechaRegistro >= DATEADD(day, -@Dias, GETUTCDATE())
|
||||||
|
ORDER BY
|
||||||
|
FechaRegistro ASC;"; // ASC es importante para dibujar la línea del gráfico
|
||||||
|
|
||||||
|
return await connection.QueryAsync<CotizacionBolsa>(sql, new { Ticker = ticker, Mercado = mercado, Dias = dias });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,6 @@ namespace Mercados.Infrastructure.Persistence.Repositories
|
|||||||
{
|
{
|
||||||
Task GuardarMuchosAsync(IEnumerable<CotizacionBolsa> cotizaciones);
|
Task GuardarMuchosAsync(IEnumerable<CotizacionBolsa> cotizaciones);
|
||||||
Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado);
|
Task<IEnumerable<CotizacionBolsa>> ObtenerUltimasPorMercadoAsync(string mercado);
|
||||||
|
Task<IEnumerable<CotizacionBolsa>> ObtenerHistorialPorTickerAsync(string ticker, string mercado, int dias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ IHost host = Host.CreateDefaultBuilder(args)
|
|||||||
// que todos implementan la interfaz IDataFetcher.
|
// que todos implementan la interfaz IDataFetcher.
|
||||||
services.AddScoped<IDataFetcher, MercadoAgroFetcher>();
|
services.AddScoped<IDataFetcher, MercadoAgroFetcher>();
|
||||||
services.AddScoped<IDataFetcher, BcrDataFetcher>();
|
services.AddScoped<IDataFetcher, BcrDataFetcher>();
|
||||||
//services.AddScoped<IDataFetcher, FinnhubDataFetcher>();
|
services.AddScoped<IDataFetcher, FinnhubDataFetcher>();
|
||||||
services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>();
|
services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>();
|
||||||
|
|
||||||
// El cliente HTTP es fundamental para hacer llamadas a APIs externas.
|
// El cliente HTTP es fundamental para hacer llamadas a APIs externas.
|
||||||
|
|||||||
Reference in New Issue
Block a user