Files
Lizandro Guarnizo 2abce6d4c2 feat: multi-archivo órdenes visible en enfermero, domicilios y órdenes médicas
- OrdenMedica::filtrarCampos(): agrega domicilio_id al whitelist
- Domicilio::obtener(): consulta secundaria archivos_ordenes[] con fallback
- Enfermera::agenda(): bulk-query archivos_ordenes[] por domicilio_id
- api/lab/link_ordenes_domicilio.php: API POST vincula orden_ids a domicilio_id
- conversations.php guardar(): trackea allOrdenIds[], llama link API post-save
- lab_domicilios.php: renderOrdenesDetail() multi-archivo en detalle
- enfermero_portal.php: renderOrdenesEnfermero() multi-archivo en cards
- lab_ordenes.php: botón 'Ver domicilio' cuando domicilio_id está vinculado
- migrations/: ALTER TABLE lab_ordenes_medicas ADD domicilio_id + índice
2026-03-24 21:51:29 -05:00

317 lines
12 KiB
PHP

<?php
/**
* OrdenMedica — Gestión de órdenes médicas recibidas por WhatsApp.
*
* Flujo de estados:
* pendiente → en_revision → autorizada → en_domicilio → completada
* └→ rechazada
*/
require_once __DIR__ . '/../../classes/Database.php';
require_once __DIR__ . '/ActividadAdmin.php';
class OrdenMedica {
// Estados válidos
const ESTADOS = [
'pendiente',
'en_revision',
'autorizada',
'rechazada',
'en_domicilio',
'completada',
];
private Database $db;
private ActividadAdmin $log;
public function __construct() {
$this->db = Database::getInstance();
$this->log = new ActividadAdmin();
}
// ─────────────────────────────────────────────────────────────────────────
// CRUD
// ─────────────────────────────────────────────────────────────────────────
/**
* Lista paginada de órdenes con filtros.
*
* @param array $filtros estado, paciente_id, desde, hasta, busqueda
*/
public function listar(array $filtros = [], int $pagina = 1, int $porPagina = 30): array {
$wheres = [];
$params = [];
if (!empty($filtros['estado'])) {
$wheres[] = 'o.estado = ?';
$params[] = $filtros['estado'];
}
if (!empty($filtros['paciente_id'])) {
$wheres[] = 'o.paciente_id = ?';
$params[] = $filtros['paciente_id'];
}
if (!empty($filtros['desde'])) {
$wheres[] = 'DATE(o.created_at) >= ?';
$params[] = $filtros['desde'];
}
if (!empty($filtros['hasta'])) {
$wheres[] = 'DATE(o.created_at) <= ?';
$params[] = $filtros['hasta'];
}
if (!empty($filtros['busqueda'])) {
$like = '%' . $filtros['busqueda'] . '%';
$wheres[] = '(p.nombre_completo LIKE ? OR p.numero_documento LIKE ?)';
$params[] = $like;
$params[] = $like;
}
$where = $wheres ? 'WHERE ' . implode(' AND ', $wheres) : '';
$offset = ($pagina - 1) * $porPagina;
$total = $this->db->fetch(
"SELECT COUNT(*) AS n
FROM lab_ordenes_medicas o
JOIN lab_pacientes p ON p.id = o.paciente_id
$where",
$params
)['n'] ?? 0;
$rows = $this->db->fetchAll("
SELECT
o.*,
p.nombre_completo AS paciente_nombre,
p.numero_documento,
p.telefono AS paciente_telefono,
rev.full_name AS revisada_por_nombre,
aut.full_name AS autorizada_por_nombre
FROM lab_ordenes_medicas o
JOIN lab_pacientes p ON p.id = o.paciente_id
LEFT JOIN admin_users rev ON rev.id = o.revisada_por
LEFT JOIN admin_users aut ON aut.id = o.autorizada_por
$where
ORDER BY o.created_at DESC
LIMIT ? OFFSET ?
", array_merge($params, [$porPagina, $offset]));
return [
'data' => $rows,
'total' => (int)$total,
'pagina' => $pagina,
'por_pagina' => $porPagina,
'paginas' => (int)ceil($total / $porPagina),
];
}
/**
* Obtener una orden por ID (con todos los datos relacionados).
*/
public function obtener(int $id): ?array {
$orden = $this->db->fetch("
SELECT
o.*,
p.nombre_completo AS paciente_nombre,
p.numero_documento,
p.telefono AS paciente_telefono,
p.eps AS paciente_eps,
p.user_id,
u.phone_number,
rev.full_name AS revisada_por_nombre,
aut.full_name AS autorizada_por_nombre,
c.local_file AS conv_local_file,
c.whatsapp_media_id AS conv_media_id
FROM lab_ordenes_medicas o
JOIN lab_pacientes p ON p.id = o.paciente_id
LEFT JOIN users u ON u.id = p.user_id
LEFT JOIN admin_users rev ON rev.id = o.revisada_por
LEFT JOIN admin_users aut ON aut.id = o.autorizada_por
LEFT JOIN conversations c ON c.id = o.conversation_id
WHERE o.id = ?
", [$id]);
if (!$orden) {
return null;
}
// Historial de cambios de estado
$orden['historial'] = $this->db->fetchAll("
SELECT
la.*,
au.full_name AS admin_nombre
FROM lab_autorizaciones la
LEFT JOIN admin_users au ON au.id = la.realizada_por
WHERE la.orden_id = ?
ORDER BY la.created_at ASC
", [$id]);
return $orden;
}
/**
* Crear nueva orden médica.
*/
public function crear(array $datos, ?int $adminId = null): int {
$campos = $this->filtrarCampos($datos);
$id = $this->db->insert('lab_ordenes_medicas', $campos);
// Registrar en historial
$this->registrarCambioEstado($id, null, 'pendiente', $adminId, 'Orden creada', 'creada');
$this->log->registrar($adminId, 'ordenes', 'crear', $id, $campos);
return $id;
}
/**
* Actualizar datos de la orden (sin cambiar estado).
*/
public function actualizar(int $id, array $datos, ?int $adminId = null): bool {
$campos = $this->filtrarCampos($datos);
unset($campos['estado']); // el estado se cambia con cambiarEstado()
$ok = $this->db->update('lab_ordenes_medicas', $campos, 'id = ?', [$id]);
if ($ok) {
$this->registrarCambioEstado($id, null, null, $adminId, 'Datos actualizados', 'editada');
$this->log->registrar($adminId, 'ordenes', 'editar', $id, $campos);
}
return $ok > 0;
}
// ─────────────────────────────────────────────────────────────────────────
// Gestión de estados (flujo de revisión)
// ─────────────────────────────────────────────────────────────────────────
/**
* Cambia el estado de la orden con trazabilidad completa.
*
* @param int $id
* @param string $nuevoEstado Debe estar en self::ESTADOS
* @param int $adminId
* @param string $comentario
*/
public function cambiarEstado(
int $id,
string $nuevoEstado,
int $adminId,
string $comentario = ''
): bool {
if (!in_array($nuevoEstado, self::ESTADOS)) {
throw new InvalidArgumentException("Estado inválido: $nuevoEstado");
}
$orden = $this->obtener($id);
if (!$orden) {
throw new RuntimeException("Orden #$id no encontrada");
}
$estadoAnterior = $orden['estado'];
$ahora = date('Y-m-d H:i:s');
$campos = ['estado' => $nuevoEstado];
// Campos adicionales según el nuevo estado
if ($nuevoEstado === 'en_revision') {
$campos['revisada_por'] = $adminId;
$campos['revisada_at'] = $ahora;
} elseif (in_array($nuevoEstado, ['autorizada', 'rechazada'])) {
$campos['autorizada_por'] = $adminId;
$campos['autorizada_at'] = $ahora;
$campos['comentario_revision'] = $comentario;
}
$ok = $this->db->update('lab_ordenes_medicas', $campos, 'id = ?', [$id]);
if ($ok) {
$this->registrarCambioEstado($id, $estadoAnterior, $nuevoEstado, $adminId, $comentario);
$this->log->registrar($adminId, 'ordenes', $nuevoEstado, $id, [
'estado_anterior' => $estadoAnterior,
'comentario' => $comentario,
]);
}
return $ok > 0;
}
// Alias de conveniencia
public function autorizar(int $id, int $adminId, string $comentario = ''): bool {
return $this->cambiarEstado($id, 'autorizada', $adminId, $comentario);
}
public function rechazar(int $id, int $adminId, string $motivo): bool {
return $this->cambiarEstado($id, 'rechazada', $adminId, $motivo);
}
public function iniciarRevision(int $id, int $adminId): bool {
return $this->cambiarEstado($id, 'en_revision', $adminId);
}
// ─────────────────────────────────────────────────────────────────────────
// Estadísticas / Dashboard
// ─────────────────────────────────────────────────────────────────────────
/**
* Conteo de órdenes por estado.
*/
public function contadorPorEstado(): array {
$rows = $this->db->fetchAll("
SELECT estado, COUNT(*) AS total
FROM lab_ordenes_medicas
GROUP BY estado
");
$result = array_fill_keys(self::ESTADOS, 0);
foreach ($rows as $row) {
$result[$row['estado']] = (int)$row['total'];
}
return $result;
}
/**
* Órdenes pendientes más antiguas (sin revisar).
*/
public function pendientesAntiguos(int $limit = 10): array {
return $this->db->fetchAll("
SELECT
o.*,
p.nombre_completo,
TIMESTAMPDIFF(HOUR, o.created_at, NOW()) AS horas_espera
FROM lab_ordenes_medicas o
JOIN lab_pacientes p ON p.id = o.paciente_id
WHERE o.estado = 'pendiente'
ORDER BY o.created_at ASC
LIMIT ?
", [$limit]);
}
// ─────────────────────────────────────────────────────────────────────────
// Helpers privados
// ─────────────────────────────────────────────────────────────────────────
private function registrarCambioEstado(
int $ordenId,
?string $estadoAnterior,
?string $estadoNuevo,
?int $adminId,
string $comentario = '',
string $accion = ''
): void {
$accion = $accion ?: ($estadoNuevo ?? 'editada');
$this->db->insert('lab_autorizaciones', [
'orden_id' => $ordenId,
'accion' => $accion,
'estado_anterior'=> $estadoAnterior,
'estado_nuevo' => $estadoNuevo,
'realizada_por' => $adminId,
'comentario' => $comentario,
]);
}
private function filtrarCampos(array $datos): array {
$permitidos = [
'paciente_id', 'conversation_id', 'whatsapp_media_id', 'local_file',
'domicilio_id', 'estado', 'medico_nombre', 'medico_registro', 'fecha_orden',
'diagnostico', 'examenes_solicitados', 'requiere_ayuno',
'horas_ayuno', 'indicaciones', 'revisada_por', 'revisada_at',
'autorizada_por', 'autorizada_at', 'comentario_revision', 'notas_admin',
];
return array_intersect_key($datos, array_flip($permitidos));
}
}