Ir al contenido
learn

Diario

  • Código del curso: aplicaciones basadas en archivos, fragmentos rechazados y soluciones de ejercicios, comparados con su salida esperada por check.sh
  • CI en Linux, Windows y macOS
  • Lección 1: instalar .NET y ejecutar tu primer programa
  • Lección 2: variables, tipos y entrada
  • Lección 3: condiciones y bucles
  • Lección 4: métodos, arrays y listas
  • Lección 5: clases y objetos
  • Lección 6: records, structs y enums
  • Lección 7: interfaces y herencia
  • Lección 8: excepciones y seguridad frente a null
  • Lección 9: colecciones y LINQ
  • Lección 10: archivos y texto
  • Lección 11: pruebas unitarias
  • Lección 12: un pequeño proyecto
Esperado Qué ocurre Dónde Medición Estado
Mostrar una plantilla de acorde de GA muestra su Name, como pretende el ToString() => Name del record base Los records derivados TonalModal y Analytical sintetizan cada uno su propio ToString, que enumera todas las propiedades: TonalModal { Name = Major 7th, PitchClassSet = 0 4 7 E, … }. Un record derivado solo conserva el ToString de la base si es sealed (records, formato integrado) ChordTemplate.cs:84; un llamador, ContextualChordsController.cs:301, pasa template.ToString() a un servicio que lo registra (FretboardServices.cs:536) Una sonda de consola que referencia GA.Domain.Core en el commit 5c3a52a, SDK 10.0.112: el volcado para ToString() y $"{template}" en los dos records; una réplica con sealed mostró maj7 (entrada) Reproducido; corrección propuesta en la PR 816 de GA, aún sin fusionar (2026-10-04); sigue abierta el 2026-10-06, con el defecto sin cambios en main 40d33747 (2026-10-06)
Las advertencias de nulabilidad aparecen en el código de GA: GA.Domain.Core y GA.Core activan <Nullable>enable</Nullable> Directory.Build.props pone 15 advertencias de nulabilidad (CS8600, CS8602, CS8618, CS8625…) en NoWarn «to achieve a clean baseline», y Directory.Build.targets las repite. La base está limpia sin ellas: las listas solo ocultan los errores nuevos Directory.Build.props:21, Directory.Build.targets:7 Un archivo con siete errores de nulabilidad, añadido a GA.Domain.Core en el commit 5c3a52a: 0 advertencias con la configuración de GA, 7 sin las dos listas; sin ese archivo, 0 en GA.Domain.Core y GA.Core (entrada, ga-probes) Reproducido; no comunicado a GA
Los servicios de conocimiento musical de GA cargan ChordProgressions.yaml, GuitarTechniques.yaml y SpecializedTunings.yaml Ninguno de los tres se deserializa: una cadena donde la clase tiene una List<string>, cadenas donde espera objetos, una línea que no es YAML válido. Cada cargador captura la excepción, muestra Error loading … y sigue con un solo elemento por defecto ChordProgressions.yaml:93 frente a ChordProgressionsConfigLoader.cs:16; GuitarTechniques.yaml:23 frente a GuitarTechniquesConfigLoader.cs:20; SpecializedTunings.yaml:95; el catch de ChordProgressionsConfigLoader.cs:137, y el mismo en los otros dos cargadores artists.cs en el commit 5c3a52a, SDK 10.0.112: 17 acordes emblemáticos, luego 1 elemento para cada uno de los tres archivos, y 16 artistas distintos (entrada). Las tres líneas siguen igual en el main de GA, ae196bf Reproducido; comunicado en la issue 797 de GA el 2026-10-03; corrección propuesta en la PR 808 de GA, aún sin fusionar (2026-10-04)
GetArtistBreakdown devuelve los 20 artistas con más entradas, de más a menos, como dice su comentario // Top 20 artists Take(20) corta la lista alfabética antes de ordenar por número de entradas, y el resultado ordenado va a un Dictionary, cuyo orden la documentación deja sin definir MusicalKnowledgeService.cs:226 y :235; GetAllArtists ordena por nombre en :125 La misma sonda: con 16 artistas no queda ninguno fuera, y el diccionario sale por número de entradas, los empates en orden alfabético. El corte solo actúa con más de 20 artistas, una vez corregida la fila anterior Latente, leído en el código; corrección propuesta en la PR 811 de GA, aún sin fusionar (2026-10-04)
PitchClassSet.ScaleVideoUrl da la URL del vídeo de una escala listada en scale_video_urls.json, que contiene 1490 El archivo no está incrustado en la biblioteca ni se copia junto a ella: GA.Domain.Core.csproj no tiene ninguna regla para él. El nombre de recurso GA.Domain.Core.Scales.Data.scale_video_urls.json y la ruta de repliegue Scales/Data/ no aciertan con su carpeta, Theory/Tonal/Scales/Data/, y LoadVideoUrls devuelve un diccionario vacío sin mensaje ScaleVideoUrlById.cs:30, :36-38; PitchClassSet.cs:350, ScaleMetadataRegistry.cs:36 files.cs en el commit 5c3a52a, SDK 10.0.112: 0 recursos en GA.Domain.Core, ningún archivo de repliegue, 0 escalas con URL (entrada). El nombre de recurso y la ruta de repliegue siguen igual en el main de GA el 2026-10-03 Reproducido; corrección propuesta en la PR 817 de GA, aún sin fusionar (2026-10-04); sigue abierta el 2026-10-06, con el defecto sin cambios en main 40d33747 (2026-10-06)
IconicChordsConfigLoader.ReloadConfiguration() vuelve a leer IconicChords.yaml tras un cambio Asigna el campo static readonly _configuration con FieldInfo.SetValue, que lanza FieldAccessException desde .NET Core 3.0 (cambio importante) IconicChordsConfigLoader.cs:107-111, :12 files.cs: FieldAccessException: Cannot set initonly static field '_configuration' after type 'GA.Domain.Services.IconicChordsConfigLoader' is initialized. (entrada). Sigue igual en el main de GA el 2026-10-03 Reproducido; corrección propuesta en la PR 811 de GA, aún sin fusionar (2026-10-04)
Un IconicChords.yaml que falta se comunica FindYamlFile prueba cuatro rutas, dos de ellas relativas al directorio actual. Cuando no existe ninguna, un catch vacío se traga su FileNotFoundException, y el cargador devuelve un solo acorde por defecto, sin mensaje IconicChordsConfigLoader.cs:94-104, :25-28 files.cs, tras borrar los archivos YAML junto a él: 1 acorde, «C Major Triad», lanzado desde ga-probes; 17 desde la raíz del código de GA, por <directorio actual>/Common/GA.Business.Config; nada impreso (entrada) Reproducido; una decisión de diseño, no comunicada a GA
GaCLI lee el appsettings.yaml que su compilación copia junto al programa SetBasePath(Directory.GetCurrentDirectory()) lo lee en el directorio actual, como archivo opcional: lanzado desde otra carpeta, GaCLI funcionaría sin su configuración, en silencio Program.cs:17-19, GaCLI.csproj:59-61 Compilado en 5c3a52a y lanzado desde otra carpeta: imprime su lista de comandos. Las dos secciones del archivo, EmbeddingService y LmStudio, se leen en un solo sitio, sync-mongodb, en una configuración que ningún servicio resuelve (entrada) Verificado: el archivo no cambia nada en este commit; no comunicado a GA
Cada fila del CSV de naturalidad de GA tiene los 6 valores de su encabezado :F2 y {deltaStretch} usan la cultura de la máquina: con fr-FR, 2.50 se escribe 2,50, y una fila recibe 8 valores; el lector corta por las , NaturalnessTrainingDataGenerator.cs:160, :21; TrainNaturalnessModelCommand.cs:51-54 l10_culture.cs con la cadena de formato de GA: 6 valores con en-CA, 8 con fr-FR. El generador de GA no se ejecutó: necesita un corpus de tablaturas (entrada) Reproducido con la cadena de formato; corrección propuesta en la PR 811 de GA, aún sin fusionar (2026-10-04)
El comando de GA imprime el número de filas de datos que escribió csvContent.Split('\n').Length - 1 quita el elemento vacío tras el último salto de línea pero cuenta el encabezado: una fila de más GenerateNaturalnessDataCommand.cs:38 La aritmética de Split('\n'), medida por l10_line_endings.cs: 3 líneas dan 4 elementos. El comando de GA no se ejecutó Latente, leído en el código; corrección propuesta en la PR 811 de GA, aún sin fusionar (2026-10-04)
Una prueba de los servicios de conocimiento musical de GA falla cuando sus archivos YAML no se cargan Tres de las ocho pruebas se omiten con [Ignore("Configuration files not loaded in test environment")]. De las cinco restantes, tres solo comprueban que un número es mayor que cero o que una lista no está vacía, lo que cumple el único elemento por defecto de cada cargador que falla MusicalKnowledgeServiceTests.cs:108-110, :141-143, :207-209; :16, :31-33, :179-183 Sonda knowledge-tests, archivo de pruebas de GA compilado sin cambios, SDK 10.0.112: 5 superadas, 3 omitidas, 0 fallidas, mientras tres de los cuatro archivos YAML no se cargan. La búsqueda de «jazz» pasó con 1 resultado, la progresión de acordes por defecto (entrada). Sigue igual en el main de GA, 0843879 Reproducido; relacionado con la issue 797 de GA; corrección propuesta en la PR 808 de GA, aún sin fusionar (2026-10-04)
El [SetUpFixture] TestEnvironment de GA se ejecuta antes de las pruebas de configuración: define GA_TEST_MODE=1 y lleva el directorio actual a la raíz del repositorio Está solo en el namespace GA.Business.Core.Tests.TestBootstrap, y NUnit solo ejecuta un setup fixture para las pruebas de su namespace y de los namespaces que contiene (NUnit): nunca se ejecuta TestEnvironment.cs:1, :7-14 Sonda knowledge-tests: una prueba en GA.Business.Core.Tests.Configuration imprimió GA_TEST_MODE: (not set) y Current directory is the test's bin folder: True (entrada). En main, 0843879, el namespace sigue conteniendo solo este archivo Reproducido; corrección propuesta en la PR 816 de GA, aún sin fusionar (2026-10-04); sigue abierta el 2026-10-06, con el defecto sin cambios en main 40d33747 (2026-10-06)
Los archivos de prueba de GA.Business.Core.Tests se compilan y se ejecutan El archivo de proyecto quita 35 archivos de la compilación, «Exclude test files with missing service implementations»: 5 de los nombres no corresponden a ningún archivo, y los otros 30 contienen 333 líneas que empiezan por [Test] o [TestCase] GA.Business.Core.Tests.csproj:39-75 git ls-tree y un recuento en el commit 5c3a52a; el proyecto tiene 136 archivos .cs. Las 35 entradas siguen igual en main, 0843879 Contado en el código; no comunicado a GA
La CI de GA ejecuta las pruebas de cada proyecto de pruebas Sus jobs compilan y prueban AllProjects.slnx, que lista 75 de los 111 archivos de proyecto. Seis proyectos de pruebas quedan fuera y ningún workflow los nombra; sus archivos tienen 102 líneas con un atributo de prueba. Uno de ellos, GA.Business.Core.Graphiti.Tests, no compila: usa GA.Business.Graphiti y no referencia ningún proyecto AllProjects.slnx, ci.yml:43, :103, GA.Business.Core.Graphiti.Tests.csproj lesson12_counts.py en 5c3a52a; la compilación de las pruebas Graphiti se detiene con 5 errores, CS0234 en sus dos líneas using GA.Business.Graphiti... y CS0246 Contado y compilado; corrección propuesta en la PR 812 de GA, aún sin fusionar (2026-10-04)
Los elementos PackageReference Update del Directory.Build.props de GA alinean las versiones de los paquetes, bajo «Centralized package version alignment and vulnerability remediation» Directory.Build.props se importa antes que los elementos propios del proyecto, así que Update no encuentra nada que actualizar. 23 declaraciones de los seis paquetes afectados mantienen otra versión, 13 de ellas en proyectos de la solución Directory.Build.props:32-58 Restauración de GA.Business.Core.Graphiti.Tests: Microsoft.Extensions.Http 9.0.10, como pide su archivo de proyecto. Con la misma línea Update en Directory.Build.targets: 10.0.0 Reproducido, con un control; corrección propuesta en la PR 812 de GA, aún sin fusionar (2026-10-04)
El job de análisis de seguridad de GA informa de los paquetes vulnerables Directory.Build.props y Directory.Build.targets desactivan NU1902, NU1903 y NU1904 para todos los proyectos. El paso de análisis tiene continue-on-error: true, y dotnet list package --vulnerable termina con el código 0 cuando lista un paquete vulnerable: el job no puede fallar Directory.Build.props:16-17, Directory.Build.targets:5-11, ci.yml:398-401 Código de salida 0 con un paquete vulnerable, medido en un proyecto de prueba (tabla de experimentos); el análisis de GA no se ejecutó Leído en el código; corrección propuesta en la PR 812 de GA, aún sin fusionar (2026-10-04)
El workflow Quality Check de GA ejecuta un benchmark de calidad de GaCLI benchmark-quality lanza NotImplementedException, como 12 de los 28 comandos de GaCLI, incluido analyze-chord, el primer ejemplo de su ayuda. El paso tiene continue-on-error: true quality_check.yml:26-29, Program.cs:179-182 Compilación local en 5c3a52a: NotImplementedException no controlada. Ejecución 37167745677 de GA, en un commit posterior cuyo workflow y Program.cs son los mismos archivos: código de salida 134, ejecución terminada con éxito Reproducido; no comunicado a GA
Un comando de GaCLI que falla se lo dice a quien lo llama sync-mongodb captura su excepción, imprime Error during sync: Unable to resolve service for type 'GA.Data.MongoDB.Services.MusicalObjectsService' while attempting to activate 'GaCLI.Commands.Runner'. y termina con el código 0: Runner necesita un servicio que el comando no registra Program.cs:356, :364-368 Lanzado desde bin/Debug/net10.0 y desde otra carpeta: el mismo mensaje, código de salida 0 Reproducido; corrección propuesta en la PR 811 de GA, aún sin fusionar (2026-10-04)
Pregunta Hipótesis, escrita antes de medir Resultado Veredicto Entrada y código
¿Cuántas advertencias de nulabilidad oculta el NoWarn de GA en GA.Domain.Core? Más de 100 0 en GA.Domain.Core y GA.Core sin las dos listas; un archivo de prueba con siete errores recibe 7 advertencias sin las listas, 0 con ellas Refutada entrada, ZzNullableCanary.cs
¿Qué hace el Fret de GA con 50? new Fret(50), Fret.FromValue(50) y Fret f = 50; lanzan una ArgumentOutOfRangeException; Fret.TryCreate(50) devuelve el fallo Fret number must be between -1 (muted) and 36, got 50 Las tres lanzan la excepción, para 50 y para -2; el mensaje de TryCreate coincide palabra por palabra Confirmada entrada, probe.cs, results.txt
¿Compila sin aviso new PositionLocation(5, 3), con la cuerda y el traste intercambiados como simples números? Los dos órdenes compilan sin advertencia; la llamada intercambiada construye la cuerda 5, traste 3 0 advertencias en las líneas de la sonda; muestra 5,3 Confirmada entrada, probe.cs
¿En qué orden lee un foreach un diccionario después de un Remove y un Add? Claves C, D, E, G, A, luego Remove("D") y Add("F#"): F# ocupa el lugar de D, C F# E G A, no el último lugar C F# E G A Confirmada entrada, l09_dictionary_order.cs
¿Modificar un diccionario dentro de su propio foreach lanza una excepción, como con una lista? Remove no lanza nada en .NET 10; añadir una clave lanza una excepción; List<T>.Remove lanza una InvalidOperationException Remove deja C E sin excepción; añadir B, y el Remove de la lista, lanzan ambos Collection was modified; enumeration operation may not execute. Confirmada entrada, l09_modify_dictionary.cs, l09_modify_while_looping.cs
¿Qué artistas deja fuera el GetArtistBreakdown de GA con Take(20)? Más de 20 artistas distintos: se quedan los 20 primeros del alfabeto, y al menos un artista excluido tiene más entradas que el menor número conservado; el diccionario sale por número de entradas descendente 16 artistas, ninguno fuera, porque tres de los cuatro archivos YAML no se cargan (tabla QA). El diccionario sí sale por número de entradas, los empates en orden alfabético Refutada entrada, artists.cs, artists-results.txt
En un archivo de 3 líneas que termina con un salto de línea, ¿qué devuelven ReadAllText(path).Split('\n') y ReadAllLines, con LF y con CRLF? Split da 4 elementos, el último vacío, y con CRLF los tres primeros terminan en \r; ReadAllLines da 3 líneas sin \r en los dos casos Longitudes LF 2 2 2 0 y 2 2 2, CRLF 3 3 3 0 y 2 2 2; con CRLF, split[0] == "E2" es False Confirmada entrada, l10_line_endings.cs
¿Cuántos bytes escriben WriteAllText(path, "Ré") y WriteAllText(path, "Ré", Encoding.UTF8)? 3 sin codificación, 6 con Encoding.UTF8, empezando por EF BB BF; ReadAllText devuelve Ré en los dos casos 52C3A9, luego EFBBBF52C3A9; Ré, 2 caracteres Confirmada entrada, l10_bom.cs
¿Depende File.Exists("data/ga-projects.csv") de la carpeta desde la que arranca dotnet run? True desde code/csharp-beginner, False desde la raíz del repositorio; una ruta construida a partir de EntryPointFileDirectoryPath existe desde las dos Como se predijo, y False también desde examples; check.sh ejecuta ahora los dos casos en tres sistemas. EntryPointFileDirectoryPath se conocía antes de la hipótesis Confirmada entrada, l10_where.cs
Con los archivos YAML borrados junto a la sonda, ¿cuántos acordes emblemáticos carga GA, lanzado desde la raíz de GA y desde ga-probes? 17 desde la raíz de GA, por la tercera ruta; 1 acorde por defecto desde ga-probes, sin excepción ni mensaje 17, «Hendrix Chord» primero, y 1, «C Major Triad»; nada impreso Confirmada entrada, files.cs, files-without-yaml.txt, files-from-ga-root.txt
¿Qué hace IconicChordsConfigLoader.ReloadConfiguration() de GA en .NET 10? Lanza FieldAccessException: SetValue sobre un campo static readonly se rechaza desde .NET Core 3.0 FieldAccessException: Cannot set initonly static field '_configuration' … Confirmada entrada, files.cs, files-results.txt
¿Cuántos valores separados por comas tiene una fila del CSV de naturalidad de GA con en-CA y con fr-FR? 6 con en-CA, 1,2.50,2.50,2,1,4; 8 con fr-FR, 1,2,50,2,50,2,1,4. Conocido antes de la hipótesis: $"{2.5}" da 2,5 con fr-FR Exactamente esas dos filas Confirmada entrada, l10_culture.cs
¿Qué tamaño tiene un archivo tras StreamWriter.Write("E2 A2 D3"), antes y después de Dispose? 0 bytes antes, 8 después 0, luego 8 Confirmada entrada, l10_dispose.cs
¿Encuentra ScaleVideoUrlById de GA sus datos? Ningún recurso llamado …scale_video_urls.json en GA.Domain.Core, ningún archivo de repliegue junto a la sonda, un resultado vacío, ninguna excepción. La lectura de un subagente lo sugirió antes de que yo leyera el código 0 recursos en total, ningún archivo de repliegue, 0 escalas con URL Confirmada entrada, files.cs
¿Qué crea dotnet new xunit con el SDK 10.0.112, y qué imprime dotnet test? xUnit v2, paquete xunit 2.9.x, con xunit.runner.visualstudio, Microsoft.NET.Test.Sdk y coverlet.collector; ejecutado por VSTest, con una línea Passed! al final xunit 2.9.3, xunit.runner.visualstudio 3.1.4, Microsoft.NET.Test.Sdk 17.14.1, coverlet.collector 6.0.4; VSTest 18.0.2; Passed! - Failed: 0, Passed: 1, … Confirmada entrada, Fretboard.Tests.csproj
¿Una [Theory] con cuatro filas [InlineData] cuenta como 4 pruebas o como 1? 4 15 pruebas en el proyecto: contando la teoría una sola vez habrían sido 12 Confirmada entrada, GuitarTests.cs
¿Qué hacen Assert.Equal(0.3, 0.1 + 0.2) y Assert.Equal(0.3, 0.1 + 0.2, 10)? La primera falla y muestra el valor obtenido 0.30000000000000004; la segunda pasa Como se predijo. No previsto: el valor esperado se muestra como 0.29999999999999999 Confirmada entrada, PitfallTests.cs
Cuando el método lanza ArgumentOutOfRangeException, ¿qué hacen Assert.Throws<ArgumentException> y Assert.ThrowsAny<ArgumentException>? Throws falla, quiere el tipo exacto; ThrowsAny pasa Assert.Throws() Failure: Exception type was not an exact match; ThrowsAny pasa Confirmada entrada, PitfallTests.cs, GuitarTests.cs
¿Crea xUnit una instancia nueva de la clase de prueba para cada prueba? Sí: un campo incrementado por cada una de dos pruebas vale 1 en las dos Las dos pruebas pasan Confirmada entrada, InstanceTests.cs
Ejecutadas sin cambios contra los servicios de GA en el commit 5c3a52a, ¿qué dan las MusicalKnowledgeServiceTests de GA? 0 fallidas, 3 omitidas, 5 superadas, incluida la de las afinaciones especiales, porque la única afinación por defecto hace que su lista no esté vacía. Escrita tras leer el archivo de pruebas, antes de ejecutarlo 5 superadas, 3 omitidas, 0 fallidas; 1 afinación especial, «Overall Valid: True» Confirmada entrada, knowledge-tests, knowledge-results.txt
¿Se ejecuta el [SetUpFixture] TestEnvironment de GA antes de las pruebas de GA.Business.Core.Tests.Configuration? No: está solo en su namespace. Una prueba de la sonda junto a las de GA ve GA_TEST_MODE sin definir y su carpeta bin como directorio actual. Escrita tras leer el bootstrap, antes de ejecutarlo GA_TEST_MODE: (not set), Current directory is the test's bin folder: True Confirmada entrada, knowledge-tests, knowledge-results.txt
¿Qué crea dotnet new sln con el SDK 10.0.112? Un archivo .slnx, no un .sln Fretboard.slnx: <Solution> y </Solution>, ningún proyecto Confirmada entrada, l12-project
En la carpeta de una solución con una biblioteca, una aplicación de consola y un proyecto de pruebas, ¿qué compilan y ejecutan dotnet build y dotnet test? dotnet build compila los tres proyectos; dotnet test ejecuta solo las pruebas del proyecto de pruebas dotnet build: Fretboard, Fretboard.App, Fretboard.Tests. dotnet test: un ensamblado de pruebas, 6 superadas. No previsto: tras dotnet clean, dotnet test deja Fretboard.App sin compilar Confirmada entrada, l12-project
Con un error de compilación en la aplicación de consola, ¿qué hacen dotnet test y dotnet build en la carpeta de la solución? Escrita tras ver la aplicación sin compilar, antes de esta ejecución dotnet test pasa y termina con el código 0; dotnet build falla dotnet test: superado, código de salida 0. dotnet build: compilación fallida, código de salida 1 Confirmada entrada
¿Qué escribe dotnet add package CsvHelper, y lee CsvHelper un valor entre comillas que contiene una coma como un solo campo? La última versión estable; sí, donde Split(',') da un campo de más Version="33.1.0" el 3 de octubre de 2026; Split: 4 campos, CsvHelper: 3 Confirmada entrada, l12_csvhelper.cs
¿Funciona #:package en una aplicación basada en archivos, con y sin versión? #:package CsvHelper@33.1.0 restaura y se ejecuta; sin @version la restauración lo rechaza 3 campos; sin la versión, error NU1015: The following PackageReference item(s) do not have a version specified: CsvHelper, código de salida 1 Confirmada entrada, l12_package_no_version.cs
¿Puede GetRecords<Project>() de CsvHelper rellenar un record posicional que también tiene una propiedad calculada? Sí, por su constructor, según los nombres del encabezado, ignorando SourceFiles 12 records y 632 archivos fuente; su lista es igual a una lista construida a mano Confirmada entrada, ProjectFileTests.cs
¿Qué hace dotnet pack sin opciones con una biblioteca que referencia CsvHelper? Una compilación Release, bin/Release/Fretboard.1.0.0.nupkg, un zip con lib/net10.0/Fretboard.dll y un .nuspec que lista CsvHelper «>= 33.1.0» Como se predijo; el .nuspec escribe version="33.1.0", la notación de NuGet para 33.1.0 o posterior, con exclude="Build,Analyzers". No previsto: el mensaje sobre el readme, el commit de HEAD en el .nuspec, y un segundo dotnet pack sin cambios que no imprime ninguna de las dos líneas Confirmada entrada, l12_ex_package.cs
¿Un paquete con un aviso de seguridad conocido provoca una advertencia, y falla dotnet list package --vulnerable? Newtonsoft.Json 12.0.1 da NU1903 al restaurar; el comando lo lista y termina con el código 0 NU1903 con GHSA-5crp-9r3c-p9vr, impreso dos veces por una aplicación basada en archivos; dotnet list package --vulnerable y dotnet package list --vulnerable imprimen la tabla y terminan con el código 0 Confirmada entrada, l12_audit.cs
¿Cuánto tardan dos Task.Delay(500), esperados uno tras otro y con Task.WhenAll? Al menos 1000 ms; menos de 900 ms Cinco ejecuciones: de 1014 a 1023 ms, y después de 508 a 514 ms Confirmada entrada, l12_async.cs
¿Qué pasa con una llamada async sin await en un programa de instrucciones de nivel superior? Advertencia CS4014, y la línea escrita tras su espera nunca aparece CS4014 en (2,1); Before, After, código de salida 0 Confirmada entrada, l12_forgotten_await.cs
¿Qué dice el compilador de un await en un constructor, y de un constructor async? CS4033; CS0106, modificador no válido CS4033, como se predijo. public async VoicingIndex() da CS0246 por un tipo async y CS0542, un miembro con el nombre de su tipo: el analizador lee un método Refutada en parte entrada, l12_constructor_await.cs
¿Lista AllProjects.slnx de GA todos los archivos de proyecto del repositorio? No: faltan al menos 5 .csproj o .fsproj Faltan 36 de los 111; ninguno se lista sin existir Confirmada entrada, lesson12_counts.py, lesson12-results.txt
¿Se ejecutan en la CI los proyectos de pruebas de GA fuera de la solución, y se compilan de todos modos proyectos que están fuera? Escrita tras el recuento anterior Al menos un proyecto de pruebas no lo nombra ningún workflow; al menos un proyecto de fuera lo referencia uno listado Seis proyectos de pruebas, 102 líneas con un atributo de prueba, sin workflow que los nombre; 7 proyectos de fuera referenciados, GA.Business.Core por 4 Confirmada entrada, lesson12_counts.py, lesson12-results.txt
¿Compila GA.Business.Core.Graphiti.Tests? Escrita tras leer su archivo de proyecto y su prueba No: usa GA.Business.Graphiti sin ProjectReference, así que CS0246 5 errores: CS0234 en las líneas 7 y 8, después CS0246 Confirmada entrada, ga-probes
¿Se declara algún paquete con varias versiones en los archivos de proyecto de GA? Sí, al menos uno 35 paquetes; Microsoft.Extensions.Hosting y MongoDB.Driver con cinco versiones cada uno Confirmada entrada, lesson12_counts.py, lesson12-results.txt
¿Cambian una versión los elementos PackageReference Update del Directory.Build.props de GA? Escrita tras leer el archivo No: Microsoft.Extensions.Http se queda en la 9.0.10 del archivo de proyecto de las pruebas Graphiti 9.0.10; con la misma línea en Directory.Build.targets, 10.0.0, mientras que Microsoft.Extensions.Options, actualizado solo en el archivo props, se queda en 9.0.10 Confirmada entrada, ga-probes
¿Bloquea el código de GA fuera de las pruebas sobre tareas en al menos 20 sitios? Sí 27 coincidencias de .Result, .Wait() y GetAwaiter().GetResult(): 4 son SemaphoreSlim.Wait(), 7 leen tareas tras await Task.WhenAll, 5 están en archivos excluidos de la compilación; quedan 11, 3 de ellas en constructores Refutada entrada, lesson12_counts.py, lesson12-results.txt
¿Tiene GA un método async void que no sea un manejador de eventos? Sí, al menos uno Ninguno Refutada entrada, lesson12_counts.py
¿Funciona GaCLI desde otra carpeta sin appsettings.yaml, y arranca sync-mongodb? Escrita tras leer Program.cs Imprime su ayuda; sync-mongodb se detiene por un MusicalObjectsService que falta, desde las dos carpetas Ayuda impresa; Error during sync: Unable to resolve service for type 'GA.Data.MongoDB.Services.MusicalObjectsService'... desde las dos carpetas, código de salida 0 Confirmada entrada, ga-probes

