prepare("SELECT id, estado, lugar_destino_id, paciente_id FROM turnero_turnos WHERE id = ?"); $stmt->execute([$turnoId]); $turno = $stmt->fetch(PDO::FETCH_ASSOC); if (!$turno) jsonError('Turno no encontrado.', 404); // ── Formularios de lugar ────────────────────────────────────────────────────── // Solo cuando el turno tiene lugar destino asignado. En recepción (NULL), // fidsDeLugar queda vacío → todos los consentimientos reciben origen_lugar_id=null // → pasan el filtro !c.origen_lugar_id de recepcion.php. $lugarDestinoId = (int)($turno['lugar_destino_id'] ?? 0); $fidsDeLugar = []; if ($lugarDestinoId) { $stmtL = $pdo->prepare( "SELECT formulario_id FROM turnero_lugar_consentimientos WHERE lugar_id = ?" ); $stmtL->execute([$lugarDestinoId]); $fidsDeLugar = array_map('intval', $stmtL->fetchAll(PDO::FETCH_COLUMN)); } // ── Consentimientos ─────────────────────────────────────────── $stmt = $pdo->prepare( "SELECT tc.id, tc.turno_id, tc.formulario_id, tc.token, tc.estado, tc.enviado_at, tc.firmado_at, (tc.firma_profesional_svg IS NOT NULL) AS tiene_firma_profesional, tc.firmado_profesional_at, tc.siguiente_toma_at, tc.toma_inicio_at, tc.datos_respuestas, f.nombre AS formulario_nombre, f.esquema AS formulario_esquema, f.es_toma_progresiva AS formulario_es_toma_prog FROM turnero_consentimientos tc LEFT JOIN lab_formularios f ON f.id = tc.formulario_id WHERE tc.turno_id = ? ORDER BY tc.id ASC" ); $stmt->execute([$turnoId]); $consentimientos = $stmt->fetchAll(PDO::FETCH_ASSOC); // Compute flags desde el esquema; origen_lugar_id calculado según config actual foreach ($consentimientos as &$c) { $esquema = $c['formulario_esquema'] ?? null; $campos = []; if ($esquema) { $decoded = json_decode($esquema, true); $campos = is_array($decoded) ? $decoded : ($decoded['campos'] ?? []); } $camposFirmaPro = array_values(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma_profesional')); $tieneFirmaPro = !empty($camposFirmaPro); $tieneFirmaPac = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma')); // Toma progresiva: override explícito en DB gana; si es NULL, usar heurística (>1 campo firma_profesional) $overrideProg = $c['formulario_es_toma_prog'] ?? null; $esTomaProg = $overrideProg !== null ? (bool)(int)$overrideProg : count($camposFirmaPro) > 1; $tomasTotal = 0; $tomasFirm = 0; if ($esTomaProg) { $dr = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : []; // Mapear cada firma_profesional a los valores de examen de su sección condicional $firmaCondMap = []; $curCondCampo = null; $curCondVals = []; foreach ($campos as $campo) { $ft = $campo['tipo'] ?? ''; if ($ft === 'separador') { $cond = $campo['condicion'] ?? null; $curCondCampo = $cond ? ($cond['campo_id'] ?? null) : null; $curCondVals = $cond ? ($cond['valores'] ?? ($cond['valor'] ? [$cond['valor']] : [])) : []; } elseif ($ft === 'firma_profesional' && !empty($campo['id'])) { $firmaCondMap[$campo['id']] = ['campo' => $curCondCampo, 'valores' => $curCondVals]; } } // Filtrar por exámenes seleccionados en datos_respuestas $relevantIds = []; foreach ($firmaCondMap as $fid => $info) { if ($info['campo'] === null) { $relevantIds[] = $fid; // sin condición → siempre aplica } else { $ctrlVal = $dr[$info['campo']] ?? null; $ctrlArr = is_array($ctrlVal) ? $ctrlVal : ($ctrlVal !== null ? [$ctrlVal] : []); if (!empty(array_intersect($info['valores'], $ctrlArr))) { $relevantIds[] = $fid; } } } $tomasTotal = count($relevantIds); foreach ($relevantIds as $fid) { if ((!empty($dr[$fid]) && strlen($dr[$fid]) > 10) || (!empty($dr[$fid.'_svg']) && strlen($dr[$fid.'_svg']) > 10)) $tomasFirm++; } } $c['requiere_firma_profesional'] = $tieneFirmaPro; $c['requiere_firma_paciente'] = !($tieneFirmaPro && !$tieneFirmaPac); $c['origen_lugar_id'] = in_array((int)$c['formulario_id'], $fidsDeLugar, true) ? ($lugarDestinoId ?: -1) : null; $c['es_toma_progresiva'] = $esTomaProg; $c['tomas_total'] = $tomasTotal; $c['tomas_firmadas'] = $tomasFirm; // Exponer campos firma_profesional (ids) para el frontend $c['campos_firma_pro'] = array_column($camposFirmaPro, 'id'); unset($c['formulario_esquema'], $c['formulario_es_toma_prog'], $c['datos_respuestas']); } unset($c); $respuesta = [ 'turno_id' => $turnoId, 'turno_estado' => $turno['estado'], 'consentimientos' => $consentimientos, ]; // ── Datos adicionales si solicita solicitud ─────────────────── if ($incluirSolicitud) { $stmt = $pdo->prepare( "SELECT s.id, s.turno_id, s.paciente_id, s.lugar_id, s.numero_orden, s.total_cobrado, s.metodo_pago, s.observaciones, s.embarazada, s.solo_muestras, s.medico_id, s.creado_at, l.nombre AS lugar_nombre, CONCAT(m.nombres, ' ', m.apellidos) AS medico_nombre, m.cod_especialidad AS medico_especialidad, m.codigo AS medico_codigo FROM turnero_solicitudes s LEFT JOIN turnero_lugares l ON l.id = s.lugar_id LEFT JOIN medicos m ON m.id = s.medico_id WHERE s.turno_id = ? LIMIT 1" ); $stmt->execute([$turnoId]); $solicitud = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; $paciente = null; $examenes = []; if ($solicitud) { // Paciente $stmt = $pdo->prepare( "SELECT id, nombre_completo, tipo_documento, numero_documento, fecha_nacimiento, telefono FROM lab_pacientes WHERE id = ?" ); $stmt->execute([$solicitud['paciente_id']]); $paciente = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; // Exámenes $stmt = $pdo->prepare( "SELECT et.id, et.codigo, et.nombre, et.categoria FROM turnero_examen_items tei JOIN exam_tipos et ON et.id = tei.exam_tipo_id WHERE tei.solicitud_id = ? ORDER BY et.categoria, et.nombre" ); $stmt->execute([$solicitud['id']]); $examenes = $stmt->fetchAll(PDO::FETCH_ASSOC); // Visita de solo entrega de muestras: no requiere consentimientos ni formularios if (!empty($solicitud['solo_muestras'])) { $respuesta['consentimientos'] = []; $respuesta['solicitud'] = $solicitud; $respuesta['paciente'] = $paciente; $respuesta['examenes'] = $examenes; // Muestras pendientes de visitas anteriores $muestras = []; try { $stmtPrev = $pdo->prepare( "SELECT tm.id, tm.tipo_muestra, tm.estado, tm.motivo_rechazo, tm.recibida_at, COALESCE(lt.nombre, tm.tipo_muestra) AS label, 1 AS es_pendiente_anterior, ts.numero_orden AS solicitud_orden_anterior FROM turnero_muestras tm JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id LEFT JOIN lab_tipos_muestra lt ON lt.codigo = tm.tipo_muestra WHERE ts.paciente_id = ? AND tm.solicitud_id != ? AND tm.estado = 'pendiente' ORDER BY tm.id ASC" ); $stmtPrev->execute([$solicitud['paciente_id'], $solicitud['id']]); $muestras = $stmtPrev->fetchAll(PDO::FETCH_ASSOC); } catch (\Throwable $_) {} $respuesta['muestras'] = $muestras; jsonOk($respuesta); } // Auto-crear consentimientos requeridos desde DOS fuentes: // 1. Por examen → exam_tipo_consentimientos // 2. Por lugar → turnero_lugar_consentimientos $requeridos = []; $vistosFIds = []; // Fuente 1: examen if (!empty($examenes)) { $examIds = array_column($examenes, 'id'); $ph = implode(',', array_fill(0, count($examIds), '?')); $stmtE = $pdo->prepare( "SELECT DISTINCT etc.formulario_id, f.nombre AS formulario_nombre FROM exam_tipo_consentimientos etc JOIN lab_formularios f ON f.id = etc.formulario_id WHERE etc.exam_tipo_id IN ($ph) AND f.is_active = 1" ); $stmtE->execute($examIds); foreach ($stmtE->fetchAll(PDO::FETCH_ASSOC) as $r) { $fid = (int)$r['formulario_id']; if (!in_array($fid, $vistosFIds, true)) { $requeridos[] = $r; $vistosFIds[] = $fid; } } } // Fuente 2: lugar destino ($lugarDestinoId y $fidsDeLugar ya calculados arriba) if ($lugarDestinoId) { $stmtL = $pdo->prepare( "SELECT DISTINCT tlc.formulario_id, f.nombre AS formulario_nombre FROM turnero_lugar_consentimientos tlc JOIN lab_formularios f ON f.id = tlc.formulario_id WHERE tlc.lugar_id = ? AND f.is_active = 1" ); $stmtL->execute([$lugarDestinoId]); foreach ($stmtL->fetchAll(PDO::FETCH_ASSOC) as $r) { $fid = (int)$r['formulario_id']; if (!in_array($fid, $vistosFIds, true)) { $requeridos[] = $r; $vistosFIds[] = $fid; } } } // Auto-crear los registros que falten if (!empty($requeridos)) { $existFormIds = array_map('intval', array_column($consentimientos, 'formulario_id')); $stmtIns = $pdo->prepare( "INSERT IGNORE INTO turnero_consentimientos (turno_id, formulario_id, token, estado) VALUES (?, ?, ?, 'pendiente')" ); $creados = 0; foreach ($requeridos as $r) { if (!in_array((int)$r['formulario_id'], $existFormIds, true)) { $token = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0x0fff)|0x4000, mt_rand(0,0x3fff)|0x8000, mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff) ); $stmtIns->execute([$turnoId, (int)$r['formulario_id'], $token]); $creados++; } } // Re-leer si se crearon nuevos registros y recalcular flags if ($creados > 0) { $stmtC = $pdo->prepare( "SELECT tc.id, tc.turno_id, tc.formulario_id, tc.token, tc.estado, tc.enviado_at, tc.firmado_at, (tc.firma_profesional_svg IS NOT NULL) AS tiene_firma_profesional, tc.firmado_profesional_at, tc.siguiente_toma_at, tc.toma_inicio_at, tc.datos_respuestas, f.nombre AS formulario_nombre, f.esquema AS formulario_esquema, f.es_toma_progresiva AS formulario_es_toma_prog FROM turnero_consentimientos tc LEFT JOIN lab_formularios f ON f.id = tc.formulario_id WHERE tc.turno_id = ? ORDER BY tc.id ASC" ); $stmtC->execute([$turnoId]); $consentimientos = $stmtC->fetchAll(PDO::FETCH_ASSOC); foreach ($consentimientos as &$c) { $esquema = $c['formulario_esquema'] ?? null; $campos = []; if ($esquema) { $decoded = json_decode($esquema, true); $campos = is_array($decoded) ? $decoded : ($decoded['campos'] ?? []); } $camposFirmaPro = array_values(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma_profesional')); $tieneFirmaPro = !empty($camposFirmaPro); $tieneFirmaPac = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma')); $overrideProg = $c['formulario_es_toma_prog'] ?? null; $esTomaProg = $overrideProg !== null ? (bool)(int)$overrideProg : count($camposFirmaPro) > 1; $tomasTotal2 = 0; $tomasFirm = 0; if ($esTomaProg) { $dr2 = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : []; $firmaCondMap2 = []; $curC2 = null; $curV2 = []; foreach ($campos as $campo2) { $ft2 = $campo2['tipo'] ?? ''; if ($ft2 === 'separador') { $cond2 = $campo2['condicion'] ?? null; $curC2 = $cond2 ? ($cond2['campo_id'] ?? null) : null; $curV2 = $cond2 ? ($cond2['valores'] ?? ($cond2['valor'] ? [$cond2['valor']] : [])) : []; } elseif ($ft2 === 'firma_profesional' && !empty($campo2['id'])) { $firmaCondMap2[$campo2['id']] = ['campo' => $curC2, 'valores' => $curV2]; } } $relevantIds2 = []; foreach ($firmaCondMap2 as $fid2 => $info2) { if ($info2['campo'] === null) { $relevantIds2[] = $fid2; } else { $cv2 = $dr2[$info2['campo']] ?? null; $ca2 = is_array($cv2) ? $cv2 : ($cv2 !== null ? [$cv2] : []); if (!empty(array_intersect($info2['valores'], $ca2))) $relevantIds2[] = $fid2; } } $tomasTotal2 = count($relevantIds2); foreach ($relevantIds2 as $fid2) { if (!empty($dr2[$fid2]) && strlen($dr2[$fid2]) > 10) $tomasFirm++; } } $c['requiere_firma_profesional'] = $tieneFirmaPro; $c['requiere_firma_paciente'] = !($tieneFirmaPro && !$tieneFirmaPac); $c['origen_lugar_id'] = in_array((int)$c['formulario_id'], $fidsDeLugar, true) ? $lugarDestinoId : null; $c['es_toma_progresiva'] = $esTomaProg; $c['tomas_total'] = $tomasTotal2; $c['tomas_firmadas'] = $tomasFirm; $c['campos_firma_pro'] = array_column($camposFirmaPro, 'id'); unset($c['formulario_esquema'], $c['formulario_es_toma_prog'], $c['datos_respuestas']); } unset($c); $respuesta['consentimientos'] = $consentimientos; } } } // Muestras pendientes / recibidas del turno $muestras = []; if ($solicitud) { try { $stmtM = $pdo->prepare( "SELECT tm.id, tm.tipo_muestra, tm.estado, tm.motivo_rechazo, tm.recibida_at, COALESCE(lt.nombre, tm.tipo_muestra) AS label, 0 AS es_pendiente_anterior, NULL AS solicitud_orden_anterior FROM turnero_muestras tm LEFT JOIN lab_tipos_muestra lt ON lt.codigo = tm.tipo_muestra WHERE tm.solicitud_id = ? ORDER BY tm.id ASC" ); $stmtM->execute([$solicitud['id']]); $muestras = $stmtM->fetchAll(PDO::FETCH_ASSOC); // Si es visita de entrega de muestras, agregar pendientes de solicitudes anteriores if (!empty($solicitud['solo_muestras']) && !empty($solicitud['paciente_id'])) { $stmtPrev = $pdo->prepare( "SELECT tm.id, tm.tipo_muestra, tm.estado, tm.motivo_rechazo, tm.recibida_at, COALESCE(lt.nombre, tm.tipo_muestra) AS label, 1 AS es_pendiente_anterior, ts.numero_orden AS solicitud_orden_anterior FROM turnero_muestras tm JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id LEFT JOIN lab_tipos_muestra lt ON lt.codigo = tm.tipo_muestra WHERE ts.paciente_id = ? AND tm.solicitud_id != ? AND tm.estado = 'pendiente' ORDER BY tm.id ASC" ); $stmtPrev->execute([$solicitud['paciente_id'], $solicitud['id']]); $previas = $stmtPrev->fetchAll(PDO::FETCH_ASSOC); $muestras = array_merge($previas, $muestras); } } catch (\Throwable $_) { // Tabla aún no existe } } // Fallback: sin solicitud pero el turno tiene paciente_id → buscar muestras pendientes igualmente if (!$solicitud && !empty($turno['paciente_id'])) { $pacId = (int)$turno['paciente_id']; $stmt = $pdo->prepare( "SELECT id, nombre_completo, tipo_documento, numero_documento, fecha_nacimiento, telefono FROM lab_pacientes WHERE id = ?" ); $stmt->execute([$pacId]); $paciente = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; try { $stmtPrev = $pdo->prepare( "SELECT tm.id, tm.tipo_muestra, tm.estado, tm.motivo_rechazo, tm.recibida_at, COALESCE(lt.nombre, tm.tipo_muestra) AS label, 1 AS es_pendiente_anterior, ts.numero_orden AS solicitud_orden_anterior FROM turnero_muestras tm JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id LEFT JOIN lab_tipos_muestra lt ON lt.codigo = tm.tipo_muestra WHERE ts.paciente_id = ? AND tm.estado = 'pendiente' ORDER BY tm.id ASC" ); $stmtPrev->execute([$pacId]); $muestras = $stmtPrev->fetchAll(PDO::FETCH_ASSOC); } catch (\Throwable $_) {} } $respuesta['solicitud'] = $solicitud; $respuesta['paciente'] = $paciente; $respuesta['examenes'] = $examenes; $respuesta['muestras'] = $muestras; } jsonOk($respuesta);