{"version":3,"file":"groupTail-uGuxL_eY.cjs","sources":["../src/dashboard/utils/groupTail.ts"],"sourcesContent":["import { OTHERS_KEY } from './chartColors';\n\n/**\n * Agrupa la cola de categorías en un único \"Otros\".\n *\n * <b>Por qué hace falta.</b> Una dona con doce segmentos no comunica una\n * distribución: comunica que hay doce cosas. Los segmentos pequeños quedan por\n * debajo del ancho de un trazo, la leyenda ocupa más que el gráfico y ningún color\n * alcanza para distinguirlos. Lo mismo pasa con un ranking de veinte barras, donde\n * las últimas quince son ruido bajo la línea de lo legible.\n *\n * Agrupar la cola es lo que convierte \"hay muchas categorías\" en \"estas mandan, el\n * resto suma esto\".\n *\n * <b>Preserva el total.</b> \"Otros\" es la SUMA de lo que absorbe, no un descarte:\n * si el usuario compara la dona contra un KPI del mismo dato, los dos tienen que\n * dar la misma cifra.\n *\n * <b>Ordena SIEMPRE, no solo cuando hay cola que agrupar.</b> Antes había un atajo que devolvía\n * las filas tal cual si no superaban el límite, así que el criterio de orden del gráfico cambiaba\n * al cruzarlo: un ranking de cinco categorías salía en el orden en que lo devolviera la consulta\n * y el de ocho, ordenado. Un ranking desordenado no es un ranking.\n *\n * ⚠️ Por eso mismo, NO usar en gráficos de dimensión temporal: ordenar por valor una serie de\n * tiempo destruye justamente lo que la serie cuenta (ver `ChartVentasMensual`).\n */\nexport function groupTail<T extends Record<string, unknown>>(\n  rows: T[],\n  categoryField: string,\n  valueField: string,\n  limit: number,\n): Record<string, unknown>[] {\n  const toNumber = (value: unknown): number => (typeof value === 'number' ? value : Number(value) || 0);\n\n  // `[...rows]` antes de ordenar: `sort` es in-place y estas filas vienen de la caché de\n  // TanStack Query, que otros consumidores comparten.\n  const sorted = [...rows].sort((a, b) => toNumber(b[valueField]) - toNumber(a[valueField]));\n  if (sorted.length <= limit) return sorted as Record<string, unknown>[];\n\n  const head = sorted.slice(0, limit);\n  const tail = sorted.slice(limit);\n\n  const tailTotal = tail.reduce((sum, row) => sum + toNumber(row[valueField]), 0);\n\n  return [\n    ...(head as Record<string, unknown>[]),\n    {\n      [categoryField]: OTHERS_KEY,\n      [valueField]: tailTotal,\n      /** Cuántas categorías absorbió. El tooltip lo dice: \"Otros (7 categorías)\". */\n      __groupedCount: tail.length,\n    },\n  ];\n}\n\n/** Si una fila es el grupo de cola. */\nexport function isOthersRow(value: unknown): boolean {\n  return value === OTHERS_KEY;\n}\n"],"names":["groupTail","rows","categoryField","valueField","limit","toNumber","value","sorted","a","b","head","tail","tailTotal","sum","row","OTHERS_KEY","isOthersRow"],"mappings":"qDA0BO,SAASA,EACdC,EACAC,EACAC,EACAC,EAC2B,CAC3B,MAAMC,EAAYC,GAA4B,OAAOA,GAAU,SAAWA,EAAQ,OAAOA,CAAK,GAAK,EAI7FC,EAAS,CAAC,GAAGN,CAAI,EAAE,KAAK,CAACO,EAAGC,IAAMJ,EAASI,EAAEN,CAAU,CAAC,EAAIE,EAASG,EAAEL,CAAU,CAAC,CAAC,EACzF,GAAII,EAAO,QAAUH,EAAO,OAAOG,EAEnC,MAAMG,EAAOH,EAAO,MAAM,EAAGH,CAAK,EAC5BO,EAAOJ,EAAO,MAAMH,CAAK,EAEzBQ,EAAYD,EAAK,OAAO,CAACE,EAAKC,IAAQD,EAAMR,EAASS,EAAIX,CAAU,CAAC,EAAG,CAAC,EAE9E,MAAO,CACL,GAAIO,EACJ,CACE,CAACR,CAAa,EAAGa,EAAAA,WACjB,CAACZ,CAAU,EAAGS,EAEd,eAAgBD,EAAK,MAAA,CACvB,CAEJ,CAGO,SAASK,EAAYV,EAAyB,CACnD,OAAOA,IAAUS,EAAAA,UACnB"}