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 ────── // Construir mapa: campo_id → minuto (o hora fija) leyendo el separador previo $firmasCampos = []; // [ [id, minutos|null, hora_fija|null], ... ] en orden $separadorLabel = null; foreach ($esquema as $campo) { $tipo = $campo['tipo'] ?? ''; if ($tipo === 'separador') { $separadorLabel = $campo['label'] ?? ''; } if ($tipo === 'firma_profesional') { $minutos = null; $horaFija = null; if ($separadorLabel !== null) { // "Taukit · Minuto 10" → 10; "Toma · Minuto 60" → 60 if (preg_match('/Minuto\s+(\d+)/i', $separadorLabel, $m)) { $minutos = (int)$m[1]; } // "3:00 p.m." / "4:00 p.m." → hora fija 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]; } } // ── Determinar estado y siguiente_toma_at ──────────────────── $firmados = array_filter($firmasCampos, fn($f) => isset($datosMerge[$f['id']]) && strlen($datosMerge[$f['id']]) > 10); $pendientes = array_filter($firmasCampos, fn($f) => !isset($datosMerge[$f['id']]) || strlen($datosMerge[$f['id']]) <= 10); $pendientes = array_values($pendientes); $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 { // Calcular cuándo es la siguiente toma $next = $pendientes[0]; if ($next['hora_fija'] !== null) { // Hora fija en el día actual $siguienteToma = date('Y-m-d') . ' ' . $next['hora_fija']; } elseif ($next['minutos'] !== null && $tomaInicioAt) { // Minuto relativo desde el inicio de la toma $siguienteToma = date('Y-m-d H:i:s', strtotime($tomaInicioAt) + $next['minutos'] * 60); } } // ── 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'));