diff --git a/src/hooks/useHospitalStore-mariadb.ts b/src/hooks/useHospitalStore-mariadb.ts new file mode 100644 index 0000000..f168d42 --- /dev/null +++ b/src/hooks/useHospitalStore-mariadb.ts @@ -0,0 +1,1027 @@ +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((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; + }, []); + + const actualizarPaciente = useCallback((id: string, datos: Partial) => { + setState(prev => ({ + ...prev, + pacientes: prev.pacientes.map(p => p.id === id ? { ...p, ...datos } : p), + })); + }, []); + + const eliminarPaciente = useCallback((id: string) => { + setState(prev => ({ + ...prev, + pacientes: prev.pacientes.filter(p => p.id !== id), + })); + }, []); + + // Acciones de camas + const actualizarCama = useCallback((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; + } + setState(prev => ({ + ...prev, + camas: prev.camas.map(c => c.id === id ? { ...c, ...datos } : c), + })); + }, [state.camas, state.currentUser]); + + const agregarCama = useCallback((cama: Omit) => { + const nuevaCama: Cama = { + ...cama, + id: generateUUID(), + } as Cama; + setState(prev => ({ + ...prev, + camas: [...prev.camas, nuevaCama], + })); + return nuevaCama.id; + }, []); + + 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), + })); + }, []); + + // 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((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; + + 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'); + } + + 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]); + + // Acciones de evoluciones + const agregarEvolucion = useCallback((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(), + }; + setState(prev => ({ + ...prev, + evoluciones: [...prev.evoluciones, nuevaEvolucion], + })); + + markChanged('evoluciones'); + return nuevaEvolucion.id; + }, [state.internaciones, state.currentUser, markChanged]); + + const eliminarEvolucion = useCallback((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; + } + setState(prev => ({ + ...prev, + evoluciones: prev.evoluciones.filter(e => e.id !== id), + })); + markChanged('evoluciones'); + }, [markChanged]); + + const actualizarEvolucion = useCallback((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; + } + setState(prev => ({ + ...prev, + evoluciones: prev.evoluciones.map(e => e.id === id ? { ...e, ...datos } : e), + })); + markChanged('evoluciones'); + }, [state.internaciones, state.currentUser, markChanged]); + + // Acciones de laboratorios + const agregarLaboratorio = useCallback((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(), + }; + setState(prev => ({ + ...prev, + laboratorios: [...prev.laboratorios, nuevoLaboratorio], + })); + return nuevoLaboratorio.id; + }, [state.internaciones, state.currentUser]); + + const eliminarLaboratorio = useCallback((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; + } + setState(prev => ({ + ...prev, + laboratorios: prev.laboratorios.filter(l => l.id !== id), + })); + }, [state.internaciones, state.currentUser]); + + const actualizarLaboratorio = useCallback((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; + } + setState(prev => ({ + ...prev, + laboratorios: prev.laboratorios.map(l => l.id === id ? { ...l, ...datos } : l), + })); + }, [state.internaciones, state.currentUser]); + + // Acciones de ácido-base + const agregarAcidoBase = useCallback((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(), + }; + setState(prev => ({ + ...prev, + acidosBase: [...prev.acidosBase, nuevoAcidoBase], + })); + return nuevoAcidoBase.id; + }, [state.internaciones, state.currentUser]); + + const actualizarAcidoBase = useCallback((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; + } + setState(prev => ({ + ...prev, + acidosBase: prev.acidosBase.map(a => a.id === id ? { ...a, ...datos } : a), + })); + }, [state.internaciones, state.currentUser]); + + const eliminarAcidoBase = useCallback((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; + } + setState(prev => ({ + ...prev, + acidosBase: prev.acidosBase.filter(a => a.id !== id), + })); + }, [state.internaciones, state.currentUser]); + + // Acciones de cultivos + const agregarCultivo = useCallback((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(), + }; + setState(prev => ({ + ...prev, + cultivos: [...prev.cultivos, nuevoCultivo], + })); + return nuevoCultivo.id; + }, [state.internaciones, state.currentUser]); + + const actualizarCultivo = useCallback((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; + } + setState(prev => ({ + ...prev, + cultivos: prev.cultivos.map(c => c.id === id ? { ...c, ...datos } : c), + })); + }, [state.internaciones, state.currentUser]); + + const eliminarCultivo = useCallback((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; + } + setState(prev => ({ + ...prev, + cultivos: prev.cultivos.filter(c => c.id !== id), + })); + }, [state.internaciones, state.currentUser]); + + // Acciones de estudios complementarios + const agregarEstudioComplementario = useCallback((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(), + }; + setState(prev => ({ + ...prev, + estudiosComplementarios: [...prev.estudiosComplementarios, nuevoEstudio], + })); + return nuevoEstudio.id; + }, [state.internaciones, state.currentUser]); + + const actualizarEstudioComplementario = useCallback((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; + } + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.map(e => e.id === id ? { ...e, ...datos } : e), + })); + }, [state.internaciones, state.currentUser]); + + const eliminarEstudioComplementario = useCallback((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; + } + setState(prev => ({ + ...prev, + estudiosComplementarios: prev.estudiosComplementarios.filter(e => e.id !== id), + })); + }, [state.internaciones, state.currentUser]); + + // Acciones de interconsultas + const agregarInterconsulta = useCallback((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() }; + setState(prev => ({ + ...prev, + interconsultas: [...(prev.interconsultas || []), nuevaInterconsulta], + })); + return nuevaInterconsulta.id; + }, [state.internaciones, state.currentUser]); + + const actualizarInterconsulta = useCallback((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; + } + setState(prev => ({ + ...prev, + interconsultas: (prev.interconsultas || []).map(ic => ic.id === id ? { ...ic, ...datos } : ic), + })); + }, [state.internaciones, state.currentUser]); + + const eliminarInterconsulta = useCallback((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; + } + setState(prev => ({ + ...prev, + interconsultas: (prev.interconsultas || []).filter(ic => ic.id !== id), + })); + }, [state.internaciones, state.currentUser]); + + const agregarATB = useCallback((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() }; + setState(prev => ({ + ...prev, + atb: [...prev.atb, nuevoATB], + })); + return nuevoATB.id; + }, [state.internaciones, state.currentUser]); + + const actualizarATB = useCallback((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; + } + setState(prev => ({ + ...prev, + atb: prev.atb.map(a => a.id === id ? { ...a, ...datos } : a), + })); + }, [state.internaciones, state.currentUser]); + + 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((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 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((area: Omit) => { + const nuevaArea: Area = { ...area, id: generateUUID() }; + setState(prev => ({ + ...prev, + areas: [...prev.areas, nuevaArea], + })); + return nuevaArea.id; + }, []); + + const actualizarArea = useCallback((id: string, datos: Partial) => { + setState(prev => ({ + ...prev, + areas: prev.areas.map(a => a.id === id ? { ...a, ...datos } : a), + })); + }, []); + + 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), + })); + }, []); + + // 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, + }; +}