feat: sistema de notas clínicas para enfermero (ficha + notas libres) y fix lab_reportes métricas
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* POST /api/lab/delete_nota_domicilio.php
|
||||
* Elimina una nota libre de domicilio.
|
||||
* Las notas tipo='clinica' NO se pueden eliminar (solo editar/vaciar).
|
||||
*
|
||||
* Body JSON: { id }
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
|
||||
try {
|
||||
$datos = inputJson();
|
||||
$id = (int)($datos['id'] ?? 0);
|
||||
if (!$id) jsonError('id requerido');
|
||||
|
||||
$db = Database::getInstance();
|
||||
$nota = $db->fetch('SELECT * FROM lab_domicilio_notas WHERE id = ?', [$id]);
|
||||
if (!$nota) jsonError('Nota no encontrada', 404);
|
||||
|
||||
if ($nota['tipo'] === 'clinica') {
|
||||
jsonError('La ficha clínica no puede eliminarse, solo editarse');
|
||||
}
|
||||
|
||||
// Solo el dueño o admin puede borrar
|
||||
if (userRole() === 'enfermero' && (int)$nota['enfermera_id'] !== enfermeraId()) {
|
||||
jsonError('Sin permiso para eliminar esta nota', 403);
|
||||
}
|
||||
|
||||
// Eliminar imagen adjunta si existe
|
||||
if (!empty($nota['imagen_path'])) {
|
||||
$imgPath = realpath(__DIR__ . '/../../uploads/media/' . $nota['imagen_path']);
|
||||
if ($imgPath && file_exists($imgPath)) {
|
||||
@unlink($imgPath);
|
||||
}
|
||||
}
|
||||
|
||||
$db->delete('lab_domicilio_notas', 'id = ?', [$id]);
|
||||
jsonOk(['id' => $id], 'Nota eliminada');
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[delete_nota_domicilio] ' . $e->getMessage());
|
||||
jsonError($e->getMessage(), 500);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* GET /api/lab/get_notas_domicilio.php
|
||||
* Devuelve todas las notas de un domicilio.
|
||||
*
|
||||
* Parámetros GET:
|
||||
* domicilio_id (requerido)
|
||||
*
|
||||
* Respuesta: { success, data: [ nota, … ] }
|
||||
* Orden: ficha clínica primero, luego libres por created_at ASC
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('GET');
|
||||
|
||||
try {
|
||||
$domId = (int)($_GET['domicilio_id'] ?? 0);
|
||||
if (!$domId) jsonError('domicilio_id requerido');
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Verificar acceso: enfermero solo puede ver si está asignado
|
||||
if (userRole() === 'enfermero') {
|
||||
$enfId = enfermeraId();
|
||||
$asig = $db->fetch(
|
||||
"SELECT id FROM lab_asignaciones
|
||||
WHERE domicilio_id = ? AND enfermera_id = ?",
|
||||
[$domId, $enfId]
|
||||
);
|
||||
if (!$asig) jsonError('Sin acceso a este domicilio', 403);
|
||||
}
|
||||
|
||||
$notas = $db->fetchAll(
|
||||
"SELECT n.*, e.nombre_completo AS enfermera_nombre
|
||||
FROM lab_domicilio_notas n
|
||||
LEFT JOIN lab_enfermeras e ON e.id = n.enfermera_id
|
||||
WHERE n.domicilio_id = ?
|
||||
ORDER BY FIELD(n.tipo,'clinica','libre'), n.created_at ASC",
|
||||
[$domId]
|
||||
);
|
||||
|
||||
jsonOk(['data' => $notas]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[get_notas_domicilio] ' . $e->getMessage());
|
||||
jsonError($e->getMessage(), 500);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* POST /api/lab/save_nota_domicilio.php
|
||||
* Crea o actualiza una nota de domicilio.
|
||||
*
|
||||
* Body JSON:
|
||||
* { domicilio_id, tipo } — campos comunes (para crear)
|
||||
* { id, tipo, … } — actualizar por id
|
||||
*
|
||||
* Campos tipo='clinica':
|
||||
* antecedentes, medicamentos, acudiente_nombre, acudiente_documento
|
||||
*
|
||||
* Campos tipo='libre':
|
||||
* titulo (requerido), cuerpo, imagen_path
|
||||
*
|
||||
* Reglas:
|
||||
* · Solo puede existir UNA nota tipo='clinica' por domicilio.
|
||||
* Si ya existe y no viene `id`, se hace UPSERT automático.
|
||||
* · Las notas 'libre' son ilimitadas.
|
||||
* · Solo el enfermero dueño o un admin puede editar/crear.
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
|
||||
// Tags HTML permitidos en el cuerpo de notas libres (desde contenteditable)
|
||||
const ALLOWED_HTML_TAGS = '<b><i><u><strong><em><s><strike><ul><ol><li><br><p><div>';
|
||||
|
||||
try {
|
||||
$datos = inputJson();
|
||||
$db = Database::getInstance();
|
||||
|
||||
$tipo = $datos['tipo'] ?? '';
|
||||
if (!in_array($tipo, ['clinica', 'libre'], true)) {
|
||||
jsonError('tipo debe ser "clinica" o "libre"');
|
||||
}
|
||||
|
||||
// ── Actualizar nota existente ─────────────────────────────────────────
|
||||
if (!empty($datos['id'])) {
|
||||
$id = (int)$datos['id'];
|
||||
$nota = $db->fetch('SELECT * FROM lab_domicilio_notas WHERE id = ?', [$id]);
|
||||
if (!$nota) jsonError('Nota no encontrada', 404);
|
||||
|
||||
// Verificar propiedad
|
||||
if (userRole() === 'enfermero' && (int)$nota['enfermera_id'] !== enfermeraId()) {
|
||||
jsonError('No puede editar esta nota', 403);
|
||||
}
|
||||
|
||||
$campos = _extraerCampos($datos, $tipo);
|
||||
$db->update('lab_domicilio_notas', $campos, 'id = ?', [$id]);
|
||||
jsonOk(['id' => $id], 'Nota actualizada');
|
||||
}
|
||||
|
||||
// ── Crear nota nueva (o upsert clínica) ───────────────────────────────
|
||||
$domId = (int)($datos['domicilio_id'] ?? 0);
|
||||
if (!$domId) jsonError('domicilio_id requerido');
|
||||
|
||||
// Verificar que el domicilio existe
|
||||
$dom = $db->fetch('SELECT id FROM lab_domicilios WHERE id = ?', [$domId]);
|
||||
if (!$dom) jsonError('Domicilio no encontrado', 404);
|
||||
|
||||
// Verificar acceso
|
||||
if (userRole() === 'enfermero') {
|
||||
$enfId = enfermeraId();
|
||||
$asig = $db->fetch(
|
||||
"SELECT id FROM lab_asignaciones WHERE domicilio_id = ? AND enfermera_id = ?",
|
||||
[$domId, $enfId]
|
||||
);
|
||||
if (!$asig) jsonError('Sin acceso a este domicilio', 403);
|
||||
}
|
||||
|
||||
$enfId = (userRole() === 'enfermero') ? enfermeraId() : (int)($datos['enfermera_id'] ?? 0);
|
||||
|
||||
// Para nota clínica: solo puede haber una por domicilio → UPDATE si ya existe
|
||||
if ($tipo === 'clinica') {
|
||||
$existente = $db->fetch(
|
||||
'SELECT id FROM lab_domicilio_notas WHERE domicilio_id = ? AND tipo = "clinica"',
|
||||
[$domId]
|
||||
);
|
||||
$campos = _extraerCampos($datos, 'clinica');
|
||||
if ($existente) {
|
||||
$db->update('lab_domicilio_notas', $campos, 'id = ?', [(int)$existente['id']]);
|
||||
jsonOk(['id' => (int)$existente['id']], 'Ficha clínica actualizada');
|
||||
} else {
|
||||
$campos['domicilio_id'] = $domId;
|
||||
$campos['enfermera_id'] = $enfId;
|
||||
$campos['tipo'] = 'clinica';
|
||||
$id = $db->insert('lab_domicilio_notas', $campos);
|
||||
jsonOk(['id' => $id], 'Ficha clínica creada');
|
||||
}
|
||||
}
|
||||
|
||||
// Nota libre
|
||||
if (empty(trim($datos['titulo'] ?? ''))) jsonError('El título es obligatorio');
|
||||
$campos = _extraerCampos($datos, 'libre');
|
||||
$campos['domicilio_id'] = $domId;
|
||||
$campos['enfermera_id'] = $enfId;
|
||||
$campos['tipo'] = 'libre';
|
||||
$id = $db->insert('lab_domicilio_notas', $campos);
|
||||
jsonOk(['id' => $id], 'Nota creada');
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[save_nota_domicilio] ' . $e->getMessage());
|
||||
jsonError($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
// ── Helper interno ────────────────────────────────────────────────────────
|
||||
function _extraerCampos(array $datos, string $tipo): array {
|
||||
if ($tipo === 'clinica') {
|
||||
return array_filter([
|
||||
'antecedentes' => isset($datos['antecedentes']) ? trim($datos['antecedentes']) : null,
|
||||
'medicamentos' => isset($datos['medicamentos']) ? trim($datos['medicamentos']) : null,
|
||||
'acudiente_nombre' => isset($datos['acudiente_nombre']) ? trim($datos['acudiente_nombre']) : null,
|
||||
'acudiente_documento' => isset($datos['acudiente_documento']) ? trim($datos['acudiente_documento']) : null,
|
||||
], fn($v) => $v !== null);
|
||||
}
|
||||
// libre
|
||||
$cuerpo = isset($datos['cuerpo'])
|
||||
? strip_tags((string)$datos['cuerpo'], ALLOWED_HTML_TAGS)
|
||||
: null;
|
||||
return array_filter([
|
||||
'titulo' => isset($datos['titulo']) ? mb_substr(trim($datos['titulo']), 0, 200) : null,
|
||||
'cuerpo' => $cuerpo ?: null,
|
||||
'imagen_path' => isset($datos['imagen_path']) ? preg_replace('/[^a-zA-Z0-9._\-]/', '', (string)$datos['imagen_path']) : null,
|
||||
], fn($v) => $v !== null);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* POST /api/lab/upload_nota_imagen.php
|
||||
* Sube una imagen adjunta a una nota libre de domicilio.
|
||||
*
|
||||
* POST multipart/form-data con campo 'file'
|
||||
* Devuelve: { success, imagen_path, file_size }
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/config.php';
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
$code = $_FILES['file']['error'] ?? -1;
|
||||
echo json_encode(['success' => false, 'error' => "No se recibió imagen (código $code)"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_FILES['file'];
|
||||
|
||||
// Máximo 5 MB para imágenes de notas
|
||||
if ($file['size'] > 5 * 1024 * 1024) {
|
||||
echo json_encode(['success' => false, 'error' => 'La imagen no puede superar 5 MB']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validar MIME real (solo imágenes)
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($mime, $allowedMimes, true)) {
|
||||
echo json_encode(['success' => false, 'error' => "Tipo de archivo no permitido ($mime). Solo imágenes."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Extensión segura
|
||||
$mimeToExt = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif'];
|
||||
$ext = $mimeToExt[$mime] ?? 'jpg';
|
||||
$newName = 'nota_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
|
||||
|
||||
$uploadDir = __DIR__ . '/../../uploads/media';
|
||||
if (!is_dir($uploadDir)) {
|
||||
@mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
$destPath = realpath($uploadDir) . '/' . $newName;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Error al guardar el archivo en el servidor']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'imagen_path' => $newName,
|
||||
'file_size' => $file['size'],
|
||||
]);
|
||||
Reference in New Issue
Block a user