prepare( "SELECT tc.id, tc.estado, tc.datos_respuestas, tc.toma_inicio_at, t.sesion_id, f.esquema FROM turnero_consentimientos tc JOIN turnero_turnos t ON t.id = tc.turno_id JOIN lab_formularios f ON f.id = tc.formulario_id WHERE tc.turno_id = ? AND tc.formulario_id = ?" ); $stmt->execute([$turnoId, $formularioId]); $tc = $stmt->fetch(PDO::FETCH_ASSOC); if (!$tc) jsonError('Consentimiento no encontrado.', 404); $esquema = json_decode($tc['esquema'] ?? '[]', true); if (!is_array($esquema)) $esquema = []; // ── Merge datos_respuestas existentes + nuevos ─────────────── $datosActuales = []; if ($tc['datos_respuestas']) { $d = json_decode($tc['datos_respuestas'], true); if (is_array($d)) $datosActuales = $d; } // Guardar svg en el campo firmado $datosInput[$campoId] = $svg; $datosMerge = array_merge($datosActuales, $datosInput); // ── Analizar esquema: campos firma_profesional en orden ────── // Solo incluye firmas cuya sección condicional coincide con el examen seleccionado. $firmasCampos = []; $separadorLabel = null; $curCondField = null; $curCondVals = []; foreach ($esquema as $campo) { $tipo = $campo['tipo'] ?? ''; if ($tipo === 'separador') { $separadorLabel = $campo['label'] ?? ''; $cond = $campo['condicion'] ?? null; if ($cond) { $curCondField = $cond['campo_id'] ?? null; $curCondVals = $cond['valores'] ?? ($cond['valor'] ? [$cond['valor']] : []); } else { $curCondField = null; $curCondVals = []; } } if ($tipo === 'firma_profesional') { // Filtrar por condición del separador padre if ($curCondField !== null && !empty($curCondVals)) { $ctrlVal = $datosMerge[$curCondField] ?? null; $ctrlArr = is_array($ctrlVal) ? $ctrlVal : ($ctrlVal !== null ? [$ctrlVal] : []); if (empty(array_intersect($curCondVals, $ctrlArr))) continue; } $minutos = null; $horaFija = null; if ($separadorLabel !== null) { if (preg_match('/Minuto\s+(\d+)/i', $separadorLabel, $m)) { $minutos = (int)$m[1]; } elseif (preg_match('/(\d+):(\d+)\s*(a\.?m\.?|p\.?m\.?)/i', $separadorLabel, $m)) { $h = (int)$m[1]; $min = (int)$m[2]; $pm = strtolower(preg_replace('/[^apm]/i', '', $m[3])) === 'pm'; if ($pm && $h < 12) $h += 12; $horaFija = sprintf('%02d:%02d:00', $h, $min); } } $firmasCampos[] = ['id' => $campo['id'], 'minutos' => $minutos, 'hora_fija' => $horaFija]; } } // ── Aplicar filtro de ciclos configurados (tomas_config) ──── $__tcFirmas = $datosMerge['_tomas_config'] ?? null; if (is_array($__tcFirmas)) { $__allowed = []; foreach ($__tcFirmas as $__g => $__ids) { if (is_array($__ids)) foreach ($__ids as $__id) $__allowed[$__id] = true; } $firmasCampos = array_values(array_filter($firmasCampos, fn($f) => isset($__allowed[$f['id']]))); } // ── Determinar estado y siguiente_toma_at ──────────────────── // Acepta firma guardada con clave campo_id O campo_id_svg (compatibilidad registros históricos) $hasFirma = fn($f) => (isset($datosMerge[$f['id']]) && strlen($datosMerge[$f['id']]) > 10) || (isset($datosMerge[$f['id'].'_svg']) && strlen($datosMerge[$f['id'].'_svg']) > 10); $firmados = array_filter($firmasCampos, $hasFirma); $pendientes = array_values(array_filter($firmasCampos, fn($f) => !$hasFirma($f))); $todasFirmadas = empty($pendientes); $siguienteToma = null; $nuevoEstado = 'en_progreso'; $tomaInicioAt = $tc['toma_inicio_at']; if (empty($tc['toma_inicio_at'])) { $tomaInicioAt = date('Y-m-d H:i:s'); } if ($todasFirmadas) { $nuevoEstado = 'firmado'; } else { $next = $pendientes[0]; if ($next['hora_fija'] !== null) { $siguienteToma = date('Y-m-d') . ' ' . $next['hora_fija']; } elseif ($next['minutos'] !== null && $tomaInicioAt) { $siguienteToma = date('Y-m-d H:i:s', strtotime($tomaInicioAt) + $next['minutos'] * 60); } // Si la siguiente toma ya pasó o es ahora (minuto 0), no enviar countdown if ($siguienteToma !== null && $siguienteToma <= date('Y-m-d H:i:s')) { $siguienteToma = null; } } // ── Persistir ──────────────────────────────────────────────── $pdo->prepare( "UPDATE turnero_consentimientos SET datos_respuestas = ?, estado = ?, siguiente_toma_at = ?, toma_inicio_at = COALESCE(toma_inicio_at, ?), firmado_at = IF(? = 'firmado', NOW(), firmado_at), firmado_profesional_at = IF(? = 'firmado', NOW(), firmado_profesional_at) WHERE turno_id = ? AND formulario_id = ?" )->execute([ json_encode($datosMerge, JSON_UNESCAPED_UNICODE), $nuevoEstado, $siguienteToma, date('Y-m-d H:i:s'), $nuevoEstado, $nuevoEstado, $turnoId, $formularioId, ]); notificarSSE((int)$tc['sesion_id']); $totalFirmas = count($firmasCampos); $firmadasCount = count($firmados) + ($todasFirmadas ? 0 : 1); // incluye la recién guardada jsonOk([ 'estado' => $nuevoEstado, 'siguiente_toma_at'=> $siguienteToma, 'tomas_firmadas' => min($firmadasCount, $totalFirmas), 'tomas_total' => $totalFirmas, 'completado' => $todasFirmadas, ], $todasFirmadas ? 'Formulario completado.' : 'Toma guardada. Próxima: ' . ($siguienteToma ?? 'pendiente'));