2026-09-14 — El SDK y las aplicaciones basadas en archivos

Sección titulada «2026-09-14 — El SDK y las aplicaciones basadas en archivos»
  • Mi máquina tiene dos SDK: 10.0.112 y 11.0.100-preview.3.26207.106. Sin un global.json, dotnet elige la preview. La carpeta del curso fija 10.0.100 con rollForward: latestFeature, lo que selecciona 10.0.112 aquí.
  • WinGet ofrece Microsoft.DotNet.SDK.10 en la versión 10.0.401 ese mismo día: una banda de características más reciente que la mía. La página de aplicaciones basadas en archivos indica que #:include está disponible desde el SDK 10.0.300, así que la lección 1 dice que una aplicación basada en archivos es un solo archivo por defecto, y que #:include añade otros a partir de ese SDK. No he probado #:include: mi SDK es anterior.
  • Los ejemplos son aplicaciones basadas en archivos en lugar de un proyecto por lección: un principiante escribe un archivo y lo ejecuta, sin nada que configurar. Un único dotnet run app.cs tardó 0.9 s la primera vez y 0.2 s la siguiente.
  • Los demás archivos .cs de la misma carpeta no se compilan con la aplicación: hello.cs se ejecutó sin problemas junto a un archivo lleno de errores.
  • Las advertencias solo se imprimen cuando el SDK compila. Un segundo dotnet run de un archivo sin cambios no imprime ninguna advertencia, y dotnet run --no-cache tampoco ayuda: se salta la comprobación de actualización de la aplicación basada en archivos, pero MSBuild sigue viendo la salida compilada al día y se salta el compilador. dotnet clean app.cs antes de cada ejecución devuelve las advertencias; check.sh lo hace, y la lección 3 se lo cuenta al lector.
  • Los errores del compilador van a la salida estándar, con la ruta completa del archivo; The build failed. Fix the build errors and run again. va a la salida de error. check.sh junta ambas y conserva solo el nombre del archivo, para que los archivos esperados sean iguales en todas las máquinas.
  • Los errores de sintaxis ocultan los demás: en el programa roto del ejercicio 2 de la lección 1, el Writeline mal escrito (CS0117) solo se señala una vez corregidos el ; y las comillas que faltan. El ejercicio se basa en eso.
  • Entrada estándar: cuando la entrada viene de un archivo, el texto tecleado no aparece en la salida, así que los archivos esperados contienen las preguntas seguidas directamente de las respuestas. Las lecciones muestran la terminal tal como la ve una persona, con las líneas tecleadas, y lo dicen.
  • Cultura: la cultura de mi Windows es en-CA. Con es-ES, double.TryParse("1.5") devuelve true y 15, ya que el punto es el separador de miles en español; con fr-FR, devuelve false. El ejemplo fija cada cultura explícitamente, así que la salida es la misma en todos los sistemas operativos. Los demás ejemplos solo usan formatos que se imprimen igual en en-CA, en-US y la cultura invariable del runner Linux.
  • Commit b19a296, ejecución 34914488934: en verde en los tres sistemas operativos. actions/setup-dotnet con el global.json del curso instaló el SDK 10.0.401 en los tres runners, mientras que las salidas se capturaron con 10.0.112: todos los mensajes del compilador son idénticos. Los jobs tardaron 54 s en Linux, 1 min 36 s en macOS y 2 min 18 s en Windows, con un dotnet clean y una compilación para cada una de las 58 aplicaciones basadas en archivos.
  • Math.Pow(2, 7 / 12.0) impreso con todos sus dígitos, 164.81377845643496, es igual en los tres sistemas operativos.
  • La línea shebang #!/usr/bin/env -S dotnet -- funciona en los runners Linux y macOS después de chmod +x, y dotnet run la ignora en Windows.
  • Los códigos de salida de una excepción no controlada difieren, así que check.sh los imprime y solo compara exit crash:
    • Linux y macOS: 134 (el proceso aborta, SIGABRT) para los tres ejemplos que fallan;
    • Windows, visto desde Git Bash: 127 para IndexOutOfRangeException y SwitchExpressionException, y 139 con un mensaje «Segmentation fault» para NullReferenceException;
    • Windows, visto desde PowerShell: 0xE0434352 (-532462766), el código de una excepción .NET, para IndexOutOfRangeException, pero 0xC0000005 (-1073741819), una violación de acceso, para NullReferenceException.
  • Los ejemplos usan Guitar Alchemist como datos pequeños: la afinación estándar de Tuning.Default en el commit a826864, y doce nombres de proyectos de code/ladybugdb/data/ga/projects.csv, extraídos en el commit a26a7893. Nada en estas lecciones reveló un problema en GA.
  • La lección 5 presenta la construcción, la identidad de las instancias, los campos privados, las propiedades públicas, los métodos y un contador static compartido; después propone crear una clase PracticeSession. Las páginas inglesa, francesa y española comparten los mismos fragmentos C# probados y las mismas salidas del compilador.
  • En Windows, con el SDK 10.0.112, C:/Program Files/Git/bin/bash.exe check.sh pasó para los ejemplos, soluciones y fragmentos rechazados del curso, incluidos los nuevos archivos l05_*. Los dos nuevos fragmentos rechazados produjeron CS0122 y CS0200, respectivamente. El bash predeterminado del equipo era WSL y no encontraba el dotnet de Windows: aquel primer fallo era del entorno de pruebas, no del código.
  • Este resultado local no sustituye una nueva CI en tres sistemas ni demuestra que la lección esté publicada. Las lecciones 6–12 siguen solo en el plan.

