Use Cases

Implementacion de formato de moneda con Claude Code

Aprende sobre la implementacion de formato de moneda usando Claude Code. Incluye ejemplos de codigo practicos.

Implementando correctamente el formato de moneda con Claude Code

En sitios de comercio electronico, aplicaciones de contabilidad y servicios internacionales, el formato preciso de moneda es indispensable. Las reglas de punto decimal, separador de miles y simbolo de moneda varian segun el pais y la moneda, por lo que formatear manualmente puede ser fuente de errores. Con Claude Code, puedes construir eficientemente implementaciones robustas usando Intl.NumberFormat o bibliotecas especializadas.

Uso de Intl.NumberFormat

> Crea una utilidad de formato de moneda compatible con multiples monedas.
> Compatible con yen japones, dolar estadounidense y euro.
> Incluye visualizacion por locale, conversion de divisas y validacion de montos.
// src/lib/currency.ts

export type CurrencyCode = 'JPY' | 'USD' | 'EUR' | 'GBP' | 'CNY' | 'KRW';

interface FormatOptions {
  currency: CurrencyCode;
  locale?: string;
  compact?: boolean;      // Visualizacion abreviada como "1.2K"
  showSign?: boolean;     // Mostrar signo +/-
}

/**
 * Formatear moneda
 */
export function formatCurrency(
  amount: number,
  options: FormatOptions
): string {
  const { currency, locale = 'es-ES', compact = false, showSign = false } = options;

  const formatter = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
    notation: compact ? 'compact' : 'standard',
    signDisplay: showSign ? 'exceptZero' : 'auto',
    // Las monedas decimales usan 2 decimales, JPY/KRW usan 0 (determinacion automatica)
  });

  return formatter.format(amount);
}

// Ejemplo de uso
formatCurrency(1234567, { currency: 'JPY' });          // "¥1,234,567"
formatCurrency(1234.56, { currency: 'USD' });           // "$1,234.56"
formatCurrency(1234.56, { currency: 'EUR', locale: 'de-DE' }); // "1.234,56 €"
formatCurrency(1234567, { currency: 'JPY', compact: true });    // "¥123万"

Consideraciones para calculos con moneda

// src/lib/currency-math.ts

/**
 * Para evitar errores de punto flotante, calcular con enteros (unidad minima de moneda)
 * JPY: 1 yen = 1 | USD: 1 centavo = 1 | EUR: 1 centimo = 1
 */
export class Money {
  constructor(
    private readonly amountInMinorUnit: number,
    private readonly currency: CurrencyCode
  ) {}

  // Decimales de centavo/yen
  private static decimals(currency: CurrencyCode): number {
    return ['JPY', 'KRW'].includes(currency) ? 0 : 2;
  }

  static fromMajorUnit(amount: number, currency: CurrencyCode): Money {
    const decimals = Money.decimals(currency);
    const minor = Math.round(amount * Math.pow(10, decimals));
    return new Money(minor, currency);
  }

  toMajorUnit(): number {
    const decimals = Money.decimals(this.currency);
    return this.amountInMinorUnit / Math.pow(10, decimals);
  }

  add(other: Money): Money {
    this.assertSameCurrency(other);
    return new Money(this.amountInMinorUnit + other.amountInMinorUnit, this.currency);
  }

  subtract(other: Money): Money {
    this.assertSameCurrency(other);
    return new Money(this.amountInMinorUnit - other.amountInMinorUnit, this.currency);
  }

  multiply(factor: number): Money {
    return new Money(Math.round(this.amountInMinorUnit * factor), this.currency);
  }

  format(locale?: string): string {
    return formatCurrency(this.toMajorUnit(), { currency: this.currency, locale });
  }

  private assertSameCurrency(other: Money) {
    if (this.currency !== other.currency) {
      throw new Error(`Las monedas son diferentes: ${this.currency} y ${other.currency}`);
    }
  }
}

// Ejemplo de uso
const price = Money.fromMajorUnit(19.80, 'USD');
const tax = price.multiply(0.1);
const total = price.add(tax);
console.log(total.format()); // "$21.78"

Conversion de tipo de cambio

// src/lib/exchange.ts
interface ExchangeRates {
  base: CurrencyCode;
  rates: Record<CurrencyCode, number>;
  updatedAt: string;
}

let cachedRates: ExchangeRates | null = null;

export async function getExchangeRates(base: CurrencyCode = 'USD'): Promise<ExchangeRates> {
  // Reutilizar cache si tiene menos de 1 hora
  if (cachedRates && Date.now() - new Date(cachedRates.updatedAt).getTime() < 3600000) {
    return cachedRates;
  }

  const res = await fetch(`https://api.exchangerate-api.com/v4/latest/${base}`);
  const data = await res.json();

  cachedRates = {
    base,
    rates: data.rates,
    updatedAt: new Date().toISOString(),
  };

  return cachedRates;
}

export async function convertCurrency(
  amount: number,
  from: CurrencyCode,
  to: CurrencyCode
): Promise<{ converted: number; rate: number }> {
  const rates = await getExchangeRates(from);
  const rate = rates.rates[to];

  return {
    converted: Math.round(amount * rate * 100) / 100,
    rate,
  };
}

Componente de entrada de moneda

// src/components/CurrencyInput.tsx
'use client';
import { useState } from 'react';

interface Props {
  value: number;
  currency: CurrencyCode;
  onChange: (value: number) => void;
}

export function CurrencyInput({ value, currency, onChange }: Props) {
  const [displayValue, setDisplayValue] = useState(value.toLocaleString());

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const raw = e.target.value.replace(/[^0-9.-]/g, '');
    setDisplayValue(e.target.value);
    const num = parseFloat(raw);
    if (!isNaN(num)) onChange(num);
  };

  const handleBlur = () => {
    setDisplayValue(value.toLocaleString());
  };

  const symbol = new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency,
    currencyDisplay: 'narrowSymbol',
  }).formatToParts(0).find((p) => p.type === 'currency')?.value || '';

  return (
    <div className="relative">
      <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">{symbol}</span>
      <input
        type="text"
        value={displayValue}
        onChange={handleChange}
        onBlur={handleBlur}
        className="w-full border rounded-lg pl-8 pr-4 py-2 text-right"
        inputMode="decimal"
      />
    </div>
  );
}

La trampa del punto flotante

El problema de que en JavaScript 0.1 + 0.2 === 0.30000000000000004 es bien conocido. En calculos de moneda, la regla es siempre trabajar con enteros de la unidad minima (centavos/yenes) y convertir a decimales solo al momento de mostrar.

Articulos relacionados

Para el formato de fechas, consulta manejo de fecha y hora, y para la internacionalizacion en general, consulta la guia de implementacion de i18n.

La especificacion detallada de Intl.NumberFormat se puede consultar en MDN (developer.mozilla.org/Intl/NumberFormat).

#Claude Code #currency #formato #Intl #internationalization