📊 Comparativo por Renglón (comparativo)
🧠 Concepto general
El bloque comparativo representa el análisis de cada renglón de la licitación desde el punto de vista del cliente logueado.
👉 Cada elemento del array corresponde a un renglón
👉 Dentro de cada renglón ya vienen calculadas todas las métricas necesarias para comparar contra la competencia
🧱 Estructura de cada renglón
{
"idRenglon": 15,
"numeroRenglon": 1,
"descripcion": "Producto X",
"miOferta": {
"ofertaTotal": 10000,
"mejorOferta": false,
"posicion": 2,
"diferenciaConMejor": 500,
"diferenciaPctConMejor": 5
},
"mejorOfertaGeneral": 9500,
"cantidadOferentes": 4,
"ofertas": [
{
"oferente": "Proveedor A",
"ofertaTotal": 9500,
"posicion": 1,
"mejorOferta": true,
"esCliente": false
},
{
"oferente": "Mi Empresa",
"ofertaTotal": 10000,
"posicion": 2,
"mejorOferta": false,
"esCliente": true
}
]
}
🔑 Campos principales
miOferta
Es el campo más importante.
Puede ser null si el cliente no ofertó ese renglón.
Contiene:
ofertaTotal: importe ofertado por el clienteposicion: ranking en ese renglón (1 = mejor precio)mejorOferta: indica si el cliente tiene la mejor ofertadiferenciaConMejor: diferencia en dinero contra la mejor ofertadiferenciaPctConMejor: diferencia porcentual
👉 Todos estos valores ya vienen calculados desde el backend.
mejorOfertaGeneral
Es el menor precio ofertado en ese renglón.
👉 Se usa como referencia para comparar rápidamente contra la oferta del cliente.
cantidadOferentes
Cantidad total de oferentes que participaron en ese renglón.
ofertas
Listado completo de ofertas del renglón.
- Ya viene ordenado por
ofertaTotal ASC - Cada elemento incluye:
posicionmejorOfertaesCliente
👉 Este array sirve para mostrar el ranking completo en el detalle.
🖥️ Uso en la interfaz
Tabla principal (vista compacta)
Mostrar una fila por renglón con:
- Número de renglón
- Descripción
- Mi oferta
- Mejor oferta
- Diferencia en $
- Diferencia en %
- Posición
- Cantidad de oferentes
🎯 Lógica visual recomendada
Si el cliente ofertó (miOferta != null)
posicion == 1→ estado GANANDO (verde)posicion > 1→ estado PERDIENDO
Opcional (según diferencia):
diferenciaPctConMejor < 2%→ cerca (amarillo)diferenciaPctConMejor > 5%→ lejos (rojo)
Si el cliente NO ofertó (miOferta == null)
Mostrar:
- "No ofertado"
- Estilo visual atenuado (gris)
🔍 Detalle expandible
Al expandir un renglón:
Usar ofertas para mostrar:
- Posición
- Oferente
- Importe
Y marcar:
- Cliente (
esCliente = true) - Mejor oferta (
mejorOferta = true)
🚫 Importante
El frontend NO debe recalcular:
- posiciones
- orden de ofertas
- diferencias
- mejor oferta
👉 Todo eso ya viene resuelto desde el backend.
🧩 Resumen rápido
comparativo= lista de renglonesmiOferta= desempeño del cliente en ese renglónofertas= ranking completo ya ordenadomejorOfertaGeneral= mejor precio del renglón
👉 El frontend solo debe renderizar la información.
📘 Tipos TypeScript
🧱 Interface principal
export interface ComparativoClienteResponse {
cliente: ClienteResumen | null;
comparativoCliente: RenglonComparativo[];
}
👤 Resumen del cliente
export interface ClienteResumen {
cuit: string;
nombre: string | null;
renglonesOfertados: number;
renglonesMejorOferta: number;
importeTotalOfertado: number;
importeMejorOferta: number;
tasaEfectividad: number;
posicionPromedio: number;
}
📦 Renglón comparativo
export interface RenglonComparativo {
idRenglon: number;
numeroRenglon: number;
descripcion: string;
cantidad: number;
unidad: string;
miOferta: OfertaCliente | null;
mejorOfertaGeneral: number;
cantidadOferentes: number;
ofertas: OfertaRenglon[];
}
🧠 Oferta del cliente
export interface OfertaCliente {
oferente: string;
cuit: string;
ofertaTotal: number;
mejorOferta: boolean;
posicion: number;
diferenciaConMejor: number;
diferenciaPctConMejor: number;
}
🏁 Oferta general del renglón
export interface OfertaRenglon {
oferente: string;
cuit: string;
ofertaTotal: number;
mejorOferta: boolean;
posicion: number;
esCliente: boolean;
}
⚠️ Notas importantes
miOferta
- Puede ser
null→ significa que el cliente no ofertó ese renglón - Siempre validar antes de acceder
if (r.miOferta) {
console.log(r.miOferta.posicion);
}
ofertas
- Siempre viene ordenado por
ofertaTotal ASC - No es necesario ordenar en el frontend
mejorOfertaGeneral
- Es el menor precio del renglón
- Puede usarse como referencia directa en UI
🧩 Ejemplo de uso
comparativoCliente.forEach((r) => {
if (r.miOferta) {
console.log(
`Renglón ${r.numeroRenglon}: posición ${r.miOferta.posicion}`
);
} else {
console.log(`Renglón ${r.numeroRenglon}: no ofertado`);
}
});