📊 Comparativo por Renglón (comparativo)
⚠️ IMPORTANTE
Para utilizar este endpoint en modo comparativo es obligatorio enviar:
include_comparativo=1
Parámetro de búsqueda:
find_eq_numero
🧠 Comportamiento del endpoint
🔎 Búsqueda vacía
find_eq_numero=
➡ Devuelve lista de licitaciones
🔎 Búsqueda con valor
Se busca usando coincidencia parcial (LIKE).
Resultado:
- 0 resultados → lista vacía\
- Más de 1 resultado → lista\
- 1 resultado pero NO coincide exactamente → lista\
- 1 resultado y coincide EXACTAMENTE → comparativo
✔ Condición exacta:
licitacion.numero === find_eq_numero
🧠 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
}
]
}
🔑 Campos principales
miOferta
- Puede ser
null - Ya viene calculado
mejorOfertaGeneral
Menor precio del renglón
cantidadOferentes
Cantidad de oferentes
ofertas
- Ordenado por precio ASC
- Incluye ranking
🖥️ Uso en la interfaz
Vista
- Tabla por renglón
- Detalle expandible
🚫 Importante
El frontend NO debe recalcular nada.
📘 Tipos TypeScript
export interface LicitacionComparativoItem {
id: number;
numero: string;
nombre: string;
tipo: string;
fechaApertura: string | null;
unidadEjecutora: string | null;
servicioAdmFinanciero: string | null;
tipoOrden: "ABIERTA" | "SIN_MODALIDAD" | null;
rubro: string | null;
fechaPublicacion: string | null;
}
export interface OferenteResumen {
nombre: string | null;
cuit: string | null;
renglonesOfertados: number;
renglonesMejorOferta: number;
importeMejorOferta: number;
tasaEfectividad: number;
}
export interface ClienteResumen {
cuit: string;
nombre: string | null;
renglonesOfertados: number;
renglonesMejorOferta: number;
importeTotalOfertado: number;
importeMejorOferta: number;
tasaEfectividad: number;
posicionPromedio: number;
}
export interface OfertaRenglon {
oferente: string;
cuit: string;
ofertaTotal: number;
mejorOferta: boolean;
posicion: number;
esCliente: boolean;
}
export interface MiOferta extends OfertaRenglon {
diferenciaConMejor: number;
diferenciaPctConMejor: number;
}
export interface RenglonComparativo {
idRenglon: number;
numeroRenglon: number;
descripcion: string;
cantidad: number;
unidad: string | null;
miOferta: MiOferta | null;
mejorOfertaGeneral: number;
cantidadOferentes: number;
ofertas: OfertaRenglon[];
}
export interface ComparativoClienteResponse {
cliente: ClienteResumen | null;
comparativoCliente: RenglonComparativo[];
}
export interface ComparativoListaResponse {
modo: "lista";
find_eq_numero: string;
total: number;
items: LicitacionComparativoItem[];
}
export interface ComparativoDetalleResponse {
modo: "comparativo";
find_eq_numero: string;
licitacion: unknown; // si después querés, esto lo tipamos fino con la entidad serializada real
oferentes: OferenteResumen[];
comparativo: ComparativoClienteResponse;
}
export type ComparativoEndpointResponse =
| ComparativoListaResponse
| ComparativoDetalleResponse;
export function isComparativoDetalle(
data: ComparativoEndpointResponse
): data is ComparativoDetalleResponse {
return data.modo === "comparativo";
}
export function isComparativoLista(
data: ComparativoEndpointResponse
): data is ComparativoListaResponse {
return data.modo === "lista";