mandioca-tabs-mcp 0.2.18 → 0.2.19

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.
Files changed (2) hide show
  1. package/index.mjs +524 -159
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -13731,7 +13731,7 @@ var init_kit = __esm({
13731
13731
  KIT = [
13732
13732
  { indice: 0, nombre: "Bombo", midi: 36, linea: 9, cabeza: "normal", grupo: "Parches" },
13733
13733
  { indice: 1, nombre: "Caja", midi: 38, linea: 5, cabeza: "normal", grupo: "Parches" },
13734
- { indice: 2, nombre: "Aro (rimshot)", midi: 37, linea: 5, cabeza: "x", grupo: "Parches" },
13734
+ { indice: 2, nombre: "Aro (side stick)", midi: 37, linea: 5, cabeza: "x", grupo: "Parches" },
13735
13735
  { indice: 3, nombre: "Tom agudo", midi: 48, linea: 3, cabeza: "normal", grupo: "Parches" },
13736
13736
  { indice: 4, nombre: "Tom medio", midi: 45, linea: 4, cabeza: "normal", grupo: "Parches" },
13737
13737
  { indice: 5, nombre: "Tom de piso", midi: 41, linea: 7, cabeza: "normal", grupo: "Parches" },
@@ -14164,11 +14164,11 @@ var init_enBlanco = __esm({
14164
14164
 
14165
14165
  // packages/model/src/armadura.ts
14166
14166
  function armaduraEscrita(concierto, transposicion) {
14167
- if (!transposicion) return concierto;
14167
+ if (!transposicion || transposicion % 12 === 0) return concierto;
14168
14168
  return normalizar(concierto - 7 * transposicion);
14169
14169
  }
14170
14170
  function armaduraDeConcierto(escrita, transposicion) {
14171
- if (!transposicion) return escrita;
14171
+ if (!transposicion || transposicion % 12 === 0) return escrita;
14172
14172
  return normalizar(escrita + 7 * transposicion);
14173
14173
  }
14174
14174
  var normalizar;
@@ -14554,6 +14554,7 @@ function borrarNotaDeCuerda(documento, indice, cursor, cuerda) {
14554
14554
  documento.doc.transact(() => {
14555
14555
  documento.notas.delete(nota.id);
14556
14556
  if (quedan === 0) documento.figuras.get(figura.id)?.set("esSilencio", true);
14557
+ repararLigaduras(documento, [cursor.pistaId], "soltar");
14557
14558
  });
14558
14559
  return true;
14559
14560
  }
@@ -14570,6 +14571,7 @@ function escribirSilencio(documento, indice, cursor, duracion = DURACION_INICIAL
14570
14571
  id2 = figuraExistente.id;
14571
14572
  for (const nota of notas) documento.notas.delete(nota.id);
14572
14573
  documento.figuras.get(id2)?.set("esSilencio", true);
14574
+ if (notas.length > 0) repararLigaduras(documento, [cursor.pistaId], "soltar");
14573
14575
  } else {
14574
14576
  id2 = documento.agregarFigura({ ...figuraNueva(compasId, cursor, duracion), esSilencio: true });
14575
14577
  }
@@ -14750,32 +14752,61 @@ function copiar(indice, seleccion) {
14750
14752
  }
14751
14753
  const ultima = copiadas[copiadas.length - 1];
14752
14754
  const ultimaFigura = figuras[figuras.length - 1];
14755
+ const tipo = indice.pista(seleccion.pistaId)?.datos.tipo;
14753
14756
  return {
14754
14757
  figuras: copiadas,
14755
- tics: ultima && ultimaFigura ? ultima.desplazamiento + ultimaFigura.tics : 0
14758
+ tics: ultima && ultimaFigura ? ultima.desplazamiento + ultimaFigura.tics : 0,
14759
+ ...tipo ? { tipo } : {}
14756
14760
  };
14757
14761
  }
14762
+ function tipoDeNota(nota) {
14763
+ if (nota.articulacion !== null && nota.articulacion !== void 0) return "percusion";
14764
+ if (nota.altura !== null && nota.altura !== void 0) return "altura";
14765
+ return "trastes";
14766
+ }
14767
+ function porQueNoSePuedePegar(indice, cursor, portapapeles2) {
14768
+ const destino = indice.pista(cursor.pistaId)?.datos;
14769
+ if (!destino) return "Esa pista no existe.";
14770
+ const notas = portapapeles2.figuras.flatMap((f) => f.notas);
14771
+ if (notas.length === 0) return null;
14772
+ const origen = portapapeles2.tipo;
14773
+ if (origen ? origen !== destino.tipo : notas.some((n) => tipoDeNota(n) !== destino.tipo)) {
14774
+ return "Lo copiado es de otro tipo de instrumento.";
14775
+ }
14776
+ if (destino.tipo === "trastes") {
14777
+ const cuerdas = destino.afinacion.length;
14778
+ const fuera = notas.find((n) => n.cuerda < 1 || n.cuerda > cuerdas);
14779
+ if (fuera) return `Esta pista no tiene cuerda ${fuera.cuerda}.`;
14780
+ }
14781
+ return null;
14782
+ }
14758
14783
  function pegar(documento, indice, cursor, portapapeles2) {
14759
14784
  if (portapapeles2.figuras.length === 0) return false;
14760
14785
  if (!indice.pista(cursor.pistaId)) return false;
14786
+ if (porQueNoSePuedePegar(indice, cursor, portapapeles2)) return false;
14761
14787
  const destinos = [];
14762
14788
  let compas = cursor.compas;
14763
- let tiempo = cursor.tiempo;
14789
+ let exacto = cursor.tiempo;
14764
14790
  for (const figura of portapapeles2.figuras) {
14765
14791
  let maestro = indice.compasMaestro(compas);
14766
14792
  if (!maestro) return false;
14767
- while (tiempo >= ticsDeCompas(maestro.datos)) {
14768
- tiempo -= ticsDeCompas(maestro.datos);
14793
+ while (Math.round(exacto) >= ticsDeCompas(maestro.datos)) {
14794
+ exacto -= ticsDeCompas(maestro.datos);
14769
14795
  compas += 1;
14770
14796
  maestro = indice.compasMaestro(compas);
14771
14797
  if (!maestro) return false;
14772
14798
  }
14773
- destinos.push({ compas, tiempo, figura });
14774
- tiempo += ticsDeFigura({ ...figura, tupletNumerador: figura.tupletNumerador, tupletDenominador: figura.tupletDenominador });
14799
+ const tiempo = Math.round(exacto);
14800
+ exacto += ticsExactosDeFigura(figura);
14801
+ destinos.push({ compas, tiempo, fin: Math.round(exacto), figura });
14802
+ if (Math.round(exacto) > ticsDeCompas(maestro.datos)) {
14803
+ compas += 1;
14804
+ exacto = 0;
14805
+ }
14775
14806
  }
14776
14807
  const tramos = /* @__PURE__ */ new Map();
14777
14808
  for (const d of destinos) {
14778
- const fin = d.tiempo + ticsDeFigura(d.figura);
14809
+ const fin = d.fin;
14779
14810
  const tramo = tramos.get(d.compas);
14780
14811
  if (tramo) {
14781
14812
  tramo.desde = Math.min(tramo.desde, d.tiempo);
@@ -14866,7 +14897,9 @@ function duplicarUltima(documento, indice, cursor, opciones = {}) {
14866
14897
  } = anterior.datos;
14867
14898
  if (!opciones.sinAcorde && acordeSegundaTipografia !== void 0)
14868
14899
  Object.assign(golpe, { acordeSegundaTipografia });
14900
+ const tipo = indice.pista(cursor.pistaId)?.datos.tipo;
14869
14901
  const portapapeles2 = {
14902
+ ...tipo ? { tipo } : {},
14870
14903
  tics: ticsDeFigura(anterior.datos),
14871
14904
  figuras: [
14872
14905
  {
@@ -15162,14 +15195,17 @@ function ponerSilabaEnElGolpe(documento, indice, cursor, texto4, marcas) {
15162
15195
  });
15163
15196
  return true;
15164
15197
  }
15165
- function escribirLetraEnLosGolpes(documento, indice, pistaId, voz, desdeCompas, silabas2) {
15198
+ function escribirLetraEnLosGolpes(documento, indice, pistaId, voz, desdeCompas, silabas2, pentagrama = 0) {
15166
15199
  let escritas = 0;
15167
15200
  documento.doc.transact(() => {
15168
15201
  for (let compas = desdeCompas; compas < indice.cantidadDeCompases; compas++) {
15169
15202
  if (escritas >= silabas2.length) break;
15170
- const compasId = indice.compasId(pistaId, compas);
15203
+ const compasId = indice.compasId(pistaId, compas, pentagrama);
15171
15204
  for (const figura of indice.figuras(compasId, voz)) {
15172
15205
  if (escritas >= silabas2.length) break;
15206
+ const notas = indice.notas(figura.id);
15207
+ const seAtaca = !figura.datos.esSilencio && notas.length > 0 && notas.some((n) => !n.datos.ligadaAAnterior);
15208
+ if (!seAtaca) continue;
15173
15209
  const palabra = silabas2[escritas].trim();
15174
15210
  escritas += 1;
15175
15211
  if (palabra === "") continue;
@@ -16449,12 +16485,14 @@ function moverNotasDeCuerda(documento, indice, cursor, seleccion, hacia) {
16449
16485
  }
16450
16486
  documento.doc.transact(() => {
16451
16487
  for (const { id: id2, cuerda } of destinos) documento.notas.get(id2)?.set("cuerda", cuerda);
16488
+ repararLigaduras(documento, [cursor.pistaId], "soltar");
16452
16489
  });
16453
16490
  return { hecho: true, movidas: destinos.length };
16454
16491
  }
16455
16492
  var init_moverNotas = __esm({
16456
16493
  "packages/editor/src/moverNotas.ts"() {
16457
16494
  init_efectos();
16495
+ init_ligaduras();
16458
16496
  }
16459
16497
  });
16460
16498
 
@@ -16481,7 +16519,7 @@ var init_atajos = __esm({
16481
16519
  init_teclasDeFabrica();
16482
16520
  init_comandos();
16483
16521
  init_teclado();
16484
- conMayusculas = (tecla) => `Shift+${tecla}`;
16522
+ conMayusculas = (tecla) => `Ctrl+Shift+${tecla}`;
16485
16523
  COMANDOS_DE_MENU = [
16486
16524
  { id: "nuevo", nombre: "Nuevo archivo\u2026", grupo: "Archivo y salida" },
16487
16525
  { id: "abrir", nombre: "Abrir\u2026", grupo: "Archivo y salida" },
@@ -16815,7 +16853,10 @@ var init_atajos = __esm({
16815
16853
  })),
16816
16854
  // ── Compases ──────────────────────────────────────────────────────────────
16817
16855
  { id: "insertar-compas", nombre: "Insertar un comp\xE1s", grupo: "Compases", teclas: ["Ctrl+Enter"], reasignable: true },
16818
- { id: "borrar-compas", nombre: "Borrar el comp\xE1s", grupo: "Compases", teclas: ["Shift+Delete", "Shift+Backspace"], reasignable: true },
16856
+ // ⛔ (2026-11-11, C7) Decía «Shift+Delete»: la pantalla mostraba «May + Supr», y May + Supr
16857
+ // borra la NOTA (la tabla general no mira las Mayúsculas). La tecla que de verdad borra el
16858
+ // compás es Ctrl + May + Supr. Si se cambia de combinación es decisión de Daniel.
16859
+ { id: "borrar-compas", nombre: "Borrar el comp\xE1s", grupo: "Compases", teclas: ["Ctrl+Shift+Delete", "Ctrl+Shift+Backspace"], reasignable: true },
16819
16860
  // ── Reproducción ──────────────────────────────────────────────────────────
16820
16861
  { id: "reproducir", nombre: "Reproducir o pausar", grupo: "Reproducci\xF3n", teclas: [" "], reasignable: true },
16821
16862
  // ★ «Desde el principio» (2026-10-15): Mayús + Espacio, como el «Play from Start» de Guitar Pro.
@@ -16893,7 +16934,8 @@ function haceDiptongo(a, b) {
16893
16934
  if (esDebilAtona(a) && esDebilAtona(b)) return a !== b;
16894
16935
  return true;
16895
16936
  }
16896
- function silabas(palabra, idioma = "es") {
16937
+ function silabas(palabraTalCual, idioma = "es") {
16938
+ const palabra = palabraTalCual.normalize("NFC");
16897
16939
  if (!palabra) return [];
16898
16940
  if (idioma !== "es") return [palabra];
16899
16941
  const unidades = enUnidades(palabra);
@@ -16906,8 +16948,16 @@ function silabas(palabra, idioma = "es") {
16906
16948
  continue;
16907
16949
  }
16908
16950
  let fin = i2;
16909
- while (fin + 1 < unidades.length && unidades[fin + 1].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 1].texto.toLowerCase())) {
16910
- fin++;
16951
+ for (; ; ) {
16952
+ if (fin + 1 < unidades.length && unidades[fin + 1].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 1].texto.toLowerCase())) {
16953
+ fin++;
16954
+ continue;
16955
+ }
16956
+ if (fin + 2 < unidades.length && unidades[fin + 1].texto.toLowerCase() === "h" && unidades[fin + 2].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 2].texto.toLowerCase())) {
16957
+ fin += 2;
16958
+ continue;
16959
+ }
16960
+ break;
16911
16961
  }
16912
16962
  nucleos.push({ desde: i2, hasta: fin });
16913
16963
  i2 = fin + 1;
@@ -16937,7 +16987,7 @@ function silabas(palabra, idioma = "es") {
16937
16987
  }
16938
16988
  function leerLetra(texto4, idioma = "es") {
16939
16989
  const salida = [];
16940
- for (const cruda of texto4.split(/\r?\n/)) {
16990
+ for (const cruda of texto4.normalize("NFC").split(/\r?\n/)) {
16941
16991
  const linea = cruda.trim();
16942
16992
  if (!linea) continue;
16943
16993
  const palabras = [];
@@ -20494,6 +20544,11 @@ function nombreLegible(deteccion) {
20494
20544
  const limpio = acorde.type === "major" && acorde.tonic ? acorde.tonic : cuerpo;
20495
20545
  return bajo ? `${limpio}/${bajo}` : limpio;
20496
20546
  }
20547
+ function afinacionQueSuena(pista) {
20548
+ if (!pista) return [];
20549
+ const capo = Number.isInteger(pista.capo) && pista.capo > 0 ? pista.capo : 0;
20550
+ return pista.afinacion.map((n) => n + capo);
20551
+ }
20497
20552
  function notaMidi(cuerda, traste, afinacion) {
20498
20553
  const alAire = afinacion[cuerda - 1];
20499
20554
  return alAire === void 0 ? null : alAire + traste;
@@ -20524,10 +20579,10 @@ function acordeEnElCursor(indice, cursor) {
20524
20579
  const compasId = indice.compasId(cursor.pistaId, cursor.compas, cursor.pentagrama);
20525
20580
  const figura = compasId ? indice.figuraEn(compasId, cursor.voz, cursor.tiempo) : void 0;
20526
20581
  if (!figura) return null;
20527
- const afinacion = indice.pista(cursor.pistaId)?.datos.afinacion;
20528
- if (!afinacion) return null;
20582
+ const pista = indice.pista(cursor.pistaId)?.datos;
20583
+ if (!pista) return null;
20529
20584
  const notas = indice.notas(figura.id).map((n) => n.datos);
20530
- return acordeDeNotas(notas, afinacion);
20585
+ return acordeDeNotas(notas, afinacionQueSuena(pista));
20531
20586
  }
20532
20587
  function claveDeForma(forma) {
20533
20588
  return [...forma].sort((a, b) => a.cuerda - b.cuerda).map((n) => `${n.cuerda}:${n.traste}`).join(" ");
@@ -20621,15 +20676,17 @@ function ponerAcorde(documento, indice, cursor, nombre, duracion = DURACION_INIC
20621
20676
  return null;
20622
20677
  }
20623
20678
  const yaTieneNotas = figura ? indice.notas(figura.id).length > 0 : false;
20624
- if (sinNombre && (yaTieneNotas || forma.length === 0)) return null;
20679
+ const pista = indice.pista(cursor.pistaId)?.datos;
20680
+ const formaQueEntra = pista?.tipo === "trastes" && forma.every((n) => n.cuerda >= 1 && n.cuerda <= pista.afinacion.length) ? forma : [];
20681
+ if (sinNombre && (yaTieneNotas || formaQueEntra.length === 0)) return null;
20625
20682
  documento.doc.transact(() => {
20626
20683
  const compasId = compasExistente ?? documento.agregarCompas(normalizarCompas({ pistaId: cursor.pistaId, compasMaestroId: maestro.id, pentagrama: cursor.pentagrama }));
20627
20684
  const id2 = figura?.id ?? documento.agregarFigura({ ...figuraNueva(compasId, cursor, duracion), esSilencio: true });
20628
20685
  const mapa = documento.figuras.get(id2);
20629
20686
  if (mapa && !sinNombre) escribirConMarcas(mapa, "acorde", nombre ?? "", marcas, { recortar: true });
20630
- if (!yaTieneNotas && forma.length > 0) {
20687
+ if (!yaTieneNotas && formaQueEntra.length > 0) {
20631
20688
  documento.figuras.get(id2)?.set("esSilencio", false);
20632
- for (const { cuerda, traste } of forma) {
20689
+ for (const { cuerda, traste } of formaQueEntra) {
20633
20690
  documento.agregarNota(notaNueva(id2, cuerda, traste));
20634
20691
  }
20635
20692
  }
@@ -21420,7 +21477,7 @@ var init_estado = __esm({
21420
21477
  "estado.info": {
21421
21478
  nombre: "Panel de info",
21422
21479
  accion: "info",
21423
- principiante: "Muestra u oculta el panel de arriba a la derecha, que explica lo que est\xE1 bajo el puntero. Cuando el c\xEDrculo est\xE1 amarillo, el panel est\xE1 a la vista; gris, est\xE1 oculto. Tambi\xE9n se prende y se apaga con la tecla de signo de pregunta.",
21480
+ principiante: "Muestra u oculta la franja de debajo de las herramientas, que explica lo que est\xE1 bajo el puntero. Cuando el c\xEDrculo est\xE1 amarillo, el panel est\xE1 a la vista; gris, est\xE1 oculto. Tambi\xE9n se prende y se apaga con la tecla de signo de pregunta.",
21424
21481
  profesional: "Muestra u oculta el panel de info. Tecla: ?"
21425
21482
  }
21426
21483
  };
@@ -21455,8 +21512,8 @@ var init_menus = __esm({
21455
21512
  },
21456
21513
  "menu-compas": {
21457
21514
  nombre: "Men\xFA Comp\xE1s",
21458
- principiante: "Lo que se hace con el comp\xE1s donde est\xE1 el cursor: insertar uno nuevo, borrarlo, cambiarle el tempo, la m\xE9trica o la tonalidad desde ah\xED, marcarlo como anacruza o ponerle el nombre de una secci\xF3n.",
21459
- profesional: "Estructura del comp\xE1s del cursor: insertar, borrar, tempo, m\xE9trica, tonalidad, anacruza y secci\xF3n."
21515
+ principiante: "Lo que se hace con el comp\xE1s donde est\xE1 el cursor: insertar uno nuevo, borrarlo, cambiarle el tempo, la m\xE9trica o la tonalidad desde ah\xED, marcarlo como anacruza o ponerle el nombre de una secci\xF3n. Tambi\xE9n pone repeticiones, casillas, D.C., D.S. y Coda.",
21516
+ profesional: "Comp\xE1s del cursor: insertar, borrar, tempo, m\xE9trica, tonalidad, anacruza, secci\xF3n, repetici\xF3n, casillas y saltos."
21460
21517
  },
21461
21518
  "menu-nota": {
21462
21519
  nombre: "Men\xFA Nota",
@@ -21470,8 +21527,8 @@ var init_menus = __esm({
21470
21527
  },
21471
21528
  "menu-configuracion": {
21472
21529
  nombre: "Men\xFA Configuraci\xF3n",
21473
- principiante: "Los ajustes del programa que son tuyos y no de la obra: las teclas de cada acci\xF3n y c\xF3mo se sincroniza la letra.",
21474
- profesional: "Atajos de teclado y sincronizaci\xF3n de letra."
21530
+ principiante: "Los ajustes del programa que son tuyos y no de la obra: las teclas de cada acci\xF3n, c\xF3mo se sincroniza la letra, c\xF3mo se escribe y tu privacidad.",
21531
+ profesional: "Atajos, sincronizaci\xF3n de letra, escritura y privacidad."
21475
21532
  },
21476
21533
  // ── Archivo ─────────────────────────────────────────────────────────────────
21477
21534
  nuevo: {
@@ -21481,8 +21538,8 @@ var init_menus = __esm({
21481
21538
  },
21482
21539
  abrir: {
21483
21540
  nombre: "Abrir\u2026",
21484
- principiante: "Abre una obra que est\xE1 en tu computadora: un proyecto de Mandioca Tabs (.mtabs) o un archivo de Guitar Pro, MusicXML y otros formatos de tablatura. Si hay cambios sin guardar, te pregunta antes.",
21485
- profesional: "Abre .mtabs, .gp y MusicXML."
21541
+ principiante: "Abre una obra que est\xE1 en tu computadora: un proyecto de Mandioca Tabs (.mtabs) o un archivo de Guitar Pro, MusicXML, MIDI y otros formatos de tablatura. Si hay cambios sin guardar, te pregunta antes.",
21542
+ profesional: "Abre .mtabs, .gp, MusicXML y MIDI."
21486
21543
  },
21487
21544
  "menu-recientes": {
21488
21545
  nombre: "Abrir reciente",
@@ -21601,7 +21658,7 @@ var init_menus = __esm({
21601
21658
  nombre: "Insertar un comp\xE1s",
21602
21659
  accion: "insertar-compas",
21603
21660
  principiante: "Agrega un comp\xE1s vac\xEDo justo despu\xE9s del comp\xE1s donde est\xE1 el cursor. Para meterlo antes de uno en particular, haz clic derecho sobre su l\xEDnea divisoria.",
21604
- profesional: "Inserta un comp\xE1s despu\xE9s del del cursor."
21661
+ profesional: "Inserta un comp\xE1s despu\xE9s del comp\xE1s del cursor."
21605
21662
  },
21606
21663
  "menu-borrar-compas": {
21607
21664
  nombre: "Borrar el comp\xE1s",
@@ -21616,7 +21673,7 @@ var init_menus = __esm({
21616
21673
  profesional: "Borra del comp\xE1s del cursor al final."
21617
21674
  },
21618
21675
  "menu-borrar-hasta": {
21619
- nombre: "Borrar para atr\xE1s",
21676
+ nombre: "Borrar los compases antes",
21620
21677
  accion: "comando-borrar-compases-hasta",
21621
21678
  principiante: "Borra todos los compases que est\xE1n antes del cursor, desde el principio de la obra. El comp\xE1s del cursor pasa a ser el primero.",
21622
21679
  profesional: "Borra del principio al comp\xE1s anterior al cursor."
@@ -21778,12 +21835,12 @@ var init_menus = __esm({
21778
21835
  },
21779
21836
  "menu-compases-por-linea": {
21780
21837
  nombre: "Compases por l\xEDnea",
21781
- principiante: "Cu\xE1ntos compases entran en cada rengl\xF3n. En vista horizontal, cu\xE1ntos se ven a la vez en la ventana. Con \xABLos que entren\xBB el programa los acomoda seg\xFAn su contenido.",
21838
+ principiante: "Cu\xE1ntos compases entran en cada rengl\xF3n. En vista horizontal, cu\xE1ntos se ven a la vez en la ventana. Con \xABLos que entren\xBB el programa pone los que quepan en el ancho, seg\xFAn lo que lleve cada uno.",
21782
21839
  profesional: "Compases por sistema."
21783
21840
  },
21784
21841
  "menu-cpl": {
21785
21842
  nombre: "Compases por l\xEDnea",
21786
- principiante: "Pone esta cantidad de compases en cada rengl\xF3n. \xABLos que entren\xBB deja que el programa decida seg\xFAn el ancho de la pantalla.",
21843
+ principiante: "Pone esta cantidad de compases en cada rengl\xF3n. \xABLos que entren\xBB pone los que quepan en el ancho, seg\xFAn lo que lleve cada uno.",
21787
21844
  profesional: "Fija los compases por sistema."
21788
21845
  },
21789
21846
  "menu-ritmo": {
@@ -22253,7 +22310,7 @@ var init_paneles = __esm({
22253
22310
  },
22254
22311
  "mezcla.volumen": {
22255
22312
  nombre: "Volumen de la pista",
22256
- principiante: "Cu\xE1nto suena esta pista, en decibeles. 0 dB es el m\xE1ximo; los n\xFAmeros negativos bajan. Se cambia arrastrando, con las flechas o escribiendo.",
22313
+ principiante: "Cu\xE1nto suena esta pista, en decibeles. Llega hasta +6 dB; los n\xFAmeros negativos bajan. Se cambia arrastrando, con las flechas o escribiendo.",
22257
22314
  profesional: "Volumen de la pista, en dB."
22258
22315
  },
22259
22316
  "mezcla.paneo": {
@@ -22349,6 +22406,11 @@ var init_paneles = __esm({
22349
22406
  principiante: "Empieza la separaci\xF3n con la cantidad elegida. Las partes aparecen como grabaciones nuevas de la obra.",
22350
22407
  profesional: "Lanza la separaci\xF3n."
22351
22408
  },
22409
+ "audio.stems-cancelar": {
22410
+ nombre: "Cancelar la separaci\xF3n",
22411
+ principiante: "Detiene la separaci\xF3n. No se agrega ninguna pista a la obra.",
22412
+ profesional: "Aborta la separaci\xF3n sin escribir."
22413
+ },
22352
22414
  "audio.letra-cerrar": {
22353
22415
  nombre: "Cerrar la letra",
22354
22416
  principiante: "Cierra el panel de la letra y vuelve a la partitura. Lo escrito se queda.",
@@ -22475,7 +22537,7 @@ var init_paneles = __esm({
22475
22537
  },
22476
22538
  "inicio.abrir": {
22477
22539
  nombre: "Abrir un archivo",
22478
- principiante: "Abre un proyecto o un archivo de Guitar Pro o MusicXML que est\xE9 en tu computadora.",
22540
+ principiante: "Abre un proyecto o un archivo de Guitar Pro, MusicXML o MIDI que est\xE9 en tu computadora.",
22479
22541
  profesional: "Abre un archivo."
22480
22542
  },
22481
22543
  "inicio.reciente": {
@@ -88086,6 +88148,9 @@ function juntarManosDelPiano(documento) {
88086
88148
  const porCorto = baseDeLasManos(String(d.get("nombreCorto") ?? ""), String(iz.get("nombreCorto") ?? ""));
88087
88149
  const base = porNombre ?? (porCorto !== null ? String(d.get("nombre") ?? "").trim() || porCorto : null);
88088
88150
  if (!base) continue;
88151
+ const clave = porNombre !== null ? "nombre" : "nombreCorto";
88152
+ const explicitas = MANO_EXPLICITA.test(String(d.get(clave) ?? "").trim()) && MANO_EXPLICITA.test(String(iz.get(clave) ?? "").trim());
88153
+ if (!esTeclado(d.get("programaMidi")) && !explicitas) continue;
88089
88154
  parejas.push({ derecha: enOrden[i2], izquierda: enOrden[i2 + 1], indiceIzquierda: i2 + 1, base });
88090
88155
  i2++;
88091
88156
  }
@@ -88124,7 +88189,7 @@ function juntarManosDelPiano(documento) {
88124
88189
  });
88125
88190
  return parejas.length;
88126
88191
  }
88127
- var DO_CENTRAL, PARAMETROS_DE_MANOS, DERECHA, IZQUIERDA;
88192
+ var DO_CENTRAL, PARAMETROS_DE_MANOS, DERECHA, IZQUIERDA, MANO_EXPLICITA, esTeclado;
88128
88193
  var init_manosDelPiano = __esm({
88129
88194
  "packages/io/src/manosDelPiano.ts"() {
88130
88195
  init_src2();
@@ -88143,6 +88208,8 @@ var init_manosDelPiano = __esm({
88143
88208
  };
88144
88209
  DERECHA = /^(.*?)[\s\-_.:,(]*\b(mano\s+derecha|m\.?\s?d\.?|right\s+hand|r\.?\s?h\.?|rh|md|derecha|right|treble)\)?\s*$/i;
88145
88210
  IZQUIERDA = /^(.*?)[\s\-_.:,(]*\b(mano\s+izquierda|m\.?\s?i\.?|left\s+hand|l\.?\s?h\.?|lh|mi|izquierda|left|bass)\)?\s*$/i;
88211
+ MANO_EXPLICITA = /\b(mano\s+(derecha|izquierda)|(right|left)\s+hand|m\.?\s?[di]\.?|[rl]\.?\s?h\.?|md|mi|rh|lh)\)?\s*$/i;
88212
+ esTeclado = (programa) => typeof programa === "number" && (programa <= 7 || programa >= 16 && programa <= 20);
88146
88213
  }
88147
88214
  });
88148
88215
 
@@ -89156,6 +89223,19 @@ function alturaMusicXml(midi, fifths, modo = 0) {
89156
89223
  if (abajo) return { paso: abajo.paso, alteracion: 1, octava };
89157
89224
  throw new Error(`altura MIDI no representable: ${midi}`);
89158
89225
  }
89226
+ function elementoTranspose(semitonos, numero2) {
89227
+ if (!semitonos) return [];
89228
+ const octavas = Math.trunc(semitonos / 12);
89229
+ const resto = semitonos - octavas * 12;
89230
+ const diatonico = Math.sign(resto) * PASOS_POR_SEMITONOS[Math.abs(resto)];
89231
+ return [
89232
+ numero2 === null ? "<transpose>" : `<transpose number="${numero2}">`,
89233
+ `<diatonic>${diatonico}</diatonic>`,
89234
+ `<chromatic>${resto}</chromatic>`,
89235
+ ...octavas ? [`<octave-change>${octavas}</octave-change>`] : [],
89236
+ "</transpose>"
89237
+ ];
89238
+ }
89159
89239
  function claveDelPentagrama(staff, bar) {
89160
89240
  if (staff.isPercussion) return { signo: "percussion", linea: 2, lineas: 5, conAfinacion: false };
89161
89241
  if (staff.showTablature && staff.tuning.length > 0)
@@ -89242,7 +89322,7 @@ function escribirNota(note, beat, staff, opciones) {
89242
89322
  s.push("</unpitched>");
89243
89323
  } else {
89244
89324
  const { paso, alteracion, octava } = alturaMusicXml(
89245
- note.calculateRealValue(false, false),
89325
+ note.calculateRealValue(false, false) - staff.displayTranspositionPitch,
89246
89326
  opciones.fifths,
89247
89327
  note.accidentalMode
89248
89328
  );
@@ -89276,6 +89356,11 @@ function escribirNota(note, beat, staff, opciones) {
89276
89356
  const notaciones = [];
89277
89357
  if (note.isTieDestination) notaciones.push('<tied type="stop"/>');
89278
89358
  if (note.isTieOrigin) notaciones.push('<tied type="start"/>');
89359
+ if (!opciones.esAcorde) {
89360
+ const n = opciones.numeroDeVoz;
89361
+ if (beat.isLegatoOrigin && !beat.isLegatoDestination) notaciones.push(`<slur type="start" number="${n}"/>`);
89362
+ if (beat.isLegatoDestination && !beat.isLegatoOrigin) notaciones.push(`<slur type="stop" number="${n}"/>`);
89363
+ }
89279
89364
  if (opciones.abreTresillo) notaciones.push('<tuplet type="start" number="1"/>');
89280
89365
  if (opciones.cierraTresillo) notaciones.push('<tuplet type="stop" number="1"/>');
89281
89366
  const tecnica = [];
@@ -89374,8 +89459,11 @@ function exportarMusicXml(score) {
89374
89459
  for (const [art, idInstrumento] of instrumentos) {
89375
89460
  const a = track.percussionArticulations[art];
89376
89461
  l.push(`<score-instrument id="${idInstrumento}">`);
89377
- l.push(...elemento("instrument-name", `Percusi\xF3n ${art + 1}`));
89462
+ l.push(...elemento("instrument-name", a?.elementType || `Percusi\xF3n ${art + 1}`));
89378
89463
  l.push("</score-instrument>");
89464
+ }
89465
+ for (const [art, idInstrumento] of instrumentos) {
89466
+ const a = track.percussionArticulations[art];
89379
89467
  l.push(`<midi-instrument id="${idInstrumento}">`);
89380
89468
  l.push("<midi-channel>10</midi-channel>");
89381
89469
  l.push(`<midi-unpitched>${(a?.outputMidiNumber ?? 36) + 1}</midi-unpitched>`);
@@ -89464,6 +89552,13 @@ function exportarMusicXml(score) {
89464
89552
  }
89465
89553
  clavesAnteriores = firmaDeClaves;
89466
89554
  }
89555
+ if (indiceDeCompas2 === 0) {
89556
+ const distintas = new Set(pentagramas.map((st) => st.displayTranspositionPitch)).size > 1;
89557
+ pentagramas.forEach((st, k) => {
89558
+ if (k > 0 && !distintas) return;
89559
+ cambios.push(...elementoTranspose(st.displayTranspositionPitch, distintas ? k + 1 : null));
89560
+ });
89561
+ }
89467
89562
  if (cambios.length > 0) l.push("<attributes>", ...cambios, "</attributes>");
89468
89563
  const casilla = master?.alternateEndings ?? 0;
89469
89564
  const casillaAntes = score.masterBars[indiceDeCompas2 - 1]?.alternateEndings ?? 0;
@@ -89597,7 +89692,7 @@ function exportarMusicXmlComprimido(score) {
89597
89692
  { mtime: FECHA_FIJA_DEL_ZIP }
89598
89693
  );
89599
89694
  }
89600
- var DIVISIONES_POR_NEGRA, FECHA_FIJA_DEL_ZIP, PASOS, NOMBRE_DE_LA_FIGURA2, ALTERACION_POR_MODO, D, SALTOS, claveDeSalto, SALTO_POR_CLAVE;
89695
+ var DIVISIONES_POR_NEGRA, FECHA_FIJA_DEL_ZIP, PASOS, NOMBRE_DE_LA_FIGURA2, ALTERACION_POR_MODO, PASOS_POR_SEMITONOS, D, SALTOS, claveDeSalto, SALTO_POR_CLAVE;
89601
89696
  var init_musicxml = __esm({
89602
89697
  "packages/io/src/musicxml.ts"() {
89603
89698
  init_alphaTab();
@@ -89627,6 +89722,7 @@ var init_musicxml = __esm({
89627
89722
  [256, "256th"]
89628
89723
  ]);
89629
89724
  ALTERACION_POR_MODO = { 2: 0, 3: 1, 4: 2, 5: -1, 6: -2 };
89725
+ PASOS_POR_SEMITONOS = [0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6];
89630
89726
  D = _barrel_exports$4.Direction;
89631
89727
  SALTOS = [
89632
89728
  [D.TargetSegno, { simbolo: "segno", sonido: 'segno="segno"' }],
@@ -89701,11 +89797,13 @@ function prepararMusicXml(xml) {
89701
89797
  });
89702
89798
  if (atributosMovidos) {
89703
89799
  correcciones.push(
89704
- `${atributosMovidos} cambios de clave, armadura o comp\xE1s escritos entre las notas se pusieron al principio del comp\xE1s donde rigen.`
89800
+ atributosMovidos === 1 ? "1 cambio de clave, armadura o comp\xE1s escrito entre las notas se puso al principio del comp\xE1s donde rige." : `${atributosMovidos} cambios de clave, armadura o comp\xE1s escritos entre las notas se pusieron al principio del comp\xE1s donde rigen.`
89705
89801
  );
89706
89802
  }
89707
89803
  if (acordesSeparados) {
89708
- correcciones.push(`${acordesSeparados} notas de acordes repartidos entre los dos pentagramas se pusieron en el suyo.`);
89804
+ correcciones.push(
89805
+ acordesSeparados === 1 ? "1 nota de un acorde repartido entre los dos pentagramas se puso en el suyo." : `${acordesSeparados} notas de acordes repartidos entre los dos pentagramas se pusieron en el suyo.`
89806
+ );
89709
89807
  }
89710
89808
  if (saltosMovidos) correcciones.push(`${saltosMovidos} saltos (D.C., D.S., Coda, Fine, Segno) se leyeron de su indicaci\xF3n.`);
89711
89809
  let juntados = 0;
@@ -89732,7 +89830,10 @@ function prepararMusicXml(xml) {
89732
89830
  return `${a}${texto4.trimEnd()}-${b}`;
89733
89831
  });
89734
89832
  });
89735
- if (silabas2) correcciones.push(`${silabas2} s\xEDlabas unidas de la letra se marcaron con su guion.`);
89833
+ if (silabas2)
89834
+ correcciones.push(
89835
+ silabas2 === 1 ? "1 s\xEDlaba unida de la letra se marc\xF3 con su guion." : `${silabas2} s\xEDlabas unidas de la letra se marcaron con su guion.`
89836
+ );
89736
89837
  let tempos = 0;
89737
89838
  salida = salida.replace(/<metronome\b[^>]*>[\s\S]*?<\/metronome>/g, (met) => {
89738
89839
  const unidad = /<beat-unit>\s*([\w-]+)\s*<\/beat-unit>/.exec(met)?.[1];
@@ -90066,6 +90167,28 @@ function piezaDeBateria(pista, numero2) {
90066
90167
  const buscada = PARIENTE_GM[midi] ?? midi;
90067
90168
  return KIT.find((p) => p.midi === buscada)?.indice ?? null;
90068
90169
  }
90170
+ function golpesEnLigadura(score) {
90171
+ const marcados = /* @__PURE__ */ new Set();
90172
+ for (const track of score.tracks)
90173
+ for (const staff of track.staves)
90174
+ for (const bar of staff.bars)
90175
+ for (const voice of bar.voices)
90176
+ for (const beat of voice.beats) {
90177
+ if (beat.isLegatoOrigin) marcados.add(beat);
90178
+ for (const nota of beat.notes) {
90179
+ const llegada = nota.slurDestination?.beat;
90180
+ if (!llegada) continue;
90181
+ const camino = [];
90182
+ let b = beat;
90183
+ while (b && b !== llegada && camino.length < 1e4) {
90184
+ camino.push(b);
90185
+ b = b.nextBeat;
90186
+ }
90187
+ if (b === llegada) for (const g of camino) marcados.add(g);
90188
+ }
90189
+ }
90190
+ return marcados;
90191
+ }
90069
90192
  function importarGp(bytes, opciones = {}) {
90070
90193
  const xml = textoMusicXml(bytes);
90071
90194
  if (xml === null) {
@@ -90111,6 +90234,7 @@ function importarScore(score, opciones = {}, datos = {}) {
90111
90234
  );
90112
90235
  }
90113
90236
  const saltosEscritos = saltosDelTexto(score);
90237
+ const enLigadura = golpesEnLigadura(score);
90114
90238
  const idsDeCompasMaestro = score.masterBars.map(
90115
90239
  (maestro) => documento.agregarCompasMaestro(normalizarCompasMaestro({
90116
90240
  numerador: maestro.timeSignatureNumerator,
@@ -90168,6 +90292,7 @@ function importarScore(score, opciones = {}, datos = {}) {
90168
90292
  if (id2 !== void 0 && i2 > 0) documento.ponerSaltoDeRenglon(id2, true);
90169
90293
  }
90170
90294
  const letrasDobles = { cuantas: 0, versosDeMas: 0 };
90295
+ const temposCorridos = { movidos: [], perdidos: [] };
90171
90296
  const tipoDelPentagrama = (p) => p.isPercussion ? "percusion" : p.tuning.length === 0 ? "altura" : "trastes";
90172
90297
  for (const pista of score.tracks) {
90173
90298
  if (pista.staves.length === 0) {
@@ -90233,6 +90358,7 @@ function importarScore(score, opciones = {}, datos = {}) {
90233
90358
  for (const a of maestroDelCompas?.tempoAutomations ?? []) {
90234
90359
  if (a.ratioPosition > 0) temposAMitad.set(Math.round(a.ratioPosition * ticsDelCompas), a.value);
90235
90360
  }
90361
+ moverTemposAGolpes(temposAMitad, compasDeAlphaTab.voices[0], indice + 1, temposCorridos);
90236
90362
  }
90237
90363
  const pedalesDelCompas = /* @__PURE__ */ new Map();
90238
90364
  for (const marcador of compasDeAlphaTab.sustainPedals ?? []) {
@@ -90367,7 +90493,9 @@ function importarScore(score, opciones = {}, datos = {}) {
90367
90493
  armonicoValor: nota.harmonicType !== _barrel_exports$4.HarmonicType.None && nota.harmonicType !== _barrel_exports$4.HarmonicType.Natural ? nota.harmonicValue : 0,
90368
90494
  // ── Los signos de Guitar Pro, a nivel de nota ──────────────────
90369
90495
  acento: nota.accentuated,
90370
- ligaduraDeExpresion: nota.isSlurDestination || nota.slurDestination !== null,
90496
+ // ⛔ (D1) Antes: `isSlurDestination || slurDestination !== null`, que marcaba la nota
90497
+ // de LLEGADA (donde la curva termina) y nunca leía el `<Legato>` de un `.gp`.
90498
+ ligaduraDeExpresion: enLigadura.has(golpe),
90371
90499
  vibratoDePalanca: 0,
90372
90500
  // ★ La anchura del vibrato: el campo viejo `vibrato` solo decía si había.
90373
90501
  anchoDeVibrato: nota.vibrato,
@@ -90418,6 +90546,17 @@ function importarScore(score, opciones = {}, datos = {}) {
90418
90546
  `${letrasDobles.versosDeMas} golpe(s) tra\xEDan letra de un segundo verso o m\xE1s: se guard\xF3 el primero. Tabs lleva un rengl\xF3n de letra.`
90419
90547
  );
90420
90548
  }
90549
+ const enCompases = (lista2) => `${lista2.length === 1 ? "comp\xE1s" : "compases"} ${lista2.join(", ")}`;
90550
+ if (temposCorridos.movidos.length > 0) {
90551
+ avisos.push(
90552
+ `Un cambio de tempo ca\xEDa a mitad de una figura (${enCompases(temposCorridos.movidos)}). Se movi\xF3 al golpe m\xE1s cercano.`
90553
+ );
90554
+ }
90555
+ if (temposCorridos.perdidos.length > 0) {
90556
+ avisos.push(
90557
+ `Un cambio de tempo ca\xEDa a mitad de una figura, sin otro golpe en el comp\xE1s (${enCompases(temposCorridos.perdidos)}). No se import\xF3.`
90558
+ );
90559
+ }
90421
90560
  const audioEmbebido = importarAudioYSync(score, documento, idsDeCompasMaestro, avisos);
90422
90561
  const manosJuntadas = juntarManosDelPiano(documento);
90423
90562
  if (manosJuntadas > 0)
@@ -90515,6 +90654,25 @@ function acordeDelGolpe(golpe, pentagrama, bajos, parte, numeroDePentagrama, com
90515
90654
  const bajo = bajos?.get(`${parte}|${numeroDePentagrama}|${compas}|${golpe.displayStart}`) ?? "";
90516
90655
  return bajo && !nombre.endsWith(bajo) ? nombre + bajo : nombre;
90517
90656
  }
90657
+ function moverTemposAGolpes(tempos, voz, compas, corridos) {
90658
+ if (tempos.size === 0) return;
90659
+ const comienzos = new Set(
90660
+ (voz?.isEmpty ? [] : voz?.beats ?? []).filter((g) => !g.isEmpty && g.graceType === _barrel_exports$4.GraceType.None && g.displayStart > 0).map((g) => g.displayStart)
90661
+ );
90662
+ const ordenados = [...tempos.entries()].sort((a, b) => a[0] - b[0]);
90663
+ tempos.clear();
90664
+ for (const [tic, bpm] of ordenados) {
90665
+ if (comienzos.has(tic)) {
90666
+ tempos.set(tic, bpm);
90667
+ continue;
90668
+ }
90669
+ let mejor = null;
90670
+ for (const c of comienzos) if (mejor === null || Math.abs(c - tic) < Math.abs(mejor - tic) || Math.abs(c - tic) === Math.abs(mejor - tic) && c > mejor) mejor = c;
90671
+ const lista2 = mejor === null ? corridos.perdidos : corridos.movidos;
90672
+ if (!lista2.includes(compas)) lista2.push(compas);
90673
+ if (mejor !== null) tempos.set(mejor, bpm);
90674
+ }
90675
+ }
90518
90676
  function tempoDelGolpe(golpe, delMaestro) {
90519
90677
  if (golpe.displayStart === 0) return void 0;
90520
90678
  const propio = golpe.automations?.find((a) => a.type === _barrel_exports$4.AutomationType.Tempo)?.value;
@@ -91136,7 +91294,36 @@ function mejorDigitacion(alturas, afinacion, prohibidas) {
91136
91294
  let mejor = null;
91137
91295
  const actual = [];
91138
91296
  const usadas = /* @__PURE__ */ new Set();
91297
+ const cuerdasLibres = afinacion.length - [...prohibidas].filter((c) => c >= 1 && c <= afinacion.length).length;
91298
+ const puedeMejorar = (i2, colocadas, maximo, suma) => {
91299
+ if (!mejor) return true;
91300
+ const libres = cuerdasLibres - usadas.size;
91301
+ const tope = colocadas + Math.min(alturas.length - i2, libres);
91302
+ if (tope < mejor.colocadas) return false;
91303
+ if (tope > mejor.colocadas) return true;
91304
+ if (maximo > mejor.maximo) return false;
91305
+ const faltan = mejor.colocadas - colocadas;
91306
+ if (faltan <= 0) return maximo < mejor.maximo || suma < mejor.suma;
91307
+ const minimos = [];
91308
+ for (let k = i2; k < alturas.length; k++) {
91309
+ let menor = Infinity;
91310
+ for (let cuerda = 1; cuerda <= afinacion.length; cuerda++) {
91311
+ if (usadas.has(cuerda) || prohibidas.has(cuerda)) continue;
91312
+ const traste = alturas[k] - afinacion[cuerda - 1];
91313
+ if (traste >= 0 && traste <= TRASTES && traste < menor) menor = traste;
91314
+ }
91315
+ if (menor !== Infinity) minimos.push(menor);
91316
+ }
91317
+ if (minimos.length < faltan) return false;
91318
+ minimos.sort((a, b) => a - b);
91319
+ const maximoPosible = Math.max(maximo, minimos[faltan - 1]);
91320
+ if (maximoPosible > mejor.maximo) return false;
91321
+ let sumaPosible = suma;
91322
+ for (let k = 0; k < faltan; k++) sumaPosible += minimos[k];
91323
+ return maximoPosible < mejor.maximo || sumaPosible < mejor.suma;
91324
+ };
91139
91325
  const probar = (i2, colocadas, maximo, suma) => {
91326
+ if (!puedeMejorar(i2, colocadas, maximo, suma)) return;
91140
91327
  if (i2 === alturas.length) {
91141
91328
  if (!mejor || colocadas > mejor.colocadas || colocadas === mejor.colocadas && (maximo < mejor.maximo || maximo === mejor.maximo && suma < mejor.suma)) {
91142
91329
  mejor = { cuerdas: [...actual], colocadas, maximo, suma };
@@ -91376,7 +91563,8 @@ function armarScore(leido, opciones, avisos) {
91376
91563
  avisos.decir(`Se juntaron ${avisos.cuantas("repetidas")} nota(s) repetidas: la misma tecla dos veces en el mismo instante.`);
91377
91564
  }
91378
91565
  if (avisos.cuantas("sin-voz") > 0) {
91379
- avisos.decir(`Se descartaron ${avisos.cuantas("sin-voz")} nota(s) porque sonaban m\xE1s de ${MAXIMO_DE_VOCES} voces a la vez en el mismo pentagrama.`);
91566
+ const n = avisos.cuantas("sin-voz");
91567
+ avisos.decir(`${n === 1 ? "Se descart\xF3 1 nota" : `Se descartaron ${n} notas`} porque sonaban m\xE1s de ${MAXIMO_DE_VOCES} voces a la vez en el mismo pentagrama.`);
91380
91568
  }
91381
91569
  if (avisos.cuantas("sin-cuerda") > 0) {
91382
91570
  avisos.decir(`Se descartaron ${avisos.cuantas("sin-cuerda")} nota(s) que no cab\xEDan en las cuerdas (dos notas en la misma cuerda a la vez).`);
@@ -91585,7 +91773,8 @@ function llenarVoz(voice, eventos, compas, indiceDeVoz, pista, cortesDeTresillo,
91585
91773
  beat.tupletNumerator = 3;
91586
91774
  beat.tupletDenominator = 2;
91587
91775
  }
91588
- for (const n of tramo.notas ?? []) {
91776
+ const sinGolpe = pista.clase === "bateria" && (!primera || tramo.continua);
91777
+ for (const n of sinGolpe ? [] : tramo.notas ?? []) {
91589
91778
  const note = new _barrel_exports$4.Note();
91590
91779
  if (pista.clase === "bateria") {
91591
91780
  note.percussionArticulation = indiceDeTecla.get(n.altura);
@@ -91767,7 +91956,7 @@ var init_importarMidi = __esm({
91767
91956
  "Sintetizador (sierra)",
91768
91957
  "Sintetizador (cal\xEDope)",
91769
91958
  "Sintetizador (chiff)",
91770
- "Sintetizador (charango)",
91959
+ "Sintetizador (charang)",
91771
91960
  "Sintetizador (voz)",
91772
91961
  "Sintetizador (quintas)",
91773
91962
  "Sintetizador (bajo y melod\xEDa)",
@@ -91855,6 +92044,16 @@ function formatoQueNecesita(documento) {
91855
92044
  const pentagramas = pista.get("pentagramas");
91856
92045
  if (typeof pentagramas === "number" && pentagramas > 1) return 5;
91857
92046
  }
92047
+ const conMarcas2 = (mapa) => {
92048
+ let hay = false;
92049
+ mapa.forEach((valor, clave) => {
92050
+ if (clave.endsWith(SUFIJO_SEGUNDA) && typeof valor === "string" && valor.includes("1")) hay = true;
92051
+ });
92052
+ return hay;
92053
+ };
92054
+ if (conMarcas2(documento.cabecera)) return 5;
92055
+ for (const grupo of [documento.compasesMaestros, documento.pistas, documento.figuras])
92056
+ for (const entidad of grupo.values()) if (conMarcas2(entidad)) return 5;
91858
92057
  return 4;
91859
92058
  }
91860
92059
  function empaquetar(documento, opciones = {}) {
@@ -91903,7 +92102,12 @@ function desempaquetar(bytes) {
91903
92102
  if (!manifiesto || typeof manifiesto !== "object") {
91904
92103
  throw new ArchivoIlegible("El manifiesto del archivo est\xE1 da\xF1ado.");
91905
92104
  }
91906
- if (!Number.isFinite(manifiesto.formato) || manifiesto.formato > VERSION_DEL_FORMATO) {
92105
+ if (typeof manifiesto.formato !== "number" || !Number.isFinite(manifiesto.formato)) {
92106
+ throw new ArchivoIlegible(
92107
+ "El manifiesto del archivo no dice de qu\xE9 versi\xF3n es. Abrirlo podr\xEDa borrarte lo que guard\xF3."
92108
+ );
92109
+ }
92110
+ if (manifiesto.formato > VERSION_DEL_FORMATO) {
91907
92111
  throw new ArchivoIlegible(
91908
92112
  `Este archivo se guard\xF3 con una versi\xF3n m\xE1s nueva de Mandioca Tabs (formato ${manifiesto.formato}; esta versi\xF3n entiende hasta el ${VERSION_DEL_FORMATO}). Abrirlo igual podr\xEDa borrarte lo que esa versi\xF3n guard\xF3.`
91909
92113
  );
@@ -92360,6 +92564,13 @@ function obraAbierta() {
92360
92564
  }
92361
92565
  return abierta;
92362
92566
  }
92567
+ function olvidarLaObra() {
92568
+ abierta = null;
92569
+ sigueViva = null;
92570
+ }
92571
+ function soltarLaObraDeLaPesta\u00F1a() {
92572
+ if (abierta?.origen.clase === "pesta\xF1a") olvidarLaObra();
92573
+ }
92363
92574
  async function abrirDeArchivo(ruta) {
92364
92575
  let bytes;
92365
92576
  try {
@@ -92499,7 +92710,7 @@ function avisoDeLoQueNoSuena(mudas, huerfanas) {
92499
92710
  const partes = [];
92500
92711
  if (mudas > 0) {
92501
92712
  partes.push(
92502
- `${mudas} grabaci\xF3n(es) viajaron con su onda pero sin audio: no est\xE1n cargadas en esta sesi\xF3n. El archivo las conserva como fichas.`
92713
+ mudas === 1 ? "1 grabaci\xF3n viaj\xF3 con su onda pero sin audio: no est\xE1 cargada en esta sesi\xF3n. El archivo la conserva como ficha." : `${mudas} grabaciones viajaron con su onda pero sin audio: no est\xE1n cargadas en esta sesi\xF3n. El archivo las conserva como fichas.`
92503
92714
  );
92504
92715
  }
92505
92716
  if (huerfanas > 0) {
@@ -92562,7 +92773,7 @@ var VERSION_DEL_CONECTOR;
92562
92773
  var init_version = __esm({
92563
92774
  "apps/mcp/src/version.ts"() {
92564
92775
  "use strict";
92565
- VERSION_DEL_CONECTOR = true ? "0.2.18" : null;
92776
+ VERSION_DEL_CONECTOR = true ? "0.2.19" : null;
92566
92777
  }
92567
92778
  });
92568
92779
 
@@ -92577,9 +92788,11 @@ __export(puente_exports, {
92577
92788
  SALA_DE_LA_OBRA: () => SALA_DE_LA_OBRA,
92578
92789
  SALA_DE_PEDIDOS: () => SALA_DE_PEDIDOS,
92579
92790
  SALA_OCIOSA_MS: () => SALA_OCIOSA_MS,
92791
+ VENCIMIENTO_DEL_OFRECIDO_MS: () => VENCIMIENTO_DEL_OFRECIDO_MS,
92580
92792
  abrirPuente: () => abrirPuente,
92581
92793
  buscarLaPesta\u00F1a: () => buscarLaPesta\u00F1a,
92582
92794
  cerrarPuente: () => cerrarPuente,
92795
+ cuantosArchivosOfrecidos: () => cuantosArchivosOfrecidos,
92583
92796
  elegirLaObra: () => elegirLaObra,
92584
92797
  exigirPesta\u00F1a: () => exigirPesta\u00F1a,
92585
92798
  obraDeLaEtiqueta: () => obraDeLaEtiqueta,
@@ -92592,6 +92805,7 @@ __export(puente_exports, {
92592
92805
  pesta\u00F1asAhora: () => pesta\u00F1asAhora,
92593
92806
  puenteAbierto: () => puenteAbierto,
92594
92807
  reservarArchivo: () => reservarArchivo,
92808
+ retirarArchivo: () => retirarArchivo,
92595
92809
  soltarReserva: () => soltarReserva
92596
92810
  });
92597
92811
  import { createServer } from "node:http";
@@ -92654,30 +92868,31 @@ function obraDeLaEtiqueta(etiqueta) {
92654
92868
  const buscada = etiqueta.trim().toUpperCase();
92655
92869
  return puenteAbierto().obras().find((o) => o.etiqueta === buscada) ?? null;
92656
92870
  }
92657
- function puenteAbierto() {
92871
+ function puenteAbierto(reclamar = true) {
92658
92872
  if (!puente) {
92659
92873
  throw new ProblemaConLaObra(
92660
92874
  "El puente con la aplicaci\xF3n no est\xE1 abierto. Usa `conectar_con_la_app` primero."
92661
92875
  );
92662
92876
  }
92663
- reclamado = true;
92877
+ if (reclamar) reclamado = true;
92664
92878
  return puente;
92665
92879
  }
92666
92880
  async function pedirQueLoSuelte(puerto) {
92667
92881
  try {
92668
92882
  const r = await fetch(`http://127.0.0.1:${puerto}/soltar`, { method: "POST" });
92669
- if (!r.ok) return false;
92883
+ if (r.status === 409) return "lo-usa-otra-ia";
92884
+ if (!r.ok) return "no-es-un-conector";
92670
92885
  for (let i2 = 0; i2 < 40; i2++) {
92671
92886
  try {
92672
92887
  await fetch(`http://127.0.0.1:${puerto}/version`);
92673
92888
  } catch {
92674
- return true;
92889
+ return "soltado";
92675
92890
  }
92676
92891
  await new Promise((listo) => setTimeout(listo, 50));
92677
92892
  }
92678
- return false;
92893
+ return "no-es-un-conector";
92679
92894
  } catch {
92680
- return false;
92895
+ return "no-es-un-conector";
92681
92896
  }
92682
92897
  }
92683
92898
  function exigirPesta\u00F1a() {
@@ -92689,7 +92904,7 @@ function exigirPesta\u00F1a() {
92689
92904
  }
92690
92905
  return p;
92691
92906
  }
92692
- function ofrecerArchivo(ruta) {
92907
+ function ofrecerArchivo(ruta, venceEnMs = VENCIMIENTO_DEL_OFRECIDO_MS) {
92693
92908
  let tama\u00F1o;
92694
92909
  try {
92695
92910
  const datos = statSync(ruta);
@@ -92701,8 +92916,15 @@ function ofrecerArchivo(ruta) {
92701
92916
  const ficha = randomUUID();
92702
92917
  const ofrecido = { ruta, nombre: basename2(ruta), bytes: tama\u00F1o };
92703
92918
  mostrador.set(ficha, ofrecido);
92919
+ setTimeout(() => mostrador.delete(ficha), venceEnMs).unref?.();
92704
92920
  return { ...ofrecido, ficha };
92705
92921
  }
92922
+ function retirarArchivo(ficha) {
92923
+ mostrador.delete(ficha);
92924
+ }
92925
+ function cuantosArchivosOfrecidos() {
92926
+ return mostrador.size;
92927
+ }
92706
92928
  function olvidarArchivosOfrecidos() {
92707
92929
  mostrador.clear();
92708
92930
  casillero.clear();
@@ -92764,7 +92986,7 @@ function recibirEnElCasillero(ficha, pedido, respuesta) {
92764
92986
  });
92765
92987
  pedido.pipe(salida);
92766
92988
  }
92767
- function atenderElMostrador(pedido, respuesta) {
92989
+ function atenderElMostrador(pedido, respuesta, puerto) {
92768
92990
  respuesta.setHeader("Access-Control-Allow-Origin", "*");
92769
92991
  const ruta = new URL(pedido.url ?? "", "http://127.0.0.1").pathname;
92770
92992
  if (pedido.method === "OPTIONS") {
@@ -92774,6 +92996,12 @@ function atenderElMostrador(pedido, respuesta) {
92774
92996
  return;
92775
92997
  }
92776
92998
  if (ruta === "/version") {
92999
+ respuesta.removeHeader("Access-Control-Allow-Origin");
93000
+ const origen = pedido.headers.origin;
93001
+ if (origen !== void 0 && origenPermitido(origen, puerto)) {
93002
+ respuesta.setHeader("Access-Control-Allow-Origin", origen);
93003
+ }
93004
+ respuesta.setHeader("Vary", "Origin");
92777
93005
  respuesta.writeHead(200, { "Content-Type": "application/json" });
92778
93006
  respuesta.end(JSON.stringify({ version: VERSION_DEL_CONECTOR }));
92779
93007
  return;
@@ -92921,7 +93149,7 @@ function abrirDeVerdad(puerto) {
92921
93149
  };
92922
93150
  for (const [sala, doc] of docs) escuchar(sala, doc);
92923
93151
  return new Promise((resolver, rechazar) => {
92924
- const http = createServer(atenderElMostrador);
93152
+ const http = createServer((pedido, respuesta) => atenderElMostrador(pedido, respuesta, puerto));
92925
93153
  const servidor2 = new import_websocket_server.default({
92926
93154
  server: http,
92927
93155
  // Ver `origenPermitido`: una página de otro sitio recibe un 401 y no llega a la obra.
@@ -92959,6 +93187,13 @@ function abrirDeVerdad(puerto) {
92959
93187
  writeSyncStep1(saludo, doc);
92960
93188
  conexion.send(toUint8Array(saludo));
92961
93189
  conexion.on("message", (crudo) => {
93190
+ try {
93191
+ atenderElMensaje(crudo);
93192
+ } catch {
93193
+ conexion.close(1003, "mensaje ilegible");
93194
+ }
93195
+ });
93196
+ const atenderElMensaje = (crudo) => {
92962
93197
  const bytes = new Uint8Array(crudo instanceof ArrayBuffer ? crudo : crudo);
92963
93198
  const lector = createDecoder(bytes);
92964
93199
  const tipo = readVarUint(lector);
@@ -92977,7 +93212,7 @@ function abrirDeVerdad(puerto) {
92977
93212
  if (otra !== conexion && otra.readyState === import_websocket.default.OPEN) otra.send(bytes);
92978
93213
  }
92979
93214
  }
92980
- });
93215
+ };
92981
93216
  conexion.on("close", () => {
92982
93217
  conexiones.delete(conexion);
92983
93218
  olvidarSiQuedoVacia(sala);
@@ -93158,7 +93393,7 @@ async function pedirALaPesta\u00F1a(herramienta, argumentos = {}, msDelAcuse = E
93158
93393
  canal.transact(() => mapa.delete(id2));
93159
93394
  }
93160
93395
  }
93161
- var envoltoriosPorDoc, SINCRONIZAR, PRESENCIA, SALA_DE_LA_OBRA, SALA_DE_PEDIDOS, PUERTO_POR_OMISION, ORIGENES_DE_TABS, PUERTOS_LOCALES_DE_TABS, PUERTO_DEL_CONECTOR, puente, reclamado, ESPERA_POR_LA_PESTA\u00D1A, mostrador, casillero, TIPOS, abriendo, siguienteId, INSTANCIA, ESPERA_DEL_ACUSE_MS, LATIDO_MS, LATIDO_DEL_PUENTE_MS, AUSENCIA_PARA_DARLA_POR_IDA_MS, SALA_OCIOSA_MS, MARGEN_PARA_QUE_ENTRE_MS;
93396
+ var envoltoriosPorDoc, SINCRONIZAR, PRESENCIA, SALA_DE_LA_OBRA, SALA_DE_PEDIDOS, PUERTO_POR_OMISION, ORIGENES_DE_TABS, PUERTOS_LOCALES_DE_TABS, PUERTO_DEL_CONECTOR, puente, reclamado, ESPERA_POR_LA_PESTA\u00D1A, mostrador, VENCIMIENTO_DEL_OFRECIDO_MS, casillero, TIPOS, abriendo, siguienteId, INSTANCIA, ESPERA_DEL_ACUSE_MS, LATIDO_MS, LATIDO_DEL_PUENTE_MS, AUSENCIA_PARA_DARLA_POR_IDA_MS, SALA_OCIOSA_MS, MARGEN_PARA_QUE_ENTRE_MS;
93162
93397
  var init_puente = __esm({
93163
93398
  "apps/mcp/src/puente.ts"() {
93164
93399
  "use strict";
@@ -93188,6 +93423,7 @@ var init_puente = __esm({
93188
93423
  reclamado = false;
93189
93424
  ESPERA_POR_LA_PESTA\u00D1A = 3500;
93190
93425
  mostrador = /* @__PURE__ */ new Map();
93426
+ VENCIMIENTO_DEL_OFRECIDO_MS = 10 * 6e4;
93191
93427
  casillero = /* @__PURE__ */ new Map();
93192
93428
  TIPOS = Object.fromEntries(
93193
93429
  FORMATOS_DE_GRABACION.map((f) => [f.extension, f.mime])
@@ -100707,6 +100943,9 @@ function proyectar(documento, opciones = {}) {
100707
100943
  const figurasPorCompas = /* @__PURE__ */ new Map();
100708
100944
  for (const [id2, entidad] of documento.figuras.entries()) {
100709
100945
  const figura = normalizarFigura(entidad.toJSON());
100946
+ if (!Number.isInteger(figura.voz) || figura.voz < 0 || figura.voz >= VOCES_MAXIMAS) continue;
100947
+ if (figura.tiempo < 0 || figura.tiempo > TIEMPO_MAXIMO) continue;
100948
+ if (figura.puntillos > PUNTILLOS_MAXIMOS) continue;
100710
100949
  const lista2 = figurasPorCompas.get(figura.compasId);
100711
100950
  if (lista2) lista2.push({ id: id2, datos: figura });
100712
100951
  else figurasPorCompas.set(figura.compasId, [{ id: id2, datos: figura }]);
@@ -100932,7 +101171,6 @@ function proyectar(documento, opciones = {}) {
100932
101171
  notaAlphaTab.trillValue = nota.trino;
100933
101172
  notaAlphaTab.trillSpeed = nota.velocidadDeTrino;
100934
101173
  notaAlphaTab.accidentalMode = nota.alteracion;
100935
- if (nota.ligaduraDeExpresion) notaAlphaTab.isEffectSlurOrigin = true;
100936
101174
  if (nota.bend.length > 0) {
100937
101175
  notaAlphaTab.bendType = _barrel_exports$4.BendType.Custom;
100938
101176
  notaAlphaTab.bendStyle = _barrel_exports$4.BendStyle.Default;
@@ -100975,6 +101213,7 @@ function proyectar(documento, opciones = {}) {
100975
101213
  if (marca.subrayar) anotarSubrayado(notaAlphaTab, marca.subrayar);
100976
101214
  golpe.addNote(notaAlphaTab);
100977
101215
  }
101216
+ if (notasDelGolpe2.some((n) => n.ligaduraDeExpresion)) golpe.isLegatoOrigin = true;
100978
101217
  voz.addBeat(golpe);
100979
101218
  }
100980
101219
  if (voz.beats.length === 0) {
@@ -101007,6 +101246,12 @@ function proyectar(documento, opciones = {}) {
101007
101246
  });
101008
101247
  }
101009
101248
  score.finish(ajustes);
101249
+ for (const track of score.tracks)
101250
+ for (const staff of track.staves)
101251
+ for (const bar of staff.bars)
101252
+ for (const voice of bar.voices)
101253
+ for (const beat of voice.beats)
101254
+ if (beat.isLegatoOrigin && (!beat.nextBeat || beat.nextBeat.isRest)) beat.isLegatoOrigin = false;
101010
101255
  aplicarSync(score, documento);
101011
101256
  return score;
101012
101257
  }
@@ -101048,7 +101293,7 @@ function pasarLetraALaFila(score) {
101048
101293
  golpe.text = null;
101049
101294
  }
101050
101295
  }
101051
- var VOCES_POR_COMPAS, volumenDeMezcla;
101296
+ var VOCES_POR_COMPAS, VOCES_MAXIMAS, TIEMPO_MAXIMO, PUNTILLOS_MAXIMOS, volumenDeMezcla;
101052
101297
  var init_proyectar = __esm({
101053
101298
  "packages/projection/src/proyectar.ts"() {
101054
101299
  init_alphaTab();
@@ -101059,6 +101304,9 @@ var init_proyectar = __esm({
101059
101304
  init_src2();
101060
101305
  init_src2();
101061
101306
  VOCES_POR_COMPAS = 4;
101307
+ VOCES_MAXIMAS = 16;
101308
+ TIEMPO_MAXIMO = 960 * 4 * 64;
101309
+ PUNTILLOS_MAXIMOS = 8;
101062
101310
  volumenDeMezcla = /* @__PURE__ */ new WeakMap();
101063
101311
  }
101064
101312
  });
@@ -125207,7 +125455,7 @@ var LEEN = /* @__PURE__ */ new Set([
125207
125455
  "obras_conectadas",
125208
125456
  "obras_recientes",
125209
125457
  "piezas_de_bateria",
125210
- "precision_del_alineador",
125458
+ // ⛔ (E10) `precision_del_alineador` ya NO está acá: con `precision` cambia un ajuste.
125211
125459
  "que_hace_un_control",
125212
125460
  "secciones_de_la_obra",
125213
125461
  "signos_de_una_nota",
@@ -125237,11 +125485,15 @@ var BORRAN = /* @__PURE__ */ new Set([
125237
125485
  "exportar_pdf",
125238
125486
  "exportar_video"
125239
125487
  ]);
125488
+ var BAJAN_MODELOS = /* @__PURE__ */ new Set(["separar_en_stems", "regenerar_stems", "sincronizar_letra"]);
125240
125489
  function anotacionesDe(nombre) {
125241
125490
  if (LEEN.has(nombre)) return { readOnlyHint: true, openWorldHint: false };
125242
- return { readOnlyHint: false, destructiveHint: BORRAN.has(nombre), openWorldHint: false };
125491
+ return { readOnlyHint: false, destructiveHint: BORRAN.has(nombre), openWorldHint: BAJAN_MODELOS.has(nombre) };
125243
125492
  }
125244
125493
 
125494
+ // apps/mcp/src/servidor.ts
125495
+ init_obra();
125496
+
125245
125497
  // apps/mcp/src/herramientas/documento.ts
125246
125498
  init_src2();
125247
125499
  init_src3();
@@ -125260,7 +125512,7 @@ function texto2(cosa) {
125260
125512
  function empezarUnPaso() {
125261
125513
  obraAbierta().documento.historial().stopCapturing();
125262
125514
  }
125263
- function situarse(pista, compasDelMusico, voz) {
125515
+ function situarse(pista, compasDelMusico, voz, pentagrama) {
125264
125516
  const { documento } = obraAbierta();
125265
125517
  documento.historial().stopCapturing();
125266
125518
  const indice = indexar(documento);
@@ -125272,7 +125524,11 @@ function situarse(pista, compasDelMusico, voz) {
125272
125524
  volcado,
125273
125525
  pistaId,
125274
125526
  compas: indiceDeCompas(compasDelMusico, indice),
125275
- voz: (voz ?? 1) - 1
125527
+ voz: (voz ?? 1) - 1,
125528
+ // ⛔ (E7, 2026-11-11) El pentagrama, desde 0 y comprobado contra la pista. Sin esto, a la
125529
+ // mano izquierda del piano sólo llegaban `escribir_notas` y `borrar_notas`: una
125530
+ // duración, un signo o una sílaba pedidos ahí caían en la derecha.
125531
+ pentagrama: pentagramaDelMusico(pentagrama, indice, pistaId, pista ?? 1)
125276
125532
  };
125277
125533
  }
125278
125534
  function indiceDeCompas(numeroDelMusico, indice) {
@@ -125337,6 +125593,7 @@ function ticsDelGolpe(indice, pistaId, compas, voz, golpe, tics, pentagrama = 0)
125337
125593
  function cursorEn(indice, pistaId, compas, voz, tiempo, cuerda, pentagrama = 0) {
125338
125594
  return { ...cursorInicial(indice, pistaId), compas, voz, tiempo, cuerda, ...pentagrama > 0 ? { pentagrama } : {} };
125339
125595
  }
125596
+ var campoDePentagrama = external_exports.number().int().min(1).max(4).optional().describe("En qu\xE9 pentagrama de la pista, desde 1. S\xF3lo el piano tiene dos: 1 la mano derecha (arriba), 2 la izquierda. Por omisi\xF3n el 1.");
125340
125597
  function pentagramaDelMusico(pedido, indice, pistaId, numeroDePista) {
125341
125598
  if (pedido === void 0) return 0;
125342
125599
  const tiene = indice.pentagramasDe(pistaId);
@@ -125377,7 +125634,7 @@ var unaNota = {
125377
125634
  golpe: external_exports.number().int().min(1).max(999).optional(),
125378
125635
  tics: external_exports.number().int().min(0).max(99999).optional(),
125379
125636
  voz: external_exports.number().int().min(1).max(4).optional(),
125380
- pentagrama: external_exports.number().int().min(1).max(4).optional().describe("En qu\xE9 pentagrama de la pista, desde 1. S\xF3lo el piano tiene dos: 1 la mano derecha (arriba), 2 la izquierda. Por omisi\xF3n el 1.")
125637
+ pentagrama: campoDePentagrama
125381
125638
  };
125382
125639
  function piezaPorNombre(nombre) {
125383
125640
  const plano = (t) => t.normalize("NFD").replace(/[̀-ͯ]/g, "").trim().toLowerCase();
@@ -125796,16 +126053,24 @@ function registrarHerramientasDelDocumento(servidor2) {
125796
126053
  let borradas = 0;
125797
126054
  const laPista = pistaDelMusico(pista ?? 1, volcar(documento), documento);
125798
126055
  const pistaId = laPista.id;
125799
- for (const nota of notas) {
125800
- const indice = indexar(documento);
125801
- const compas = indiceDeCompas(nota.compas, indice);
125802
- const voz = (nota.voz ?? 1) - 1;
125803
- const pentagrama = pentagramaDelMusico(nota.pentagrama, indice, pistaId, pista ?? 1);
125804
- const tiempo = ticsDelGolpe(indice, pistaId, compas, voz, nota.golpe, nota.tics, pentagrama);
125805
- const nombrada = comoSeNombraLaNota(laPista.tipo, laPista.nombre, pista ?? 1, nota);
125806
- const cursor = cursorEn(indice, pistaId, compas, voz, tiempo, nombrada.clase === "trastes" ? nombrada.cuerda : 1, pentagrama);
125807
- const salio = nombrada.clase === "altura" ? borrarAltura(documento, indice, cursor, nombrada.altura) : nombrada.clase === "percusion" ? borrarPercusion(documento, indice, cursor, nombrada.articulacion) : borrarNotaDeCuerda(documento, indice, cursor, nombrada.cuerda);
125808
- if (salio) borradas += 1;
126056
+ try {
126057
+ for (const nota of notas) {
126058
+ const indice = indexar(documento);
126059
+ const compas = indiceDeCompas(nota.compas, indice);
126060
+ const voz = (nota.voz ?? 1) - 1;
126061
+ const pentagrama = pentagramaDelMusico(nota.pentagrama, indice, pistaId, pista ?? 1);
126062
+ const tiempo = ticsDelGolpe(indice, pistaId, compas, voz, nota.golpe, nota.tics, pentagrama);
126063
+ const nombrada = comoSeNombraLaNota(laPista.tipo, laPista.nombre, pista ?? 1, nota);
126064
+ const cursor = cursorEn(indice, pistaId, compas, voz, tiempo, nombrada.clase === "trastes" ? nombrada.cuerda : 1, pentagrama);
126065
+ const salio = nombrada.clase === "altura" ? borrarAltura(documento, indice, cursor, nombrada.altura) : nombrada.clase === "percusion" ? borrarPercusion(documento, indice, cursor, nombrada.articulacion) : borrarNotaDeCuerda(documento, indice, cursor, nombrada.cuerda);
126066
+ if (salio) borradas += 1;
126067
+ }
126068
+ } catch (e) {
126069
+ if (borradas > 0) documento.historial().undo();
126070
+ const motivo = e instanceof Error ? e.message : String(e);
126071
+ throw new ProblemaConLaObra(
126072
+ borradas > 0 ? `${motivo} No se borr\xF3 ninguna nota de esta tanda: las ${borradas} anteriores volvieron.` : motivo
126073
+ );
125809
126074
  }
125810
126075
  return texto2({ pedidas: notas.length, borradas });
125811
126076
  }
@@ -125853,11 +126118,23 @@ function registrarHerramientasDelPuente(servidor2) {
125853
126118
  },
125854
126119
  async ({ puerto }) => {
125855
126120
  const elPuerto = puerto ?? PUERTO_POR_OMISION;
126121
+ let abierto;
125856
126122
  try {
125857
- await abrirPuente(elPuerto);
126123
+ abierto = await abrirPuente(elPuerto);
125858
126124
  } catch (causa) {
125859
- if (!await pedirQueLoSuelte(elPuerto)) throw causa;
125860
- await abrirPuente(elPuerto);
126125
+ const respuesta = await pedirQueLoSuelte(elPuerto);
126126
+ if (respuesta === "lo-usa-otra-ia") {
126127
+ throw new ProblemaConLaObra(
126128
+ `El puerto ${elPuerto} lo est\xE1 usando otra IA conectada a Mandioca Tabs (otra sesi\xF3n o ventana). Que el m\xFAsico la desconecte desde ah\xED con \`desconectar_de_la_app\`, o la cierre, y vuelve a intentarlo.`
126129
+ );
126130
+ }
126131
+ if (respuesta !== "soltado") throw causa;
126132
+ abierto = await abrirPuente(elPuerto);
126133
+ }
126134
+ if (puerto !== void 0 && abierto.puerto !== puerto) {
126135
+ throw new ProblemaConLaObra(
126136
+ `El puente ya est\xE1 abierto en el puerto ${abierto.puerto}, no en el ${puerto}. Para cambiar de puerto, primero \`desconectar_de_la_app\`.`
126137
+ );
125861
126138
  }
125862
126139
  const p = await buscarLaPesta\u00F1a() ?? puenteAbierto();
125863
126140
  const conectadas = p.obras();
@@ -125871,7 +126148,7 @@ function registrarHerramientasDelPuente(servidor2) {
125871
126148
  pesta\u00F1asConectadas: p.pesta\u00F1as(),
125872
126149
  // ⚠️ Se dice lo que falta, sin adornarlo. Si no hay pestaña, el asistente tiene que
125873
126150
  // saberlo ANTES de prometerle algo al músico — no descubrirlo al primer fallo.
125874
- siguiente: conectadas.length > 1 ? `Hay ${conectadas.length} obras abiertas. Mira \`obras_conectadas\` y elige una con \`elegir_obra\`, por su letra, antes de escribir nada.` : p.pesta\u00F1as() > 0 ? "Ya hay una pesta\xF1a conectada: la obra que se lee y se escribe es la suya." : "Todav\xEDa no hay ninguna pesta\xF1a. P\xEDdele que abra Mandioca Tabs y active P\xEDdele que apriete \xABConectar IA\xBB, el bot\xF3n de la barra de estado (abajo del todo, dice \xABConectar IA\xBB)."
126151
+ siguiente: conectadas.length > 1 ? `Hay ${conectadas.length} obras abiertas. Mira \`obras_conectadas\` y elige una con \`elegir_obra\`, por su letra, antes de escribir nada.` : p.pesta\u00F1as() > 0 ? "Ya hay una pesta\xF1a conectada: la obra que se lee y se escribe es la suya." : "Todav\xEDa no hay ninguna pesta\xF1a. P\xEDdele que abra Mandioca Tabs y apriete \xABConectar IA\xBB, el bot\xF3n de la barra de estado (abajo del todo)."
125875
126152
  });
125876
126153
  }
125877
126154
  );
@@ -125883,7 +126160,7 @@ function registrarHerramientasDelPuente(servidor2) {
125883
126160
  inputSchema: {}
125884
126161
  },
125885
126162
  () => {
125886
- const p = puenteAbierto();
126163
+ const p = puenteAbierto(false);
125887
126164
  const conectadas = p.obras();
125888
126165
  const idActivo = idDeLaObraActiva();
125889
126166
  const idDeLaActiva = idActivo ?? (conectadas.length === 1 ? conectadas[0].id : null);
@@ -125930,7 +126207,7 @@ function registrarHerramientasDelPuente(servidor2) {
125930
126207
  inputSchema: {}
125931
126208
  },
125932
126209
  () => {
125933
- const p = puenteAbierto();
126210
+ const p = puenteAbierto(false);
125934
126211
  const pesta\u00F1as = p.pesta\u00F1as();
125935
126212
  if (pesta\u00F1as === 0) {
125936
126213
  return texto3({
@@ -125988,6 +126265,7 @@ function registrarHerramientasDelPuente(servidor2) {
125988
126265
  inputSchema: {}
125989
126266
  },
125990
126267
  async () => {
126268
+ soltarLaObraDeLaPesta\u00F1a();
125991
126269
  await cerrarPuente();
125992
126270
  return texto3({ desconectado: true });
125993
126271
  }
@@ -125996,7 +126274,7 @@ function registrarHerramientasDelPuente(servidor2) {
125996
126274
  "sonar",
125997
126275
  {
125998
126276
  title: "Reproducir",
125999
- description: "Le pide a la aplicaci\xF3n que reproduzca la obra desde donde est\xE1 el cursor. Necesita la pesta\xF1a conectada: el sonido s\xF3lo existe en el navegador. \u26A0\uFE0F Si el sonido de alg\xFAn instrumento todav\xEDa se est\xE1 cargando, NO suena y contesta con un error que nombra la pista que falta: esper\xE1 unos segundos y vuelve a pedirlo. Nunca suena a medias.",
126277
+ description: "Le pide a la aplicaci\xF3n que reproduzca la obra desde donde est\xE1 el cursor. Necesita la pesta\xF1a conectada: el sonido s\xF3lo existe en el navegador. \u26A0\uFE0F Si el sonido de alg\xFAn instrumento todav\xEDa se est\xE1 cargando, NO suena y contesta con un error que nombra la pista que falta: espera unos segundos y vuelve a pedirlo. Nunca suena a medias.",
126000
126278
  inputSchema: {}
126001
126279
  },
126002
126280
  async () => texto3(await pedirALaPesta\u00F1a("sonar"))
@@ -126199,7 +126477,7 @@ function registrarHerramientasDelPuente(servidor2) {
126199
126477
  "quitar_grabacion",
126200
126478
  {
126201
126479
  title: "Sacar la grabaci\xF3n de la sesi\xF3n",
126202
- description: "Deja de sonar la grabaci\xF3n de referencia. \u26A0\uFE0F **NO toca el documento**: los puntos de sincronizaci\xF3n y la onda guardada siguen donde estaban. Es \xABno quiero o\xEDr esto ahora\xBB, no \xABborr\xE1 mi trabajo\xBB.",
126480
+ description: "Deja de sonar la grabaci\xF3n de referencia. \u26A0\uFE0F **NO toca el documento**: los puntos de sincronizaci\xF3n y la onda guardada siguen donde estaban. Es \xABno quiero o\xEDr esto ahora\xBB, no \xABborra mi trabajo\xBB.",
126203
126481
  inputSchema: {}
126204
126482
  },
126205
126483
  async () => texto3(await pedirALaPesta\u00F1a("quitar_grabacion"))
@@ -126602,7 +126880,7 @@ function registrarHerramientasDelPuente(servidor2) {
126602
126880
  "sincronizar_letra",
126603
126881
  {
126604
126882
  title: "Alinear una letra contra la grabaci\xF3n",
126605
- description: "Pega cada s\xEDlaba de la letra al instante en que se canta, aline\xE1ndola contra la grabaci\xF3n **elegida**. Es F04. \u26A0\uFE0F\u26A0\uFE0F **TARDA MINUTOS** \u2014medido: unos 0,85 s por segundo de audio, o sea ~3 min para un tema de 3:30\u2014 y baja un modelo la primera vez. \u26A0\uFE0F Se alinea contra la grabaci\xF3n que est\xE9 ELEGIDA: conviene poner el stem de voz con `elegir_grabacion` antes, porque el modelo est\xE1 entrenado con habla y la mezcla completa lo confunde. \u26A0\uFE0F Esto NO es `poner_letra`: aquello escribe la s\xEDlaba en el pliego y no necesita audio; esto le pone milisegundos. Son dos letras distintas. \u26A0\uFE0F Tampoco es `guardar_letra`, que deja el texto puesto sin alinear nada y no tarda nada: si lo que se quiere es dejarle la letra lista al m\xFAsico, es aqu\xE9lla.",
126883
+ description: "Pega cada s\xEDlaba de la letra al instante en que se canta, aline\xE1ndola contra la grabaci\xF3n **elegida**. \u26A0\uFE0F\u26A0\uFE0F **TARDA MINUTOS** \u2014medido: unos 0,85 s por segundo de audio, o sea ~3 min para un tema de 3:30\u2014 y baja un modelo la primera vez. \u26A0\uFE0F Se alinea contra la grabaci\xF3n que est\xE9 ELEGIDA: conviene poner el stem de voz con `elegir_grabacion` antes, porque el modelo est\xE1 entrenado con habla y la mezcla completa lo confunde. \u26A0\uFE0F Esto NO es `poner_letra`: aquello escribe la s\xEDlaba en el pliego y no necesita audio; esto le pone milisegundos. Son dos letras distintas. \u26A0\uFE0F Tampoco es `guardar_letra`, que deja el texto puesto sin alinear nada y no tarda nada: si lo que se quiere es dejarle la letra lista al m\xFAsico, es aqu\xE9lla.",
126606
126884
  inputSchema: {
126607
126885
  texto: external_exports.string().min(1).optional().describe(
126608
126886
  "La letra completa. Se parte en s\xEDlabas sola; `silabificar` sirve para verla antes. \u26A0\uFE0F **Si se omite, se usa la letra ESTACIONADA en la obra** (`guardar_letra`), que es la que el m\xFAsico tiene escrita en su cuadro. Mandarla ac\xE1 igual sirve para alinear otra cosa, pero entonces el resultado no va a coincidir con lo que \xE9l est\xE1 viendo."
@@ -126614,7 +126892,20 @@ function registrarHerramientasDelPuente(servidor2) {
126614
126892
  hasta: external_exports.number().optional().describe("En qu\xE9 segundo de la grabaci\xF3n termina el tramo.")
126615
126893
  }
126616
126894
  },
126617
- async (args2) => texto3(await pedirALaPesta\u00F1a("sincronizar_letra", args2))
126895
+ async (args2) => {
126896
+ const { desde, hasta } = args2;
126897
+ if (desde === void 0 !== (hasta === void 0)) {
126898
+ throw new ProblemaConLaObra(
126899
+ "Un tramo va con `desde` y `hasta` juntos. Para la grabaci\xF3n entera, sin ninguno de los dos."
126900
+ );
126901
+ }
126902
+ if (desde !== void 0 && hasta !== void 0 && hasta <= desde) {
126903
+ throw new ProblemaConLaObra(
126904
+ `El tramo va al rev\xE9s o est\xE1 vac\xEDo: empieza en el segundo ${desde} y termina en el ${hasta}.`
126905
+ );
126906
+ }
126907
+ return texto3(await pedirALaPesta\u00F1a("sincronizar_letra", args2));
126908
+ }
126618
126909
  );
126619
126910
  servidor2.registerTool(
126620
126911
  "audios_rechazados_por_largos",
@@ -126637,14 +126928,18 @@ function registrarHerramientasDelPuente(servidor2) {
126637
126928
  async ({ ruta }) => {
126638
126929
  exigirPesta\u00F1a();
126639
126930
  const ofrecido = ofrecerArchivo(ruta);
126640
- return texto3(
126641
- await pedirALaPesta\u00F1a("cargar_tipografia", {
126642
- nombre: ofrecido.nombre,
126643
- ruta,
126644
- bytes: ofrecido.bytes,
126645
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126646
- })
126647
- );
126931
+ try {
126932
+ return texto3(
126933
+ await pedirALaPesta\u00F1a("cargar_tipografia", {
126934
+ nombre: ofrecido.nombre,
126935
+ ruta,
126936
+ bytes: ofrecido.bytes,
126937
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126938
+ })
126939
+ );
126940
+ } finally {
126941
+ retirarArchivo(ofrecido.ficha);
126942
+ }
126648
126943
  }
126649
126944
  );
126650
126945
  servidor2.registerTool(
@@ -126715,16 +127010,20 @@ function registrarHerramientasDelPuente(servidor2) {
126715
127010
  async ({ ruta }) => {
126716
127011
  exigirPesta\u00F1a();
126717
127012
  const ofrecido = ofrecerArchivo(ruta);
126718
- return texto3(
126719
- await pedirALaPesta\u00F1a("abrir_grabacion", {
126720
- nombre: ofrecido.nombre,
126721
- ruta,
126722
- bytes: ofrecido.bytes,
126723
- // ★ La pestaña recibe una DIRECCIÓN, no los bytes. Los bytes viajan por HTTP, en
126724
- // trozos, y sólo si el músico acepta.
126725
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126726
- })
126727
- );
127013
+ try {
127014
+ return texto3(
127015
+ await pedirALaPesta\u00F1a("abrir_grabacion", {
127016
+ nombre: ofrecido.nombre,
127017
+ ruta,
127018
+ bytes: ofrecido.bytes,
127019
+ // ★ La pestaña recibe una DIRECCIÓN, no los bytes. Los bytes viajan por HTTP, en
127020
+ // trozos, y sólo si el músico acepta.
127021
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
127022
+ })
127023
+ );
127024
+ } finally {
127025
+ retirarArchivo(ofrecido.ficha);
127026
+ }
126728
127027
  }
126729
127028
  );
126730
127029
  servidor2.registerTool(
@@ -126739,13 +127038,17 @@ function registrarHerramientasDelPuente(servidor2) {
126739
127038
  async ({ ruta }) => {
126740
127039
  exigirPesta\u00F1a();
126741
127040
  const ofrecido = ofrecerArchivo(ruta);
126742
- return texto3(
126743
- await pedirALaPesta\u00F1a("abrir_en_la_app", {
126744
- nombre: ofrecido.nombre,
126745
- ruta,
126746
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126747
- })
126748
- );
127041
+ try {
127042
+ return texto3(
127043
+ await pedirALaPesta\u00F1a("abrir_en_la_app", {
127044
+ nombre: ofrecido.nombre,
127045
+ ruta,
127046
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
127047
+ })
127048
+ );
127049
+ } finally {
127050
+ retirarArchivo(ofrecido.ficha);
127051
+ }
126749
127052
  }
126750
127053
  );
126751
127054
  }
@@ -126800,10 +127103,11 @@ function registrarHerramientasDeSignos(servidor2) {
126800
127103
  tics: external_exports.number().int().min(0).max(99999).optional(),
126801
127104
  pista: external_exports.number().int().min(1).max(999).optional(),
126802
127105
  voz: external_exports.number().int().min(1).max(4).optional(),
127106
+ pentagrama: campoDePentagrama,
126803
127107
  valor: external_exports.number().optional().describe("Para los signos que tienen grados (digitaci\xF3n, tipo de arm\xF3nico\u2026). Sin esto, alterna.")
126804
127108
  }
126805
127109
  },
126806
- ({ clave, compas, cuerda, golpe, tics, pista, voz, valor }) => {
127110
+ ({ clave, compas, cuerda, golpe, tics, pista, voz, pentagrama, valor }) => {
126807
127111
  const elegido = signo(clave);
126808
127112
  if (!elegido) {
126809
127113
  throw new ProblemaConLaObra(
@@ -126815,9 +127119,9 @@ function registrarHerramientasDeSignos(servidor2) {
126815
127119
  `\xAB${elegido.nombre}\xBB es un signo de NOTA: vive en una cuerda concreta, as\xED que hay que decir cu\xE1l. Los signos del golpe entero \u2014acentos, palm mute\u2014 no la necesitan.`
126816
127120
  );
126817
127121
  }
126818
- const sitio = situarse(pista, compas, voz);
126819
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126820
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda ?? 1);
127122
+ const sitio = situarse(pista, compas, voz, pentagrama);
127123
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127124
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda ?? 1, sitio.pentagrama);
126821
127125
  const resultado = aplicarSigno(sitio.documento, sitio.indice, cursor, clave, valor);
126822
127126
  if (!resultado.hecho) {
126823
127127
  throw new ProblemaConLaObra(
@@ -126847,18 +127151,19 @@ function registrarHerramientasDeSignos(servidor2) {
126847
127151
  tics: external_exports.number().int().min(0).max(99999).optional(),
126848
127152
  pista: external_exports.number().int().min(1).max(999).optional(),
126849
127153
  voz: external_exports.number().int().min(1).max(4).optional(),
127154
+ pentagrama: campoDePentagrama,
126850
127155
  color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/, { error: (i2) => `\xAB${String(i2.input)}\xBB no es un color: va como #rrggbb, por ejemplo #c62026.` }).optional().describe("El color como #rrggbb. Obligatorio salvo para quitar."),
126851
127156
  como: external_exports.enum(["pintar", "subrayar"]).optional().describe("Pintar la nota (por omisi\xF3n) o subrayarla."),
126852
127157
  quitar: external_exports.boolean().optional().describe("true = quitarle la marca a la nota.")
126853
127158
  }
126854
127159
  },
126855
- ({ compas, cuerda, golpe, tics, pista, voz, color, como, quitar }) => {
127160
+ ({ compas, cuerda, golpe, tics, pista, voz, pentagrama, color, como, quitar }) => {
126856
127161
  if (!quitar && !color) {
126857
127162
  throw new ProblemaConLaObra("Falta el color (#rrggbb). Para sacar la marca, `quitar: true`.");
126858
127163
  }
126859
- const sitio = situarse(pista, compas, voz);
126860
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126861
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda);
127164
+ const sitio = situarse(pista, compas, voz, pentagrama);
127165
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127166
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda, sitio.pentagrama);
126862
127167
  const marca = quitar ? null : { color, subrayada: como === "subrayar" };
126863
127168
  const tocadas = ponerMarcaDeColor(sitio.documento, sitio.indice, cursor, marca);
126864
127169
  if (tocadas === 0) {
@@ -126866,7 +127171,7 @@ function registrarHerramientasDeSignos(servidor2) {
126866
127171
  `No hay nota en el comp\xE1s ${compas}, cuerda ${cuerda}. La marca va sobre una nota escrita: si ah\xED no hay nada, primero hay que escribirla.`
126867
127172
  );
126868
127173
  }
126869
- const leida = marcaDeColorDeLasNotas(situarse(pista, compas, voz).indice, cursor);
127174
+ const leida = marcaDeColorDeLasNotas(situarse(pista, compas, voz, pentagrama).indice, cursor);
126870
127175
  return texto2({
126871
127176
  compas,
126872
127177
  cuerda,
@@ -126886,13 +127191,14 @@ function registrarHerramientasDeSignos(servidor2) {
126886
127191
  golpe: external_exports.number().int().min(1).max(999).optional(),
126887
127192
  tics: external_exports.number().int().min(0).max(99999).optional(),
126888
127193
  pista: external_exports.number().int().min(1).max(999).optional(),
126889
- voz: external_exports.number().int().min(1).max(4).optional()
127194
+ voz: external_exports.number().int().min(1).max(4).optional(),
127195
+ pentagrama: campoDePentagrama
126890
127196
  }
126891
127197
  },
126892
- ({ compas, cuerda, golpe, tics, pista, voz }) => {
126893
- const sitio = situarse(pista, compas, voz);
126894
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126895
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda);
127198
+ ({ compas, cuerda, golpe, tics, pista, voz, pentagrama }) => {
127199
+ const sitio = situarse(pista, compas, voz, pentagrama);
127200
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127201
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda, sitio.pentagrama);
126896
127202
  const puestos = signosPuestos(sitio.indice, cursor);
126897
127203
  const marca = marcaDeColorDeLasNotas(sitio.indice, cursor).comun;
126898
127204
  return texto2({
@@ -126919,12 +127225,13 @@ var DONDE = {
126919
127225
  golpe: external_exports.number().int().min(1).max(999).optional(),
126920
127226
  tics: external_exports.number().int().min(0).max(99999).optional(),
126921
127227
  pista: external_exports.number().int().min(1).max(999).optional(),
126922
- voz: external_exports.number().int().min(1).max(4).optional()
127228
+ voz: external_exports.number().int().min(1).max(4).optional(),
127229
+ pentagrama: campoDePentagrama
126923
127230
  };
126924
127231
  function ubicar(donde) {
126925
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
126926
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
126927
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1);
127232
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
127233
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
127234
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1, sitio.pentagrama);
126928
127235
  let seleccion;
126929
127236
  if (donde.hasta !== void 0) {
126930
127237
  if (donde.hasta < donde.compas) {
@@ -126933,12 +127240,12 @@ function ubicar(donde) {
126933
127240
  );
126934
127241
  }
126935
127242
  const ultimo = indiceDeCompas(donde.hasta, sitio.indice);
126936
- const compasId = sitio.indice.compasId(sitio.pistaId, ultimo);
127243
+ const compasId = sitio.indice.compasId(sitio.pistaId, ultimo, sitio.pentagrama);
126937
127244
  const golpes = sitio.indice.figuras(compasId, sitio.voz);
126938
127245
  const finDelUltimo = golpes.length > 0 ? golpes[golpes.length - 1].datos.tiempo : 0;
126939
127246
  seleccion = extenderHasta(
126940
127247
  seleccionDesde(cursor),
126941
- cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, finDelUltimo, 1)
127248
+ cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, finDelUltimo, 1, sitio.pentagrama)
126942
127249
  );
126943
127250
  }
126944
127251
  return { ...sitio, cursor, seleccion };
@@ -127549,7 +127856,11 @@ function registrarHerramientasDeEstructura(servidor2) {
127549
127856
  "cambiar_metrica_del_compas",
127550
127857
  {
127551
127858
  title: "Cambiar la m\xE9trica de un comp\xE1s",
127552
- description: "Le cambia el comp\xE1s de amalgama a un comp\xE1s: 6/8, 3/4, 7/8\u2026 Vale de ah\xED en adelante hasta que otro comp\xE1s la vuelva a cambiar. \u26A0\uFE0F El denominador dice qu\xE9 figura lleva el pulso, as\xED que tiene que ser potencia de dos: no existe un comp\xE1s de x/5.",
127859
+ description: (
127860
+ // ⛔ (E14, 2026-11-11) Decía «vale de ahí en adelante», y `cambiarMetricaDelCompas`
127861
+ // cambia UN compás, como está decidido. El texto dice lo que hace.
127862
+ "Le cambia la m\xE9trica a UN comp\xE1s: 6/8, 3/4, 7/8\u2026 S\xF3lo a \xE9se: los que siguen quedan como estaban. Para cambiar varios, se llama una vez por comp\xE1s. \u26A0\uFE0F El denominador dice qu\xE9 figura lleva el pulso, as\xED que tiene que ser potencia de dos: no existe un comp\xE1s de x/5."
127863
+ ),
127553
127864
  inputSchema: {
127554
127865
  compas: external_exports.number().int().min(1).max(9999),
127555
127866
  numerador: external_exports.number().int().min(1).max(32).describe("Cu\xE1ntos pulsos entran: el 6 de 6/8."),
@@ -127850,9 +128161,9 @@ init_src3();
127850
128161
  init_obra();
127851
128162
  var portapapeles = null;
127852
128163
  function ubicar2(donde) {
127853
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
127854
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
127855
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1);
128164
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128165
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
128166
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1, sitio.pentagrama);
127856
128167
  if (donde.hasta === void 0) return { ...sitio, cursor, seleccion: void 0 };
127857
128168
  if (donde.hasta < donde.compas) {
127858
128169
  throw new ProblemaConLaObra(
@@ -127860,7 +128171,7 @@ function ubicar2(donde) {
127860
128171
  );
127861
128172
  }
127862
128173
  const ultimo = indiceDeCompas(donde.hasta, sitio.indice);
127863
- const compasId = sitio.indice.compasId(sitio.pistaId, ultimo);
128174
+ const compasId = sitio.indice.compasId(sitio.pistaId, ultimo, sitio.pentagrama);
127864
128175
  const golpes = sitio.indice.figuras(compasId, sitio.voz);
127865
128176
  const fin = golpes.length > 0 ? golpes[golpes.length - 1].datos.tiempo : 0;
127866
128177
  return {
@@ -127868,7 +128179,7 @@ function ubicar2(donde) {
127868
128179
  cursor,
127869
128180
  seleccion: extenderHasta(
127870
128181
  seleccionDesde(cursor),
127871
- cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, fin, donde.cuerda ?? 1)
128182
+ cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, fin, donde.cuerda ?? 1, sitio.pentagrama)
127872
128183
  )
127873
128184
  };
127874
128185
  }
@@ -127878,7 +128189,8 @@ var RANGO = {
127878
128189
  golpe: external_exports.number().int().min(1).max(999).optional(),
127879
128190
  tics: external_exports.number().int().min(0).max(99999).optional(),
127880
128191
  pista: external_exports.number().int().min(1).max(999).optional(),
127881
- voz: external_exports.number().int().min(1).max(4).optional()
128192
+ voz: external_exports.number().int().min(1).max(4).optional(),
128193
+ pentagrama: campoDePentagrama
127882
128194
  };
127883
128195
  function registrarHerramientasDeMusica(servidor2) {
127884
128196
  servidor2.registerTool(
@@ -127886,7 +128198,7 @@ function registrarHerramientasDeMusica(servidor2) {
127886
128198
  {
127887
128199
  title: "Qu\xE9 acorde forman estas notas",
127888
128200
  description: "Deduce el cifrado de las notas que suenan juntas en una posici\xF3n, con Tonal.js. Sirve para saber qu\xE9 se escribi\xF3 sin tener que leerlo nota por nota.",
127889
- inputSchema: { compas: external_exports.number().int().min(1).max(9999), golpe: external_exports.number().int().min(1).max(999).optional(), tics: external_exports.number().int().min(0).max(99999).optional(), pista: external_exports.number().int().min(1).max(999).optional(), voz: external_exports.number().int().min(1).max(4).optional() }
128201
+ inputSchema: { compas: external_exports.number().int().min(1).max(9999), golpe: external_exports.number().int().min(1).max(999).optional(), tics: external_exports.number().int().min(0).max(99999).optional(), pista: external_exports.number().int().min(1).max(999).optional(), voz: external_exports.number().int().min(1).max(4).optional(), pentagrama: campoDePentagrama }
127890
128202
  },
127891
128203
  (donde) => {
127892
128204
  const sitio = ubicar2(donde);
@@ -127917,6 +128229,7 @@ function registrarHerramientasDeMusica(servidor2) {
127917
128229
  tics: external_exports.number().int().min(0).max(99999).optional(),
127918
128230
  pista: external_exports.number().int().min(1).max(999).optional(),
127919
128231
  voz: external_exports.number().int().min(1).max(4).optional(),
128232
+ pentagrama: campoDePentagrama,
127920
128233
  forma: external_exports.string().optional().describe("La letra de la digitaci\xF3n \u2014\xABA\xBB, \xABB\xBB, \xABC\xBB\u2026\u2014 tal como la informa `acordes_de_la_obra`. Si ese acorde se toca de una sola manera, su letra es \xABA\xBB."),
127921
128234
  segundaTipografia: campoDeSegundaTipografia,
127922
128235
  sinNombre: external_exports.boolean().optional().describe("true escribe s\xF3lo las notas de `forma`, sin el cifrado arriba: el Ctrl + clic de la paleta. Pide `forma` y un golpe sin notas.")
@@ -127999,7 +128312,8 @@ function registrarHerramientasDeMusica(servidor2) {
127999
128312
  golpe: external_exports.number().int().min(1).max(999).optional(),
128000
128313
  tics: external_exports.number().int().min(0).max(99999).optional(),
128001
128314
  pista: external_exports.number().int().min(1).max(999).optional(),
128002
- voz: external_exports.number().int().min(1).max(4).optional()
128315
+ voz: external_exports.number().int().min(1).max(4).optional(),
128316
+ pentagrama: campoDePentagrama
128003
128317
  }
128004
128318
  },
128005
128319
  (donde) => {
@@ -128040,7 +128354,13 @@ function registrarHerramientasDeMusica(servidor2) {
128040
128354
  "deshacer",
128041
128355
  {
128042
128356
  title: "Deshacer",
128043
- description: "Vuelve atr\xE1s un paso. Es el MISMO historial que el Ctrl+Z del m\xFAsico: si \xE9l escribi\xF3 lo \xFAltimo, esto deshace lo suyo.",
128357
+ description: (
128358
+ // ⛔ (E3, 2026-11-11) Decía que compartía el Ctrl+Z del músico, y no: el historial
128359
+ // sólo graba los cambios hechos desde este conector (`historial()` en
128360
+ // `documento.ts` acota por origen). Lo que el músico escribe llega por el puente
128361
+ // con otro origen y queda afuera.
128362
+ "Vuelve atr\xE1s un paso de lo que hizo la IA: cada herramienta que escribe es un paso. No deshace lo que escribi\xF3 el m\xFAsico; eso lo deshace \xE9l con su Ctrl+Z."
128363
+ ),
128044
128364
  inputSchema: { pasos: external_exports.number().int().min(1).max(50, { error: (i2) => i2.input === void 0 ? "Falta este dato." : `\xAB${i2.input}\xBB son demasiados pasos de una vez: van de 1 a 50.` }).optional() }
128045
128365
  },
128046
128366
  ({ pasos }) => {
@@ -128209,6 +128529,7 @@ function registrarHerramientasDeMusica(servidor2) {
128209
128529
  golpe: external_exports.number().int().min(1).max(999).optional(),
128210
128530
  pista: external_exports.number().int().min(1).max(999).optional(),
128211
128531
  voz: external_exports.number().int().min(1).max(4).optional(),
128532
+ pentagrama: campoDePentagrama,
128212
128533
  figura: external_exports.number().int().min(1).max(64, { error: (i2) => i2.input === void 0 ? "Falta este dato." : `\xAB${i2.input}\xBB no es una figura: 1 redonda, 2 blanca, 4 negra, 8 corchea, 16, 32, 64.` }).optional().describe("1 redonda, 2 blanca, 4 negra\u2026 Por omisi\xF3n, negra."),
128213
128534
  puntillos: external_exports.number().int().min(0).max(2).optional()
128214
128535
  }
@@ -128236,6 +128557,7 @@ function registrarHerramientasDeMusica(servidor2) {
128236
128557
  golpe: external_exports.number().int().min(1).max(999).optional(),
128237
128558
  pista: external_exports.number().int().min(1).max(999).optional(),
128238
128559
  voz: external_exports.number().int().min(1).max(4).optional(),
128560
+ pentagrama: campoDePentagrama,
128239
128561
  sinAcorde: external_exports.boolean().optional()
128240
128562
  }
128241
128563
  },
@@ -128527,7 +128849,8 @@ function registrarHerramientasDeMusica(servidor2) {
128527
128849
  golpe: external_exports.number().int().min(1).max(999).optional(),
128528
128850
  tics: external_exports.number().int().min(0).max(99999).optional(),
128529
128851
  pista: external_exports.number().int().min(1).max(999).optional(),
128530
- voz: external_exports.number().int().min(1).max(4).optional()
128852
+ voz: external_exports.number().int().min(1).max(4).optional(),
128853
+ pentagrama: campoDePentagrama
128531
128854
  }
128532
128855
  },
128533
128856
  ({ campo, variante, cuerda, ...donde }) => {
@@ -128579,7 +128902,8 @@ function registrarHerramientasDeMusica(servidor2) {
128579
128902
  golpe: external_exports.number().int().min(1).max(999).optional(),
128580
128903
  tics: external_exports.number().int().min(0).max(99999).optional(),
128581
128904
  pista: external_exports.number().int().min(1).max(999).optional(),
128582
- voz: external_exports.number().int().min(1).max(4).optional()
128905
+ voz: external_exports.number().int().min(1).max(4).optional(),
128906
+ pentagrama: campoDePentagrama
128583
128907
  }
128584
128908
  },
128585
128909
  ({ matiz, ...donde }) => {
@@ -128616,12 +128940,13 @@ var DONDE2 = {
128616
128940
  tics: external_exports.number().int().min(0).max(99999).optional(),
128617
128941
  pista: external_exports.number().int().min(1).max(999).optional(),
128618
128942
  voz: external_exports.number().int().min(1).max(4).optional(),
128619
- cuerda: external_exports.number().int().min(1).optional().describe("La cuerda donde est\xE1 la nota, desde la M\xC1S GRAVE empezando en 1.")
128943
+ cuerda: external_exports.number().int().min(1).optional().describe("La cuerda donde est\xE1 la nota, desde la M\xC1S GRAVE empezando en 1."),
128944
+ pentagrama: campoDePentagrama
128620
128945
  };
128621
128946
  function ubicarNota(donde) {
128622
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
128623
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
128624
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1);
128947
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128948
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
128949
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1, sitio.pentagrama);
128625
128950
  return { ...sitio, cursor, seleccion: void 0 };
128626
128951
  }
128627
128952
  function registrarHerramientasDeBend(servidor2) {
@@ -128830,7 +129155,9 @@ function registrarHerramientasDePistas(servidor2) {
128830
129155
  afinacion: external_exports.array(external_exports.number().int().min(0).max(127)).optional().describe(
128831
129156
  "Las notas al aire en n\xFAmeros MIDI, de la M\xC1S GRAVE a la m\xE1s aguda. Tiene que traer tantas como cuerdas tenga la pista. Ver `afinaciones_disponibles`."
128832
129157
  ),
128833
- capo: external_exports.number().int().min(0).optional().describe("Traste de la cejilla. 0 = sin cejilla."),
129158
+ // ⛔ (E8, 2026-11-11) Sin tope, `capo: 500` se aceptaba y la cuerda quedaba en MIDI 543.
129159
+ // El tope es el mismo traste máximo del editor (`MAX_TRASTE`).
129160
+ capo: external_exports.number().int().min(0).max(MAX_TRASTE, { error: (i2) => `\xAB${String(i2.input)}\xBB no es un traste para la cejilla: van de 0 a ${MAX_TRASTE}.` }).optional().describe(`Traste de la cejilla, de 0 a ${MAX_TRASTE}. 0 = sin cejilla.`),
128834
129161
  programaMidi: external_exports.number().int().min(0).max(127).optional().describe("El sonido, en General MIDI."),
128835
129162
  nombreSegundaTipografia: campoDeSegundaTipografia,
128836
129163
  nombreCortoSegundaTipografia: campoDeSegundaTipografia
@@ -128887,7 +129214,7 @@ function registrarHerramientasDePistas(servidor2) {
128887
129214
  "poner_letra",
128888
129215
  {
128889
129216
  title: "Escribir la letra sobre los golpes",
128890
- description: "Escribe una s\xEDlaba por golpe a partir de un comp\xE1s, que es como se escribe una letra en una tablatura. \u26A0\uFE0F **No crea golpes**: escribe sobre los que ya hay y devuelve cu\xE1ntas s\xEDlabas sobraron. Crear golpes para alojar palabras inventar\xEDa ritmo que nadie toc\xF3. \u26A0\uFE0F Una s\xEDlaba vac\xEDa en la lista SALTA ese golpe, que es c\xF3mo se dice \xABac\xE1 no va letra\xBB sin desalinear lo que sigue. \u26A0\uFE0F Esto NO es la letra sincronizada con el audio (F04): es el texto que se dibuja en el pliego, y no necesita ninguna grabaci\xF3n. Para partir un texto, `silabificar`.",
129217
+ description: "Escribe una s\xEDlaba por golpe a partir de un comp\xE1s, que es como se escribe una letra en una tablatura. \u26A0\uFE0F **No crea golpes**: escribe sobre los que ya hay y devuelve cu\xE1ntas s\xEDlabas sobraron. Crear golpes para alojar palabras inventar\xEDa ritmo que nadie toc\xF3. \u26A0\uFE0F Una s\xEDlaba vac\xEDa en la lista SALTA ese golpe, que es c\xF3mo se dice \xABac\xE1 no va letra\xBB sin desalinear lo que sigue. \u26A0\uFE0F Esto NO es la letra sincronizada con el audio: es el texto que se dibuja en el pliego, y no necesita ninguna grabaci\xF3n. Para partir un texto, `silabificar`.",
128891
129218
  inputSchema: {
128892
129219
  silabas: external_exports.array(external_exports.string()).describe("Las s\xEDlabas, en orden. Una vac\xEDa salta un golpe."),
128893
129220
  compas: external_exports.number().int().min(1).max(9999).default(1).describe("Desde qu\xE9 comp\xE1s empezar."),
@@ -128933,21 +129260,23 @@ function registrarHerramientasDePistas(servidor2) {
128933
129260
  tics: external_exports.number().int().min(0).max(99999).optional(),
128934
129261
  pista: external_exports.number().int().min(1).max(999).optional(),
128935
129262
  voz: external_exports.number().int().min(1).max(4).optional(),
129263
+ pentagrama: campoDePentagrama,
128936
129264
  segundaTipografia: campoDeSegundaTipografia
128937
129265
  }
128938
129266
  },
128939
129267
  ({ silaba, segundaTipografia, ...donde }) => {
128940
129268
  empezarUnPaso();
128941
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
129269
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128942
129270
  const tiempo = ticsDelGolpe(
128943
129271
  sitio.indice,
128944
129272
  sitio.pistaId,
128945
129273
  sitio.compas,
128946
129274
  sitio.voz,
128947
129275
  donde.golpe,
128948
- donde.tics
129276
+ donde.tics,
129277
+ sitio.pentagrama
128949
129278
  );
128950
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1);
129279
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1, sitio.pentagrama);
128951
129280
  const marcas = silaba ? marcasPedidas(silaba, segundaTipografia) : void 0;
128952
129281
  if (!ponerSilabaEnElGolpe(sitio.documento, sitio.indice, cursor, silaba, marcas)) {
128953
129282
  throw new ProblemaConLaObra(
@@ -128963,7 +129292,7 @@ function contarNotasDe(documento, numero2) {
128963
129292
  }
128964
129293
 
128965
129294
  // apps/mcp/src/herramientas/archivo.ts
128966
- import { writeFile as writeFile2, access } from "node:fs/promises";
129295
+ import { writeFile as writeFile2, access, rename as rename2, rm } from "node:fs/promises";
128967
129296
  import { extname as extname3 } from "node:path";
128968
129297
  init_obra();
128969
129298
  function registrarHerramientasDeArchivo(servidor2) {
@@ -128983,7 +129312,7 @@ function registrarHerramientasDeArchivo(servidor2) {
128983
129312
  "exportar",
128984
129313
  {
128985
129314
  title: "Exportar la obra",
128986
- description: "Escribe la obra en otro formato. La ruta va completa y con su extensi\xF3n. \u26A0\uFE0F No pisa un archivo que ya exista salvo que se pida expl\xEDcitamente.",
129315
+ description: "Escribe la obra en otro formato. La ruta va completa y con la extensi\xF3n del formato. No escribe un .mtabs: eso es `guardar_obra`. \u26A0\uFE0F No pisa un archivo que ya exista salvo que se pida expl\xEDcitamente.",
128987
129316
  inputSchema: {
128988
129317
  ruta: external_exports.string().describe("Ruta completa del archivo a escribir, con extensi\xF3n."),
128989
129318
  formato: external_exports.string().optional().describe("gp \xB7 musicxml \xB7 mxl \xB7 mid \xB7 alphatex. Por omisi\xF3n, se deduce de la extensi\xF3n."),
@@ -129003,6 +129332,17 @@ function registrarHerramientasDeArchivo(servidor2) {
129003
129332
  `No s\xE9 exportar a \xAB${formato ?? (extname3(ruta) || "(sin extensi\xF3n)")}\xBB. Los formatos son: ${claves.join(", ")}. Mira \`formatos_de_exportacion\`.`
129004
129333
  );
129005
129334
  }
129335
+ if (extension2 === "mtabs") {
129336
+ throw new ProblemaConLaObra(
129337
+ `\xAB${ruta}\xBB es un .mtabs: exportar no escribe la obra. Para guardarla est\xE1 \`guardar_obra\`.`
129338
+ );
129339
+ }
129340
+ const detalleElegido = FORMATOS_DE_EXPORTACION2.find((f) => f.clave === elegido);
129341
+ if (extension2 !== detalleElegido.extension) {
129342
+ throw new ProblemaConLaObra(
129343
+ `El formato ${elegido} se escribe con la extensi\xF3n .${detalleElegido.extension}, y \xAB${ruta}\xBB tiene ${extension2 ? `.${extension2}` : "ninguna"}. Cambia la ruta o el formato.`
129344
+ );
129345
+ }
129006
129346
  if (!pisar) {
129007
129347
  const yaEsta = await access(ruta).then(
129008
129348
  () => true,
@@ -129015,7 +129355,15 @@ function registrarHerramientasDeArchivo(servidor2) {
129015
129355
  }
129016
129356
  }
129017
129357
  const bytes = exportarComo2(proyectar2(documento), elegido);
129018
- await writeFile2(ruta, bytes);
129358
+ const parcial = `${ruta}.parcial`;
129359
+ try {
129360
+ await writeFile2(parcial, bytes);
129361
+ await rename2(parcial, ruta);
129362
+ } catch (e) {
129363
+ await rm(parcial, { force: true }).catch(() => {
129364
+ });
129365
+ throw new ProblemaConLaObra(`No se pudo escribir \xAB${ruta}\xBB: ${e.message}`);
129366
+ }
129019
129367
  const detalle = FORMATOS_DE_EXPORTACION2.find((f) => f.clave === elegido);
129020
129368
  return texto2({
129021
129369
  escrito: ruta,
@@ -129158,13 +129506,30 @@ DOS MANERAS DE LLEGAR A LA OBRA:
129158
129506
  aparece en su pantalla al instante, y lo que \xE9l escriba lo ves t\xFA.
129159
129507
  Si el m\xFAsico habla de "mi partitura" mientras tiene el programa abierto, quiere la segunda.
129160
129508
  `.trim();
129509
+ function cerrarElPaso() {
129510
+ try {
129511
+ obraAbierta().documento.historial().stopCapturing();
129512
+ } catch {
129513
+ }
129514
+ }
129161
129515
  function crearServidor() {
129162
129516
  const servidor2 = new McpServer(
129163
129517
  { name: NOMBRE, version: VERSION },
129164
129518
  { instructions: INSTRUCCIONES }
129165
129519
  );
129166
129520
  const registrar = servidor2.registerTool.bind(servidor2);
129167
- servidor2.registerTool = ((nombre, config2, cb) => registrar(nombre, { ...config2, annotations: { ...anotacionesDe(nombre), ...config2.annotations } }, cb));
129521
+ servidor2.registerTool = ((nombre, config2, cb) => registrar(
129522
+ nombre,
129523
+ { ...config2, annotations: { ...anotacionesDe(nombre), ...config2.annotations } },
129524
+ LEEN.has(nombre) ? cb : async (...a) => {
129525
+ cerrarElPaso();
129526
+ try {
129527
+ return await cb(...a);
129528
+ } finally {
129529
+ cerrarElPaso();
129530
+ }
129531
+ }
129532
+ ));
129168
129533
  registrarHerramientasDelDocumento(servidor2);
129169
129534
  registrarHerramientasDelPuente(servidor2);
129170
129535
  registrarHerramientasDeSignos(servidor2);