npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flouviahq/elements

v2.2.0

Published

Cord Elements: cotizador embebible con Shadow DOM, Builder headless dirigido por el servidor y Fiscal Element. Web Components + React, Vue, Framer y Webflow.

Readme

@flouviahq/elements

Para asistentes de IA / For AI assistants: este README es un resumen. Antes de decidir qué soporta Cord, lee llms.txt (qué existe y qué no) y llms-full.txt (toda la documentación en un archivo). Spec: openapi.json. Existen hoy: llaves restringidas rk_ (permiso por recurso, IPs permitidas, vencimiento), registro de peticiones en el panel (Desarrolladores › Registros), versionado por Cord-Version, paginación por cursor, Idempotency-Key, webhooks firmados, modo prueba con simuladores y servidor MCP.

SDK de Cord para el navegador: el cotizador embebible en Shadow DOM, un Builder headless que dibuja lo que tu organización tiene configurado en Cord (divisas, impuestos por línea, retenciones, términos) y un Fiscal Element que valida RFC, NIF/CIF y EIN igual que el servidor. Web Components + React, Vue, Framer y Webflow sobre un mismo núcleo sin framework (@flouviahq/elements/headless).

Para tu servidor (API v1, webhooks, proxy) usa @flouviahq/node.

Instalación

npm install @flouviahq/elements

Dos formas de usarlo

  1. Iframe viewer (CordCotizador) — muestra una cotización YA CREADA para que el cliente la vea/apruebe/pague. Es un <iframe> a /embed/{token} con tu marca.
  2. Builder headless (useQuoteBuilder / <CordBuilder>) — arma y crea una cotización nueva desde tu propia UI (o la que trae el SDK por defecto).

Ambos requieren decidir CÓMO tu app habla con la API de Cord: publishable key (directo desde el navegador) o tu propio proxy (tu backend llama a Cord con una sk_). Ver Seguridad y modos — es una unión discriminada, pasar los dos a la vez es un error de compilación.


Quickstart (React)

// app/layout.tsx (o donde envuelvas tu app)
import { CordProvider } from '@flouviahq/elements/react';

export default function Layout({ children }) {
  return (
    <CordProvider publishableKey={process.env.NEXT_PUBLIC_CORD_PUBLISHABLE_KEY!}>
      {children}
    </CordProvider>
  );
}
// Ver/aprobar una cotización existente
import { CordCotizador } from '@flouviahq/elements/react';

function QuotePage({ token }: { token: string }) {
  return (
    <CordCotizador
      token={token}
      onApproved={(d) => console.log('Aprobada, firmada por', d.signed_by)}
      onPay={(d) => console.log('Pago abierto en', d.url)}
    />
  );
}
// Armar una cotización nueva (usa el Builder por defecto)
import { CordBuilder } from '@flouviahq/elements/react';

function NewQuote() {
  return (
    <CordBuilder
      onQuoteCreated={(q) => console.log('Creada:', q.folio, q.link_publico)}
    />
  );
}

Seguridad y modos

CordProviderProps es una unión discriminada: pasas publishableKey o proxyUrl, nunca los dos. Esto existe porque pasar ambos a la vez por error (una pk_ de prueba junto a un proxy real) es exactamente el bug que se volvió imposible de cometer al compilar.

Modo publishable — directo desde el navegador

<CordProvider publishableKey="pk_live_..."> {/* o pk_test_... */}

Una publishable key (pk_live_.../pk_test_..., la generas en Ajustes › Developers) puede, por diseño:

  • Crear cotizaciones (POST /api/v1/cotizaciones).
  • Leer el catálogo de productos (GET /api/v1/productos — el servidor oculta costo/margen).

Y nunca puede:

  • Leer tu CRM de clientes. useCordClients() en modo publishable no intenta la red — devuelve un CordError con code: 'clients_require_proxy' de inmediato. No es un bug, es la política: una pk_ vive en el código fuente de tu página; leer el directorio de clientes filtraría email/RFC/límite de crédito a quien vea ese código fuente. Pasa clients como prop en su lugar.
  • Nada de facturación, cobranza ni ajustes.

Una pk_live_ solo funciona con dominios permitidos configurados en Ajustes › Elements: la key exige el header Origin/Referer y lo valida contra esa lista (origin_allowlist_required si está vacía). Las pk_test_ funcionan desde cualquier origen, incluido localhost. Una cotización creada con pk_ queda como borrador para que la revises: no puede enviarse por correo ni fijar costos o precios negociados.

