-
+
-
+
-
+
diff --git a/src/app/sales/import/page.tsx b/src/app/sales/import/page.tsx
index f307571..85ac054 100644
--- a/src/app/sales/import/page.tsx
+++ b/src/app/sales/import/page.tsx
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import Header from '@/components/Header';
import styles from './page.module.css';
import { useUser } from '@/lib/auth/UserContext';
+import { useLocale } from '@/lib/i18n/LocaleContext';
interface ValidationError {
row: number;
@@ -14,39 +15,9 @@ interface ValidationError {
metadata: any;
}
-const TRANSLATIONS: Record
= {
- USER_NOT_FOUND: "El colaborador '{username}' no existe en el sistema.",
- INVALID_PERIOD_FORMAT: "El período '{value}' no tiene formato válido (esperado: {expected}).",
- HOTEL_NOT_FOUND: "El hotel '{hotelCode}' no existe en el sistema.",
- HOTEL_REGION_MISMATCH: "El hotel '{hotelCode}' no pertenece a su región autorizada.",
- INVALID_AMOUNT: "El monto '{value}' no es válido. Debe ser un número positivo.",
- INVALID_COUNT: "La cantidad '{value}' no es válida. Debe ser un número entero positivo.",
- USER_REQUIRED: "El colaborador es obligatorio.",
- HOTEL_REQUIRED: "El hotel es obligatorio.",
- IMPORT_VALIDATION_FAILED: "El archivo cargado contiene inconsistencias de validación.",
- MISSING_IDEMPOTENCY_KEY: "El encabezado de idempotencia es obligatorio.",
- FILE_REQUIRED: "Debe seleccionar un archivo válido.",
- EMPTY_FILE: "El archivo está vacío y no contiene registros.",
- INTEGRATION_ERROR: "El motor de integraciones (n8n) reportó un fallo al procesar la solicitud.",
- IMPORT_ALREADY_PROCESSED: "Este archivo ya fue cargado y procesado con éxito anteriormente.",
- IMPORT_SUCCESSFUL: "Importación completada con éxito. Se cargaron {count} registros.",
- IMPORT_ACCEPTED: "La importación ha sido aceptada y se está procesando mediante n8n en segundo plano."
-};
-
-const translate = (code: string, metadata: any = {}, value?: any) => {
- let template = TRANSLATIONS[code] || code;
- const merged = { ...metadata };
- if (value !== undefined) {
- merged.value = value;
- }
- for (const key of Object.keys(merged)) {
- template = template.replace(`{${key}}`, String(merged[key]));
- }
- return template;
-};
-
export default function SalesImportPage() {
const router = useRouter();
+ const { t } = useLocale();
const [file, setFile] = useState(null);
const [dragActive, setDragActive] = useState(false);
const [isUploading, setIsUploading] = useState(false);
@@ -58,6 +29,21 @@ export default function SalesImportPage() {
const [statusMessage, setStatusMessage] = useState('');
const { role, user: currentUser, isLoading: contextLoading } = useUser();
+ const translate = (code: string, metadata: any = {}, value?: any) => {
+ let template = t('import.errors.' + code);
+ if (template === 'import.errors.' + code) {
+ template = code; // Fallback
+ }
+ const merged = { ...metadata };
+ if (value !== undefined) {
+ merged.value = value;
+ }
+ for (const key of Object.keys(merged)) {
+ template = template.replace(`{${key}}`, String(merged[key]));
+ }
+ return template;
+ };
+
useEffect(() => {
if (!contextLoading) {
if (!currentUser) {
@@ -73,7 +59,6 @@ export default function SalesImportPage() {
}, [contextLoading, currentUser, role, router]);
useEffect(() => {
- // Generate unique idempotency key for this session/upload instance
const key = 'key-' + Date.now() + '-' + Math.random().toString(36).substring(2, 9);
setIdempotencyKey(key);
}, []);
@@ -84,7 +69,7 @@ export default function SalesImportPage() {
-
Cargando módulo de importación...
+
{t('import.loading')}
@@ -113,7 +98,7 @@ export default function SalesImportPage() {
setValidationErrors([]);
setSuccessData(null);
} else {
- setGeneralError("Tipo de archivo no soportado. Cargue archivos .xlsx, .xls o .csv");
+ setGeneralError(t('import.unsupportedFile'));
}
}
};
@@ -129,7 +114,7 @@ export default function SalesImportPage() {
};
const startPolling = (key: string) => {
- setStatusMessage("Procesando integración asíncrona mediante n8n...");
+ setStatusMessage(t('import.statusPolling'));
const interval = setInterval(async () => {
try {
const res = await fetch(`/api/sales/import/status/${key}`);
@@ -154,12 +139,11 @@ export default function SalesImportPage() {
}
}, 2000);
- // Safety timeout: stop polling after 30 seconds
setTimeout(() => {
clearInterval(interval);
if (isUploading) {
setIsUploading(false);
- setGeneralError("El procesamiento por n8n está tomando más tiempo de lo esperado. Revise el historial más tarde.");
+ setGeneralError(t('import.timeout'));
}
}, 30000);
};
@@ -190,22 +174,18 @@ export default function SalesImportPage() {
const data = await res.json();
if (res.status === 201) {
- // Direct successful import
setProgress(100);
setIsUploading(false);
setSuccessData(data.metadata || { count: 0 });
} else if (res.status === 202) {
- // Accepted (asynchronous processing via n8n)
setProgress(90);
startPolling(idempotencyKey);
} else if (res.status === 200 && data.code === 'IMPORT_ALREADY_PROCESSED') {
- // Idempotency cached response
setProgress(100);
setIsUploading(false);
setSuccessData(data.metadata);
setGeneralError(translate(data.code, data.metadata));
} else {
- // Error occurred
setIsUploading(false);
setProgress(0);
if (data.error?.code === 'IMPORT_VALIDATION_FAILED') {
@@ -219,7 +199,7 @@ export default function SalesImportPage() {
} catch (err: any) {
setIsUploading(false);
setProgress(0);
- setGeneralError("Fallo de red o error inesperado al subir el archivo.");
+ setGeneralError(t('import.networkError'));
}
};
@@ -229,23 +209,22 @@ export default function SalesImportPage() {
-
Cargar Ventas Comerciales
-
Importe el archivo de resultados para procesar las comisiones del periodo.
+
{t('import.title')}
+
{t('import.subtitle')}
- {/* Drag & Drop Area */}
) : (
-
Arrastre y suelte su archivo aquí, o explore archivos
-
Formatos permitidos: .xlsx, .xls, .csv
+
{t('import.dragText')} {t('import.browseText')}
+
{t('import.supportedText')}
)}
- {/* Progress Bar */}
{isUploading && (
-
Subiendo y verificando archivo... {progress}%
+
{t('import.uploading')} {progress}%
{statusMessage &&
{statusMessage}
}
)}
- {/* Action buttons */}
{file && !isUploading && (
)}
- {/* Successful Import Alert */}
{successData && (
✓
-
Carga exitosa:
-
Se importaron exitosamente {successData.count} registros de venta.
- {successData.totalAmount !== undefined &&
Monto consolidado: ${successData.totalAmount.toLocaleString()}
}
+
{t('import.success')}
+
{t('import.successDesc').replace('{count}', String(successData.count))}
+ {successData.totalAmount !== undefined &&
{t('import.consolidated')} ${successData.totalAmount.toLocaleString()}
}
)}
- {/* General Error Alert */}
{generalError && (
⚠️
-
Inconsistencia detectada:
+
{t('import.inconsistencyAlert')}
{generalError}
)}
- {/* Row-Level Inconsistency Panel */}
{validationErrors.length > 0 && (
-
Detalle de Inconsistencias en las Filas
+
{t('import.inconsistencyTitle')}
- | Fila |
- Columna |
- Valor Leído |
- Detalle del Error |
+ {t('import.thRow')} |
+ {t('import.thColumn')} |
+ {t('import.thValue')} |
+ {t('import.thError')} |
{validationErrors.map((err, idx) => (
- | Fila {err.row} |
+ {t('import.thRow')} {err.row} |
{err.column} |
{String(err.value || '')} |
{translate(err.code, err.metadata, err.value)} |
diff --git a/src/app/sales/simulation/page.tsx b/src/app/sales/simulation/page.tsx
index b5ec957..c441048 100644
--- a/src/app/sales/simulation/page.tsx
+++ b/src/app/sales/simulation/page.tsx
@@ -142,13 +142,13 @@ export default function SimulationPage() {
});
if (simulateOnly) {
- setSuccess('Simulación completada con éxito. Los resultados no se han guardado.');
+ setSuccess(t('simulation.successSimulate'));
} else {
- setSuccess('Liquidaciones calculadas y guardadas correctamente.');
+ setSuccess(t('simulation.successCalculate'));
}
} catch (err: any) {
console.error('Calculation fetch error:', err);
- setError('Error al comunicarse con el servidor.');
+ setError(t('simulation.serverError'));
} finally {
setIsProcessing(false);
}
@@ -159,7 +159,7 @@ export default function SimulationPage() {
-
Cargando panel de simulación...
+
{t('simulation.loading')}
);
@@ -172,9 +172,9 @@ export default function SimulationPage() {
-
Acceso Restringido
+
{t('simulation.restrictedTitle')}
- Lo sentimos, esta sección es exclusiva para el Administrador y el Analista de Compensaciones.
+ {t('simulation.restrictedDesc')}
diff --git a/src/app/settlements/approvals/page.tsx b/src/app/settlements/approvals/page.tsx
index 600bbd5..b379ff3 100644
--- a/src/app/settlements/approvals/page.tsx
+++ b/src/app/settlements/approvals/page.tsx
@@ -106,7 +106,7 @@ export default function ApprovalsPage() {
fetchSettlements();
} catch (err) {
console.error('Failed to approve settlement:', err);
- setError('Error al comunicarse con el servidor.');
+ setError(t('approvals.errorServer'));
}
};
@@ -141,16 +141,16 @@ export default function ApprovalsPage() {
});
const data = await res.json();
if (!res.ok) {
- setError(data.error || 'Error al rechazar la liquidación.');
+ setError(data.error || t('approvals.errorReject'));
setIsSubmittingReject(false);
return;
}
- setSuccess('Liquidación rechazada.');
+ setSuccess(t('approvals.successReject'));
closeRejectModal();
fetchSettlements();
} catch (err) {
console.error('Failed to reject settlement:', err);
- setError('Error al comunicarse con el servidor.');
+ setError(t('approvals.errorServer'));
} finally {
setIsSubmittingReject(false);
}
@@ -161,7 +161,7 @@ export default function ApprovalsPage() {
-
Cargando panel de aprobaciones...
+
{t('approvals.loading')}
);
@@ -174,11 +174,11 @@ export default function ApprovalsPage() {
-
Panel de Aprobaciones
+
{t('approvals.title')}
{isLeader
- ? 'Revise y apruebe las liquidaciones de comisión de los colaboradores de su región.'
- : 'Visualice y audite el estado de aprobación de las liquidaciones de comisiones.'}
+ ? t('approvals.leaderDesc')
+ : t('approvals.globalDesc')}
@@ -205,7 +205,7 @@ export default function ApprovalsPage() {
{settlements.length === 0 ? (
|
- No se encontraron liquidaciones pendientes en su región.
+ {t('approvals.empty')}
|
) : (
@@ -265,14 +265,14 @@ export default function ApprovalsPage() {
className={styles.btnApprove}
onClick={() => handleApprove(s.id)}
>
- Aprobar
+ {t('approvals.btnApprove')}
) : (
@@ -292,7 +292,7 @@ export default function ApprovalsPage() {
{showRejectModal && (
-
Rechazar Liquidación
+ {t('approvals.btnReject')} Liquidación
diff --git a/src/lib/i18n/dictionaries/en.json b/src/lib/i18n/dictionaries/en.json
index d2af7b5..d375d0b 100644
--- a/src/lib/i18n/dictionaries/en.json
+++ b/src/lib/i18n/dictionaries/en.json
@@ -31,7 +31,13 @@
"thAdjustment": "Retroactive Adjustments",
"thPayout": "Total Payout",
"thStatus": "Status",
- "thAiAudit": "AI Audit"
+ "thAiAudit": "AI Audit",
+ "restrictedTitle": "Restricted Access",
+ "restrictedDesc": "Sorry, this section is exclusive to the Administrator and the Compensation Analyst.",
+ "successSimulate": "Simulation completed successfully. Results have not been saved.",
+ "successCalculate": "Settlements calculated and saved successfully.",
+ "serverError": "Error communicating with the server.",
+ "loading": "Loading simulation panel..."
},
"status": {
"ACTIVE": "Active",
@@ -63,7 +69,8 @@
"trends": "Monthly Commission Trends",
"hotels_comparison": "Hotel Performance Comparison",
"trends_desc": "Commission Paid vs Budget Cap",
- "hotels_desc": "Sales and achievements across hotels"
+ "hotels_desc": "Sales and achievements across hotels",
+ "loading": "Loading dashboard..."
},
"history": {
"title": "My Commission History",
@@ -78,7 +85,11 @@
"empty": "No history records found.",
"filters": "Filters",
"all_statuses": "All Statuses",
- "download_pdf": "Download PDF"
+ "download_pdf": "Download PDF",
+ "loading": "Loading history...",
+ "showNotes": "Show Notes",
+ "hideNotes": "Hide",
+ "allCollaborators": "All"
},
"audit": {
"title": "System Audit Logs",
@@ -92,6 +103,176 @@
"diff_panel": "Side-by-Side JSON Diff Details",
"previous": "Previous Value",
"new": "New Value",
- "no_diff": "No modification details available."
+ "no_diff": "No modification details available.",
+ "loading": "Loading logs..."
+ },
+ "login": {
+ "title": "Log In",
+ "subtitle": "Hoteles Estelar",
+ "username": "Username",
+ "password": "Password",
+ "usernamePlaceholder": "Enter your username",
+ "passwordPlaceholder": "Enter your password",
+ "submit": "Log In",
+ "footer": "Variable Remuneration & Commissions System",
+ "loading": "Loading form...",
+ "error": "Login failed. Please check your credentials.",
+ "unexpectedError": "An unexpected error occurred. Please try again."
+ },
+ "plans": {
+ "title": "Commission Plans",
+ "newPlan": "New Plan",
+ "loading": "Loading plans...",
+ "version": "Version",
+ "startValidity": "Start Validity",
+ "endValidity": "End Validity",
+ "rulesCreated": "Rules created",
+ "configureRules": "Configure Rules",
+ "viewRules": "View Rules",
+ "deactivateVersion": "Deactivate (Version)",
+ "activate": "Activate",
+ "modalTitle": "Create New Plan",
+ "modalName": "Plan Name",
+ "modalCode": "Plan Code",
+ "modalValidityStart": "Start Validity",
+ "modalType": "Remuneration Type",
+ "modalMetaAmount": "Sales Goal (Optional)",
+ "modalMaxCap": "Max Cap (Optional)",
+ "modalStatus": "Initial Status",
+ "modalCancel": "Cancel",
+ "modalSave": "Save Plan",
+ "types": {
+ "PERCENTAGE": "Simple Percentage",
+ "SCALE": "Contiguous Scale",
+ "CONDITIONAL": "Conditional by Goals",
+ "FIXED": "Fixed Commission"
+ },
+ "errorFind": "Could not find the plan.",
+ "errorLoad": "Error loading plan information."
+ },
+ "rules": {
+ "back": "Back to Plans",
+ "title": "Configure Commission Rules",
+ "subtitle": "Plan: {name} | Code: {code} | Version: {version} | Status: {status}",
+ "thType": "Rule Type",
+ "thMin": "Min Achievement (%)",
+ "thMax": "Max Achievement (%)",
+ "thRate": "Commission Rate (Decimal)",
+ "thPayout": "Fixed Payout ($)",
+ "addRule": "+ Add Bracket / Rule",
+ "cancel": "Cancel",
+ "backBtn": "Back",
+ "save": "Save Rules",
+ "loading": "Loading configuration...",
+ "delete": "Delete rule",
+ "types": {
+ "TIER": "Bracket (TIER)",
+ "BONUS": "Fixed Bonus (BONUS)"
+ },
+ "errorAtLeastOne": "The plan must have at least one rule.",
+ "errorBoundary": "Row {row}: Min achievement ({min}) must be less than max achievement ({max}).",
+ "errorNegative": "Row {row}: All values must be greater than or equal to zero.",
+ "errorSave": "Error saving rules.",
+ "errorNetwork": "A network error occurred while saving rules.",
+ "successSave": "Rules configured and saved successfully."
+ },
+ "goals": {
+ "title": "Commercial Goals",
+ "assignTitle": "Assign Goal",
+ "labelType": "Goal Type",
+ "labelSelect": "Select Collaborator",
+ "labelPeriod": "Period (YYYY-MM)",
+ "labelAmount": "Goal Amount ($)",
+ "btnSave": "Save Goal",
+ "thTarget": "Target",
+ "thType": "Type",
+ "thPeriod": "Period",
+ "thAmount": "Amount",
+ "thActions": "Actions",
+ "empty": "No assigned goals found.",
+ "loading": "Loading goals...",
+ "successSave": "Commercial goal assigned / updated successfully.",
+ "errorSave": "An error occurred while saving the goal.",
+ "errorInput": "Please enter valid values.",
+ "roles": {
+ "collaborator": "Collaborator",
+ "commercial_leader": "Commercial Leader",
+ "hotel_manager": "Manager",
+ "director": "Director",
+ "analyst": "Analyst",
+ "admin": "Administrator"
+ }
+ },
+ "import": {
+ "title": "Upload Commercial Sales",
+ "subtitle": "Import the results file to process period commissions.",
+ "templateLabel": "Use the official template to avoid inconsistencies:",
+ "downloadTemplate": "Download Template (.xlsx)",
+ "loading": "Loading import module...",
+ "unsupportedFile": "Unsupported file type. Please upload .xlsx, .xls or .csv files",
+ "uploading": "Uploading and verifying file...",
+ "process": "Process File",
+ "clear": "Clear",
+ "success": "Upload successful:",
+ "successDesc": "Successfully imported {count} sales records.",
+ "consolidated": "Consolidated amount:",
+ "inconsistencyTitle": "Inconsistency Row Details",
+ "thRow": "Row",
+ "thColumn": "Column",
+ "thValue": "Read Value",
+ "thError": "Error Detail",
+ "statusPolling": "Processing async integration via n8n...",
+ "timeout": "n8n processing is taking longer than expected. Please check history later.",
+ "networkError": "Network failure or unexpected error while uploading file.",
+ "dragText": "Drag and drop your file here, or",
+ "browseText": "browse files",
+ "supportedText": "Supported formats: .xlsx, .xls, .csv",
+ "inconsistencyAlert": "Inconsistency detected:",
+ "errors": {
+ "USER_NOT_FOUND": "Collaborator '{username}' does not exist in the system.",
+ "INVALID_PERIOD_FORMAT": "Period '{value}' has an invalid format (expected: {expected}).",
+ "HOTEL_NOT_FOUND": "Hotel '{hotelCode}' does not exist in the system.",
+ "HOTEL_REGION_MISMATCH": "Hotel '{hotelCode}' does not belong to your authorized region.",
+ "INVALID_AMOUNT": "Amount '{value}' is invalid. It must be a positive number.",
+ "INVALID_COUNT": "Quantity '{value}' is invalid. It must be a positive integer.",
+ "USER_REQUIRED": "Collaborator is required.",
+ "HOTEL_REQUIRED": "Hotel is required.",
+ "IMPORT_VALIDATION_FAILED": "The uploaded file contains validation inconsistencies.",
+ "MISSING_IDEMPOTENCY_KEY": "Idempotency key header is required.",
+ "FILE_REQUIRED": "Must select a valid file.",
+ "EMPTY_FILE": "The file is empty and contains no records.",
+ "INTEGRATION_ERROR": "The integration engine (n8n) reported a failure while processing the request.",
+ "IMPORT_ALREADY_PROCESSED": "This file has already been successfully uploaded and processed before.",
+ "IMPORT_SUCCESSFUL": "Import completed successfully. Loaded {count} records.",
+ "IMPORT_ACCEPTED": "The import has been accepted and is being processed by n8n in the background."
+ }
+ },
+ "approvals": {
+ "title": "Approvals Panel",
+ "loading": "Loading approvals panel...",
+ "leaderDesc": "Review and approve commission settlements for collaborators in your region.",
+ "globalDesc": "View and audit the approval status of commission settlements.",
+ "empty": "No pending settlements found in your region.",
+ "thCollaborator": "Collaborator",
+ "thPlan": "Plan",
+ "thPeriod": "Period",
+ "thGoal": "Goal",
+ "thSales": "Sales",
+ "thAchievement": "Achievement",
+ "thPayout": "Total Payout",
+ "thStatus": "Status",
+ "thActions": "Actions",
+ "btnApprove": "Approve",
+ "btnReject": "Reject",
+ "rejectModalTitle": "Reject Commission Settlement",
+ "rejectReasonLabel": "Reason for Rejection",
+ "rejectReasonPlaceholder": "e.g., Missing physical sales proof validation...",
+ "rejectCancel": "Cancel",
+ "rejectConfirm": "Confirm Rejection",
+ "successApprove": "Settlement approved successfully.",
+ "successReject": "Settlement rejected.",
+ "errorApprove": "Error approving settlement.",
+ "errorReject": "Error rejecting settlement.",
+ "errorServer": "Error communicating with the server."
}
-}
+}
\ No newline at end of file
diff --git a/src/lib/i18n/dictionaries/es.json b/src/lib/i18n/dictionaries/es.json
index e1c221a..54fc152 100644
--- a/src/lib/i18n/dictionaries/es.json
+++ b/src/lib/i18n/dictionaries/es.json
@@ -31,7 +31,13 @@
"thAdjustment": "Ajustes Retroactivos",
"thPayout": "Pago Total",
"thStatus": "Estado",
- "thAiAudit": "Auditoría AI"
+ "thAiAudit": "Auditoría IA",
+ "restrictedTitle": "Acceso Restringido",
+ "restrictedDesc": "Lo sentimos, esta sección es exclusiva para el Administrador y el Analista de Compensaciones.",
+ "successSimulate": "Simulación completada con éxito. Los resultados no se han guardado.",
+ "successCalculate": "Liquidaciones calculadas y guardadas correctamente.",
+ "serverError": "Error al comunicarse con el servidor.",
+ "loading": "Cargando panel de simulación..."
},
"status": {
"ACTIVE": "Activo",
@@ -63,7 +69,8 @@
"trends": "Tendencias de Comisiones Mensuales",
"hotels_comparison": "Comparación de Rendimiento de Hoteles",
"trends_desc": "Comisión Pagada vs Límite de Presupuesto",
- "hotels_desc": "Ventas y logros por hotel"
+ "hotels_desc": "Ventas y logros por hotel",
+ "loading": "Cargando panel de control..."
},
"history": {
"title": "Mi Historial de Comisiones",
@@ -78,7 +85,11 @@
"empty": "No se encontraron registros de historial.",
"filters": "Filtros",
"all_statuses": "Todos los Estados",
- "download_pdf": "Descargar PDF"
+ "download_pdf": "Descargar PDF",
+ "loading": "Cargando historial...",
+ "showNotes": "Ver Notas",
+ "hideNotes": "Ocultar",
+ "allCollaborators": "Todos"
},
"audit": {
"title": "Registros de Auditoría del Sistema",
@@ -92,6 +103,176 @@
"diff_panel": "Detalles del JSON Diff Lado a Lado",
"previous": "Valor Anterior",
"new": "Valor Nuevo",
- "no_diff": "No hay detalles de modificación disponibles."
+ "no_diff": "No hay detalles de modificación disponibles.",
+ "loading": "Cargando registros..."
+ },
+ "login": {
+ "title": "Iniciar Sesión",
+ "subtitle": "Hoteles Estelar",
+ "username": "Usuario",
+ "password": "Contraseña",
+ "usernamePlaceholder": "Ingrese su usuario",
+ "passwordPlaceholder": "Ingrese su contraseña",
+ "submit": "Ingresar",
+ "footer": "Sistema de Remuneración Variable y Comisiones",
+ "loading": "Cargando formulario...",
+ "error": "Error al iniciar sesión. Verifique sus credenciales.",
+ "unexpectedError": "Ocurrió un error inesperado. Por favor, intente nuevamente."
+ },
+ "plans": {
+ "title": "Planes de Comisión",
+ "newPlan": "Nuevo Plan",
+ "loading": "Cargando planes...",
+ "version": "Versión",
+ "startValidity": "Inicio Vigencia",
+ "endValidity": "Fin Vigencia",
+ "rulesCreated": "Reglas creadas",
+ "configureRules": "Configurar Reglas",
+ "viewRules": "Ver Reglas",
+ "deactivateVersion": "Inactivar (Versión)",
+ "activate": "Activar",
+ "modalTitle": "Crear Nuevo Plan",
+ "modalName": "Nombre del Plan",
+ "modalCode": "Código del Plan",
+ "modalValidityStart": "Inicio de Vigencia",
+ "modalType": "Tipo de Remuneración",
+ "modalMetaAmount": "Meta de Ventas (Opcional)",
+ "modalMaxCap": "Tope Máximo / Cap (Opcional)",
+ "modalStatus": "Estado Inicial",
+ "modalCancel": "Cancelar",
+ "modalSave": "Guardar Plan",
+ "types": {
+ "PERCENTAGE": "Porcentaje Simple",
+ "SCALE": "Escala Contigua",
+ "CONDITIONAL": "Condicional por Metas",
+ "FIXED": "Comisión Fija"
+ },
+ "errorFind": "No se pudo encontrar el plan.",
+ "errorLoad": "Error al cargar la información del plan."
+ },
+ "rules": {
+ "back": "Volver a Planes",
+ "title": "Configurar Reglas de Comisión",
+ "subtitle": "Plan: {name} | Código: {code} | Versión: {version} | Estado: {status}",
+ "thType": "Tipo de Regla",
+ "thMin": "Min Logro (%)",
+ "thMax": "Max Logro (%)",
+ "thRate": "Tasa Comisión (Decimal)",
+ "thPayout": "Payout Fijo ($)",
+ "addRule": "+ Agregar Rango / Regla",
+ "cancel": "Cancelar",
+ "backBtn": "Volver",
+ "save": "Guardar Reglas",
+ "loading": "Cargando configuración...",
+ "delete": "Eliminar regla",
+ "types": {
+ "TIER": "Rango (TIER)",
+ "BONUS": "Bono Fijo (BONUS)"
+ },
+ "errorAtLeastOne": "El plan debe tener al menos una regla.",
+ "errorBoundary": "Fila {row}: El logro mínimo ({min}) debe ser menor que el logro máximo ({max}).",
+ "errorNegative": "Fila {row}: Todos los valores deben ser mayores o iguales a cero.",
+ "errorSave": "Error al guardar las reglas.",
+ "errorNetwork": "Ocurrió un error de red al guardar las reglas.",
+ "successSave": "Reglas configuradas y guardadas exitosamente."
+ },
+ "goals": {
+ "title": "Metas Comerciales",
+ "assignTitle": "Asignar Meta",
+ "labelType": "Tipo de Meta",
+ "labelSelect": "Seleccionar Colaborador",
+ "labelPeriod": "Período (YYYY-MM)",
+ "labelAmount": "Monto de Meta ($)",
+ "btnSave": "Guardar Meta",
+ "thTarget": "Destino",
+ "thType": "Tipo",
+ "thPeriod": "Período",
+ "thAmount": "Monto",
+ "thActions": "Acciones",
+ "empty": "No se encontraron metas asignadas.",
+ "loading": "Cargando metas...",
+ "successSave": "Meta comercial asignada / actualizada exitosamente.",
+ "errorSave": "Ocurrió un error al guardar la meta.",
+ "errorInput": "Por favor, ingrese valores válidos.",
+ "roles": {
+ "collaborator": "Colaborador",
+ "commercial_leader": "Líder Comercial",
+ "hotel_manager": "Gerente",
+ "director": "Director",
+ "analyst": "Analista",
+ "admin": "Administrador"
+ }
+ },
+ "import": {
+ "title": "Cargar Ventas Comerciales",
+ "subtitle": "Importe el archivo de resultados para procesar las comisiones del periodo.",
+ "templateLabel": "Utilice la plantilla oficial para evitar inconsistencias:",
+ "downloadTemplate": "Descargar Plantilla (.xlsx)",
+ "loading": "Cargando módulo de importación...",
+ "unsupportedFile": "Tipo de archivo no soportado. Cargue archivos .xlsx, .xls o .csv",
+ "uploading": "Subiendo y verificando archivo...",
+ "process": "Procesar Archivo",
+ "clear": "Limpiar",
+ "success": "Carga exitosa:",
+ "successDesc": "Se importaron exitosamente {count} registros de venta.",
+ "consolidated": "Monto consolidado:",
+ "inconsistencyTitle": "Detalle de Inconsistencias en las Filas",
+ "thRow": "Fila",
+ "thColumn": "Columna",
+ "thValue": "Valor Leído",
+ "thError": "Detalle del Error",
+ "statusPolling": "Procesando integración asíncrona mediante n8n...",
+ "timeout": "El procesamiento por n8n está tomando más tiempo de lo esperado. Revise el historial más tarde.",
+ "networkError": "Fallo de red o error inesperado al subir el archivo.",
+ "dragText": "Arrastre y suelte su archivo aquí, o",
+ "browseText": "explore archivos",
+ "supportedText": "Formatos permitidos: .xlsx, .xls, .csv",
+ "inconsistencyAlert": "Inconsistencia detectada:",
+ "errors": {
+ "USER_NOT_FOUND": "El colaborador '{username}' no existe en el sistema.",
+ "INVALID_PERIOD_FORMAT": "El período '{value}' no tiene formato válido (esperado: {expected}).",
+ "HOTEL_NOT_FOUND": "El hotel '{hotelCode}' no existe en el sistema.",
+ "HOTEL_REGION_MISMATCH": "El hotel '{hotelCode}' no pertenece a su región autorizada.",
+ "INVALID_AMOUNT": "El monto '{value}' no es válido. Debe ser un número positivo.",
+ "INVALID_COUNT": "La cantidad '{value}' no es válida. Debe ser un número entero positivo.",
+ "USER_REQUIRED": "El colaborador es obligatorio.",
+ "HOTEL_REQUIRED": "El hotel es obligatorio.",
+ "IMPORT_VALIDATION_FAILED": "El archivo cargado contiene inconsistencias de validación.",
+ "MISSING_IDEMPOTENCY_KEY": "El encabezado de idempotencia es obligatorio.",
+ "FILE_REQUIRED": "Debe seleccionar un archivo válido.",
+ "EMPTY_FILE": "El archivo está vacío y no contiene registros.",
+ "INTEGRATION_ERROR": "El motor de integraciones (n8n) reportó un fallo al procesar la solicitud.",
+ "IMPORT_ALREADY_PROCESSED": "Este archivo ya fue cargado y procesado con éxito anteriormente.",
+ "IMPORT_SUCCESSFUL": "Importación completada con éxito. Se cargaron {count} registros.",
+ "IMPORT_ACCEPTED": "La importación ha sido aceptada y se está procesando mediante n8n en segundo plano."
+ }
+ },
+ "approvals": {
+ "title": "Panel de Aprobaciones",
+ "loading": "Cargando panel de aprobaciones...",
+ "leaderDesc": "Revise y apruebe las liquidaciones de comisión de los colaboradores de su región.",
+ "globalDesc": "Visualice y audite el estado de aprobación de las liquidaciones de comisiones.",
+ "empty": "No se encontraron liquidaciones pendientes en su región.",
+ "thCollaborator": "Colaborador",
+ "thPlan": "Plan",
+ "thPeriod": "Período",
+ "thGoal": "Meta",
+ "thSales": "Ventas",
+ "thAchievement": "Cumplimiento",
+ "thPayout": "Pago Total",
+ "thStatus": "Estado",
+ "thActions": "Acciones",
+ "btnApprove": "Aprobar",
+ "btnReject": "Rechazar",
+ "rejectModalTitle": "Rechazar Liquidación de Comisión",
+ "rejectReasonLabel": "Motivo del Rechazo",
+ "rejectReasonPlaceholder": "Ej. Falta validar soporte físico de ventas...",
+ "rejectCancel": "Cancelar",
+ "rejectConfirm": "Confirmar Rechazo",
+ "successApprove": "Liquidación aprobada con éxito.",
+ "successReject": "Liquidación rechazada.",
+ "errorApprove": "Error al aprobar la liquidación.",
+ "errorReject": "Error al rechazar la liquidación.",
+ "errorServer": "Error al comunicarse con el servidor."
}
-}
+}
\ No newline at end of file