2026-09-30 — La lección 5 en la CI de tres sistemas

Sección titulada «2026-09-30 — La lección 5 en la CI de tres sistemas»
  • La ejecución 36664604662 del workflow C# for beginners examples, sobre el commit 669d42a de la PR #60, pasó en ubuntu-latest, windows-latest y macos-latest. En la salida de cada job, check.sh imprime ok para l05_objects, l05_ex_practice, l05_get_only_property y l05_private_field: las salidas y los diagnósticos CS0122 y CS0200 coinciden con expected/ en los tres sistemas.
  • La lección 6 compara una estructura con una clase (copia al asignar, copia como argumento, Equals), y después presenta los records (== sobre los datos, ToString, with, readonly record struct) y las enumeraciones (numeradas desde 0, valor predeterminado, casts, Enum.IsDefined, Enum.Parse). Tiene cuatro ejemplos, tres ejercicios y cinco fragmentos rechazados, cuyos diagnósticos son CS0019, CS1612, CS8852 (tres veces en dos archivos) y CS0266. Un script comprobó que los tres idiomas comparten los mismos bloques de código, sin contar los comentarios, y que cada bloque coincide con su archivo en code/csharp-beginner y expected/.
  • En Windows, con el SDK 10.0.112 y Git Bash, check.sh terminó con código 0 y 75 líneas ok, incluidos los 12 archivos l06_*. Todavía no es una ejecución de la CI en tres sistemas.
  • Después, en la CI: la ejecución 36865237779 del workflow C# for beginners examples, sobre el commit 114b5ed de la PR #93, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para los 12 archivos l06_*; los jobs cuentan 76 líneas ok en Linux y macOS y 75 en Windows, porque check.sh solo ejecuta el ejemplo con shebang mediante ./ en Linux y macOS.
  • Sorpresas, todas recogidas en la lección: CS8524 avisa de una expresión switch que tiene un brazo por nombre, y su ejemplo es (ChordQuality)5 mientras el programa falla con 7; el literal 0 se convierte en una enumeración sin cast, mientras que 2 da CS0266; una List<T> de estructuras modificables rechaza shape[0].Fret = 5 con CS1612.
  • Un borrador del ejercicio 3 decía que shape[i].Fret += 2 sobre una lista de readonly record struct da el mismo CS1612. Una sonda compilada antes de publicar dio CS8852; el texto se corrigió, y compile_fail/l06_readonly_position.cs guarda ahora las dos líneas CS8852. Otras dos frases se apoyan en sondas que quedaron fuera del código del curso: guardar una copia modificada con shape[0] = position; mostró 5, y List<T>.Contains sobre una clase sin igualdad propia respondió False.
  • Dogfooding: en el commit 5c3a52a de GA, ChordQuality empieza por Other, y PositionLocation, Str y Fret son tipos readonly record struct. Nada en esta lección reveló un problema en GA.
  • La lección 7 construye Guitar y Ukulele sobre una clase base StringInstrument (base(...), virtual, override, base.Describe(), ToString), después una clase abstracta Instrument con un método abstracto Play, y después una interfaz IHasRange que implementan tanto una clase como un record. Tiene cuatro ejemplos, uno de los cuales compila con la advertencia CS0114, tres ejercicios y cinco fragmentos rechazados, cuyos diagnósticos son CS0506, CS0509, CS0144, CS0534 y CS0535. El script usado para la lección 6 comprobó los 20 bloques de código de los tres idiomas contra code/csharp-beginner y expected/.
  • En Windows, con el SDK 10.0.112 y Git Bash, check.sh terminó con código 0 y 87 líneas ok, incluidos los 12 archivos l07_*. Todavía no es una ejecución de la CI en tres sistemas.
  • Después, en la CI: la ejecución 36893689911 del workflow C# for beginners examples, sobre el commit b347f7d de la PR #95, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para los 12 archivos l07_*; los jobs cuentan 88 líneas ok en Linux y macOS y 87 en Windows, por la misma razón que en la lección 6.
  • Dos frases del borrador no se apoyaban en ninguna ejecución y cambiaron antes de publicar. Que solo una variable de tipo Ukulele encuentre un método que oculta es ahora una línea de examples/l07_hiding_warning.cs, que muestra a ukulele. «Los números MIDI son los de la lección 4, donde do4 vale 60» era falso, ya que la lección 4 no da ningún número MIDI; la frase dice ahora que los números MIDI cuentan semitonos, 12 por octava.
  • Dogfooding: en el commit 5c3a52a de GA, ChordTemplate es un record abstracto cuyos records derivados TonalModal y Analytical redefinen Name, y cuyo propio ToString() => Name no es sealed. La página de los records dice que un ToString sealed impide que el compilador sintetice uno en los records derivados (formato integrado para la presentación); este debía quedar, pues, oculto. Una sonda lo confirmó: un proyecto de consola que referencia GA.Domain.Core y GA.Domain.Services en el commit 5c3a52a, compilado con el SDK 10.0.112, mostró TonalModal { Name = Major 7th, PitchClassSet = 0 4 7 E, … } para ToString() y para $"{template}" en un TonalModal, en un Analytical y en ChordTemplateFactory.CreateModalChords(...).First(), y la reflexión señaló el record derivado como el tipo que declara ToString. Una réplica de la misma forma con public sealed override string ToString() => Name mostró maj7. Un solo punto de llamada depende de ello: ContextualChordsController.cs:301 pasa template.ToString() a GetVoicingsForChordAsync, que lo registra (FretboardServices.cs:536); la respuesta HTTP toma el nombre del acorde de la ruta, no de esta cadena. El hallazgo está en la tabla QA; no se ha comunicado a GA.

