From a9ad545fbbad806a8d1fe0728ec11322976322ba Mon Sep 17 00:00:00 2001 From: dmolinari Date: Sat, 21 Feb 2026 20:10:40 -0300 Subject: [PATCH] =?UTF-8?q?ux:=20men=C3=BA=20lateral=20m=C3=A1s=20descript?= =?UTF-8?q?ivo=20y=20PricingManager=20muestra=20productos=20del=20rubro=20?= =?UTF-8?q?con=20precios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/layouts/ProtectedLayout.tsx | 28 +-- .../src/pages/Pricing/PricingManager.tsx | 169 ++++++++++++------ 2 files changed, 131 insertions(+), 66 deletions(-) diff --git a/frontend/admin-panel/src/layouts/ProtectedLayout.tsx b/frontend/admin-panel/src/layouts/ProtectedLayout.tsx index 4466d47..03ec75b 100644 --- a/frontend/admin-panel/src/layouts/ProtectedLayout.tsx +++ b/frontend/admin-panel/src/layouts/ProtectedLayout.tsx @@ -49,20 +49,20 @@ export default function ProtectedLayout() { { title: "Gestión Diaria", items: [ - { label: 'Dashboard', href: '/', icon: , roles: ['Admin', 'Cajero'] }, - { label: 'Moderación', href: '/moderation', icon: , roles: ['Admin', 'Moderador'], badge: unreadCount }, - { label: 'Explorador', href: '/listings', icon: , roles: ['Admin', 'Cajero', 'Moderador'] }, + { label: 'Inicio', href: '/', icon: , roles: ['Admin', 'Cajero'] }, + { label: 'Moderación de Avisos', href: '/moderation', icon: , roles: ['Admin', 'Moderador'], badge: unreadCount }, + { label: 'Buscar Publicaciones', href: '/listings', icon: , roles: ['Admin', 'Cajero', 'Moderador'] }, { label: 'Clientes', href: '/clients', icon: , roles: ['Admin', 'Cajero'] }, ] }, { - title: "Comercial & Catálogo", + title: "Catálogo & Precios", items: [ - { label: 'Productos', href: '/products', icon: , roles: ['Admin'] }, - { label: 'Tarifas', href: '/pricing', icon: , roles: ['Admin'] }, + { label: 'Productos & Tarifas', href: '/products', icon: , roles: ['Admin'] }, + { label: 'Reglas por Rubro', href: '/pricing', icon: , roles: ['Admin'] }, { label: 'Promociones', href: '/promotions', icon: , roles: ['Admin'] }, - { label: 'Cupones', href: '/coupons', icon: , roles: ['Admin'] }, - { label: 'Categorías', href: '/categories', icon: , roles: ['Admin'] }, + { label: 'Cupones de Descuento', href: '/coupons', icon: , roles: ['Admin'] }, + { label: 'Rubros & Categorías', href: '/categories', icon: , roles: ['Admin'] }, ] }, { @@ -70,16 +70,16 @@ export default function ProtectedLayout() { items: [ { label: 'Empresas', href: '/companies', icon: , roles: ['Admin'] }, { label: 'Riesgo Crediticio', href: '/finance/credit', icon: , roles: ['Admin', 'Gerente'] }, - { label: 'Liquidación', href: '/reports/settlement', icon: , roles: ['Admin', 'Contador'] }, + { label: 'Liquidación de Cuentas', href: '/reports/settlement', icon: , roles: ['Admin', 'Contador'] }, ] }, { - title: "Operaciones & Sistema", + title: "Sistemas & Administración", items: [ - { label: 'Diagramación', href: '/diagram', icon: , roles: ['Admin', 'Diagramador'] }, - { label: 'Calendario', href: '/companies/calendar', icon: , roles: ['Admin'] }, - { label: 'Usuarios', href: '/users', icon: , roles: ['Admin'] }, - { label: 'Auditoría', href: '/audit', icon: , roles: ['Admin'] }, + { label: 'Diagramación de Página', href: '/diagram', icon: , roles: ['Admin', 'Diagramador'] }, + { label: 'Calendario de Ediciones', href: '/companies/calendar', icon: , roles: ['Admin'] }, + { label: 'Gestión de Usuarios', href: '/users', icon: , roles: ['Admin'] }, + { label: 'Registro de Auditoría', href: '/audit', icon: , roles: ['Admin'] }, ] } ]; diff --git a/frontend/admin-panel/src/pages/Pricing/PricingManager.tsx b/frontend/admin-panel/src/pages/Pricing/PricingManager.tsx index 509cc52..61e00e8 100644 --- a/frontend/admin-panel/src/pages/Pricing/PricingManager.tsx +++ b/frontend/admin-panel/src/pages/Pricing/PricingManager.tsx @@ -1,8 +1,17 @@ import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; import api from '../../services/api'; -import { Save, DollarSign, FileText, Type, AlertCircle } from 'lucide-react'; +import { Save, DollarSign, FileText, Package, ExternalLink, ArrowRight } from 'lucide-react'; import { processCategories, type FlatCategory } from '../../utils/categoryTreeUtils'; +// Datos mínimos de un Producto del Catálogo +interface ProductSummary { + id: number; + name: string; + typeCode: string; + basePrice: number; +} + interface PricingConfig { baseWordCount: number; extraWordPrice: number; @@ -21,6 +30,8 @@ const defaultConfig: PricingConfig = { export default function PricingManager() { const [flatCategories, setFlatCategories] = useState([]); const [selectedCat, setSelectedCat] = useState(null); + const [categoryProducts, setCategoryProducts] = useState([]); + const [loadingProducts, setLoadingProducts] = useState(false); const [config, setConfig] = useState(defaultConfig); const [saving, setSaving] = useState(false); @@ -35,12 +46,21 @@ export default function PricingManager() { useEffect(() => { if (selectedCat) { - // Cargar config existente + // Cargar config existente del rubro api.get(`/pricing/${selectedCat}`) .then(res => { if (res.data) setConfig(res.data); - else setConfig(defaultConfig); // Reset si es nuevo + else setConfig(defaultConfig); }); + + // Cargar productos vinculados a este rubro + setLoadingProducts(true); + api.get(`/products/by-category/${selectedCat}`) + .then(res => setCategoryProducts(res.data || [])) + .catch(() => setCategoryProducts([])) + .finally(() => setLoadingProducts(false)); + } else { + setCategoryProducts([]); } }, [selectedCat]); @@ -64,7 +84,7 @@ export default function PricingManager() {

- Gestor de Tarifas y Reglas + Reglas de Tarifación por Rubro

{/* SELECTOR DE RUBRO */} @@ -81,7 +101,7 @@ export default function PricingManager() { ))} - {/* Flecha custom para estilo */}
@@ -105,17 +124,63 @@ export default function PricingManager() {
- {/* TARIFA BASE */} + {/* TARJETA: PRECIO BASE - muestra productos vinculados */} +
+

+ Precio Base del Rubro +

+

+ El precio mínimo por aviso se define en cada Producto del Catálogo vinculado a este rubro. +

+ + {loadingProducts ? ( +
Cargando productos...
+ ) : categoryProducts.length === 0 ? ( +
+
+ +
+

Sin productos vinculados

+

+ Este rubro no tiene productos en el Catálogo. El precio base será $0 al calcular tarifas. +

+
+
+ + Ir al Catálogo de Productos para agregar uno + +
+ ) : ( +
+ {categoryProducts.map(prod => ( +
+
+ {prod.typeCode} + {prod.name} +
+ ${prod.basePrice.toLocaleString()} +
+ ))} + + Administrar precios en el Catálogo de Productos + +
+ )} +
+ + {/* TARJETA: REGLAS POR PALABRAS */}

- Tarifa Base (Texto) + Reglas por Cantidad de Palabras

-

- El Precio Base (Precio Mínimo) ahora se define directamente en los Productos del Catálogo. -

-
@@ -131,58 +196,58 @@ export default function PricingManager() {
- {/* CONTENIDO ESPECIAL */} -
-

- Caracteres Especiales -

+
-
-
- - setConfig({ ...config, specialChars: e.target.value })} /> -

Cada uno de estos símbolos se cobrará aparte.

-
+ {/* TARJETA: CARACTERES ESPECIALES */} +
+

+ Caracteres Especiales +

-
- -
- $ - setConfig({ ...config, specialCharPrice: parseFloat(e.target.value) })} /> -
+
+
+ + setConfig({ ...config, specialChars: e.target.value })} /> +

Cada uno de estos símbolos se cobrará aparte del precio por palabra.

+
+ +
+ +
+ $ + setConfig({ ...config, specialCharPrice: parseFloat(e.target.value) })} />
+
- {/* ESTILOS VISUALES */} -
-

- Estilos Visuales (Recargos) -

+ {/* TARJETA: ESTILOS VISUALES */} +
+

+ Estilos Visuales (Recargos) +

-
-
-
N
-
- - setConfig({ ...config, boldSurcharge: parseFloat(e.target.value) })} /> -
+
+
+
N
+
+ + setConfig({ ...config, boldSurcharge: parseFloat(e.target.value) })} />
+
-
-
A
-
- - setConfig({ ...config, frameSurcharge: parseFloat(e.target.value) })} /> -
+
+
A
+
+ + setConfig({ ...config, frameSurcharge: parseFloat(e.target.value) })} />
-
{/* BARRA DE ACCIÓN FLOTANTE */}