UNPKG

@birhaus/financial

Version:

Financial components and utilities for BIRHAUS design system - SEPRELAD compliant

1,149 lines (1,148 loc) 140 kB
import React4, { useState, useCallback, useEffect, useMemo } from 'react'; import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; import { useBirhaus } from '@birhaus/provider'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { BirhausSpinner } from '@birhaus/loading'; import { AlertTriangle, Wallet, EyeOff, Eye, AlertCircle, Search, Filter, ChevronDown, Download, Clock, Info, PieChart, Plus, Edit, Trash2, TrendingUp, TrendingDown, Minus, Smartphone, Building2, CreditCard, DollarSign, PiggyBank, ArrowDownLeft, ArrowUpRight, Calculator, ShoppingCart, BookOpen, Heart, Plane, Home, Car, Utensils, CheckCircle, Target, Calendar, BarChart3 } from 'lucide-react'; // src/utils/currency.ts var SUPPORTED_CURRENCIES = { PYG: { code: "PYG", symbol: "\u20B2", nameEs: "Guaran\xED paraguayo", nameEn: "Paraguayan guaran\xED", decimals: 0, // Guaraní doesn't use decimal places thousandSeparator: ".", decimalSeparator: "," }, USD: { code: "USD", symbol: "$", nameEs: "D\xF3lar estadounidense", nameEn: "US dollar", decimals: 2, thousandSeparator: ",", decimalSeparator: "." }, EUR: { code: "EUR", symbol: "\u20AC", nameEs: "Euro", nameEn: "Euro", decimals: 2, thousandSeparator: ".", decimalSeparator: "," }, BRL: { code: "BRL", symbol: "R$", nameEs: "Real brasile\xF1o", nameEn: "Brazilian real", decimals: 2, thousandSeparator: ".", decimalSeparator: "," }, ARS: { code: "ARS", symbol: "$", nameEs: "Peso argentino", nameEn: "Argentine peso", decimals: 2, thousandSeparator: ".", decimalSeparator: "," } }; var EXCHANGE_RATES = { USD: 1, PYG: 7300, // 1 USD = 7300 PYG (approximate) EUR: 0.85, // 1 USD = 0.85 EUR (approximate) BRL: 5.2, // 1 USD = 5.2 BRL (approximate) ARS: 800 // 1 USD = 800 ARS (approximate) }; var SEPRELAD_THRESHOLDS = { CASH_OPERATION: 1e4, // USD 10,000 for cash operations SUSPICIOUS_OPERATION: 5e3, // USD 5,000 for suspicious operations DAILY_CUMULATIVE: 1e4 // USD 10,000 cumulative daily }; function formatCurrency(amount, options = {}) { const { currency = "PYG", locale = "es-PY", showSymbol = true, showCode = false, minimumFractionDigits, maximumFractionDigits } = options; const currencyInfo = SUPPORTED_CURRENCIES[currency]; if (!currencyInfo) { throw new Error(`Unsupported currency: ${currency}`); } const fracDigits = currency === "PYG" ? 0 : minimumFractionDigits ?? currencyInfo.decimals; const maxFracDigits = currency === "PYG" ? 0 : maximumFractionDigits ?? currencyInfo.decimals; try { const formatter = new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: fracDigits, maximumFractionDigits: maxFracDigits }); let formatted = formatter.format(amount); if (currency === "PYG" && locale.startsWith("es")) { const numberPart = Math.abs(amount).toLocaleString("es-PY"); const sign = amount < 0 ? "-" : ""; formatted = `${sign}${currencyInfo.symbol} ${numberPart}`; } if (showCode && !showSymbol) { const numberPart = Math.abs(amount).toLocaleString(locale, { minimumFractionDigits: fracDigits, maximumFractionDigits: maxFracDigits }); const sign = amount < 0 ? "-" : ""; formatted = `${sign}${numberPart} ${currency}`; } else if (showCode && showSymbol) { formatted = `${formatted} ${currency}`; } return formatted; } catch (error) { console.warn("Currency formatting failed, using fallback:", error); const numberPart = Math.abs(amount).toFixed(fracDigits); const sign = amount < 0 ? "-" : ""; if (showSymbol) { return `${sign}${currencyInfo.symbol} ${numberPart}`; } else if (showCode) { return `${sign}${numberPart} ${currency}`; } else { return `${sign}${numberPart}`; } } } function parseCurrency(value, currency = "PYG") { if (!value || typeof value !== "string") { return null; } const currencyInfo = SUPPORTED_CURRENCIES[currency]; if (!currencyInfo) { return null; } let cleanValue = value.replace(new RegExp(`\\${currencyInfo.symbol}`, "g"), "").replace(new RegExp(currency, "g"), "").trim(); const isNegative = cleanValue.startsWith("-"); if (isNegative) { cleanValue = cleanValue.substring(1); } if (currency === "PYG") { cleanValue = cleanValue.replace(/\./g, ""); } else { if (cleanValue.includes(",") && cleanValue.includes(".")) { const lastComma = cleanValue.lastIndexOf(","); const lastDot = cleanValue.lastIndexOf("."); if (lastDot > lastComma) { cleanValue = cleanValue.replace(/,/g, "").replace(".", "."); } else { cleanValue = cleanValue.replace(/\./g, "").replace(",", "."); } } else if (cleanValue.includes(",")) { const commaCount = (cleanValue.match(/,/g) || []).length; if (commaCount === 1 && cleanValue.split(",")[1].length <= 2) { cleanValue = cleanValue.replace(",", "."); } else { cleanValue = cleanValue.replace(/,/g, ""); } } } const parsed = parseFloat(cleanValue); if (isNaN(parsed)) { return null; } return isNegative ? -parsed : parsed; } function convertCurrency(amount, fromCurrency, toCurrency) { if (fromCurrency === toCurrency) { return amount; } const usdAmount = amount / EXCHANGE_RATES[fromCurrency]; const convertedAmount = usdAmount * EXCHANGE_RATES[toCurrency]; const targetDecimals = SUPPORTED_CURRENCIES[toCurrency].decimals; return Math.round(convertedAmount * Math.pow(10, targetDecimals)) / Math.pow(10, targetDecimals); } function checkSEPRELADThreshold(amount, currency, operationType = "cash") { const amountUSD = convertCurrency(amount, currency, "USD"); let thresholdUSD; switch (operationType) { case "cash": thresholdUSD = SEPRELAD_THRESHOLDS.CASH_OPERATION; break; case "suspicious": thresholdUSD = SEPRELAD_THRESHOLDS.SUSPICIOUS_OPERATION; break; case "cumulative": thresholdUSD = SEPRELAD_THRESHOLDS.DAILY_CUMULATIVE; break; default: thresholdUSD = SEPRELAD_THRESHOLDS.CASH_OPERATION; } return { requiresReport: amountUSD >= thresholdUSD, thresholdUSD, amountUSD }; } function getCurrencyInfo(currency) { return SUPPORTED_CURRENCIES[currency]; } function validateCurrencyAmount(amount, currency) { const errors = []; if (!Number.isFinite(amount)) { errors.push("Invalid number format"); return { valid: false, errors }; } if (Math.abs(amount) > 1e12) { errors.push("Amount exceeds maximum allowed value"); } const currencyInfo = SUPPORTED_CURRENCIES[currency]; if (currencyInfo) { const decimalPlaces = (amount.toString().split(".")[1] || "").length; if (decimalPlaces > currencyInfo.decimals) { errors.push(`Too many decimal places for ${currency} (max: ${currencyInfo.decimals})`); } } const seprelad = checkSEPRELADThreshold(amount, currency); if (seprelad.requiresReport) { console.info(`Amount exceeds SEPRELAD reporting threshold: ${seprelad.amountUSD} USD`); } return { valid: errors.length === 0, errors }; } function formatCurrencyForInput(amount, currency = "PYG") { const currencyInfo = SUPPORTED_CURRENCIES[currency]; if (!currencyInfo) { return amount.toString(); } if (currency === "PYG") { return amount.toLocaleString("es-PY", { useGrouping: true, minimumFractionDigits: 0, maximumFractionDigits: 0 }); } else { return amount.toLocaleString("es-PY", { useGrouping: true, minimumFractionDigits: currencyInfo.decimals, maximumFractionDigits: currencyInfo.decimals }); } } var cn = (...inputs) => twMerge(clsx(inputs)); function BirhausCurrencyDisplay({ amount, currency = "PYG", locale, showSymbol = true, showCode = false, className, ...props }) { const { language, reportViolation } = useBirhaus(); React4.useEffect(() => { if (currency === "USD" && amount >= 1e4) { reportViolation({ type: "performance", severity: "warning", element: document.createElement("span"), // Placeholder element message: `USD amount ${amount} exceeds SEPRELAD reporting threshold`, messageEs: `Monto USD ${amount} supera umbral de reporte SEPRELAD`, messageEn: `USD amount ${amount} exceeds SEPRELAD reporting threshold`, recommendation: "Ensure proper SEPRELAD compliance reporting for amounts \u2265 $10,000 USD", birhausPrinciple: 8 // Compliance principle (hypothetical extension) }); } if (Math.abs(amount) > 1e9) { reportViolation({ type: "performance", severity: "warning", element: document.createElement("span"), message: `Currency amount ${amount} exceeds reasonable display limits`, messageEs: `Monto ${amount} supera l\xEDmites razonables de visualizaci\xF3n`, messageEn: `Currency amount ${amount} exceeds reasonable display limits`, recommendation: "Consider using abbreviated notation for very large amounts", birhausPrinciple: 1 // Cognitive load reduction }); } }, [amount, currency, reportViolation]); const displayLocale = locale || (language === "es" ? "es-PY" : "en-US"); const formattedAmount = formatCurrency(amount, { currency, locale: displayLocale, showSymbol, showCode }); const amountColorClass = cn( "font-mono", // Monospace for better number alignment { "text-green-600": amount > 0, "text-red-600": amount < 0, "text-birhaus-muted-foreground": amount === 0 } ); const ariaLabel = React4.useMemo(() => { const absAmount = Math.abs(amount); const sign = amount < 0 ? language === "es" ? "menos " : "negative " : ""; const currencyNames = { PYG: { es: "guaran\xEDes", en: "Paraguayan guaran\xEDes" }, USD: { es: "d\xF3lares americanos", en: "US dollars" }, EUR: { es: "euros", en: "euros" }, BRL: { es: "reales brasile\xF1os", en: "Brazilian reais" }, ARS: { es: "pesos argentinos", en: "Argentine pesos" } }; const currencyName = currencyNames[currency]?.[language] || currencyNames[currency]?.es; return `${sign}${absAmount.toLocaleString(displayLocale)} ${currencyName}`; }, [amount, currency, language, displayLocale]); return /* @__PURE__ */ jsx( "span", { className: cn( "inline-flex items-center", amountColorClass, className ), "aria-label": ariaLabel, title: ariaLabel, ...props, children: formattedAmount } ); } var cn2 = (...inputs) => twMerge(clsx(inputs)); var defaultPaymentMethods = { efectivo: { tipo: "efectivo", nombreEs: "Efectivo", nombreEn: "Cash", icono: /* @__PURE__ */ jsx(DollarSign, { size: 20 }), requiereVerificacion: false, limiteTransaccion: 5e7 // 50M PYG SEPRELAD limit }, tarjeta_debito: { tipo: "tarjeta_debito", nombreEs: "Tarjeta de D\xE9bito", nombreEn: "Debit Card", icono: /* @__PURE__ */ jsx(CreditCard, { size: 20 }), requiereVerificacion: true, limiteTransaccion: 1e7, // 10M PYG daily limit comision: 0, tiempoProcesamientoMinutos: 5 }, tarjeta_credito: { tipo: "tarjeta_credito", nombreEs: "Tarjeta de Cr\xE9dito", nombreEn: "Credit Card", icono: /* @__PURE__ */ jsx(CreditCard, { size: 20 }), requiereVerificacion: true, limiteTransaccion: 2e7, // 20M PYG comision: 2.5, // 2.5% commission tiempoProcesamientoMinutos: 10 }, transferencia: { tipo: "transferencia", nombreEs: "Transferencia Bancaria", nombreEn: "Bank Transfer", icono: /* @__PURE__ */ jsx(Building2, { size: 20 }), requiereVerificacion: true, limiteTransaccion: 1e8, // 100M PYG comision: 1e3, // Fixed commission tiempoProcesamientoMinutos: 60 }, billetera_digital: { tipo: "billetera_digital", nombreEs: "Billetera Digital", nombreEn: "Digital Wallet", icono: /* @__PURE__ */ jsx(Smartphone, { size: 20 }), requiereVerificacion: true, limiteTransaccion: 5e6, // 5M PYG comision: 0.5, // 0.5% commission tiempoProcesamientoMinutos: 2 } }; function BirhausPaymentForm({ availablePaymentMethods, onPaymentSubmit, amount, currency = "PYG", recipientInfo, className, ...props }) { const { language, reportViolation } = useBirhaus(); const [isSubmitting, setIsSubmitting] = useState(false); const [selectedMethod, setSelectedMethod] = useState(null); const [formData, setFormData] = useState({ monto: amount, moneda: currency, destinatario: recipientInfo ? { nombre: recipientInfo.nombreEs, documento: recipientInfo.documento, cuenta: recipientInfo.cuenta } : void 0 }); const [errors, setErrors] = useState({}); React4.useEffect(() => { if (availablePaymentMethods.length > 7) { reportViolation({ type: "cognitive", severity: "warning", element: document.createElement("form"), message: `Payment form has ${availablePaymentMethods.length} payment methods (max recommended: 7)`, messageEs: `Formulario de pago con ${availablePaymentMethods.length} m\xE9todos de pago (m\xE1ximo recomendado: 7)`, messageEn: `Payment form has ${availablePaymentMethods.length} payment methods (max recommended: 7)`, recommendation: "Group payment methods or use progressive disclosure to reduce cognitive load", birhausPrinciple: 1 // Cognitive load reduction }); } }, [availablePaymentMethods.length, reportViolation]); const getPaymentMethodInfo = useCallback((method) => { return availablePaymentMethods.find((pm) => pm.tipo === method) || defaultPaymentMethods[method]; }, [availablePaymentMethods]); const validateForm = useCallback(() => { const newErrors = {}; if (!selectedMethod) { newErrors.metodo = language === "es" ? "Selecciona un m\xE9todo de pago" : "Select a payment method"; } if (!formData.destinatario?.nombre?.trim()) { newErrors.destinatario = language === "es" ? "Ingresa el nombre del destinatario" : "Enter recipient name"; } if (!formData.destinatario?.documento?.trim()) { newErrors.documento = language === "es" ? "Ingresa el documento del destinatario" : "Enter recipient document"; } if (!formData.concepto?.trim()) { newErrors.concepto = language === "es" ? "Ingresa el concepto del pago" : "Enter payment concept"; } if (selectedMethod) { const methodInfo = getPaymentMethodInfo(selectedMethod); if (methodInfo.limiteTransaccion && amount > methodInfo.limiteTransaccion) { newErrors.monto = language === "es" ? `Monto excede l\xEDmite de ${formatCurrency(methodInfo.limiteTransaccion, { currency })}` : `Amount exceeds limit of ${formatCurrency(methodInfo.limiteTransaccion, { currency })}`; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }, [selectedMethod, formData, amount, currency, language, getPaymentMethodInfo]); const handleSubmit = useCallback(async (e) => { e.preventDefault(); if (!validateForm() || !selectedMethod) return; setIsSubmitting(true); try { const submitData = { metodo: selectedMethod, monto: amount, moneda: currency, destinatario: { nombre: formData.destinatario.nombre, documento: formData.destinatario.documento, cuenta: formData.destinatario?.cuenta }, concepto: formData.concepto, programada: formData.programada, fechaProgramada: formData.fechaProgramada }; await onPaymentSubmit(submitData); } catch (error) { console.error("Payment submission error:", error); } finally { setIsSubmitting(false); } }, [validateForm, selectedMethod, amount, currency, formData, onPaymentSubmit]); const updateFormData = useCallback((updates) => { setFormData((prev) => ({ ...prev, ...updates })); if (updates.destinatario) { const newErrors = { ...errors }; delete newErrors.destinatario; delete newErrors.documento; setErrors(newErrors); } }, [errors]); return /* @__PURE__ */ jsxs( "form", { className: cn2("space-y-6", className), onSubmit: handleSubmit, ...props, children: [ /* @__PURE__ */ jsx("div", { className: "bg-birhaus-muted/30 p-4 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [ /* @__PURE__ */ jsx("p", { className: "text-sm text-birhaus-muted-foreground", children: language === "es" ? "Monto a pagar" : "Amount to pay" }), /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-birhaus-foreground", children: formatCurrency(amount, { currency, showSymbol: true }) }) ] }) }), /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-birhaus-foreground", children: language === "es" ? "M\xE9todo de pago" : "Payment method" }), /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: availablePaymentMethods.map((method) => { const isSelected = selectedMethod === method.tipo; const methodInfo = getPaymentMethodInfo(method.tipo); const displayName = language === "es" ? method.nombreEs : method.nombreEn || method.nombreEs; const exceedsLimit = methodInfo.limiteTransaccion && amount > methodInfo.limiteTransaccion; return /* @__PURE__ */ jsx( "button", { type: "button", onClick: () => !exceedsLimit && setSelectedMethod(method.tipo), disabled: !!exceedsLimit, className: cn2( "p-4 border rounded-lg text-left transition-all duration-200", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary", isSelected && "border-birhaus-primary bg-birhaus-primary/5", !isSelected && !exceedsLimit && "border-birhaus-border hover:border-birhaus-primary/50", exceedsLimit && "border-red-200 bg-red-50 opacity-50 cursor-not-allowed" ), children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ jsx("div", { className: "mt-1 text-birhaus-muted-foreground", children: method.icono }), /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx("h4", { className: "font-medium text-birhaus-foreground", children: displayName }), isSelected && /* @__PURE__ */ jsx("div", { className: "w-5 h-5 bg-birhaus-primary rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-2 h-2 bg-white rounded-full" }) }) ] }), /* @__PURE__ */ jsxs("div", { className: "mt-1 text-xs text-birhaus-muted-foreground space-y-1", children: [ methodInfo.comision && /* @__PURE__ */ jsxs("p", { children: [ language === "es" ? "Comisi\xF3n: " : "Fee: ", typeof methodInfo.comision === "number" && methodInfo.comision < 1 ? `${methodInfo.comision}%` : formatCurrency(methodInfo.comision, { currency }) ] }), methodInfo.tiempoProcesamientoMinutos && /* @__PURE__ */ jsxs("p", { children: [ language === "es" ? "Procesamiento: " : "Processing: ", methodInfo.tiempoProcesamientoMinutos < 60 ? `${methodInfo.tiempoProcesamientoMinutos} min` : `${Math.round(methodInfo.tiempoProcesamientoMinutos / 60)}h` ] }), exceedsLimit && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-red-600", children: [ /* @__PURE__ */ jsx(AlertTriangle, { size: 12 }), /* @__PURE__ */ jsx("span", { children: language === "es" ? `L\xEDmite: ${formatCurrency(methodInfo.limiteTransaccion, { currency })}` : `Limit: ${formatCurrency(methodInfo.limiteTransaccion, { currency })}` }) ] }) ] }) ] }) ] }) }, method.tipo ); }) }), errors.metodo && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: errors.metodo }) ] }), /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [ /* @__PURE__ */ jsx("h3", { className: "font-medium text-birhaus-foreground", children: language === "es" ? "Informaci\xF3n del destinatario" : "Recipient information" }), /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [ /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-birhaus-foreground mb-1", children: language === "es" ? "Nombre completo" : "Full name" }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.destinatario?.nombre || "", onChange: (e) => updateFormData({ destinatario: { ...formData.destinatario, nombre: e.target.value } }), className: cn2( "w-full px-3 py-2 border rounded-md", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary", errors.destinatario ? "border-red-500" : "border-birhaus-border" ), placeholder: language === "es" ? "Juan P\xE9rez" : "John Doe" } ), errors.destinatario && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.destinatario }) ] }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-birhaus-foreground mb-1", children: language === "es" ? "Documento" : "Document" }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.destinatario?.documento || "", onChange: (e) => updateFormData({ destinatario: { ...formData.destinatario, documento: e.target.value } }), className: cn2( "w-full px-3 py-2 border rounded-md", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary", errors.documento ? "border-red-500" : "border-birhaus-border" ), placeholder: language === "es" ? "12345678" : "12345678" } ), errors.documento && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.documento }) ] }) ] }), selectedMethod === "transferencia" && /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-birhaus-foreground mb-1", children: language === "es" ? "N\xFAmero de cuenta" : "Account number" }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.destinatario?.cuenta || "", onChange: (e) => updateFormData({ destinatario: { ...formData.destinatario, cuenta: e.target.value } }), className: "w-full px-3 py-2 border border-birhaus-border rounded-md focus:outline-none focus:ring-2 focus:ring-birhaus-primary", placeholder: "123456789" } ) ] }) ] }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-birhaus-foreground mb-1", children: language === "es" ? "Concepto" : "Concept" }), /* @__PURE__ */ jsx( "input", { type: "text", value: formData.concepto || "", onChange: (e) => updateFormData({ concepto: e.target.value }), className: cn2( "w-full px-3 py-2 border rounded-md", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary", errors.concepto ? "border-red-500" : "border-birhaus-border" ), placeholder: language === "es" ? "Pago de servicios" : "Service payment" } ), errors.concepto && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.concepto }) ] }), /* @__PURE__ */ jsx( "button", { type: "submit", disabled: isSubmitting || !selectedMethod, className: cn2( "w-full py-3 px-4 rounded-md font-medium transition-all duration-200", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary focus:ring-offset-2", !isSubmitting && selectedMethod ? "bg-birhaus-primary text-white hover:bg-birhaus-primary/90" : "bg-birhaus-muted text-birhaus-muted-foreground cursor-not-allowed" ), children: isSubmitting ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [ /* @__PURE__ */ jsx(BirhausSpinner, { size: "sm", color: "current" }), /* @__PURE__ */ jsx("span", { children: language === "es" ? "Procesando..." : "Processing..." }) ] }) : /* @__PURE__ */ jsxs("span", { children: [ language === "es" ? "Realizar pago" : "Make payment", selectedMethod && (() => { const methodInfo = getPaymentMethodInfo(selectedMethod); const commission = methodInfo.comision; if (commission) { const commissionAmount = typeof commission === "number" && commission < 1 ? amount * (commission / 100) : commission; return ` (+ ${formatCurrency(commissionAmount, { currency })})`; } return ""; })() ] }) } ), errors.monto && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 text-center", children: errors.monto }) ] } ); } var cn3 = (...inputs) => twMerge(clsx(inputs)); var accountTypeConfig = { corriente: { icon: /* @__PURE__ */ jsx(CreditCard, { size: 20 }), colorClass: "text-blue-600 bg-blue-100", nameEs: "Cuenta Corriente", nameEn: "Checking Account" }, ahorros: { icon: /* @__PURE__ */ jsx(PiggyBank, { size: 20 }), colorClass: "text-green-600 bg-green-100", nameEs: "Cuenta de Ahorros", nameEn: "Savings Account" }, credito: { icon: /* @__PURE__ */ jsx(CreditCard, { size: 20 }), colorClass: "text-purple-600 bg-purple-100", nameEs: "L\xEDnea de Cr\xE9dito", nameEn: "Credit Line" }, inversion: { icon: /* @__PURE__ */ jsx(TrendingUp, { size: 20 }), colorClass: "text-orange-600 bg-orange-100", nameEs: "Cuenta de Inversi\xF3n", nameEn: "Investment Account" } }; var accountStatusConfig = { activa: { colorClass: "text-green-700 bg-green-100", nameEs: "Activa", nameEn: "Active" }, inactiva: { colorClass: "text-gray-700 bg-gray-100", nameEs: "Inactiva", nameEn: "Inactive" }, bloqueada: { colorClass: "text-red-700 bg-red-100", nameEs: "Bloqueada", nameEn: "Blocked" }, cerrada: { colorClass: "text-gray-700 bg-gray-200", nameEs: "Cerrada", nameEn: "Closed" } }; function BirhausAccountSummary({ accounts, showBalances = true, showInactive = false, onAccountClick, className, ...props }) { const { language, reportViolation } = useBirhaus(); const [balancesVisible, setBalancesVisible] = React4.useState(showBalances); const filteredAccounts = React4.useMemo(() => { return accounts.filter( (account) => showInactive || account.estado === "activa" ); }, [accounts, showInactive]); React4.useEffect(() => { if (filteredAccounts.length > 7) { reportViolation({ type: "cognitive", severity: "warning", element: document.createElement("div"), message: `Account summary shows ${filteredAccounts.length} accounts (max recommended: 7)`, messageEs: `Resumen de cuentas muestra ${filteredAccounts.length} cuentas (m\xE1ximo recomendado: 7)`, messageEn: `Account summary shows ${filteredAccounts.length} accounts (max recommended: 7)`, recommendation: "Consider pagination or grouping for large numbers of accounts", birhausPrinciple: 1 // Cognitive load reduction }); } }, [filteredAccounts.length, reportViolation]); const totalBalance = React4.useMemo(() => { return filteredAccounts.filter((account) => account.estado === "activa").reduce((sum, account) => { const balance = account.moneda === "PYG" ? account.saldo : account.saldo * 7300; return sum + balance; }, 0); }, [filteredAccounts]); const toggleBalanceVisibility = React4.useCallback(() => { setBalancesVisible((prev) => !prev); }, []); const handleAccountClick = React4.useCallback((account) => { onAccountClick?.(account); }, [onAccountClick]); if (filteredAccounts.length === 0) { return /* @__PURE__ */ jsxs("div", { className: cn3("p-6 text-center text-birhaus-muted-foreground", className), children: [ /* @__PURE__ */ jsx(Wallet, { size: 48, className: "mx-auto mb-4 text-birhaus-muted-foreground/50" }), /* @__PURE__ */ jsx("p", { children: language === "es" ? "No hay cuentas disponibles" : "No accounts available" }) ] }); } return /* @__PURE__ */ jsxs("div", { className: cn3("space-y-4", className), ...props, children: [ /* @__PURE__ */ jsxs("div", { className: "bg-gradient-to-r from-birhaus-primary to-birhaus-primary/80 p-6 rounded-lg text-white", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: language === "es" ? "Resumen de Cuentas" : "Account Summary" }), /* @__PURE__ */ jsx( "button", { onClick: toggleBalanceVisibility, className: "p-2 hover:bg-white/20 rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-white/50", "aria-label": balancesVisible ? language === "es" ? "Ocultar saldos" : "Hide balances" : language === "es" ? "Mostrar saldos" : "Show balances", children: balancesVisible ? /* @__PURE__ */ jsx(EyeOff, { size: 20 }) : /* @__PURE__ */ jsx(Eye, { size: 20 }) } ) ] }), /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [ /* @__PURE__ */ jsx("p", { className: "text-white/80 text-sm", children: language === "es" ? "Balance total" : "Total balance" }), /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: balancesVisible ? /* @__PURE__ */ jsx( BirhausCurrencyDisplay, { amount: totalBalance, currency: "PYG", className: "text-white" } ) : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), /* @__PURE__ */ jsxs("p", { className: "text-white/70 text-xs", children: [ filteredAccounts.filter((a) => a.estado === "activa").length, " ", language === "es" ? "cuentas activas" : "active accounts" ] }) ] }) ] }), /* @__PURE__ */ jsx("div", { className: "space-y-3", children: filteredAccounts.map((account) => { const typeConfig = accountTypeConfig[account.tipo]; const statusConfig = accountStatusConfig[account.estado]; const displayName = language === "es" ? account.nombreEs : account.nombreEn || account.nombreEs; const typeName = language === "es" ? typeConfig.nameEs : typeConfig.nameEn; const statusName = language === "es" ? statusConfig.nameEs : statusConfig.nameEn; const isClickable = Boolean(onAccountClick); const isInactive = account.estado !== "activa"; return /* @__PURE__ */ jsx( "div", { className: cn3( "p-4 border border-birhaus-border rounded-lg transition-all duration-200", isClickable && "cursor-pointer hover:border-birhaus-primary/50 hover:shadow-sm", isInactive && "opacity-60", "focus:outline-none focus:ring-2 focus:ring-birhaus-primary focus:border-birhaus-primary" ), onClick: () => isClickable && handleAccountClick(account), onKeyDown: (e) => { if (isClickable && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); handleAccountClick(account); } }, tabIndex: isClickable ? 0 : -1, role: isClickable ? "button" : void 0, "aria-label": `${displayName}, ${typeName}, ${balancesVisible ? formatCurrency(account.saldo, { currency: account.moneda }) : "saldo oculto"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [ /* @__PURE__ */ jsx("div", { className: cn3( "p-2 rounded-full flex-shrink-0", typeConfig.colorClass ), children: typeConfig.icon }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [ /* @__PURE__ */ jsx("h3", { className: "font-medium text-birhaus-foreground truncate", children: displayName }), isInactive && /* @__PURE__ */ jsx("span", { className: cn3( "px-2 py-1 text-xs font-medium rounded-full", statusConfig.colorClass ), children: statusName }) ] }), /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [ /* @__PURE__ */ jsxs("p", { className: "text-sm text-birhaus-muted-foreground", children: [ typeName, " \u2022 ", account.numero ] }), (account.limiteDiario || account.limiteMensual) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-birhaus-muted-foreground", children: [ account.limiteDiario && /* @__PURE__ */ jsxs("span", { children: [ language === "es" ? "L\xEDmite diario: " : "Daily limit: ", formatCurrency(account.limiteDiario, { currency: account.moneda }) ] }), account.limiteDiario && account.limiteMensual && " \u2022 ", account.limiteMensual && /* @__PURE__ */ jsxs("span", { children: [ language === "es" ? "L\xEDmite mensual: " : "Monthly limit: ", formatCurrency(account.limiteMensual, { currency: account.moneda }) ] }) ] }) ] }), /* @__PURE__ */ jsxs("div", { className: "text-right", children: [ /* @__PURE__ */ jsx("p", { className: cn3( "font-semibold", account.saldo >= 0 ? "text-green-600" : "text-red-600" ), children: balancesVisible ? /* @__PURE__ */ jsx( BirhausCurrencyDisplay, { amount: account.saldo, currency: account.moneda } ) : "\u2022\u2022\u2022\u2022\u2022\u2022" }), /* @__PURE__ */ jsxs("p", { className: "text-xs text-birhaus-muted-foreground", children: [ language === "es" ? "\xDAltima actividad: " : "Last activity: ", account.ultimaActividad.toLocaleDateString(language === "es" ? "es-PY" : "en-US") ] }) ] }) ] }) ] }), account.estado === "bloqueada" && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(AlertCircle, { size: 20, className: "text-red-600" }) }) ] }) }, account.id ); }) }), accounts.length > filteredAccounts.length && /* @__PURE__ */ jsx("div", { className: "text-center text-sm text-birhaus-muted-foreground", children: language === "es" ? /* @__PURE__ */ jsxs(Fragment, { children: [ "Mostrando ", filteredAccounts.length, " de ", accounts.length, " cuentas" ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [ "Showing ", filteredAccounts.length, " of ", accounts.length, " accounts" ] }) }) ] }); } var cn4 = (...inputs) => twMerge(clsx(inputs)); var transactionTypeConfig = { ingreso: { icon: /* @__PURE__ */ jsx(ArrowDownLeft, { size: 16 }), colorClass: "text-green-600 bg-green-100", nameEs: "Ingreso", nameEn: "Income" }, gasto: { icon: /* @__PURE__ */ jsx(ArrowUpRight, { size: 16 }), colorClass: "text-red-600 bg-red-100", nameEs: "Gasto", nameEn: "Expense" }, transferencia: { icon: /* @__PURE__ */ jsx(Clock, { size: 16 }), colorClass: "text-blue-600 bg-blue-100", nameEs: "Transferencia", nameEn: "Transfer" }, inversion: { icon: /* @__PURE__ */ jsx(ArrowDownLeft, { size: 16 }), colorClass: "text-green-600 bg-green-100", nameEs: "Inversi\xF3n", nameEn: "Investment" } }; var transactionStatusConfig = { pendiente: { colorClass: "text-yellow-700 bg-yellow-100", nameEs: "Pendiente", nameEn: "Pending" }, completada: { colorClass: "text-green-700 bg-green-100", nameEs: "Completada", nameEn: "Completed" }, fallida: { colorClass: "text-red-700 bg-red-100", nameEs: "Fallida", nameEn: "Failed" }, cancelada: { colorClass: "text-gray-700 bg-gray-100", nameEs: "Cancelada", nameEn: "Cancelled" }, procesando: { colorClass: "text-blue-700 bg-blue-100", nameEs: "Procesando", nameEn: "Processing" } }; function BirhausTransactionList({ transactions, loading = false, pageSize = 7, showFilters = true, showSearch = true, showExport = true, onTransactionClick, onExport, className, ...props }) { const { language, reportViolation } = useBirhaus(); const [searchTerm, setSearchTerm] = React4.useState(""); const [filters, setFilters] = React4.useState({}); const [currentPage, setCurrentPage] = React4.useState(1); const [showFiltersPanel, setShowFiltersPanel] = React4.useState(false); React4.useEffect(() => { if (pageSize > 7) { reportViolation({ type: "cognitive", severity: "warning", element: document.createElement("div"), message: `Transaction list shows ${pageSize} items per page (max recommended: 7)`, messageEs: `Lista de transacciones muestra ${pageSize} elementos por p\xE1gina (m\xE1ximo recomendado: 7)`, messageEn: `Transaction list shows ${pageSize} items per page (max recommended: 7)`, recommendation: "Use pagination to keep cognitive load manageable", birhausPrinciple: 1 // Cognitive load reduction }); } }, [pageSize, reportViolation]); const filteredTransactions = React4.useMemo(() => { let filtered = transactions; if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter( (transaction) => transaction.descripcionEs.toLowerCase().includes(term) || transaction.referencia?.toLowerCase().includes(term) || transaction.cuentaDestino?.toLowerCase().includes(term) || transaction.cuentaOrigen?.toLowerCase().includes(term) ); } if (filters.tipo) { filtered = filtered.filter((t) => filters.tipo.includes(t.tipo)); } if (filters.estado) { filtered = filtered.filter((t) => filters.estado.includes(t.estado)); } if (filters.fechaDesde) { filtered = filtered.filter((t) => new Date(t.fecha) >= new Date(filters.fechaDesde)); } if (filters.fechaHasta) { filtered = filtered.filter((t) => new Date(t.fecha) <= new Date(filters.fechaHasta)); } if (filters.montoMinimo !== void 0) { filtered = filtered.filter((t) => t.monto >= filters.montoMinimo); } if (filters.montoMaximo !== void 0) { filtered = filtered.filter((t) => t.monto <= filters.montoMaximo); } if (filters.moneda) { filtered = filtered.filter((t) => filters.moneda.includes(t.moneda)); } return filtered.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); }, [transactions, searchTerm, filters]); const totalPages = Math.ceil(filteredTransactions.length / pageSize); const paginatedTransactions = React4.useMemo(() => { const start = (currentPage - 1) * pageSize; return filteredTransactions.slice(start, start + pageSize); }, [filteredTransactions, currentPage, pageSize]); const handleSearch = React4.useCallback((value) => { setSearchTerm(value); setCurrentPage(1); }, []); const handleFilterChange = React4.useCallback((newFilters) => { setFilters((prev) => ({ ...prev, ...newFilters })); setCurrentPage(1); }, []); const clearFilters = React4.useCallback(() => { setFilters({}); setSearchTerm(""); setCurrentPage(1); }, []); const handleTransactionClick = React4.useCallback((transaction) => { onTransactionClick?.(transaction); }, [onTransactionClick]); const handleExport = React4.useCallback(() => { onExport?.(filteredTransactions); }, [onExport, filteredTransactions]); const checkSEPRELADCompliance = React4.useCallback((transaction) => { const seprelad = checkSEPRELADThreshold(transaction.monto, transaction.moneda); return seprelad.requiresReport; }, []); if (loading) { return /* @__PURE__ */ jsx("div", { className: cn4("space-y-4", className), ...props, children: Array.from({ length: pageSize }).map((_, index) => /* @__PURE__ */ jsx("div", { className: "p-4 border border-birhaus-border rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [ /* @__PURE__ */ jsx("div", { className: "w-10 h-10 bg-gray-200 rounded-full animate-pulse" }), /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [ /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded animate-pulse w-1/2" }), /* @__PURE__ */ jsx("div", { className: "h-3 bg-gray-200 rounded animate-pulse w-1/3" }) ] }), /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded animate-pulse w-24" }) ] }) }, index)) }); } return /* @__PURE__ */ jsxs("div", { className: cn4("space-y-4", className), ...props, children: [ (showSearch || showFilters || showExport) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 flex-wrap", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 flex-1", children: [ showSearch && /* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-md", children: [ /* @__PURE__ */ jsx(Search, { size: 16, className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-birhaus-muted-foreground" }), /* @__PURE__ */ jsx( "input", { type: "text", value: searchTerm, onChange: (e) => handleSearch(e.target.value), placeholder: language === "es" ? "Buscar transacciones..." : "Search transactions...", className: "w-full pl-10 pr-4 py-2 border border-birhaus-border rounded-lg focus:outline-none focus:ring-2 focus:ring-birhaus-primary focus:border-birhaus-primary" } ) ] }), showFilters && /* @__PURE__ */ jsxs( "button", { onClick: () => setShowFiltersPanel(!showFiltersPanel), className: "flex items-center gap-2 px-4 py-2 border border-birhaus-border rounded-lg hover:bg-birhaus-muted/50 transition-colors", children: [ /* @__PURE__ */ jsx(Filter, { size: 16 }), /* @__PURE__ */ jsx("span", { children: language === "es" ? "Filtros" : "Filters" }), /* @__PURE__ */ jsx(ChevronDown, { size: 16, className: cn4( "transition-transform duration-200", showFiltersPanel && "rotate-180" ) }) ] } ) ] }), showExport && /* @__PURE__ */ jsxs( "button", { onClick: handleExport, className: "flex items-center gap-2 px-4 py-2 bg-birhaus-primary text-white rounded-lg hover:bg-birhaus-primary/90 transition-colors", children: [ /* @__PURE__ */ jsx(Download, { size: 16 }), /* @__PURE__ */ jsx("span", { children: language === "es" ? "Exportar" : "Export" }) ] } ) ] }), showFilters && showFiltersPanel && /* @__PURE__ */ jsxs("div", { className: "p-4 border border-birhaus-border rounded-lg bg-birhaus-muted/10 space-y-4", children: [ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: [ /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-2", children: language === "es" ? "Tipo de Transacci\xF3n" : "Transaction Type" }), /* @__PURE__ */ jsxs( "select", { value: filters.tipo?.join(",") || "", onChange: (e) => handleFilterChange({ tipo: e.target.value ? e.target.value.split(",") : void 0 }), className: "w-full p-2 border border-birhaus-border rounded-lg focus:outline-none focus:ring-2 focus:ring-birhaus-primary", children: [ /* @__PURE__ */ jsx("option", { value: "", children: language === "es" ? "Todos" : "All" }), Object.entries(transactionTypeConfig).map(([key, config]) => /* @__PURE__ */ jsx("option", { value: key, children: language === "es" ? config.nameEs : config.nameEn }, key)) ] } ) ] }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-2", children: language === "es" ? "Estado" : "Status" }), /* @__PURE__ */ jsxs( "select", { value: filters.estado?.join(",") || "", onChange: (e) => handleFilterChange({ estado: e.target.value ? e.target.value.split(",") : void 0 }), className: "w-full p-2 border border-birhaus-border rounded-lg focus:outline-none focus:ring-2 focus:ring-birhaus-primary", children: [ /* @__PURE__ */ jsx("option", { value: "", children: language === "es" ? "Todos" : "All" }), Object.entries(transactionStatusConfig).map(([key, config]) => /* @__PURE__ */ jsx("option", { value: key, children: language === "es" ? config.nameEs : config.nameEn }, key)) ] } ) ] }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-2", children: language === "es" ? "Fecha Desde" : "Date From" }), /* @__PURE__ */ jsx( "input", { type: "date", value: filters.fechaDesde instanceof Date ? filters.fechaDesde.toISOString().split("T")[0] : filters.fechaDesde || "", onChange: (e) => handleFilterChange({ fechaDesde: e.target.value || void 0 }), className: "w-full p-2 border border-birhaus-border rounded-lg focus:outline-none focus:ring-2 focus:ring-birhaus-primary" } ) ] }), /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-2", children: language === "es" ? "Fecha Hasta" : "Date To" }), /* @__PURE__ */ jsx( "input", { type: "date", value: filters.fechaHasta instanceof Date ? filters.fechaHasta.toISOString().split("T")[0] : filters.fechaHasta || "", onChange: (e) => handleFilterChange({ fechaHasta: e.target.value || void 0 }), className: "w-full p-2 border border-birhaus-border rounded-lg focus:outline-none focus:ring-2 focus:ring-birhaus-primary" } ) ] }) ] }), /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx( "button", { onClick: clearFilters, className: "px-4 py-2 text-sm text-birhaus-muted-foreground hover:text-birhaus-foreground transition-colors", children: language === "es" ? "Limpiar Filtros" : "Clear Filters" } ) })