2026-10-02 — Excepciones y seguridad frente a null

Sección titulada «2026-10-02 — Excepciones y seguridad frente a null»
  • La lección 8 captura excepciones con try y catch, con un diagrama de las clases de excepción; ejecuta finally después de un return, después de un catch y antes de que una excepción salga del método; lanza una ArgumentOutOfRangeException con nameof; y después vuelve sobre null: CS8600 y CS8618, required, el operador !, y WarningsAsErrors=nullable definido con #:property. Tiene seis ejemplos, cuatro archivos de ejercicio (uno de ellos el punto de partida del ejercicio 3, con cuatro advertencias) y cinco fragmentos rechazados, cuyos diagnósticos son CS0160, CS0165, CS0029, CS9035 y CS8602 como error. El script usado para las lecciones 6 y 7 comprobó los 28 bloques de código de los tres idiomas contra code/csharp-beginner y expected/.
  • En Windows, con el SDK 10.0.112 y Git Bash, check.sh terminó con código 0 y 102 líneas ok, incluidos los 15 archivos l08_*. Todavía no es una ejecución de la CI en tres sistemas.
  • La primera ejecución de la CI de la PR #109 falló solo en Linux (ejecución 36968274101): el runner de Linux mostró las advertencias de l08_null_warnings.cs y l08_ex_null_start.cs en el orden de las líneas, mientras que Windows, en local y en la CI, y macOS mostraban CS8618 primero. Los mensajes eran idénticos. check.sh coloca ahora los mensajes del compilador primero, los errores antes que las advertencias, cada grupo ordenado por línea y columna, y la lección muestra ese orden. Ningún archivo esperado de las lecciones 1 a 7 cambió: sus mensajes ya venían en ese orden.
  • Después, con esa corrección: la ejecución 37009963497 del workflow C# for beginners examples, sobre el commit e2a95ef de la PR #109, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para los 15 archivos l08_*; los jobs cuentan 103 líneas ok en Linux y macOS y 102 en Windows, por la misma razón que en la lección 6.
  • Sorpresas, todas recogidas en la lección: throw "fret out of range"; da CS0029, un error de conversión, donde esperaba CS0155 (The type caught or thrown must be derived from System.Exception); string capo = null; sobre una variable local da CS8600, no CS8625, mientras que el archivo de prueba de GA de más abajo recibió CS8625 por un argumento null; en l08_null_warnings.cs, CS8618 está en la declaración de la propiedad y la línea que falla no tiene ninguna advertencia; tuner off se muestra antes que el valor que devuelve Tune.
  • Una frase se apoya en una sonda que quedó fuera del código del curso: sin su comprobación, NoteOfString(standard, 7) falla con una IndexOutOfRangeException.
  • Dogfooding en el commit 5c3a52a de GA, con las hipótesis escritas antes de medir en la tabla de experimentos, y las sondas y sus instrucciones en code/csharp-beginner/ga-probes:
    • NoWarn enumera quince advertencias de nulabilidad en Directory.Build.props:21, y otra vez en Directory.Build.targets:7. Esperaba más de 100 advertencias ocultas en GA.Domain.Core; sin las dos listas, GA.Domain.Core y GA.Core no tienen ninguna. ZzNullableCanary.cs, siete errores de nulabilidad añadidos a GA.Domain.Core, compiló sin advertencias con las listas, y con sus siete advertencias (CS8600 ×2, CS8602 ×3, CS8618, CS8625) sin ellas. La hipótesis queda refutada, y la tabla QA guarda el hallazgo: las listas solo ocultan los errores que están por venir.
    • new Fret(50), Fret.FromValue(50) y Fret f = 50; lanzan todos una ArgumentOutOfRangeException, para 50 y para -2, como se esperaba, y Fret.TryCreate(50) devuelve Failure(Fret number must be between -1 (muted) and 36, got 50). El mensaje de la excepción nombra una constante privada, GA.Domain.Core.Instruments.Primitives.Fret value (50) cannot be greater than _maxValue (36). (Parameter 'value'), porque IRangeValueObject.cs lo construye a partir de las expresiones del llamador, y su ActualValue queda vacío.
    • La pregunta abierta del 2026-10-01 tiene respuesta: new PositionLocation(5, 3), con la cuerda y el traste intercambiados como simples números, compila sin advertencia y construye la cuerda 5, traste 3. Una compilación con todos los analizadores (AnalysisLevel=latest-all) solo pidió, en esas líneas, tipos explícitos en lugar de var. Las conversiones implícitas desde int (Str.cs:45, Fret.cs:72) dejan pasar el intercambio.
  • La lección 9 cubre Dictionary<TKey, TValue>: el indexador, Add, TryGetValue, contar con GetValueOrDefault y el orden de un foreach. Cubre HashSet<T>, con sus operaciones de conjunto sobre copias y SetEquals. Presenta las lambdas y luego LINQ sobre los proyectos de GA de la lección 4, ahora records: Count, Where, Select, OrderBy, ThenBy, First, FirstOrDefault, Any, All, Take y ToList. Termina con la ejecución diferida y con la modificación de una colección dentro de su propio foreach. Tiene diez ejemplos, cuatro ejercicios y cuatro fragmentos rechazados: CS1503, CS0021, CS0266, y CS0029 con CS1662 en la misma columna. El comprobador de bloques hizo corresponder los 37 bloques de código de cada idioma con code/csharp-beginner y expected/.
  • La lección 4 anunciaba que la lección 5 sustituiría sus dos arrays de proyectos por una sola lista; la lección 5 no lo hizo. La lección 9 lo hace, y la lección 4 remite ahora a ella.
  • En Windows, con el SDK 10.0.112 y Git Bash, check.sh terminó con código 0 y 120 líneas ok, incluidos los 18 archivos l09_*. Todavía no es una ejecución de la CI en tres sistemas.
  • Después, en la CI: la ejecución 37061776660 del workflow C# for beginners examples, sobre el commit 690201d de la PR #113, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para los 18 archivos l09_*; los jobs cuentan 121 líneas ok en Linux y macOS y 120 en Windows, por la misma razón que en la lección 6.
  • Medido antes de escribir:
    • C# 14 no tiene expresiones de diccionario. Dictionary<string, int> d = ["C": 0, "E": 4]; da CS1003 y otros ocho errores de sintaxis, así que la lección usa un inicializador de índice, { ["C"] = 0 }.
    • First sin coincidencia lanza Sequence contains no matching element. Esta sonda quedó fuera del código del curso.
    • Los dos experimentos de diccionarios de la tabla. La documentación respalda ambos: «The order in which the items are returned is undefined», y Remove «may be safely called without invalidating active enumerators» desde .NET Core 3.0.
  • Dogfooding en el commit 5c3a52a de GA, con la hipótesis escrita antes de medir en la tabla de experimentos:
    • Un repaso de las colecciones en el código de GA, leído en ese commit, dio los ejemplos de la lección:
      • ChordVocabulary.PitchClasses (ChordVocabulary.cs:23), un diccionario que no distingue mayúsculas, leído con TryGetValue, donde "C#" y "Db" dan ambos 1. Sus observaciones dicen que dos copias anteriores «had drifted» (se habían separado); un git grep de ["Db"] = 1 o { "Db", 1 } encuentra 18 archivos con su propia tabla, este incluido.
      • PitchClassSet, un IReadOnlySet<PitchClass> construido sobre un ImmutableSortedSet (PitchClassSet.cs:37), igual a otro cuando sus Id, calculados a partir de las notas, lo son.
    • MusicalKnowledgeService.GetArtistBreakdown se queda con GetAllArtists().Take(20), los 20 primeros por nombre, y luego los ordena por número de entradas en un Dictionary. Esperaba más de 20 artistas, y algunos excluidos con más entradas que uno conservado. artists.cs, compilado con GA.Domain.Services extraído con git archive en la carpeta .ga ignorada, encontró 16: ninguno queda fuera. Dos ejecuciones dieron la misma salida.
    • La razón es la primera fila QA del día: tres de los cuatro archivos YAML no se deserializan, y cada cargador captura la excepción y conserva un elemento por defecto. La sonda muestra los mensajes de los cargadores. Una prueba de deserialización, fuera del código del curso, localizó las tres líneas, y leí cada una frente a la clase que llena. Las tres líneas siguen igual en el main de GA, ae196bf, el más reciente que conoce el clon local.
    • MusicalKnowledgeServiceTests muestra ArtistBreakdown.Take(5) bajo === Top Artists === (MusicalKnowledgeServiceTests.cs:51), lo que también depende del orden del diccionario.

