Pular para o conteúdo

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);
}

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.