boosterarena-mcp 0.2.0 → 0.2.2

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/dist/nucleo.mjs CHANGED
@@ -1711,6 +1711,51 @@ var FORMA_DEF = {
1711
1711
  blob: { anch: 26, alto: "cuadrado", color: "#c4b5fd" }
1712
1712
  };
1713
1713
 
1714
+ // ../src/shared/fontsAncho.ts
1715
+ var ANCHO_LETRA = {
1716
+ "Poppins": { 100: [712, 689], 200: [724, 692], 300: [736, 696], 400: [747, 699], 500: [760, 703], 600: [770, 708], 700: [782, 715], 800: [793, 723], 900: [805, 731] },
1717
+ "Montserrat": { 100: [816, 695], 200: [817, 698], 300: [819, 702], 400: [821, 707], 500: [824, 713], 600: [828, 720], 700: [833, 728], 800: [838, 735], 900: [844, 744] },
1718
+ "Nunito": { 200: [754, 595], 300: [760, 602], 400: [766, 610], 500: [769, 614], 600: [772, 618], 700: [780, 627], 800: [789, 638], 900: [798, 649] },
1719
+ "Quicksand": { 300: [738, 625], 400: [736, 631], 500: [735, 638], 600: [734, 644], 700: [732, 650] },
1720
+ "Inter": { 100: [741, 617], 200: [748, 624], 300: [756, 630], 400: [763, 637], 500: [770, 645], 600: [777, 653], 700: [785, 661], 800: [797, 672], 900: [810, 684] },
1721
+ "Roboto": { 100: [696, 586], 200: [697, 588], 300: [698, 591], 400: [702, 596], 500: [703, 596], 600: [704, 596], 700: [704, 596], 800: [705, 596], 900: [706, 597] },
1722
+ "Open Sans": { 300: [717, 611], 400: [737, 635], 500: [744, 644], 600: [752, 654], 700: [769, 675], 800: [789, 696] },
1723
+ "Lato": { 100: [759, 553], 300: [761, 567], 400: [768, 585], 700: [776, 601], 900: [785, 616] },
1724
+ "Raleway": { 100: [756, 625], 200: [756, 629], 300: [756, 634], 400: [756, 640], 500: [756, 646], 600: [756, 652], 700: [757, 658], 800: [758, 665], 900: [761, 674] },
1725
+ "Work Sans": { 100: [692, 611], 200: [701, 618], 300: [716, 632], 400: [738, 651], 500: [742, 654], 600: [746, 657], 700: [751, 661], 800: [757, 666], 900: [763, 671] },
1726
+ "Rubik": { 300: [685, 613], 400: [699, 629], 500: [716, 647], 600: [724, 656], 700: [732, 665], 800: [747, 681], 900: [762, 698] },
1727
+ "Manrope": { 200: [694, 604], 300: [703, 613], 400: [713, 622], 500: [723, 632], 600: [733, 641], 700: [743, 650], 800: [753, 660] },
1728
+ "Archivo": { 100: [764, 570], 200: [764, 577], 300: [763, 586], 400: [763, 597], 500: [763, 607], 600: [763, 619], 700: [779, 639], 800: [804, 671], 900: [840, 714] },
1729
+ "Oswald": { 200: [536, 425], 300: [543, 441], 400: [556, 468], 500: [579, 498], 600: [592, 515], 700: [602, 527] },
1730
+ "Kanit": { 100: [664, 582], 200: [674, 593], 300: [686, 604], 400: [695, 613], 500: [704, 622], 600: [712, 632], 700: [726, 644], 800: [736, 655], 900: [750, 670] },
1731
+ "Fredoka": { 300: [747, 602], 400: [741, 598], 500: [735, 595], 600: [729, 593], 700: [723, 592] },
1732
+ "Baloo 2": { 400: [661, 576], 500: [666, 586], 600: [672, 594], 700: [679, 605], 800: [685, 615] },
1733
+ "Comfortaa": { 300: [817, 708], 400: [823, 707], 500: [827, 707], 600: [830, 706], 700: [834, 705] },
1734
+ "Bebas Neue": { 400: [429, 429] },
1735
+ "Anton": { 400: [524, 534] },
1736
+ "Oswald Title": { 200: [536, 425], 300: [543, 441], 400: [556, 468], 500: [579, 498], 600: [592, 515], 700: [602, 527] },
1737
+ "Righteous": { 400: [708, 614] },
1738
+ "Titan One": { 400: [764, 719] },
1739
+ "Lilita One": { 400: [695, 573] },
1740
+ "Luckiest Guy": { 400: [667, 670] },
1741
+ "Bangers": { 400: [483, 482] },
1742
+ "Lobster": { 400: [689, 505] },
1743
+ "Abril Fatface": { 400: [754, 632] },
1744
+ "Playfair Display": { 400: [727, 604], 500: [741, 611], 600: [755, 618], 700: [770, 625], 800: [784, 632], 900: [799, 639] },
1745
+ "Merriweather": { 300: [765, 652], 400: [774, 662], 500: [776, 665], 600: [779, 669], 700: [789, 678], 800: [794, 684], 900: [798, 688] },
1746
+ "Lora": { 400: [768, 621], 500: [773, 624], 600: [778, 628], 700: [784, 632] },
1747
+ "Roboto Slab": { 100: [743, 634], 200: [749, 638], 300: [754, 641], 400: [770, 650], 500: [779, 657], 600: [783, 659], 700: [787, 662], 800: [793, 665], 900: [800, 669] },
1748
+ "Cormorant Garamond": { 300: [738, 523], 400: [739, 524], 500: [742, 527], 600: [743, 529], 700: [746, 532] },
1749
+ "EB Garamond": { 400: [768, 533], 500: [776, 547], 600: [783, 561], 700: [792, 575], 800: [797, 584] },
1750
+ "Bitter": { 100: [776, 621], 200: [774, 623], 300: [773, 626], 400: [770, 633], 500: [769, 638], 600: [767, 642], 700: [765, 646], 800: [762, 652], 900: [760, 659] },
1751
+ "Caveat": { 400: [563, 420], 500: [565, 422], 600: [567, 424], 700: [569, 426] },
1752
+ "Pacifico": { 400: [913, 520] },
1753
+ "Patrick Hand": { 400: [557, 452] },
1754
+ "Dancing Script": { 400: [677, 462], 500: [690, 466], 600: [703, 471], 700: [717, 475] },
1755
+ "Permanent Marker": { 400: [755, 628] },
1756
+ "Shadows Into Light": { 400: [592, 479] }
1757
+ };
1758
+
1714
1759
  // ../src/registro/esquemas.ts