2026-10-03 — Un diagrama del mástil dibujado en C#

Sección titulada «2026-10-03 — Un diagrama del mástil dibujado en C#»
  • La lección 3 muestra ahora las notas de los trastes 0 a 5 en un diagrama, debajo de la salida de l03_fretboard.cs. La pregunta era si ComfyUI o Blender debían dibujar algunas imágenes del curso. Una imagen generada puede equivocarse en el número de cuerdas o de trastes, y este diagrama debe mostrar exactamente las notas que imprime el programa, así que lo dibuja un programa C#: tools/fretboard.cs, con las mismas cuerdas al aire y los mismos nombres de notas que la lección.
  • El SVG no contiene palabras, solo nombres de notas y números, así que los tres idiomas comparten un único archivo, src/assets/csharp-beginner/l03-fretboard.svg. La lección lo importa como un componente, como el curso de teoría musical, para que siga el tema claro u oscuro. Para eso la lección 3 pasó a ser un archivo .mdx; su dirección no cambia. La descripción para lectores de pantalla está traducida.
  • check.sh ejecuta tools/fretboard.cs -- --check, que falla si el SVG del repositorio difiere de lo que dibuja el programa. Antes de fiarme, cambié a mano una nota del archivo: la comprobación imprimió is not up to date y terminó con el código 1. Una copia con finales de línea de Windows pasa. En Windows, check.sh imprime ahora 121 líneas ok.
  • La herramienta encuentra el repositorio a partir de su propia carpeta, AppContext.GetData("EntryPointFileDirectoryPath"), y no de la carpeta actual, y escribe cada coordenada como un int: con la cultura fr-FR, $"{2.5}" da 2,5. Los dos puntos importan para los archivos, el tema de la lección 10.
  • Renderizado con Edge sin ventana en una página clara, una oscura y a 343 píxeles de ancho, el de un teléfono: las 36 notas coinciden línea por línea con la salida del programa. Ese día, Chrome sin ventana y la captura por DevTools superaron su tiempo de espera en esta máquina.
  • Después, en la CI: la ejecución 37142282699 del workflow C# for beginners examples, sobre el commit 9229f15 de la PR #126, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok fretboard svg; los jobs cuentan 122 líneas ok en Linux y macOS y 121 en Windows, por la misma razón que en la lección 6.
  • La lección 10 está escrita en tres idiomas: File, Path, el directorio actual, los saltos de línea, un archivo CSV, los números y las culturas, los flujos y using, las codificaciones, y lo que Guitar Alchemist hace con sus archivos. Tiene 11 ejemplos, 4 ejercicios y 3 fragmentos rechazados, cada uno comparado con expected/ por check.sh. La lección es un archivo .mdx, por las pestañas que muestran una ruta en cada sistema.
  • data/ga-projects.csv es nuevo: los doce proyectos de la lección 4 con sus números de archivos .cs y .fs en el commit 5c3a52a de GA, contados con git ls-tree en la carpeta de cada proyecto. Trae dos sorpresas. GA.Business.Config, un proyecto F#, contiene 8 archivos C#, los servicios que vio la lección 9. GA.Presentation no tiene ningún archivo fuente, solo su .csproj. .gitattributes da a *.csv finales de línea LF, para que el archivo sea el mismo en cada sistema.
  • Se escribieron ocho hipótesis antes de medir, y las ocho se confirman (tabla de experimentos). Dos no eran a ciegas: al escribir tools/fretboard.cs, ese mismo día, ya había visto EntryPointFileDirectoryPath, y $"{2.5}" con fr-FR. La hipótesis E14 viene de la lectura de GA por un subagente, antes de que yo leyera ScaleVideoUrlById.
  • check.sh ejecuta l10_where.cs una segunda vez desde la raíz del repositorio y compara su salida con expected/l10_where_root.txt: así la CI comprueba en tres sistemas que el mismo programa no encuentra su archivo desde otra carpeta. La raíz no tiene global.json: allí, en mi máquina, dotnet elige el SDK 11.0.100 preliminar en lugar del 10.0.112, otra decisión que toma el directorio actual. La salida es la misma.
  • TextFieldParser compiló con using Microsoft.VisualBasic.FileIO; y sin paquete: viene con .NET.
  • La sonda files.cs ejecuta los cargadores de GA tres veces, como describe su README: tal como se compila, tras borrar los archivos YAML junto a ella, y desde la raíz del código de GA extraído. Su compilación imprime IL3000 por su propio uso de Assembly.Location, la llamada de la que depende el repliegue de ScaleVideoUrlById: en una aplicación de archivo único devuelve una cadena vacía.
  • Seis filas QA nuevas vienen de la sonda y de la lectura del código de GA que maneja sus archivos. Un subagente listó los candidatos; releí las líneas de cada uno en el commit 5c3a52a antes de escribirla. Dos de esas filas solo se leyeron en el código, no se ejecutaron.
  • El fallo de carga de los YAML de la lección 9 es ahora GuitarAlchemist/ga#797, abierta el 2026-10-03 con enlaces fijados al main de GA, ba3b9ac, donde las tres líneas siguen igual.
  • En Windows con el SDK 10.0.112, check.sh termina con el código 0 y 140 líneas ok, incluidas las 19 de la lección 10. Todavía no es una ejecución de la CI en tres sistemas.
  • Después, en la CI: la ejecución 37145323198 del workflow C# for beginners examples, sobre el commit 8b9fc03 de la PR #129, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para las 19 comprobaciones l10_*, incluida la ejecución desde la raíz del repositorio; los jobs cuentan 141 líneas ok en Linux y macOS y 140 en Windows, por la misma razón que en la lección 6.
  • La lección 11 está escrita en tres idiomas: una prueba a mano, una biblioteca y un proyecto de pruebas, [Fact] y dotnet test, [Theory], cuatro pruebas que fallan a propósito, un objeto por prueba, la superficie pública, y las pruebas de Guitar Alchemist. El código está en l11-tests: la biblioteca Fretboard, con métodos de las lecciones 4, 8 y 10 sin cambios; Fretboard.Tests, 26 pruebas con las soluciones de los ejercicios; Pitfalls.Tests, 6 pruebas de las que 4 fallan a propósito. examples/l11_by_hand.cs y compile_fail/l11_private.cs se ejecutan con los demás archivos.
  • Los proyectos vienen de dotnet new classlib, dotnet new xunit y dotnet add reference, ejecutados en la carpeta del curso; la lección cita sus mensajes. La plantilla crea un proyecto xUnit v2, mientras que el sitio de xUnit dice que la v2 está en mantenimiento y anima a usar la v3.
  • check.sh ejecuta dotnet test en los dos proyectos de pruebas tras dotnet clean, para que las advertencias de la compilación se impriman siempre, y guarda un resumen: las advertencias, cada prueba fallida y su mensaje, la última línea. Quita las rutas, las duraciones y las pilas de llamadas, y ordena las pruebas fallidas por nombre: xUnit comunicó los cuatro fallos en otro orden que el del archivo. Las flechas ↓ y ↑ de un fallo en una colección atravesaron Git Bash en Windows; la CI dirá si pasa lo mismo en los tres sistemas.
  • Se escribieron siete hipótesis antes de medir, y las siete se confirman (tabla de experimentos). Las dos últimas se escribieron tras leer el archivo de pruebas de GA y su bootstrap, antes de ejecutarlos.
  • Observado sin hipótesis: desde un proyecto de pruebas, un miembro de la biblioteca marcado internal da CS0117, «does not contain a definition», y no CS0122, en un par de proyectos de prueba; el analizador del paquete xunit avisa de los argumentos invertidos (xUnit2000) antes de que se ejecuten las pruebas.
  • El primer fallo de la lección es una comprobación a mano: FretFrequency(246.94, 1) da 261.62, y una tabla da 261.63 para el do central, porque la cuerda Si al aire ya está redondeada. El programa termina igualmente con el código 0, lo que motiva un framework.
  • La sonda knowledge-tests es un proyecto NUnit que compila sin cambios cuatro archivos de GA.Business.Core.Tests extraídos del código: MusicalKnowledgeServiceTests.cs, TestEnvironment.cs, GlobalUsings.cs y AssemblyInfo.cs, con las versiones de paquetes de ese proyecto. ProbeTests.cs es su único archivo propio. Su compilación solo imprime IDE0028 en ScaleMetadataRegistry.cs de GA. Su ejecución: 9 pruebas, 6 superadas (las 5 de GA y la de la sonda), 3 omitidas.
  • Esto responde a la pregunta abierta del diario: ninguna prueba de GA falla cuando un archivo YAML de GA.Business.Config deja de cargarse. La pregunta se quita de la lista de abajo.
  • En Windows con el SDK 10.0.112, check.sh termina con el código 0 y 144 líneas ok, incluidas las 4 de la lección 11. Todavía no es una ejecución de la CI en tres sistemas.
  • Después, en la CI: la ejecución 37170066128 del workflow C# for beginners examples, sobre el commit c3427a9 de la PR #135, pasó en ubuntu-latest, windows-latest y macos-latest. Cada job imprime ok para las 4 comprobaciones l11_*; los jobs cuentan 145 líneas ok en Linux y macOS y 144 en Windows, por la misma razón que en la lección 6. l11_Pitfalls.Tests también pasa en Windows: las flechas ↓ y ↑ del fallo en una colección atraviesan Git Bash sin cambios.
  • La lección 12 está escrita en tres idiomas: una solución con la biblioteca de la lección 11, una aplicación de consola y un proyecto de pruebas; los códigos de salida; un paquete de NuGet en una aplicación basada en archivos y en la biblioteca; un paquete con una vulnerabilidad conocida; dotnet pack; un primer vistazo a async; y la solución, las versiones de paquetes, la herramienta de línea de comandos y las esperas bloqueantes de Guitar Alchemist. El código está en l12-project: Fretboard, la biblioteca de la lección 11 con Guitar sin cambios, Project sin Parse, y ProjectFile, que lee el archivo CSV con CsvHelper 33.1.0; Fretboard.App, con tres comandos; Fretboard.Tests, 6 pruebas. Cuatro ejemplos, dos fragmentos rechazados y cuatro soluciones de ejercicios son aplicaciones basadas en archivos.
  • check.sh empaqueta la biblioteca antes del bucle, porque exercises/l12_ex_package.cs lee el paquete, y borra antes el paquete viejo: sin cambios, dotnet pack lo conserva y no imprime ninguna de sus dos líneas. Ejecuta dotnet test sobre la solución tras dotnet clean y comprueba que la aplicación de consola sigue sin compilar, y después dotnet build, cuya salida reduce a los nombres de los proyectos, ordenados, y a los totales. Ejecuta la aplicación con seis comandos e imprime cada código de salida. Una nueva regla sed quita la carpeta de los mensajes de NuGet de una línea #:package, file.csproj : warning NUxxxx.
  • Doce hipótesis sobre el SDK y nueve sobre GA se escribieron antes de medir, algunas tras leer el código que prueban (tabla de experimentos): 16 confirmadas, 1 refutada en parte, 2 refutadas. Las refutadas tratan de las esperas de GA: 11 esperas bloqueantes compiladas, no 20 o más, y ningún async void.
  • Observado sin hipótesis: dotnet test sobre una solución no compila la aplicación de consola, lo que llevó a la hipótesis sobre un error de compilación; la advertencia de la auditoría de NuGet sale dos veces en una aplicación basada en archivos; el .nuspec guarda el commit de HEAD; 12 de los 28 comandos de GaCLI lanzan NotImplementedException, y la ejecución 37167745677 de Quality Check de GA está en verde con un paso que terminó con el código de salida 134.
  • Las sondas están en ga-probes: lesson12_counts.py lee el commit solo con git, y lesson12-results.txt guarda su salida; la compilación de GA.Business.Core.Graphiti.Tests y su restauración, con una copia de control cuya línea Update está en Directory.Build.targets; GaCLI, compilado a partir de los 18 proyectos de sus referencias en 28 s con 21 advertencias, y lanzado desde dos carpetas.
  • Esto resuelve el punto por verificar sobre GaCLI lanzado desde otra carpeta: funciona, y en este commit appsettings.yaml no cambia nada. El punto se retira de la lista de abajo, y la fila de la tabla QA se actualiza.
  • En Windows con el SDK 10.0.112, check.sh termina con el código 0 y 158 líneas ok, incluidas las 14 de la lección 12. Todavía no es una ejecución de la CI en tres sistemas.
  • La primera ejecución de la CI de la PR, 37174610693, falló en l12_audit y l12_package_no_version en los tres sistemas, y pasó las otras 12 comprobaciones de la lección 12. La CI usa el SDK 10.0.401, que global.json permite (rollForward: latestFeature), y ese SDK llama l12_audit.cs.csproj al proyecto que hay detrás de una aplicación basada en archivos, donde el 10.0.112 dice l12_audit.csproj. La regla sed no esperaba un punto en el nombre y dejaba la carpeta. check.sh escribe ahora file.csproj para los dos, y la lección da los dos nombres.
  • Después, en la CI: la ejecución 37175088215 del workflow C# for beginners examples, sobre el commit 87dbafd de la PR #137, pasó en ubuntu-latest, windows-latest y macos-latest, los tres con el SDK 10.0.401. Cada job imprime ok para las 14 comprobaciones l12_*; los jobs cuentan 159 líneas ok en Linux y macOS y 158 en Windows, por la misma razón que en la lección 6. La advertencia de la auditoría de NuGet de l12_audit.cs sale en las tres máquinas, y la salida ordenada de la compilación es la misma.

