Elements headless
O mesmo estado e as mesmas regras que o Builder do Cord usa, sem uma linha de interface: o design fica por sua conta.
@flouviahq/elements/headless é o núcleo do Cord Elements. <CordBuilder>, os hooks de React e Vue e os Web Components consomem esse mesmo núcleo. Importá-lo não registra elementos nem toca o DOM: apenas estado, validação e rede. Use-o quando quiser uma interface própria, em qualquer framework ou sem nenhum.
npm install @flouviahq/elements
import { createCordClient, createQuoteBuilder, createFiscalForm } from '@flouviahq/elements/headless';
| Exporta | Para quê |
|---|---|
createCordClient |
Cliente HTTP do navegador para o Cord ou para o seu proxy. |
createQuoteBuilder |
O estado completo da montagem de um orçamento: cliente, itens, impostos, totais, envio. |
createFiscalForm |
O estado do formulário fiscal do destinatário. |
createStore |
O store observável que os três usam (get, subscribe, set). |
reduceQuoteView, INITIAL_QUOTE_VIEW |
O redutor do estado em tempo real de um orçamento incorporado. |
calculateDocumentTotals, roundMoney |
O motor de totais que o servidor usa. |
validateFiscalReceptor, validRfc, validSpainTaxId, validEin, fiscalText |
Validação fiscal e os seus textos em espanhol, inglês, português, francês e alemão. |
sanitizeAppearance, appearanceToCss |
O validador da Appearance API. |
CordError |
O erro tipado, com code e requestId. |
createCordClient
const client = createCordClient({ publishableKey: 'pk_test_...' });
// ou, com o seu backend como intermediário:
const viaProxy = createCordClient({ proxyUrl: '/api/cord' });
Passe uma das duas: publishableKey (uma chave pk_, que só pode usar as rotas do Elements) ou proxyUrl (a base do seu proxy de servidor, com o formato de /api/v1). Com as duas, ou com uma chave sk_, lança um erro.
| Opção | Padrão | Detalhe |
|---|---|---|
publishableKey |
— | Chave pk_live_ ou pk_test_. |
proxyUrl |
— | Por exemplo /api/cord. As requisições vão com os cookies do seu site. |
baseUrl |
https://cordhq.app |
Origem do Cord no modo publicável. |
timeoutMs |
15000 |
Interrompe cada tentativa. |
maxRetries |
2 (máximo 5) |
Novas tentativas diante de falha de rede, 429 e 5xx, respeitando Retry-After. |
debug |
false |
Registra cada requisição com o seu request_id no console. |
fetch |
fetch global |
Para testes. |
| Método | O que faz |
|---|---|
config({ force? }) |
GET /elements/config: moedas, impostos do país, condições e validade da sua organização. É guardada com o seu ETag e a chamada seguinte envia If-None-Match: se nada mudou, o Cord responde 304 e a cópia é usada. force: true a solicita completa. |
products() |
GET /productos: o catálogo, sem custos. |
createQuote(input, { idempotencyKey? }) |
POST /cotizaciones com uma Idempotency-Key (a sua ou um UUID novo). |
aiDraft(input, onItem, { signal? }) |
POST /elements/ai-draft: preenche itens com IA a partir de texto (texto, até 4.000 caracteres) ou de um arquivo (archivo, JPG, PNG ou PDF de até 4 MB). Chama onItem para cada linha assim que a lê. |
const config = await client.config();
console.log(config.org.moneda, config.impuestos.etiqueta, config.monedas);
createQuoteBuilder
const builder = createQuoteBuilder({
client,
onCreated: (quote) => console.log(quote.folio, quote.link_publico),
});
const unsubscribe = builder.subscribe((state) => render(state));
builder.setCliente({ empresa: 'Acme', email: 'purchasing@acme.com' });
const key = builder.addItem({ descripcion: 'Installation', cantidad: 1, precio_unitario: 12500 });
builder.updateItem(key, { cantidad: 2 });
const quote = await builder.submit(); // null se houver erros ou se falhou
Ao ser criado, o builder carrega sozinho a configuração e o catálogo (status passa de loading para ready). Cada alteração recalcula os totais com o mesmo motor do servidor, e a alíquota de cada linha vem do catálogo real de impostos da organização.
| Método | O que faz |
|---|---|
setCliente(patch) |
empresa, email, contacto, telefono. |
setFiscal(recipient | null) |
O destinatário fiscal que viaja com o orçamento. |
setMoneda(code), setTerminos(t), setVigencia(days), setNotas(text), setPreciosIncluyenImpuesto(bool) |
As condições do orçamento. |
addItem(item?) |
Adiciona uma linha e devolve a sua key. Sem tax_rate, usa a alíquota padrão. |
addProduct(product, quantity?) |
Adiciona um produto do catálogo. |
updateItem(key, patch), removeItem(key) |
Edita ou remove uma linha pela sua key. |
searchProducts(text, limit?) |
Busca no catálogo carregado, por nome ou SKU. |
validate() |
Devolve e guarda os issues. |
submit() |
Valida, cria o orçamento e devolve a resposta, ou null. |
draftWithAi(input, { signal? }) |
Preenche itens com IA; cada linha proposta traz sugerida. |
reset(), load() |
Esvazia o orçamento, ou recarrega a configuração e o catálogo. |
submit() usa uma única Idempotency-Key por tentativa de envio: um clique duplo ou uma nova tentativa por falha de rede nunca criam dois orçamentos. A chave é renovada assim que você altera qualquer coisa.
O estado (builder.get()) traz:
| Campo | Conteúdo |
|---|---|
status |
loading, ready, submitting, success ou error. |
config, products |
O que carregou do Cord. |
cliente, fiscal, moneda, terminos, vigencia_dias, notas, precios_incluyen_impuesto |
O que a pessoa preencheu. |
items |
As linhas, cada uma com a sua key. |
totals |
subtotal, impuestos, retencionTotal, total, o detalhamento por alíquota (porTasa), as retenciones e as lineas calculadas. |
issues |
{ field, code } para cada problema, por exemplo { field: 'items', code: 'no_items' }. |
result, error |
O orçamento criado, ou o CordError da última tentativa. |
ai |
status, count e error do preenchimento com IA. |
Para o seu analytics, onEvent(event, data) recebe builder.loaded, builder.item_added, builder.item_removed, builder.submitted, builder.created, builder.failed e builder.ai_drafted. Os nomes são estáveis entre versões.
createFiscalForm
const fiscal = createFiscalForm({ country: 'MX' });
fiscal.subscribe((s) => {
console.log(s.visibleErrors, s.regimenes.map((r) => r.codigo));
});
fiscal.set('tax_id', 'EKU9003173C9');
fiscal.touch('tax_id');
const recipient = fiscal.submit(); // o destinatário normalizado, ou null
builder.setFiscal(recipient);
O estado traz values, touched, validation (com ok, value, errors, warnings e persona), visibleErrors, os regimenes e usosCfdi que se aplicam ao tipo de RFC e os fields do país. As regras estão em Formulário fiscal.
Com o seu framework
O store segue o contrato get() + subscribe(), por isso se conecta a qualquer sistema reativo:
import { useSyncExternalStore } from 'react';
function useBuilderState(builder) {
return useSyncExternalStore(builder.subscribe, builder.get, builder.get);
}import { useCordQuoteBuilder, useCordStore } from '@flouviahq/elements/vue';
// useCordStore(store) devolve um ShallowRef que acompanha o store.const total = document.querySelector('#total');
builder.subscribe((s) => {
total.textContent = new Intl.NumberFormat(s.config?.org.locale ?? 'en', {
style: 'currency',
currency: s.moneda || 'USD',
}).format(s.totals.total);
});Estado em tempo real de um orçamento incorporado
reduceQuoteView(state, event) converte os eventos que o iframe de <cord-quote> retransmite em um estado (ready, status, folio, moneda, total, approved, rejected, paid, signedBy, lastEvent). É o que está por trás da propriedade state e do evento statechange do Web Component; veja Web Components.