@trazum/cli 1.9.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/i18n/es.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import type { CliMessages } from './types.js';
2
2
 
3
+ /** Counts, grouped. A log with forty thousand torn lines should say so legibly. */
4
+ const count = (value: number): string => value.toLocaleString('es-ES');
5
+
6
+ /** Un contador agrupado y su sustantivo, concordando. Ver `plural` en en.ts. */
7
+ const plural = (value: number, one: string, many = `${one}s`): string =>
8
+ `${count(value)} ${value === 1 ? one : many}`;
9
+
3
10
  /** "(hace 46 días)", o nada cuando no se sabe la antigüedad. */
4
11
  const hace = (days: number | null): string =>
5
12
  days === null ? '' : days === 0 ? ' (hoy)' : days === 1 ? ' (hace 1 día)' : ` (hace ${days} días)`;
@@ -18,6 +25,7 @@ ${bold('USO')}
18
25
  trazum baseline [dir] [opciones]
19
26
  trazum eval <fichero> --cases <fichero> [opciones]
20
27
  trazum eval <fichero> --cases <fichero> --export promptfoo -o suite.json
28
+ trazum route <log.jsonl> --prompt-file <fichero> --cases <fichero> --yes
21
29
  trazum diff <antes> <después> [opciones]
22
30
  trazum diff --all <dir> <dir> [opciones]
23
31
  trazum rank <dir> [opciones]
@@ -192,6 +200,96 @@ ${bold('OPCIONES DE eval')}
192
200
  una. Esa base es la vara de medir: sin ella, un porcentaje de divergencia no
193
201
  significa nada. Sale con código 1 cuando las respuestas divergen de verdad.
194
202
 
203
+ ${bold('OPCIONES DE profile')}
204
+ --against <log.jsonl> Compara esta factura con un registro anterior.
205
+ Positivo significa que creció; los drivers van
206
+ ordenados por su contribución al cambio. No se
207
+ asume ningún periodo — juzga las llamadas antes
208
+ que el dinero.
209
+ --label <nombre> Perfila solo las llamadas con esta etiqueta — el
210
+ zoom una vez que el informe completo nombró al
211
+ sospechoso. Una etiqueta sin llamadas es un error
212
+ que nombra las que existen. Con --against se
213
+ filtran los dos registros, así que la comparación
214
+ sigue siendo una sola carga.
215
+ --max-usd <n> Sale con código 1 si este registro gastó más de n
216
+ dólares. El presupuesto aplica exactamente al
217
+ registro entregado: perfila el log de ayer cada
218
+ noche y tienes presupuesto diario sin que Trazum
219
+ adivine qué es un día.
220
+ --max-growth-usd <n> Con --against: sale con 1 si la factura creció
221
+ más de n dólares sobre el registro anterior. Solo,
222
+ es un error, no un flag que vigila nada en silencio.
223
+ --max-cache-loss-usd <n> Sale con 1 si cachear añadió más de n dólares a
224
+ esta factura. Lee el peor caso cuando el registro
225
+ no anotó el TTL de escritura — una puerta que
226
+ leyera la mitad halagadora dejaría pasar las
227
+ facturas que existe para cazar — y dice qué
228
+ afirmación disparó.
229
+ --max-day-usd <n> Falla cuando un solo día UTC dentro del registro
230
+ gastó más que esto. Un mes dentro de presupuesto
231
+ puede esconder la tarde en que un bucle se comió
232
+ una cuarta parte. Un registro sin marcas de
233
+ tiempo falla: no medido no es dentro de
234
+ presupuesto.
235
+ --since <cuándo> Perfila solo llamadas desde/hasta ese momento. Un
236
+ --until <cuándo> día UTC (2026-08-14), una marca ISO 8601 completa,
237
+ una ventana relativa (7d, 24h) o "now";
238
+ --until con fecha sola incluye ese día entero. Las
239
+ llamadas sin "ts" no se pueden situar y quedan
240
+ fuera — contadas en voz alta, nunca en silencio.
241
+ Con --against, ambos registros llevan la misma
242
+ ventana.
243
+ --what-if <modelo> Pone precio a estas mismas llamadas en otro
244
+ modelo. Los mismos recuentos de tokens con otra
245
+ tarifa — una multiplicación, no un consejo, y lo
246
+ dice. Las llamadas mayores que la ventana de
247
+ contexto de ese modelo se nombran como
248
+ imposibles, no se cobran como baratas.
249
+ --markdown-out <fichero> Escribe además el informe en Markdown, para el
250
+ resumen de un job de CI o un comentario de PR.
251
+ --csv-shape <forma> Qué tabla escribe --csv-out: slice (por defecto),
252
+ day u hour. Una sola forma de fila por fichero,
253
+ para que nada haya que filtrar antes de sumarlo.
254
+ --csv-out <fichero> Escribe además el informe en CSV, una fila por
255
+ etiqueta y modelo. Sin fila de total, a propósito:
256
+ un total dentro de un fichero de datos acaba
257
+ sumado con los datos. Los modelos sin precio
258
+ conservan sus tokens y dejan las celdas de
259
+ dólares vacías, nunca a cero.
260
+ --pricing <fichero> Overlay local de precios, como en el resto.
261
+ --json El informe completo como datos, palancas incluidas.
262
+
263
+ Acepta un fichero de registro o un directorio de ellos — los registros
264
+ rotados por día se leen en orden de nombre como una sola factura, y se dice
265
+ cuántos se leyeron. También lee los comprimidos (.jsonl.gz y los demás),
266
+ porque es lo que es un registro rotado un día después; uno que no se pueda
267
+ descomprimir es un error que nombra el fichero, nunca una factura a la que
268
+ le falta un día en silencio.
269
+
270
+ Lee lo que el proveedor cobró de verdad. Campos opcionales desbloquean
271
+ hallazgos: "label" (qué carga), "session" (qué conversación — se agrupa y
272
+ nunca se imprime), "stop_reason"/"finish_reason" (respuestas cortadas en
273
+ max_tokens).
274
+
275
+ ${bold('OPCIONES DE route')}
276
+ --prompt-file <fichero> El prompt que mandan esas llamadas. No --prompt,
277
+ que nombra un prompt marcado dentro de un fuente.
278
+ --cases <fichero> Una entrada por línea, o un array JSON. Obligatorio.
279
+ --label <nombre> Mide esta carga en vez de la más cara.
280
+ --concurrency <n> Llamadas en vuelo a la vez. Por defecto: 3.
281
+ --yes Gasta las llamadas de verdad. Sin él se imprime la
282
+ cuenta y no se llama a nada.
283
+ --json La porción y la medición como datos.
284
+
285
+ Lee un registro de uso, busca la porción donde llevar las llamadas a un modelo
286
+ más barato vale más, y mide si ese modelo sigue haciendo el trabajo. El mismo
287
+ prompt va a los dos, y el original se ejecuta dos veces por caso — así el
288
+ veredicto se juzga contra la varianza de ese modelo y no contra un umbral
289
+ inventado.
290
+
291
+ Cuesta tres llamadas al proveedor por caso y necesita TRAZUM_LLM_* configurado.
292
+
195
293
  ${bold('OPCIONES DE diff')}
