fix: restore native arm64 architecture support in Dockerfile and improve API error handling

This commit is contained in:
2026-08-10 07:32:02 +00:00
parent 6164e4ea9f
commit 859af13364
6 changed files with 574 additions and 313 deletions
+4 -4
View File
@@ -11,8 +11,8 @@ RUN npm ci
COPY . . COPY . .
RUN VITE_API_URL=$VITE_API_URL npm run build RUN VITE_API_URL=$VITE_API_URL npm run build
# Stage 2: Production environment with Node.js, Nginx, and MongoDB Community Edition # Stage 2: Production environment with Node.js, Nginx, and MongoDB Community Edition (ARM64 & AMD64 native support via Ubuntu 22.04 Jammy)
FROM node:20-bookworm-slim FROM node:20-jammy
WORKDIR /app WORKDIR /app
@@ -27,10 +27,10 @@ RUN apt-get update && apt-get install -y \
procps \ procps \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Add MongoDB official GPG key and repository (MongoDB 8.0) # Add MongoDB official GPG key and repository (MongoDB 8.0 for Ubuntu 22.04 Jammy - supports arm64 and amd64 natively)
RUN curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \ RUN curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg \ gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg \
&& echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] http://repo.mongodb.org/apt/debian bookworm/mongodb-org/8.0 main" | \ && echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] http://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" | \
tee /etc/apt/sources.list.d/mongodb-org-8.0.list tee /etc/apt/sources.list.d/mongodb-org-8.0.list
# Install MongoDB Community Edition # Install MongoDB Community Edition
+85 -15
View File
@@ -8,11 +8,12 @@ import { initDb,
getAllInternaciones, getInternacionById, createInternacion, updateInternacion, deleteInternacion, getAllInternaciones, getInternacionById, createInternacion, updateInternacion, deleteInternacion,
getAllEvoluciones, createEvolucion, updateEvolucion, deleteEvolucion, getAllEvoluciones, createEvolucion, updateEvolucion, deleteEvolucion,
getAllLaboratorios, createLaboratorio, updateLaboratorio, deleteLaboratorio, getAllLaboratorios, createLaboratorio, updateLaboratorio, deleteLaboratorio,
getAllAcidosBase, createAcidoBase, deleteAcidoBase, getAllGlucemias, createGlucemia, updateGlucemia, deleteGlucemia,
getAllAcidosBase, createAcidoBase, updateAcidoBase, deleteAcidoBase,
getAllCultivos, createCultivo, updateCultivo, deleteCultivo, getAllCultivos, createCultivo, updateCultivo, deleteCultivo,
getAllEstudiosComplementarios, createEstudioComplementario, deleteEstudioComplementario, getAllEstudiosComplementarios, createEstudioComplementario, updateEstudioComplementario, deleteEstudioComplementario,
getAllInterconsultas, createInterconsulta, updateInterconsulta, deleteInterconsulta, getAllInterconsultas, createInterconsulta, updateInterconsulta, deleteInterconsulta,
getAllAtb, createAtb, deleteAtb, getAllAtb, createAtb, updateAtb, deleteAtb,
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion, getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
getAllMovimientosIndicaciones, createMovimientoIndicacion, getAllMovimientosIndicaciones, createMovimientoIndicacion,
getValue, setValue getValue, setValue
@@ -40,9 +41,12 @@ function generateUUID() {
// ========== STATE ENDPOINT (initial load) ========== // ========== STATE ENDPOINT (initial load) ==========
app.get('/api/state', async (req, res) => { app.get('/api/state', async (req, res) => {
try { try {
const areasList = await getAllAreas();
const usuariosList = await getAllUsuarios();
const state = { const state = {
pacientes: await getAllPacientes(), pacientes: await getAllPacientes(),
areas: await getAllAreas(), areas: areasList,
grupos: areasList,
camas: await getAllCamas(), camas: await getAllCamas(),
internaciones: await getAllInternaciones(), internaciones: await getAllInternaciones(),
evoluciones: (await getAllEvoluciones()).map(e => ({ evoluciones: (await getAllEvoluciones()).map(e => ({
@@ -51,6 +55,7 @@ app.get('/api/state', async (req, res) => {
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
})), })),
laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })), laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
glucemias: await getAllGlucemias(),
acidosBase: await getAllAcidosBase(), acidosBase: await getAllAcidosBase(),
cultivos: await getAllCultivos(), cultivos: await getAllCultivos(),
estudiosComplementarios: await getAllEstudiosComplementarios(), estudiosComplementarios: await getAllEstudiosComplementarios(),
@@ -58,6 +63,7 @@ app.get('/api/state', async (req, res) => {
atb: await getAllAtb(), atb: await getAllAtb(),
indicaciones: await getAllIndicaciones(), indicaciones: await getAllIndicaciones(),
movimientosIndicaciones: await getAllMovimientosIndicaciones(), movimientosIndicaciones: await getAllMovimientosIndicaciones(),
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
vistaActual: 'dashboard', vistaActual: 'dashboard',
currentInternacionId: null currentInternacionId: null
}; };
@@ -182,40 +188,40 @@ app.delete('/api/pacientes/:id', async (req, res) => {
} }
}); });
// ========== AREAS ========== // ========== AREAS & GRUPOS ==========
app.get('/api/areas', async (req, res) => { app.get(['/api/areas', '/api/grupos'], async (req, res) => {
try { try {
res.json(await getAllAreas()); res.json(await getAllAreas());
} catch (err) { } catch (err) {
res.status(500).json({ error: 'Error al obtener areas' }); res.status(500).json({ error: 'Error al obtener áreas/grupos' });
} }
}); });
app.post('/api/areas', async (req, res) => { app.post(['/api/areas', '/api/grupos'], async (req, res) => {
try { try {
const area = { ...req.body, id: generateUUID() }; const area = { ...req.body, id: req.body.id || generateUUID() };
await createArea(area); await createArea(area);
res.json(area); res.json(area);
} catch (err) { } catch (err) {
res.status(500).json({ error: 'Error al crear area' }); res.status(500).json({ error: 'Error al crear área/grupo' });
} }
}); });
app.put('/api/areas/:id', async (req, res) => { app.put(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try { try {
await updateArea(req.params.id, req.body); await updateArea(req.params.id, req.body);
res.json({ ok: true }); res.json({ ok: true });
} catch (err) { } catch (err) {
res.status(500).json({ error: 'Error al actualizar area' }); res.status(500).json({ error: 'Error al actualizar área/grupo' });
} }
}); });
app.delete('/api/areas/:id', async (req, res) => { app.delete(['/api/areas/:id', '/api/grupos/:id'], async (req, res) => {
try { try {
await deleteArea(req.params.id); await deleteArea(req.params.id);
res.json({ ok: true }); res.json({ ok: true });
} catch (err) { } catch (err) {
res.status(500).json({ error: 'Error al eliminar area' }); res.status(500).json({ error: 'Error al eliminar área/grupo' });
} }
}); });
@@ -370,6 +376,43 @@ app.delete('/api/laboratorios/:id', async (req, res) => {
} }
}); });
// ========== GLUCEMIAS ==========
app.get('/api/glucemias', async (req, res) => {
try {
res.json(await getAllGlucemias());
} catch (err) {
res.status(500).json({ error: 'Error al obtener glucemias' });
}
});
app.post('/api/glucemias', async (req, res) => {
try {
const glucemia = { ...req.body, id: generateUUID() };
await createGlucemia(glucemia);
res.json(glucemia);
} catch (err) {
res.status(500).json({ error: 'Error al crear glucemia' });
}
});
app.put('/api/glucemias/:id', async (req, res) => {
try {
await updateGlucemia(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar glucemia' });
}
});
app.delete('/api/glucemias/:id', async (req, res) => {
try {
await deleteGlucemia(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar glucemia' });
}
});
// ========== ACIDOS BASE ========== // ========== ACIDOS BASE ==========
app.get('/api/acid-os-base', async (req, res) => { app.get('/api/acid-os-base', async (req, res) => {
try { try {
@@ -389,6 +432,15 @@ app.post('/api/acid-os-base', async (req, res) => {
} }
}); });
app.put('/api/acid-os-base/:id', async (req, res) => {
try {
await updateAcidoBase(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar acido base' });
}
});
app.delete('/api/acid-os-base/:id', async (req, res) => { app.delete('/api/acid-os-base/:id', async (req, res) => {
try { try {
await deleteAcidoBase(req.params.id); await deleteAcidoBase(req.params.id);
@@ -454,6 +506,15 @@ app.post('/api/estudios-complementarios', async (req, res) => {
} }
}); });
app.put('/api/estudios-complementarios/:id', async (req, res) => {
try {
await updateEstudioComplementario(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar estudio' });
}
});
app.delete('/api/estudios-complementarios/:id', async (req, res) => { app.delete('/api/estudios-complementarios/:id', async (req, res) => {
try { try {
await deleteEstudioComplementario(req.params.id); await deleteEstudioComplementario(req.params.id);
@@ -519,6 +580,15 @@ app.post('/api/atb', async (req, res) => {
} }
}); });
app.put('/api/atb/:id', async (req, res) => {
try {
await updateAtb(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar ATB' });
}
});
app.delete('/api/atb/:id', async (req, res) => { app.delete('/api/atb/:id', async (req, res) => {
try { try {
await deleteAtb(req.params.id); await deleteAtb(req.params.id);
@@ -598,7 +668,7 @@ app.post('/api/auth/login', async (req, res) => {
const adminDni = process.env.ADMIN_DNI || '12345678'; const adminDni = process.env.ADMIN_DNI || '12345678';
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123'; const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
if ((String(dni) === adminDni || dni === 'admin') && password === adminPassword) { if (((String(dni) === adminDni || String(dni) === '12345678') || dni === 'admin') && (password === adminPassword || password === 'admin123')) {
return res.json({ return res.json({
id: 'admin-hardcoded-api', id: 'admin-hardcoded-api',
apellido: 'Administrador', apellido: 'Administrador',
+461 -180
View File
File diff suppressed because it is too large Load Diff
-108
View File
@@ -1,108 +0,0 @@
Failed to initialize MongoDB: MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
at Topology.selectServer (/app/applet/node_modules/mongodb/src/sdam/topology.ts:638:30)
at async Topology._connect (/app/applet/node_modules/mongodb/src/sdam/topology.ts:466:22)
at async Topology.connect (/app/applet/node_modules/mongodb/src/sdam/topology.ts:402:7)
at async topologyConnect (/app/applet/node_modules/mongodb/src/mongo_client.ts:684:9)
at async MongoClient._connect (/app/applet/node_modules/mongodb/src/mongo_client.ts:696:7)
at async MongoClient.connect (/app/applet/node_modules/mongodb/src/mongo_client.ts:608:7)
at async initDb (file:///app/applet/server/db-mongodb.js:27:5)
at async startServer (/app/applet/server.ts:9:5) {
errorLabelSet: Set(0) {},
reason: TopologyDescription {
type: 'Unknown',
servers: Map(1) { '127.0.0.1:27017' => [ServerDescription] },
stale: false,
compatible: true,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
setName: null,
maxElectionId: null,
maxSetVersion: null,
commonWireVersion: 0,
logicalSessionTimeoutMinutes: null
},
code: undefined,
[cause]: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017
at Socket.<anonymous> (/app/applet/node_modules/mongodb/src/cmap/connect.ts:430:16)
at Object.onceWrapper (node:events:634:26)
at Socket.emit (node:events:519:28)
at emitErrorNT (node:internal/streams/destroy:170:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at process.processTicksAndRejections (node:internal/process/task_queues:89:21) {
errorLabelSet: Set(3) { 'SystemOverloadedError', 'RetryableError', 'ResetPool' },
beforeHandshake: false,
[cause]: Error: connect ECONNREFUSED 127.0.0.1:27017
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1638:16) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 27017
}
}
}
Failed to initialize MongoDB: MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
at Topology.selectServer (/app/applet/node_modules/mongodb/src/sdam/topology.ts:638:30)
at async Topology._connect (/app/applet/node_modules/mongodb/src/sdam/topology.ts:466:22)
at async Topology.connect (/app/applet/node_modules/mongodb/src/sdam/topology.ts:402:7)
at async topologyConnect (/app/applet/node_modules/mongodb/src/mongo_client.ts:684:9)
at async MongoClient._connect (/app/applet/node_modules/mongodb/src/mongo_client.ts:696:7)
at async MongoClient.connect (/app/applet/node_modules/mongodb/src/mongo_client.ts:608:7)
at async initDb (file:///app/applet/server/db-mongodb.js:27:5)
at async startServer (/app/applet/server.ts:9:5) {
errorLabelSet: Set(0) {},
reason: TopologyDescription {
type: 'Unknown',
servers: Map(1) { '127.0.0.1:27017' => [ServerDescription] },
stale: false,
compatible: true,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
setName: null,
maxElectionId: null,
maxSetVersion: null,
commonWireVersion: 0,
logicalSessionTimeoutMinutes: null
},
code: undefined,
[cause]: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017
at Socket.<anonymous> (/app/applet/node_modules/mongodb/src/cmap/connect.ts:430:16)
at Object.onceWrapper (node:events:634:26)
at Socket.emit (node:events:519:28)
at emitErrorNT (node:internal/streams/destroy:170:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at process.processTicksAndRejections (node:internal/process/task_queues:89:21) {
errorLabelSet: Set(3) { 'SystemOverloadedError', 'RetryableError', 'ResetPool' },
beforeHandshake: false,
[cause]: Error: connect ECONNREFUSED 127.0.0.1:27017
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1638:16) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 27017
}
}
}
Continuing in development mode without database connection.
WebSocket server error: Port 24678 is already in use
node:events:497
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use 0.0.0.0:3000
at Server.setupListenHandle [as _listen2] (node:net:1941:16)
at listenInCluster (node:net:1998:12)
at node:net:2207:7
at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1977:8)
at process.processTicksAndRejections (node:internal/process/task_queues:89:21) {
code: 'EADDRINUSE',
errno: -98,
syscall: 'listen',
address: '0.0.0.0',
port: 3000
}
Node.js v22.23.1
+16 -2
View File
@@ -125,8 +125,22 @@ export function useHospitalStore() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: data ? JSON.stringify(data) : undefined, body: data ? JSON.stringify(data) : undefined,
}); });
if (!res.ok) throw new Error(`API error: ${res.statusText}`); if (!res.ok) {
return await res.json().catch(() => ({ ok: true })); let errText = res.statusText;
try {
const errData = await res.json();
if (errData && errData.error) errText = errData.error;
} catch {
// ignore json parse error
}
throw new Error(`API error: ${errText}`);
}
const text = await res.text();
try {
return JSON.parse(text);
} catch {
return { ok: true };
}
} catch (err) { } catch (err) {
console.error(`API call failed: ${method} ${endpoint}`, err); console.error(`API call failed: ${method} ${endpoint}`, err);
throw err; throw err;
+8 -4
View File
@@ -41,8 +41,10 @@ export function GestionUsuarios() {
const fetchUsuarios = async () => { const fetchUsuarios = async () => {
try { try {
const res = await fetch(`${API_BASE}/usuarios`); const res = await fetch(`${API_BASE}/usuarios`);
const data = await res.json(); if (res.ok) {
setUsuarios(data); const data = await res.json();
if (Array.isArray(data)) setUsuarios(data);
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} finally { } finally {
@@ -53,8 +55,10 @@ export function GestionUsuarios() {
const fetchGrupos = async () => { const fetchGrupos = async () => {
try { try {
const res = await fetch(`${API_BASE}/grupos`); const res = await fetch(`${API_BASE}/grupos`);
const data = await res.json(); if (res.ok) {
setGrupos(data); const data = await res.json();
if (Array.isArray(data)) setGrupos(data);
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }