diff($nacimiento)->y; } catch (Exception $e) { return null; } } /** * Quita los campos clínicos sensibles de un registro de paciente * cuando quien consulta no es el profesional. */ function filtrarSegunRol($paciente) { if (esProfesional()) { return $paciente; } unset( $paciente['motivo_consulta'], $paciente['patologia'], $paciente['sintomas'], $paciente['observaciones_generales'], $paciente['sesiones'] ); return $paciente; } /** * Confirma que el paciente con este ID pertenece al profesional * activo de la sesión. Si no, corta con 404 (no se revela ni * siquiera que el paciente existe, para no filtrar información * de otros profesionales). */ function obtenerPacienteDelProfesionalActivo($pdo, $id, $profesionalActivoId) { $stmt = $pdo->prepare(' SELECT p.*, o.nombre AS obra_social_nombre, s.nombre AS sede_nombre FROM pacientes p LEFT JOIN obras_sociales o ON o.id = p.obra_social_id LEFT JOIN sedes s ON s.id = p.sede_id WHERE p.id = ? AND p.profesional_id = ? '); $stmt->execute([$id, $profesionalActivoId]); $paciente = $stmt->fetch(); if (!$paciente) { http_response_code(404); echo json_encode(['ok' => false, 'error' => 'Legajo no encontrado.']); exit; } return $paciente; } // ------------------------------------------------------------ // CREAR LEGAJO (POST ?accion=crear) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'crear') { $d = json_decode(file_get_contents('php://input'), true); $errores = validarPaciente($d); if (!empty($errores)) { http_response_code(400); echo json_encode(['ok' => false, 'error' => implode(' ', $errores)]); exit; } $esAdmin = !esProfesional(); $sedeId = $_SESSION['sede_id'] ?? null; try { $stmt = $pdo->prepare(' INSERT INTO pacientes (profesional_id, sede_id, nombre, apellido, dni, fecha_nacimiento, sexo, obra_social_id, numero_afiliado, telefono, email, direccion, motivo_consulta, patologia, sintomas, observaciones_generales) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '); $stmt->execute([ $profesionalActivoId, $sedeId, trim($d['nombre']), trim($d['apellido']), trim($d['dni']), $d['fecha_nacimiento'], $d['sexo'], $d['obra_social_id'] ?: null, $d['numero_afiliado'] ?? null, $d['telefono'] ?? null, $d['email'] ?? null, $d['direccion'] ?? null, $esAdmin ? null : ($d['motivo_consulta'] ?? null), $esAdmin ? null : ($d['patologia'] ?? null), $esAdmin ? null : ($d['sintomas'] ?? null), $esAdmin ? null : ($d['observaciones_generales'] ?? null), ]); $id = $pdo->lastInsertId(); if (!$esAdmin && !empty($d['sesiones']) && is_array($d['sesiones'])) { $stmtS = $pdo->prepare('INSERT INTO sesiones (paciente_id, fecha_sesion, descripcion, evolucion, proxima_cita) VALUES (?, ?, ?, ?, ?)'); $stmtC = $pdo->prepare("INSERT INTO citas (paciente_id, profesional_id, fecha, motivo, estado) VALUES (?, ?, ?, 'Próxima cita agendada desde sesión anterior', 'pendiente')"); foreach ($d['sesiones'] as $s) { if (empty($s['fecha_sesion']) || empty($s['descripcion'])) continue; $stmtS->execute([$id, $s['fecha_sesion'], $s['descripcion'], $s['evolucion'] ?? null, $s['proxima_cita'] ?? null]); if (!empty($s['proxima_cita'])) { $stmtC->execute([$id, $profesionalActivoId, $s['proxima_cita']]); } } } registrarAuditoria($pdo, 'crear', 'paciente', $id, "Se creó el legajo de {$d['nombre']} {$d['apellido']}."); echo json_encode(['ok' => true, 'id' => $id, 'mensaje' => 'Legajo creado correctamente.']); } catch (PDOException $e) { if ($e->getCode() === '23000') { http_response_code(409); echo json_encode(['ok' => false, 'error' => 'Ya existe un legajo con ese DNI.']); } else { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'Error al guardar el legajo.']); } } exit; } // ------------------------------------------------------------ // AGREGAR SESIÓN A LEGAJO EXISTENTE (POST ?accion=agregar_sesion) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'agregar_sesion') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); if (empty($d['paciente_id']) || empty($d['fecha_sesion']) || empty($d['descripcion'])) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Faltan datos de la sesión.']); exit; } obtenerPacienteDelProfesionalActivo($pdo, $d['paciente_id'], $profesionalActivoId); $stmt = $pdo->prepare('INSERT INTO sesiones (paciente_id, fecha_sesion, descripcion, evolucion, proxima_cita) VALUES (?, ?, ?, ?, ?)'); $stmt->execute([$d['paciente_id'], $d['fecha_sesion'], $d['descripcion'], $d['evolucion'] ?? null, $d['proxima_cita'] ?? null]); if (!empty($d['proxima_cita'])) { $stmtC = $pdo->prepare("INSERT INTO citas (paciente_id, profesional_id, fecha, motivo, estado) VALUES (?, ?, ?, 'Próxima cita agendada desde sesión anterior', 'pendiente')"); $stmtC->execute([$d['paciente_id'], $profesionalActivoId, $d['proxima_cita']]); } registrarAuditoria($pdo, 'crear', 'sesion', $pdo->lastInsertId(), "Se agregó una sesión del " . $d['fecha_sesion'] . "."); echo json_encode(['ok' => true, 'id' => $pdo->lastInsertId()]); exit; } // ------------------------------------------------------------ // EDITAR SESIÓN EXISTENTE (POST ?accion=editar_sesion) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'editar_sesion') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); if (empty($d['id']) || empty($d['fecha_sesion']) || empty($d['descripcion'])) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Faltan datos de la sesión.']); exit; } $stmtCheck = $pdo->prepare(' SELECT s.id FROM sesiones s INNER JOIN pacientes p ON p.id = s.paciente_id WHERE s.id = ? AND p.profesional_id = ? '); $stmtCheck->execute([$d['id'], $profesionalActivoId]); if (!$stmtCheck->fetch()) { http_response_code(404); echo json_encode(['ok' => false, 'error' => 'Sesión no encontrada.']); exit; } $stmt = $pdo->prepare('UPDATE sesiones SET fecha_sesion = ?, descripcion = ?, evolucion = ? WHERE id = ?'); $stmt->execute([$d['fecha_sesion'], $d['descripcion'], $d['evolucion'] ?? null, $d['id']]); registrarAuditoria($pdo, 'editar', 'sesion', $d['id'], "Se editó una sesión del " . $d['fecha_sesion'] . "."); echo json_encode(['ok' => true, 'mensaje' => 'Sesión actualizada.']); exit; } // ------------------------------------------------------------ // ELIMINAR SESIÓN (POST ?accion=eliminar_sesion) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'eliminar_sesion') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); $idSesion = $d['id'] ?? 0; $stmtCheck = $pdo->prepare(' SELECT s.id, s.fecha_sesion FROM sesiones s INNER JOIN pacientes p ON p.id = s.paciente_id WHERE s.id = ? AND p.profesional_id = ? '); $stmtCheck->execute([$idSesion, $profesionalActivoId]); $sesion = $stmtCheck->fetch(); if (!$sesion) { http_response_code(404); echo json_encode(['ok' => false, 'error' => 'Sesión no encontrada.']); exit; } $pdo->prepare('DELETE FROM sesiones WHERE id = ?')->execute([$idSesion]); registrarAuditoria($pdo, 'eliminar', 'sesion', $idSesion, "Se eliminó una sesión del " . $sesion['fecha_sesion'] . "."); echo json_encode(['ok' => true, 'mensaje' => 'Sesión eliminada.']); exit; } // ------------------------------------------------------------ // ACTUALIZAR DATOS DE LEGAJO (POST ?accion=actualizar) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'actualizar') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); if (empty($d['id'])) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Falta el ID del legajo.']); exit; } $errores = validarPaciente($d); if (!empty($errores)) { http_response_code(400); echo json_encode(['ok' => false, 'error' => implode(' ', $errores)]); exit; } obtenerPacienteDelProfesionalActivo($pdo, $d['id'], $profesionalActivoId); try { $stmt = $pdo->prepare(' UPDATE pacientes SET nombre = ?, apellido = ?, dni = ?, fecha_nacimiento = ?, sexo = ?, obra_social_id = ?, numero_afiliado = ?, telefono = ?, email = ?, direccion = ?, motivo_consulta = ?, patologia = ?, sintomas = ?, observaciones_generales = ? WHERE id = ? AND profesional_id = ? '); $stmt->execute([ trim($d['nombre']), trim($d['apellido']), trim($d['dni']), $d['fecha_nacimiento'], $d['sexo'], $d['obra_social_id'] ?: null, $d['numero_afiliado'] ?? null, $d['telefono'] ?? null, $d['email'] ?? null, $d['direccion'] ?? null, $d['motivo_consulta'] ?? null, $d['patologia'] ?? null, $d['sintomas'] ?? null, $d['observaciones_generales'] ?? null, $d['id'], $profesionalActivoId ]); registrarAuditoria($pdo, 'editar', 'paciente', $d['id'], "Se editaron los datos de {$d['nombre']} {$d['apellido']}."); echo json_encode(['ok' => true, 'mensaje' => 'Legajo actualizado.']); } catch (PDOException $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'Error al actualizar. Verificá que el DNI no esté repetido.']); } exit; } // ------------------------------------------------------------ // MIGRAR PACIENTE A OTRA SEDE (POST ?accion=migrar_sede) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'migrar_sede') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); $id = $d['id'] ?? 0; $sedeNuevaId = $d['sede_id'] ?? 0; $paciente = obtenerPacienteDelProfesionalActivo($pdo, $id, $profesionalActivoId); $stmtSede = $pdo->prepare('SELECT nombre FROM sedes WHERE id = ? AND activa = 1'); $stmtSede->execute([$sedeNuevaId]); $sedeNueva = $stmtSede->fetch(); if (!$sedeNueva) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Sede no válida.']); exit; } $pdo->prepare('UPDATE pacientes SET sede_id = ? WHERE id = ?')->execute([$sedeNuevaId, $id]); registrarAuditoria($pdo, 'editar', 'paciente', $id, "Se migró a {$paciente['nombre']} {$paciente['apellido']} a la sede \"{$sedeNueva['nombre']}\"."); echo json_encode(['ok' => true, 'mensaje' => 'Paciente migrado a la nueva sede.']); exit; } // ------------------------------------------------------------ // LISTAR SEDES DISPONIBLES PARA MIGRAR (GET ?accion=sedes_disponibles) // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'sedes_disponibles') { $stmt = $pdo->prepare(' SELECT s.id, s.nombre FROM sedes s INNER JOIN usuarios_sedes us ON us.sede_id = s.id WHERE us.usuario_id = ? AND s.activa = 1 ORDER BY s.nombre ASC '); $stmt->execute([$profesionalActivoId]); echo json_encode(['ok' => true, 'datos' => $stmt->fetchAll()]); exit; } // ------------------------------------------------------------ // BUSCAR LEGAJOS (GET ?accion=buscar&tipo=dni|nombre|fecha|obra_social|sede&...) // Siempre acotado al profesional_id activo de la sesión. // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'buscar') { $tipo = $_GET['tipo'] ?? ''; $sqlBase = ' SELECT p.*, o.nombre AS obra_social_nombre, s.nombre AS sede_nombre FROM pacientes p LEFT JOIN obras_sociales o ON o.id = p.obra_social_id LEFT JOIN sedes s ON s.id = p.sede_id '; $condiciones = ['p.profesional_id = ?']; $params = [$profesionalActivoId]; if ($tipo === 'dni') { $condiciones[] = 'p.dni LIKE ?'; $params[] = '%' . trim($_GET['valor'] ?? '') . '%'; } elseif ($tipo === 'nombre') { $condiciones[] = '(p.nombre LIKE ? OR p.apellido LIKE ? OR CONCAT(p.nombre, " ", p.apellido) LIKE ?)'; $valor = '%' . trim($_GET['valor'] ?? '') . '%'; $params[] = $valor; $params[] = $valor; $params[] = $valor; } elseif ($tipo === 'fecha') { $desde = $_GET['desde'] ?? null; $hasta = $_GET['hasta'] ?? null; $sqlBase = ' SELECT DISTINCT p.*, o.nombre AS obra_social_nombre, s.nombre AS sede_nombre FROM pacientes p LEFT JOIN obras_sociales o ON o.id = p.obra_social_id LEFT JOIN sedes s ON s.id = p.sede_id INNER JOIN sesiones se ON se.paciente_id = p.id '; if ($desde) { $condiciones[] = 'se.fecha_sesion >= ?'; $params[] = $desde; } if ($hasta) { $condiciones[] = 'se.fecha_sesion <= ?'; $params[] = $hasta; } } elseif ($tipo === 'obra_social') { $condiciones[] = 'p.obra_social_id = ?'; $params[] = $_GET['obra_social_id'] ?? 0; } elseif ($tipo === 'sede') { $condiciones[] = 'p.sede_id = ?'; $params[] = $_GET['sede_id'] ?? 0; } $sql = $sqlBase . ' WHERE ' . implode(' AND ', $condiciones) . ' ORDER BY p.apellido ASC, p.nombre ASC'; $stmt = $pdo->prepare($sql); $stmt->execute($params); $resultados = $stmt->fetchAll(); foreach ($resultados as &$r) { $r['edad'] = calcularEdad($r['fecha_nacimiento']); $r = filtrarSegunRol($r); } echo json_encode(['ok' => true, 'datos' => $resultados, 'total' => count($resultados)]); exit; } // ------------------------------------------------------------ // VER DETALLE DE UN LEGAJO (GET ?accion=detalle&id=X) // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'detalle') { $id = $_GET['id'] ?? 0; $paciente = obtenerPacienteDelProfesionalActivo($pdo, $id, $profesionalActivoId); $paciente['edad'] = calcularEdad($paciente['fecha_nacimiento']); if (esProfesional()) { $stmtSesiones = $pdo->prepare('SELECT * FROM sesiones WHERE paciente_id = ? ORDER BY fecha_sesion DESC'); $stmtSesiones->execute([$id]); $paciente['sesiones'] = $stmtSesiones->fetchAll(); } $paciente = filtrarSegunRol($paciente); echo json_encode(['ok' => true, 'datos' => $paciente]); exit; } // ------------------------------------------------------------ // ELIMINAR LEGAJO (POST ?accion=eliminar) // ------------------------------------------------------------ if ($metodo === 'POST' && $accion === 'eliminar') { requiereRolProfesional(); $d = json_decode(file_get_contents('php://input'), true); $id = $d['id'] ?? 0; $paciente = obtenerPacienteDelProfesionalActivo($pdo, $id, $profesionalActivoId); $stmtSesiones = $pdo->prepare('SELECT * FROM sesiones WHERE paciente_id = ?'); $stmtSesiones->execute([$id]); $paciente['sesiones'] = $stmtSesiones->fetchAll(); $pdo->beginTransaction(); try { $stmtArchivo = $pdo->prepare(' INSERT INTO legajos_eliminados (paciente_id_original, profesional_id_original, sede_id_original, nombre_completo, dni, datos_json) VALUES (?, ?, ?, ?, ?, ?) '); $stmtArchivo->execute([ $id, $paciente['profesional_id'], $paciente['sede_id'], $paciente['nombre'] . ' ' . $paciente['apellido'], $paciente['dni'], json_encode($paciente, JSON_UNESCAPED_UNICODE) ]); $stmtBorrar = $pdo->prepare('DELETE FROM pacientes WHERE id = ?'); $stmtBorrar->execute([$id]); $pdo->commit(); registrarAuditoria($pdo, 'eliminar', 'paciente', $id, "Se eliminó el legajo de {$paciente['nombre']} {$paciente['apellido']}."); echo json_encode(['ok' => true, 'mensaje' => 'Legajo eliminado del sistema activo. Queda resguardado en la base histórica.']); } catch (Exception $e) { $pdo->rollBack(); http_response_code(500); echo json_encode(['ok' => false, 'error' => 'No se pudo eliminar el legajo.']); } exit; } // ------------------------------------------------------------ // VER PAPELERA / BASE HISTÓRICA (GET ?accion=papelera) // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'papelera') { requiereRolProfesional(); $stmt = $pdo->query('SELECT id, paciente_id_original, nombre_completo, dni, eliminado_en FROM legajos_eliminados ORDER BY eliminado_en DESC'); echo json_encode(['ok' => true, 'datos' => $stmt->fetchAll()]); exit; } // ------------------------------------------------------------ // VER DETALLE DE UN LEGAJO ELIMINADO (GET ?accion=papelera_detalle&id=X) // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'papelera_detalle') { requiereRolProfesional(); $id = $_GET['id'] ?? 0; $stmt = $pdo->prepare('SELECT * FROM legajos_eliminados WHERE id = ?'); $stmt->execute([$id]); $fila = $stmt->fetch(); if (!$fila) { http_response_code(404); echo json_encode(['ok' => false, 'error' => 'Registro no encontrado.']); exit; } $fila['datos'] = json_decode($fila['datos_json'], true); unset($fila['datos_json']); echo json_encode(['ok' => true, 'datos' => $fila]); exit; } // ------------------------------------------------------------ // EXPORTACIÓN MASIVA (GET ?accion=exportar_todo) — descarga un // único archivo .json con todos los legajos activos del // profesional, cada uno con sus sesiones, citas y la lista de // adjuntos (metadata, no los archivos físicos en sí). Pensado // como backup propio, fuera de la base de datos. // ------------------------------------------------------------ if ($metodo === 'GET' && $accion === 'exportar_todo') { requiereRolProfesional(); $stmtPacientes = $pdo->prepare(' SELECT p.*, o.nombre AS obra_social_nombre, s.nombre AS sede_nombre FROM pacientes p LEFT JOIN obras_sociales o ON o.id = p.obra_social_id LEFT JOIN sedes s ON s.id = p.sede_id WHERE p.profesional_id = ? ORDER BY p.apellido ASC, p.nombre ASC '); $stmtPacientes->execute([$profesionalActivoId]); $pacientes = $stmtPacientes->fetchAll(); $stmtSesiones = $pdo->prepare('SELECT * FROM sesiones WHERE paciente_id = ? ORDER BY fecha_sesion ASC'); $stmtCitas = $pdo->prepare('SELECT fecha, hora, motivo, estado, notas, confirmada_por_paciente FROM citas WHERE paciente_id = ? ORDER BY fecha ASC'); $stmtAdjuntos = $pdo->prepare('SELECT nombre_original, tipo_mime, tamanio_bytes, descripcion, subido_en FROM archivos_adjuntos WHERE paciente_id = ?'); foreach ($pacientes as &$p) { $p['edad'] = calcularEdad($p['fecha_nacimiento']); $stmtSesiones->execute([$p['id']]); $p['sesiones'] = $stmtSesiones->fetchAll(); $stmtCitas->execute([$p['id']]); $p['citas'] = $stmtCitas->fetchAll(); $stmtAdjuntos->execute([$p['id']]); $p['adjuntos'] = $stmtAdjuntos->fetchAll(); } $exportacion = [ 'sistema' => 'Del Austral', 'tipo' => 'Exportación completa de legajos', 'generado_en' => date('Y-m-d H:i:s'), 'total_pacientes' => count($pacientes), 'pacientes' => $pacientes, ]; $nombreArchivo = 'del-austral-export-' . date('Y-m-d') . '.json'; header('Content-Type: application/json; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $nombreArchivo . '"'); echo json_encode($exportacion, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); exit; } http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Solicitud no válida.']);