Skip to content

Configurando o Loop de Polling de Comandos

O loop de polling de comandos é o coração da integração. A cada ciclo, o ERP pergunta à Maggu: "há algum comando novo para mim?" — e executa o que receber.

Endpoint

http
GET /v3/comandos/pendentes
Authorization: Bearer <token_da_loja>

Response 200:

json
[
  {
    "id": 5001,
    "tipo": "ENVIAR_TODOS_USUARIOS",
    "argumentos": {},
    "criadoEm": "2026-05-13T10:00:00Z"
  },
  {
    "id": 5002,
    "tipo": "ENVIAR_VENDAS_INTERVALO",
    "argumentos": {
      "de": "2026-05-01T00:00:00Z",
      "ate": "2026-05-13T23:59:59Z"
    },
    "criadoEm": "2026-05-13T10:05:00Z"
  }
]

Retorna [] se não houver comandos pendentes para a loja.

Loop principal

csharp
public async Task IniciarAsync(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        await ExecutarAsync(ct);
        await Task.Delay(Config.IntervalMs, ct);
    }
}

private async Task ExecutarAsync(CancellationToken ct)
{
    var requisicao = new RestRequest("api/v3/comandos/pendentes", Method.Get);

    var resposta = await _provedor.Client.GetAsync(requisicao, ct);
    var comandos  = JsonSerializer.Deserialize<List<Comando>>(resposta.Content!, jsonOptions);
    if (comandos is null) return;

    // Processar do mais antigo para o mais recente
    foreach (var comando in comandos.OrderBy(c => c.CriadoEm))
        await _orquestrador.ProcessarAsync(comando, ct);
}

Notificando o resultado

Após executar cada comando, informe a Maggu:

csharp
// Sucesso
var req = new RestRequest("api/v3/comandos/completou", Method.Post);
req.AddJsonBody(new { id = command.Id });
await _provedor.Client.PostAsync(req, ct);

// Falha
var req = new RestRequest("api/v3/comandos/falhou", Method.Post);
req.AddJsonBody(new { id = command.Id, motivo = "Descrição do erro" });
await _provedor.Client.PostAsync(req, ct);

Endpoint opcional: POST /v3/comandos/iniciou pode ser chamado logo antes de executar o comando para registrar que o ERP começou o processamento. Útil para diagnóstico de comandos travados.

Prevenindo execução duplicada

O mesmo comando pode aparecer em ciclos consecutivos se a confirmação ainda não chegou à Maggu. Use um repositório local em memória para rastrear o estado:

csharp
public class RepositorioComandos
{
    private readonly ConcurrentDictionary<int, StatusComando> _estado = new();

    public bool TentarObter(int id, out StatusComando status)
        => _estado.TryGetValue(id, out status);

    public void Salvar(int id, StatusComando status)
        => _estado[id] = status;
}

Antes de executar, verifique:

csharp
if (_repositorio.TentarObter(comando.Id, out var status))
{
    if (status == StatusComando.Pendente)   return; // já em andamento
    if (status == StatusComando.Concluido)  { _notificador.Concluido(comando); return; }
    if (status == StatusComando.Falhou)     { _notificador.Falhou(comando, motivo); return; }
}
_repositorio.Salvar(comando.Id, StatusComando.Pendente);

Resumo das respostas

EndpointSucessoErro
GET /v3/comandos/pendentes200 OK + lista403 não autorizado
POST /v3/comandos/completou204 No Content404 comando não encontrado
POST /v3/comandos/falhou204 No Content404 comando não encontrado

Implementação completa do ProcessarAsync

Junta o controle de duplicatas, execução e notificação em um único método:

csharp
public async Task ProcessarAsync(Comando comando, CancellationToken ct)
{
    // 1. Evitar execução duplicada
    if (_repositorio.TentarObter(comando.Id, out var status))
    {
        if (status == StatusComando.Pendente)   return;
        if (status == StatusComando.Concluido)  { await NotificarConcluidoAsync(comando, ct); return; }
        if (status == StatusComando.Falhou)     { await NotificarFalhaAsync(comando, _repositorio.ObterMotivo(comando.Id), ct); return; }
    }

    _repositorio.Salvar(comando.Id, StatusComando.Pendente);

    // 2. Sinalizar início (opcional — útil para diagnóstico)
    var iniciou = new RestRequest("api/v3/comandos/iniciou", Method.Post);
    iniciou.AddJsonBody(new { id = comando.Id, total = totalRegistros }); // total recomendado
    await _provedor.Client.PostAsync(iniciou, ct);

    // 3. Executar com retry local
    try
    {
        await comando.ExecutarAsync(ct);
        _repositorio.Salvar(comando.Id, StatusComando.Concluido);
        await NotificarConcluidoAsync(comando, ct);
    }
    catch (Exception ex) when (ex is not OperationCanceledException)
    {
        _repositorio.Salvar(comando.Id, StatusComando.Falhou, ex.Message);
        await NotificarFalhaAsync(comando, ex.Message, ct);
    }
}

private async Task NotificarConcluidoAsync(Comando comando, CancellationToken ct)
{
    var req = new RestRequest("api/v3/comandos/completou", Method.Post);
    req.AddJsonBody(new { id = comando.Id });
    await _provedor.Client.PostAsync(req, ct);
}

private async Task NotificarFalhaAsync(Comando comando, string motivo, CancellationToken ct)
{
    var req = new RestRequest("api/v3/comandos/falhou", Method.Post);
    req.AddJsonBody(new { id = comando.Id, motivo });
    await _provedor.Client.PostAsync(req, ct);
}