feat: integración de SIU Guaraní, auto-completado y rediseño en grilla de recorrido

This commit is contained in:
Carlos Tello
2026-03-29 03:06:36 -03:00
parent ca33ed19ce
commit 490bf2355b
8 changed files with 1210 additions and 583 deletions
+5 -1
View File
@@ -1,3 +1,7 @@
{ {
"typescript.preferences.autoImportFileExcludePatterns": ["@ionic/angular/common", "@ionic/angular/standalone"] "typescript.preferences.autoImportFileExcludePatterns": [
"@ionic/angular/common",
"@ionic/angular/standalone"
],
"java.configuration.updateBuildConfiguration": "disabled"
} }
+41 -3
View File
@@ -601,6 +601,7 @@
"integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==", "integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@angular-eslint/bundled-angular-compiler": "20.7.0", "@angular-eslint/bundled-angular-compiler": "20.7.0",
"eslint-scope": "^9.0.0" "eslint-scope": "^9.0.0"
@@ -630,6 +631,7 @@
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.18.tgz",
"integrity": "sha512-XFxgSyjfs0SRD2vQVFJljmM4z9nTvUoI8TRqSre/+l8D2FgzD5pG67Aj2BgDgpSFAUkIcI37G48ijK7a3ZZ3WA==", "integrity": "sha512-XFxgSyjfs0SRD2vQVFJljmM4z9nTvUoI8TRqSre/+l8D2FgzD5pG67Aj2BgDgpSFAUkIcI37G48ijK7a3ZZ3WA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -779,6 +781,7 @@
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.18.tgz",
"integrity": "sha512-M62oQbSTRmnGavIVCwimoadg/PDWadgNhactMm9fgH0eM9rx+iWBAYJk4VufO0bwOhysFpRZpJgXlFjOifz/Jw==", "integrity": "sha512-M62oQbSTRmnGavIVCwimoadg/PDWadgNhactMm9fgH0eM9rx+iWBAYJk4VufO0bwOhysFpRZpJgXlFjOifz/Jw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -795,6 +798,7 @@
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.18.tgz",
"integrity": "sha512-AaP/LCiDNcYmF135EEozjyR04NRBT38ZfBHQwjhgwiBBTejmvcpHwJaHSkraLpZqZzE4BQqqmgiQ1EJqxEwLVA==", "integrity": "sha512-AaP/LCiDNcYmF135EEozjyR04NRBT38ZfBHQwjhgwiBBTejmvcpHwJaHSkraLpZqZzE4BQqqmgiQ1EJqxEwLVA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -808,6 +812,7 @@
"integrity": "sha512-zsoEgLgnblmRbi47YwMghKirJ8IBKJ3+I8TxLBRIBrhx+KHFp+6oeDeLyu9H+djdyk88zexVd09wzR/YK73F0g==", "integrity": "sha512-zsoEgLgnblmRbi47YwMghKirJ8IBKJ3+I8TxLBRIBrhx+KHFp+6oeDeLyu9H+djdyk88zexVd09wzR/YK73F0g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "7.28.3", "@babel/core": "7.28.3",
"@jridgewell/sourcemap-codec": "^1.4.14", "@jridgewell/sourcemap-codec": "^1.4.14",
@@ -840,6 +845,7 @@
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.18.tgz",
"integrity": "sha512-B+NQQngd/aDbcfW0zGLis3wTLDeHTeTYMl/mGKQH+HwdPaRCKI1wEtaXaOYVJXkP2FeThocPevB8gLwNlPQUUw==", "integrity": "sha512-B+NQQngd/aDbcfW0zGLis3wTLDeHTeTYMl/mGKQH+HwdPaRCKI1wEtaXaOYVJXkP2FeThocPevB8gLwNlPQUUw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -865,6 +871,7 @@
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.18.tgz",
"integrity": "sha512-x6/99LfxolyZIFUL3Wr0OrtuXHEDwEz/rwx+WzE7NL+n35yO40t3kp0Sn5uMFwI94i91QZJmXHltMpZhrVLuYg==", "integrity": "sha512-x6/99LfxolyZIFUL3Wr0OrtuXHEDwEz/rwx+WzE7NL+n35yO40t3kp0Sn5uMFwI94i91QZJmXHltMpZhrVLuYg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -893,6 +900,7 @@
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.18.tgz",
"integrity": "sha512-q6s5rEN1yYazpHYp+k4pboXRzMsRB9auzTRBEhyXSGYxqzrnn3qHN0DqgsLC9WAdyhCgnIEMFA8kRT+W277DqQ==", "integrity": "sha512-q6s5rEN1yYazpHYp+k4pboXRzMsRB9auzTRBEhyXSGYxqzrnn3qHN0DqgsLC9WAdyhCgnIEMFA8kRT+W277DqQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -933,6 +941,7 @@
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.18.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.18.tgz",
"integrity": "sha512-3CWejsEYr+ze+ktvWN/qHdyq5WLrj96QZpGYJyxh1pchIcpMPE9MmLpdjf0CUrWYB7g/85u0Geq/xsz72JrGng==", "integrity": "sha512-3CWejsEYr+ze+ktvWN/qHdyq5WLrj96QZpGYJyxh1pchIcpMPE9MmLpdjf0CUrWYB7g/85u0Geq/xsz72JrGng==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@@ -977,6 +986,7 @@
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
@@ -2826,6 +2836,7 @@
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.2.0.tgz", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.2.0.tgz",
"integrity": "sha512-oKaoNeNtH2iIZMDFVrb1atoyRECDGHcfLMunJ5KWN8DtvpVBeeA4c41e20NTuhMxw1cSYbpq2PV2hb+/9CJxlQ==", "integrity": "sha512-oKaoNeNtH2iIZMDFVrb1atoyRECDGHcfLMunJ5KWN8DtvpVBeeA4c41e20NTuhMxw1cSYbpq2PV2hb+/9CJxlQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
@@ -3957,6 +3968,7 @@
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@inquirer/checkbox": "^4.2.1", "@inquirer/checkbox": "^4.2.1",
"@inquirer/confirm": "^5.1.14", "@inquirer/confirm": "^5.1.14",
@@ -7079,6 +7091,7 @@
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.18.0" "undici-types": "~7.18.0"
} }
@@ -7219,6 +7232,7 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1", "@typescript-eslint/types": "8.56.1",
@@ -7326,6 +7340,7 @@
"integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}, },
@@ -7398,6 +7413,7 @@
"integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.9.1", "@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/scope-manager": "8.56.1",
@@ -7682,6 +7698,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -7757,6 +7774,7 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1", "fast-uri": "^3.0.1",
@@ -8455,6 +8473,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -10114,6 +10133,7 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -10606,6 +10626,7 @@
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",
"body-parser": "^2.2.1", "body-parser": "^2.2.1",
@@ -11394,6 +11415,7 @@
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=16.9.0" "node": ">=16.9.0"
} }
@@ -12476,7 +12498,8 @@
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
"integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/jasmine-spec-reporter": { "node_modules/jasmine-spec-reporter": {
"version": "5.0.2", "version": "5.0.2",
@@ -12525,6 +12548,7 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@@ -12665,6 +12689,7 @@
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@colors/colors": "1.5.0", "@colors/colors": "1.5.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
@@ -13242,6 +13267,7 @@
"integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==", "integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"copy-anything": "^2.0.1", "copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1", "parse-node-version": "^1.0.1",
@@ -13386,6 +13412,7 @@
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"cli-truncate": "^4.0.0", "cli-truncate": "^4.0.0",
"colorette": "^2.0.20", "colorette": "^2.0.20",
@@ -15283,6 +15310,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -16115,6 +16143,7 @@
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
@@ -16208,6 +16237,7 @@
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
"immutable": "^5.0.2", "immutable": "^5.0.2",
@@ -17350,6 +17380,7 @@
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.14.0", "acorn": "^8.14.0",
@@ -17551,7 +17582,8 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD",
"peer": true
}, },
"node_modules/tuf-js": { "node_modules/tuf-js": {
"version": "4.1.0", "version": "4.1.0",
@@ -17687,6 +17719,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -17952,6 +17985,7 @@
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -18086,6 +18120,7 @@
"integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.7", "@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8", "@types/estree": "^1.0.8",
@@ -18188,6 +18223,7 @@
"integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/bonjour": "^3.5.13", "@types/bonjour": "^3.5.13",
"@types/connect-history-api-fallback": "^1.5.4", "@types/connect-history-api-fallback": "^1.5.4",
@@ -19170,6 +19206,7 @@
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
@@ -19188,7 +19225,8 @@
"version": "0.15.1", "version": "0.15.1",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
"license": "MIT" "license": "MIT",
"peer": true
} }
} }
} }
+66 -6
View File
@@ -206,9 +206,14 @@
<ion-card-content> <ion-card-content>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span class="selector-title" style="margin: 0;">Plan de Estudios / Recorrido</span> <span class="selector-title" style="margin: 0;">Plan de Estudios / Recorrido</span>
<ion-button fill="clear" size="small" (click)="forzarRespaldo()" style="margin: 0;"> <div style="display: flex; gap: 8px;">
<ion-icon name="save-outline" slot="icon-only" color="primary"></ion-icon> <ion-button fill="outline" color="primary" size="small" (click)="forzarRespaldo()" style="margin: 0;" title="Guardar Manualmente" class="header-btn">
</ion-button> <ion-icon name="save-outline" slot="icon-only"></ion-icon>
</ion-button>
<ion-button fill="outline" [color]="siuMaterias && siuMaterias.length > 0 ? 'success' : 'tertiary'" size="small" (click)="openSiuModal()" style="margin: 0;" title="Conectar con SIU Guaraní" class="header-btn">
<ion-icon name="school-outline" slot="icon-only"></ion-icon>
</ion-button>
</div>
</div> </div>
<p class="selector-desc">Selecciona tu carrera para guardar tu avance y ver el recorrido sugerido, toca materias <p class="selector-desc">Selecciona tu carrera para guardar tu avance y ver el recorrido sugerido, toca materias
para marcarlas como aprobadas o en curso.</p> para marcarlas como aprobadas o en curso.</p>
@@ -238,29 +243,84 @@
<div class="materia-cell" [class.aprobada]="isAprobada(row.c1.name)" <div class="materia-cell" [class.aprobada]="isAprobada(row.c1.name)"
[class.encurso]="isEnCurso(row.c1.name)" [class.optativa-cell]="row.c1.isOptativa" [class.encurso]="isEnCurso(row.c1.name)" [class.optativa-cell]="row.c1.isOptativa"
(click)="handleCellClick(row.c1)"> (click)="handleCellClick(row.c1)">
{{ row.c1.name }} <span class="m-name">{{ abreviarMateria(row.c1.name) }}</span>
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c1.name) as siuMat" [ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
{{ getSiuBadgeText(siuMat) }}
</ion-badge>
</div> </div>
</td> </td>
<td> <td>
<div class="materia-cell" [class.aprobada]="isAprobada(row.c2.name)" <div class="materia-cell" [class.aprobada]="isAprobada(row.c2.name)"
[class.encurso]="isEnCurso(row.c2.name)" [class.optativa-cell]="row.c2.isOptativa" [class.encurso]="isEnCurso(row.c2.name)" [class.optativa-cell]="row.c2.isOptativa"
(click)="handleCellClick(row.c2)"> (click)="handleCellClick(row.c2)">
{{ row.c2.name }} <span class="m-name">{{ abreviarMateria(row.c2.name) }}</span>
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c2.name) as siuMat" [ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
{{ getSiuBadgeText(siuMat) }}
</ion-badge>
</div> </div>
</td> </td>
<td> <td>
<div class="materia-cell" [class.aprobada]="isAprobada(row.c3.name)" <div class="materia-cell" [class.aprobada]="isAprobada(row.c3.name)"
[class.encurso]="isEnCurso(row.c3.name)" [class.optativa-cell]="row.c3.isOptativa" [class.encurso]="isEnCurso(row.c3.name)" [class.optativa-cell]="row.c3.isOptativa"
(click)="handleCellClick(row.c3)"> (click)="handleCellClick(row.c3)">
{{ row.c3.name }} <span class="m-name">{{ abreviarMateria(row.c3.name) }}</span>
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c3.name) as siuMat" [ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
{{ getSiuBadgeText(siuMat) }}
</ion-badge>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- The SIU Guaraní list at the bottom was removed per user request -->
</div> </div>
<!-- Modal SIU Guaraní (Login) -->
<ion-modal [isOpen]="isSiuModalOpen" (didDismiss)="closeSiuModal()" class="siu-login-modal" initialBreakpoint="0.8" [breakpoints]="[0, 0.8, 1]">
<ng-template>
<ion-header class="ion-no-border">
<ion-toolbar color="primary">
<ion-title>Conectar con SIU</ion-title>
<ion-buttons slot="end">
<ion-button (click)="closeSiuModal()" [disabled]="siuLoading">Cerrar</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding siu-login-content">
<div class="ion-text-center siu-logo-container ion-margin-bottom">
<ion-icon name="school" color="primary" style="font-size: 64px;"></ion-icon>
<h2 style="margin-top: 10px;">Historia Académica</h2>
<p style="color: var(--ion-color-medium);">Ingresá para traer tu recorrido y notas automáticamente a la app.</p>
</div>
<ion-list class="ion-margin-bottom siu-form-list" lines="full">
<ion-item>
<ion-icon name="person-outline" slot="start" color="medium"></ion-icon>
<ion-input type="text" placeholder="DNI (Sin puntos)" [(ngModel)]="siuDni" [disabled]="siuLoading" clearInput required></ion-input>
</ion-item>
<ion-item>
<ion-icon name="lock-closed-outline" slot="start" color="medium"></ion-icon>
<ion-input type="password" placeholder="Contraseña de SIU" [(ngModel)]="siuPass" [disabled]="siuLoading" clearOnEdit="false" required></ion-input>
</ion-item>
</ion-list>
<div class="siu-info ion-margin-bottom" style="font-size: 0.85rem; color: var(--ion-color-medium); background: rgba(var(--ion-color-primary-rgb), 0.1); padding: 12px; border-radius: 8px; border-left: 4px solid var(--ion-color-primary);">
<p style="margin-top: 0;"><ion-icon name="shield-checkmark-outline"></ion-icon> Privacidad Zero-Trust: Las credenciales no se envían a ningún servidor nuestro. La app se conecta directo con el portal localmente desde tu teléfono.</p>
<p style="margin-bottom: 0;"><ion-icon name="alert-circle-outline"></ion-icon> Al conectar, se importará tu última situación curricular.</p>
</div>
<ion-button expand="block" color="primary" class="ion-margin-top" (click)="sincronizarSiu()" [disabled]="siuLoading || !siuDni || !siuPass">
<ng-container *ngIf="!siuLoading">
<ion-icon name="log-in-outline" slot="start"></ion-icon>
Conectar y Sincronizar
</ng-container>
<ion-spinner *ngIf="siuLoading" name="crescent"></ion-spinner>
</ion-button>
</ion-content>
</ng-template>
</ion-modal>
</ion-content> </ion-content>
<ion-footer class="ion-no-border"> <ion-footer class="ion-no-border">
+231 -29
View File
@@ -185,15 +185,15 @@ ion-card.subject-card {
gap: 12px; gap: 12px;
.share-icon, .share-icon,
.menu-icon { .menu-icon {
font-size: 1.6rem; font-size: 1.6rem;
cursor: pointer; cursor: pointer;
transition: transform 0.2s; transition: transform 0.2s;
&:active { &:active {
transform: scale(0.9); transform: scale(0.9);
}
} }
}
} }
@@ -225,6 +225,7 @@ ion-card.subject-card {
from { from {
box-shadow: 0 0 5px var(--ion-color-secondary); box-shadow: 0 0 5px var(--ion-color-secondary);
} }
to { to {
box-shadow: 0 0 20px var(--ion-color-secondary); box-shadow: 0 0 20px var(--ion-color-secondary);
} }
@@ -233,11 +234,13 @@ ion-card.subject-card {
/* Dark mode overwrites for custom colored text */ /* Dark mode overwrites for custom colored text */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.aula-container .aula-tag { .aula-container .aula-tag {
color: #ae7fff; /* Lighter vivid purple for dark background */ color: #ae7fff;
/* Lighter vivid purple for dark background */
} }
.schedule-text { .schedule-text {
color: #ff8ab6 !important; /* Lighter magenta for dark background */ color: #ff8ab6 !important;
/* Lighter magenta for dark background */
} }
.highlight-card { .highlight-card {
@@ -249,6 +252,7 @@ ion-card.subject-card {
from { from {
box-shadow: 0 0 5px var(--ion-color-primary); box-shadow: 0 0 5px var(--ion-color-primary);
} }
to { to {
box-shadow: 0 0 20px var(--ion-color-primary); box-shadow: 0 0 20px var(--ion-color-primary);
} }
@@ -260,9 +264,17 @@ ion-card.subject-card {
.proxima-label { color: rgba(255, 255, 255, 0.6); } .proxima-label {
.proxima-asignatura { color: #fff; } color: rgba(255, 255, 255, 0.6);
.proxima-detalles { color: rgba(255, 255, 255, 0.75); } }
.proxima-asignatura {
color: #fff;
}
.proxima-detalles {
color: rgba(255, 255, 255, 0.75);
}
} }
.evento-badge { .evento-badge {
@@ -287,7 +299,7 @@ ion-card.subject-card {
gap: 6px; gap: 6px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.3px; letter-spacing: 0.3px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
ion-icon { ion-icon {
font-size: 0.95rem; font-size: 0.95rem;
@@ -322,8 +334,15 @@ ion-card.subject-card {
} }
@keyframes pulse-urgente { @keyframes pulse-urgente {
0%, 100% { box-shadow: 0 4px 18px rgba(var(--ion-color-danger-rgb), 0.15); }
50% { box-shadow: 0 4px 28px rgba(var(--ion-color-danger-rgb), 0.35); } 0%,
100% {
box-shadow: 0 4px 18px rgba(var(--ion-color-danger-rgb), 0.15);
}
50% {
box-shadow: 0 4px 28px rgba(var(--ion-color-danger-rgb), 0.35);
}
} }
.proxima-header { .proxima-header {
@@ -342,7 +361,9 @@ ion-card.subject-card {
text-transform: uppercase; text-transform: uppercase;
color: var(--ion-color-medium); color: var(--ion-color-medium);
ion-icon { font-size: 1rem; } ion-icon {
font-size: 1rem;
}
} }
} }
@@ -353,12 +374,23 @@ ion-card.subject-card {
--padding-start: 10px; --padding-start: 10px;
--padding-end: 10px; --padding-end: 10px;
ion-icon { margin-right: 4px; } ion-icon {
margin-right: 4px;
}
} }
@keyframes pulse-chip { @keyframes pulse-chip {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.75; transform: scale(1.05); } 0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.75;
transform: scale(1.05);
}
} }
.pulse-chip { .pulse-chip {
@@ -386,7 +418,10 @@ ion-card.subject-card {
color: var(--ion-color-medium-shade); color: var(--ion-color-medium-shade);
font-weight: 500; font-weight: 500;
ion-icon { font-size: 1rem; flex-shrink: 0; } ion-icon {
font-size: 1rem;
flex-shrink: 0;
}
} }
} }
@@ -401,7 +436,7 @@ ion-card.subject-card {
.carrera-selector-card { .carrera-selector-card {
margin: 10px 0 20px 0; margin: 10px 0 20px 0;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
ion-card-content { ion-card-content {
padding: 16px; padding: 16px;
@@ -441,14 +476,22 @@ ion-card.subject-card {
} }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.recorrido-table { .recorrido-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-size: 0.75rem; font-size: 0.70rem;
/* Fuente más chica para nombres largos */
th { th {
background: var(--ion-color-primary); background: var(--ion-color-primary);
@@ -466,21 +509,58 @@ ion-card.subject-card {
} }
.materia-cell { .materia-cell {
padding: 8px 4px; padding: 6px 4px;
height: 100%; width: 100%;
min-height: 60px; aspect-ratio: 1 / 0.65;
/* Reducimos 15% el alto respecto al ancho */
background: var(--ion-color-light); background: var(--ion-color-light);
border-radius: 6px; border-radius: 6px;
text-align: center; text-align: center;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
user-select: none; user-select: none;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 1.5px solid transparent; border: 1.5px solid transparent;
.m-name {
display: flex;
flex: 1;
align-items: center;
}
.siu-cell-badge {
margin-top: 4px;
font-size: 0.75rem;
padding: 3px 8px;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
&.custom-badge-success {
--background: rgba(255, 255, 255, 0.95);
color: #a31c62;
/* Rosa oscuro legible sobre blanco */
font-weight: 800;
letter-spacing: 0.5px;
}
&.custom-badge-warning {
--background: rgba(255, 255, 255, 0.95);
color: #0b5f7e;
/* Azul/Cyan oscuro legible sobre blanco */
font-weight: 800;
letter-spacing: 0.5px;
}
&.custom-badge-danger {
--background: rgba(var(--ion-color-danger-rgb), 0.15);
color: var(--ion-color-danger);
}
}
&:active { &:active {
transform: scale(0.95); transform: scale(0.95);
} }
@@ -490,7 +570,7 @@ ion-card.subject-card {
color: white; color: white;
font-weight: 600; font-weight: 600;
box-shadow: 0 2px 6px rgba(104, 44, 173, 0.3); box-shadow: 0 2px 6px rgba(104, 44, 173, 0.3);
border-color: rgba(0,0,0,0.1); border-color: rgba(0, 0, 0, 0.1);
} }
&.encurso { &.encurso {
@@ -515,3 +595,125 @@ ion-card.subject-card {
margin-top: 12px; margin-top: 12px;
font-style: italic; font-style: italic;
} }
// ─── SIU Guaraní ───────────────────────────────────────────
.siu-container {
padding: 10px 0;
.siu-header {
padding: 0 10px;
}
.ultima-sync {
padding: 0 10px;
}
}
.siu-card {
margin: 10px 0;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
ion-card-header {
padding: 12px 16px 4px;
ion-card-title {
color: var(--ion-color-dark);
font-weight: 700;
}
}
.siu-card-content {
padding: 10px 16px 16px;
ion-badge {
font-size: 0.9rem;
padding: 6px 10px;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
}
.siu-login-modal {
--border-radius: 16px 16px 0 0;
.siu-logo-container {
padding-top: 20px;
h2 {
font-weight: 800;
color: var(--ion-color-primary);
}
}
.siu-form-list {
background: transparent;
ion-item {
--background: rgba(var(--ion-color-light-rgb), 0.5);
--border-radius: 8px;
margin-bottom: 10px;
--padding-start: 12px;
ion-input {
margin-left: 10px;
font-weight: 500;
}
}
}
ion-button[expand="block"] {
margin-top: 20px;
height: 48px;
--border-radius: 12px;
font-weight: bold;
font-size: 1.05rem;
letter-spacing: 0.5px;
}
}
@media (prefers-color-scheme: dark) {
.siu-card {
background: var(--ion-color-step-100);
}
.siu-login-modal {
.siu-form-list ion-item {
--background: var(--ion-color-step-100);
}
.siu-info {
background: var(--ion-color-step-100) !important;
}
}
/* Mejorar el contraste de los botones de Optativa en modo oscuro */
.recorrido-table .materia-cell.optativa-cell:not(.aprobada):not(.encurso) {
border-color: rgba(255, 255, 255, 0.45);
color: #ff8ab6;
/* Lighter magenta for dark background */
}
/* Mejorar íconos y bordes de encabezado (botones SIU/Guardar) */
ion-button.header-btn {
--border-width: 1.5px;
}
ion-button.header-btn[color="primary"] {
--color: #ff8ab6 !important;
--border-color: #ff8ab6 !important;
}
ion-button.header-btn[color="tertiary"] {
--color: #ae7fff !important;
--border-color: #ae7fff !important;
}
ion-button.header-btn[color="success"] {
--color: #68f4a1 !important;
--border-color: #68f4a1 !important;
}
}
+284 -33
View File
@@ -13,6 +13,7 @@ import { LocalStorageService } from '../services/local-storage.service';
import { EventosService } from '../services/eventos.service'; import { EventosService } from '../services/eventos.service';
import { EventoModalComponent } from '../components/evento-modal/evento-modal.component'; import { EventoModalComponent } from '../components/evento-modal/evento-modal.component';
import { AgendaModalComponent } from '../components/agenda-modal/agenda-modal.component'; import { AgendaModalComponent } from '../components/agenda-modal/agenda-modal.component';
import { SiuService } from '../services/siu-guarani.service';
export interface ProximaClaseInfo { export interface ProximaClaseInfo {
@@ -66,6 +67,14 @@ export class HomePage implements OnInit {
carreraSeleccionada: string = 'tecnologias'; carreraSeleccionada: string = 'tecnologias';
// SIU Guaraní State
siuMaterias: any[] = [];
siuLastSync: string | null = null;
isSiuModalOpen: boolean = false;
siuLoading: boolean = false;
siuDni: string = '';
siuPass: string = '';
// Mapa de días en español al getDay() de JS (0=Dom) // Mapa de días en español al getDay() de JS (0=Dom)
private readonly DIAS_DOW: Record<string, number> = { private readonly DIAS_DOW: Record<string, number> = {
@@ -81,7 +90,8 @@ export class HomePage implements OnInit {
private alertCtrl: AlertController, private alertCtrl: AlertController,
private localStorageService: LocalStorageService, private localStorageService: LocalStorageService,
private actionSheetCtrl: ActionSheetController, private actionSheetCtrl: ActionSheetController,
private eventosService: EventosService private eventosService: EventosService,
private siuService: SiuService
) { } ) { }
@@ -118,6 +128,14 @@ export class HomePage implements OnInit {
this.aprobadas = userData.aprobadas || []; this.aprobadas = userData.aprobadas || [];
this.encurso = userData.encurso || []; this.encurso = userData.encurso || [];
this.optativasSeleccionadas = userData.optativas || []; this.optativasSeleccionadas = userData.optativas || [];
this.siuMaterias = userData.siuMaterias || [];
this.siuLastSync = userData.siuLastSync || null;
// Auto-completar desde SIU si hay datos previamente cacheados
if (this.siuMaterias.length > 0) {
this.autoCompletarDesdeSiu();
}
// Sincronizar eventos cargados desde el CSV con el servicio de eventos // Sincronizar eventos cargados desde el CSV con el servicio de eventos
for (const ev of userData.eventos) { for (const ev of userData.eventos) {
await this.eventosService.saveEvento(ev); await this.eventosService.saveEvento(ev);
@@ -219,6 +237,8 @@ export class HomePage implements OnInit {
this.aprobadas = imported.aprobadas || []; this.aprobadas = imported.aprobadas || [];
this.encurso = imported.encurso || []; this.encurso = imported.encurso || [];
this.optativasSeleccionadas = imported.optativas || []; this.optativasSeleccionadas = imported.optativas || [];
this.siuMaterias = imported.siuMaterias || [];
this.siuLastSync = imported.siuLastSync || null;
// Sincronizar eventos cargados desde el CSV con el servicio de eventos // Sincronizar eventos cargados desde el CSV con el servicio de eventos
for (const ev of imported.eventos) { for (const ev of imported.eventos) {
@@ -704,7 +724,7 @@ export class HomePage implements OnInit {
return [ return [
{ c1: mat('Gestión de Sistemas de Información'), c2: mat('Administración de Negocios Digitales'), c3: mat('Seguridad de la Información') }, { c1: mat('Gestión de Sistemas de Información'), c2: mat('Administración de Negocios Digitales'), c3: mat('Seguridad de la Información') },
{ c1: mat('Análisis de Datos I'), c2: mat('Sistemas Digitales'), c3: mat('Programación de Vanguardia') }, { c1: mat('Análisis de Datos I'), c2: mat('Sistemas Digitales'), c3: mat('Programación de Vanguardia') },
{ c1: mat('Taller: Proyectos Digitales'), c2: mat('Taller: Emprendedurismo en Innovación Digital'), c3: mat('Taller: Control de calidad de Software') }, { c1: mat('Taller: Gestión de Proyectos Digitales'), c2: mat('Taller: Emprendedurismo en Innovación Digital'), c3: mat('Taller: Control de Calidad de Software') },
{ c1: getSlot('tec_opt1', 'Optativa 1'), c2: getSlot('tec_opt2', 'Optativa 2'), c3: getSlot('tec_opt3', 'Optativa 3') } { c1: getSlot('tec_opt1', 'Optativa 1'), c2: getSlot('tec_opt2', 'Optativa 2'), c3: getSlot('tec_opt3', 'Optativa 3') }
]; ];
} else if (this.carreraSeleccionada === 'datos') { } else if (this.carreraSeleccionada === 'datos') {
@@ -887,7 +907,9 @@ export class HomePage implements OnInit {
eventos: this.eventos, eventos: this.eventos,
aprobadas: this.aprobadas, aprobadas: this.aprobadas,
optativas: this.optativasSeleccionadas, optativas: this.optativasSeleccionadas,
encurso: this.encurso encurso: this.encurso,
siuMaterias: this.siuMaterias,
siuLastSync: this.siuLastSync
}); });
} }
@@ -898,7 +920,9 @@ export class HomePage implements OnInit {
eventos: this.eventos, eventos: this.eventos,
aprobadas: this.aprobadas, aprobadas: this.aprobadas,
optativas: this.optativasSeleccionadas, optativas: this.optativasSeleccionadas,
encurso: this.encurso encurso: this.encurso,
siuMaterias: this.siuMaterias,
siuLastSync: this.siuLastSync
}; };
const actionSheet = await this.actionSheetCtrl.create({ const actionSheet = await this.actionSheetCtrl.create({
@@ -909,14 +933,14 @@ export class HomePage implements OnInit {
text: 'Compartir (Drive, WhatsApp, Mail...)', text: 'Compartir (Drive, WhatsApp, Mail...)',
icon: 'share-social-outline', icon: 'share-social-outline',
handler: async () => { handler: async () => {
await this.ejecutarExportacion(data, false); await this.ejecutarExportacion(data, false);
} }
}, },
{ {
text: 'Descargar Offline (A la carpeta Descargas)', text: 'Descargar Offline (A la carpeta Descargas)',
icon: 'download-outline', icon: 'download-outline',
handler: async () => { handler: async () => {
await this.ejecutarExportacion(data, true); await this.ejecutarExportacion(data, true);
} }
}, },
{ {
@@ -939,44 +963,271 @@ export class HomePage implements OnInit {
icon: 'save' icon: 'save'
}); });
toast.present(); toast.present();
await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => {}); await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => { });
} }
} }
async importarRespaldoManual() { async importarRespaldoManual() {
const imported = await this.localStorageService.importBackup(); const imported = await this.localStorageService.importBackup();
if (imported && (imported.favoritos.length > 0 || imported.eventos.length > 0 || imported.aprobadas?.length > 0 || imported.optativas?.length > 0 || imported.encurso?.length > 0)) { if (imported && (imported.favoritos.length > 0 || imported.eventos.length > 0 || imported.aprobadas?.length > 0 || imported.optativas?.length > 0 || imported.encurso?.length > 0)) {
this.favoritos = imported.favoritos; this.favoritos = imported.favoritos;
this.aprobadas = imported.aprobadas || []; this.aprobadas = imported.aprobadas || [];
this.encurso = imported.encurso || []; this.encurso = imported.encurso || [];
this.optativasSeleccionadas = imported.optativas || []; this.optativasSeleccionadas = imported.optativas || [];
this.siuMaterias = imported.siuMaterias || [];
this.siuLastSync = imported.siuLastSync || null;
for (const ev of imported.eventos) { for (const ev of imported.eventos) {
await this.eventosService.saveEvento(ev); await this.eventosService.saveEvento(ev);
} }
this.eventos = await this.eventosService.loadEventos(); this.eventos = await this.eventosService.loadEventos();
await Preferences.set({ key: 'favoritos', value: JSON.stringify(this.favoritos) }); await Preferences.set({ key: 'favoritos', value: JSON.stringify(this.favoritos) });
this.guardarProgreso(); this.guardarProgreso();
this.filterHorarios(); this.filterHorarios();
const toast = await this.toastCtrl.create({ const toast = await this.toastCtrl.create({
message: '¡Respaldo importado correctamente!', message: '¡Respaldo importado correctamente!',
duration: 3000, duration: 3000,
color: 'success', color: 'success',
icon: 'checkmark-circle' icon: 'checkmark-circle'
}); });
toast.present(); toast.present();
await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => {}); await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => { });
} else { } else {
const toast = await this.toastCtrl.create({ const toast = await this.toastCtrl.create({
message: 'No se procesó ningún archivo válido.', message: 'No se procesó ningún archivo válido.',
duration: 2500, duration: 2500,
color: 'warning', color: 'warning',
icon: 'alert-circle' icon: 'alert-circle'
}); });
toast.present(); toast.present();
} }
} }
// --- SIU Guaraní ---
openSiuModal() {
this.isSiuModalOpen = true;
}
closeSiuModal() {
this.isSiuModalOpen = false;
this.siuDni = '';
this.siuPass = '';
}
async sincronizarSiu() {
if (!this.siuDni || !this.siuPass) {
const toast = await this.toastCtrl.create({
message: 'Por favor, ingresá DNI y Contraseña para conectar',
duration: 2500,
color: 'warning'
});
toast.present();
return;
}
this.siuLoading = true;
try {
const materias = await this.siuService.getAcademicHistory(this.siuDni, this.siuPass);
this.siuMaterias = materias;
this.siuLastSync = new Date().toISOString();
this.autoCompletarDesdeSiu();
this.guardarProgreso();
const toast = await this.toastCtrl.create({
message: 'Historial del SIU cargado con éxito!',
duration: 3000,
color: 'success'
});
toast.present();
this.closeSiuModal();
} catch (error) {
console.error(error);
const toast = await this.toastCtrl.create({
message: 'Error al conectar con SIU. Comprobá tus datos o intentá más tarde.',
duration: 4000,
color: 'danger'
});
toast.present();
} finally {
this.siuLoading = false;
}
}
getNotaColor(nota: string): string {
if (!nota) return 'medium';
const n = parseInt(nota, 10);
if (!isNaN(n)) {
if (n >= 4) return 'success';
return 'danger';
}
// Fallback if not numeric
const lower = nota.toLowerCase();
if (lower.includes('aprobado') || lower.includes('promocionado') || lower.includes('equivalencia')) return 'success';
if (lower.includes('regular') || lower.includes('en curso')) return 'warning';
return 'medium';
}
// --- SIU Guaraní Mapeo y Auto-Sync ---
getCleanSubjectName(name: string): string {
if (!name) return '';
return name.split(' (')[0].trim();
}
matcheaSiu(siuName: string, appName: string): boolean {
const siuN = this.normalizeText(siuName);
const appN = this.normalizeText(this.getCleanSubjectName(appName));
if (!siuN || !appN) return false;
return siuN === appN || appN.includes(siuN) || siuN.includes(appN);
}
getSiuMateriaParaCelda(materiaNombre: string): any {
if (!materiaNombre || !this.siuMaterias) return null;
const siuMat = this.siuMaterias.find(m => this.matcheaSiu(m.nombre, materiaNombre));
if (siuMat && this.determinarEstadoSiu(siuMat.nota, siuMat.origen) !== null) {
return siuMat;
}
return null;
}
getSiuBadgeText(siuMat: any): string {
if (!siuMat) return '';
const notaValid = siuMat.nota && siuMat.nota.trim() !== '' && siuMat.nota !== 'SIN NOTA' && siuMat.nota !== '-';
let txt = notaValid ? siuMat.nota : '';
const estado = this.determinarEstadoSiu(siuMat.nota, siuMat.origen);
if (estado === 'encurso') {
if (txt) {
return txt.toLowerCase().includes('curso') ? txt : `${txt} (En Curso)`;
}
return 'En Curso';
}
let badgeText = txt || 'Aprobado';
badgeText = badgeText.replace(/Promocionado/gi, 'Prom.');
badgeText = badgeText.replace(/Aprobado/gi, 'Aprob.');
return badgeText;
}
abreviarMateria(nombre: string): string {
if (!nombre) return '';
let n = nombre;
// Sustituciones específicas para encajar mejor en los zócalos
n = n.replace('Gestión de Sistemas de Información', 'Gest. Sist. de Info.');
n = n.replace('Administración de Negocios Digitales', 'Admin. Negocios Dig.');
n = n.replace('Seguridad de la Información', 'Seguridad Info.');
n = n.replace('Programación de Vanguardia', 'Prog. de Vanguardia');
n = n.replace('Taller: Emprendedurismo en Innovación Digital', 'Taller Emprendedurismo');
n = n.replace('Taller: Gestión de Proyectos Digitales', 'Taller Proyectos');
n = n.replace('Taller: Control de Calidad de Software', 'Taller QA Software');
n = n.replace(/Gestión del Talento Humano en la Industria [dD]igital/, 'Gest. Talento Humano');
n = n.replace('Gestión de Redes Informáticas', 'Redes Informáticas');
n = n.replace('Desarrollo en Dispositivos Móviles', 'Des. Dispositivos Móviles');
n = n.replace('Diseño y Producción de Videojuegos', 'Diseño y Prod. Videojuegos');
return n;
}
get siuMateriasFiltradas() {
if (!this.siuMaterias) return [];
const allCareerSubjects: string[] = [];
for (const row of this.getRecorridoData()) {
allCareerSubjects.push(row.c1.name, row.c2.name, row.c3.name);
}
return this.siuMaterias.filter(mat => {
const inCareer = allCareerSubjects.some(appName => this.matcheaSiu(mat.nombre, appName));
const hasState = this.determinarEstadoSiu(mat.nota, mat.origen) !== null;
return inCareer && hasState;
});
}
determinarEstadoSiu(nota: string, origen: string): 'aprobada' | 'encurso' | null {
const n = parseInt(nota, 10);
const lowerNota = (nota || '').toLowerCase();
const lowerOrigen = (origen || '').toLowerCase();
// Si la nota es numérica y es mayor o igual a 4, está aprobada
if (!isNaN(n) && n >= 4) return 'aprobada';
if (lowerNota.includes('aprobado') || lowerNota.includes('promocionado') || lowerNota.includes('equivalencia')) return 'aprobada';
if (lowerOrigen.includes('aprobado') || lowerOrigen.includes('promocionado')) return 'aprobada';
// Si no está aprobada, pero cursa regular o está anotado
if (lowerOrigen.includes('en curso') || lowerNota.includes('en curso') || lowerOrigen.includes('regular')) return 'encurso';
return null;
}
autoCompletarDesdeSiu() {
let changed = false;
// Primero, pre-mapear optativas huérfanas
const newlyMapped = this.autoAsignarOptativasSiu();
if (newlyMapped) changed = true;
const allCareerSubjects: string[] = [];
for (const row of this.getRecorridoData()) {
allCareerSubjects.push(row.c1.name, row.c2.name, row.c3.name);
}
for (const appName of allCareerSubjects) {
if (appName.startsWith('Optativa') && !appName.includes('ASIG')) continue; // Optativa no seleccionada
const siuMat = this.getSiuMateriaParaCelda(appName);
if (siuMat) {
const estadoSiu = this.determinarEstadoSiu(siuMat.nota, siuMat.origen);
if (estadoSiu === 'aprobada' && !this.aprobadas.includes(appName)) {
this.aprobadas = [...this.aprobadas, appName];
this.encurso = this.encurso.filter(x => x !== appName);
changed = true;
} else if (estadoSiu === 'encurso' && !this.aprobadas.includes(appName) && !this.encurso.includes(appName)) {
this.encurso = [...this.encurso, appName];
changed = true;
}
}
}
if (changed) this.guardarProgreso();
}
autoAsignarOptativasSiu(): boolean {
if (!this.siuMaterias) return false;
let mappedAlgo = false;
const allCareerSubjects: string[] = [];
for (const row of this.getRecorridoData()) {
allCareerSubjects.push(row.c1.name, row.c2.name, row.c3.name);
}
const optativasSiu = this.siuMaterias.filter(mat => {
const estado = this.determinarEstadoSiu(mat.nota, mat.origen);
if (!estado) return false;
return !allCareerSubjects.some(appName => this.matcheaSiu(mat.nombre, appName));
});
if (optativasSiu.length > 0) {
let slots = ['tec_opt1', 'tec_opt2', 'tec_opt3'];
if (this.carreraSeleccionada === 'datos') slots = ['dat_opt1', 'dat_opt2', 'dat_opt3'];
else if (this.carreraSeleccionada === 'videojuegos') slots = ['vid_opt1', 'vid_opt2', 'vid_opt3'];
for (const siuOpt of optativasSiu) {
const cleanName = this.getCleanSubjectName(siuOpt.nombre);
const alreadySelected = this.optativasSeleccionadas.find(o => this.matcheaSiu(siuOpt.nombre, o.materia));
if (!alreadySelected) {
const freeSlot = slots.find(s => !this.optativasSeleccionadas.find(o => o.slot === s));
if (freeSlot) {
this.optativasSeleccionadas.push({ slot: freeSlot, materia: cleanName });
// actualizamos el map de todo para que no intente la próxima vuelta
allCareerSubjects.push(cleanName);
mappedAlgo = true;
}
}
}
}
return mappedAlgo;
}
} }
+9 -3
View File
@@ -12,6 +12,8 @@ export interface AppData {
aprobadas: string[]; aprobadas: string[];
optativas: {slot: string, materia: string}[]; optativas: {slot: string, materia: string}[];
encurso: string[]; encurso: string[];
siuMaterias?: {nombre: string, nota: string, origen: string}[];
siuLastSync?: string | null;
} }
@Injectable({ @Injectable({
@@ -55,7 +57,9 @@ export class LocalStorageService {
eventos: parsed.eventos || [], eventos: parsed.eventos || [],
aprobadas: parsed.aprobadas || [], aprobadas: parsed.aprobadas || [],
optativas: parsed.optativas || [], optativas: parsed.optativas || [],
encurso: parsed.encurso || [] encurso: parsed.encurso || [],
siuMaterias: parsed.siuMaterias || [],
siuLastSync: parsed.siuLastSync || null
}; };
} }
return null; return null;
@@ -130,7 +134,9 @@ export class LocalStorageService {
eventos: parsed.eventos || [], eventos: parsed.eventos || [],
aprobadas: parsed.aprobadas || [], aprobadas: parsed.aprobadas || [],
optativas: parsed.optativas || [], optativas: parsed.optativas || [],
encurso: parsed.encurso || [] encurso: parsed.encurso || [],
siuMaterias: parsed.siuMaterias || [],
siuLastSync: parsed.siuLastSync || null
}; };
await this.saveData(importedData); await this.saveData(importedData);
return importedData; return importedData;
@@ -208,7 +214,7 @@ export class LocalStorageService {
} }
const importedData: AppData = { const importedData: AppData = {
favoritos: favs, eventos: evs, aprobadas: aprobadas, optativas: optativasMap, encurso: encurso favoritos: favs, eventos: evs, aprobadas: aprobadas, optativas: optativasMap, encurso: encurso, siuMaterias: [], siuLastSync: null
}; };
if (saveAfter) { if (saveAfter) {
+66
View File
@@ -0,0 +1,66 @@
import { Injectable } from '@angular/core';
import { CapacitorHttp, HttpResponse } from '@capacitor/core';
@Injectable({ providedIn: 'root' })
export class SiuService {
private readonly BASE_URL = 'https://siuuniciu.eastus2.cloudapp.azure.com/caba';
async getAcademicHistory(dni: string, pass: string, carreraId: string = '30'): Promise<any[]> {
try {
// 1. LOGIN POST
const loginData = `usuario=${dni}&password=${pass}&login=Ingresar`;
await CapacitorHttp.post({
url: `${this.BASE_URL}/acceso?auth=form`,
data: loginData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
// 2. CAMBIO DE CARRERA (Simulando el GET que capturamos)
// Esto es lo que destrabó el cuello de botella
await CapacitorHttp.get({
url: `${this.BASE_URL}/acceso/cambiar_carrera?id=${carreraId}&op=inicio_alumno`,
});
// 3. OBTENER PLAN DE ESTUDIO
const resPlan: HttpResponse = await CapacitorHttp.get({
url: `${this.BASE_URL}/plan_estudio`,
});
// 4. PARSEO DEL JSON EMBEBIDO
// Buscamos el bloque on_arrival que contiene "info_plan"
const rawHtml = resPlan.data;
const regex = /on_arrival\((\{.*?"id":"info_plan".*?\})\);/s;
const match = rawHtml.match(regex);
if (match && match[1]) {
const jsonData = JSON.parse(match[1]);
const contentHtml = jsonData.content;
return this.parseHtmlSubjects(contentHtml);
}
throw new Error('No se encontró el bloque de materias');
} catch (error) {
console.error('Error SIU:', error);
throw error;
}
}
private parseHtmlSubjects(html: string): any[] {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const rows = Array.from(doc.querySelectorAll('tr'));
return rows.map(row => {
const cols = row.querySelectorAll('td');
if (cols.length >= 6) {
const nombreRaw = cols[0].textContent?.trim() || '';
return {
nombre: nombreRaw.split(' (')[0],
nota: cols[4].textContent?.trim(),
origen: cols[5].textContent?.trim()
};
}
return null;
}).filter(item => item && item.nombre);
}
}