CheckPoint: Avances Varios

This commit is contained in:
2025-12-18 13:32:50 -03:00
parent 8f535f3a6e
commit 32663e6324
92 changed files with 12629 additions and 195 deletions

View File

@@ -0,0 +1,142 @@
import { useState, useEffect } from 'react';
import api from '../../services/api';
import { Save, DollarSign } from 'lucide-react';
interface PricingConfig {
basePrice: number;
baseWordCount: number;
extraWordPrice: number;
specialChars: string;
specialCharPrice: number;
boldSurcharge: number;
frameSurcharge: number;
}
interface Category { id: number; name: string; }
export default function PricingManager() {
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCat, setSelectedCat] = useState<number | null>(null);
const [config, setConfig] = useState<PricingConfig>({
basePrice: 0, baseWordCount: 15, extraWordPrice: 0,
specialChars: '!', specialCharPrice: 0, boldSurcharge: 0, frameSurcharge: 0
});
const [loading, setLoading] = useState(false);
useEffect(() => {
// Cargar categorías
api.get('/categories').then(res => setCategories(res.data));
}, []);
useEffect(() => {
if (selectedCat) {
// Cargar config existente
api.get(`/pricing/${selectedCat}`).then(res => {
if (res.data) setConfig(res.data);
else setConfig({ // Default si no existe
basePrice: 0, baseWordCount: 15, extraWordPrice: 0,
specialChars: '!', specialCharPrice: 0, boldSurcharge: 0, frameSurcharge: 0
});
});
}
}, [selectedCat]);
const handleSave = async () => {
if (!selectedCat) return;
setLoading(true);
try {
await api.post('/pricing', { ...config, categoryId: selectedCat });
alert('Configuración guardada correctamente.');
} catch (e) {
alert('Error al guardar.');
} finally {
setLoading(false);
}
};
return (
<div className="max-w-4xl mx-auto">
<h2 className="text-2xl font-bold mb-6 flex items-center gap-2">
<DollarSign className="text-green-600" />
Gestor de Tarifas y Reglas
</h2>
<div className="bg-white p-6 rounded shadow mb-6">
<label className="block text-sm font-medium mb-2">Seleccionar Rubro a Configurar</label>
<select
className="w-full border p-2 rounded"
onChange={e => setSelectedCat(Number(e.target.value))}
value={selectedCat || ''}
>
<option value="">-- Seleccione --</option>
{categories.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
</div>
{selectedCat && (
<div className="bg-white p-6 rounded shadow border border-gray-200">
<h3 className="font-bold text-lg mb-4 border-b pb-2">Reglas de Precio</h3>
<div className="grid grid-cols-2 gap-6">
{/* Tarifa Base */}
<div className="space-y-4">
<h4 className="font-semibold text-blue-600">Base</h4>
<div>
<label className="block text-sm text-gray-600">Precio Mínimo ($)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.basePrice} onChange={e => setConfig({ ...config, basePrice: parseFloat(e.target.value) })} />
</div>
<div>
<label className="block text-sm text-gray-600">Palabras Incluidas (Cant.)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.baseWordCount} onChange={e => setConfig({ ...config, baseWordCount: parseInt(e.target.value) })} />
</div>
<div>
<label className="block text-sm text-gray-600">Costo Palabra Extra ($)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.extraWordPrice} onChange={e => setConfig({ ...config, extraWordPrice: parseFloat(e.target.value) })} />
</div>
</div>
{/* Extras y Estilos */}
<div className="space-y-4">
<h4 className="font-semibold text-purple-600">Extras y Recargos</h4>
<div>
<label className="block text-sm text-gray-600">Caracteres Especiales (ej: !$%)</label>
<input type="text" className="border p-2 rounded w-full"
value={config.specialChars} onChange={e => setConfig({ ...config, specialChars: e.target.value })} />
</div>
<div>
<label className="block text-sm text-gray-600">Costo por Caracter Especial ($)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.specialCharPrice} onChange={e => setConfig({ ...config, specialCharPrice: parseFloat(e.target.value) })} />
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-sm text-gray-600">Recargo Negrita ($)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.boldSurcharge} onChange={e => setConfig({ ...config, boldSurcharge: parseFloat(e.target.value) })} />
</div>
<div>
<label className="block text-sm text-gray-600">Recargo Recuadro ($)</label>
<input type="number" className="border p-2 rounded w-full"
value={config.frameSurcharge} onChange={e => setConfig({ ...config, frameSurcharge: parseFloat(e.target.value) })} />
</div>
</div>
</div>
</div>
<div className="mt-8 flex justify-end">
<button
onClick={handleSave}
disabled={loading}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 flex items-center gap-2"
>
<Save size={18} /> Guardar Configuración
</button>
</div>
</div>
)}
</div>
);
}