package infra import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) // HttpPostJSON realiza un POST request con body JSON y parsea la respuesta como JSON. // Agrega Content-Type: application/json y Accept: application/json automaticamente. // Retorna error si status >= 400 incluyendo status code y los primeros 200 bytes del body. func HttpPostJSON(url string, body any, headers map[string]string, timeout time.Duration) (map[string]any, error) { data, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("http_post_json: marshal body: %w", err) } client := &http.Client{Timeout: timeout} req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) if err != nil { return nil, fmt.Errorf("http_post_json: build request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") for k, v := range headers { req.Header.Set(k, v) } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("http_post_json: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("http_post_json: read body: %w", err) } if resp.StatusCode >= 400 { preview := respBody if len(preview) > 200 { preview = preview[:200] } shortURL := url if len(shortURL) > 100 { shortURL = shortURL[:100] } return nil, fmt.Errorf("http_post_json: HTTP %d at %q — %s", resp.StatusCode, shortURL, preview) } var result map[string]any if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("http_post_json: parse JSON: %w", err) } return result, nil }