up
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* ActividadAdmin — Trazabilidad del módulo administrativo de laboratorio
|
||||
* Registra toda acción realizada sobre las entidades del módulo.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
|
||||
class ActividadAdmin {
|
||||
|
||||
private Database $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Registro de actividad
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Registra una acción en el log.
|
||||
*
|
||||
* @param int|null $adminId admin_users.id
|
||||
* @param string $modulo pacientes|ordenes|domicilios|enfermeras|asignaciones
|
||||
* @param string $accion crear|editar|eliminar|autorizar|rechazar|asignar...
|
||||
* @param int|null $entidadId ID del registro afectado
|
||||
* @param mixed $detalle Texto o array (se serializa como JSON)
|
||||
*/
|
||||
public function registrar(
|
||||
?int $adminId,
|
||||
string $modulo,
|
||||
string $accion,
|
||||
?int $entidadId = null,
|
||||
$detalle = null
|
||||
): int {
|
||||
$adminNombre = null;
|
||||
|
||||
if ($adminId) {
|
||||
$row = $this->db->fetch(
|
||||
'SELECT full_name FROM admin_users WHERE id = ?',
|
||||
[$adminId]
|
||||
);
|
||||
$adminNombre = $row['full_name'] ?? null;
|
||||
}
|
||||
|
||||
$detalleStr = is_array($detalle) || is_object($detalle)
|
||||
? json_encode($detalle, JSON_UNESCAPED_UNICODE)
|
||||
: (string)($detalle ?? '');
|
||||
|
||||
return $this->db->insert('lab_actividad_admin', [
|
||||
'admin_id' => $adminId,
|
||||
'admin_nombre'=> $adminNombre,
|
||||
'modulo' => $modulo,
|
||||
'accion' => $accion,
|
||||
'entidad_id' => $entidadId,
|
||||
'detalle' => $detalleStr,
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Consultas
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Actividad reciente del módulo.
|
||||
*
|
||||
* @param int $limit
|
||||
* @param string $modulo Filtro opcional por módulo
|
||||
*/
|
||||
public function reciente(int $limit = 50, string $modulo = ''): array {
|
||||
$where = $modulo ? 'WHERE a.modulo = ?' : '';
|
||||
$params = $modulo ? [$modulo] : [];
|
||||
$params[] = $limit;
|
||||
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
a.*,
|
||||
u.username AS admin_username
|
||||
FROM lab_actividad_admin a
|
||||
LEFT JOIN admin_users u ON u.id = a.admin_id
|
||||
$where
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT ?
|
||||
", $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actividad de un administrador específico.
|
||||
*/
|
||||
public function porAdmin(int $adminId, int $limit = 100): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT * FROM lab_actividad_admin
|
||||
WHERE admin_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
", [$adminId, $limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actividad sobre una entidad concreta.
|
||||
*/
|
||||
public function porEntidad(string $modulo, int $entidadId): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
a.*,
|
||||
u.username AS admin_username
|
||||
FROM lab_actividad_admin a
|
||||
LEFT JOIN admin_users u ON u.id = a.admin_id
|
||||
WHERE a.modulo = ? AND a.entidad_id = ?
|
||||
ORDER BY a.created_at ASC
|
||||
", [$modulo, $entidadId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estadísticas de actividad por módulo en un rango de fechas.
|
||||
*/
|
||||
public function estadisticas(string $desde, string $hasta): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
modulo,
|
||||
accion,
|
||||
COUNT(*) AS total
|
||||
FROM lab_actividad_admin
|
||||
WHERE DATE(created_at) BETWEEN ? AND ?
|
||||
GROUP BY modulo, accion
|
||||
ORDER BY modulo, total DESC
|
||||
", [$desde, $hasta]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Asignacion — Asignación de enfermera a un domicilio.
|
||||
* Solo puede haber una asignación activa por domicilio (UNIQUE KEY).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/ActividadAdmin.php';
|
||||
|
||||
class Asignacion {
|
||||
|
||||
private Database $db;
|
||||
private ActividadAdmin $log;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
$this->log = new ActividadAdmin();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Obtener asignación por ID.
|
||||
*/
|
||||
public function obtener(int $id): ?array {
|
||||
return $this->db->fetch("
|
||||
SELECT
|
||||
a.*,
|
||||
e.nombre_completo AS enfermera_nombre,
|
||||
e.telefono AS enfermera_telefono,
|
||||
d.fecha_programada,
|
||||
d.hora_programada,
|
||||
d.estado AS domicilio_estado,
|
||||
d.direccion,
|
||||
p.nombre_completo AS paciente_nombre
|
||||
FROM lab_asignaciones a
|
||||
JOIN lab_enfermeras e ON e.id = a.enfermera_id
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
WHERE a.id = ?
|
||||
", [$id]) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asignar (o reasignar) enfermera a un domicilio.
|
||||
* - Si ya existía asignación la reemplaza.
|
||||
*/
|
||||
public function asignar(
|
||||
int $domicilioId,
|
||||
int $enfermeraId,
|
||||
?int $adminId = null,
|
||||
string $notas = ''
|
||||
): int {
|
||||
// ¿Ya existe?
|
||||
$existente = $this->db->fetch(
|
||||
'SELECT id FROM lab_asignaciones WHERE domicilio_id = ?',
|
||||
[$domicilioId]
|
||||
);
|
||||
|
||||
if ($existente) {
|
||||
$this->db->update('lab_asignaciones', [
|
||||
'enfermera_id' => $enfermeraId,
|
||||
'asignada_por' => $adminId,
|
||||
'estado' => 'asignada',
|
||||
'notas' => $notas,
|
||||
], 'id = ?', [$existente['id']]);
|
||||
|
||||
$id = $existente['id'];
|
||||
$accion = 'reasignar';
|
||||
} else {
|
||||
$id = $this->db->insert('lab_asignaciones', [
|
||||
'domicilio_id' => $domicilioId,
|
||||
'enfermera_id' => $enfermeraId,
|
||||
'asignada_por' => $adminId,
|
||||
'estado' => 'asignada',
|
||||
'notas' => $notas,
|
||||
]);
|
||||
$accion = 'asignar';
|
||||
}
|
||||
|
||||
$this->log->registrar($adminId, 'asignaciones', $accion, $id, [
|
||||
'domicilio_id' => $domicilioId,
|
||||
'enfermera_id' => $enfermeraId,
|
||||
]);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liberar asignación (sin eliminar, cambia estado a 'liberada').
|
||||
*/
|
||||
public function liberar(int $id, ?int $adminId = null, string $motivo = ''): bool {
|
||||
$ok = $this->db->update('lab_asignaciones', ['estado' => 'liberada'], 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'asignaciones', 'liberar', $id, ['motivo' => $motivo]);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marcar asignación como completada.
|
||||
*/
|
||||
public function completar(int $id, ?int $adminId = null): bool {
|
||||
$ok = $this->db->update('lab_asignaciones', ['estado' => 'completada'], 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'asignaciones', 'completar', $id);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Consultas
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Asignaciones de una fecha (para vista de agenda general).
|
||||
*/
|
||||
public function porFecha(string $fecha = ''): array {
|
||||
$fecha = $fecha ?: date('Y-m-d');
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
a.*,
|
||||
e.nombre_completo AS enfermera_nombre,
|
||||
e.telefono AS enfermera_telefono,
|
||||
d.hora_programada,
|
||||
d.estado AS domicilio_estado,
|
||||
d.direccion,
|
||||
d.barrio,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
p.telefono AS paciente_telefono
|
||||
FROM lab_asignaciones a
|
||||
JOIN lab_enfermeras e ON e.id = a.enfermera_id
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
WHERE d.fecha_programada = ?
|
||||
ORDER BY e.nombre_completo, d.hora_programada ASC
|
||||
", [$fecha]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga actual del día: cuántos domicilios tiene cada enfermera hoy.
|
||||
*/
|
||||
public function cargaHoy(): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
e.id,
|
||||
e.nombre_completo,
|
||||
e.telefono,
|
||||
COUNT(a.id) AS total_asignaciones,
|
||||
SUM(d.estado = 'completado') AS completados
|
||||
FROM lab_enfermeras e
|
||||
JOIN lab_asignaciones a ON a.enfermera_id = e.id
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
WHERE d.fecha_programada = CURDATE()
|
||||
AND a.estado NOT IN ('liberada')
|
||||
GROUP BY e.id
|
||||
ORDER BY total_asignaciones DESC
|
||||
");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
/**
|
||||
* Domicilio — Gestión de servicios a domicilio del laboratorio.
|
||||
*
|
||||
* Flujo de estados:
|
||||
* programado → confirmado → en_camino → en_domicilio → completado
|
||||
* └→ cancelado / reprogramado
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/ActividadAdmin.php';
|
||||
|
||||
class Domicilio {
|
||||
|
||||
const ESTADOS = [
|
||||
'programado',
|
||||
'confirmado',
|
||||
'en_camino',
|
||||
'en_domicilio',
|
||||
'completado',
|
||||
'cancelado',
|
||||
'reprogramado',
|
||||
];
|
||||
|
||||
private Database $db;
|
||||
private ActividadAdmin $log;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
$this->log = new ActividadAdmin();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Lista de domicilios con filtros.
|
||||
*
|
||||
* @param array $filtros fecha, estado, enfermera_id, paciente_id
|
||||
*/
|
||||
public function listar(array $filtros = [], int $pagina = 1, int $porPagina = 30): array {
|
||||
$wheres = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filtros['fecha'])) {
|
||||
$wheres[] = 'd.fecha_programada = ?';
|
||||
$params[] = $filtros['fecha'];
|
||||
}
|
||||
if (!empty($filtros['desde'])) {
|
||||
$wheres[] = 'd.fecha_programada >= ?';
|
||||
$params[] = $filtros['desde'];
|
||||
}
|
||||
if (!empty($filtros['hasta'])) {
|
||||
$wheres[] = 'd.fecha_programada <= ?';
|
||||
$params[] = $filtros['hasta'];
|
||||
}
|
||||
if (!empty($filtros['estado'])) {
|
||||
$wheres[] = 'd.estado = ?';
|
||||
$params[] = $filtros['estado'];
|
||||
}
|
||||
if (!empty($filtros['enfermera_id'])) {
|
||||
$wheres[] = 'a.enfermera_id = ?';
|
||||
$params[] = $filtros['enfermera_id'];
|
||||
}
|
||||
if (!empty($filtros['paciente_id'])) {
|
||||
$wheres[] = 'd.paciente_id = ?';
|
||||
$params[] = $filtros['paciente_id'];
|
||||
}
|
||||
|
||||
$where = $wheres ? 'WHERE ' . implode(' AND ', $wheres) : '';
|
||||
$offset = ($pagina - 1) * $porPagina;
|
||||
|
||||
$total = $this->db->fetch(
|
||||
"SELECT COUNT(*) AS n
|
||||
FROM lab_domicilios d
|
||||
LEFT JOIN lab_asignaciones a ON a.domicilio_id = d.id
|
||||
$where",
|
||||
$params
|
||||
)['n'] ?? 0;
|
||||
|
||||
$rows = $this->db->fetchAll("
|
||||
SELECT
|
||||
d.*,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
p.telefono AS paciente_telefono,
|
||||
e.nombre_completo AS enfermera_nombre,
|
||||
e.telefono AS enfermera_telefono,
|
||||
a.id AS asignacion_id,
|
||||
a.estado AS asignacion_estado
|
||||
FROM lab_domicilios d
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
LEFT JOIN lab_asignaciones a ON a.domicilio_id = d.id
|
||||
LEFT JOIN lab_enfermeras e ON e.id = a.enfermera_id
|
||||
$where
|
||||
ORDER BY d.fecha_programada ASC, d.hora_programada ASC
|
||||
LIMIT ? OFFSET ?
|
||||
", array_merge($params, [$porPagina, $offset]));
|
||||
|
||||
return [
|
||||
'data' => $rows,
|
||||
'total' => (int)$total,
|
||||
'pagina' => $pagina,
|
||||
'por_pagina' => $porPagina,
|
||||
'paginas' => (int)ceil($total / $porPagina),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Domicilio por ID con todos los datos relacionados.
|
||||
*/
|
||||
public function obtener(int $id): ?array {
|
||||
return $this->db->fetch("
|
||||
SELECT
|
||||
d.*,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
p.telefono AS paciente_telefono,
|
||||
p.direccion AS paciente_direccion,
|
||||
o.examenes_solicitados,
|
||||
o.indicaciones AS indicaciones_orden,
|
||||
e.nombre_completo AS enfermera_nombre,
|
||||
e.telefono AS enfermera_telefono,
|
||||
a.id AS asignacion_id,
|
||||
a.estado AS asignacion_estado,
|
||||
adm.full_name AS creado_por_nombre
|
||||
FROM lab_domicilios d
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
LEFT JOIN lab_ordenes_medicas o ON o.id = d.orden_id
|
||||
LEFT JOIN lab_asignaciones a ON a.domicilio_id = d.id
|
||||
LEFT JOIN lab_enfermeras e ON e.id = a.enfermera_id
|
||||
LEFT JOIN admin_users adm ON adm.id = d.creado_por
|
||||
WHERE d.id = ?
|
||||
", [$id]) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear nuevo domicilio.
|
||||
*/
|
||||
public function crear(array $datos, ?int $adminId = null): int {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$campos['creado_por'] = $adminId;
|
||||
|
||||
// Actualizar orden médica si aplica
|
||||
if (!empty($campos['orden_id'])) {
|
||||
$this->db->update(
|
||||
'lab_ordenes_medicas',
|
||||
['estado' => 'en_domicilio'],
|
||||
'id = ?', [$campos['orden_id']]
|
||||
);
|
||||
}
|
||||
|
||||
$id = (int)$this->db->insert('lab_domicilios', $campos);
|
||||
|
||||
$this->log->registrar($adminId, 'domicilios', 'crear', $id, $campos);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar domicilio.
|
||||
*/
|
||||
public function actualizar(int $id, array $datos, ?int $adminId = null): bool {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$ok = $this->db->update('lab_domicilios', $campos, 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'domicilios', 'editar', $id, $campos);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cambiar estado del domicilio.
|
||||
*/
|
||||
public function cambiarEstado(
|
||||
int $id,
|
||||
string $nuevoEstado,
|
||||
?int $adminId,
|
||||
array $extras = []
|
||||
): bool {
|
||||
if (!in_array($nuevoEstado, self::ESTADOS)) {
|
||||
throw new InvalidArgumentException("Estado inválido: $nuevoEstado");
|
||||
}
|
||||
|
||||
$campos = array_merge(['estado' => $nuevoEstado], $extras);
|
||||
$ok = $this->db->update('lab_domicilios', $campos, 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'domicilios', $nuevoEstado, $id, $extras);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Agenda del día
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Todos los domicilios de hoy (o la fecha indicada).
|
||||
*/
|
||||
public function agendaDia(string $fecha = ''): array {
|
||||
$fecha = $fecha ?: date('Y-m-d');
|
||||
return $this->listar(['fecha' => $fecha], 1, 200)['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Domicilios sin enfermera asignada.
|
||||
*/
|
||||
public function sinAsignar(string $fecha = ''): array {
|
||||
$fecha = $fecha ?: date('Y-m-d');
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
d.*,
|
||||
p.nombre_completo AS paciente_nombre
|
||||
FROM lab_domicilios d
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
LEFT JOIN lab_asignaciones a ON a.domicilio_id = d.id
|
||||
WHERE d.fecha_programada = ?
|
||||
AND a.id IS NULL
|
||||
AND d.estado NOT IN ('cancelado','completado')
|
||||
ORDER BY d.hora_programada ASC
|
||||
", [$fecha]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estadísticas: conteo de domicilios por estado hoy.
|
||||
*/
|
||||
public function estadisticasHoy(): array {
|
||||
$rows = $this->db->fetchAll("
|
||||
SELECT estado, COUNT(*) AS total
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada = CURDATE()
|
||||
GROUP BY estado
|
||||
");
|
||||
$result = array_fill_keys(self::ESTADOS, 0);
|
||||
foreach ($rows as $row) {
|
||||
$result[$row['estado']] = (int)$row['total'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private function filtrarCampos(array $datos): array {
|
||||
$permitidos = [
|
||||
'orden_id', 'paciente_id', 'direccion', 'ciudad', 'barrio',
|
||||
'indicaciones_dir', 'fecha_programada', 'hora_programada',
|
||||
'tipo_servicio', 'tipo_cliente', 'examenes_solicitados',
|
||||
'estado', 'motivo_cancelacion',
|
||||
'fecha_reprogramada', 'hora_llegada', 'hora_salida',
|
||||
'observaciones', 'muestras_tomadas', 'notas_admin',
|
||||
'seguro_nombre', 'autorizacion', 'copago_laboratorio',
|
||||
'valor_domicilio', 'valor_copago',
|
||||
];
|
||||
$campos = array_intersect_key($datos, array_flip($permitidos));
|
||||
|
||||
// Campos FK que deben ser NULL cuando vienen vacíos o cero
|
||||
foreach (['orden_id', 'paciente_id'] as $fk) {
|
||||
if (array_key_exists($fk, $campos) && ($campos[$fk] === '' || $campos[$fk] === '0' || $campos[$fk] === 0)) {
|
||||
$campos[$fk] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $campos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Enfermera — Gestión del personal de enfermería (domicilios).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/ActividadAdmin.php';
|
||||
|
||||
class Enfermera {
|
||||
|
||||
private Database $db;
|
||||
private ActividadAdmin $log;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
$this->log = new ActividadAdmin();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Lista de enfermeras activas/todas.
|
||||
*/
|
||||
public function listar(bool $soloActivas = true): array {
|
||||
$where = $soloActivas ? 'WHERE is_active = 1' : '';
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
e.*,
|
||||
(SELECT COUNT(*) FROM lab_asignaciones a
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
WHERE a.enfermera_id = e.id
|
||||
AND d.fecha_programada = CURDATE()
|
||||
AND a.estado NOT IN ('liberada','completada')
|
||||
) AS domicilios_hoy
|
||||
FROM lab_enfermeras e
|
||||
$where
|
||||
ORDER BY e.nombre_completo ASC
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enfermera por ID.
|
||||
*/
|
||||
public function obtener(int $id): ?array {
|
||||
return $this->db->fetch(
|
||||
'SELECT * FROM lab_enfermeras WHERE id = ?',
|
||||
[$id]
|
||||
) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear enfermera.
|
||||
*/
|
||||
public function crear(array $datos, ?int $adminId = null): int {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$id = $this->db->insert('lab_enfermeras', $campos);
|
||||
$this->log->registrar($adminId, 'enfermeras', 'crear', $id, $campos);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar enfermera.
|
||||
*/
|
||||
public function actualizar(int $id, array $datos, ?int $adminId = null): bool {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$ok = $this->db->update('lab_enfermeras', $campos, 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'enfermeras', 'editar', $id, $campos);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desactivar (soft-delete).
|
||||
*/
|
||||
public function desactivar(int $id, ?int $adminId = null): bool {
|
||||
$ok = $this->db->update('lab_enfermeras', ['is_active' => 0], 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'enfermeras', 'desactivar', $id);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Agenda
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Agenda completa de una enfermera (domicilios asignados con detalle).
|
||||
*
|
||||
* @param int $enfermeraId
|
||||
* @param string $fecha YYYY-MM-DD, por defecto hoy
|
||||
*/
|
||||
public function agenda(int $enfermeraId, string $fecha = ''): array {
|
||||
$fecha = $fecha ?: date('Y-m-d');
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
d.id AS domicilio_id,
|
||||
d.fecha_programada,
|
||||
d.hora_programada,
|
||||
d.estado AS domicilio_estado,
|
||||
d.direccion,
|
||||
d.barrio,
|
||||
d.tipo_servicio,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
p.telefono AS paciente_telefono,
|
||||
a.estado AS asignacion_estado,
|
||||
a.id AS asignacion_id
|
||||
FROM lab_asignaciones a
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
JOIN lab_pacientes p ON p.id = d.paciente_id
|
||||
WHERE a.enfermera_id = ?
|
||||
AND d.fecha_programada = ?
|
||||
ORDER BY d.hora_programada ASC
|
||||
", [$enfermeraId, $fecha]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga de trabajo por período: domicilios asignados por día.
|
||||
*/
|
||||
public function cargaTrabajo(int $enfermeraId, string $desde, string $hasta): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
d.fecha_programada,
|
||||
COUNT(*) AS total,
|
||||
SUM(d.estado = 'completado') AS completados
|
||||
FROM lab_asignaciones a
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
WHERE a.enfermera_id = ?
|
||||
AND d.fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY d.fecha_programada
|
||||
ORDER BY d.fecha_programada ASC
|
||||
", [$enfermeraId, $desde, $hasta]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private function filtrarCampos(array $datos): array {
|
||||
$permitidos = [
|
||||
'numero_documento', 'tipo_documento', 'nombre_completo',
|
||||
'telefono', 'telefono_alt', 'email',
|
||||
'zona', 'notas', 'is_active',
|
||||
];
|
||||
return array_intersect_key($datos, array_flip($permitidos));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Formulario — Gestión de plantillas y envíos de formularios clínicos.
|
||||
*/
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/ActividadAdmin.php';
|
||||
|
||||
class Formulario {
|
||||
|
||||
private Database $db;
|
||||
private ActividadAdmin $log;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
$this->log = new ActividadAdmin();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// PLANTILLAS (admin)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function listar(bool $soloActivos = true): array {
|
||||
$w = $soloActivos ? 'WHERE f.is_active = 1' : '';
|
||||
return $this->db->fetchAll("
|
||||
SELECT f.*, u.full_name AS creado_por_nombre,
|
||||
(SELECT COUNT(*) FROM lab_form_envios e WHERE e.formulario_id = f.id) AS total_envios,
|
||||
(SELECT COUNT(*) FROM lab_form_envios e WHERE e.formulario_id = f.id AND e.estado IN ('completado','firmado')) AS total_completados
|
||||
FROM lab_formularios f
|
||||
LEFT JOIN admin_users u ON u.id = f.creado_por
|
||||
$w
|
||||
ORDER BY f.created_at DESC
|
||||
");
|
||||
}
|
||||
|
||||
public function obtener(int $id): ?array {
|
||||
$row = $this->db->fetch(
|
||||
"SELECT f.*, u.full_name AS creado_por_nombre
|
||||
FROM lab_formularios f
|
||||
LEFT JOIN admin_users u ON u.id = f.creado_por
|
||||
WHERE f.id = ?",
|
||||
[$id]
|
||||
);
|
||||
if ($row) {
|
||||
$row['esquema_decoded'] = json_decode($row['esquema'], true) ?? [];
|
||||
}
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
public function crear(array $datos, int $adminId): int {
|
||||
$this->validarEsquema($datos['esquema'] ?? '[]');
|
||||
$id = $this->db->insert('lab_formularios', [
|
||||
'nombre' => trim($datos['nombre']),
|
||||
'descripcion' => $datos['descripcion'] ?? null,
|
||||
'categoria' => $datos['categoria'] ?? 'otro',
|
||||
'esquema' => $datos['esquema'] ?? '[]',
|
||||
'permite_firma' => !empty($datos['permite_firma']) ? 1 : 0,
|
||||
'requiere_firma' => !empty($datos['requiere_firma']) ? 1 : 0,
|
||||
'doc_encabezado' => $datos['doc_encabezado'] ?? null,
|
||||
'doc_subtitulo' => $datos['doc_subtitulo'] ?? null,
|
||||
'doc_logo_base64' => $datos['doc_logo_base64'] ?? null,
|
||||
'doc_color' => $datos['doc_color'] ?? null,
|
||||
'doc_pie_pagina' => $datos['doc_pie_pagina'] ?? null,
|
||||
'creado_por' => $adminId,
|
||||
]);
|
||||
$this->log->registrar($adminId, 'formularios', 'crear', $id, ['nombre' => $datos['nombre']]);
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function actualizar(int $id, array $datos, int $adminId): bool {
|
||||
$permitidos = [
|
||||
'nombre','descripcion','categoria','esquema',
|
||||
'permite_firma','requiere_firma','is_active',
|
||||
'doc_encabezado','doc_subtitulo','doc_logo_base64','doc_color','doc_pie_pagina',
|
||||
];
|
||||
$campos = array_intersect_key($datos, array_flip($permitidos));
|
||||
if (isset($campos['esquema'])) {
|
||||
$this->validarEsquema($campos['esquema']);
|
||||
$campos['version'] = (int)$this->db->fetch('SELECT version FROM lab_formularios WHERE id = ?', [$id])['version'] + 1;
|
||||
}
|
||||
$ok = $this->db->update('lab_formularios', $campos, 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'formularios', 'editar', $id, $campos);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// ENVÍOS
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crear un envío con datos pre-llenados, genera token único.
|
||||
*/
|
||||
public function crearEnvio(array $datos, int $enviadoPor): int {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$expira = date('Y-m-d H:i:s', strtotime('+7 days'));
|
||||
|
||||
$id = $this->db->insert('lab_form_envios', [
|
||||
'formulario_id' => (int)$datos['formulario_id'],
|
||||
'paciente_id' => !empty($datos['paciente_id']) ? (int)$datos['paciente_id'] : null,
|
||||
'domicilio_id' => !empty($datos['domicilio_id']) ? (int)$datos['domicilio_id'] : null,
|
||||
'token' => $token,
|
||||
'datos_prefilled' => $datos['datos_prefilled'] ?? '{}',
|
||||
'estado' => 'pendiente',
|
||||
'enviado_por' => $enviadoPor,
|
||||
'enviado_via' => $datos['enviado_via'] ?? 'whatsapp',
|
||||
'expira_en' => $expira,
|
||||
]);
|
||||
$this->log->registrar($enviadoPor, 'formularios', 'enviar', $id, [
|
||||
'formulario_id' => $datos['formulario_id'],
|
||||
'via' => $datos['enviado_via'] ?? 'whatsapp',
|
||||
]);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener envío por token (uso público del cliente).
|
||||
*/
|
||||
public function obtenerPorToken(string $token): ?array {
|
||||
$envio = $this->db->fetch(
|
||||
"SELECT e.*, f.nombre AS form_nombre, f.esquema, f.permite_firma, f.requiere_firma, f.descripcion AS form_descripcion,
|
||||
f.doc_color, f.doc_logo_base64, f.doc_encabezado, f.doc_subtitulo, f.doc_pie_pagina,
|
||||
p.nombre_completo AS paciente_nombre, p.numero_documento, p.telefono AS paciente_telefono
|
||||
FROM lab_form_envios e
|
||||
JOIN lab_formularios f ON f.id = e.formulario_id
|
||||
LEFT JOIN lab_pacientes p ON p.id = e.paciente_id
|
||||
WHERE e.token = ?",
|
||||
[$token]
|
||||
);
|
||||
if (!$envio) return null;
|
||||
if ($envio['estado'] === 'expirado') return null;
|
||||
if ($envio['expira_en'] && strtotime($envio['expira_en']) < time()) {
|
||||
$this->db->update('lab_form_envios', ['estado' => 'expirado'], 'id = ?', [$envio['id']]);
|
||||
return null;
|
||||
}
|
||||
$envio['esquema_decoded'] = json_decode($envio['esquema'], true) ?? [];
|
||||
$envio['prefilled_decoded'] = json_decode($envio['datos_prefilled'], true) ?? [];
|
||||
return $envio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar respuesta del cliente (firma + datos).
|
||||
*/
|
||||
public function guardarRespuesta(string $token, array $datosCliente, ?string $firmaSvg, string $ip, string $ua): bool {
|
||||
$envio = $this->db->fetch('SELECT id, estado FROM lab_form_envios WHERE token = ?', [$token]);
|
||||
if (!$envio || $envio['estado'] === 'expirado') return false;
|
||||
|
||||
$tieneCanvas = !empty($firmaSvg);
|
||||
$tieneFoto = !empty($datosCliente['__firma_foto']);
|
||||
$estado = ($tieneCanvas || $tieneFoto) ? 'firmado' : 'completado';
|
||||
|
||||
// Hash de verificación SHA-256 (vincula contenido + firma + timestamp)
|
||||
$payloadHash = json_encode($datosCliente, JSON_UNESCAPED_UNICODE)
|
||||
. ($firmaSvg ?? '')
|
||||
. $envio['id']
|
||||
. $envio['token']
|
||||
. date('Y-m-d H:i:s');
|
||||
$hash = hash('sha256', $payloadHash);
|
||||
|
||||
$this->db->update('lab_form_envios', [
|
||||
'datos_cliente' => json_encode($datosCliente, JSON_UNESCAPED_UNICODE),
|
||||
'firma_svg' => $firmaSvg,
|
||||
'hash_verificacion' => $hash,
|
||||
'ip_cliente' => $ip,
|
||||
'user_agent' => substr($ua, 0, 512),
|
||||
'estado' => $estado,
|
||||
'completado_en' => date('Y-m-d H:i:s'),
|
||||
], 'token = ?', [$token]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listar envíos (admin/enfermero).
|
||||
*/
|
||||
public function listarEnvios(array $filtros = []): array {
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
if (!empty($filtros['formulario_id'])) { $where[] = 'e.formulario_id = ?'; $params[] = $filtros['formulario_id']; }
|
||||
if (!empty($filtros['paciente_id'])) { $where[] = 'e.paciente_id = ?'; $params[] = $filtros['paciente_id']; }
|
||||
if (!empty($filtros['enviado_por'])) { $where[] = 'e.enviado_por = ?'; $params[] = $filtros['enviado_por']; }
|
||||
if (!empty($filtros['estado'])) { $where[] = 'e.estado = ?'; $params[] = $filtros['estado']; }
|
||||
$w = implode(' AND ', $where);
|
||||
return $this->db->fetchAll("
|
||||
SELECT e.*, f.nombre AS form_nombre, f.categoria, f.esquema,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
u.full_name AS enviado_por_nombre
|
||||
FROM lab_form_envios e
|
||||
JOIN lab_formularios f ON f.id = e.formulario_id
|
||||
LEFT JOIN lab_pacientes p ON p.id = e.paciente_id
|
||||
LEFT JOIN admin_users u ON u.id = e.enviado_por
|
||||
WHERE $w
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT 200
|
||||
", $params);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helper
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
private function validarEsquema(string $json): void {
|
||||
$decoded = json_decode($json, true);
|
||||
if (!is_array($decoded)) {
|
||||
throw new InvalidArgumentException('El esquema del formulario no es un JSON válido');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?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');
|
||||
$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',
|
||||
'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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* Paciente — Gestión de pacientes del módulo administrativo de laboratorio.
|
||||
* Extiende la información del contacto WhatsApp (users.id).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/ActividadAdmin.php';
|
||||
|
||||
class Paciente {
|
||||
|
||||
private Database $db;
|
||||
private ActividadAdmin $log;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
$this->log = new ActividadAdmin();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Lista paginada de pacientes con búsqueda.
|
||||
*/
|
||||
public function listar(
|
||||
string $busqueda = '',
|
||||
int $pagina = 1,
|
||||
int $porPagina = 30
|
||||
): array {
|
||||
$offset = ($pagina - 1) * $porPagina;
|
||||
$like = "%$busqueda%";
|
||||
$params = $busqueda
|
||||
? [$like, $like, $like, $like]
|
||||
: [];
|
||||
|
||||
$where = $busqueda
|
||||
? "WHERE p.nombre_completo LIKE ?
|
||||
OR p.numero_documento LIKE ?
|
||||
OR p.telefono LIKE ?
|
||||
OR p.email LIKE ?"
|
||||
: '';
|
||||
|
||||
$total = $this->db->fetch(
|
||||
"SELECT COUNT(*) AS n FROM lab_pacientes p $where",
|
||||
$params
|
||||
)['n'] ?? 0;
|
||||
|
||||
$rows = $this->db->fetchAll("
|
||||
SELECT
|
||||
p.*,
|
||||
u.phone_number,
|
||||
u.name AS whatsapp_name,
|
||||
(SELECT COUNT(*) FROM lab_ordenes_medicas o WHERE o.paciente_id = p.id) AS total_ordenes
|
||||
FROM lab_pacientes p
|
||||
LEFT JOIN users u ON u.id = p.user_id
|
||||
$where
|
||||
ORDER BY p.nombre_completo ASC
|
||||
LIMIT ? OFFSET ?
|
||||
", array_merge($params, [$porPagina, $offset]));
|
||||
|
||||
return [
|
||||
'data' => $rows,
|
||||
'total' => (int)$total,
|
||||
'pagina' => $pagina,
|
||||
'por_pagina' => $porPagina,
|
||||
'paginas' => (int)ceil($total / $porPagina),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Un paciente por ID (con datos del usuario WhatsApp).
|
||||
*/
|
||||
public function obtener(int $id): ?array {
|
||||
return $this->db->fetch("
|
||||
SELECT
|
||||
p.*,
|
||||
u.phone_number,
|
||||
u.name AS whatsapp_name,
|
||||
u.status AS whatsapp_status,
|
||||
u.created_at AS whatsapp_desde
|
||||
FROM lab_pacientes p
|
||||
LEFT JOIN users u ON u.id = p.user_id
|
||||
WHERE p.id = ?
|
||||
", [$id]) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buscar por número de documento.
|
||||
*/
|
||||
public function porDocumento(string $documento): ?array {
|
||||
return $this->db->fetch(
|
||||
'SELECT * FROM lab_pacientes WHERE numero_documento = ?',
|
||||
[$documento]
|
||||
) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buscar por user_id (teléfono WhatsApp).
|
||||
*/
|
||||
public function porUserId(int $userId): ?array {
|
||||
return $this->db->fetch(
|
||||
'SELECT * FROM lab_pacientes WHERE user_id = ?',
|
||||
[$userId]
|
||||
) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear paciente nuevo.
|
||||
*
|
||||
* @param array $datos Campos del formulario
|
||||
* @param int|null $adminId Quien lo crea (para trazabilidad)
|
||||
* @return int ID del paciente creado
|
||||
*/
|
||||
public function crear(array $datos, ?int $adminId = null): int {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$id = $this->db->insert('lab_pacientes', $campos);
|
||||
$this->log->registrar($adminId, 'pacientes', 'crear', $id, $campos);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar paciente existente.
|
||||
*/
|
||||
public function actualizar(int $id, array $datos, ?int $adminId = null): bool {
|
||||
$campos = $this->filtrarCampos($datos);
|
||||
$ok = $this->db->update('lab_pacientes', $campos, 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'pacientes', 'editar', $id, $campos);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desactivar (soft-delete).
|
||||
*/
|
||||
public function desactivar(int $id, ?int $adminId = null): bool {
|
||||
$ok = $this->db->update('lab_pacientes', ['is_active' => 0], 'id = ?', [$id]);
|
||||
$this->log->registrar($adminId, 'pacientes', 'desactivar', $id);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vincular paciente con un usuario de WhatsApp.
|
||||
*/
|
||||
public function vincularUsuario(int $pacienteId, int $userId, ?int $adminId = null): bool {
|
||||
$ok = $this->db->update('lab_pacientes', ['user_id' => $userId], 'id = ?', [$pacienteId]);
|
||||
$this->log->registrar($adminId, 'pacientes', 'vincular_whatsapp', $pacienteId, [
|
||||
'user_id' => $userId,
|
||||
]);
|
||||
return $ok > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Historial
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Órdenes médicas del paciente.
|
||||
*/
|
||||
public function ordenes(int $pacienteId, int $limit = 20): array {
|
||||
return $this->db->fetchAll("
|
||||
SELECT
|
||||
o.*,
|
||||
au.full_name AS autorizada_por_nombre
|
||||
FROM lab_ordenes_medicas o
|
||||
LEFT JOIN admin_users au ON au.id = o.autorizada_por
|
||||
WHERE o.paciente_id = ?
|
||||
ORDER BY o.created_at DESC
|
||||
LIMIT ?
|
||||
", [$pacienteId, $limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Historial de actividad de trazabilidad del paciente.
|
||||
*/
|
||||
public function actividad(int $pacienteId): array {
|
||||
return $this->log->porEntidad('pacientes', $pacienteId);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Crea automáticamente un paciente a partir de un users.id (whatsapp)
|
||||
* si aún no existe. Devuelve el id del paciente.
|
||||
*/
|
||||
public function obtenerOCrearDesdeWhatsapp(int $userId): int {
|
||||
$existente = $this->porUserId($userId);
|
||||
if ($existente) {
|
||||
return $existente['id'];
|
||||
}
|
||||
|
||||
$user = $this->db->fetch(
|
||||
'SELECT * FROM users WHERE id = ?',
|
||||
[$userId]
|
||||
);
|
||||
|
||||
return $this->crear([
|
||||
'user_id' => $userId,
|
||||
'nombre_completo'=> $user['name'] ?? ('Paciente ' . $user['phone_number']),
|
||||
'telefono' => $user['phone_number'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
private function filtrarCampos(array $datos): array {
|
||||
$permitidos = [
|
||||
'user_id', 'numero_documento', 'tipo_documento',
|
||||
'nombre_completo', 'telefono', 'email',
|
||||
'fecha_nacimiento', 'genero', 'direccion',
|
||||
'ciudad', 'barrio', 'eps', 'notas_admin', 'is_active',
|
||||
];
|
||||
return array_intersect_key($datos, array_flip($permitidos));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user