From 705c200f1ba268812ec88fd063c4be80ff1ed9d9 Mon Sep 17 00:00:00 2001 From: Sergio Nicolas Lavaise Date: Fri, 24 Apr 2026 10:50:05 -0300 Subject: [PATCH] Migrate to MariaDB backend and fix bed management - Switch from SQLite to MariaDB for database backend - Fix bed state updates on admission (iniciarInternacion) - Fix bed updates on admission edit (actualizarInternacion) - Fix bed updates on discharge (finalizarInternacion): delete if fuera de area, mark available otherwise - Fix NuevoIngreso to wait for cama creation before creating internacion - Fix patient undefined check in Cultivos - Update CORS to allow all origins - Remove unused files --- package-lock.json | 56 +- package.json | 1 + server/api-mariadb.js | 5 +- server/db-mariadb.js | 4 +- server/package-lock.json | 477 ++------- server/package.json | 3 +- src/App.tsx | 2 +- src/hooks/useHospitalStore-mariadb.ts | 1277 ------------------------- src/hooks/useHospitalStore.ts | 906 +++++++++++------- src/sections/Cultivos.tsx | 2 +- src/sections/EditIngreso.tsx | 2 +- src/sections/MapaCamas.tsx | 6 +- src/sections/NuevoIngreso.tsx | 25 +- 13 files changed, 694 insertions(+), 2072 deletions(-) delete mode 100644 src/hooks/useHospitalStore-mariadb.ts diff --git a/package-lock.json b/package-lock.json index d5c14b7..d27e227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "input-otp": "^1.4.2", "jspdf": "^4.2.1", "lucide-react": "^0.562.0", + "mariadb": "^3.5.2", "next-themes": "^0.4.6", "pdf-lib": "^1.17.1", "react": "^19.2.0", @@ -4815,6 +4816,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4826,7 +4833,6 @@ "version": "24.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -6321,6 +6327,15 @@ "license": "MIT", "optional": true }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7977,6 +7992,44 @@ "license": "ISC", "optional": true }, + "node_modules/mariadb": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.5.2.tgz", + "integrity": "sha512-9rztrI4nouxAY/82a+RlzzZ5ie2vxu2eYclkBvTy1ATXH1B9cnvZ0O71Pzsy/mlfDb5P3HhOg0JzQKkDRhctyA==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": ">=18", + "denque": "^2.1.0", + "iconv-lite": "^0.7.2", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mariadb/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10340,7 +10393,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 9ec1149..6682c1d 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "input-otp": "^1.4.2", "jspdf": "^4.2.1", "lucide-react": "^0.562.0", + "mariadb": "^3.5.2", "next-themes": "^0.4.6", "pdf-lib": "^1.17.1", "react": "^19.2.0", diff --git a/server/api-mariadb.js b/server/api-mariadb.js index 67b448d..0449dc3 100644 --- a/server/api-mariadb.js +++ b/server/api-mariadb.js @@ -19,7 +19,10 @@ import { initDb, } from './db-mariadb.js'; const app = express(); -app.use(cors()); +app.use(cors({ + origin: true, + credentials: true +})); app.use(express.json({ limit: '50mb' })); // UUID generator diff --git a/server/db-mariadb.js b/server/db-mariadb.js index c20570c..8326297 100644 --- a/server/db-mariadb.js +++ b/server/db-mariadb.js @@ -1,5 +1,5 @@ -import mariadb from 'mariadb'; -import bcrypt from 'bcryptjs'; +import * as mariadb from 'mariadb'; +import * as bcrypt from 'bcryptjs'; // MariaDB connection pool const pool = mariadb.createPool({ diff --git a/server/package-lock.json b/server/package-lock.json index 1d80b17..3999ec5 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -8,12 +8,19 @@ "name": "hospital-server", "version": "1.0.0", "dependencies": { - "better-sqlite3": "^11.7.0", + "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.21.2", - "http-proxy-middleware": "^3.0.5" + "http-proxy-middleware": "^3.0.5", + "mariadb": "^3.4.0" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/http-proxy": { "version": "1.17.17", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", @@ -51,57 +58,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, - "node_modules/better-sqlite3": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", - "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", @@ -138,30 +100,6 @@ "node": ">=8" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -200,12 +138,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -268,28 +200,13 @@ "ms": "2.0.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" + "node": ">=0.10" } }, "node_modules/depd": { @@ -311,15 +228,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -349,15 +257,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -409,15 +308,6 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -464,12 +354,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -538,12 +422,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -590,12 +468,6 @@ "node": ">= 0.4" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -718,38 +590,12 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -798,6 +644,44 @@ "node": ">=0.10.0" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/mariadb": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.5.2.tgz", + "integrity": "sha512-9rztrI4nouxAY/82a+RlzzZ5ie2vxu2eYclkBvTy1ATXH1B9cnvZ0O71Pzsy/mlfDb5P3HhOg0JzQKkDRhctyA==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": ">=18", + "denque": "^2.1.0", + "iconv-lite": "^0.7.2", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -880,45 +764,12 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -928,18 +779,6 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -973,15 +812,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1009,33 +839,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1049,16 +852,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/qs": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", @@ -1098,35 +891,6 @@ "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -1159,18 +923,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/send": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", @@ -1294,51 +1046,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1348,52 +1055,6 @@ "node": ">= 0.8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1415,18 +1076,6 @@ "node": ">=0.6" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1455,12 +1104,6 @@ "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1478,12 +1121,6 @@ "engines": { "node": ">= 0.8" } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" } } } diff --git a/server/package.json b/server/package.json index 87b577d..d9a6489 100644 --- a/server/package.json +++ b/server/package.json @@ -10,7 +10,6 @@ "cors": "^2.8.5", "express": "^4.21.2", "http-proxy-middleware": "^3.0.5", - "sqlite": "^5.1.7", - "sqlite3": "^5.1.7" + "mariadb": "^3.4.0" } } diff --git a/src/App.tsx b/src/App.tsx index 4b909f2..778c680 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,7 +52,7 @@ function AppContent() { pacientes={store.pacientes} internaciones={store.internaciones} onActualizarCama={store.actualizarCama} - onAgregarCama={store.agregarCama} + onAgregarCama={store.agregarCama as any} onEliminarCama={store.eliminarCama} onIniciarInternacion={store.iniciarInternacion} getPacienteById={store.getPacienteById} diff --git a/src/hooks/useHospitalStore-mariadb.ts b/src/hooks/useHospitalStore-mariadb.ts deleted file mode 100644 index 2af8782..0000000 --- a/src/hooks/useHospitalStore-mariadb.ts +++ /dev/null @@ -1,1277 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; -import type { - Paciente, - Cama, - Area, - Internacion, - Evolucion, - Laboratorio, - AcidoBase, - Cultivo, - EstudioComplementario, - Interconsulta, - ATB, - Indicacion, - Vista, - Usuario, - RolUsuario -} from '@/types'; - -// Fallback UUID generator for non-secure contexts (HTTP without localhost) -function generateUUID(): string { - if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID(); - } - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - const v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); -} - - -interface HospitalState { - pacientes: Paciente[]; - camas: Cama[]; - areas: Area[]; - internaciones: Internacion[]; - evoluciones: Evolucion[]; - laboratorios: Laboratorio[]; - acidosBase: AcidoBase[]; - cultivos: Cultivo[]; - estudiosComplementarios: EstudioComplementario[]; - interconsultas: Interconsulta[]; - atb: ATB[]; - indicaciones: Indicacion[]; - movimientosIndicaciones: any[]; - vistaActual: Vista; - currentInternacionId?: string | null; - usuarios: Usuario[]; - currentUser: Usuario | null; - isAuthenticated: boolean; -} - - -const API_BASE = 'http://localhost:4000/api'; - -const defaultState = (): HospitalState => ({ - pacientes: [], - areas: [], - internaciones: [], - evoluciones: [], - laboratorios: [], - acidosBase: [], - cultivos: [], - estudiosComplementarios: [], - interconsultas: [], - atb: [], - vistaActual: 'dashboard', - indicaciones: [], - movimientosIndicaciones: [], - camas: [], - usuarios: [], - currentUser: null, - isAuthenticated: false, -}); - -export function useHospitalStore() { - const [state, setState] = useState(defaultState); - const [isLoaded, setIsLoaded] = useState(false); - - // Load state from backend on mount - useEffect(() => { - let mounted = true; - (async () => { - try { - const res = await fetch(`${API_BASE}/state`); - if (!res.ok) throw new Error('no state'); - const body = await res.json(); - if (mounted && body) { - const defaults = defaultState(); - const storedUser = sessionStorage.getItem('hospital_user'); - const currentUser = storedUser ? JSON.parse(storedUser) : null; - const normalized = { - ...defaults, - ...body, - estudiosComplementarios: body.estudiosComplementarios || [], - interconsultas: body.interconsultas || [], - atb: body.atb || [], - movimientosIndicaciones: body.movimientosIndicaciones || [], - currentUser, - isAuthenticated: !!currentUser, - }; - setState(normalized); - } - } catch (err) { - // no remote state or unreachable — keep defaults - } finally { - if (mounted) setIsLoaded(true); - } - })(); - return () => { mounted = false; }; - }, []); - - // Helper to make API calls with error handling - const apiCall = useCallback(async (method: string, endpoint: string, data?: any) => { - try { - const res = await fetch(`${API_BASE}${endpoint}`, { - method, - headers: { 'Content-Type': 'application/json' }, - body: data ? JSON.stringify(data) : undefined, - }); - if (!res.ok) throw new Error(`API error: ${res.statusText}`); - return await res.json().catch(() => ({ ok: true })); - } catch (err) { - console.error(`API call failed: ${method} ${endpoint}`, err); - throw err; - } - }, []); - - // Also save on特定 events (optional) - const queueSave = (keys: string[]) => { - keys.forEach(markChanged); - }; - - // Utility functions - const getCamaAreaId = useCallback((camaId: string): string | null => { - const cama = state.camas.find(c => c.id === camaId); - return cama?.areaId || null; - }, [state.camas]); - - const getInternacionAreaId = useCallback((internacionId: string): string | null => { - const internacion = state.internaciones.find(i => i.id === internacionId); - return internacion?.areaId || null; - }, [state.internaciones]); - - const getPacienteAreaId = useCallback((pacienteId: string): string | null => { - const internacion = state.internaciones.find(i => i.pacienteId === pacienteId && i.activa); - if (!internacion) { - const anyInternacion = state.internaciones.find(i => i.pacienteId === pacienteId); - if (anyInternacion) return getCamaAreaId(anyInternacion.camaId); - return null; - } - return getCamaAreaId(internacion.camaId); - }, [state.internaciones, getCamaAreaId]); - - // Permission functions - const canAccessArea = useCallback((areaId: string | null | undefined): boolean => { - const user = state.currentUser; - if (!user) return false; - if (user.rol === 'admin') return true; - if (!areaId) return false; - return user.areaId === areaId; - }, [state.currentUser]); - - const canChangeBed = useCallback((internacionId: string): boolean => { - const internacion = state.internaciones.find(i => i.id === internacionId); - if (!internacion) return false; - return canAccessArea(internacion.areaId); - }, [state.internaciones, canAccessArea]); - - const canEditInternacion = useCallback((internacionId: string): boolean => { - const areaId = getInternacionAreaId(internacionId); - return canAccessArea(areaId); - }, [getInternacionAreaId, canAccessArea]); - - const canEditCama = useCallback((camaId: string): boolean => { - const areaId = getCamaAreaId(camaId); - return canAccessArea(areaId); - }, [getCamaAreaId, canAccessArea]); - - const canEditPaciente = useCallback((pacienteId: string): boolean => { - const areaId = getPacienteAreaId(pacienteId); - return canAccessArea(areaId); - }, [getPacienteAreaId, canAccessArea]); - - const hasPermission = useCallback((permission: 'read' | 'write', section: string): boolean => { - const user = state.currentUser; - if (!user) return false; - if (user.rol === 'admin') return true; - if (user.rol === 'enfermero') { - if (section === 'glucemias' || section === 'signosvitales') return permission === 'read' || permission === 'write'; - return permission === 'read'; - } - if (user.rol === 'medico') { - if (section === 'miArea') return permission === 'read' || permission === 'write'; - return permission === 'read'; - } - return false; - }, [state.currentUser]); - - // Acciones de navegación - const setVista = useCallback((vista: Vista) => { - setState(prev => ({ ...prev, vistaActual: vista })); - }, []); - - // Acciones de pacientes - const agregarPaciente = useCallback(async (paciente: Omit) => { - const nuevoPaciente: Paciente = { - ...paciente, - id: generateUUID(), - fechaRegistro: new Date().toISOString().split('T')[0], - }; - try { - await apiCall('POST', '/pacientes', nuevoPaciente); - setState(prev => ({ - ...prev, - pacientes: [...prev.pacientes, nuevoPaciente], - })); - return nuevoPaciente.id; - } catch (err) { - console.error('Error al agregar paciente:', err); - throw err; - } - }, [apiCall]); - - const actualizarPaciente = useCallback(async (id: string, datos: Partial) => { - try { - await apiCall('PUT', `/pacientes/${id}`, datos); - setState(prev => ({ - ...prev, - pacientes: prev.pacientes.map(p => p.id === id ? { ...p, ...datos } : p), - })); - } catch (err) { - console.error('Error al actualizar paciente:', err); - throw err; - } - }, [apiCall]); - - const eliminarPaciente = useCallback(async (id: string) => { - try { - await apiCall('DELETE', `/pacientes/${id}`); - setState(prev => ({ - ...prev, - pacientes: prev.pacientes.filter(p => p.id !== id), - })); - } catch (err) { - console.error('Error al eliminar paciente:', err); - throw err; - } - }, [apiCall]); - - // Acciones de camas - const actualizarCama = useCallback(async (id: string, datos: Partial) => { - const cama = state.camas.find(c => c.id === id); - if (!cama) return; - const user = state.currentUser; - const areaId = cama.areaId || null; - if (user && user.rol !== 'admin' && areaId && user.areaId !== areaId) { - console.warn('No tiene permisos para actualizar cama en esta área'); - return; - } - try { - await apiCall('PUT', `/camas/${id}`, datos); - setState(prev => ({ - ...prev, - camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c), - })); - } catch (err) { - console.error('Error al actualizar cama:', err); - throw err; - } - }, [state.camas, state.currentUser, apiCall]); - - const agregarCama = useCallback(async (cama: Omit) => { - const nuevaCama: Cama = { - ...cama, - id: generateUUID(), - } as Cama; - try { - await apiCall('POST', '/camas', nuevaCama); - setState(prev => ({ - ...prev, - camas: [...prev.camas, nuevaCama], - })); - return nuevaCama.id; - } catch (err) { - console.error('Error al agregar cama:', err); - throw err; - } - }, [apiCall]); - - const eliminarCama = useCallback(async (id: string) => { - try { - await apiCall('DELETE', `/camas/${id}`); - setState(prev => ({ - ...prev, - camas: prev.camas.filter(c => c.id !== id), - internaciones: prev.internaciones.map(i => i.camaId === id ? { ...i, activa: false } : i), - })); - } catch (err) { - console.error('Error al eliminar cama:', err); - throw err; - } - }, [apiCall]); - - // Acciones de internaciones - const iniciarInternacion = useCallback(async (internacion: Omit) => { - const nuevaInternacion: Internacion = { - ...internacion, - id: generateUUID(), - activa: true, - }; - - try { - // API call inmediata - await apiCall('POST', '/internaciones', nuevaInternacion); - - // Actualizar estado local después del éxito - setState(prev => ({ - ...prev, - internaciones: [...prev.internaciones, nuevaInternacion], - camas: prev.camas.map(c => - c.id === internacion.camaId - ? { ...c, estado: 'Ocupada' as const, pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id } - : c - ), - })); - - return nuevaInternacion.id; - } catch (err) { - console.error('Error al crear internación:', err); - throw err; - } - }, [apiCall]); - - const finalizarInternacion = useCallback(async (internacionId: string, datos: { - fechaEgreso: string; - diagnosticoEgreso: string; - motivoEgreso: Internacion['motivoEgreso'] - }) => { - const internacion = state.internaciones.find(i => i.id === internacionId); - if (!internacion) return; - - const areaFueraDeArea = state.areas.find(a => (a.nombre || '').trim().toLowerCase() === 'fuera de area'); - const areaFueraDeAreaId = areaFueraDeArea?.id; - const esFueraDeArea = internacion.areaId === areaFueraDeAreaId; - - try { - // API call inmediata - await apiCall('PUT', `/internaciones/${internacionId}`, { ...datos, activa: false }); - - // Actualizar estado local después del éxito - setState(prev => { - return { - ...prev, - internaciones: prev.internaciones.map(i => - i.id === internacionId - ? { ...i, ...datos, activa: false } - : i - ), - camas: esFueraDeArea - ? prev.camas.filter(c => c.id !== internacion.camaId) - : prev.camas.map(c => - c.id === internacion.camaId - ? { ...c, estado: 'Disponible', pacienteId: undefined, internacionId: undefined } - : c - ), - }; - }); - } catch (err) { - console.error('Error al finalizar internación:', err); - throw err; - } - }, [state.areas, apiCall]); - - const actualizarInternacion = useCallback(async (internacionId: string, datos: Partial) => { - const internacion = state.internaciones.find(i => i.id === internacionId); - if (!internacion) return; - - const oldCamaId = internacion.camaId; - const newCamaId = datos.camaId; - const cambioDeCama = newCamaId && newCamaId !== oldCamaId; - - if (cambioDeCama && !canChangeBed(internacionId)) { - console.warn('No tiene permisos para cambiar la cama de esta internación'); - return; - } - - if (!cambioDeCama && !canAccessArea(internacion.areaId)) { - console.warn('No tiene permisos para actualizar esta internación'); - return; - } - - const pacienteId = datos.pacienteId || internacion.pacienteId; - - try { - // API call inmediata - await apiCall('PUT', `/internaciones/${internacionId}`, datos); - - // Actualizar estado local después del éxito - setState(prev => { - const result = { - ...prev, - internaciones: prev.internaciones.map(i => - i.id === internacionId - ? { ...i, ...datos } - : i - ), - camas: prev.camas.map(c => { - if (c.id === newCamaId && newCamaId !== oldCamaId) { - return { ...c, estado: 'Ocupada' as const, pacienteId, internacionId }; - } - if (c.id === oldCamaId && oldCamaId !== newCamaId) { - return { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined }; - } - return c; - }), - }; - return result; - }); - } catch (err) { - console.error('Error al actualizar internación:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de evoluciones - const agregarEvolucion = useCallback(async (evolucion: Omit) => { - const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar evolución en esta área'); - return null; - } - const nuevaEvolucion: Evolucion = { - ...evolucion, - id: generateUUID(), - }; - - try { - await apiCall('POST', '/evoluciones', nuevaEvolucion); - setState(prev => ({ - ...prev, - evoluciones: [...prev.evoluciones, nuevaEvolucion], - })); - return nuevaEvolucion.id; - } catch (err) { - console.error('Error al agregar evolución:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarEvolucion = useCallback(async (id: string) => { - const evolucion = state.evoluciones.find(e => e.id === id); - if (!evolucion) return; - const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar evolución en esta área'); - return; - } - - try { - await apiCall('DELETE', `/evoluciones/${id}`); - setState(prev => ({ - ...prev, - evoluciones: prev.evoluciones.filter(e => e.id !== id), - })); - } catch (err) { - console.error('Error al eliminar evolución:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarEvolucion = useCallback(async (id: string, datos: Partial) => { - const evolucion = state.evoluciones.find(e => e.id === id); - if (!evolucion) return; - const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar evolución en esta área'); - return; - } - - try { - await apiCall('PUT', `/evoluciones/${id}`, datos); - setState(prev => ({ - ...prev, - evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e), - })); - } catch (err) { - console.error('Error al actualizar evolución:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de laboratorios - const agregarLaboratorio = useCallback(async (laboratorio: Omit) => { - const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar laboratorio en esta área'); - return null; - } - const nuevoLaboratorio: Laboratorio = { - ...laboratorio, - id: generateUUID(), - }; - try { - await apiCall('POST', '/laboratorios', nuevoLaboratorio); - setState(prev => ({ - ...prev, - laboratorios: [...prev.laboratorios, nuevoLaboratorio], - })); - return nuevoLaboratorio.id; - } catch (err) { - console.error('Error al agregar laboratorio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarLaboratorio = useCallback(async (id: string) => { - const laboratorio = state.laboratorios.find(l => l.id === id); - if (!laboratorio) return; - const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar laboratorio en esta área'); - return; - } - try { - await apiCall('DELETE', `/laboratorios/${id}`); - setState(prev => ({ - ...prev, - laboratorios: prev.laboratorios.filter(l => l.id !== id), - })); - } catch (err) { - console.error('Error al eliminar laboratorio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarLaboratorio = useCallback(async (id: string, datos: Partial) => { - const laboratorio = state.laboratorios.find(l => l.id === id); - if (!laboratorio) return; - const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar laboratorio en esta área'); - return; - } - try { - await apiCall('PUT', `/laboratorios/${id}`, datos); - setState(prev => ({ - ...prev, - laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l), - })); - } catch (err) { - console.error('Error al actualizar laboratorio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de ácido-base - const agregarAcidoBase = useCallback(async (acidoBase: Omit) => { - const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar ácido-base en esta área'); - return null; - } - const nuevoAcidoBase: AcidoBase = { - ...acidoBase, - id: generateUUID(), - }; - try { - await apiCall('POST', '/acid-os-base', nuevoAcidoBase); - setState(prev => ({ - ...prev, - acidosBase: [...prev.acidosBase, nuevoAcidoBase], - })); - return nuevoAcidoBase.id; - } catch (err) { - console.error('Error al agregar ácido-base:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarAcidoBase = useCallback(async (id: string, datos: Partial) => { - const acidoBase = state.acidosBase.find(a => a.id === id); - if (!acidoBase) return; - const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar ácido-base en esta área'); - return; - } - try { - await apiCall('PUT', `/acid-os-base/${id}`, datos); - setState(prev => ({ - ...prev, - acidosBase: prev.acidosBase.map(a => a.id === id ? { ...a, ...datos } : a), - })); - } catch (err) { - console.error('Error al actualizar ácido-base:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarAcidoBase = useCallback(async (id: string) => { - const acidoBase = state.acidosBase.find(a => a.id === id); - if (!acidoBase) return; - const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar ácido-base en esta área'); - return; - } - try { - await apiCall('DELETE', `/acid-os-base/${id}`); - setState(prev => ({ - ...prev, - acidosBase: prev.acidosBase.filter(a => a.id !== id), - })); - } catch (err) { - console.error('Error al eliminar ácido-base:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de cultivos - const agregarCultivo = useCallback(async (cultivo: Omit) => { - const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar cultivo en esta área'); - return null; - } - const nuevoCultivo: Cultivo = { - ...cultivo, - id: generateUUID(), - }; - try { - await apiCall('POST', '/cultivos', nuevoCultivo); - setState(prev => ({ - ...prev, - cultivos: [...prev.cultivos, nuevoCultivo], - })); - return nuevoCultivo.id; - } catch (err) { - console.error('Error al agregar cultivo:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarCultivo = useCallback(async (id: string, datos: Partial) => { - const cultivo = state.cultivos.find(c => c.id === id); - if (!cultivo) return; - const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar cultivo en esta área'); - return; - } - try { - await apiCall('PUT', `/cultivos/${id}`, datos); - setState(prev => ({ - ...prev, - cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c), - })); - } catch (err) { - console.error('Error al actualizar cultivo:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarCultivo = useCallback(async (id: string) => { - const cultivo = state.cultivos.find(c => c.id === id); - if (!cultivo) return; - const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar cultivo en esta área'); - return; - } - try { - await apiCall('DELETE', `/cultivos/${id}`); - setState(prev => ({ - ...prev, - cultivos: prev.cultivos.filter(c => c.id !== id), - })); - } catch (err) { - console.error('Error al eliminar cultivo:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de estudios complementarios - const agregarEstudioComplementario = useCallback(async (estudio: Omit) => { - const internacion = state.internaciones.find(i => i.id === estudio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar estudio complementario en esta área'); - return null; - } - const nuevoEstudio: EstudioComplementario = { - ...estudio, - id: generateUUID(), - }; - try { - await apiCall('POST', '/estudios-complementarios', nuevoEstudio); - setState(prev => ({ - ...prev, - estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio], - })); - return nuevoEstudio.id; - } catch (err) { - console.error('Error al agregar estudio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarEstudioComplementario = useCallback(async (id: string, datos: Partial) => { - const estudio = state.estudiosComplementarios.find(e => e.id === id); - if (!estudio) return; - const internacion = state.internaciones.find(i => i.id === estudio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar estudio complementario en esta área'); - return; - } - try { - await apiCall('PUT', `/estudios-complementarios/${id}`, datos); - setState(prev => ({ - ...prev, - estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e), - })); - } catch (err) { - console.error('Error al actualizar estudio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarEstudioComplementario = useCallback(async (id: string) => { - const estudio = state.estudiosComplementarios.find(e => e.id === id); - if (!estudio) return; - const internacion = state.internaciones.find(i => i.id === estudio.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar estudio complementario en esta área'); - return; - } - try { - await apiCall('DELETE', `/estudios-complementarios/${id}`); - setState(prev => ({ - ...prev, - estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id), - })); - } catch (err) { - console.error('Error al eliminar estudio:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - // Acciones de interconsultas - const agregarInterconsulta = useCallback(async (interconsulta: Omit) => { - const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar interconsulta en esta área'); - return null; - } - const nuevaInterconsulta: Interconsulta = { ...interconsulta, id: generateUUID() }; - try { - await apiCall('POST', '/interconsultas', nuevaInterconsulta); - setState(prev => ({ - ...prev, - interconsultas: [...(prev.interconsultas || []), nuevaInterconsulta], - })); - return nuevaInterconsulta.id; - } catch (err) { - console.error('Error al agregar interconsulta:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarInterconsulta = useCallback(async (id: string, datos: Partial) => { - const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); - if (!interconsulta) return; - const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar interconsulta en esta área'); - return; - } - try { - await apiCall('PUT', `/interconsultas/${id}`, datos); - setState(prev => ({ - ...prev, - interconsultas: (prev.interconsultas || []).map(ic => ic.id === id ? { ...ic, ...datos } : ic), - })); - } catch (err) { - console.error('Error al actualizar interconsulta:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarInterconsulta = useCallback(async (id: string) => { - const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); - if (!interconsulta) return; - const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar interconsulta en esta área'); - return; - } - try { - await apiCall('DELETE', `/interconsultas/${id}`); - setState(prev => ({ - ...prev, - interconsultas: (prev.interconsultas || []).filter(ic => ic.id !== id), - })); - } catch (err) { - console.error('Error al eliminar interconsulta:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const agregarATB = useCallback(async (atb: Omit) => { - const internacion = state.internaciones.find(i => i.id === atb.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar ATB en esta área'); - return null; - } - const nuevoATB: ATB = { ...atb, id: generateUUID() }; - try { - await apiCall('POST', '/atb', nuevoATB); - setState(prev => ({ - ...prev, - atb: [...prev.atb, nuevoATB], - })); - return nuevoATB.id; - } catch (err) { - console.error('Error al agregar ATB:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarATB = useCallback(async (id: string, datos: Partial) => { - const atb = state.atb.find(a => a.id === id); - if (!atb) return; - const internacion = state.internaciones.find(i => i.id === atb.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar ATB en esta área'); - return; - } - try { - await apiCall('PUT', `/atb/${id}`, datos); - setState(prev => ({ - ...prev, - atb: prev.atb.map(a => a.id === id ? { ...a, ...datos } : a), - })); - } catch (err) { - console.error('Error al actualizar ATB:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarATB = useCallback(async (id: string) => { - const atb = state.atb.find(a => a.id === id); - if (!atb) return; - const internacion = state.internaciones.find(i => i.id === atb.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar ATB en esta área'); - return; - } - try { - await apiCall('DELETE', `/atb/${id}`); - setState(prev => ({ - ...prev, - atb: prev.atb.filter(a => a.id !== id), - })); - } catch (err) { - console.error('Error al eliminar ATB:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const agregarIndicacion = useCallback(async (indicacion: Omit) => { - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar indicación en esta área'); - return null; - } - const nuevaIndicacion: Indicacion = { ...indicacion, id: generateUUID() }; - try { - await apiCall('POST', '/indicaciones', nuevaIndicacion); - setState(prev => ({ - ...prev, - indicaciones: [...prev.indicaciones, nuevaIndicacion], - })); - return nuevaIndicacion.id; - } catch (err) { - console.error('Error al agregar indicación:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const actualizarIndicacion = useCallback(async (id: string, datos: Partial) => { - const indicacion = state.indicaciones.find(i => i.id === id); - if (!indicacion) return; - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar indicación en esta área'); - return; - } - try { - await apiCall('PUT', `/indicaciones/${id}`, datos); - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.map(i => i.id === id ? { ...i, ...datos } : i), - })); - } catch (err) { - console.error('Error al actualizar indicación:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarIndicacion = useCallback(async (id: string) => { - const indicacion = state.indicaciones.find(i => i.id === id); - if (!indicacion) return; - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar indicación en esta área'); - return; - } - try { - await apiCall('DELETE', `/indicaciones/${id}`); - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.filter(i => i.id !== id), - })); - } catch (err) { - console.error('Error al eliminar indicación:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const eliminarATB = useCallback((id: string) => { - const atb = state.atb.find(a => a.id === id); - if (!atb) return; - const internacion = state.internaciones.find(i => i.id === atb.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar ATB en esta área'); - return; - } - setState(prev => ({ - ...prev, - atb: prev.atb.filter(a => a.id !== id), - })); - }, [state.internaciones, state.currentUser]); - - const agregarIndicacion = useCallback((indicacion: Omit) => { - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar indicación en esta área'); - return null; - } - const nuevaIndicacion: Indicacion = { ...indicacion, id: generateUUID() }; - setState(prev => ({ - ...prev, - indicaciones: [...prev.indicaciones, nuevaIndicacion], - })); - return nuevaIndicacion.id; - }, [state.internaciones, state.currentUser]); - - const actualizarIndicacion = useCallback((id: string, datos: Partial) => { - const indicacion = state.indicaciones.find(i => i.id === id); - if (!indicacion) return; - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para actualizar indicación en esta área'); - return; - } - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.map(i => i.id === id ? { ...i, ...datos } : i), - })); - }, [state.internaciones, state.currentUser]); - - const eliminarIndicacion = useCallback((id: string) => { - const indicacion = state.indicaciones.find(i => i.id === id); - if (!indicacion) return; - const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para eliminar indicación en esta área'); - return; - } - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.filter(i => i.id !== id), - })); - }, [state.internaciones, state.currentUser]); - - const agregarMovimientoIndicacion = useCallback(async (movimiento: any) => { - const internacion = state.internaciones.find(i => i.id === movimiento.internacionId); - const areaId = internacion?.areaId || null; - if (!canAccessArea(areaId)) { - console.warn('No tiene permisos para agregar movimiento de indicación en esta área'); - return; - } - const nuevoMovimiento = { ...movimiento, id: generateUUID() }; - try { - await apiCall('POST', '/movimientos-indicaciones', nuevoMovimiento); - setState(prev => ({ - ...prev, - movimientosIndicaciones: [...(prev.movimientosIndicaciones || []), nuevoMovimiento], - })); - } catch (err) { - console.error('Error al agregar movimiento:', err); - throw err; - } - }, [state.internaciones, state.currentUser, apiCall]); - - const getMovimientosByInternacion = useCallback((internacionId: string) => { - return (state.movimientosIndicaciones || []) - .filter((m: any) => m.internacionId === internacionId) - .sort((a: any, b: any) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); - }, [state.movimientosIndicaciones]); - - // Acciones de areas - const agregarArea = useCallback(async (area: Omit) => { - const nuevaArea: Area = { ...area, id: generateUUID() }; - try { - await apiCall('POST', '/areas', nuevaArea); - setState(prev => ({ - ...prev, - areas: [...prev.areas, nuevaArea], - })); - return nuevaArea.id; - } catch (err) { - console.error('Error al agregar área:', err); - throw err; - } - }, [apiCall]); - - const actualizarArea = useCallback(async (id: string, datos: Partial) => { - try { - await apiCall('PUT', `/areas/${id}`, datos); - setState(prev => ({ - ...prev, - areas: prev.areas.map(a => a.id === id ? { ...a, ...datos } : a), - })); - } catch (err) { - console.error('Error al actualizar área:', err); - throw err; - } - }, [apiCall]); - - const eliminarArea = useCallback(async (id: string) => { - try { - await apiCall('DELETE', `/areas/${id}`); - setState(prev => ({ - ...prev, - areas: prev.areas.filter(a => a.id !== id), - camas: prev.camas.map(c => c.areaId === id ? { ...c, areaId: undefined } : c), - })); - } catch (err) { - console.error('Error al eliminar área:', err); - throw err; - } - }, [apiCall]); - - // Funciones de utilidad para consultas - const getPacienteById = useCallback((id: string) => { - return state.pacientes.find(p => p.id === id); - }, [state.pacientes]); - - const getCamaById = useCallback((id: string) => { - return state.camas.find(c => c.id === id); - }, [state.camas]); - - const getInternacionById = useCallback((id: string) => { - return state.internaciones.find(i => i.id === id); - }, [state.internaciones]); - - const getInternacionActivaByPaciente = useCallback((pacienteId: string) => { - return state.internaciones.find(i => i.pacienteId === pacienteId && i.activa); - }, [state.internaciones]); - - const getEvolucionesByInternacion = useCallback((internacionId: string) => { - return state.evoluciones - .filter(e => e.internacionId === internacionId) - .sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); - }, [state.evoluciones]); - - const getLaboratoriosByPaciente = useCallback((pacienteId: string) => { - return state.laboratorios - .filter(l => l.pacienteId === pacienteId) - .sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); - }, [state.laboratorios]); - - const getAcidosBaseByPaciente = useCallback((pacienteId: string) => { - return state.acidosBase - .filter(a => a.pacienteId === pacienteId) - .sort((a, b) => new Date(b.fecha + 'T' + b.hora).getTime() - new Date(a.fecha + 'T' + a.hora).getTime()); - }, [state.acidosBase]); - - const getCultivosByPaciente = useCallback((pacienteId: string) => { - return state.cultivos - .filter(c => c.pacienteId === pacienteId) - .sort((a, b) => new Date(b.fechaToma).getTime() - new Date(a.fechaToma).getTime()); - }, [state.cultivos]); - - const getEstadisticas = useCallback(() => { - const fueraDeAreaList = state.camas.filter(c => c.areaId === 'Fuera de Area'); - const camasActivas = state.camas.filter(c => c.areaId !== 'Fuera de Area'); - const camaPrincipal = state.camas.filter(c => c.areaId !== 'Fuera de Area'); - const camaOcupadas = state.camas.filter(c => c.estado === 'Ocupada' && c.areaId !== 'Fuera de Area').length; - const camasDisponibles = state.camas.filter(c => c.estado === 'Disponible' && c.areaId !== 'Fuera de Area').length; - const camasMantenimiento = state.camas.filter(c => c.estado === 'Reparacion').length; - const internacionesActivas = state.internaciones.filter(i => i.activa).length; - const totalPacientes = state.pacientes.length; - const cultivosPendientes = state.cultivos.filter(c => c.estado === 'NAF/Pendiente').length; - - return { - camaOcupadas, - camasOcupadas: camaOcupadas, - camasDisponibles, - camasMantenimiento, - totalCamas: state.camas.length, - totalCamasActivas: camasActivas.length, - camasFueraDeArea: fueraDeAreaList.length, - internacionesActivas, - totalPacientes, - cultivosPendientes, - porcentajeOcupacion: camaPrincipal.length > 0 ? Math.round((camaOcupadas / camaPrincipal.length) * 100) : 0, - }; - }, [state]); - - // Current internacion selection (for HC page) - const setCurrentInternacion = useCallback((id?: string | null) => { - setState(prev => ({ ...prev, currentInternacionId: id ?? null })); - }, []); - - // Auth functions - const login = useCallback(async (dni: string, password: string) => { - try { - const res = await fetch(`${API_BASE}/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dni, password }), - }); - if (!res.ok) { - const err = await res.json(); - throw new Error(err.error || 'Error en autenticación'); - } - const user = await res.json(); - sessionStorage.setItem('hospital_user', JSON.stringify(user)); - setState(prev => ({ ...prev, currentUser: user, isAuthenticated: true })); - return user; - } catch (err) { - throw err; - } - }, []); - - const logout = useCallback(() => { - sessionStorage.removeItem('hospital_user'); - setState(prev => { - const newState = { ...prev, currentUser: null, isAuthenticated: false }; - return newState; - }); - }, []); - - const changePassword = useCallback(async (dni: string, oldPassword: string, newPassword: string) => { - const res = await fetch(`${API_BASE}/auth/change-password`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dni, oldPassword, newPassword }), - }); - if (!res.ok) { - const err = await res.json(); - throw new Error(err.error || 'Error al cambiar contraseña'); - } - return true; - }, []); - - const updateEmail = useCallback(async (dni: string, newEmail: string) => { - const res = await fetch(`${API_BASE}/auth/update-email`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dni, newEmail }), - }); - if (!res.ok) { - const err = await res.json(); - throw new Error(err.error || 'Error al actualizar email'); - } - return true; - }, []); - - return { - ...state, - isLoaded, - setVista, - agregarPaciente, - actualizarPaciente, - eliminarPaciente, - actualizarCama, - iniciarInternacion, - finalizarInternacion, - actualizarInternacion, - agregarCama, - eliminarCama, - agregarEvolucion, - actualizarEvolucion, - eliminarEvolucion, - agregarLaboratorio, - actualizarLaboratorio, - eliminarLaboratorio, - agregarAcidoBase, - actualizarAcidoBase, - eliminarAcidoBase, - agregarCultivo, - actualizarCultivo, - eliminarCultivo, - agregarEstudioComplementario, - actualizarEstudioComplementario, - eliminarEstudioComplementario, - agregarInterconsulta, - actualizarInterconsulta, - eliminarInterconsulta, - agregarATB, - actualizarATB, - eliminarATB, - agregarIndicacion, - actualizarIndicacion, - eliminarIndicacion, - agregarMovimientoIndicacion, - getMovimientosByInternacion, - agregarArea, - actualizarArea, - eliminarArea, - getPacienteById, - getCamaById, - getInternacionById, - getInternacionActivaByPaciente, - getEvolucionesByInternacion, - getLaboratoriosByPaciente, - getAcidosBaseByPaciente, - getCultivosByPaciente, - setCurrentInternacion, - getEstadisticas, - login, - logout, - changePassword, - updateEmail, - hasPermission, - canAccessArea, - canChangeBed, - canEditInternacion, - canEditCama, - canEditPaciente, - getCamaAreaId, - getInternacionAreaId, - getPacienteAreaId, - }; -} diff --git a/src/hooks/useHospitalStore.ts b/src/hooks/useHospitalStore.ts index 9bfaa8b..ce74723 100644 --- a/src/hooks/useHospitalStore.ts +++ b/src/hooks/useHospitalStore.ts @@ -52,7 +52,7 @@ interface HospitalState { } -const API_BASE = 'http://localhost:4001/api'; +const API_BASE = 'http://localhost:4000/api'; const defaultState = (): HospitalState => ({ pacientes: [], @@ -111,61 +111,25 @@ export function useHospitalStore() { return () => { mounted = false; }; }, []); - // Persistence: event-driven + periodic save - const [saveQueue, setSaveQueue] = useState([]); - const [isSaving, setIsSaving] = useState(false); - - // Track which parts changed - const [changedKeys, setChangedKeys] = useState>(new Set()); - - // Mark parts as changed - const markChanged = (key: string) => { - setChangedKeys(prev => { - const next = new Set(prev); - next.add(key); - return next; - }); - }; - - // Save only changed parts - const saveChanges = useCallback(async () => { - if (isSaving) return; - setIsSaving(true); + // Helper to make API calls with error handling + const apiCall = useCallback(async (method: string, endpoint: string, data?: any) => { try { - // If there are marked changed keys, save only those; otherwise save all state - const keysToSave = changedKeys.size > 0 ? Array.from(changedKeys) : Object.keys(state).filter(k => !['currentUser', 'isAuthenticated', 'vistaActual', 'currentInternacionId'].includes(k)); - const partialState: any = {}; - keysToSave.forEach(key => { - if (key in state) { - partialState[key] = state[key]; - } - }); - - await fetch(`${API_BASE}/state/partial`, { - method: 'PUT', + const res = await fetch(`${API_BASE}${endpoint}`, { + method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(partialState), + body: data ? JSON.stringify(data) : undefined, }); - setChangedKeys(new Set()); + if (!res.ok) throw new Error(`API error: ${res.statusText}`); + return await res.json().catch(() => ({ ok: true })); } catch (err) { - console.error('Save error:', err); - } finally { - setIsSaving(false); + console.error(`API call failed: ${method} ${endpoint}`, err); + throw err; } - }, [state, changedKeys, isSaving]); - - // Periodic save every 5 seconds if there are changes - useEffect(() => { - if (!isLoaded) return; - const t = setInterval(() => { - saveChanges(); - }, 5000); - return () => clearInterval(t); - }, [isLoaded, saveChanges]); + }, []); - // Also save on特定 events (optional) + // Also save on specific events (optional) const queueSave = (keys: string[]) => { - keys.forEach(markChanged); + // Reserved for future implementation }; // Utility functions @@ -240,35 +204,53 @@ export function useHospitalStore() { }, []); // Acciones de pacientes - const agregarPaciente = useCallback((paciente: Omit) => { + const agregarPaciente = useCallback(async (paciente: Omit) => { const nuevoPaciente: Paciente = { ...paciente, id: generateUUID(), fechaRegistro: new Date().toISOString().split('T')[0], }; - setState(prev => ({ - ...prev, - pacientes: [...prev.pacientes, nuevoPaciente], - })); - return nuevoPaciente.id; - }, []); + try { + await apiCall('POST', '/pacientes', nuevoPaciente); + setState(prev => ({ + ...prev, + pacientes: [...prev.pacientes, nuevoPaciente], + })); + return nuevoPaciente.id; + } catch (err) { + console.error('Error al agregar paciente:', err); + throw err; + } + }, [apiCall]); - const actualizarPaciente = useCallback((id: string, datos: Partial) => { - setState(prev => ({ - ...prev, - pacientes: prev.pacientes.map(p => p.id === id ? { ...p, ...datos } : p), - })); - }, []); + const actualizarPaciente = useCallback(async (id: string, datos: Partial) => { + try { + await apiCall('PUT', `/pacientes/${id}`, datos); + setState(prev => ({ + ...prev, + pacientes: prev.pacientes.map(p => p.id === id ? { ...p, ...datos } : p), + })); + } catch (err) { + console.error('Error al actualizar paciente:', err); + throw err; + } + }, [apiCall]); - const eliminarPaciente = useCallback((id: string) => { - setState(prev => ({ - ...prev, - pacientes: prev.pacientes.filter(p => p.id !== id), - })); - }, []); + const eliminarPaciente = useCallback(async (id: string) => { + try { + await apiCall('DELETE', `/pacientes/${id}`); + setState(prev => ({ + ...prev, + pacientes: prev.pacientes.filter(p => p.id !== id), + })); + } catch (err) { + console.error('Error al eliminar paciente:', err); + throw err; + } + }, [apiCall]); // Acciones de camas - const actualizarCama = useCallback((id: string, datos: Partial) => { + const actualizarCama = useCallback(async (id: string, datos: Partial) => { const cama = state.camas.find(c => c.id === id); if (!cama) return; const user = state.currentUser; @@ -277,57 +259,88 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar cama en esta área'); return; } - setState(prev => ({ - ...prev, - camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c), - })); - }, [state.camas, state.currentUser]); + try { + await apiCall('PUT', `/camas/${id}`, datos); + setState(prev => ({ + ...prev, + camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c), + })); + } catch (err) { + console.error('Error al actualizar cama:', err); + throw err; + } + }, [state.camas, state.currentUser, apiCall]); - const agregarCama = useCallback((cama: Omit) => { + const agregarCama = useCallback(async (cama: Omit) => { const nuevaCama: Cama = { ...cama, id: generateUUID(), } as Cama; - setState(prev => ({ - ...prev, - camas: [...prev.camas, nuevaCama], - })); - return nuevaCama.id; - }, []); + try { + await apiCall('POST', '/camas', nuevaCama); + setState(prev => ({ + ...prev, + camas: [...prev.camas, nuevaCama], + })); + return nuevaCama.id; + } catch (err) { + console.error('Error al agregar cama:', err); + throw err; + } + }, [apiCall]); - const eliminarCama = useCallback((id: string) => { - setState(prev => ({ - ...prev, - camas: prev.camas.filter(c => c.id !== id), - internaciones: prev.internaciones.map(i => i.camaId === id ? { ...i, activa: false } : i), - })); - }, []); + const eliminarCama = useCallback(async (id: string) => { + try { + await apiCall('DELETE', `/camas/${id}`); + setState(prev => ({ + ...prev, + camas: prev.camas.filter(c => c.id !== id), + internaciones: prev.internaciones.map(i => i.camaId === id ? { ...i, activa: false } : i), + })); + } catch (err) { + console.error('Error al eliminar cama:', err); + throw err; + } + }, [apiCall]); // Acciones de internaciones - const iniciarInternacion = useCallback((internacion: Omit) => { + const iniciarInternacion = useCallback(async (internacion: Omit) => { const nuevaInternacion: Internacion = { ...internacion, id: generateUUID(), activa: true, }; - setState(prev => ({ - ...prev, - internaciones: [...prev.internaciones, nuevaInternacion], - camas: prev.camas.map(c => - c.id === internacion.camaId - ? { ...c, estado: 'Ocupada' as const, pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id } - : c - ), - })); - // Marcar como cambiadas para el guardado - markChanged('internaciones'); - markChanged('camas'); - - return nuevaInternacion.id; - }, [markChanged]); + try { + // API call inmediata para crear internación + await apiCall('POST', '/internaciones', nuevaInternacion); + + // Actualizar estado de la cama en la base de datos + await apiCall('PUT', `/camas/${internacion.camaId}`, { + estado: 'Ocupada', + pacienteId: internacion.pacienteId, + internacionId: nuevaInternacion.id + }); + + // Actualizar estado local después del éxito + setState(prev => ({ + ...prev, + internaciones: [...prev.internaciones, nuevaInternacion], + camas: prev.camas.map(c => + c.id === internacion.camaId + ? { ...c, estado: 'Ocupada' as const, pacienteId: internacion.pacienteId, internacionId: nuevaInternacion.id } + : c + ), + })); + + return nuevaInternacion.id; + } catch (err) { + console.error('Error al crear internación:', err); + throw err; + } + }, [apiCall]); - const finalizarInternacion = useCallback((internacionId: string, datos: { +const finalizarInternacion = useCallback(async (internacionId: string, datos: { fechaEgreso: string; diagnosticoEgreso: string; motivoEgreso: Internacion['motivoEgreso'] @@ -338,31 +351,52 @@ export function useHospitalStore() { const areaFueraDeArea = state.areas.find(a => (a.nombre || '').trim().toLowerCase() === 'fuera de area'); const areaFueraDeAreaId = areaFueraDeArea?.id; const esFueraDeArea = internacion.areaId === areaFueraDeAreaId; + const camaId = internacion.camaId; - setState(prev => { - return { - ...prev, - internaciones: prev.internaciones.map(i => - i.id === internacionId - ? { ...i, ...datos, activa: false } - : i - ), - camas: esFueraDeArea - ? prev.camas.filter(c => c.id !== internacion.camaId) - : prev.camas.map(c => - c.id === internacion.camaId - ? { ...c, estado: 'Disponible', pacienteId: undefined, internacionId: undefined } - : c - ), - }; - }); - - // Marcar como cambiadas para el guardado - markChanged('internaciones'); - markChanged('camas'); - }, [state.areas, markChanged]); + try { + // API call inmediata para finalizar internación + await apiCall('PUT', `/internaciones/${internacionId}`, { ...datos, activa: false }); + + // Eliminar o actualizar la cama según el área + if (camaId) { + if (esFueraDeArea) { + // Eliminar la cama si es "Fuera de área" + await apiCall('DELETE', `/camas/${camaId}`); + } else { + // Marcar como disponible si es un área normal + await apiCall('PUT', `/camas/${camaId}`, { + estado: 'Disponible', + pacienteId: null, + internacionId: null + }); + } + } + + // Actualizar estado local después del éxito + setState(prev => { + return { + ...prev, + internaciones: prev.internaciones.map(i => + i.id === internacionId + ? { ...i, ...datos, activa: false } + : i + ), + cams: esFueraDeArea + ? prev.camas.filter(c => c.id !== camaId) + : prev.camas.map(c => + c.id === camaId + ? { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined } + : c + ), + }; + }); + } catch (err) { + console.error('Error al finalizar internación:', err); + throw err; + } + }, [state.areas, apiCall]); - const actualizarInternacion = useCallback((internacionId: string, datos: Partial) => { +const actualizarInternacion = useCallback(async (internacionId: string, datos: Partial) => { const internacion = state.internaciones.find(i => i.id === internacionId); if (!internacion) return; @@ -382,53 +416,53 @@ export function useHospitalStore() { const pacienteId = datos.pacienteId || internacion.pacienteId; - setState(prev => { - const result = { - ...prev, - internaciones: prev.internaciones.map(i => - i.id === internacionId - ? { ...i, ...datos } - : i - ), - camas: prev.camas.map(c => { - if (c.id === newCamaId && newCamaId !== oldCamaId) { - return { ...c, estado: 'Ocupada' as const, pacienteId, internacionId }; - } - if (c.id === oldCamaId && oldCamaId !== newCamaId) { - return { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined }; - } - return c; - }), - }; - return result; - }); - - // Marcar como cambiadas para el guardado - markChanged('internaciones'); - if (cambioDeCama) { - markChanged('camas'); + try { + // API call inmediata + await apiCall('PUT', `/internaciones/${internacionId}`, datos); + + // Si hay cambio de cama, actualizar estados en la base de datos + if (cambioDeCama && newCamaId && oldCamaId) { + await apiCall('PUT', `/camas/${newCamaId}`, { + estado: 'Ocupada', + pacienteId, + internacionId + }); + await apiCall('PUT', `/camas/${oldCamaId}`, { + estado: 'Disponible', + pacienteId: null, + internacionId: null + }); + } + + // Actualizar estado local después del éxito + setState(prev => { + const result = { + ...prev, + internaciones: prev.internaciones.map(i => + i.id === internacionId + ? { ...i, ...datos } + : i + ), + camas: prev.camas.map(c => { + if (c.id === newCamaId && newCamaId !== oldCamaId) { + return { ...c, estado: 'Ocupada' as const, pacienteId, internacionId }; + } + if (c.id === oldCamaId && oldCamaId !== newCamaId) { + return { ...c, estado: 'Disponible' as const, pacienteId: undefined, internacionId: undefined }; + } + return c; + }), + }; + return result; + }); + } catch (err) { + console.error('Error al actualizar internación:', err); + throw err; } - - if (oldCamaId !== newCamaId && newCamaId && oldCamaId) { - setTimeout(() => { - fetch(`${API_BASE}/camas/${newCamaId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ estado: 'Ocupada' }), - }).catch(() => {}); - }, 500); - setTimeout(() => { - fetch(`${API_BASE}/camas/${oldCamaId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ estado: 'Disponible' }), - }).catch(() => {}); - }, 1000); - } - }, [state.internaciones, state.currentUser]); + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de evoluciones - const agregarEvolucion = useCallback((evolucion: Omit) => { + const agregarEvolucion = useCallback(async (evolucion: Omit) => { const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -439,16 +473,21 @@ export function useHospitalStore() { ...evolucion, id: generateUUID(), }; - setState(prev => ({ - ...prev, - evoluciones: [...prev.evoluciones, nuevaEvolucion], - })); - markChanged('evoluciones'); - return nuevaEvolucion.id; - }, [state.internaciones, state.currentUser, markChanged]); + try { + await apiCall('POST', '/evoluciones', nuevaEvolucion); + setState(prev => ({ + ...prev, + evoluciones: [...prev.evoluciones, nuevaEvolucion], + })); + return nuevaEvolucion.id; + } catch (err) { + console.error('Error al agregar evolución:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarEvolucion = useCallback((id: string) => { + const eliminarEvolucion = useCallback(async (id: string) => { const evolucion = state.evoluciones.find(e => e.id === id); if (!evolucion) return; const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); @@ -457,14 +496,20 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar evolución en esta área'); return; } - setState(prev => ({ - ...prev, - evoluciones: prev.evoluciones.filter(e => e.id !== id), - })); - markChanged('evoluciones'); - }, [markChanged]); + + try { + await apiCall('DELETE', `/evoluciones/${id}`); + setState(prev => ({ + ...prev, + evoluciones: prev.evoluciones.filter(e => e.id !== id), + })); + } catch (err) { + console.error('Error al eliminar evolución:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarEvolucion = useCallback((id: string, datos: Partial) => { + const actualizarEvolucion = useCallback(async (id: string, datos: Partial) => { const evolucion = state.evoluciones.find(e => e.id === id); if (!evolucion) return; const internacion = state.internaciones.find(i => i.id === evolucion.internacionId); @@ -473,15 +518,21 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar evolución en esta área'); return; } - setState(prev => ({ - ...prev, - evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e), - })); - markChanged('evoluciones'); - }, [state.internaciones, state.currentUser, markChanged]); + + try { + await apiCall('PUT', `/evoluciones/${id}`, datos); + setState(prev => ({ + ...prev, + evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e), + })); + } catch (err) { + console.error('Error al actualizar evolución:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de laboratorios - const agregarLaboratorio = useCallback((laboratorio: Omit) => { + const agregarLaboratorio = useCallback(async (laboratorio: Omit) => { const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -492,14 +543,20 @@ export function useHospitalStore() { ...laboratorio, id: generateUUID(), }; - setState(prev => ({ - ...prev, - laboratorios: [...prev.laboratorios, nuevoLaboratorio], - })); - return nuevoLaboratorio.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/laboratorios', nuevoLaboratorio); + setState(prev => ({ + ...prev, + laboratorios: [...prev.laboratorios, nuevoLaboratorio], + })); + return nuevoLaboratorio.id; + } catch (err) { + console.error('Error al agregar laboratorio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarLaboratorio = useCallback((id: string) => { + const eliminarLaboratorio = useCallback(async (id: string) => { const laboratorio = state.laboratorios.find(l => l.id === id); if (!laboratorio) return; const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); @@ -508,13 +565,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar laboratorio en esta área'); return; } - setState(prev => ({ - ...prev, - laboratorios: prev.laboratorios.filter(l => l.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/laboratorios/${id}`); + setState(prev => ({ + ...prev, + laboratorios: prev.laboratorios.filter(l => l.id !== id), + })); + } catch (err) { + console.error('Error al eliminar laboratorio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarLaboratorio = useCallback((id: string, datos: Partial) => { + const actualizarLaboratorio = useCallback(async (id: string, datos: Partial) => { const laboratorio = state.laboratorios.find(l => l.id === id); if (!laboratorio) return; const internacion = state.internaciones.find(i => i.id === laboratorio.internacionId); @@ -523,14 +586,20 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar laboratorio en esta área'); return; } - setState(prev => ({ - ...prev, - laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/laboratorios/${id}`, datos); + setState(prev => ({ + ...prev, + laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l), + })); + } catch (err) { + console.error('Error al actualizar laboratorio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de ácido-base - const agregarAcidoBase = useCallback((acidoBase: Omit) => { + const agregarAcidoBase = useCallback(async (acidoBase: Omit) => { const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -541,14 +610,20 @@ export function useHospitalStore() { ...acidoBase, id: generateUUID(), }; - setState(prev => ({ - ...prev, - acidosBase: [...prev.acidosBase, nuevoAcidoBase], - })); - return nuevoAcidoBase.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/acid-os-base', nuevoAcidoBase); + setState(prev => ({ + ...prev, + acidosBase: [...prev.acidosBase, nuevoAcidoBase], + })); + return nuevoAcidoBase.id; + } catch (err) { + console.error('Error al agregar ácido-base:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarAcidoBase = useCallback((id: string, datos: Partial) => { + const actualizarAcidoBase = useCallback(async (id: string, datos: Partial) => { const acidoBase = state.acidosBase.find(a => a.id === id); if (!acidoBase) return; const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); @@ -557,13 +632,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar ácido-base en esta área'); return; } - setState(prev => ({ - ...prev, - acidosBase: prev.acidosBase.map(a => a.id === id ? { ...a, ...datos } : a), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/acid-os-base/${id}`, datos); + setState(prev => ({ + ...prev, + acidosBase: prev.acidosBase.map(a => a.id === id ? { ...a, ...datos } : a), + })); + } catch (err) { + console.error('Error al actualizar ácido-base:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarAcidoBase = useCallback((id: string) => { + const eliminarAcidoBase = useCallback(async (id: string) => { const acidoBase = state.acidosBase.find(a => a.id === id); if (!acidoBase) return; const internacion = state.internaciones.find(i => i.id === acidoBase.internacionId); @@ -572,14 +653,20 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar ácido-base en esta área'); return; } - setState(prev => ({ - ...prev, - acidosBase: prev.acidosBase.filter(a => a.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/acid-os-base/${id}`); + setState(prev => ({ + ...prev, + acidosBase: prev.acidosBase.filter(a => a.id !== id), + })); + } catch (err) { + console.error('Error al eliminar ácido-base:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de cultivos - const agregarCultivo = useCallback((cultivo: Omit) => { + const agregarCultivo = useCallback(async (cultivo: Omit) => { const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -590,14 +677,20 @@ export function useHospitalStore() { ...cultivo, id: generateUUID(), }; - setState(prev => ({ - ...prev, - cultivos: [...prev.cultivos, nuevoCultivo], - })); - return nuevoCultivo.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/cultivos', nuevoCultivo); + setState(prev => ({ + ...prev, + cultivos: [...prev.cultivos, nuevoCultivo], + })); + return nuevoCultivo.id; + } catch (err) { + console.error('Error al agregar cultivo:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarCultivo = useCallback((id: string, datos: Partial) => { + const actualizarCultivo = useCallback(async (id: string, datos: Partial) => { const cultivo = state.cultivos.find(c => c.id === id); if (!cultivo) return; const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); @@ -606,13 +699,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar cultivo en esta área'); return; } - setState(prev => ({ - ...prev, - cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/cultivos/${id}`, datos); + setState(prev => ({ + ...prev, + cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c), + })); + } catch (err) { + console.error('Error al actualizar cultivo:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarCultivo = useCallback((id: string) => { + const eliminarCultivo = useCallback(async (id: string) => { const cultivo = state.cultivos.find(c => c.id === id); if (!cultivo) return; const internacion = state.internaciones.find(i => i.id === cultivo.internacionId); @@ -621,14 +720,20 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar cultivo en esta área'); return; } - setState(prev => ({ - ...prev, - cultivos: prev.cultivos.filter(c => c.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/cultivos/${id}`); + setState(prev => ({ + ...prev, + cultivos: prev.cultivos.filter(c => c.id !== id), + })); + } catch (err) { + console.error('Error al eliminar cultivo:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de estudios complementarios - const agregarEstudioComplementario = useCallback((estudio: Omit) => { + const agregarEstudioComplementario = useCallback(async (estudio: Omit) => { const internacion = state.internaciones.find(i => i.id === estudio.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -639,14 +744,20 @@ export function useHospitalStore() { ...estudio, id: generateUUID(), }; - setState(prev => ({ - ...prev, - estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio], - })); - return nuevoEstudio.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/estudios-complementarios', nuevoEstudio); + setState(prev => ({ + ...prev, + estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio], + })); + return nuevoEstudio.id; + } catch (err) { + console.error('Error al agregar estudio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarEstudioComplementario = useCallback((id: string, datos: Partial) => { + const actualizarEstudioComplementario = useCallback(async (id: string, datos: Partial) => { const estudio = state.estudiosComplementarios.find(e => e.id === id); if (!estudio) return; const internacion = state.internaciones.find(i => i.id === estudio.internacionId); @@ -655,13 +766,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar estudio complementario en esta área'); return; } - setState(prev => ({ - ...prev, - estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/estudios-complementarios/${id}`, datos); + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e), + })); + } catch (err) { + console.error('Error al actualizar estudio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarEstudioComplementario = useCallback((id: string) => { + const eliminarEstudioComplementario = useCallback(async (id: string) => { const estudio = state.estudiosComplementarios.find(e => e.id === id); if (!estudio) return; const internacion = state.internaciones.find(i => i.id === estudio.internacionId); @@ -670,14 +787,20 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar estudio complementario en esta área'); return; } - setState(prev => ({ - ...prev, - estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/estudios-complementarios/${id}`); + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id), + })); + } catch (err) { + console.error('Error al eliminar estudio:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); // Acciones de interconsultas - const agregarInterconsulta = useCallback((interconsulta: Omit) => { + const agregarInterconsulta = useCallback(async (interconsulta: Omit) => { const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -685,14 +808,20 @@ export function useHospitalStore() { return null; } const nuevaInterconsulta: Interconsulta = { ...interconsulta, id: generateUUID() }; - setState(prev => ({ - ...prev, - interconsultas: [...(prev.interconsultas || []), nuevaInterconsulta], - })); - return nuevaInterconsulta.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/interconsultas', nuevaInterconsulta); + setState(prev => ({ + ...prev, + interconsultas: [...(prev.interconsultas || []), nuevaInterconsulta], + })); + return nuevaInterconsulta.id; + } catch (err) { + console.error('Error al agregar interconsulta:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarInterconsulta = useCallback((id: string, datos: Partial) => { + const actualizarInterconsulta = useCallback(async (id: string, datos: Partial) => { const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); if (!interconsulta) return; const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); @@ -701,13 +830,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar interconsulta en esta área'); return; } - setState(prev => ({ - ...prev, - interconsultas: (prev.interconsultas || []).map(ic => ic.id === id ? { ...ic, ...datos } : ic), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/interconsultas/${id}`, datos); + setState(prev => ({ + ...prev, + interconsultas: (prev.interconsultas || []).map(ic => ic.id === id ? { ...ic, ...datos } : ic), + })); + } catch (err) { + console.error('Error al actualizar interconsulta:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarInterconsulta = useCallback((id: string) => { + const eliminarInterconsulta = useCallback(async (id: string) => { const interconsulta = (state.interconsultas || []).find(ic => ic.id === id); if (!interconsulta) return; const internacion = state.internaciones.find(i => i.id === interconsulta.internacionId); @@ -716,13 +851,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar interconsulta en esta área'); return; } - setState(prev => ({ - ...prev, - interconsultas: (prev.interconsultas || []).filter(ic => ic.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/interconsultas/${id}`); + setState(prev => ({ + ...prev, + interconsultas: (prev.interconsultas || []).filter(ic => ic.id !== id), + })); + } catch (err) { + console.error('Error al eliminar interconsulta:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const agregarATB = useCallback((atb: Omit) => { + const agregarATB = useCallback(async (atb: Omit) => { const internacion = state.internaciones.find(i => i.id === atb.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -730,14 +871,20 @@ export function useHospitalStore() { return null; } const nuevoATB: ATB = { ...atb, id: generateUUID() }; - setState(prev => ({ - ...prev, - atb: [...prev.atb, nuevoATB], - })); - return nuevoATB.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/atb', nuevoATB); + setState(prev => ({ + ...prev, + atb: [...prev.atb, nuevoATB], + })); + return nuevoATB.id; + } catch (err) { + console.error('Error al agregar ATB:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarATB = useCallback((id: string, datos: Partial) => { + const actualizarATB = useCallback(async (id: string, datos: Partial) => { const atb = state.atb.find(a => a.id === id); if (!atb) return; const internacion = state.internaciones.find(i => i.id === atb.internacionId); @@ -746,13 +893,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar ATB en esta área'); return; } - setState(prev => ({ - ...prev, - atb: prev.atb.map(a => a.id === id ? { ...a, ...datos } : a), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/atb/${id}`, datos); + setState(prev => ({ + ...prev, + atb: prev.atb.map(a => a.id === id ? { ...a, ...datos } : a), + })); + } catch (err) { + console.error('Error al actualizar ATB:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarATB = useCallback((id: string) => { + const eliminarATB = useCallback(async (id: string) => { const atb = state.atb.find(a => a.id === id); if (!atb) return; const internacion = state.internaciones.find(i => i.id === atb.internacionId); @@ -761,13 +914,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar ATB en esta área'); return; } - setState(prev => ({ - ...prev, - atb: prev.atb.filter(a => a.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/atb/${id}`); + setState(prev => ({ + ...prev, + atb: prev.atb.filter(a => a.id !== id), + })); + } catch (err) { + console.error('Error al eliminar ATB:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const agregarIndicacion = useCallback((indicacion: Omit) => { + const agregarIndicacion = useCallback(async (indicacion: Omit) => { const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { @@ -775,14 +934,20 @@ export function useHospitalStore() { return null; } const nuevaIndicacion: Indicacion = { ...indicacion, id: generateUUID() }; - setState(prev => ({ - ...prev, - indicaciones: [...prev.indicaciones, nuevaIndicacion], - })); - return nuevaIndicacion.id; - }, [state.internaciones, state.currentUser]); + try { + await apiCall('POST', '/indicaciones', nuevaIndicacion); + setState(prev => ({ + ...prev, + indicaciones: [...prev.indicaciones, nuevaIndicacion], + })); + return nuevaIndicacion.id; + } catch (err) { + console.error('Error al agregar indicación:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const actualizarIndicacion = useCallback((id: string, datos: Partial) => { + const actualizarIndicacion = useCallback(async (id: string, datos: Partial) => { const indicacion = state.indicaciones.find(i => i.id === id); if (!indicacion) return; const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); @@ -791,13 +956,19 @@ export function useHospitalStore() { console.warn('No tiene permisos para actualizar indicación en esta área'); return; } - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.map(i => i.id === id ? { ...i, ...datos } : i), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('PUT', `/indicaciones/${id}`, datos); + setState(prev => ({ + ...prev, + indicaciones: prev.indicaciones.map(i => i.id === id ? { ...i, ...datos } : i), + })); + } catch (err) { + console.error('Error al actualizar indicación:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const eliminarIndicacion = useCallback((id: string) => { + const eliminarIndicacion = useCallback(async (id: string) => { const indicacion = state.indicaciones.find(i => i.id === id); if (!indicacion) return; const internacion = state.internaciones.find(i => i.id === indicacion.internacionId); @@ -806,24 +977,37 @@ export function useHospitalStore() { console.warn('No tiene permisos para eliminar indicación en esta área'); return; } - setState(prev => ({ - ...prev, - indicaciones: prev.indicaciones.filter(i => i.id !== id), - })); - }, [state.internaciones, state.currentUser]); + try { + await apiCall('DELETE', `/indicaciones/${id}`); + setState(prev => ({ + ...prev, + indicaciones: prev.indicaciones.filter(i => i.id !== id), + })); + } catch (err) { + console.error('Error al eliminar indicación:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); - const agregarMovimientoIndicacion = useCallback((movimiento: any) => { + const agregarMovimientoIndicacion = useCallback(async (movimiento: any) => { const internacion = state.internaciones.find(i => i.id === movimiento.internacionId); const areaId = internacion?.areaId || null; if (!canAccessArea(areaId)) { console.warn('No tiene permisos para agregar movimiento de indicación en esta área'); return; } - setState(prev => ({ - ...prev, - movimientosIndicaciones: [...(prev.movimientosIndicaciones || []), { ...movimiento, id: generateUUID() }], - })); - }, [state.internaciones, state.currentUser]); + const nuevoMovimiento = { ...movimiento, id: generateUUID() }; + try { + await apiCall('POST', '/movimientos-indicaciones', nuevoMovimiento); + setState(prev => ({ + ...prev, + movimientosIndicaciones: [...(prev.movimientosIndicaciones || []), nuevoMovimiento], + })); + } catch (err) { + console.error('Error al agregar movimiento:', err); + throw err; + } + }, [state.internaciones, state.currentUser, apiCall]); const getMovimientosByInternacion = useCallback((internacionId: string) => { return (state.movimientosIndicaciones || []) @@ -832,29 +1016,47 @@ export function useHospitalStore() { }, [state.movimientosIndicaciones]); // Acciones de areas - const agregarArea = useCallback((area: Omit) => { + const agregarArea = useCallback(async (area: Omit) => { const nuevaArea: Area = { ...area, id: generateUUID() }; - setState(prev => ({ - ...prev, - areas: [...prev.areas, nuevaArea], - })); - return nuevaArea.id; - }, []); + try { + await apiCall('POST', '/areas', nuevaArea); + setState(prev => ({ + ...prev, + areas: [...prev.areas, nuevaArea], + })); + return nuevaArea.id; + } catch (err) { + console.error('Error al agregar área:', err); + throw err; + } + }, [apiCall]); - const actualizarArea = useCallback((id: string, datos: Partial) => { - setState(prev => ({ - ...prev, - areas: prev.areas.map(a => a.id === id ? { ...a, ...datos } : a), - })); - }, []); + const actualizarArea = useCallback(async (id: string, datos: Partial) => { + try { + await apiCall('PUT', `/areas/${id}`, datos); + setState(prev => ({ + ...prev, + areas: prev.areas.map(a => a.id === id ? { ...a, ...datos } : a), + })); + } catch (err) { + console.error('Error al actualizar área:', err); + throw err; + } + }, [apiCall]); - const eliminarArea = useCallback((id: string) => { - setState(prev => ({ - ...prev, - areas: prev.areas.filter(a => a.id !== id), - camas: prev.camas.map(c => c.areaId === id ? { ...c, areaId: undefined } : c), - })); - }, []); + const eliminarArea = useCallback(async (id: string) => { + try { + await apiCall('DELETE', `/areas/${id}`); + setState(prev => ({ + ...prev, + areas: prev.areas.filter(a => a.id !== id), + camas: prev.camas.map(c => c.areaId === id ? { ...c, areaId: undefined } : c), + })); + } catch (err) { + console.error('Error al eliminar área:', err); + throw err; + } + }, [apiCall]); // Funciones de utilidad para consultas const getPacienteById = useCallback((id: string) => { diff --git a/src/sections/Cultivos.tsx b/src/sections/Cultivos.tsx index 661ab18..2546938 100644 --- a/src/sections/Cultivos.tsx +++ b/src/sections/Cultivos.tsx @@ -107,7 +107,7 @@ export function Cultivos({ }; const guardarNuevo = () => { - if (!protocolo.trim()) return; + if (!protocolo.trim() || !patient) return; onAgregarCultivo({ pacienteId: patient.id, internacionId, diff --git a/src/sections/EditIngreso.tsx b/src/sections/EditIngreso.tsx index 5e61598..9908fcb 100644 --- a/src/sections/EditIngreso.tsx +++ b/src/sections/EditIngreso.tsx @@ -16,7 +16,7 @@ interface EditIngresoProps { camas: Cama[]; areas: Area[]; onActualizarInternacion: (id: string, datos: Partial) => void; - onAgregarCama?: (cama: Omit) => string; + onAgregarCama?: (cama: Omit) => any; onVolver: () => void; getAreaName: (areaId: string | undefined) => string; } diff --git a/src/sections/MapaCamas.tsx b/src/sections/MapaCamas.tsx index f8b808d..26e8a26 100644 --- a/src/sections/MapaCamas.tsx +++ b/src/sections/MapaCamas.tsx @@ -17,10 +17,10 @@ interface MapaCamasProps { pacientes: Paciente[]; internaciones: Internacion[]; onActualizarCama: (id: string, datos: Partial) => void; - onAgregarCama: (cama: Omit) => string; + onAgregarCama: (cama: Omit) => any; onEliminarCama: (id: string) => void; - onIniciarInternacion: (internacion: Omit) => void; - onAgregarArea: (area: Omit) => string; + onIniciarInternacion: (internacion: Omit) => Promise; + onAgregarArea: (area: Omit) => any; onActualizarArea: (id: string, datos: Partial) => void; onEliminarArea: (id: string) => void; getPacienteById: (id: string) => Paciente | undefined; diff --git a/src/sections/NuevoIngreso.tsx b/src/sections/NuevoIngreso.tsx index ef284fb..63cb740 100644 --- a/src/sections/NuevoIngreso.tsx +++ b/src/sections/NuevoIngreso.tsx @@ -12,8 +12,8 @@ interface NuevoIngresoProps { pacientes: Paciente[]; camas: Cama[]; areas: Area[]; - onIniciarInternacion: (internacion: Omit) => void; - onAgregarCama?: (cama: Omit) => string; + onIniciarInternacion: (internacion: Omit) => Promise; + onAgregarCama?: (cama: Omit) => Promise; onAgregarPaciente?: (paciente: Omit) => void; onActualizarPaciente?: (id: string, datos: Partial) => void; onVolver: () => void; @@ -125,14 +125,19 @@ export function NuevoIngreso({ pacientes, camas, areas, onIniciarInternacion, on camaId = camaExistente.id; newAreaId = camaExistente.areaId || ''; } else if (onAgregarCama) { - const nuevaCamaId = onAgregarCama({ - numero: numeroCama, - areaId: areaFueraDeAreaId, - tipo: 'General', - estado: 'Ocupada' - }); - camaId = nuevaCamaId; - newAreaId = areaFueraDeAreaId; + try { + camaId = await onAgregarCama({ + numero: numeroCama, + areaId: areaFueraDeAreaId, + tipo: 'General', + estado: 'Ocupada' + }); + newAreaId = areaFueraDeAreaId; + } catch (err) { + console.error('Error al crear cama:', err); + alert('Error al crear la cama'); + return; + } } else { alert('No se puede agregar una nueva cama'); return;