# glob — Módulo de globalización `glob` es la capa de globalización del proyecto. Agrupa cuatro módulos de formateo y conversión —número, moneda, fecha/hora y unidades— bajo un único punto de entrada con locale compartido. --- ## Índice 1. [Diseño](#diseño) 2. [LocaleResolver](#localeresolver) 3. [createGlob](#createglob) 4. [numr — Números](#numr) 5. [curr — Moneda](#curr) 6. [dati — Fecha y hora](#dati) 7. [unit — Unidades](#unit) 8. [Referencia de tipos](#referencia-de-tipos) --- ## Diseño ### Principios - **Un locale, todos los módulos.** El locale vive en la instancia `ling`. Cuando se llama a `glob.setLocale()`, todos los módulos formatean con el nuevo locale de forma automática, sin necesidad de recrearlos. - **Módulos independientes.** Cada módulo (`numr`, `curr`, `dati`, `unit`) puede instanciarse de forma autónoma con `createNumr`, `createCurr`, etc., sin necesitar `createGlob`. - **Locale reactivo.** Cada función `f()` acepta un `LocaleResolver` opcional. Si se omite, usa el default configurado al crear la instancia (que puede ser una función que lee el locale en tiempo de ejecución). - **Fábricas puras.** No hay clases ni singletons. Cada llamada a `createX()` devuelve un objeto cerrado con su estado interno. ### Estructura de archivos ``` src/glob/ ├── comn_types.ts — GlobLocale, LocaleResolver ├── comn_engine.ts — resolveLocale() ├── glob_types.ts — GlobConfig, GlobInstance ├── glob_engine.ts — createGlob() ├── numr_types.ts — NumrConfig, NumrInstance ├── numr_engine.ts — createNumr() ├── curr_types.ts — CurrDefinition, CurrConfig, CurrInstance ├── curr_engine.ts — createCurr() ├── dati_types.ts — DatiConfig, DatiInstance, DatiOptions ├── dati_engine.ts — createDati() ├── unit_types.ts — UnitCategory, UnitConfig, UnitInstance ├── unit_engine.ts — createUnit() └── unit_langs.ts — defaultCategories (definiciones por defecto) ``` ### Flujo de locale en `createGlob` ``` glob.setLocale('en') └─▶ ling.setLocale('en') // fuente de verdad └─▶ listeners.forEach(fn => fn()) // notifica suscriptores externos glob.numr.f(1234) └─▶ resolveLocale(getLocale) // llama a ling.getLocale() en cada f() └─▶ Intl.NumberFormat('en').format(1234) ``` Los módulos reciben `getLocale` (una función `() => GlobLocale`) como `defaultLocale`, por lo que leen el locale actual en cada invocación sin necesidad de reconectarse. --- ## LocaleResolver ```ts type LocaleResolver = GlobLocale | (() => GlobLocale); ``` Todos los métodos `f()` aceptan un `LocaleResolver` como último parámetro. Puede ser: | Forma | Cuándo usarla | |---|---| | `'es'` | Locale fijo en esa llamada concreta | | `() => user.locale` | Locale dinámico que se lee en cada llamada | | *(omitido)* | Usa el `defaultLocale` configurado al crear la instancia | `resolveLocale(l)` en `comn_engine.ts` normaliza ambas formas: ```ts resolveLocale('es') // → 'es' resolveLocale(() => 'en') // → 'en' ``` --- ## createGlob Punto de entrada principal. Crea una instancia integrada con locale compartido. ```ts import { createGlob } from '@/glob/glob_engine'; const glob = createGlob({ locale: 'es', ling, // instancia de LingInstance numr: { maxDecimals: 2 }, curr: { definitions: { EUR: eurDef, USD: usdDef }, selectedCurrency: 'EUR', }, dati: { selectedTimeFormat: '24h' }, unit: { selectedSystem: 'metric' }, }); ``` ### API de GlobInstance | Método / propiedad | Descripción | |---|---| | `getLocale()` | Locale activo (delegado a `ling`) | | `setLocale(locale)` | Cambia el locale en `ling` y notifica suscriptores | | `onLocaleChange(fn)` | Suscribe un listener; devuelve función de desuscripción | | `t(key, params?)` | Traducción via `ling.t` | | `ts(key, params?)` | Traducción con soporte plural via `ling.ts` | | `ling` | La instancia `LingInstance` completa | | `numr` | Módulo de números | | `curr` | Módulo de moneda | | `dati` | Módulo de fecha/hora | | `unit` | Módulo de unidades | ### Cambio reactivo de locale ```ts glob.setLocale('en'); glob.numr.f(1234.5); // '1,234.5' glob.curr.f(99); // '99.00 €' glob.dati.f(new Date()); // 'March 2, 2026' ``` ### Suscriptor de cambios ```ts const unsub = glob.onLocaleChange(locale => { console.log('Nuevo locale:', locale); }); glob.setLocale('fr'); // → 'Nuevo locale: fr' unsub(); // elimina el listener ``` --- ## numr Formateo de números usando `Intl.NumberFormat`. ### Creación independiente ```ts import { createNumr } from '@/glob/numr_engine'; const numr = createNumr({ minDecimals: 0, maxDecimals: 2 }, 'es'); ``` ### `f(n, opts?, locale?)` ```ts numr.f(1234567.89) // '1.234.567,89' (es) numr.f(1234567.89, {}, 'en') // '1,234,567.89' numr.f(1234.5, { maxDecimals: 0 }) // '1.235' numr.f(0.1234, { minDecimals: 2, maxDecimals: 4 }) // '0,1234' ``` | Parámetro | Tipo | Default | |---|---|---| | `n` | `number` | — | | `opts.minDecimals` | `number` | `0` | | `opts.maxDecimals` | `number` | `2` | | `locale` | `LocaleResolver` | defaultLocale de instancia | --- ## curr Formateo y conversión de moneda. ### Definir monedas ```ts import type { CurrDefinition } from '@/glob/curr_types'; import { p } from '@/ling/engine'; const EUR: CurrDefinition = { symbol: '€', code: 'EUR', name: p({ es: { one: 'euro', other: 'euros' }, en: { one: 'euro', other: 'euros' }, }), conversionValue: 1, // relativo a moneda base }; ``` ### Creación independiente ```ts import { createCurr } from '@/glob/curr_engine'; const curr = createCurr( { EUR, USD, MXN }, // definiciones ling, // instancia LingInstance { selectedCurrency: 'EUR', decimals: 2 }, 'es' // defaultLocale ); ``` ### `f(amount, display?, locale?)` ```ts curr.f(99.5) // '99,50 €' (symbol, es) curr.f(99.5, 'symbol', 'en') // '99.50 €' curr.f(99.5, 'code') // '99,50 EUR' curr.f(1, 'name') // '1,00 euro' curr.f(2, 'name') // '2,00 euros' ``` | `display` | Resultado ejemplo | |---|---| | `'symbol'` (default) | `99,50 €` | | `'code'` | `99,50 EUR` | | `'name'` | `99,50 euros` | ### Conversión ```ts curr.convert(100, 'EUR', 'USD') // → 108 (si USD.conversionValue = 1.08) curr.convertTo(100, 'USD') // convierte desde selectedCurrency ``` `conversionValue` puede ser un número estático o una función `() => number` para tasas dinámicas: ```ts const USD: CurrDefinition = { // ... conversionValue: () => fetchLiveRate('USD'), }; ``` ### Gestión de moneda activa ```ts curr.getCurrency() // 'EUR' curr.setCurrency('USD') // cambia la moneda activa curr.get('EUR') // → CurrDefinition | undefined ``` --- ## dati Formateo de fechas y horas usando `Intl.DateTimeFormat`. ### Creación independiente ```ts import { createDati } from '@/glob/dati_engine'; const dati = createDati({ selectedTimeFormat: '24h' }, 'es'); ``` ### `f(date, opts?, locale?)` ```ts const d = new Date('2026-03-02T15:30:00'); dati.f(d) // '2 de marzo de 2026' (long, es) dati.f(d, 'short') // '2/3/26' dati.f(d, { date: 'long', time: 'short' }) // '2 de marzo de 2026, 15:30' dati.f(d, { time: 'short' }, 'en') // '3:30 PM' (si timeFormat = '12h') ``` #### `DatiOptions` | Valor | Resultado | |---|---| | `'long'` (default) | Solo fecha en formato largo | | `'short'` | Solo fecha en formato corto | | `{ date: 'long' }` | Solo fecha larga | | `{ time: 'short' }` | Solo hora corta | | `{ date: 'long', time: 'short' }` | Fecha y hora | ### Preferencias ```ts dati.setTimeFormat('12h') // '12h' | '24h' dati.getTimeFormat() // '12h' dati.setDateOrder('MDY') // 'DMY' | 'MDY' | 'YMD' dati.getDateOrder() // 'MDY' ``` > `dateOrder` está almacenado en el estado pero el orden real lo gestiona `Intl.DateTimeFormat` según el locale. Se expone para que la UI pueda reflejarlo. --- ## unit Conversión y formateo de unidades de medida. Soporta dos sistemas: `metric` e `imperial`. ### Categorías disponibles | Categoría | Base métrica | Base imperial | Unidades | |---|---|---|---| | `weight` | `kg` | `lb` | kg, g, lb, oz, t | | `length` | `m` | `mi` | km, m, cm, mm, mi, ft, in, yd | | `temp` | `c` | `f` | c, f, k | | `volume` | `l` | `gal` | l, ml, m³, gal, fl oz, pt | | `area` | `m²` | `ft²` | m², km², ha, ft², mi², ac | ### Creación independiente ```ts import { createUnit } from '@/glob/unit_engine'; const unit = createUnit({}, { selectedSystem: 'metric' }, 'es'); ``` ### Sistema activo ```ts unit.setSystem('imperial') unit.getSystem() // 'imperial' ``` ### `category.f(value, display?, locale?)` Formatea el valor en la unidad base del sistema activo: ```ts unit.weight.f(70) // '70 kg' (metric, symbol) unit.weight.f(70, 'name') // '70 kilogramos' unit.weight.f(70, 'symbol', 'en') // '70 kg' unit.setSystem('imperial'); unit.weight.f(70) // '154,32 lb' ``` ### `category.fTo(value, targetUnit?, display?, locale?)` Convierte desde la base métrica y formatea en la unidad destino: ```ts unit.length.fTo(1000, 'km') // '1 km' unit.temp.fTo(100, 'f') // '212 °F' (100°C → °F) unit.area.fTo(10000, 'ha') // '1 ha' ``` Si se omite `targetUnit`, usa la base del sistema activo. ### `category.convert(value, from, to)` Conversión numérica pura, sin formateo: ```ts unit.weight.convert(1, 'kg', 'lb') // 2.204... unit.temp.convert(100, 'c', 'f') // 212 unit.length.convert(1, 'mi', 'km') // 1.609... ``` La temperatura usa conversión directa (no por factor lineal), manejada internamente con `convertTemp()`. ### Personalizar categorías Se pueden sobreescribir las categorías por defecto o añadir nuevas unidades: ```ts const unit = createUnit( { weight: { base: 'kg', baseImperial: 'lb', units: { // solo las unidades que necesitas kg: { factor: 1, system: 'metric', symbol: { es: 'kg', en: 'kg' }, name: ... }, }, }, }, { selectedSystem: 'metric' }, 'es' ); ``` --- ## Referencia de tipos ### `GlobLocale` ```ts type GlobLocale = SupportedLocale | (string & {}); ``` Acepta cualquier código BCP 47 (`'es'`, `'en'`, `'fr'`, `'es-MX'`, etc.). ### `LocaleResolver` ```ts type LocaleResolver = GlobLocale | (() => GlobLocale); ``` ### `CurrDefinition` ```ts interface CurrDefinition { symbol : string; code : string; name : LingPluralFn>; conversionValue?: number | (() => number | null) | null; } ``` ### `UnitDefinition` ```ts interface UnitDefinition { symbol : LingRecord; // { es: 'kg', en: 'kg' } name : LingPluralFn<...>; // plural bilingüe factor : number; // relativo a la base de la categoría system : 'metric' | 'imperial'; } ``` La conversión entre unidades (excepto temperatura) se calcula como: ``` resultado = (valor × factor_origen) / factor_destino ``` donde `factor` es la equivalencia de cada unidad respecto a la base de la categoría (ej. `kg` tiene factor `1`, `g` tiene factor `0.001`).