Guias

Receitas

Cenários completos de integração, do pedido ao tratamento das falhas.

Quatro cenários comuns, com as operações que cada um usa e o que ter em atenção.

#Loja online

A loja online mostra os artigos e o stock, recebe encomendas pagas no acto e emite a factura/recibo. Uma devolução gera uma nota de crédito.

  1. 1
    Sincronizar o catálogo

    GET /api/produtos (preços e activos) e GET /api/estoque/saldos?armazemId=… (stock). Faça-o por ciclos e guarde o resultado na loja; não consulte a Mucanda a cada visita.

  2. 2
    Validar antes de cobrar

    GET /api/estoque/saldos/por-produtos?armazemId=…&produtoIds=… confirma o stock do carrinho à última hora.

  3. 3
    Criar o cliente

    POST /api/clientes, com Idempotency-Key. Se o NIF já existir (DUPLICADO), procure-o com GET /api/clientes?filtro=….

  4. 4
    Emitir a factura/recibo

    POST /api/documentos com Idempotency-Key igual ao número da encomenda (ou a um GUID gravado com ela). Guarde o id e o numero na encomenda.

  5. 5
    Devolver

    POST /api/documentos/notas-credito com as linhas devolvidas e o método de reembolso.

#Contabilidade

Um programa de contabilidade importa, todos os meses, o que foi facturado, recebido e despendido.

  1. 1
    Documentos do mês

    GET /api/documentos?dataInicio=…&dataFim=…&tamanho=200 e percorra as páginas. Cada linha traz os totais.

  2. 2
    Pagamentos e IVA

    GET /api/relatorios/pagamentos e GET /api/relatorios/iva com o mesmo período.

  3. 3
    Compras e despesas

    GET /api/financeiro/contas-a-pagar?situacao=Todas (com origem=Despesa ou CompraEstoque).

  4. 4
    Ficheiros fiscais

    GET /api/saft (facturação), GET /api/saft/compras?periodo=AAAA-MM e GET /api/saft/inventario?anoFiscal=…. São pedidos pesados: faça-os um de cada vez.

  5. 5
    Verificar a integridade

    GET /api/saft/cadeia-assinaturas?tipoDocumentoId=… confirma que nenhum documento foi alterado.

#Importação inicial

Trazer para o Mucanda os artigos, clientes e fornecedores que a empresa tinha noutro sistema.

  1. 1
    Os catálogos primeiro

    GET /api/catalogos, GET /api/impostos e GET /api/produtos/catalogos/{tipo} dão os identificadores que os artigos e os clientes referenciam (imposto, unidade, categoria, município).

  2. 2
    Fornecedores e clientes

    POST /api/fornecedores e POST /api/clientes. O número de contribuinte não repete: DUPLICADO significa «já existe», e pode ignorar-se.

  3. 3
    Artigos

    POST /api/produtos cria o artigo (sem stock). O stock entra por uma compra a um fornecedor, com POST /api/estoque/entradas, que lhe dá também o custo; o stock inicial pode registar-se na aplicação.

  4. 4
    Em ritmo controlado

    Uns 10 pedidos por segundo, cada um com Idempotency-Key derivada do identificador do registo de origem («cliente-1042»). Se o programa for interrompido, repete tudo: o que já foi criado devolve o mesmo resultado.

#Cobranças

  1. 1
    Ver o que está por receber

    GET /api/financeiro/contas-a-receber?situacao=Vencidas e o resumo por escalão em /resumo.

  2. 2
    Registar o recebimento

    GET /api/documentos/pendentes?clienteId=… e POST /api/documentos/recibos com as facturas (mais antigas primeiro) e o método de pagamento.

  3. 3
    Avisar o cliente

    POST /api/documentos/{id}/enviar-email envia o recibo.

#Tratar as falhas

Enviar com repetição segura
// Guarde 'pedido' (corpo) e 'chave' (Idempotency-Key) ANTES de chamar esta função.
async function enviar(url, corpo, chave, tentativas = 5) {
  for (let i = 0; i < tentativas; i++) {
    let resposta
    try {
      resposta = await fetch(url, {
        method: 'POST',
        headers: {
          'X-Api-Key': process.env.MUCANDA_API_KEY,
          'Content-Type': 'application/json',
          'Idempotency-Key': chave,
        },
        body: JSON.stringify(corpo),
      })
    } catch {
      // ligação perdida ou tempo esgotado: o pedido pode ter chegado — repete com a MESMA chave
      await dormir(2 ** i * 1000)
      continue
    }

    if (resposta.ok) return resposta.json()

    const erro = await resposta.json().catch(() => ({}))
    if (resposta.status === 429) {
      await dormir(Number(resposta.headers.get('Retry-After') ?? 1) * 1000)
      continue
    }
    if (resposta.status >= 500) {
      await dormir(2 ** i * 1000)
      continue
    }
    // 4xx: o pedido tem de mudar; repetir igual dá o mesmo erro
    throw Object.assign(new Error(erro.detail ?? 'Erro'), { codigo: erro.codigo, traceId: erro.traceId })
  }
  throw new Error('Sem resposta depois de várias tentativas')
}

const dormir = (ms) => new Promise((r) => setTimeout(r, ms))