diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7d9b009..30b6d45 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.10.0" + ".": "0.10.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 426c5f1..5a83464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ desde los Conventional Commits y bumpea `pyproject.toml`. Al mergear ese PR se c `vX.Y.Z` y el GitHub Release. Las secciones por debajo de `[0.3.0]` son el historial previo a la conexión del tooling (se mantuvieron a mano); de acá en adelante las gestiona el bot. +## [0.10.1](https://github.com/complexluise/bib2graph/compare/v0.10.0...v0.10.1) (2026-06-30) + + +### Bug Fixes + +* **export:** escribir CSV en utf-8-sig (BOM) para Excel-Windows ([#216](https://github.com/complexluise/bib2graph/issues/216)) ([66339a7](https://github.com/complexluise/bib2graph/commit/66339a787debb04e88413147cfc8d7e22916ae2f)) + ## [0.10.0](https://github.com/complexluise/bib2graph/compare/v0.9.0...v0.10.0) (2026-06-28) diff --git a/pyproject.toml b/pyproject.toml index eb7389c..dc1043b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ build-backend = "hatchling.build" [project] name = "bib2graph" -version = "0.10.0" # Hitos 7 (dedup) + 8 (co-citación). De acá en adelante el bump +version = "0.10.1" # Hitos 7 (dedup) + 8 (co-citación). De acá en adelante el bump # lo maneja release-please vía PR de release (CI ya conectado). description = "Librería de Python para transformar corpus bibliográficos en redes bibliométricas reproducibles (co-citación, colaboración, co-ocurrencia)." readme = "README.md" diff --git a/src/bib2graph/exporters/csv.py b/src/bib2graph/exporters/csv.py index 5616743..51be5d0 100644 --- a/src/bib2graph/exporters/csv.py +++ b/src/bib2graph/exporters/csv.py @@ -50,7 +50,8 @@ def export( def _write_aristas(self, g: _Graph, out_path: Path) -> None: """Escribe aristas.csv con columnas source,target,weight (D5).""" - with open(out_path / "aristas.csv", "w", newline="", encoding="utf-8") as f: + # utf-8-sig (BOM) para que Excel-Windows autodetecte UTF-8 y no rompa tildes (#214) + with open(out_path / "aristas.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["source", "target", "weight"]) # Orden determinista: aristas ordenadas por (source, target) @@ -85,7 +86,8 @@ def _write_nodos( metric_cols = sorted(metric_keys) header = ["id", "label", *attr_cols, *metric_cols] - with open(out_path / "nodos.csv", "w", newline="", encoding="utf-8") as f: + # utf-8-sig (BOM) para que Excel-Windows autodetecte UTF-8 y no rompa tildes (#214) + with open(out_path / "nodos.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(header) # Orden determinista: nodos ordenados por id como string diff --git a/tests/unit/test_exporters.py b/tests/unit/test_exporters.py index 6cb97f4..333d11d 100644 --- a/tests/unit/test_exporters.py +++ b/tests/unit/test_exporters.py @@ -148,6 +148,30 @@ def test_csv_aristas_columnas_correctas( """aristas.csv tiene columnas source,target,weight (D5).""" CsvExporter().export(simple_graph, results_dict, tmp_path) - content = (tmp_path / "aristas.csv").read_text(encoding="utf-8") + content = (tmp_path / "aristas.csv").read_text(encoding="utf-8-sig") header = content.splitlines()[0] assert header == "source,target,weight" + + +@pytest.mark.unit +def test_csv_escribe_con_bom_utf8_para_excel(tmp_path: Path) -> None: + """Los CSV se escriben en utf-8-sig (BOM) para Excel-Windows (#214). + + Sin BOM, Excel asume cp1252 y rompe las tildes (Valoración → Valoración). + Regresión: el export debe empezar con el BOM UTF-8 y las tildes deben + sobrevivir el round-trip. + """ + g = nx.Graph() + g.add_node("doi:abc", label="Valoración estética (Müller, 1987)") + g.add_node("doi:def", label="Crítica de la razón") + g.add_edge("doi:abc", "doi:def", weight=3) + + CsvExporter().export(g, {"grado": dict(g.degree())}, tmp_path) + + for name in ("nodos.csv", "aristas.csv"): + raw = (tmp_path / name).read_bytes() + assert raw.startswith(b"\xef\xbb\xbf"), f"{name} debe empezar con BOM UTF-8" + + # Las tildes sobreviven el round-trip leyendo utf-8-sig + nodos = (tmp_path / "nodos.csv").read_text(encoding="utf-8-sig") + assert "Valoración estética (Müller, 1987)" in nodos