Guía de Implementación Frontend: Distribución Equitativa y Porcentaje
✅ Objetivo
Este documento describe cómo implementar un componente en **Next.js** que permita distribuir una cantidad total entre meses seleccionados, usando dos métodos:
- Equitativa: Divide automáticamente entre los meses seleccionados.
- Por porcentaje: El usuario asigna porcentajes manualmente (deben sumar 100%).
El componente realiza la distribución en el cliente y envía los datos validados al backend .NET.
Aquí tienes la guía explicativa sobre el problema clásico de distribución proporcional con redondeo y cómo resolverlo:
✅ Guía: Distribución proporcional y el problema del redondeo
1. ¿Qué es la distribución proporcional?
Cuando tienes una cantidad total (por ejemplo, \$10,000) y deseas distribuirla entre varias partes (meses) según porcentajes, el cálculo básico es:
Ejemplo:
- Total = $10,000
- Enero = 8.33% → $833.33
2. El problema del redondeo
En sistemas financieros, los valores se muestran con 2 decimales.
Si calculas cada mes y redondeas individualmente, la suma puede no coincidir con el total:
833.33+833.33+⋯=9,999.99(faltan 0.01)
Este error acumulado ocurre porque cada valor se redondea antes de sumar.
3. ¿Por qué es crítico?
- En presupuestos y contabilidad, la suma debe ser exacta.
- Si no corriges el error, el backend puede rechazar la operación o generar inconsistencias.
4. Solución: Algoritmo de ajuste
Pasos:
- Calcular todos los valores sin redondear.
- Redondear cada valor a 2 decimales.
- Calcular la diferencia entre la suma redondeada y el total.
- Ajustar la diferencia en uno o más elementos (normalmente el último o el mayor valor).
Ejemplo:
- Total = $10,000
- Suma redondeada = $9,999.99
- Diferencia = $0.01
→ Ajustar el último valor: $833.34.
5. Beneficios
✔ Garantiza que la suma final = cantidad total.
✔ Cumple reglas contables.
✔ Evita errores en validación del backend.
¿Quieres que ahora te entregue el componente actualizado en Next.js con este algoritmo integrado, listo para usar en tu proyecto?
Este componente:
- Calcula distribución equitativa o por porcentaje.
- Aplica el ajuste para que la suma sea exacta.
- Prepara el payload para enviar al backend.
¿Confirmas que lo haga?
✅ Diseño de Interfaz / Referencias visuales:
-Vista Equitativa
– Vista Por porcentaje
✅ Código del Componente Next.js
import { useState } from "react"; const meses = [ "Enero","Febrero","Marzo","Abril","Mayo","Junio", "Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre" ]; export default function ModalDistribucionConAjuste() { const [cantidadTotal, setCantidadTotal] = useState<number>(10000); const [metodo, setMetodo] = useState<"equitativa" | "porcentaje">("equitativa"); const [mesesSeleccionados, setMesesSeleccionados] = useState<string[]>([...meses]); const [porcentajes, setPorcentajes] = useState<{[key:string]: number}>({}); const [loading, setLoading] = useState(false); const toggleMes = (mes: string) => { setMesesSeleccionados(prev => prev.includes(mes) ? prev.filter(m => m !== mes) : [...prev, mes] ); }; const handlePorcentajeChange = (mes: string, value: number) => { setPorcentajes(prev => ({ ...prev, [mes]: value })); }; const sumaPorcentajes = Object.values(porcentajes).reduce((a,b)=>a+b,0); /** Algoritmo para calcular distribución con ajuste */ const calcularDistribucion = () => { let valores = mesesSeleccionados.map(mes => { const porcentaje = metodo === "equitativa" ? (100 / mesesSeleccionados.length) : (porcentajes[mes] || 0); return (cantidadTotal * porcentaje) / 100; }); // Redondear a 2 decimales let valoresRedondeados = valores.map(v => parseFloat(v.toFixed(2))); // Ajustar diferencia const sumaRedondeada = valoresRedondeados.reduce((a,b)=>a+b,0); const diferencia = parseFloat((cantidadTotal - sumaRedondeada).toFixed(2)); if (diferencia !== 0 && valoresRedondeados.length > 0) { valoresRedondeados[valoresRedondeados.length - 1] += diferencia; } return valoresRedondeados; }; const valoresFinales = calcularDistribucion(); const isDistribuirDisabled = metodo === "porcentaje" && sumaPorcentajes !== 100; const handleSubmit = async () => { const distribucion = mesesSeleccionados.map((mes, index) => ({ mes, porcentaje: metodo === "equitativa" ? (100 / mesesSeleccionados.length) : (porcentajes[mes] || 0), valor: valoresFinales[index] })); const payload = { cantidadTotal, metodo, distribucion }; try { setLoading(true); const response = await fetch("/api/distribuir", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error("Error al enviar datos"); alert("Distribución enviada correctamente"); } catch (error) { console.error(error); alert("Hubo un problema al enviar la distribución"); } finally { setLoading(false); } }; return ( <div className="p-6 bg-white rounded shadow-lg w-[650px]"> <h2 className="text-xl font-bold mb-4">Asignar distribución</h2> {/* Cantidad total */} <div className="mb-4"> <label className="block mb-1">Cantidad total:</label> <input type="number" className="border p-2 w-full" value={cantidadTotal} onChange={(e) => setCantidadTotal(Number(e.target.value))} /> </div> {/* Selector de método */} <div className="mb-4 flex gap-6"> <label className="flex items-center gap-2"> <input type="radio" checked={metodo === "equitativa"} onChange={() => setMetodo("equitativa")} /> Equitativa </label> <label className="flex items-center gap-2"> <input type="radio" checked={metodo === "porcentaje"} onChange={() => setMetodo("porcentaje")} /> Por porcentaje </label> </div> {/* Selección de meses */} <div className="grid grid-cols-3 gap-2 mb-4"> {meses.map(mes => ( <label key={mes} className="flex items-center gap-2"> <input type="checkbox" checked={mesesSeleccionados.includes(mes)} onChange={() => toggleMes(mes)} /> {mes} </label> ))} </div> {/* Tabla */} <table className="w-full border"> <thead> <tr className="bg-gray-100"> <th className="p-2 border">Mes</th> <th className="p-2 border">Porcentaje</th> <th className="p-2 border">Valor calculado</th> </tr> </thead> <tbody> {mesesSeleccionados.map((mes, index) => ( <tr key={mes}> <td className="border p-2">{mes}</td> <td className="border p-2"> {metodo === "porcentaje" ? ( <input type="number" className="border p-1 w-20" value={porcentajes[mes] || ""} onChange={(e) => handlePorcentajeChange(mes, Number(e.target.value))} /> % ) : ( `${(100 / mesesSeleccionados.length).toFixed(2)}%` )} </td> <td className="border p-2">{valoresFinales[index].toFixed(2)}</td> </tr> ))} </tbody> </table> {/* Validación */} {metodo === "porcentaje" && ( <p className={`mt-2 text-sm ${sumaPorcentajes === 100 ? "text-green-600" : "text-red-600"}`}> Suma porcentajes: {sumaPorcentajes}% </p> )} {/* Botón */} <button className="mt-4 bg-green-600 text-white px-4 py-2 rounded disabled:bg-gray-400" disabled={isDistribuirDisabled || loading} onClick={handleSubmit} > {loading ? "Enviando..." : "Distribuir"} </button> </div> ); }
✅ Consideraciones
- Validar que la suma de porcentajes sea exactamente 100% antes de enviar.
- En modo Equitativa, los porcentajes y valores se calculan automáticamente.
- El componente debe ser responsivo y compatible con Tailwind CSS.
- Reemplazar
"/api/distribuir"
por la URL real del backend .NET.
Comentarios recientes