206 lines
9.9 KiB
PHP
206 lines
9.9 KiB
PHP
<?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']);
|
|
$row = $this->db->fetch('SELECT version FROM lab_formularios WHERE id = ?', [$id]);
|
|
$campos['version'] = (int)($row['version'] ?? 0) + 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');
|
|
}
|
|
}
|
|
}
|