1715
1760
  var texto = (que, o = {}) => ({ tipo: "texto", que, ...o });
1716
1761
  var html = (que) => ({ tipo: "html", que });
@@ -2693,6 +2738,19 @@ function pesoReal(family, peso) {
2693
2738
  if (!w?.length) return peso;
2694
2739
  return w.reduce((a, c) => Math.abs(c - peso) < Math.abs(a - peso) ? c : a, w[0]);
2695
2740
  }
2741
+ function emsDeTexto(family, peso, texto2, caps, tracking) {
2742
+ const nombre = FONTS.find((f) => f.family === family)?.name;
2743
+ const fila = nombre ? ANCHO_LETRA[nombre] : void 0;
2744
+ let par = [850, 720];
2745
+ if (fila) {
2746
+ const pesos = Object.keys(fila).map(Number);
2747
+ par = fila[pesos.reduce((a, c) => Math.abs(c - peso) < Math.abs(a - peso) ? c : a, pesos[0])];
2748
+ }
2749
+ return Math.max(0, ...(caps ? texto2.toUpperCase() : texto2).split("\n").map((linea2) => {
2750
+ const letras = [...linea2];
2751
+ return letras.reduce((t, ch) => t + (ch !== ch.toLowerCase() ? par[0] : par[1]) / 1e3, 0) + letras.length * tracking;
2752
+ }));
2753
+ }
2696
2754
  var esMoneda = (el) => el.kind === "widget" && el.w <= 9;
