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