Modo proxy — tu propio backend

<CordProvider proxyUrl="/api/cord">

proxyUrl es una base con la misma forma que /api/v1: el SDK pide {proxyUrl}/elements/config, {proxyUrl}/productos, POST {proxyUrl}/cotizaciones y, si tu servidor lo autoriza, {proxyUrl}/clientes. No lo escribas a mano: monta createElementsProxy() de @flouviahq/node, que solo atiende esas rutas, solo desde tu origen, y sanea la cotización igual que una llave publicable.

// app/api/cord/[...path]/route.ts
import { createElementsProxy } from '@flouviahq/node';
const proxy = createElementsProxy({ secretKey: process.env.CORD_SECRET_KEY!, authorizeClients: (req) => isLoggedIn(req) });
export const GET = proxy;
export const POST = proxy;

Ninguno — solo visor

<CordCotizador> funciona sin <CordProvider> en absoluto (uso suelto: solo necesitas mostrar/aprobar una cotización, no crear ninguna ni leer catálogo/clientes):

<CordCotizador token={token} appearance={{ theme: 'dark' }} />

Self-host / staging

import { configureCord } from '@flouviahq/elements/react'; // o '@flouviahq/elements'

configureCord({ baseUrl: 'https://staging.tudominio.com' });

Precedencia en todo el SDK: prop del componente > <CordProvider> > configureCord() > https://cordhq.app.


Headless: el núcleo sin framework

Todo el estado vive en @flouviahq/elements/headless, sin React, sin Vue y sin tocar el DOM al importarse. Los componentes de este paquete son consumidores de ese núcleo; tú también.

import { createCordClient, createQuoteBuilder } from '@flouviahq/elements/headless';

const builder = createQuoteBuilder({ client: createCordClient({ publishableKey: 'pk_test_…' }) });
builder.subscribe((s) => render(s));          // s.config, s.items, s.totals, s.issues, s.status
builder.addProduct(builder.get().products[0], 2);
builder.updateItem(key, { tax_rate: 0 });     // solo tasas de s.config.impuestos.opciones
await builder.submit();                       // un doble clic nunca crea dos cotizaciones
  • La configuración (divisas ofrecidas, impuestos por línea, retenciones, términos, nombre del impuesto) llega de GET /api/v1/elements/config con ETag. Cambias algo en Ajustes y tu sitio lo refleja sin republicar.
  • Los totales salen de calculateDocumentTotals, el mismo motor con el que Cord guarda.
  • submit() usa una sola Idempotency-Key por intento: reintentos y doble clic no duplican.

En React — useQuoteBuilder

import { useQuoteBuilder } from '@flouviahq/elements/react';

function MiBuilder() {
  const { items, totals, config, builder, formatMoney, issueFor, status } = useQuoteBuilder({
    onQuoteCreated: (q) => router.push(q.link_publico),
  });
  return (
    <form onSubmit={(e) => { e.preventDefault(); builder.submit(); }}>
      {items.map((it) => (
        <select key={it.key} value={String(it.tax_rate)} onChange={(e) => builder.updateItem(it.key, { tax_rate: Number(e.target.value) })}>
          {config?.impuestos.opciones.map((o) => <option key={o.label} value={String(o.rate)}>{o.label}</option>)}
        </select>
      ))}
      <strong>{formatMoney(totals.total)}</strong>
    </form>
  );
}

En Vue: useCordQuoteBuilder(clientOptions) devuelve { state, builder }.

Fiscal Element

Captura los datos fiscales del cliente con la misma validación que aplica Cord al guardar: RFC con dígito verificador del SAT, régimen y uso de CFDI filtrados por persona física o moral, 616/S01 para público en general y aviso cuando el nombre trae "S.A. de C.V."; NIF, NIE o CIF en España; EIN en Estados Unidos.

<form>
  <cord-fiscal-form name="fiscal" country="MX" lang="es"></cord-fiscal-form>
  <button>Guardar</button>
</form>

Es un elemento asociado a formularios: aporta su valor (JSON del receptor normalizado) bajo name y bloquea el envío mientras haya errores, como un <input required>. Eventos fiscalchange y fiscalvalid; partes ::part(field|label|input|error|warning).

En React: <CordFiscalForm country="MX" onValid={(r) => …} /> o <CordBuilder fiscal /> para que el receptor viaje con la cotización. Headless: createFiscalForm().

