LI
Documentación interna
Comparativo

📊 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 cliente
  • posicion: ranking en ese renglón (1 = mejor precio)
  • mejorOferta: indica si el cliente tiene la mejor oferta
  • diferenciaConMejor: diferencia en dinero contra la mejor oferta
  • diferenciaPctConMejor: 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:
    • posicion
    • mejorOferta
    • esCliente

👉 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 renglones
  • miOferta = desempeño del cliente en ese renglón
  • ofertas = ranking completo ya ordenado
  • mejorOfertaGeneral = 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`);
  }
});
Uso interno