up
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Helpers compartidos para los endpoints del módulo Registro de Exámenes.
|
||||
* Incluido al inicio de cada archivo en modules/registro_exams/api/.
|
||||
*/
|
||||
|
||||
ob_start();
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
ini_set('display_errors', '0');
|
||||
ini_set('html_errors', '0');
|
||||
|
||||
register_shutdown_function(function () {
|
||||
$err = error_get_last();
|
||||
if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||||
ob_clean();
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => 'Error interno: ' . $err['message']]);
|
||||
}
|
||||
});
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Respuestas JSON ──────────────────────────────────────────
|
||||
|
||||
function jsonOk(array $payload = [], string $mensaje = ''): void
|
||||
{
|
||||
ob_clean();
|
||||
$resp = ['ok' => true, 'success' => true];
|
||||
if ($mensaje) {
|
||||
$resp['message'] = $mensaje;
|
||||
}
|
||||
echo json_encode(array_merge($resp, $payload), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function jsonError(string $mensaje, int $code = 400): void
|
||||
{
|
||||
ob_clean();
|
||||
http_response_code($code);
|
||||
echo json_encode(['ok' => false, 'success' => false, 'error' => $mensaje], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function requireMethod(string $method): void
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== strtoupper($method)) {
|
||||
jsonError('Método no permitido', 405);
|
||||
}
|
||||
}
|
||||
|
||||
function inputJson(): array
|
||||
{
|
||||
static $parsed = null;
|
||||
if ($parsed === null) {
|
||||
$raw = file_get_contents('php://input');
|
||||
$parsed = json_decode($raw, true) ?? [];
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
// ── Autenticación ────────────────────────────────────────────
|
||||
|
||||
function requireAuth(): void
|
||||
{
|
||||
if (empty($_SESSION['admin_user']['id'])) {
|
||||
jsonError('No autenticado', 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exige sesión + acceso al módulo registro_exams.
|
||||
*/
|
||||
function requireExams(): void
|
||||
{
|
||||
requireAuth();
|
||||
$modules = $_SESSION['admin_user']['modules'] ?? [];
|
||||
$roleId = $_SESSION['admin_user']['role_id'] ?? null;
|
||||
|
||||
// Superadmin / admin global = acceso total
|
||||
if (empty($roleId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('registro_exams', (array) $modules, true)) {
|
||||
jsonError('Sin acceso al módulo Registro Exámenes', 403);
|
||||
}
|
||||
}
|
||||
|
||||
function adminId(): ?int
|
||||
{
|
||||
return isset($_SESSION['admin_user']['id'])
|
||||
? (int) $_SESSION['admin_user']['id']
|
||||
: null;
|
||||
}
|
||||
|
||||
// ── Helper BD ────────────────────────────────────────────────
|
||||
|
||||
function db(): PDO
|
||||
{
|
||||
return Database::getInstance()->getConnection();
|
||||
}
|
||||
|
||||
// ── Código de orden ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Genera el siguiente código de orden legible: EX-YYYYMMDD-NNNN
|
||||
* Usa SELECT FOR UPDATE para evitar duplicados en concurrencia.
|
||||
*/
|
||||
function generarCodigoOrden(): string
|
||||
{
|
||||
$pdo = db();
|
||||
$hoy = date('Ymd');
|
||||
$prefijo = "EX-{$hoy}-";
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COALESCE(MAX(CAST(SUBSTRING(codigo, -4) AS UNSIGNED)), 0) + 1 AS siguiente
|
||||
FROM exam_ordenes
|
||||
WHERE codigo LIKE ?
|
||||
FOR UPDATE"
|
||||
);
|
||||
$stmt->execute(["{$prefijo}%"]);
|
||||
$n = (int) $stmt->fetchColumn();
|
||||
|
||||
return $prefijo . str_pad($n, 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
// ── Etiqueta de prioridad ────────────────────────────────────
|
||||
|
||||
function badgePrioridad(string $p): string
|
||||
{
|
||||
return match ($p) {
|
||||
'urgente' => '<span class="badge bg-warning text-dark">Urgente</span>',
|
||||
'stat' => '<span class="badge bg-danger">STAT</span>',
|
||||
default => '<span class="badge bg-secondary">Normal</span>',
|
||||
};
|
||||
}
|
||||
|
||||
function badgeEstadoOrden(string $e): string
|
||||
{
|
||||
return match ($e) {
|
||||
'en_proceso' => '<span class="badge bg-primary">En proceso</span>',
|
||||
'completa' => '<span class="badge bg-success">Completa</span>',
|
||||
'entregada' => '<span class="badge bg-info text-dark">Entregada</span>',
|
||||
'cancelada' => '<span class="badge bg-dark">Cancelada</span>',
|
||||
default => '<span class="badge bg-light text-dark border">Pendiente</span>',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/registro_exams/api/cambiar_estado_item.php
|
||||
* POST — cambia el estado de un ítem, muestra o de la orden entera.
|
||||
*
|
||||
* Body JSON:
|
||||
* {
|
||||
* "tipo": "item" | "muestra" | "orden",
|
||||
* "id": int,
|
||||
* "estado": string, // valor según ENUM de la tabla
|
||||
* "notas": string // opcional, solo para ítem
|
||||
* }
|
||||
*
|
||||
* Adicional para tipo="muestra" (crear muestra si id=null):
|
||||
* {
|
||||
* "tipo": "muestra",
|
||||
* "id": null, // null = crear nueva
|
||||
* "orden_id": int,
|
||||
* "tipo_muestra": "sangre_venosa" | ...,
|
||||
* "codigo_barras": string,
|
||||
* "estado": "tomada"
|
||||
* }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
requireExams();
|
||||
|
||||
$body = inputJson();
|
||||
$tipo = $body['tipo'] ?? '';
|
||||
|
||||
$estadosItem = ['pendiente','muestra_tomada','en_proceso','resultado_listo','entregado'];
|
||||
$estadosMuestra = ['pendiente','tomada','procesando','procesada','rechazada'];
|
||||
$estadosOrden = ['pendiente','en_proceso','completa','entregada','cancelada'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
switch ($tipo) {
|
||||
|
||||
// ── Cambiar estado de un ítem individual ──────────────────────
|
||||
case 'item': {
|
||||
$id = isset($body['id']) ? (int) $body['id'] : 0;
|
||||
$estado = $body['estado'] ?? '';
|
||||
if ($id <= 0 || !in_array($estado, $estadosItem, true)) {
|
||||
jsonError('Parámetros inválidos para ítem');
|
||||
}
|
||||
$notas = isset($body['notas']) && $body['notas'] !== '' ? $body['notas'] : null;
|
||||
|
||||
// Obtener orden_id para actualizar padre si aplica
|
||||
$stmtG = $pdo->prepare('SELECT orden_id FROM exam_items WHERE id = ?');
|
||||
$stmtG->execute([$id]);
|
||||
$item = $stmtG->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$item) {
|
||||
jsonError('Ítem no encontrado', 404);
|
||||
}
|
||||
|
||||
$pdo->prepare(
|
||||
'UPDATE exam_items SET estado = ?, notas_tecnico = ?, creado_at = creado_at WHERE id = ?'
|
||||
)->execute([$estado, $notas, $id]);
|
||||
|
||||
// Si todos los ítems de la orden están listos → marcar orden como completa
|
||||
if ($estado === 'resultado_listo' || $estado === 'entregado') {
|
||||
$stmtC = $pdo->prepare(
|
||||
"SELECT COUNT(*) FROM exam_items
|
||||
WHERE orden_id = ?
|
||||
AND estado NOT IN ('resultado_listo','entregado')"
|
||||
);
|
||||
$stmtC->execute([$item['orden_id']]);
|
||||
if ((int) $stmtC->fetchColumn() === 0) {
|
||||
$pdo->prepare(
|
||||
"UPDATE exam_ordenes SET estado = 'completa', updated_at = NOW() WHERE id = ?"
|
||||
)->execute([$item['orden_id']]);
|
||||
}
|
||||
}
|
||||
|
||||
jsonOk([], 'Estado actualizado');
|
||||
}
|
||||
|
||||
// ── Crear o actualizar una muestra ──────────────────────────
|
||||
case 'muestra': {
|
||||
$id = isset($body['id']) && $body['id'] !== null ? (int) $body['id'] : null;
|
||||
$estado = $body['estado'] ?? 'tomada';
|
||||
$ordenId = isset($body['orden_id']) ? (int) $body['orden_id'] : 0;
|
||||
$tipoMuest = $body['tipo_muestra'] ?? 'sangre_venosa';
|
||||
$barcode = trim($body['codigo_barras'] ?? '');
|
||||
|
||||
if (!in_array($estado, $estadosMuestra, true)) {
|
||||
jsonError('Estado de muestra inválido');
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
// Crear nueva muestra
|
||||
if ($ordenId <= 0) {
|
||||
jsonError('orden_id requerido para crear muestra');
|
||||
}
|
||||
$pdo->prepare('
|
||||
INSERT INTO exam_muestras
|
||||
(orden_id, tipo_muestra, codigo_barras, tomada_por, tomada_at, estado)
|
||||
VALUES (?, ?, ?, ?, NOW(), ?)
|
||||
')->execute([$ordenId, $tipoMuest, $barcode ?: null, adminId(), $estado]);
|
||||
|
||||
$newId = (int) $pdo->lastInsertId();
|
||||
|
||||
// Marcar ítems como muestra_tomada si están pendientes
|
||||
$pdo->prepare(
|
||||
"UPDATE exam_items SET estado = 'muestra_tomada'
|
||||
WHERE orden_id = ? AND estado = 'pendiente'"
|
||||
)->execute([$ordenId]);
|
||||
|
||||
// Actualizar orden a en_proceso si aún está pendiente
|
||||
$pdo->prepare(
|
||||
"UPDATE exam_ordenes SET estado = 'en_proceso', updated_at = NOW()
|
||||
WHERE id = ? AND estado = 'pendiente'"
|
||||
)->execute([$ordenId]);
|
||||
|
||||
jsonOk(['muestra_id' => $newId], 'Muestra registrada');
|
||||
} else {
|
||||
// Actualizar muestra existente
|
||||
$pdo->prepare(
|
||||
'UPDATE exam_muestras SET estado = ?, codigo_barras = ? WHERE id = ?'
|
||||
)->execute([$estado, $barcode ?: null, $id]);
|
||||
|
||||
jsonOk([], 'Muestra actualizada');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cambiar estado de la orden completa ─────────────────────
|
||||
case 'orden': {
|
||||
$id = isset($body['id']) ? (int) $body['id'] : 0;
|
||||
$estado = $body['estado'] ?? '';
|
||||
if ($id <= 0 || !in_array($estado, $estadosOrden, true)) {
|
||||
jsonError('Parámetros inválidos para orden');
|
||||
}
|
||||
$pdo->prepare(
|
||||
'UPDATE exam_ordenes SET estado = ?, updated_at = NOW() WHERE id = ?'
|
||||
)->execute([$estado, $id]);
|
||||
|
||||
jsonOk([], 'Estado de orden actualizado');
|
||||
}
|
||||
|
||||
default:
|
||||
jsonError('Tipo de operación desconocido: ' . $tipo);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/registro_exams/api/get_orden.php
|
||||
* GET ?id=<orden_id>
|
||||
* Devuelve la orden completa: datos, paciente, ítems, muestras, resultados.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('GET');
|
||||
requireExams();
|
||||
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
if ($id <= 0) {
|
||||
jsonError('ID de orden inválido');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Orden + paciente
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT
|
||||
o.id, o.codigo, o.prioridad, o.estado,
|
||||
o.requiere_ayuno, o.horas_ayuno,
|
||||
o.medico_nombre, o.medico_registro,
|
||||
o.diagnostico, o.notas,
|
||||
o.solicitud_id, o.orden_medica_id,
|
||||
o.creado_por, o.creado_at, o.updated_at,
|
||||
p.id AS paciente_id,
|
||||
p.nombre_completo,
|
||||
p.numero_documento,
|
||||
p.tipo_documento,
|
||||
p.telefono,
|
||||
p.email,
|
||||
p.fecha_nacimiento,
|
||||
p.genero,
|
||||
p.eps
|
||||
FROM exam_ordenes o
|
||||
JOIN lab_pacientes p ON p.id = o.paciente_id
|
||||
WHERE o.id = ?
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$orden = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$orden) {
|
||||
jsonError('Orden no encontrada', 404);
|
||||
}
|
||||
|
||||
// Calcular edad
|
||||
if ($orden['fecha_nacimiento']) {
|
||||
$diff = (new DateTime($orden['fecha_nacimiento']))->diff(new DateTime());
|
||||
$orden['edad'] = $diff->y;
|
||||
} else {
|
||||
$orden['edad'] = null;
|
||||
}
|
||||
|
||||
// Ítems
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT
|
||||
ei.id, ei.orden_id, ei.exam_tipo_id, ei.estado, ei.notas_tecnico, ei.creado_at,
|
||||
et.codigo AS tipo_codigo, et.nombre AS tipo_nombre,
|
||||
et.categoria, et.requiere_ayuno AS tipo_requiere_ayuno,
|
||||
et.instrucciones AS tipo_instrucciones
|
||||
FROM exam_items ei
|
||||
JOIN exam_tipos et ON et.id = ei.exam_tipo_id
|
||||
WHERE ei.orden_id = ?
|
||||
ORDER BY et.categoria, et.nombre
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Resultados por ítem
|
||||
$resultadosPorItem = [];
|
||||
if ($items) {
|
||||
$itemIds = array_column($items, 'id');
|
||||
$inPh = implode(',', array_fill(0, count($itemIds), '?'));
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT er.*, au.nombre AS ingresado_nombre
|
||||
FROM exam_resultados er
|
||||
LEFT JOIN admin_users au ON au.id = er.ingresado_por
|
||||
WHERE er.item_id IN ({$inPh})
|
||||
ORDER BY er.item_id, er.id
|
||||
");
|
||||
$stmt->execute($itemIds);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $res) {
|
||||
$resultadosPorItem[(int) $res['item_id']][] = $res;
|
||||
}
|
||||
}
|
||||
|
||||
// Agregar resultados a cada ítem
|
||||
foreach ($items as &$item) {
|
||||
$item['resultados'] = $resultadosPorItem[(int) $item['id']] ?? [];
|
||||
}
|
||||
unset($item);
|
||||
|
||||
// Muestras
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT em.*, au.nombre AS tomada_nombre
|
||||
FROM exam_muestras em
|
||||
LEFT JOIN admin_users au ON au.id = em.tomada_por
|
||||
WHERE em.orden_id = ?
|
||||
ORDER BY em.id
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$muestras = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
jsonOk([
|
||||
'data' => array_merge($orden, [
|
||||
'items' => $items,
|
||||
'muestras' => $muestras,
|
||||
]),
|
||||
]);
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/registro_exams/api/get_ordenes.php
|
||||
* GET ?fecha=YYYY-MM-DD &estado=pendiente &busqueda=texto &page=1 &limit=25
|
||||
* Devuelve lista de órdenes con datos del paciente e ítems (count).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('GET');
|
||||
requireExams();
|
||||
|
||||
$pdo = db();
|
||||
|
||||
$fecha = trim($_GET['fecha'] ?? date('Y-m-d'));
|
||||
$estado = trim($_GET['estado'] ?? '');
|
||||
$busqueda = trim($_GET['busqueda'] ?? '');
|
||||
$page = max(1, (int) ($_GET['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int) ($_GET['limit'] ?? 25)));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// Validar fecha
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
|
||||
$fecha = date('Y-m-d');
|
||||
}
|
||||
|
||||
$where = ['DATE(o.creado_at) = ?'];
|
||||
$params = [$fecha];
|
||||
|
||||
if ($estado !== '') {
|
||||
$allowed = ['pendiente', 'en_proceso', 'completa', 'entregada', 'cancelada'];
|
||||
if (in_array($estado, $allowed, true)) {
|
||||
$where[] = 'o.estado = ?';
|
||||
$params[] = $estado;
|
||||
}
|
||||
}
|
||||
|
||||
if ($busqueda !== '') {
|
||||
$like = '%' . $busqueda . '%';
|
||||
$where[] = '(p.nombre_completo LIKE ? OR p.numero_documento LIKE ? OR o.codigo LIKE ?)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
$whereSQL = 'WHERE ' . implode(' AND ', $where);
|
||||
|
||||
// Total para paginación
|
||||
$countStmt = $pdo->prepare(
|
||||
"SELECT COUNT(*) FROM exam_ordenes o
|
||||
JOIN lab_pacientes p ON p.id = o.paciente_id
|
||||
{$whereSQL}"
|
||||
);
|
||||
$countStmt->execute($params);
|
||||
$total = (int) $countStmt->fetchColumn();
|
||||
|
||||
// Datos
|
||||
$sql = "
|
||||
SELECT
|
||||
o.id, o.codigo, o.prioridad, o.estado, o.requiere_ayuno, o.horas_ayuno,
|
||||
o.medico_nombre, o.diagnostico, o.notas,
|
||||
o.creado_at, o.updated_at,
|
||||
p.id AS paciente_id,
|
||||
p.nombre_completo,
|
||||
p.numero_documento,
|
||||
p.tipo_documento,
|
||||
p.telefono,
|
||||
p.fecha_nacimiento,
|
||||
(SELECT COUNT(*) FROM exam_items ei WHERE ei.orden_id = o.id) AS total_items,
|
||||
(SELECT COUNT(*) FROM exam_items ei WHERE ei.orden_id = o.id AND ei.estado = 'resultado_listo') AS items_listos,
|
||||
(SELECT COUNT(*) FROM exam_muestras em WHERE em.orden_id = o.id AND em.estado = 'tomada') AS muestras_tomadas
|
||||
FROM exam_ordenes o
|
||||
JOIN lab_pacientes p ON p.id = o.paciente_id
|
||||
{$whereSQL}
|
||||
ORDER BY
|
||||
FIELD(o.prioridad, 'stat', 'urgente', 'normal'),
|
||||
o.creado_at ASC
|
||||
LIMIT {$limit} OFFSET {$offset}
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$ordenes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Calcular edad en años para cada paciente
|
||||
foreach ($ordenes as &$row) {
|
||||
if ($row['fecha_nacimiento']) {
|
||||
$diff = (new DateTime($row['fecha_nacimiento']))->diff(new DateTime());
|
||||
$row['edad'] = $diff->y;
|
||||
} else {
|
||||
$row['edad'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonOk([
|
||||
'data' => $ordenes,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'pages' => (int) ceil($total / $limit),
|
||||
'fecha' => $fecha,
|
||||
]);
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/registro_exams/api/save_orden.php
|
||||
* POST — crea o actualiza una orden de examen.
|
||||
*
|
||||
* Body JSON:
|
||||
* {
|
||||
* "id": null | int, // null = nueva / int = editar
|
||||
* "paciente_id": int, // requerido
|
||||
* "solicitud_id": null | int,
|
||||
* "orden_medica_id": null | int,
|
||||
* "medico_nombre": string,
|
||||
* "medico_registro": string,
|
||||
* "diagnostico": string,
|
||||
* "prioridad": "normal"|"urgente"|"stat",
|
||||
* "notas": string,
|
||||
* "items": [int, int, ...] // exam_tipo_id[]
|
||||
* }
|
||||
*
|
||||
* Respuesta exitosa:
|
||||
* { ok: true, id: <orden_id>, codigo: "EX-YYYYMMDD-NNNN" }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
requireExams();
|
||||
|
||||
$body = inputJson();
|
||||
|
||||
// ── Validaciones ──────────────────────────────────────────────
|
||||
$pacienteId = isset($body['paciente_id']) ? (int) $body['paciente_id'] : 0;
|
||||
if ($pacienteId <= 0) {
|
||||
jsonError('Debe indicar el paciente (paciente_id)');
|
||||
}
|
||||
|
||||
$items = isset($body['items']) && is_array($body['items'])
|
||||
? array_map('intval', $body['items'])
|
||||
: [];
|
||||
|
||||
// Filtrar zeros
|
||||
$items = array_values(array_filter($items, fn($v) => $v > 0));
|
||||
if (count($items) === 0) {
|
||||
jsonError('Debe agregar al menos un examen');
|
||||
}
|
||||
|
||||
// Normalizar prioridad
|
||||
$prioridadesPermitidas = ['normal', 'urgente', 'stat'];
|
||||
$prioridad = in_array($body['prioridad'] ?? '', $prioridadesPermitidas, true)
|
||||
? $body['prioridad'] : 'normal';
|
||||
|
||||
$ordenId = isset($body['id']) && $body['id'] ? (int) $body['id'] : null;
|
||||
$solicitudId = isset($body['solicitud_id']) ? ((int) $body['solicitud_id'] ?: null) : null;
|
||||
$ordenMedId = isset($body['orden_medica_id']) ? ((int) $body['orden_medica_id'] ?: null) : null;
|
||||
$medicoNombre = trim($body['medico_nombre'] ?? '');
|
||||
$medicoReg = trim($body['medico_registro'] ?? '');
|
||||
$diagnostico = trim($body['diagnostico'] ?? '');
|
||||
$notas = trim($body['notas'] ?? '');
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($ordenId === null) {
|
||||
// ── CREAR nueva orden ──────────────────────────────────
|
||||
$codigo = generarCodigoOrden();
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO exam_ordenes
|
||||
(paciente_id, solicitud_id, orden_medica_id, codigo,
|
||||
medico_nombre, medico_registro, diagnostico,
|
||||
prioridad, notas, creado_por, creado_at)
|
||||
VALUES
|
||||
(?, ?, ?, ?,
|
||||
?, ?, ?,
|
||||
?, ?, ?, NOW())
|
||||
');
|
||||
$stmt->execute([
|
||||
$pacienteId, $solicitudId, $ordenMedId, $codigo,
|
||||
$medicoNombre ?: null, $medicoReg ?: null, $diagnostico ?: null,
|
||||
$prioridad, $notas ?: null, adminId(),
|
||||
]);
|
||||
$ordenId = (int) $pdo->lastInsertId();
|
||||
|
||||
} else {
|
||||
// ── ACTUALIZAR orden existente ─────────────────────────
|
||||
$stmt = $pdo->prepare('
|
||||
UPDATE exam_ordenes SET
|
||||
medico_nombre = ?,
|
||||
medico_registro = ?,
|
||||
diagnostico = ?,
|
||||
prioridad = ?,
|
||||
notas = ?,
|
||||
updated_at = NOW()
|
||||
WHERE id = ?
|
||||
');
|
||||
$stmt->execute([
|
||||
$medicoNombre ?: null, $medicoReg ?: null, $diagnostico ?: null,
|
||||
$prioridad, $notas ?: null, $ordenId,
|
||||
]);
|
||||
|
||||
// Leer código actual para devolverlo
|
||||
$stmt2 = $pdo->prepare('SELECT codigo FROM exam_ordenes WHERE id = ?');
|
||||
$stmt2->execute([$ordenId]);
|
||||
$codigo = $stmt2->fetchColumn() ?: '';
|
||||
}
|
||||
|
||||
// ── Sincronizar ítems ──────────────────────────────────────
|
||||
// Obtener tipos ya existentes en la orden
|
||||
$stmtEx = $pdo->prepare(
|
||||
'SELECT exam_tipo_id FROM exam_items WHERE orden_id = ?'
|
||||
);
|
||||
$stmtEx->execute([$ordenId]);
|
||||
$existentes = array_column($stmtEx->fetchAll(PDO::FETCH_ASSOC), 'exam_tipo_id');
|
||||
$existentes = array_map('intval', $existentes);
|
||||
|
||||
// Insertar los que no existen aún
|
||||
$nuevos = array_diff($items, $existentes);
|
||||
if ($nuevos) {
|
||||
$stmtIns = $pdo->prepare(
|
||||
'INSERT INTO exam_items (orden_id, exam_tipo_id) VALUES (?, ?)'
|
||||
);
|
||||
foreach ($nuevos as $tipoId) {
|
||||
$stmtIns->execute([$ordenId, $tipoId]);
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar los que ya no están en la lista (solo si aún están pendientes)
|
||||
$eliminar = array_diff($existentes, $items);
|
||||
if ($eliminar) {
|
||||
$inPlaceholders = implode(',', array_fill(0, count($eliminar), '?'));
|
||||
$stmtDel = $pdo->prepare(
|
||||
"DELETE FROM exam_items
|
||||
WHERE orden_id = ?
|
||||
AND exam_tipo_id IN ({$inPlaceholders})
|
||||
AND estado = 'pendiente'"
|
||||
);
|
||||
$stmtDel->execute(array_merge([$ordenId], array_values($eliminar)));
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
jsonOk(['id' => $ordenId, 'codigo' => $codigo], 'Orden guardada correctamente');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
jsonError('Error al guardar la orden: ' . $e->getMessage(), 500);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/registro_exams/api/save_resultado.php
|
||||
* POST — agrega o reemplaza resultados de un ítem de examen.
|
||||
*
|
||||
* Body JSON:
|
||||
* {
|
||||
* "item_id": int, // requerido
|
||||
* "resultados": [
|
||||
* {
|
||||
* "campo_nombre": "Hemoglobina",
|
||||
* "valor_texto": "14.5",
|
||||
* "valor_numerico": 14.5, // null si no aplica
|
||||
* "unidad": "g/dL",
|
||||
* "referencia_min": 12.0, // null si no aplica
|
||||
* "referencia_max": 16.0,
|
||||
* "referencia_texto":"12.0 – 16.0 g/dL",
|
||||
* "es_anormal": false
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* Respuesta: { ok: true, inserted: N }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
requireExams();
|
||||
|
||||
$body = inputJson();
|
||||
$itemId = isset($body['item_id']) ? (int) $body['item_id'] : 0;
|
||||
|
||||
if ($itemId <= 0) {
|
||||
jsonError('item_id inválido');
|
||||
}
|
||||
|
||||
$resultados = $body['resultados'] ?? [];
|
||||
if (!is_array($resultados) || count($resultados) === 0) {
|
||||
jsonError('Debe enviar al menos un resultado');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Verificar que el ítem existe
|
||||
$stmt = $pdo->prepare('SELECT id, orden_id FROM exam_items WHERE id = ?');
|
||||
$stmt->execute([$itemId]);
|
||||
$item = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$item) {
|
||||
jsonError('Ítem no encontrado', 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Borrar resultados anteriores del ítem (reemplazo completo)
|
||||
$pdo->prepare('DELETE FROM exam_resultados WHERE item_id = ?')->execute([$itemId]);
|
||||
|
||||
$ins = $pdo->prepare('
|
||||
INSERT INTO exam_resultados
|
||||
(item_id, campo_nombre, valor_texto, valor_numerico,
|
||||
unidad, referencia_min, referencia_max, referencia_texto,
|
||||
es_anormal, ingresado_por)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
|
||||
$inserted = 0;
|
||||
foreach ($resultados as $r) {
|
||||
$campo = trim($r['campo_nombre'] ?? '');
|
||||
if ($campo === '') {
|
||||
continue;
|
||||
}
|
||||
$ins->execute([
|
||||
$itemId,
|
||||
$campo,
|
||||
isset($r['valor_texto']) && $r['valor_texto'] !== '' ? $r['valor_texto'] : null,
|
||||
isset($r['valor_numerico']) && $r['valor_numerico'] !== null ? (float) $r['valor_numerico'] : null,
|
||||
isset($r['unidad']) && $r['unidad'] !== '' ? $r['unidad'] : null,
|
||||
isset($r['referencia_min']) && $r['referencia_min'] !== null ? (float) $r['referencia_min'] : null,
|
||||
isset($r['referencia_max']) && $r['referencia_max'] !== null ? (float) $r['referencia_max'] : null,
|
||||
isset($r['referencia_texto']) && $r['referencia_texto'] !== '' ? $r['referencia_texto'] : null,
|
||||
empty($r['es_anormal']) ? 0 : 1,
|
||||
adminId(),
|
||||
]);
|
||||
$inserted++;
|
||||
}
|
||||
|
||||
if ($inserted === 0) {
|
||||
$pdo->rollBack();
|
||||
jsonError('No se guardó ningún resultado (verifique los campos)');
|
||||
}
|
||||
|
||||
// Actualizar estado del ítem a resultado_listo
|
||||
$pdo->prepare(
|
||||
"UPDATE exam_items SET estado = 'resultado_listo' WHERE id = ?"
|
||||
)->execute([$itemId]);
|
||||
|
||||
// Verificar si todos los ítems de la orden están listos para cerrarla
|
||||
$stmtCheck = $pdo->prepare(
|
||||
"SELECT COUNT(*) FROM exam_items
|
||||
WHERE orden_id = ?
|
||||
AND estado NOT IN ('resultado_listo','entregado')"
|
||||
);
|
||||
$stmtCheck->execute([$item['orden_id']]);
|
||||
$pendientes = (int) $stmtCheck->fetchColumn();
|
||||
|
||||
if ($pendientes === 0) {
|
||||
$pdo->prepare(
|
||||
"UPDATE exam_ordenes SET estado = 'completa', updated_at = NOW() WHERE id = ?"
|
||||
)->execute([$item['orden_id']]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
jsonOk(['inserted' => $inserted], 'Resultados guardados');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
jsonError('Error al guardar resultados: ' . $e->getMessage(), 500);
|
||||
}
|
||||
Reference in New Issue
Block a user