2026-10-04 — Correcciones propuestas a Guitar Alchemist

Sección titulada «2026-10-04 — Correcciones propuestas a Guitar Alchemist»

Trece filas de la tabla QA volvieron a GA como cinco pull requests, abiertas desde un clon aparte en main 75959835. Cada una mide el defecto antes y después de la corrección, y la CI de GA pasa en las cinco. Ninguna está fusionada todavía: GA deja la revisión y la fusión a alguien distinto del autor.

PR Filas QA Cambio Antes → después
808 Los tres archivos YAML (issue 797); las pruebas que no ven un valor de repliegue Los tres archivos se cargan; las tres pruebas [Ignore] se ejecutan, y una prueba nueva falla cuando un archivo recurre a su elemento por defecto Con los archivos antiguos fallan 5 de las 9 pruebas, la nueva con Expected: greater than 1 But was: 1 para cada archivo roto
811 ReloadConfiguration, GetArtistBreakdown, la cultura del CSV, su número de filas, sync-mongodb Un Lazy nuevo en lugar de FieldInfo.SetValue; el orden antes de Take(20); FormattableString.Invariant; la cabecera fuera de la cuenta; Environment.Exit(1) FieldAccessException → los cuatro archivos se recargan; < 6, 8, 8, … > valores por fila con fr-FR → 6; 3 filas contadas para una cabecera y dos filas → 2; sync-mongodb sale con 0 → 1
812 Los proyectos de pruebas fuera de AllProjects.slnx, los elementos PackageReference Update, el escaneo de seguridad Los elementos pasan a Directory.Build.targets; tres proyectos de pruebas entran en la solución; el escaneo falla con un aviso que una línea base no lista Degradaciones NU1605 ocultas por NoWarn: 80 → 40; 56 pruebas más en la CI; 21 avisos en la línea base
816 El ToString de una plantilla de acorde, el [SetUpFixture] ChordTemplate.ToString pasa a ser sealed; TestEnvironment pasa al espacio de nombres raíz del proyecto Un volcado de 442 o 475 caracteres → el nombre; GA_TEST_MODE sin definir → 1, con la raíz del repositorio como directorio actual
817 ScaleVideoUrl GA.Domain.Core.csproj incrusta scale_video_urls.json, y ScaleVideoUrlById lo lee con su nombre por defecto Conjuntos con URL de vídeo: 0 → 1 490 de 4 096
  • Con el YAML de la 808, GA cuenta 63 artistas en lugar de 16: el corte de GetArtistBreakdown antes de ordenar se nota, y la prueba de la 811 sobre ese reparto falla con esos datos y el código antiguo.
  • Mover solo los elementos Update hacía fallar la restauración, error NU1107: Version conflict detected for Microsoft.Extensions.Logging.Abstractions en GA.Infrastructure, porque solo cinco paquetes Microsoft.Extensions estaban alineados. La 812 pone los 19 que se distribuyen con .NET y ASP.NET Core en una sola versión, 10.0.6, la más alta que pide un proyecto o un paquete.
  • Tres de los seis proyectos de pruebas fuera de la solución, que ningún workflow nombra, entran en ella: las pruebas de Graphiti, con la referencia de proyecto que les faltaba, las de ProbabilisticGrammar y las de TabConversion.Api. GaChatbot.Tests y Tests/GaApi.Tests tampoco compilan en main, y FloorManager.Tests.Playwright necesita un FloorManager en marcha.
  • La comprobación risk-report de GA falla en la 812 por diseño: su política prohíbe a un agente modificar .github/workflows y considera Directory.Build.*, *.csproj y *.slnx decisiones irreversibles, así que debe revisarla un mantenedor.
  • La 817 modifica un .csproj, que la política de riesgo de GA cuenta como una puerta de un solo sentido: por eso es una pull request aparte.
  • La primera ejecución de la CI de la 816 falló en una prueba de otro proyecto que da 10 segundos a PowerShell; la misma prueba había fallado en otra rama el 2026-05-23, y la nueva ejecución pasó.

