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.
- 1Sincronizar o catálogo
GET /api/produtos(preços e activos) eGET /api/estoque/saldos?armazemId=…(stock). Faça-o por ciclos e guarde o resultado na loja; não consulte a Mucanda a cada visita. - 2Validar antes de cobrar
GET /api/estoque/saldos/por-produtos?armazemId=…&produtoIds=…confirma o stock do carrinho à última hora. - 3Criar o cliente
POST /api/clientes, comIdempotency-Key. Se o NIF já existir (DUPLICADO), procure-o comGET /api/clientes?filtro=…. - 4Emitir a factura/recibo
POST /api/documentoscomIdempotency-Keyigual ao número da encomenda (ou a um GUID gravado com ela). Guarde oide onumerona encomenda. - 5Devolver
POST /api/documentos/notas-creditocom 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.
- 1Documentos do mês
GET /api/documentos?dataInicio=…&dataFim=…&tamanho=200e percorra as páginas. Cada linha traz os totais. - 2Pagamentos e IVA
GET /api/relatorios/pagamentoseGET /api/relatorios/ivacom o mesmo período. - 3Compras e despesas
GET /api/financeiro/contas-a-pagar?situacao=Todas(comorigem=DespesaouCompraEstoque). - 4Ficheiros fiscais
GET /api/saft(facturação),GET /api/saft/compras?periodo=AAAA-MMeGET /api/saft/inventario?anoFiscal=…. São pedidos pesados: faça-os um de cada vez. - 5Verificar 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.
- 1Os catálogos primeiro
GET /api/catalogos,GET /api/impostoseGET /api/produtos/catalogos/{tipo}dão os identificadores que os artigos e os clientes referenciam (imposto, unidade, categoria, município). - 2Fornecedores e clientes
POST /api/fornecedoresePOST /api/clientes. O número de contribuinte não repete:DUPLICADOsignifica «já existe», e pode ignorar-se. - 3Artigos
POST /api/produtoscria o artigo (sem stock). O stock entra por uma compra a um fornecedor, comPOST /api/estoque/entradas, que lhe dá também o custo; o stock inicial pode registar-se na aplicação. - 4Em ritmo controlado
Uns 10 pedidos por segundo, cada um com
Idempotency-Keyderivada 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
- 1Ver o que está por receber
GET /api/financeiro/contas-a-receber?situacao=Vencidase o resumo por escalão em/resumo. - 2Registar o recebimento
GET /api/documentos/pendentes?clienteId=…ePOST /api/documentos/reciboscom as facturas (mais antigas primeiro) e o método de pagamento. - 3Avisar o cliente
POST /api/documentos/{id}/enviar-emailenvia o recibo.
#Tratar as falhas
// 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))