196
294
  --max-growth <n> Falla si el prompt ha crecido más de n tokens.
197
295
  --all Compara dos directorios de prompts, emparejados por
@@ -238,6 +336,12 @@ ${bold('FICHERO DE CONFIGURACIÓN')}
238
336
  budgets { "prompts/**": 2000, "prompts/system.txt": 4000 }
239
337
  baseline { "path": "trazum.baseline.json", "maxGrowthTokens": 0, "maxGrowthPct": 5 }
240
338
  pricing "./prices.json" — correcciones locales de precios, ver abajo
339
+ labels { "support-rag": "prompts/soporte.txt" } — qué fichero de prompt
340
+ manda cada label del registro de uso, para que "trazum profile"
341
+ lea el fichero y diga por qué falla una caché que falla
342
+ spend { "maxUsd": 200, "byLabel": { "chat": 40 } } — presupuestos en
343
+ dólares para "trazum profile". Una etiqueta con presupuesto y sin
344
+ llamadas se informa como no medida, nunca como aprobada
241
345
 
242
346
  Las opciones ganan al config; el config gana a los valores por defecto. Los
243
347
  presupuestos se resuelven con el patrón más específico que encaje — gana el
@@ -376,7 +480,7 @@ ${bold('EJEMPLOS')}
376
480
  inputTokens: () => 'Tokens de entrada',
377
481
  estimated: (offFamily) =>
378
482
  offFamily === null
379
- ? ' (estimado, ±15%)'
483
+ ? ' (estimado, ±10%)'
380
484
  : ` (estimado — el contador está calibrado sobre Claude, no sobre ${offFamily})`,
381
485
  exactCount: () => ' (recuento exacto)',
382
486
  rulesApplied: () => 'Reglas aplicadas',
@@ -419,8 +523,18 @@ ${bold('EJEMPLOS')}
419
523
  tokensOnlyAsked: () => 'Costes ocultos porque has pedido solo tokens.',
420
524
  tokensSaved: (tokens) =>
421
525
  `${tokens} token${tokens === '1' ? '' : 's'} de vuelta, en cada llamada.`,
526
+ windowNegligible: (tokens, model, window) =>
527
+ `${tokens} tokens de la ventana de ${window} de ${model} — menos de una décima de por ciento, así que la ventana no es lo que limita este prompt.`,
528
+ windowUnmoved: (share, model, window) =>
529
+ `${share} de la ventana de ${window} de ${model}, antes y después: este cambio es demasiado pequeño para moverla.`,
530
+ beyondThisPromptTokensOnly: () =>
531
+ 'Acortar un prompt es la palanca más pequeña que hay: medido en un prompt de soporte corriente, las reglas recuperan alrededor del 1% de una factura mensual. Si alguno de tus prompts va a una API de pago por uso, "trazum profile <usage.jsonl>" lee lo que el proveedor cobró de verdad y calcula las palancas que no son el prompt. Grabar ese registro son unas pocas líneas y nunca contiene texto del prompt.',
532
+ beyondThisPrompt: () =>
533
+ 'Acortar un prompt es la palanca más pequeña que hay: medido en un prompt de soporte corriente, las reglas recuperan alrededor del 1% de una factura mensual. En una API de pago por uso, lo que mueve del 40% al 80% es a qué modelo va la llamada, la Batch API, la caché de prompts, y lo que cuesta reenviar la conversación — y "trazum profile <usage.jsonl>" calcula las cuatro a partir de lo que el proveedor cobró de verdad. Grabar ese registro son unas pocas líneas y nunca contiene texto del prompt.',
422
534
  windowUse: (before, after, model, window) =>
423
535
  `Ventana de contexto: ${before} → ${after} de los ${window} tokens de ${model} — sitio que se lleva la conversación.`,
536
+ tokensOnlyAskedFor: () =>
537
+ 'Has nombrado un escenario y no se ha calculado su coste: Trazum se está ejecutando en un sitio que factura por suscripción, así que aquí no hay factura que reducir. Añade --cost para calcularlo igualmente — el host dice dónde se ejecuta Trazum, no a dónde va tu prompt.',
424
538
  tokensOnlyCost: () => 'Usa --cost si este prompt va a una API de pago por uso.',
425
539
  pricingOverlaid: (models, lastReviewed) =>
426
540
  `Los precios de ${models} vienen de un overlay local revisado el ${lastReviewed}, no del catálogo incluido.`,
@@ -565,7 +679,7 @@ ${bold('EJEMPLOS')}
565
679
  `Mostrando las ${shown} más recientes. Usa --limit para ver más.`,
566
680
  followedRename: (from) => `Se ha seguido un renombrado: las revisiones anteriores son ${from}.`,
567
681
  estimateNote: () =>
568
- 'Los recuentos son estimaciones (±15%). Lo que importa es la tendencia, no las cifras absolutas.',
682
+ 'Los recuentos son estimaciones (±10%). Lo que importa es la tendencia, no las cifras absolutas.',
569
683
  },
570
684
 
571
685
  languages: {
@@ -766,7 +880,7 @@ ${bold('EJEMPLOS')}
766
880
  footer: (source, level) => `Recuento de tokens ${source} · nivel de reglas \`${level}\``,
767
881
  pricingOverlaid: (count, lastReviewed) =>
768
882
  `Los precios de ${count} ${count === 1 ? 'modelo' : 'modelos'} vienen de un overlay local revisado el ${lastReviewed}.`,
769
- sourceEstimated: () => 'estimado, ±15%',
883
+ sourceEstimated: () => 'estimado, ±10%',
770
884
  sourceExact: () => 'exacto',
771
885
  measuringOptimised: () =>
772
886
  'Se mide lo que dejarían las reglas, no lo que está escrito en el fichero.',
@@ -811,6 +925,294 @@ ${bold('EJEMPLOS')}
811
925
  exactCountsCost: (files) =>
812
926
  `Contando ${files} ${files === 1 ? 'fichero' : 'ficheros'} con la API, una llamada por cada uno. Esto tarda un momento.`,
813
927
  },