2026-10-06 — Las PR 816 y 817 de GA, revisadas

Sección titulada «2026-10-06 — Las PR 816 y 817 de GA, revisadas»

Las PR 816 y 817 de GA siguen abiertas, sin revisión ni comentarios. Su CI pasa, 16 comprobaciones con 5 omitidas, y GitHub las da por fusionables sin conflicto, aunque el main de GA avanzó 14 commits desde su base, hasta 40d33747. Leídos con la API de GitHub, los tres defectos no cambiaron en ese commit:

  • El ToString() => Name de ChordTemplate sigue sin ser sealed (ChordTemplate.cs:84).
  • ScaleVideoUrlById sigue leyendo el recurso GA.Domain.Core.Scales.Data.scale_video_urls.json y la ruta de repliegue Scales/Data/ (ScaleVideoUrlById.cs:30, :38), y GA.Domain.Core.csproj sigue sin nombrar el archivo.
  • TestEnvironment sigue solo en GA.Business.Core.Tests.TestBootstrap (TestEnvironment.cs:1).

La lección 7 muestra ahora la trampa de ToString junto a ChordTemplate, con examples/l07_record_tostring.cs. Un record derivado imprime Seventh { Name = Major 7th, Quality = Major 7th }; el mismo record bajo una base cuyo ToString es sealed imprime Major 7th. Las dos salidas vienen del SDK 10.0.112, comparadas con expected/ como lo hace check.sh. El ejemplo existente l07_inheritance.cs, ejecutado igual, sigue coincidiendo con su salida esperada. Las sondas de GA no se volvieron a ejecutar: necesitan una copia de GA fuera del código del curso. Tampoco se ejecutó check.sh completo.

  • Cuántos artistas contaría GA una vez cargados sus tres archivos YAML. Un script de PyYAML sobre los archivos en bruto, fuera del código del curso, estima 63. Según la misma estimación, Take(20) dejaría fuera a 8 artistas con más entradas que el menor número conservado, entre ellos The Beatles (3 entradas).
  • Los comandos de instalación para Linux y macOS: en esos sistemas operativos solo se ejecutó el setup-dotnet de la CI.
  • El recorrido del depurador de la lección 3 en VS Code, Visual Studio y Rider, para una aplicación basada en archivos y para un proyecto. VS Code 1.118 y Rider están instalados en mi máquina; Visual Studio no.
  • Las condiciones de licencia de los tres editores, en el momento de leerlas.
  • ¿Tienen los demás proyectos de GA, más allá de GA.Domain.Core y GA.Core, advertencias de nulabilidad que las dos listas NoWarn ocultan? Solo se midieron esos dos (2026-10-02).