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, string $origen = '' ): array { $offset = ($pagina - 1) * $porPagina; $like = "%$busqueda%"; $conditions = []; $params = []; if ($busqueda) { $conditions[] = "(p.nombre_completo LIKE ? OR p.numero_documento LIKE ? OR p.telefono LIKE ? OR p.email LIKE ?)"; $params = [$like, $like, $like, $like]; } if ($origen) { $conditions[] = "p.origen = ?"; $params[] = $origen; } $where = $conditions ? 'WHERE ' . implode(' AND ', $conditions) : ''; $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.created_at DESC, 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), ]; } public function stats(): array { return $this->db->fetch(" SELECT COUNT(*) AS total, SUM(origen = 'lab') AS importados, SUM(origen = 'manual') AS manuales, SUM(origen = 'whatsapp') AS whatsapp, SUM(user_id IS NOT NULL) AS con_wa FROM lab_pacientes WHERE is_active = 1 ") ?: ['total' => 0, 'importados' => 0, 'manuales' => 0, 'whatsapp' => 0, 'con_wa' => 0]; } /** * 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); // Validar documento duplicado antes de insertar if (!empty($campos['numero_documento'])) { $existente = $this->db->fetch( 'SELECT id, nombre_completo FROM lab_pacientes WHERE numero_documento = ?', [$campos['numero_documento']] ); if ($existente) { throw new Exception( 'Ya existe un paciente registrado con ese número de documento: ' . $existente['nombre_completo'] ); } } $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); // Validar que el documento no pertenezca a otro paciente if (!empty($campos['numero_documento'])) { $existente = $this->db->fetch( 'SELECT id, nombre_completo FROM lab_pacientes WHERE numero_documento = ? AND id != ?', [$campos['numero_documento'], $id] ); if ($existente) { throw new Exception( 'Ese número de documento ya está registrado para: ' . $existente['nombre_completo'] ); } } $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', 'origen', ]; $campos = array_intersect_key($datos, array_flip($permitidos)); if (!empty($campos['telefono'])) { $campos['telefono'] = $this->normalizarTelefono($campos['telefono']); } return $campos; } /** * Normaliza un número de teléfono colombiano al formato 57XXXXXXXXXX. * - Elimina espacios, guiones y paréntesis. * - Si empieza por +57 → quita el +. * - Si tiene 10 dígitos empezando por 3 → antepone 57. * - Cualquier otro caso lo devuelve limpio de caracteres no numéricos. */ private function normalizarTelefono(string $tel): string { $tel = preg_replace('/[^0-9+]/', '', $tel); if (str_starts_with($tel, '+')) { $tel = ltrim($tel, '+'); } // Sin prefijo (10 dígitos empezando por 3) → asume Colombia 57 if (strlen($tel) === 10 && str_starts_with($tel, '3')) { $tel = '57' . $tel; } // Si ya trae prefijo de 2 dígitos (57xx... o 58xx...) lo respetamos return $tel; } }