xonecode 0.38.4 → 0.38.11

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 (35) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-ClGfUHp0.js → EditorDeFichero-C_95A4sB.js} +1 -1
  2. package/apps/web/dist/assets/index-CAqQp65A.js +506 -0
  3. package/apps/web/dist/assets/index-tzWrUZYU.css +1 -0
  4. package/apps/web/dist/assets/{php-DFC2MqmC.js → php-CkVQ0jw6.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-BfhTj9bK.js → ruby-m3xVWkBq.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/config/settingsEnDisco.js +44 -4
  8. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  9. package/dist/agent/tareas/tareasEnDisco.js +23 -1
  10. package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
  11. package/dist/core/dispositivoDeSesion.js +2 -7
  12. package/dist/core/dispositivoDeSesion.js.map +1 -1
  13. package/dist/core/dispositivoElegido.js +22 -0
  14. package/dist/core/dispositivoElegido.js.map +1 -0
  15. package/dist/core/puertosDeAvd.js +53 -8
  16. package/dist/core/puertosDeAvd.js.map +1 -1
  17. package/dist/core/settings.js +21 -1
  18. package/dist/core/settings.js.map +1 -1
  19. package/dist/core/tareas.js +29 -0
  20. package/dist/core/tareas.js.map +1 -1
  21. package/dist/web/servidor/arranque.js +192 -12
  22. package/dist/web/servidor/arranque.js.map +1 -1
  23. package/dist/web/servidor/corredorDeTareas.js +27 -5
  24. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  25. package/dist/web/servidor/transporte.js.map +1 -1
  26. package/dist/web/servidor/vestibulo.js +26 -1
  27. package/dist/web/servidor/vestibulo.js.map +1 -1
  28. package/package.json +1 -1
  29. package/skills/xone-hotswap/SKILL.md +4 -2
  30. package/skills/xone-hotswap/lib/dispositivo.mjs +106 -1
  31. package/skills/xone-hotswap/scripts/xone-desplegar-android +3 -1
  32. package/skills/xone-hotswap/scripts/xone-hotswap +4 -1
  33. package/skills/xone-hotswap/scripts/xone-reiniciar-android +3 -1
  34. package/apps/web/dist/assets/index-BKbTb9C_.js +0 -506
  35. package/apps/web/dist/assets/index-QE7QDed9.css +0 -1
@@ -57,7 +57,7 @@ import { motivoDeDominioInaceptable } from "../../core/tunel.js";
57
57
  import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
58
58
  import { arrancarEmulador, pararEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
59
59
  import { borrarAvd, clonarAvd, correrPasoDeReceta, crearAvd } from "../../agent/dispositivos/instalacionEnMaquina.js";
60
- import { asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeAvd, PUERTO_DEL_HOTSWAP, } from "../../core/puertosDeAvd.js";
60
+ import { asignarPuertosDeFisicos, asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeDispositivo, } from "../../core/puertosDeAvd.js";
61
61
  import { abrirCarpetaDelSistema, abrirDirectorioDelSistema } from "../../agent/config/selectorEnMaquina.js";
62
62
  import { recargarFicheroDelProyecto, relanzarEnAparato, } from "../../agent/dispositivos/recargaEnAparato.js";
63
63
  import { textoDeRecarga } from "../../core/recargaEnCaliente.js";
@@ -73,7 +73,7 @@ import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDi
73
73
  import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
74
74
  import { crearCheckpointerDeProyecto, mantenimientoDelCheckpointer, } from "../../agent/sesiones/checkpointer.js";
75
75
  import { resumenDePoda } from "../../core/podaDeCheckpoint.js";
76
- import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarModoPorDefecto, guardarAjustesDeRemoto as guardarAjustesDeRemotoEnDisco, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarAjusteDeAvd, quitarAjustesDeAvd, guardarPuertosAsignados, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
76
+ import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarModoPorDefecto, guardarDispositivoPorDefecto, guardarAjustesDeRemoto as guardarAjustesDeRemotoEnDisco, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarAjusteDeAvd, quitarAjustesDeAvd, guardarPuertosAsignados, guardarPuertosDeFisicos, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
77
77
  import { dentroDelWorkspace, depuracionActiva, expandirConCasa, mismaRuta, motivoParaNoOlvidarEntorno, motivoDeNombreDeEntornoInaceptable, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
78
78
  import { esModoDeEscritura, MODO_POR_OMISION, } from "../../core/modoDeEscritura.js";
79
79
  import { megasDeLasMudanzas, mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
@@ -83,7 +83,7 @@ import { abrirEnSistema, olvidarEntorno as olvidarCredencialesDeEntorno, rutaAut
83
83
  import { nombreDePersona } from "../../agent/config/persona.js";
84
84
  import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "../../agent/sesiones/sesionGit.js";
85
85
  import { commitDeTurno, cambiosPendientes, parcheDeSubida, pendientesDeSubida, sinSeguir, trabajoSinCommitear, } from "../../agent/sesiones/gitSync.js";
86
- import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
86
+ import { listarSesiones, marcarTareaDeSesion, reabrirSesion, sembrarConsumosPendientes } from "./sesiones.js";
87
87
  import { RUTA_ARTEFACTOS, esRutaDeArtefacto, rutaRelativaDeArtefacto } from "../../core/artefactos.js";
88
88
  import { MIMES_DE_ARTEFACTO_AL_MOVIL, TOPE_DE_ARTEFACTO_AL_MOVIL, trozosDeArtefacto } from "../../core/remoto.js";
89
89
  import { arbolDeProyecto, escribirFicheroDeProyecto, iconoDelProyecto, leerFicheroDeProyecto, mimeDeImagen, motivoDeRutaInaceptable, motivoParaNoEnsenar, } from "../../agent/grafo/arbolDeProyecto.js";
@@ -103,8 +103,8 @@ import { SIN_CONSUMO } from "../../agent/subagentes/consumoExterno.js";
103
103
  import { AumentadorGuionizado } from "../../core/ports.js";
104
104
  import { arrancarServidor } from "./servidor.js";
105
105
  import { consolaParaTarea, crearCorredorDeTareas, revisionConGit, } from "./corredorDeTareas.js";
106
- import { CONCURRENCIA_POR_OMISION, conEstado, darPorBuenaAMano, puedeArrancarYa, TEXTO_DE_CONTINUAR, tituloDeTarea, } from "../../core/tareas.js";
107
- import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareas/tareasEnDisco.js";
106
+ import { CONCURRENCIA_POR_OMISION, conEstado, darPorBuenaAMano, puedeArrancarYa, puedeEditarEncargo, TEXTO_DE_CONTINUAR, tituloDeTarea, } from "../../core/tareas.js";
107
+ import { aplicarEncargo, aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareas/tareasEnDisco.js";
108
108
  import { nombreDeAdjuntoAceptable } from "../../core/adjuntos.js";
109
109
  import { copiarAdjuntosDeSesion, guardarAdjuntoDeSesion, TOPE_DE_ADJUNTO_DE_SESION, } from "../../agent/sesiones/adjuntosDeSesion.js";
110
110
  import { motivoDePropuestaInaceptable, nombreDeAdjuntoPropuesto, propuestaPendiente, tareasEncadenadas, } from "../../core/repartoDeEncargo.js";
@@ -1282,6 +1282,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1282
1282
  const depuracion = mensajeDeDepuracion();
1283
1283
  // El modo de las sesiones nuevas (IXCODE-31), por lo mismo: está en Ajustes > General.
1284
1284
  const modoPorDefecto = mensajeDeModoPorDefecto();
1285
+ // Y el aparato de las sesiones nuevas (IXCODE-51), en la misma sección.
1286
+ const dispositivoPorDefecto = mensajeDeDispositivoPorDefecto();
1285
1287
  // Y los conectores MCP, tras el workspace y por la misma regla: solo si esta ejecución
1286
1288
  // los monta, y entonces la ventana de Ajustes no se abre con la sección en blanco.
1287
1289
  const conectores = mensajeDeConectores();
@@ -1310,6 +1312,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1310
1312
  cliente(depuracion);
1311
1313
  if (modoPorDefecto !== undefined)
1312
1314
  cliente(modoPorDefecto);
1315
+ if (dispositivoPorDefecto !== undefined)
1316
+ cliente(dispositivoPorDefecto);
1313
1317
  // La sesión remota, SIEMPRE que esta ejecución la monta: con `habilitado`, que es lo que
1314
1318
  // pinta el interruptor de Ajustes (y, apagado, no hay botón en el chat). Un puente abierto
1315
1319
  // con el interruptor quitado a mano en `settings.json` sale igual, para poder apagarlo.
@@ -1499,6 +1503,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1499
1503
  const modo = opciones.modoPorDefectoGlobal?.();
1500
1504
  return modo === undefined ? undefined : { clase: "modoPorDefecto", modo };
1501
1505
  };
1506
+ const mensajeDeDispositivoPorDefecto = () => {
1507
+ if (opciones.dispositivoPorDefecto === undefined)
1508
+ return undefined;
1509
+ const dispositivo = opciones.dispositivoPorDefecto();
1510
+ return { clase: "dispositivoPorDefecto", ...(dispositivo === undefined ? {} : { dispositivo }) };
1511
+ };
1502
1512
  /**
1503
1513
  * El servicio de conectores, construido UNA VEZ al montar — no en cada mensaje, porque es
1504
1514
  * quien guarda en memoria las pruebas y las autorizaciones pendientes (`servicioDeConectores.ts`).
@@ -2027,6 +2037,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2027
2037
  informar(`no se pudieron guardar los puertos de los emuladores (${codigoDe(error)})`);
2028
2038
  }
2029
2039
  }
2040
+ // Y los móviles físicos (IXCODE-32), DESPUÉS y releyendo: un AVD recién sembrado y un móvil
2041
+ // nuevo no pueden llevarse el mismo puerto.
2042
+ const fisicos = asignarPuertosDeFisicos(informeDeDispositivos.dispositivos.filter((d) => d.plataforma === "android" && d.clase === "fisico").map((d) => d.id), ajustesDeDispositivos());
2043
+ if (fisicos !== undefined && opciones.guardarPuertosDeFisicos !== undefined) {
2044
+ try {
2045
+ opciones.guardarPuertosDeFisicos(fisicos);
2046
+ }
2047
+ catch (error) {
2048
+ informar(`no se pudieron guardar los puertos de los móviles (${codigoDe(error)})`);
2049
+ }
2050
+ }
2030
2051
  emitir({
2031
2052
  ...mensajeDeDispositivos(informeDeDispositivos),
2032
2053
  ...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
@@ -3375,12 +3396,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3375
3396
  dispositivo: medida.dispositivo,
3376
3397
  raiz: medida.abierto.raiz,
3377
3398
  app: medida.app,
3378
- // El puerto LOCAL del túnel es el de SU AVD: con dos emuladores y el mismo puerto,
3379
- // el `adb forward` del segundo le quita el túnel al primero sin error. Un físico no
3380
- // tiene AVD y va por el de fábrica, que es lo que hace el lanzador sin puerto.
3381
- ...(medida.dispositivo.avd === undefined
3382
- ? {}
3383
- : { puerto: puertoDeAvd(ajustesDeDispositivos(), medida.dispositivo.avd) }),
3399
+ // El puerto LOCAL del túnel es el de SU aparato: con dos aparatos y el mismo puerto,
3400
+ // el `adb forward` del segundo le quita el túnel al primero sin error. Un físico
3401
+ // tiene el suyo por su serie (IXCODE-32): antes iba al 8443 del primer AVD.
3402
+ ...(medida.dispositivo.plataforma === "android"
3403
+ ? { puerto: puertoDeDispositivo(ajustesDeDispositivos(), medida.dispositivo) }
3404
+ : {}),
3384
3405
  }, { alFase });
3385
3406
  trabajo.cancelar = enCurso.cancelar;
3386
3407
  const resultado = await enCurso.terminado;
@@ -4143,7 +4164,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
4143
4164
  return { motivo: "app.ini no dice el nombre de la app (name=)" };
4144
4165
  return {
4145
4166
  serie: dispositivo.id,
4146
- puerto: dispositivo.avd === undefined ? PUERTO_DEL_HOTSWAP : puertoDeAvd(ajustesDeDispositivos(), dispositivo.avd),
4167
+ puerto: puertoDeDispositivo(ajustesDeDispositivos(), dispositivo),
4147
4168
  app,
4148
4169
  };
4149
4170
  };
@@ -5975,6 +5996,41 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
5975
5996
  respuesta.end(JSON.stringify(proyecto === undefined ? {} : { proyecto }));
5976
5997
  return;
5977
5998
  }
5999
+ if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "leerSesion") {
6000
+ /**
6001
+ * LEER una conversación sin abrirle consola (IXCODE-36): para cuando no se puede escribir en
6002
+ * ella —una tarea escribe en su proyecto, o el proyecto trabaja en otra conversación—. Del
6003
+ * cable llegan los IDS; el nombre sale del listado del servidor, la raíz la compone el
6004
+ * vestíbulo y la sesión tiene que estar en el ÍNDICE de ese proyecto: nunca se lee un fichero
6005
+ * que el cliente nombre. Contesta los actos en la propia respuesta.
6006
+ */
6007
+ const negar = (motivo) => {
6008
+ respuesta.writeHead(409, { "content-type": "application/json" });
6009
+ respuesta.end(JSON.stringify({ motivo }));
6010
+ };
6011
+ const identidad = proyectos.find((p) => p.id === mensaje.proyecto);
6012
+ const sesion = mensaje.sesion;
6013
+ if (entornoElegido === undefined || identidad === undefined)
6014
+ return negar("ese proyecto no está en el listado del entorno activo");
6015
+ const raiz = vestibulo.raizDeProyecto(entornoElegido, identidad.nombre);
6016
+ if (!esProyectoEnDisco(raiz))
6017
+ return negar("ese proyecto no está descargado en este equipo");
6018
+ // El índice del DISCO, la misma fuente que lee `reabrirSesion` justo debajo.
6019
+ if (typeof sesion !== "string" || !listarSesiones(raiz).some((s) => s.id === sesion)) {
6020
+ return negar("esa conversación no está en el proyecto");
6021
+ }
6022
+ let actos;
6023
+ try {
6024
+ actos = reabrirSesion(raiz, sesion).actos;
6025
+ }
6026
+ catch (error) {
6027
+ contar(error);
6028
+ return negar("no se pudo leer esa conversación");
6029
+ }
6030
+ respuesta.writeHead(200, { "content-type": "application/json" });
6031
+ respuesta.end(JSON.stringify({ actos }));
6032
+ return;
6033
+ }
5978
6034
  if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "soporte" && mensaje.accion === "listar") {
5979
6035
  // El listado de la pestaña Soporte, con el análisis PREVIO de cada chat y cada tarea
5980
6036
  // (`core/analisisDeSesion.ts`), contestado en la propia respuesta como `resumen`.
@@ -6333,6 +6389,92 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
6333
6389
  respuesta.end();
6334
6390
  return;
6335
6391
  }
6392
+ if (mensaje.accion === "parar" && opciones.colaDeTareas !== undefined) {
6393
+ /**
6394
+ * PARAR una tarea en marcha sin borrarla (IXCODE-50): el mismo corte que «Descartar»
6395
+ * (`Corredor.cortar`), con `pausa`, y la tarea queda aparcada con su conversación —se sigue
6396
+ * con «Continuar»—. Se ESPERA al corte para poder decir por qué no, con 409.
6397
+ */
6398
+ const negar = (motivo) => {
6399
+ respuesta.writeHead(409, { "content-type": "application/json" });
6400
+ respuesta.end(JSON.stringify({ motivo }));
6401
+ };
6402
+ const tarea = opciones.colaDeTareas.listar().find((t) => t.id === mensaje.id);
6403
+ if (tarea === undefined)
6404
+ return negar("esa tarea ya no existe");
6405
+ if (tarea.estado !== "en-proceso")
6406
+ return negar("esa tarea no está en marcha");
6407
+ if (opciones.corredorDeTareas === undefined || opciones.corredorDeTareas.corriendoAqui() === false) {
6408
+ return negar("su turno lo ejecuta otro proceso, y no se puede parar desde aquí");
6409
+ }
6410
+ let cortada;
6411
+ try {
6412
+ cortada = await opciones.corredorDeTareas.cortar(mensaje.id, { pausa: true });
6413
+ }
6414
+ catch (error) {
6415
+ contar(error);
6416
+ return negar("no se pudo parar su turno");
6417
+ }
6418
+ if (!cortada)
6419
+ return negar("su turno no soltó el proyecto a tiempo: sigue en marcha, inténtalo otra vez");
6420
+ opciones.revisarTareas?.();
6421
+ emitirTareas();
6422
+ respuesta.writeHead(204);
6423
+ respuesta.end();
6424
+ return;
6425
+ }
6426
+ if (mensaje.accion === "regenerarEncargo" && opciones.colaDeTareas !== undefined) {
6427
+ /**
6428
+ * Volver a PREPARAR el encargo de una tarea que no ha arrancado, desde su petición editada
6429
+ * (IXCODE-53). El mismo aumentador que al crearla, con SU proyecto y SUS adjuntos (del disco).
6430
+ * Contesta en la PROPIA respuesta y no guarda nada: rellena el campo de la ventana, y se
6431
+ * guarda con «Guardar». No va por el mensaje `augmentado`, que llega a todas las pestañas y es
6432
+ * de la ventana «Nueva tarea»: las dos se pisarían.
6433
+ */
6434
+ const negar = (motivo) => {
6435
+ respuesta.writeHead(409, { "content-type": "application/json" });
6436
+ respuesta.end(JSON.stringify({ motivo }));
6437
+ };
6438
+ const tarea = opciones.colaDeTareas.listar().find((t) => t.id === mensaje.id);
6439
+ const peticion = typeof mensaje.peticion === "string" ? mensaje.peticion.trim() : "";
6440
+ if (tarea === undefined)
6441
+ return negar("esa tarea ya no existe");
6442
+ if (!puedeEditarEncargo(tarea))
6443
+ return negar("esa tarea ya arrancó: su encargo ya no se edita");
6444
+ if (peticion === "")
6445
+ return negar("escribe qué quieres que haga antes de preparar el encargo");
6446
+ if (opciones.augmentar === undefined)
6447
+ return negar("esta ejecución no puede preparar encargos");
6448
+ try {
6449
+ const encargo = await opciones.augmentar({
6450
+ texto: peticion,
6451
+ proyecto: tarea.proyecto,
6452
+ adjuntos: tarea.adjuntos.map((a) => ({ nombre: a.nombre, ...(a.mime === undefined ? {} : { mime: a.mime }) })),
6453
+ });
6454
+ respuesta.writeHead(200, { "content-type": "application/json" });
6455
+ respuesta.end(JSON.stringify({ encargo }));
6456
+ }
6457
+ catch (error) {
6458
+ contar(error);
6459
+ negar(`no se pudo preparar el encargo: ${motivoLegible(error)}`);
6460
+ }
6461
+ return;
6462
+ }
6463
+ if (mensaje.accion === "encargo" && opciones.colaDeTareas !== undefined) {
6464
+ // Editar el encargo de una tarea que todavía no ha arrancado (IXCODE-52). La regla —solo si
6465
+ // nunca arrancó, y no vacío— es de `core/tareas.ts#conEncargo`; la negativa vuelve con motivo.
6466
+ const peticion = mensaje.peticion;
6467
+ const resultado = aplicarEncargo(opciones.colaDeTareas, mensaje.id, typeof mensaje.encargo === "string" ? mensaje.encargo : "", typeof peticion === "string" ? peticion : undefined);
6468
+ if (!resultado.hecho) {
6469
+ respuesta.writeHead(409, { "content-type": "application/json" });
6470
+ respuesta.end(JSON.stringify({ motivo: resultado.motivo }));
6471
+ return;
6472
+ }
6473
+ emitirTareas();
6474
+ respuesta.writeHead(204);
6475
+ respuesta.end();
6476
+ return;
6477
+ }
6336
6478
  if (mensaje.accion === "arrancarAhora" && opciones.colaDeTareas !== undefined) {
6337
6479
  /**
6338
6480
  * La persona CEDE el proyecto: se cierra su consola de ahí (si no está trabajando) y la tarea arranca, porque lo
@@ -6405,6 +6547,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
6405
6547
  mensaje.accion !== "feedback" &&
6406
6548
  mensaje.accion !== "continuar" &&
6407
6549
  mensaje.accion !== "arrancarAhora" &&
6550
+ mensaje.accion !== "encargo" &&
6551
+ mensaje.accion !== "regenerarEncargo" &&
6552
+ mensaje.accion !== "parar" &&
6408
6553
  opciones.colaDeTareas !== undefined) {
6409
6554
  /**
6410
6555
  * Ya no es `void x(); revisarTareas(); emitirTareas();` seguidas — reintentar y
@@ -6541,6 +6686,25 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
6541
6686
  respuesta.end();
6542
6687
  return;
6543
6688
  }
6689
+ if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "dispositivoPorDefecto") {
6690
+ // Como la pastilla: el cliente manda el ID y el resto sale de la última MEDIDA. Un id que no
6691
+ // está en ella no se guarda (es una foto vieja del cliente); se reemite lo que hay.
6692
+ const pedido = mensaje.id;
6693
+ if (pedido === undefined)
6694
+ opciones.guardarDispositivoPorDefecto?.(undefined);
6695
+ else if (typeof pedido === "string") {
6696
+ const d = informeDeDispositivos?.dispositivos.find((x) => x.id === pedido);
6697
+ if (d !== undefined) {
6698
+ opciones.guardarDispositivoPorDefecto?.({ id: d.id, nombre: d.nombre, plataforma: d.plataforma, clase: d.clase });
6699
+ }
6700
+ }
6701
+ const m = mensajeDeDispositivoPorDefecto();
6702
+ if (m !== undefined)
6703
+ emitir(m);
6704
+ respuesta.writeHead(204);
6705
+ respuesta.end();
6706
+ return;
6707
+ }
6544
6708
  if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "depuracion" && typeof mensaje.activa === "boolean") {
6545
6709
  opciones.guardarDepuracion?.(mensaje.activa);
6546
6710
  emitirDepuracion();
@@ -7445,6 +7609,7 @@ export function emuladoresCableados(opciones = {}) {
7445
7609
  guardarAjusteDeAvd: (avd, cambio) => void guardarAjusteDeAvd(opciones.casa, avd, cambio),
7446
7610
  quitarAjustesDeAvd: (avd) => void quitarAjustesDeAvd(opciones.casa, avd),
7447
7611
  guardarPuertosAsignados: (asignados) => void guardarPuertosAsignados(opciones.casa, asignados),
7612
+ guardarPuertosDeFisicos: (asignados) => void guardarPuertosDeFisicos(opciones.casa, asignados),
7448
7613
  };
7449
7614
  }
7450
7615
  /**
@@ -7489,6 +7654,17 @@ export function ajusteDeModoPorDefectoCableado(opciones = {}) {
7489
7654
  guardarModoDelProyecto: (raiz, modo) => guardarProyecto(raiz, modo),
7490
7655
  };
7491
7656
  }
7657
+ /**
7658
+ * El aparato con que NACEN las sesiones nuevas (IXCODE-51), cableado contra `settings.json`.
7659
+ * Extraída por el patrón de fallo de siempre: la lectura va al vestíbulo (cada apertura) y a
7660
+ * Ajustes, y las dos tienen que leer el MISMO fichero que escribe el guardado.
7661
+ */
7662
+ export function ajusteDeDispositivoPorDefectoCableado(opciones = {}) {
7663
+ return {
7664
+ dispositivoPorDefecto: () => cargarSettings(opciones.casa).settings.dispositivoPorDefecto,
7665
+ guardarDispositivoPorDefecto: (dispositivo) => void guardarDispositivoPorDefecto(opciones.casa, dispositivo),
7666
+ };
7667
+ }
7492
7668
  /**
7493
7669
  * La opción `conectores` de `montarRutas`, cableada — extraída por el MISMO motivo que
7494
7670
  * `ajusteDeWorkspaceCableado`: un literal inline dentro de `arrancarConsolaWeb` no quedaría
@@ -7918,6 +8094,8 @@ export async function arrancarConsolaWeb(opciones) {
7918
8094
  // El modo con que nacen las sesiones nuevas (IXCODE-31), con la MISMA disciplina. El
7919
8095
  // lector por raíz va al vestíbulo (`vestibuloReal`); aquí, los de Ajustes y del panel.
7920
8096
  ...(({ modoPorDefecto: _paraElVestibulo, ...resto }) => resto)(ajusteDeModoPorDefectoCableado()),
8097
+ // El aparato de las sesiones nuevas (IXCODE-51), con la MISMA disciplina.
8098
+ ...ajusteDeDispositivoPorDefectoCableado(),
7921
8099
  // Los conectores MCP, con la MISMA disciplina: la composición extraída y probada
7922
8100
  // (`ajusteDeConectoresCableado`), nunca un literal aquí dentro.
7923
8101
  // Y el gestor de tareas (IXCODE-11) llama por ESE mismo servicio: los dos salen de
@@ -8180,6 +8358,8 @@ function vestibuloReal(opciones, servidor, escribir, informar, conectores) {
8180
8358
  fuentes,
8181
8359
  // Con qué modo nace una sesión nueva (IXCODE-31): el del proyecto de ESA raíz, si no el global.
8182
8360
  modoPorDefecto: ajusteDeModoPorDefectoCableado().modoPorDefecto,
8361
+ // Y con qué aparato (IXCODE-51): también una tarea, que sin él iba al emulador.
8362
+ dispositivoPorDefecto: ajusteDeDispositivoPorDefectoCableado().dispositivoPorDefecto,
8183
8363
  // Se resuelve UNA vez, aquí, y no en cada `anunciarAlta`: `git config`/`os.userInfo`
8184
8364
  // no cambian a media conexión, y repetir el subproceso en cada anuncio del alta sería
8185
8365
  // gastar sin motivo. Nunca viaja hacia CloudStudio ni hacia ningún acto —