Formateo y validación de datos de América Latina: RUT, CPF, CUIT, monedas y teléfonos.
npm i @commenteme/paltaCada país es un namespace de dos letras. Dentro de cada uno, los módulos siempre tienen la
misma forma: format, validate, strip, mask para documentos; format, parse,
symbol, code para moneda.
import { br, cl, ar } from '@commenteme/palta'
// Documentos
br.cpf.format('11144477735') // '111.444.777-35'
br.cpf.validate('111.444.777-35') // true
br.cpf.strip('111.444.777-35') // '11144477735'
br.cpf.mask // '###.###.###-##'
cl.rut.format('123456785') // '12.345.678-5'
ar.cuit.validate('20123456786') // true- 23 países de América Latina: documentos de identidad y tributarios (CPF, CNPJ, RUT, CUIT, NIT, RUC y mas) con
format,validate,stripymask. - Monedas, teléfonos y códigos postales de cada país, con el formato local.
- Medidas con auto-escala: largo, peso y volumen (
150 cm→1,50 m). detect(): identifica país y tipo de un documento.- Adaptador para Shopify: precios en unidad mínima,
MoneyV2y pesos de la Admin API. - Cero dependencias en runtime, ESM y CJS, tipos incluidos y un build global para usar sin bundler.
La wiki está en github.com/commentemeapp/palta/wiki.
// Moneda — formato local, con o sin símbolo
br.currency.format(1234.5) // 'R$ 1.234,50'
br.currency.format(1234.5, { symbol: false }) // '1.234,50'
br.currency.parse('R$ 1.234,50') // 1234.5// Teléfonos y códigos postales
cl.phone.format('912345678') // '+56 9 1234 5678' (internacional por defecto)
br.phone.format('11987654321') // '(11) 98765-4321' (Brasil es la excepción: nacional)
br.phone.format('11987654321', { international: true }) // '+55 (11) 98765-4321'
br.zipcode.format('01310100') // '01310-100'import { measurements } from '@commenteme/palta'
// Medidas — auto-escala (cm >= 100 -> m, g >= 1000 -> kg, ml >= 1000 -> l)
measurements.length.format(150, 'cm') // '1,50 m'
measurements.weight.format(2500, 'g') // '2,50 kg'
measurements.length.convert(1, 'm', 'cm') // 100Pensado para VTEX IO, que entrega dimensiones en cm y peso en g: format() escala solo
para mostrar y nunca altera el valor original. Para México, Puerto Rico o República Dominicana,
{ decimalSeparator: '.' } cambia a 1,500.00 m.
import { detect } from '@commenteme/palta'
// Detección — identifica país y tipo a partir del valor
detect('111.444.777-35')
// -> { country: 'BR', type: 'cpf', valid: true, formatted: '111.444.777-35' }detect() cubre los documentos principales (CPF, CNPJ, RUT, CUIT, DNI, CC, NIT, RUC, RFC,
CURP, RIF, CI) y devuelve null cuando no reconoce el valor. No es exhaustiva entre los 23
países.
Sin bundler (HTML puro, temas de Shopify), el build palta.global.js expone window.palta.
Fija la versión en la URL para que una versión mayor no rompa la página:
<script src="https://cdn.jsdelivr.net/npm/@commenteme/palta@1/dist/palta.global.js"></script>
<script>
palta.br.cpf.validate('111.444.777-35') // true
</script>Para reducir el bundle, cada país se puede importar por subpath:
import { cpf, currency } from '@commenteme/palta/br'
import { length } from '@commenteme/palta/measurements'@commenteme/palta/shopify traduce lo que entrega Shopify a los módulos de cada país: códigos ISO
de país y moneda, precios en unidad mínima y unidades de peso de la Admin API.
En un tema (Liquid), con el build global. Shopify entrega los precios multiplicados por 100,
incluso en monedas sin centavos (CLP $10.990 llega como 1099000), y formatCents lo
contempla:
<span data-price>{{ product.price }}</span>
<input data-tax-id placeholder="CPF">
<script src="https://cdn.jsdelivr.net/npm/@commenteme/palta@1/dist/palta.global.js"></script>
<script>
const currencyCode = {{ cart.currency.iso_code | json }}
const country = palta.shopify.byCountry({{ localization.country.iso_code | json }})
document.querySelectorAll('[data-price]').forEach((el) => {
el.textContent = palta.shopify.formatCents(Number(el.textContent), currencyCode)
})
// En tiendas de Brasil valida el CPF; en otros países `country.cpf` no existe
const input = document.querySelector('[data-tax-id]')
input.addEventListener('blur', () => {
input.setCustomValidity(country?.cpf?.validate(input.value) === false ? 'CPF inválido' : '')
})
</script>En Hydrogen o con la Storefront API, MoneyV2 llega con amount como string:
import { formatMoney, formatWeight } from '@commenteme/palta/shopify'
formatMoney({ amount: '1234.5', currencyCode: 'BRL' }) // 'R$ 1.234,50'
formatMoney({ amount: '1234.5', currencyCode: 'USD' }, { country: 'PR' }) // '$ 1,234.50'
formatWeight({ value: 0.5, unit: 'KILOGRAMS' }) // '500 g'| función | que hace |
|---|---|
byCountry(iso) |
namespace del país por ISO 3166-1 ('BR' → br, 'DO' → do_); undefined fuera de LatAm |
byCurrency(code, country?) |
CurrencyModule por ISO 4217; country elige el formato de USD (Ecuador por defecto) |
fromCents(cents) |
unidad mínima de Shopify a unidad de moneda (siempre ÷100) |
formatCents(cents, currencyCode, options?) |
precio de Liquid o /cart.js formateado |
formatMoney(money, options?) |
MoneyV2 formateado; options acepta decimals, symbol y country |
weightUnit(unit) |
g/kg/lb/oz y GRAMS/KILOGRAMS/OUNCES/POUNDS → WeightUnit |
formatWeight({ value, unit }, options?) |
peso de la Admin API con auto-escala g ↔ kg |
En Liquid, variant.weight siempre está en gramos: measurements.weight.format(weight, 'g')
alcanza. Shopify no tiene dimensiones nativas (se guardan en metafields), así que
measurements.length se usa con la unidad que haya elegido la tienda.
shopify importa los 23 países, porque no sabe de antemano cuál va a pedir la tienda. Si la
tienda vende en un solo país, importa ese subpath (@commenteme/palta/br) y fromCents.
Versión del código: 1.4.0, según package.json. El inventario de países está en src/index.ts; los contratos de los módulos están en src/types.ts y los cambios de API en CHANGELOG.md.
La validación comprueba las reglas implementadas para cada documento (formato y, cuando corresponde, dígito verificador). No consulta registros fiscales ni verifica la identidad de una persona. La detección automática tampoco sustituye el país elegido por el usuario.
Cada namespace de país exporta currency, phone, zipcode y uno o dos documentos. Los
contratos están en src/types.ts y son idénticos en todos los países.
| módulo | exporta | notas |
|---|---|---|
| documento | format strip validate mask |
mask es la plantilla, por ejemplo ###.###.###-## |
currency |
format parse symbol code |
format(value, { decimals, symbol }) |
phone |
format validate mask countryCode |
format(value, { international }) — internacional por defecto en todos los países menos Brasil, que devuelve el formato nacional salvo que se pida { international: true } |
zipcode |
format validate mask |
Los 23 países y sus documentos
| subpath | país | documentos |
|---|---|---|
./ar |
Argentina | cuit dni |
./bo |
Bolivia | ci nit |
./br |
Brasil | cpf cnpj |
./cl |
Chile | rut |
./co |
Colombia | cc nit |
./cr |
Costa Rica | cedula dimex juridica |
./cu |
Cuba | ni reeup |
./do |
República Dominicana | cedula rnc |
./ec |
Ecuador | ci ruc |
./gt |
Guatemala | dpi nit |
./hn |
Honduras | dni rtn |
./ht |
Haití | cin nif |
./jm |
Jamaica | nin trn |
./mx |
México | curp rfc |
./ni |
Nicaragua | cedula ruc |
./pa |
Panamá | cedula ruc |
./pe |
Perú | dni ruc |
./pr |
Puerto Rico | ein ssn |
./py |
Paraguay | ci ruc |
./sv |
El Salvador | dui nit |
./tt |
Trinidad y Tobago | nis tin |
./uy |
Uruguay | ci rut |
./ve |
Venezuela | ci rif |
En el import raíz, República Dominicana se expone como do_ porque do es palabra reservada
de JavaScript. El subpath sigue siendo @commenteme/palta/do.
| export global | subpath | que hace |
|---|---|---|
detect |
. |
identifica país y tipo de un documento; null si no lo reconoce |
measurements.length |
./measurements |
mm cm m km in ft |
measurements.weight |
./measurements |
mg g kg oz lb |
measurements.volume |
./measurements |
ml l fl oz |
shopify |
./shopify |
adaptador para Liquid, /cart.js, Storefront y Admin API |
- Node
>= 16en runtime - ESM y CJS, con tipos incluidos
- Sin dependencias en runtime
El toolchain de desarrollo pide mas: vitest 4 exige Node ^20 || ^22 || >=24 y la CI corre en Node 22. Esa
diferencia importa en VTEX IO — el paquete instala y corre en Node 16 igual, porque la salida
compilada no usa ninguna API exclusiva de versiones nuevas.
.
├── src/
│ ├── [pais]/ # un directorio por país
│ │ ├── index.ts # re-exporta los submódulos
│ │ ├── [documento].ts
│ │ ├── currency.ts
│ │ ├── phone.ts
│ │ └── zipcode.ts
│ ├── measurements/ # length, weight, volume
│ ├── shopify/ # adaptador para Shopify
│ ├── detect.ts
│ ├── types.ts # contratos compartidos
│ └── utils.ts
├── tests/ # espejo de src/, un archivo por módulo
├── demo/index.html
├── tsup.config.ts
└── vitest.config.ts
git clone https://github.com/commentemeapp/palta.git
cd palta
npm install
npm test # vitest
npm run test:coverage # cobertura, umbral 94%
npm run lint # tsc --noEmit, única puerta estática
npm run build # tsup: ESM + CJS + .d.ts + IIFE (palta.global.js)Los tests importan directo de src/, así que corren sin haber hecho build. La CI
(.github/workflows/ci.yml) corre lint, cobertura y build en cada push a dev y main y en cada PR contra main.
Para agregar un país nuevo hay que tocar cinco lugares, en este orden:
src/xx/index.tsre-exportando los submódulosexport * as xx from './xx/index.js'ensrc/index.ts- entrada en
tsup.config.ts - entrada en
exportsdepackage.json - tests en
tests/xx/con cobertura mínima de 94%
Publicar es manual: npm run publish:npm. prepublishOnly corre lint, tests y build antes
de subir nada.
Cero dependencias en runtime, TypeScript estricto sin any, funciones puras que nunca lanzan y cobertura de 94% o mas; Conventional Commits y un issue antes de cualquier cambio grande (CONTRIBUTING.md).
MIT.
Hecho por commente.me · hablecon@commente.me