2697
2755
  var COLOR_DE_FONDO = /^(#[0-9a-f]{3,8}|rgba?\([^)]*\)|hsla?\([^)]*\)|(linear|radial)-gradient\(.*\))$/i;
2698
2756
  var escenaDe = (b) => b.doc[Math.min(b.escena, b.doc.length - 1)];
@@ -2713,6 +2771,12 @@ function capaPorNombre(b, ref) {
2713
2771
  const id = b.aliasCapa.get(ref);
2714
2772
  return s.layers.find((l) => l.id === id) ?? s.layers.find((l) => l.name.toLowerCase() === ref.toLowerCase()) ?? s.layers.find((l) => l.name.toLowerCase().includes(ref.toLowerCase()));
2715
2773
  }
2774
+ function slideDelEl(b, el) {
2775
+ for (const sc of b.doc) for (const sl of sc.slides) {
2776
+ if (sl.base.some((e) => e.id === el.id) || sl.layers.some((l) => l.els.some((e) => e.id === el.id))) return sl;
2777
+ }
2778
+ return void 0;
2779
+ }
2716
2780
  function crearCapa(b, ref) {
2717
2781
  const s = slideDe(b);
2718
2782
  const capa = { id: nid("capa"), name: ref || `Capa ${s.layers.length + 1}`, els: [] };
@@ -2806,6 +2870,15 @@ function armarAccion(b, a, el) {
2806
2870
  return { accion: { type: "resetSequence" } };
2807
2871
  case "abrirCapa": {
2808
2872
  const ref = txt(a.capa).trim();
2873
+ if (b.estricto) {
2874
+ const sl = slideDelEl(b, el) ?? s;
2875
+ const id = b.aliasCapa.get(ref);
2876
+ const capa2 = sl.layers.find((l) => l.id === id) ?? sl.layers.find((l) => l.name.trim().toLowerCase() === ref.toLowerCase());
2877
+ if (!capa2) {
2878
+ return { accion: null, fallo: `${ref ? `No hay ninguna capa "${ref}"` : "Falta `capa`"} en el slide de ese objeto, y no la creo: un nombre mal escrito dejar\xEDa un bot\xF3n que abre una capa en blanco. ${sl.layers.length ? `Las que hay: ${sl.layers.map((l) => `\xAB${l.name}\xBB`).join(", ")}.` : "Ese slide no tiene capas."} Cr\xE9ala antes con \`nueva_capa\` o poniendo algo dentro con \`capa\`.` };
2879
+ }
2880
+ return { accion: { type: "showLayer", layerId: capa2.id } };
2881
+ }
2809
2882
  const capa = capaPorNombre(b, ref) ?? crearCapa(b, ref);
2810
2883
  return { accion: { type: "showLayer", layerId: capa.id } };
2811
2884
  }
@@ -2938,6 +3011,15 @@ function ejecutar(b, nombre, args, fab) {
2938
3011
  const puestos = [];
2939
3012
  tpl.layers.forEach((l, i) => {
2940
3013
  const dice = textos[i]?.trim() ? textos[i] : i === protagonista && unico.trim() ? unico : l.text;
3014
+ const f0 = (l.fontSize ?? 0) * escala;
3015
+ let k = 1;
3016
+ if (l.fontSize && dice !== l.text) {
3017
+ const peso = l.weight ?? 800;
3018
+ const nuevo = emsDeTexto(l.font, peso, dice, !!l.caps, l.tracking ?? 0);
3019
+ const viejo = emsDeTexto(l.font, peso, l.text, !!l.caps, l.tracking ?? 0);
3020
+ const anchoPx = l.w / 100 * (caja.w / 100) * 1920 * 0.97;
3021
+ if (nuevo * f0 > anchoPx) k = Math.min(1, Math.max(anchoPx / (nuevo * f0), nuevo > viejo ? viejo / nuevo : 1));
3022
+ }
2941
3023
  const el = {
2942
3024
  id: nid(`tt${i}`),
2943
3025
  kind: "text",
@@ -2948,7 +3030,7 @@ function ejecutar(b, nombre, args, fab) {
2948
3030
  h: +(l.h / 100 * caja.h).toFixed(2),
2949
3031
  text: dice,
2950
3032
  ...l.font ? { font: l.font } : {},
2951
- ...l.fontSize ? { fontSize: Math.round(l.fontSize * escala) } : {},
3033
+ ...l.fontSize ? { fontSize: Math.max(10, Math.floor(f0 * k)) } : {},
2952
3034
  ...l.color ? { color: l.color } : {},
2953
3035
  ...l.weight ? { weight: l.weight } : {},
2954
3036
  ...l.tracking ? { tracking: l.tracking } : {},
@@ -2967,7 +3049,7 @@ function ejecutar(b, nombre, args, fab) {
2967
3049
  const r = refBase ? i === protagonista ? refBase : `${refBase}_${i + 1}` : "";
2968
3050
  plantar(b, el, r, args.capa);
2969
3051
  if (refBase) b.alias.set(`${refBase}_${i + 1}`, el.id);
2970
- puestos.push(`${i + 1}. \xAB${dice}\xBB (id ${el.id})`);
3052
+ puestos.push(`${i + 1}. \xAB${dice}\xBB (id ${el.id})${k < 1 ? ` \u2014 letra ajustada de ${Math.round((l.fontSize ?? 0) * escala)} a ${el.fontSize} para que quepa en su ancho${l.curve ? "; va en ARCO y puede no caber igual, m\xEDralo" : ""}` : ""}`);
2971
3053
  });
2972
3054
  b.bitacora.push(`\u2712\uFE0F Plantilla \xAB${tpl.name}\xBB`);
2973
3055
  return `Plantilla "${tpl.name}" puesta, ${tpl.layers.length} rengl\xF3n(es) agrupados:
@@ -3829,7 +3911,7 @@ var ARENAS_APP = [
3829
3911
  { type: "las-vegas", emoji: "\u{1F3B0}", label: "The Fabulous Las Vegas", blurb: "Tragamonedas para construir frases: jala la palanca, los rodillos frenan uno a uno y sale una combinaci\xF3n \u2014SHE \xB7 WRITTEN \xB7 A LETTER\u2014 que la clase dice en voz alta. De 1 a 4 rodillos, y cada casilla puede ser una palabra o un dibujo.", premium: false, ready: true },
3830
3912
  // Sentence Line (app-native): la hermana de Word Factory — mismo motor, pero la pieza es una
3831
3913
  // PALABRA y lo que se arma es una oración.
3832
- { type: "sentenceline", emoji: "\u{1F529}", label: "Sentence Line", blurb: "Ensambla la oraci\xF3n: las palabras llegan revueltas en la bandeja y el alumno las va poniendo en la barra hasta que la frase queda bien; entonces la m\xE1quina la SUELDA y sale entera. Se parte sola por los espacios, o marcas trozos con barras \u2014\xABThe black cat / is sleeping / on the sofa\xBB\u2014 para ense\xF1ar la estructura.", premium: false, ready: true },
3914
+ { type: "sentenceline", emoji: "\u{1F309}", label: "Sentence Bridge", blurb: "El puente de tablones: cada palabra es un tabl\xF3n y hay que ponerlas en orden para armar el puente. El explorador salta al tabl\xF3n que acabas de poner, y si la oraci\xF3n no es, los tablones equivocados se caen al r\xEDo. Cuando la frase queda bien, cruza al otro lado. Se parte sola por los espacios, o marcas trozos con barras \u2014\xABThe black cat / is sleeping / on the sofa\xBB\u2014 para ense\xF1ar la estructura.", premium: false, ready: true },
3833
3915
  // Word Factory (app-native): deletrear ordenando las letras que llegan por la cinta.
3834
3916
  { type: "wordfactory", emoji: "\u{1F3ED}", label: "Word Factory", blurb: "Una f\xE1brica de palabras: entra un dibujo y sus letras llegan revueltas por la cinta transportadora. El alumno las mete en la m\xE1quina en el orden correcto y, cuando la palabra sale bien, la m\xE1quina fabrica el objeto y lo sella. Sirve para cualquier vocabulario: sube tu dibujo y escribe su palabra.", premium: false, ready: true },
3835
3917
  // Mazo de Cartas (app-native): reemplaza la actividad de cartas de Wordwall. El maestro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boosterarena-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "El MCP de Booster: tu agente crea y edita presentaciones dentro de boosterarena.com",
5
5
  "keywords": [
6
6
  "mcp",
package/servidor.mjs CHANGED
@@ -369,6 +369,7 @@ async function llamar(nombre, args = {}) {
369
369
  if (p.type !== 'presentation') throw new Error(`«${p.title}» es un ${p.type}, y por aquí solo se trabajan presentaciones.`);
370
370
  yo.proyecto = p;
371
371
  yo.borrador = nucleo.nuevoBorrador(Array.isArray(p.document) ? p.document : [], 0, 0, '');
372
+ yo.borrador.estricto = true; // sin nadie delante: lo que el Robot resuelve solo, aquí se niega
372
373
  await nucleo.cargarBiblioteca(); // `poner_dibujo` busca en caliente
373
374
  // El id va en la PRIMERA línea y con su nombre: antes no venía y había que llamar a
374
375
  // `listar_proyectos` y buscar por título, que con ocho pilotos de nombre parecido es jugársela.