paraguay-localization
Paraguay-specific business patterns including local payment methods, tax regulations, phone formats, address handling, and cultural considerations. Use when building features for the Paraguayan market.
When & Why to Use This Skill
This Claude skill serves as a comprehensive localization guide for the Paraguayan market, providing standardized business patterns, ready-to-use code snippets for local payment integrations, and essential tax compliance logic. It is designed to help developers and businesses seamlessly adapt their software platforms to meet Paraguay's unique regulatory, financial, and cultural requirements, ensuring a localized user experience and legal adherence.
Use Cases
- Integrating local Paraguayan payment gateways such as Bancard vPOS and Tigo Money into e-commerce or service platforms.
- Implementing automated tax calculations (IVA) and generating legally compliant invoice numbers (Timbrado) following SET regulations.
- Validating and formatting regional data including Paraguayan phone numbers (09xx), RUC (Tax ID) check digits, and department-based addresses.
- Localizing financial applications with proper Guaraní (PYG) currency formatting, including specific thousands separators and rounding rules.
- Enhancing customer engagement by incorporating regional Spanish linguistic nuances and Paraguayan cultural considerations into UI/UX and communication.
| name | paraguay-localization |
|---|---|
| description | Paraguay-specific business patterns including local payment methods, tax regulations, phone formats, address handling, and cultural considerations. Use when building features for the Paraguayan market. |
Paraguay Localization Guide
Overview
This skill covers Paraguay-specific business patterns for the Vete veterinary platform, including payment methods, tax regulations, phone number formats, address handling, and cultural/linguistic considerations.
1. Payment Methods
Popular Payment Methods in Paraguay
| Method | Type | Market Share | Integration |
|---|---|---|---|
| Bancard | Cards (debit/credit) | ~40% | vPOS API |
| Tigo Money | Mobile wallet | ~25% | Tigo Money API |
| Personal Pay | Mobile wallet | ~10% | Personal API |
| Billetera Personal | Mobile wallet | ~8% | Personal API |
| PagoExpress | Payment network | ~10% | PagoExpress API |
| Efectivo | Cash | ~7% | N/A |
Bancard vPOS Integration
// lib/payments/bancard.ts
interface BancardConfig {
publicKey: string;
privateKey: string;
environment: 'sandbox' | 'production';
}
const BANCARD_URLS = {
sandbox: 'https://vpos.infonet.com.py:8888',
production: 'https://vpos.infonet.com.py',
};
interface SingleBuyRequest {
public_key: string;
operation: {
token: string;
shop_process_id: number;
amount: string; // "150000.00" (Guaraníes, 2 decimals)
currency: 'PYG';
additional_data: string;
description: string;
return_url: string;
cancel_url: string;
};
}
export async function createBancardPayment(
config: BancardConfig,
data: {
orderId: string;
amount: number; // In Guaraníes
description: string;
returnUrl: string;
cancelUrl: string;
}
) {
const token = generateToken(config.privateKey, data.orderId, data.amount);
const request: SingleBuyRequest = {
public_key: config.publicKey,
operation: {
token,
shop_process_id: parseInt(data.orderId.replace(/\D/g, '').slice(-8)),
amount: data.amount.toFixed(2),
currency: 'PYG',
additional_data: '',
description: data.description,
return_url: data.returnUrl,
cancel_url: data.cancelUrl,
},
};
const response = await fetch(
`${BANCARD_URLS[config.environment]}/vpos/api/0.3/single_buy`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
}
);
const result = await response.json();
if (result.status === 'success') {
return {
processId: result.process_id,
redirectUrl: `${BANCARD_URLS[config.environment]}/vpos/api/0.3/external-commerce/purchase?process_id=${result.process_id}`,
};
}
throw new Error(result.messages?.[0]?.description || 'Bancard error');
}
function generateToken(
privateKey: string,
orderId: string,
amount: number
): string {
const crypto = require('crypto');
const data = `${privateKey}${orderId}${amount.toFixed(2)}PYG`;
return crypto.createHash('md5').update(data).digest('hex');
}
Tigo Money Integration
// lib/payments/tigo-money.ts
interface TigoMoneyConfig {
clientId: string;
clientSecret: string;
merchantAccount: string;
environment: 'sandbox' | 'production';
}
const TIGO_URLS = {
sandbox: 'https://securesandbox.tigo.com.py',
production: 'https://secure.tigo.com.py',
};
export async function createTigoMoneyPayment(
config: TigoMoneyConfig,
data: {
msisdn: string; // Customer phone (09xx format)
amount: number; // In Guaraníes
orderId: string;
description: string;
}
) {
// Get OAuth token
const token = await getTigoToken(config);
const response = await fetch(
`${TIGO_URLS[config.environment]}/v1/tigo/mfs/payments/authorizations`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
MasterMerchant: {
account: config.merchantAccount,
pin: '1234', // Configured PIN
},
Subscriber: {
account: formatTigoPhone(data.msisdn),
},
Transaction: {
amount: data.amount.toString(),
currency: 'PYG',
type: 'PAYMENT',
id: data.orderId,
fee: '0',
},
}),
}
);
return response.json();
}
function formatTigoPhone(phone: string): string {
const digits = phone.replace(/\D/g, '');
if (digits.startsWith('595')) return digits;
if (digits.startsWith('0')) return `595${digits.slice(1)}`;
return `595${digits}`;
}
2. Tax Regulations (IVA & Timbrado)
IVA (Impuesto al Valor Agregado)
// lib/tax/paraguay-tax.ts
export const PARAGUAY_TAX = {
IVA_RATE: 0.10, // 10% standard rate
IVA_REDUCED: 0.05, // 5% reduced rate (some services)
IVA_EXEMPT: 0, // Exempt items
};
// IVA categories for veterinary services
export const VET_SERVICE_TAX_RATES: Record<string, number> = {
consulta_general: 0.10, // Standard IVA
cirugia: 0.10,
vacunacion: 0.10,
peluqueria: 0.10,
medicamentos: 0.10, // Vet medicines are taxed
alimentos_mascotas: 0.10,
accesorios: 0.10,
};
export function calculateIVA(subtotal: number, rate: number = 0.10) {
const ivaAmount = subtotal * rate;
const total = subtotal + ivaAmount;
return {
subtotal,
ivaRate: rate,
ivaAmount: Math.round(ivaAmount), // Round to whole Guaraníes
total: Math.round(total),
};
}
// For invoices showing IVA included (common in Paraguay)
export function extractIVA(totalWithIVA: number, rate: number = 0.10) {
const subtotal = totalWithIVA / (1 + rate);
const ivaAmount = totalWithIVA - subtotal;
return {
subtotal: Math.round(subtotal),
ivaRate: rate,
ivaAmount: Math.round(ivaAmount),
total: totalWithIVA,
};
}
Timbrado (Invoice Authorization)
// lib/invoicing/timbrado.ts
interface Timbrado {
numero: string; // e.g., "15234567"
fechaInicio: Date;
fechaVencimiento: Date;
rangoDesde: string; // "001-001-0000001"
rangoHasta: string; // "001-001-9999999"
ruc: string; // e.g., "80012345-6"
}
interface InvoiceNumber {
establecimiento: string; // 3 digits
puntoExpedicion: string; // 3 digits
numero: string; // 7 digits
full: string; // "001-001-0000001"
}
export function generateInvoiceNumber(
timbrado: Timbrado,
lastNumber: number
): InvoiceNumber {
const nextNumber = lastNumber + 1;
// Parse range to get establecimiento and punto
const [est, punto] = timbrado.rangoDesde.split('-');
const numero = nextNumber.toString().padStart(7, '0');
const full = `${est}-${punto}-${numero}`;
// Validate within range
if (full > timbrado.rangoHasta) {
throw new Error('Timbrado range exhausted - need new timbrado');
}
// Check timbrado validity
if (new Date() > timbrado.fechaVencimiento) {
throw new Error('Timbrado expired - need new timbrado');
}
return {
establecimiento: est,
puntoExpedicion: punto,
numero,
full,
};
}
// Invoice types in Paraguay
export const INVOICE_TYPES = {
FACTURA: 'Factura', // For registered businesses (with RUC)
BOLETA: 'Boleta de Venta', // For end consumers (without RUC)
NOTA_CREDITO: 'Nota de Crédito', // Credit notes
NOTA_DEBITO: 'Nota de Débito', // Debit notes
AUTOFACTURA: 'Autofactura', // Self-billing
};
// RUC validation
export function validateRUC(ruc: string): boolean {
// Format: XXXXXXXX-X (8 digits, dash, 1 check digit)
const pattern = /^\d{8}-\d$/;
if (!pattern.test(ruc)) return false;
const [base, checkDigit] = ruc.split('-');
const calculated = calculateRUCCheckDigit(base);
return calculated === parseInt(checkDigit);
}
function calculateRUCCheckDigit(base: string): number {
const weights = [2, 3, 4, 5, 6, 7, 2, 3];
let sum = 0;
for (let i = 0; i < 8; i++) {
sum += parseInt(base[7 - i]) * weights[i];
}
const remainder = sum % 11;
return remainder < 2 ? 0 : 11 - remainder;
}
Invoice PDF Requirements
// Required fields for Paraguayan invoices
interface ParaguayanInvoice {
// Timbrado info (header)
timbrado: string;
validoDesde: string;
validoHasta: string;
// Business info
razonSocial: string;
nombreFantasia: string;
ruc: string;
direccion: string;
telefono: string;
// Invoice info
tipoComprobante: string;
numeroComprobante: string; // "001-001-0000001"
fecha: string;
condicionVenta: 'Contado' | 'Crédito';
// Customer info
clienteNombre: string;
clienteRuc?: string; // Optional for Boleta
clienteDireccion?: string;
// Items
items: Array<{
cantidad: number;
descripcion: string;
precioUnitario: number;
exenta: number;
iva5: number;
iva10: number;
}>;
// Totals
subtotalExenta: number;
subtotalIva5: number;
subtotalIva10: number;
totalIva5: number;
totalIva10: number;
totalGeneral: number;
// Footer (legal text)
leyenda: string; // Required legal disclaimer
}
3. Phone Number Formats
// lib/utils/phone-paraguay.ts
export const PARAGUAY_PHONE = {
COUNTRY_CODE: '595',
MOBILE_PREFIXES: ['9'],
LANDLINE_PREFIXES: ['21', '31', '32', '33', '41', '42', '43', '44', '45', '46', '47', '48', '71', '72', '73', '81', '82', '83'],
};
// Carrier detection
export const MOBILE_CARRIERS: Record<string, string> = {
'981': 'Tigo',
'982': 'Tigo',
'983': 'Tigo',
'984': 'Tigo',
'985': 'Tigo',
'971': 'Personal',
'972': 'Personal',
'973': 'Personal',
'974': 'Personal',
'975': 'Personal',
'991': 'Claro',
'992': 'Claro',
'993': 'Claro',
'994': 'Claro',
'961': 'VOX',
'962': 'VOX',
};
export function formatPhone(phone: string, format: 'local' | 'international' | 'display' = 'international'): string {
const digits = phone.replace(/\D/g, '');
// Normalize to local format (without country code, with leading 0)
let local: string;
if (digits.startsWith('595')) {
local = '0' + digits.slice(3);
} else if (!digits.startsWith('0')) {
local = '0' + digits;
} else {
local = digits;
}
switch (format) {
case 'local':
return local; // 0981123456
case 'international':
return '+595' + local.slice(1); // +595981123456
case 'display':
// Format: 0981 123 456
if (local.length === 10 && local.startsWith('09')) {
return `${local.slice(0, 4)} ${local.slice(4, 7)} ${local.slice(7)}`;
}
// Landline: 021 123 4567
return `${local.slice(0, 3)} ${local.slice(3, 6)} ${local.slice(6)}`;
}
}
export function detectCarrier(phone: string): string | null {
const formatted = formatPhone(phone, 'local');
const prefix = formatted.slice(1, 4); // e.g., "981"
return MOBILE_CARRIERS[prefix] || null;
}
export function validatePhone(phone: string): { valid: boolean; type: 'mobile' | 'landline' | 'unknown'; carrier?: string } {
const formatted = formatPhone(phone, 'local');
// Mobile: 09XX XXX XXX (10 digits)
if (/^09\d{8}$/.test(formatted)) {
return {
valid: true,
type: 'mobile',
carrier: detectCarrier(phone) || undefined,
};
}
// Landline: 0XX XXX XXXX (10 digits) or 0XXX XXXX (8 digits for some areas)
if (/^0(21|31|32|33|41|42|43|44|45|46|47|48|71|72|73|81|82|83)\d{6,7}$/.test(formatted)) {
return {
valid: true,
type: 'landline',
};
}
return { valid: false, type: 'unknown' };
}
4. Address Formatting
// lib/utils/address-paraguay.ts
export interface ParaguayAddress {
calle: string;
numero?: string;
barrio: string;
ciudad: string;
departamento: string;
codigoPostal?: string;
referencias?: string; // "Frente a la plaza", "Entre X y Y"
}
// Departments of Paraguay
export const DEPARTAMENTOS = [
{ code: 'ASU', name: 'Asunción', capital: 'Asunción' },
{ code: 'CON', name: 'Concepción', capital: 'Concepción' },
{ code: 'SAN', name: 'San Pedro', capital: 'San Pedro del Ycuamandiyú' },
{ code: 'COR', name: 'Cordillera', capital: 'Caacupé' },
{ code: 'GUA', name: 'Guairá', capital: 'Villarrica' },
{ code: 'CAA', name: 'Caaguazú', capital: 'Coronel Oviedo' },
{ code: 'CAZ', name: 'Caazapá', capital: 'Caazapá' },
{ code: 'ITA', name: 'Itapúa', capital: 'Encarnación' },
{ code: 'MIS', name: 'Misiones', capital: 'San Juan Bautista' },
{ code: 'PAR', name: 'Paraguarí', capital: 'Paraguarí' },
{ code: 'APG', name: 'Alto Paraná', capital: 'Ciudad del Este' },
{ code: 'CEN', name: 'Central', capital: 'Areguá' },
{ code: 'NEE', name: 'Ñeembucú', capital: 'Pilar' },
{ code: 'AMA', name: 'Amambay', capital: 'Pedro Juan Caballero' },
{ code: 'CAN', name: 'Canindeyú', capital: 'Salto del Guairá' },
{ code: 'PHA', name: 'Presidente Hayes', capital: 'Villa Hayes' },
{ code: 'BOQ', name: 'Boquerón', capital: 'Filadelfia' },
{ code: 'APS', name: 'Alto Paraguay', capital: 'Fuerte Olimpo' },
];
// Gran Asunción cities (most common)
export const GRAN_ASUNCION = [
'Asunción',
'Fernando de la Mora',
'Lambaré',
'Luque',
'San Lorenzo',
'Mariano Roque Alonso',
'Capiatá',
'Limpio',
'Ñemby',
'Villa Elisa',
'San Antonio',
'Itauguá',
];
export function formatAddress(address: ParaguayAddress): string {
const parts: string[] = [];
if (address.calle) {
parts.push(address.numero ? `${address.calle} ${address.numero}` : address.calle);
}
if (address.barrio) {
parts.push(`Barrio ${address.barrio}`);
}
if (address.referencias) {
parts.push(`(${address.referencias})`);
}
if (address.ciudad) {
parts.push(address.ciudad);
}
if (address.departamento && address.departamento !== address.ciudad) {
parts.push(address.departamento);
}
return parts.join(', ');
}
export function formatAddressShort(address: ParaguayAddress): string {
const street = address.numero ? `${address.calle} ${address.numero}` : address.calle;
return `${street}, ${address.ciudad}`;
}
5. Currency Formatting
// lib/utils/currency-paraguay.ts
export const GUARANI = {
code: 'PYG',
symbol: '₲',
name: 'Guaraní',
decimals: 0, // Guaraníes don't use decimals in practice
};
export function formatGuarani(amount: number, options?: {
showSymbol?: boolean;
showCode?: boolean;
}): string {
const { showSymbol = true, showCode = false } = options || {};
// Round to whole number
const rounded = Math.round(amount);
// Format with thousands separator (Paraguay uses . for thousands)
const formatted = rounded.toLocaleString('es-PY');
if (showCode) {
return `${formatted} PYG`;
}
if (showSymbol) {
return `₲ ${formatted}`;
}
return formatted;
}
// Parse Guaraní input (handles both . and , as thousands separator)
export function parseGuarani(input: string): number {
// Remove currency symbol and spaces
let cleaned = input.replace(/[₲\s]/g, '');
// Remove thousands separators (. or ,)
cleaned = cleaned.replace(/[.,]/g, '');
return parseInt(cleaned, 10) || 0;
}
// Common price points in Guaraníes
export const COMMON_PRICES = {
consultaGeneral: 150000,
vacunaBasica: 80000,
vacunaPremium: 120000,
peluqueriaBasica: 60000,
peluqueriaPremium: 100000,
cirugiaEsterilizacion: 350000,
};
6. Calendar & Holidays
// lib/utils/calendar-paraguay.ts
export const PARAGUAY_TIMEZONE = 'America/Asuncion';
// Official holidays (feriados)
export const HOLIDAYS_2024: Array<{ date: string; name: string; type: 'nacional' | 'religioso' }> = [
{ date: '2024-01-01', name: 'Año Nuevo', type: 'nacional' },
{ date: '2024-03-01', name: 'Día de los Héroes', type: 'nacional' },
{ date: '2024-03-28', name: 'Jueves Santo', type: 'religioso' },
{ date: '2024-03-29', name: 'Viernes Santo', type: 'religioso' },
{ date: '2024-05-01', name: 'Día del Trabajador', type: 'nacional' },
{ date: '2024-05-14', name: 'Día de la Independencia (observado)', type: 'nacional' },
{ date: '2024-05-15', name: 'Día de la Independencia', type: 'nacional' },
{ date: '2024-06-12', name: 'Paz del Chaco', type: 'nacional' },
{ date: '2024-08-15', name: 'Fundación de Asunción', type: 'nacional' },
{ date: '2024-09-29', name: 'Victoria de Boquerón', type: 'nacional' },
{ date: '2024-12-08', name: 'Virgen de Caacupé', type: 'religioso' },
{ date: '2024-12-25', name: 'Navidad', type: 'religioso' },
];
export function isHoliday(date: Date): { isHoliday: boolean; name?: string } {
const dateStr = date.toISOString().slice(0, 10);
const holiday = HOLIDAYS_2024.find(h => h.date === dateStr);
return {
isHoliday: !!holiday,
name: holiday?.name,
};
}
export function isBusinessDay(date: Date): boolean {
const day = date.getDay();
// Saturday (6) = half day, Sunday (0) = closed
if (day === 0) return false;
// Check if holiday
if (isHoliday(date).isHoliday) return false;
return true;
}
// Typical business hours in Paraguay
export const BUSINESS_HOURS = {
weekday: { open: '08:00', close: '18:00', lunchBreak: { start: '12:00', end: '14:00' } },
saturday: { open: '08:00', close: '12:00', lunchBreak: null },
sunday: null,
};
7. Spanish (Paraguay) Considerations
Paraguayan Spanish Phrases
// lib/i18n/paraguay-spanish.ts
export const PARAGUAY_PHRASES = {
// Greetings
hello: '¡Hola!', // Standard
helloInformal: '¡Hola, qué tal!',
goodbye: '¡Hasta luego!',
goodbyeInformal: '¡Chau!', // Very common in Paraguay
// Common expressions
yes: 'Sí',
no: 'No',
please: 'Por favor',
thanks: 'Gracias',
thanksInformal: '¡Gracias, che!', // "che" is very Paraguayan
yourWelcome: 'De nada',
// Customer service
howCanIHelp: '¿En qué puedo ayudarte?',
anythingElse: '¿Algo más?',
haveAGoodDay: '¡Que te vaya bien!',
// Veterinary specific
petOwner: 'dueño/a', // Owner
appointmentConfirmed: 'Tu cita está confirmada',
appointmentReminder: 'Te recordamos tu cita',
vaccineDue: 'Vacuna pendiente',
prescriptionReady: 'Receta lista',
// Payment
total: 'Total',
subtotal: 'Subtotal',
tax: 'IVA',
cash: 'Efectivo',
card: 'Tarjeta',
mobilePayment: 'Pago móvil',
};
// Guaraní influences (common in Paraguay)
export const GUARANI_LOANWORDS = {
che: 'friend/buddy', // Used like "dude" in English
piko: 'right?/isn't it?', // Question particle
nde: 'you', // Informal
mbaé: 'what/thing',
mbaeichapa: 'how are you?',
iporã: 'good/fine',
};
// Date formatting in Paraguayan Spanish
export function formatDateParaguay(date: Date): string {
return date.toLocaleDateString('es-PY', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
// Example: "viernes, 15 de enero de 2024"
Common Veterinary Terms in Spanish
export const VET_TERMS_ES = {
// Animals
dog: 'perro',
cat: 'gato',
puppy: 'cachorro',
kitten: 'gatito',
pet: 'mascota',
// Medical
consultation: 'consulta',
vaccine: 'vacuna',
vaccination: 'vacunación',
surgery: 'cirugía',
prescription: 'receta',
medication: 'medicamento',
treatment: 'tratamiento',
diagnosis: 'diagnóstico',
exam: 'examen',
labResults: 'resultados de laboratorio',
// Conditions
healthy: 'sano/a',
sick: 'enfermo/a',
injured: 'herido/a',
emergency: 'emergencia',
// Actions
schedule: 'agendar',
confirm: 'confirmar',
cancel: 'cancelar',
reschedule: 'reprogramar',
// Status
pending: 'pendiente',
confirmed: 'confirmado',
completed: 'completado',
cancelled: 'cancelado',
};
8. Legal Requirements
Required Business Information on Invoices
export const INVOICE_LEGAL_REQUIREMENTS = {
// Must appear on all invoices
required: [
'Razón Social',
'RUC',
'Timbrado (número y validez)',
'Número de comprobante',
'Fecha de emisión',
'Condición de venta (Contado/Crédito)',
'Descripción de bienes/servicios',
'Valor de venta',
'IVA discriminado',
],
// Legal disclaimer (required on all invoices)
leyendaObligatoria: 'Emisión autorizada por la SET. Este documento no tiene valor comercial ni fiscal si presenta tachaduras o enmiendas.',
};
// Data protection (based on Paraguay Law 1682/01)
export const DATA_PROTECTION = {
consentRequired: true,
retentionPeriod: '5 years', // Tax records
customerRights: [
'Acceso a sus datos',
'Rectificación de datos incorrectos',
'Eliminación de datos (salvo obligaciones legales)',
'Oposición al uso de datos para marketing',
],
};
Reference: SET (Subsecretaría de Estado de Tributación), Bancard documentation, local carrier APIs