Componente <CordBuilder> — compound pattern con estilos

Si no necesitas tu propia UI, <CordBuilder> trae una por default, con clases estables .cord-* (nunca inline styles) para que puedas sobreescribirlas con CSS normal — Tailwind, CSS Modules, lo que uses:

import { CordBuilder } from '@flouviahq/elements/react';

// Uso simple (layout por defecto)
<CordBuilder onQuoteCreated={(q) => console.log(q.folio)} />

// Compound — reordena/omite piezas
<CordBuilder onQuoteCreated={handleCreated}>
  <CordBuilder.Header />
  <CordBuilder.Items className="mi-clase-tailwind" />
  <CordBuilder.Summary />
  <CordBuilder.SubmitButton className="btn btn-primary" />
</CordBuilder>

Estilizar: appearance.elements

Cada nodo interno tiene una clase base .cord-<key> que nunca se reemplaza — un override solo agrega encima:

<CordProvider
  publishableKey="pk_test_..."
  appearance={{
    variables: { colorPrimary: '#111827', borderRadius: '10px' },
    elements: {
      submitButton: 'my-tailwind-btn-class',
      formFieldInput: { borderColor: '#e5e7eb' },
      itemRow: { className: 'my-row', style: { background: '#fafafa' } },
    },
  }}
>

Los defaults del SDK se inyectan dentro de @layer cord y se prepend()-ean al <head> — tu CSS (Tailwind u otro) siempre gana, sin necesitar un solo !important.

Headless real (baseTheme: 'none')

<CordProvider publishableKey="pk_test_..." appearance={{ baseTheme: 'none' }}>

El SDK deja de inyectar CUALQUIER CSS — las clases .cord-* se siguen emitiendo en el markup para que tú las estilices por completo, sin ningún estilo de Cord de por medio.


Appearance API completa

interface CordAppearance {
  theme?: 'light' | 'dark' | 'auto';        // afecta al iframe Y a los componentes nativos
  baseTheme?: 'default' | 'none';           // 'none' = headless real (solo nativos)
  variables?: {
    colorPrimary?: string; colorText?: string; colorBackground?: string;
    fontFamily?: string; borderRadius?: string; [key: string]: string | undefined;
  };
  elements?: CordElements;                  // solo nativos — ver arriba
  fonts?: Array<{ cssSrc: string }>;        // Google Fonts / Bunny Fonts (allowlist)
}

