post method

Future<Map<String, dynamic>> post(
  1. String endpoint,
  2. BaseRequest request, {
  3. String? contratanteNumero,
  4. String? autorPedidoDadosNumero,
  5. String? procuradorToken,
})

Executa uma requisição POST para a API do SERPRO

endpoint: Caminho do endpoint (ex: '/Ccmei/Emitir') request: Objeto BaseRequest contendo os dados da requisição contratanteNumero: CNPJ do contratante (opcional, usa o padrão se não fornecido) autorPedidoDadosNumero: CPF/CNPJ do autor (opcional, usa o padrão se não fornecido) procuradorToken: Token de procurador para operações que requerem procuração

Retorna: Map com a resposta da API ou lança exceção em caso de erro

Renovação Automática de Tokens

Este método verifica automaticamente se o token está próximo de expirar e renova antes de fazer a requisição. Isso é transparente para o usuário.

Implementation

Future<Map<String, dynamic>> post(
  String endpoint,
  BaseRequest request, {
  String? contratanteNumero,
  String? autorPedidoDadosNumero,
  String? procuradorToken,
}) async {
  // Verificar se o cliente foi autenticado
  if (_authModel == null) {
    throw _buildErrorResponse(
      mensagem: 'Cliente não autenticado',
      status: 401,
      resposta: 'Primeiro faça a autenticação usando o método authenticate()',
    );
  }

  // RENOVAÇÃO AUTOMÁTICA: Verificar se token está próximo de expirar
  if (_authModel!.shouldRefresh || _authModel!.isExpired) {
    if (_storedCredentials != null && _authService != null) {
      try {
        // Re-autenticar automaticamente
        _authModel = await _authService!.authenticate(_storedCredentials!);
      } catch (e) {
        // Se falhar a re-autenticação, limpar tudo
        _authModel = null;
        throw Exception(
          'Token expirado e não foi possível renovar automaticamente. '
          'Erro: $e. Por favor, chame authenticate() novamente.',
        );
      }
    } else {
      // Não temos credenciais para re-autenticar
      _authModel = null;
      throw Exception(
        'Token expirado. Por favor, chame authenticate() novamente.',
      );
    }
  }

  // Usar dados customizados se fornecidos, senão usar os dados padrão
  final finalContratanteNumero =
      contratanteNumero ?? _authModel!.contratanteNumero;

  // Quando há token de procurador, o autorPedidoDadosNumero deve ser o contribuinteNumero
  final finalAutorPedidoDadosNumero =
      autorPedidoDadosNumero ??
      (hasProcuradorToken
          ? request.contribuinteNumero
          : _authModel!.autorPedidoDadosNumero);

  // Criar o JSON completo usando os dados de autenticação
  final requestBody = request.toJsonWithAuth(
    contratanteNumero: finalContratanteNumero,
    contratanteTipo: ValidacoesUtils.detectDocumentType(
      finalContratanteNumero,
    ),
    autorPedidoDadosNumero: finalAutorPedidoDadosNumero,
    autorPedidoDadosTipo: ValidacoesUtils.detectDocumentType(
      finalAutorPedidoDadosNumero,
    ),
  );

  // Preparar headers obrigatórios
  final headers = <String, String>{
    'Authorization': 'Bearer ${_authModel!.accessToken}',
    'jwt_token': _authModel!.jwtToken,
    'Content-Type': 'application/json',
  };

  // Adicionar token de procurador (sempre do authModel, parâmetro ignorado)
  if (_authModel != null && _authModel!.procuradorToken.isNotEmpty) {
    headers['autenticar_procurador_token'] = _authModel!.procuradorToken;
  }

  // Gerar e adicionar identificador de requisição
  final requestTag = RequestTagGenerator.generateRequestTag(
    autorPedidoDadosNumero: finalAutorPedidoDadosNumero,
    contribuinteNumero: request.contribuinteNumero,
    idServico: request.pedidoDados.idServico,
  );
  headers['X-Request-Tag'] = requestTag;

  // Executar requisição HTTP POST
  final response = await (() async {
    // Se urlProxy estiver configurado (Web), usar proxy
    if (_urlProxy != null && _urlProxy!.isNotEmpty) {
      final proxyUrl = Uri.parse('$_urlProxy/proxy_serpro');
      final proxyHeaders = <String, String>{
        'Content-Type': 'application/json',
      };
      final proxyBody = {
        'endpoint': endpoint,
        'body': requestBody,
        'access_token': _authModel!.accessToken,
        'jwt_token': _authModel!.jwtToken,
        'procurador_token': _authModel!.procuradorToken.isNotEmpty
            ? _authModel!.procuradorToken
            : null,
        'ambiente': _ambiente,
        'certificado_base64':
            _cloudFunctionCertBase64 ?? _storedCredentials?.certBase64,
        'certificado_senha':
            _cloudFunctionCertPassword ?? _storedCredentials?.certPassword,
      };

      //print("================================================");
      //print("Usando proxy: $proxyUrl");
      //print("Proxy body: ${json.encode(proxyBody)}");
      //print("================================================");

      return await http.post(
        proxyUrl,
        headers: proxyHeaders,
        body: json.encode(proxyBody),
      );
    } else {
      // Requisição direta (Desktop/Mobile ou quando urlProxy não está configurado)
      //print("================================================");
      //print("Endpoint direto: $_baseUrl$endpoint");
      //print("headers: ${headers}");
      //print("requestBody: ${json.encode(requestBody)}");
      //print("================================================");

      return await http.post(
        Uri.parse('$_baseUrl$endpoint'),
        headers: headers,
        body: json.encode(requestBody),
      );
    }
  })();
  // Verificar se a requisição foi bem-sucedida
  if (response.statusCode >= 200 && response.statusCode < 300) {
    Map<String, dynamic> responseBody =
        json.decode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>;

    // Verificar se a API retornou um erro de negócio
    if (responseBody.isNotEmpty &&
        responseBody['mensagens'] != null &&
        responseBody['mensagens'] is List &&
        (responseBody['mensagens'] as List).isNotEmpty &&
        responseBody['mensagens'][0]['codigo'] == "ERRO") {
      // Reformatar resposta de erro
      responseBody = {
        "rota": endpoint,
        "status": response.statusCode,
        "idSistema": requestBody['pedidoDados']['idSistema'],
        "idServico": requestBody['pedidoDados']['idServico'],
        "mensagens": "${responseBody['mensagens'][0]['texto']}",
        "body": json.encode(requestBody),
      };
      throw Exception(responseBody);
    }
    return responseBody;
  } else if (response.statusCode == 401) {
    throw Exception({
      "status": response.statusCode,
      "mensagens":
          "Credenciais inválidas. Certifique-se de ter fornecido as credenciais de segurança corretas",
      "body": "Credenciais inválidas",
    });
  } else if (response.statusCode == 304) {
    final autenticarProcuradorToken = response.headers['etag']
        .toString()
        .replaceAll(':', '":"');
    final expiresISO =
        FormatadorUtils.converterHttpExpiresParaISO(
          response.headers['expires'],
        ) ??
        '';
    final stringBody =
        "{$autenticarProcuradorToken, \"data_hora_expiracao\":\"$expiresISO\"}";
    final body = jsonDecode(stringBody);
    return {
      "status": response.statusCode,
      "mensagens": "Resposta em cache (304 Not Modified)",
      "dados": body,
    };
  } else {
    throw Exception(
      'Falha na requisição: ${response.statusCode} - ${utf8.decode(response.bodyBytes)}',
    );
  }
}