928
+ profile: {
929
+ noTarget: () =>
930
+ 'Apúntalo a un log de uso: trazum profile usage.jsonl — un objeto JSON por línea, cada uno con un "model" y el objeto "usage" que devolvió la API. Añade "label" (qué carga), "session" (qué conversación) y "ts" (cuándo) ya que estás: sin ellos todas las llamadas parecen iguales, y los hallazgos más grandes de este comando — el crecimiento de conversación, y si el TTL de la caché encaja con el ritmo de tus turnos — no se pueden hacer siquiera. Registrarlo son cuatro líneas en tu propio código, nunca contiene el texto del prompt, y la clave de sesión se agrupa y nunca se imprime.',
931
+ heading: () => 'Adónde fue el dinero',
932
+ calls: (n) => plural(n, 'llamada'),
933
+ spent: (calls, total) => `${calls} · ${total}`,
934
+ part: (name, usd, pct, tokens) => `${name.padEnd(15)}${usd.padStart(11)} ${pct.padStart(5)} ${tokens} tokens`,
935
+ partInput: () => 'Entrada',
936
+ partCacheRead: () => 'Lecturas caché',
937
+ partCacheWrite: () => 'Escrituras caché',
938
+ partOutput: () => 'Salida',
939
+ byLabelHeading: () => 'Por etiqueta',
940
+ byModelHeading: () => 'Por modelo',
941
+ row: (name, usd, pct, calls) => `${usd.padStart(11)} ${pct.padStart(5)} ${name} (${calls})`,
942
+ unlabelled: () => '(sin label)',
943
+ cacheHit: (pct) => `Tasa de acierto de caché: ${pct} de la entrada facturable.`,
944
+ cacheNever: () => 'No se usó caché en estas llamadas. Si algún prefijo se repite, ese es el mayor ahorro disponible.',
945
+ cacheLost: (usd, writes, reads) =>
946
+ `La caché añadió ${usd} a esta factura en lugar de restarlos. Se escribieron ${writes} tokens en la caché y se leyeron ${reads} — y una escritura cuesta 1,25x la entrada normal, o 2x con el TTL de una hora. Un prefijo que cambia más rápido de lo que se reutiliza paga esa prima a cambio de nada. O cachea un prefijo que no se mueva, o desactiva la caché aquí.`,
947
+ cachePaidOff: (usd) => `La caché quitó ${usd} de esta factura, frente a esos mismos tokens sin cachear.`,
948
+ cacheNoDifference: () =>
949
+ 'La caché quedó a cero en esta factura: lo que cobró por estos tokens es lo que habrían costado como entrada normal. Ni se paga a sí misma ni te está costando nada.',
950
+ cacheLostBy: (labels) => `La pérdida está en: ${labels}.`,
951
+ cacheLostHidden: (usd, labels) =>
952
+ `El total de arriba esconde una pérdida: la caché cuesta ${usd} repartidos en ${labels}.`,
953
+ andMoreLabels: (n) => `y ${count(n)} más`,
954
+ cacheTtlUnsettled: (calls, asRecorded, atLongTtl) =>
955
+ `Este registro no puede decir si la caché salió a cuenta. ${count(calls)} ${calls === 1 ? 'llamada no registró' : 'llamadas no registraron'} qué TTL de escritura de caché se usó: con la tarifa de 5 minutos la caché quitó ${asRecorded} de esta factura, y con la de 1 hora esas mismas llamadas le añadieron ${atLongTtl}. No se da ninguna de las dos como respuesta. Registra el objeto "cache_creation" que devuelve la API y esto se resuelve solo.`,
956
+ cacheTtlBound: (calls, atLongTtl) =>
957
+ `Esa cifra es una cota, no una medición: ${count(calls)} ${calls === 1 ? 'llamada no registró' : 'llamadas no registraron'} el TTL de escritura de caché, y con la tarifa de 1 hora es ${atLongTtl}.`,
958
+ cacheTtlUnsettledLabels: (labels) =>
959
+ `Estas estarían perdiendo dinero si sus escrituras sin registrar usaron el TTL de 1 hora: ${labels}.`,
960
+ biggestPart: (name, pct) => `${name} es el ${pct} de esta factura.`,
961
+ outputDominates: (pct) =>
962
+ `La salida es el ${pct} de esta factura, así que acortar prompts tiene un techo bajo aquí. Lo que mueve la aguja es pedir respuestas más cortas y limitar max_tokens.`,
963
+ unpriced: (models, calls) =>
964
+ `${count(calls)} ${calls === 1 ? 'llamada no está' : 'llamadas no están'} en estos totales: el catálogo de precios no conoce ${models}. Añádelos con un overlay de precios (--pricing) para incluirlos.`,
965
+ skipped: (lineCount, lines) =>
966
+ `No se ${lineCount === 1 ? 'pudo leer' : 'pudieron leer'} ${count(lineCount)} ${lineCount === 1 ? 'línea y quedó fuera' : 'líneas y quedaron fuera'} (${lineCount === 1 ? 'línea' : 'líneas'} ${lines}).`,
967
+ empty: () => 'No hay registros de uso en ese archivo.',
968
+ nothingPriced: () =>
969
+ 'Ninguno de los modelos de ese log está en el catálogo de precios, así que no hay factura que reportar. Añádelos con un overlay de precios (--pricing) y vuelve a ejecutarlo.',
970
+ leversHeading: () => 'Lo que de verdad movería esta factura',
971
+ leverSlice: (label, model, usd, pct) =>
972
+ `${label} en ${model} — hasta ${usd} de esta factura (${pct})`,
973
+ leverRoute: (candidate, usd) => `llévalo a ${candidate}, ${usd}`,
974
+ leverRouteVerify: (candidate) =>
975
+ `Si eso aguanta es una pregunta de evaluación, no de aritmética, y aquí no se ha visto ni una sola respuesta. Mídelo: trazum route <log> --prompt-file <prompt> --cases <casos> --yes`,
976
+ leverBatch: (usd) => `mándalo por la Batch API, ${usd}`,
977
+ leverCalls: (calls, spent) => `${calls}, ${spent} gastados`,
978
+ leverPromptCeiling: (usd, pct) =>
979
+ `Para comparar: acortar el texto del prompt puede tocar ${usd} como mucho — el ${pct} de esta factura, y solo si borraras hasta el último token de entrada. La cifra real está muy por debajo, porque la mayoría de esos tokens son contexto recuperado, historial de conversación y resultados de herramientas que no están en ningún fichero de prompt.`,
980
+ historyHeading: () => 'Lo que cuesta reenviar la conversación',
981
+ historyGrowth: (label, model, first, last, turns) =>
982
+ `${label} en ${model}: la entrada va de ${first} tokens en el turno más pequeño a ${last} en el más grande, en conversaciones de hasta ${turns} turnos.`,
983
+ historyCeiling: (usd, pct, flat, spent) =>
984
+ `Si cada turno hubiera tenido el tamaño del más pequeño, esa entrada habría costado ${flat} en vez de ${spent} — así que como mucho ${usd} de esta factura es crecimiento de la conversación (${pct}). Es un techo y no un ahorro: parte de eso son los mensajes nuevos del propio usuario, que nada puede truncar, y esto lee cuentas y no contenido, así que no puede separarlos. Lo que lo mueve es limitar el historial que reproduces, o resumirlo.`,
985
+ truncatedWaste: (calls, usd, pct) =>
986
+ `${calls} chocaron con el techo de max_tokens: ${usd} del gasto en salida (${pct}) compró respuestas cortadas a medias — pagadas enteras, a menudo reintentadas y facturadas otra vez. Donde la respuesta de verdad necesite el espacio, sube max_tokens; donde no, pide menos. En cualquier caso este es el único trozo de una factura que es desperdicio sin contrapartida.`,
987
+ againstHeading: () => 'Contra el registro anterior',
988
+ againstTotals: (before, after, delta, pct, callsBefore, callsAfter) =>
989
+ `${before} → ${after} ${delta} (${pct}) ${callsBefore} → ${callsAfter}. Positivo significa que la factura creció. Las dos cifras son exactamente lo que contiene cada fichero — no se asume ningún periodo, así que juzga las llamadas antes de juzgar el dinero.`,
990
+ againstDriver: (delta, label, before, after) => `${delta} ${label} (${before} → ${after})`,
991
+ againstDriverNew: (delta, label) => `${delta} ${label} (nuevo desde el registro anterior)`,
992
+ againstDriverGone: (delta, label) => `${delta} ${label} (desaparecido desde el registro anterior)`,
993
+ againstByModel: () =>
994
+ 'El mismo cambio, por modelo — hacia dónde se movió la mezcla:',
995
+ labelPrefixBelowMinimum: (file, prefix, minimum, model) =>
996
+ `${file} (tal y como está hoy — el registro puede ser anterior): el prefijo estable son ${prefix} tokens y ${model} no cachea nada por debajo de ${minimum}. Poner cache_control ahí no da error, simplemente nunca cachea — que es exactamente el aspecto que tiene en la factura una caché que solo escribe.`,
997
+ labelPrefixMovable: (file, movable, prefix) =>
998
+ `${file} (tal y como está hoy — el registro puede ser anterior): ~${movable} tokens estables están detrás del primer marcador, donde la caché no llega; el prefijo cacheable es ${prefix}. "trazum optimize ${file} --reorder" los mueve delante y enseña el diff.`,
999
+ labelPrefixHealthy: (file, prefix, minimum) =>
1000
+ `${file} (tal y como está hoy — el registro puede ser anterior): el prefijo estable son ${prefix} tokens, por encima del mínimo de ${minimum}. El fichero no es el problema; mira si el prefijo es byte a byte idéntico entre llamadas.`,
1001
+ labelFileMissing: (label, file) =>
1002
+ `labels["${label}"] apunta a ${file}, que no existe — el mapeo se ha saltado.`,
1003
+ againstNothingPriced: () =>
1004
+ 'El registro anterior no tiene nada que el catálogo de precios conozca, así que no hay comparación que hacer.',
1005
+ truncatedNotRecorded: () =>
1006
+ 'No se pudo medir si alguna respuesta quedó cortada — ninguna llamada de este registro lleva stop reason. Añade "stop_reason" (Anthropic) o "finish_reason" (OpenAI) al registro; la API ya lo devuelve junto a "usage".',
1007
+ historyNoSessions: () =>
1008
+ 'Ninguna llamada de este registro llevaba sesión, así que no se pudo medir lo que cuesta reenviar la conversación — normalmente la línea más grande de una factura de chat o de agente. Añade "session" (o "conversation_id") al registro y vuelve a ejecutarlo. Trazum agrupa por ese campo y nunca lo imprime.',
1009
+ leversUnlabelled: () =>
1010
+ 'Ninguna de estas llamadas llevaba label, así que esto es todas las cargas en una fila — un clasificador y un pipeline RAG fundidos en una sola cifra, con una única ruta sugerida para los dos. Añade "label" al registro y las palancas se separan por carga, que es la agrupación en la que de verdad se toma una decisión.',
1011
+ outputShapeHeading: () => 'Dónde se concentra el gasto en salida',
1012
+ outputTail: (label, model, callPct, spendPct, above, usd) =>
1013
+ `${label} en ${model}: el ${callPct} de las llamadas concentra el ${spendPct} del gasto en salida — las que responden con más de ${above} tokens, de ${usd} de salida en esta porción.`,
1014
+ outputTailAdvice: () =>
1015
+ 'Eso es una cola, y una cola tiene una causa: un camino del prompt que invita a una redacción, una llamada sin max_tokens, una recuperación que devolvió un libro. Encontrarla es una mañana; no es "acorta todo".',
1016
+ outputFlat: (label, model, callPct, spendPct, usd) =>
1017
+ `${label} en ${model}: el gasto en salida está donde están las llamadas — el ${callPct} concentra el ${spendPct} de ${usd}. No hay cola que cazar.`,
1018
+ outputFlatAdvice: () =>
1019
+ 'Aquí la longitud de la respuesta es la tarea, así que las palancas son las bastas: pide respuestas más cortas en el prompt, y limita max_tokens.',
1020
+ outputPercentiles: (p50, p95) =>
1021
+ `La mitad de las respuestas medidas caben en ${p50} tokens de salida, y el 95% en ${p95} — el número que un límite de max_tokens quiere de verdad. Medido en estas llamadas, prometido para ninguna.`,
1022
+ inputShapeHeading: () => 'Cómo de grandes son estas llamadas',
1023
+ inputSkewed: (label, model, p50, p95, ratio, usd) =>
1024
+ `${label} en ${model} es desigual: la mitad de sus llamadas caben en ${p50} tokens de entrada y el 95% en ${p95} — unas ${ratio} veces la llamada normal, sobre ${usd} de gasto de entrada.`,
1025
+ inputSkewedAdvice: () =>
1026
+ 'Por encima de cuatro veces la mediana, la llamada normal está bien y algo crece encima: una conversación que nadie corta, una recuperación sin tope, un resultado de herramienta pegado entero. El arreglo es un límite en las llamadas grandes, no reescribir el prompt que mandan todas.',
1027
+ inputEven: (label, model, p50, p95, usd) =>
1028
+ `${label} en ${model} es pareja: la mitad de sus llamadas caben en ${p50} tokens de entrada y el 95% en ${p95}, sobre ${usd} de gasto de entrada.`,
1029
+ inputEvenAdvice: () =>
1030
+ 'Las llamadas grandes no son mucho mayores que la normal, así que no hay cola que capar — el prompt simplemente es grande. Las palancas son menos documentos recuperados, un bloque de sistema más corto y caché si el prefijo se repite.',
1031
+ inputHuge: (label, model, calls, usd) =>
1032
+ `${label} en ${model}: cada una de sus ${calls} es mayor de lo que esta herramienta mide con precisión, sobre ${usd} de gasto de entrada. No se nombra techo porque no hay ninguno que nombrar con honestidad — ese tamaño es ya el hallazgo.`,
1033
+ repeatsHeading: () => 'La misma petición, otra vez',
1034
+ repeatsFound: (label, model, repeats, checked, seconds, usd) =>
1035
+ `${label} en ${model}: ${repeats} de ${checked} llamadas reenviaron el tamaño de entrada exacto de la llamada anterior en menos de ${seconds} segundos, en la misma conversación, costando ${usd}.`,
1036
+ repeatsAdvice: () =>
1037
+ 'La entrada de una conversación crece en cada turno, así que el mismo tamaño dos veces seguidas con segundos de diferencia suele ser un reintento tras un timeout, un paso de agente que se repite o un bucle — esto lee recuentos y no puede ver el contenido, así que nombra el patrón y para. Sea lo que sea, ese dinero no compró nada que la llamada anterior no hubiera pagado ya.',
1038
+ inputMostlyCached: (share) =>
1039
+ `El ${share} de esos tokens fueron lecturas de caché, facturadas a una décima parte de la tarifa de entrada — el tamaño es real y la mayor parte es barata.`,
1040
+ inputFullRate: () =>
1041
+ 'Casi nada de eso fue lectura de caché, así que cada uno de esos tokens se facturó a tarifa de entrada completa. Si algún prefijo se repite entre estas llamadas, la caché es la palanca con el techo más alto aquí.',
1042
+ leversNone: () =>
1043
+ 'Aquí no hay nada que llegue al 1% de la factura: estas llamadas ya van al modelo más barato de su familia, o su proveedor no tiene Batch API. Eso es una respuesta de verdad, no una sección vacía.',
1044
+ assumedWriteTtl: (calls) =>
1045
+ `${count(calls)} ${calls === 1 ? 'llamada no dijo' : 'llamadas no dijeron'} qué TTL de escritura de caché se usó, así que se asumió la tarifa más barata, la de 5 minutos. Una entrada de 1 hora cuesta 2x la entrada en vez de 1.25x, así que este total es un suelo para esas llamadas. Registra el objeto "cache_creation" que devuelve la API para quitar la suposición.`,
1046
+ spanLine: (from, to, days) =>
1047
+ `Este registro abarca ${from} → ${to} (${days} días). El periodo se declara, nunca se extrapola: la aritmética mensual es tuya, y ahora es válida.`,
1048
+ spanPartial: (withTs, total) =>
1049
+ `Solo ${withTs} de ${total} llamadas llevan marca de tiempo; el periodo describe esas.`,
1050
+ ttlFitExpires: (label, model, gap) =>
1051
+ `${label} en ${model}: los turnos llegan con una mediana de ${gap} entre sí y la entrada de 5 minutos ya no existe para entonces — las escrituras caducan antes de que el siguiente turno las lea, que visto desde la factura es una caché que solo escribe. El TTL de 1 hora cuesta 2x la entrada al escribir y sobreviviría a estos huecos; la otra opción honesta es apagar la caché aquí.`,
1052
+ ttlFitExpiresBoth: (label, model, gap) =>
1053
+ `${label} en ${model}: los turnos llegan con una mediana de ${gap} entre sí, y ninguna entrada de caché vive tanto — hasta el TTL de 1 hora ha caducado para el siguiente turno. La caché no puede funcionar a este ritmo; apágala aquí y deja de pagar la prima de escritura.`,
1054
+ ttlFitOverlong: (label, model, gap, usd) =>
1055
+ `${label} en ${model}: los turnos llegan con una mediana de ${gap} entre sí — de sobra dentro de la ventana de 5 minutos — y estas escrituras pagan la tarifa de 1 hora, 2x la entrada frente a 1.25x, por una resistencia que los huecos nunca usan. Las mismas escrituras con el TTL de 5 minutos salen ${usd} más baratas en este registro, y esa cifra es exacta: los mismos tokens a la otra tarifa publicada.`,
1056
+ ttlFitUnsettledGap: (label, model, gap) =>
1057
+ `${label} en ${model}: los turnos llegan con una mediana de ${gap} entre sí — una entrada de 5 minutos ya no existe para entonces y una de 1 hora sobrevive — y el registro no anotó cuáles eran estas escrituras, así que no se puede resolver si alguna vez se leen de vuelta. Registra el objeto "cache_creation" que devuelve la API y se resuelve solo.`,
1058
+ ttlFitFits: (label, model, gap) =>
1059
+ `${label} en ${model}: los turnos llegan con una mediana de ${gap} entre sí, dentro de la vida útil que usan estas escrituras. El TTL no es el problema aquí.`,
1060
+ ttlFitUnmeasured: () =>
1061
+ 'No se pudo medir si el TTL de la caché encaja con el ritmo de los turnos: hacen falta "session" y "ts" en el registro. Una entrada de 5 minutos en una carga cuyos turnos llegan cada nueve caduca sin leerse en cada escritura, y solo el reloj puede verlo. Trazum agrupa por la sesión y nunca la muestra.',
1062
+ dayPeak: (day, usd, xMedian) =>
1063
+ `El día más caro de este registro fue ${day}: ${usd}, ${xMedian}x el día mediano.`,
1064
+ dayPeakLabel: (label, usd) => `Casi todo fue ${label} (${usd}).`,
1065
+ maxUsdOk: (total, max) => `Dentro del presupuesto: ${total} gastados contra --max-usd ${max}.`,
1066
+ maxUsdFailed: (total, max) =>
1067
+ `FALLO — este registro gastó ${total} contra un --max-usd de ${max}. Las cifras son los recuentos facturados por el proveedor sobre exactamente este registro; no se asumió ningún periodo.`,
1068
+ maxGrowthUsdFailed: (delta, max) =>
1069
+ `FALLO — la factura creció ${delta} respecto al registro anterior, por encima del límite --max-growth-usd de ${max}.`,
1070
+ maxGrowthNeedsAgainst: () =>
1071
+ '--max-growth-usd no tiene con qué comparar sin --against <anterior.jsonl>. Por sí solo habría corrido en silencio sin vigilar nada, y eso no es una respuesta.',
1072
+ maxCacheLossOk: (worst, max) =>
1073
+ `Caché dentro del presupuesto: cachear costó como mucho ${worst} contra --max-cache-loss-usd ${max}, peor caso incluido.`,
1074
+ maxCacheLossFailed: (delta, max) =>
1075
+ `FALLO — cachear añadió ${delta} a esta factura (los mismos tokens como entrada normal habrían costado menos), por encima del límite --max-cache-loss-usd de ${max}. El contrafactual es exacto: los mismos tokens a la tarifa de entrada publicada.`,
1076
+ maxDayOk: (day, usd, max) =>
1077
+ `Ningún día por encima del presupuesto: el peor fue ${day} con ${usd}, contra --max-day-usd ${max}.`,
1078
+ maxDayFailed: (day, usd, max) =>
1079
+ `FALLO — ${day} gastó ${usd}, por encima del límite --max-day-usd de ${max}. Un total dentro de presupuesto puede esconder un solo día desbocado, que es justo lo que vigila esta puerta.`,
1080
+ maxDayNoClock: () =>
1081
+ 'FALLO — se pidió --max-day-usd y ningún registro de este fichero lleva marca de tiempo, así que no hay días que juzgar. Eso no es un aprobado: una factura que nadie pudo medir por días no es una factura que se mantuvo bajo un presupuesto diario. Añade "ts" al registro y la puerta se arma.',
1082
+ maxDayUndated: (calls) =>
1083
+ `${calls} llamadas no llevan marca de tiempo, así que están en la factura y en ninguno de los días de arriba — el peor día es un suelo por lo que valieran esas llamadas. Un fallo se sostendría igual; este aprobado cubre la parte que se pudo fechar.`,
1084
+ maxCacheLossWorstCase: (calls, worst, max) =>
1085
+ `FALLO — ${count(calls)} ${calls === 1 ? 'llamada no registró' : 'llamadas no registraron'} qué TTL de escritura se pagó, y a la tarifa de 1 hora cachear añadió hasta ${worst}, por encima del límite --max-cache-loss-usd de ${max}. La puerta lee el peor caso a propósito: una puerta que leyera la mitad halagadora dejaría pasar exactamente las facturas que existe para cazar. Registra el objeto "cache_creation" que devuelve la API y el techo se vuelve una cifra.`,
1086
+ pricesStale: (date, days) =>
1087
+ `La tabla de precios detrás de cada dólar de aquí se revisó por última vez el ${date} — hace ${count(days)} días, más de los 45 que esta herramienta considera vigentes. Si el proveedor cambió precios desde entonces, este informe se equivoca exactamente en ese cambio. --pricing-live trae los precios de hoy; --pricing superpone los tuyos.`,
1088
+ dayTableDay: () => 'Día (UTC)',
1089
+ dayTableCalls: () => 'llamadas',
1090
+ dayTableTop: () => 'lo más caro del día',
1091
+ dayTableEarlier: (days) =>
1092
+ `…y ${count(days)} ${days === 1 ? 'día anterior que no se muestra' : 'días anteriores que no se muestran'} aquí. La serie completa va en --json como spendByDay.`,
1093
+ gateOnFloor: (reasons) =>
1094
+ `Aviso: la cifra vigilada es un suelo, no la factura — ${reasons}. Lo que costaran esas llamadas no está en el número que la puerta acaba de juzgar, así que pasar aquí significa "cabe la parte que pude leer", nunca "cabe la factura".`,
1095
+ floorSkipped: (lines) =>
1096
+ `${count(lines)} ${lines === 1 ? 'línea resultó ilegible y quedó fuera' : 'líneas resultaron ilegibles y quedaron fuera'}`,
1097
+ floorUnpriced: (calls) =>
1098
+ `${count(calls)} ${calls === 1 ? 'llamada usa un modelo' : 'llamadas usan modelos'} que la tabla de precios no conoce`,
1099
+ floorUndated: (calls) =>
1100
+ `${count(calls)} ${calls === 1 ? 'llamada no lleva marca de tiempo y cayó' : 'llamadas no llevan marca de tiempo y cayeron'} fuera de la ventana`,
1101
+ sessionCost: (label, model, sessions, median, medianTurns, p95, max) =>
1102
+ `${label} en ${model}: en ${sessions} conversaciones, la mediana cuesta ${median} a lo largo de ${medianTurns} turnos, el 95% queda por debajo de ${p95} y la más cara fue ${max}. Recuentos facturados exactos, por conversación — la cifra con la que se fija un precio por asiento o una cuota. Una conversación que empezó antes de este registro o sigue después solo cuenta por los turnos aquí registrados.`,
1103
+ labelBudgetOk: (label, usd, max) =>
1104
+ `Dentro del presupuesto: ${label} gastó ${usd} contra ${max}.`,
1105
+ labelBudgetFailed: (label, usd, max) =>
1106
+ `FALLO — ${label} gastó ${usd} contra su presupuesto de ${max} en trazum.config.json.`,
1107
+ labelBudgetMissing: (label) =>
1108
+ `${label} tiene presupuesto en trazum.config.json y ninguna llamada en este registro, así que no se midió nada para esa carga. No es un aprobado: una carga que no apareció no es una carga que quedó por debajo del presupuesto.`,
1109
+ labelBudgetWindowed: () =>
1110
+ 'Los presupuestos por etiqueta de trazum.config.json no se aplicaron: --since/--until hacen que "lo que gastó esta etiqueta" signifique una porción, y un presupuesto escrito para el periodo completo estaría vigilando algo que no describe.',
1111
+ duplicateLines: (calls, usd) =>
1112
+ `${plural(calls, 'línea es un duplicado exacto', 'líneas son duplicados exactos')} de una línea anterior — mismos recuentos, misma etiqueta y sesión, mismo milisegundo — y eso suma ${usd} al total de arriba. Si un registro se exportó dos veces o dos ficheros del directorio se solapan, esta factura está inflada en esa cantidad. Que dos llamadas reales coincidan en todo eso es posible; solo que improbable.`,
1113
+ budgetVsWire: (label, file, budget, perCall, share) =>
1114
+ `El presupuesto de ${file} son ${budget} tokens, y las llamadas con etiqueta ${label} llevan unos ${perCall} tokens de entrada cada una — así que esa puerta vigila alrededor del ${share} de lo que realmente sale por el cable. El resto es contexto recuperado, historial de conversación y resultados de herramientas, que ningún fichero de prompt contiene y ningún presupuesto sobre uno puede ver. El presupuesto no está mal; simplemente es más pequeño que la factura.`,
1115
+ badCsvShape: (value) =>
1116
+ `--csv-shape no conoce "${value}". Acepta "slice" (una fila por etiqueta y modelo, el valor por defecto), "day" u "hour".`,
1117
+ whatIfHeading: (model) => `Estas mismas llamadas en ${model}`,
1118
+ whatIfAssumption: () =>
1119
+ 'Esto es una multiplicación, no un consejo: los mismos recuentos de tokens con otra tarifa. No dice nada sobre si ese modelo podría hacer el trabajo, y un modelo que responda más largo o al que haya que reintentar no enviaría estos recuentos.',
1120
+ whatIfTotal: (current, target, delta) =>
1121
+ `${current} de gasto movible habrían sido ${target} — una diferencia de ${delta}.`,
1122
+ whatIfCheaper: () =>
1123
+ 'Compruébalo antes de mover nada: trazum route mide un prompt contra ambos modelos con tus propios ejemplos.',
1124
+ whatIfDearer: () => 'Esa dirección cuesta más. La aritmética está aquí para que el número no sea una suposición.',
1125
+ whatIfSlice: (label, model, current, target) => `${label} en ${model}: ${current} → ${target}`,
1126
+ whatIfOverContext: (label, tokens, window, usd) =>
1127
+ `${label} no puede moverse: su llamada más grande lleva ${tokens} tokens de entrada y la ventana de ese modelo es de ${window}. Esas llamadas fallarían, no costarían menos, así que sus ${usd} quedan fuera de las cifras de arriba.`,
1128
+ whatIfAlreadyThere: (calls, usd) =>
1129
+ `Ya en ese modelo: ${calls} por valor de ${usd}, fuera de las cifras de arriba — dinero que no puede moverse haría que la diferencia pareciera menor de lo que es.`,
1130
+ whatIfUnpriced: (calls, models) =>
1131
+ `Fuera de la comparación: ${calls} cuyo modelo no tiene precio aquí (${models}). Su coste en el modelo destino sí se puede calcular; la diferencia no, porque no hay cifra actual de la que restar.`,
1132
+ whatIfNothingToMove: () =>
1133
+ 'Nada que comparar: todas las llamadas con precio de este registro ya están en ese modelo, o son demasiado grandes para su ventana de contexto.',
1134
+ whatIfUnknown: (value, available) =>
1135
+ `--what-if no conoce "${value}". Modelos con precio: ${available}. Añádelo con --pricing si tienes sus tarifas.`,
1136
+ badGzip: (file, detail) =>
1137
+ `${file} está comprimido con gzip y no se ha podido descomprimir: ${detail}. Leer el resto y no decir nada daría una factura a la que le falta lo que hubiera en ese fichero, así que se para aquí. Revisa el fichero, o sácalo del directorio.`,
1138
+ coverageHeading: () => 'Lo que este registro todavía no puede responder',
1139
+ needsLabel: (seen) =>
1140
+ `"label" en ${seen} registros: sin él todas las cargas son una sola fila, así que no hay gasto por carga, ni zoom, y las palancas describen una mezcla en vez de una decisión.`,
1141
+ needsSession: (seen) =>
1142
+ `"session" en ${seen} registros: sin él no hay crecimiento de conversación, ni coste por conversación, ni encaje del TTL de caché. Se agrupa por él y nunca se imprime.`,
1143
+ needsTs: (seen) =>
1144
+ `"ts" en ${seen} registros: sin él el registro no tiene periodo, ni forma por día o por hora, y la pregunta del TTL de caché no se puede ni plantear.`,
1145
+ needsStopReason: (seen) =>
1146
+ `"stop_reason" (Anthropic) o "finish_reason" (OpenAI) en ${seen} registros: sin él las respuestas cortadas en max_tokens son invisibles — y el silencio ahí no es lo mismo que ninguna.`,
1147
+ needsCacheTtl: (seen) =>
1148
+ `el objeto "cache_creation" en ${seen} de los registros que escribieron en caché: sin él se asume la tarifa de 5 minutos, así que esos totales son un suelo y algunos veredictos de caché no se pueden cerrar.`,
1149
+ hoursConcentrated: (hours, list) =>
1150
+ `El 80% de este gasto cae en ${hours} horas del día UTC (${list}) — tráfico interactivo con alguien esperando, donde las 24 horas de la Batch API no encajan. Las horas son UTC; desplázalas tú si tu tráfico está en una sola región.`,
1151
+ hoursFlat: (hours) =>
1152
+ `El gasto está repartido por el día: hacen falta ${hours} horas del día UTC para cubrir el 80%. Esa es la forma del trabajo de fondo, y el trabajo de fondo es justo lo que la Batch API abarata a la mitad — mira las palancas de arriba para ver cuánto valdría aquí. Si estas llamadas pueden esperar lo dices tú; el registro solo enseña cuándo ocurrieron.`,
1153
+ truncatedBy: (label, calls, measured, rate, usd) =>
1154
+ `${label}: ${calls} de ${measured} llamadas que registraron motivo de parada quedaron cortadas (${rate}), ${usd} de salida. El denominador son las llamadas que midieron, no todas — una carga que registra el campo la mitad de las veces no es una carga cuya otra mitad terminó.`,
1155
+ truncatedCeiling: (p95) =>
1156
+ `El 95% de las respuestas que sí terminaron cabe en ${p95} tokens de salida, así que un tope por ahí dejaría de cortarlas. Medido en estas llamadas, prometido para ninguna.`,
1157
+ readFiles: (files, directory) =>
1158
+ `Leídos ${count(files)} ficheros de registro de ${directory}, en orden de nombre, como una sola factura. Todas las cifras de abajo los cubren todos.`,
1159
+ noLogsInDirectory: (directory, extensions) =>
1160
+ `No hay registros de uso en "${directory}". Se buscaron ficheros terminados en ${extensions}. Un directorio sin nada legible es un error, no un informe vacío, que se leería como "no has gastado nada".`,
1161
+ sessionCostTail: (ratio) =>
1162
+ `El percentil 95 es ${ratio}x la mediana ahí: casi todas las conversaciones son baratas y unas pocas no, y esa es una cola que una cuota puede cazar. Cuando mediana y p95 quedan cerca, la carga es cara sin más y no hay cola que perseguir.`,
1163
+ againstOverlap: (from, to) =>
1164
+ `Estos dos registros cubren ambos ${from} → ${to}, así que algunas de las mismas llamadas están a los dos lados de esta resta y parte del cambio es el mismo dinero contado dos veces. Compara periodos que no se solapen — o acota ambos registros con --since/--until.`,
1165
+ windowLine: (since, until) =>
1166
+ `Filtrado con --since ${since} --until ${until}. Todo lo de abajo describe esta ventana, no el registro completo; una fecha sola significa ese día UTC entero.`,
1167
+ windowUndated: (calls) =>
1168
+ `${count(calls)} ${calls === 1 ? 'llamada no lleva' : 'llamadas no llevan'} marca de tiempo y no se ${calls === 1 ? 'puede situar' : 'pueden situar'} dentro o fuera de esta ventana, así que ${calls === 1 ? 'quedó fuera' : 'quedaron fuera'}. Su gasto está en el registro y no en este informe — las cifras de la ventana son un suelo del periodo.`,
1169
+ windowRelative: () =>
1170
+ 'Esa ventana es relativa al reloj de esta máquina, no al último registro del log — un registro exportado hace un mes responderá "los últimos 7 días" con nada.',
1171
+ windowRelativeEmpty: () =>
1172
+ 'Una ventana relativa se mide desde el reloj de esta máquina: si este registro se exportó antes, pide las fechas que cubre.',
1173
+ windowNeedsClock: () =>
1174
+ 'Ningún registro lleva marca de tiempo, así que --since/--until no tienen por qué filtrar. Una ventana temporal sobre un registro sin reloj no vigilaría nada, y eso no es una respuesta. Añade "ts" a los registros — la receta del README dice dónde.',
1175
+ windowMatchesNothing: (from, to) =>
1176
+ `Ningún registro cae dentro de esta ventana. El registro cubre ${from} → ${to}. Una ventana que no encuentra nada no debe volverse un informe de $0 — bajo --max-usd pasaría una puerta de presupuesto sobre un periodo que el registro no cubre.`,
1177
+ sinceAfterUntil: () =>
1178
+ '--since es igual o posterior a --until, así que la ventana no contiene tiempo alguno. Revisa las dos fechas.',
1179
+ badWhen: (flag, value) =>
1180
+ `--${flag} no pudo leer "${value}". Acepta un día UTC (2026-08-14) o una marca ISO 8601 completa (2026-08-14T09:30:00Z).`,
1181
+ singleTurnCeiling: (label, model, single, sessions, usd) =>
1182
+ `${label} en ${model}: ${single} de ${sessions} conversaciones terminaron tras su primer turno, y sus escrituras de caché — ${usd} — pagaron una reutilización que su propia conversación nunca hizo. Otra conversación con el mismo prefijo dentro del TTL pudo haberlas leído; el registro no puede ver de quién era la escritura que una lectura encontró, así que esa cifra es un techo del desperdicio, no una factura.`,
1183
+ singleTurnConfirmed: (label, model, single, sessions, usd) =>
1184
+ `${label} en ${model}: ${single} de ${sessions} conversaciones terminaron tras su primer turno y gastaron ${usd} escribiendo una caché que nada en este registro leyó jamás. Dentro de la conversación, entre conversaciones — ninguna lectura en ningún sitio, así que esas escrituras no compraron nada. Cachear una llamada de un solo uso es puro sobreprecio de escritura; deja de marcar estas llamadas con cache_control.`,
1185
+ },
1186
+
1187
+ route: {
1188
+ noTarget: () =>
1189
+ 'Apunta esto a un registro de uso y a un prompt: trazum route usage.jsonl --prompt-file prompts/soporte.txt --cases casos.txt --yes. Busca la porción que más vale y mide si el modelo más barato sigue haciendo el trabajo. El flag es --prompt-file y no --prompt, porque en el resto de la herramienta --prompt nombra un prompt marcado dentro de un fichero fuente.',
1190
+ needsPrompt: () =>
1191
+ '--prompt y --cases son los dos obligatorios. El registro dice qué ruta vale dinero; solo el prompt y los casos pueden decir si funciona.',
1192
+ labelNotFound: (label, available) =>
1193
+ `Ninguna llamada de este registro lleva el label "${label}". Los labels que hay son: ${available}.`,
1194
+ noRoute: () =>
1195
+ 'Ninguna ruta de este registro llega al 1% de la factura. Estas llamadas ya van al modelo más barato de su familia, o el catálogo no tiene nada por debajo.',
1196
+ picked: (label, model, candidate, usd, pct) =>
1197
+ `${label} en ${model} → ${candidate}, vale ${usd} de esta factura (${pct}).`,
1198
+ willSpend: (calls, model, candidate) =>
1199
+ `Esto hará ${count(calls)} llamadas al proveedor: dos por caso en ${model} para medir su propia varianza, una por caso en ${candidate}. Todavía no se ha gastado nada — añade --yes para ejecutarlo.`,
1200
+ dryRun: () => 'No se llamó a nada.',
1201
+ running: (cases) => `Ejecutando ${count(cases)} casos...`,
1202
+ agreement: (cross, self) =>
1203
+ `El modelo más barato coincide con el original el ${cross} de las veces. El original coincide consigo mismo el ${self} — esa es la vara de medir, no el 100%.`,
1204
+ holds: (usd) =>
1205
+ `AGUANTA — la diferencia está dentro del propio ruido del modelo original. En esta factura esa ruta vale ${usd}.`,
1206
+ diverges: (usd) =>
1207
+ `DIVERGE — el modelo más barato da respuestas materialmente distintas. Los ${usd} son reales y el cambio de comportamiento también; esto no es dinero gratis.`,
1208
+ inconclusive: () =>
1209
+ 'NO CONCLUYENTE — el modelo original fue demasiado inconsistente consigo mismo en estos casos como para juzgar nada contra eso. Añade casos, o elige unos con menos margen para que el modelo divague.',
1210
+ unlabelledSlice: () =>
1211
+ 'Estas llamadas no llevan label, así que Trazum no puede saber si son todas este prompt. Si no lo son, la cifra de arriba cubre llamadas que esta medición no tocó — añade "label" al registro y la porción pasa a ser una sola carga, que es lo que hace la cifra atribuible.',
1212
+ yours: () =>
1213
+ 'Coincidir no es acertar. Esto mide si las respuestas se movieron, no si alguna vez fueron correctas — la decisión sigue siendo tuya.',
1214
+ },
1215
+
814
1216
  baseline: {
815
1217
  recorded: (path, files, tokens) =>
816
1218
  `Registrados ${files} prompts, ${tokens} tokens, en ${path}. Haz commit: la puerta compara el \u00e1rbol con lo que est\u00e9 commiteado.`,