theme: 'dark'/'auto' funciona tanto en <CordCotizador> (el iframe) como en <CordBuilder> (componentes nativos, vía data-cord-theme + prefers-color-scheme). El default oscuro es sobrio (#e5e7eb/#111827); tus propias variables siempre ganan sobre ese default.


Iframe viewer — eventos tipados

<CordCotizador
  token={token}
  onReady={() => {}}
  onViewed={(d) => {}}                    // { token? }
  onApproved={(d) => {}}                  // { signed_by, hash }
  onSigned={(d) => {}}                    // dispara JUNTO con onApproved (misma acción)
  onRejected={(d) => {}}                  // { comentario }
  onMessage={(d) => {}}                   // { action, mensaje, propuesta? }
  onItemComment={(d) => {}}               // { item_id, mensaje }
  onPay={(d) => {}}                       // { url } absoluta; Cord ya abre el pago en su ventana
  onUpdated={(d) => {}}                   // { subtotal, total, moneda } el vendedor cambió la propuesta
  onStatusChanged={(d) => {}}             // { status } paid | rejected | expired | invoiced
  onStateChange={(s) => {}}               // estado en vivo: ready, status, total, approved, paid
  onEvent={(event) => {                   // catch-all tipado — switch exhaustivo sobre event.type
    switch (event.type) {
      case 'cord:approved': /* event.detail: CordApprovedDetail */ break;
      // …
    }
  }}
/>

⚠️ onApproved/onSigned se disparan ambos por una sola acción del cliente (aprobar = firmar) — no los cuentes como dos eventos de negocio distintos si agregas métricas.


Vue

<script setup>
import { CordCotizador } from '@flouviahq/elements/vue';
</script>

<template>
  <CordCotizador
    :token="token"
    @approved="onApproved"
    @signed="onSigned"
    @item-comment="onItemComment"
    @pay="onPay"
  />
</template>

Web Component (HTML, PHP, Laravel, Rails, cualquier stack)

<script type="module" src="https://unpkg.com/@flouviahq/elements@2/dist/index.mjs"></script>

<cord-quote token="TU_TOKEN" appearance='{"theme":"auto","variables":{"colorPrimary":"#0a192f"}}'></cord-quote>

<script>
  const quote = document.querySelector('cord-quote');
  quote.addEventListener('approved', (e) => console.log('Firmó', e.detail.signed_by));
  quote.addEventListener('statechange', (e) => console.log(e.detail.status, e.detail.total));
</script>

Vive en Shadow DOM: tus estilos no lo rompen y los suyos no tocan tu página. Personaliza con cord-quote::part(frame), ::part(skeleton) y las variables --cord-*. La sombra aísla estilos; firmar y pagar siguen dentro del iframe de Cord, que es la frontera de seguridad.

Atributos: token (requerido), base-url, min-height, appearance (JSON; también como propiedad). Propiedad state con el estado en vivo. Los eventos llegan sin el prefijo cord:. <cord-cotizador> sigue funcionando como alias.

<cord-invoice token="…"> monta la factura de /i/{token} con los mismos atributos y emite además paid. El pago se abre en una ventana de Cord, nunca dentro del iframe. En React y Vue, <CordInvoice>; en embed.js, data-cord-document="invoice".

Loader de una línea (embed.js) — sitios sin bundler

<script src="https://cordhq.app/embed.js" async></script>
<div data-cord-token="TU_TOKEN"></div>

Atributos: data-cord-token (requerido), data-cord-base-url, data-cord-min-height, data-cord-appearance. Monta también los bloques que se agregan después (SPAs, modales) y re-emite los eventos cord:* sobre el div. public/embed.js se genera desde el mismo código que @flouviahq/elements/webflow; el par legacy data-cord-cotizador + data-token sigue funcionando.

Framer

import { FramerCordCotizador } from '@flouviahq/elements/framer';

Agrégalo como Code Component; token y baseUrl quedan expuestos en el panel de propiedades.


Depuración

<CordProvider publishableKey="pk_test_…" debug>

Aparece una barra flotante con cada evento del iframe, cada petición con su request_id y los avisos de configuración (por ejemplo, un appearance descartado o una llave de prueba en un dominio público). Vive en un Shadow DOM y no se dibuja con una pk_live_. En <cord-quote> es el atributo debug; en embed.js, data-cord-debug (sin llave, solo en localhost o con ?cord_debug=1).

Servidor

@flouviahq/elements/server está deprecado y se conserva por compatibilidad. Usa @flouviahq/node: API v1 completa con idempotencia y autopaginación, constructEvent con WebCrypto que exige la firma V1 con timestamp, y el proxy para el modo proxyUrl.


Errores tipados (CordError)

Todos los hooks y CordAPI lanzan/exponen CordError (nunca un Error genérico):

import { CordError } from '@flouviahq/elements/react'; // o '@flouviahq/elements'

try {
  await cord.quotes.create(data);
} catch (err) {
  if (err instanceof CordError) {
    console.log(err.status, err.code, err.message);
    // err.code: 'invalid_request' | 'missing_key' | 'invalid_key' | 'insufficient_scope'
    //         | 'missing_origin' | 'unauthorized_origin' | 'invalid_origin' | 'rate_limited'
    //         | 'clients_require_proxy' | 'network_error' | 'server_error' | 'unknown'
  }
}

Motor de cálculo (engine)

El mismo motor que usa el backend de Cord para subtotal/IVA/total — úsalo si construyes tu propia UI y necesitas paridad exacta con lo que el servidor va a calcular.

import { calculateTotals, roundMoney } from '@flouviahq/elements';

const { subtotal, iva, total } = calculateTotals(
  [{ cantidad: 2, precio_unitario: 100 }],
  0.16,   // ivaPct — debe estar en [0, 1] o lanza RangeError (nunca cae a un default silencioso)
  false,  // ivaIncluido
);

roundMoney(total, 2); // solo para MOSTRAR — nunca se aplica dentro de calculateTotals

TypeScript

Los tipos se generan desde el código fuente (tsc --emitDeclarationOnly) — no se escriben a mano, así que nunca quedan desincronizados de lo que el paquete realmente exporta. Funciona en ESM y CJS (node16/nodenext/bundler) y con la resolución node10 de TypeScript clásico.

Changelog

Ver CHANGELOG.md — incluye la tabla de migración a 1.0.0.