chore: Remove obsolete build and GUI files, and introduce a new Docker Compose setup for microservices architecture. This change streamlines the project structure by eliminating legacy scripts and integrating a PostgreSQL database service alongside extraction and cleaning services.

This commit is contained in:
erik
2026-07-03 18:33:50 -05:00
parent 78df190d27
commit b55ad74539
75 changed files with 1699 additions and 8514 deletions
+52
View File
@@ -0,0 +1,52 @@
"""Gestor de escritura CSV.
Maneja la escritura incremental segura (con lock) en archivos CSV.
"""
import csv
import os
import re
import threading
from typing import Any, Dict, List
_LINE_BREAK_RE = re.compile(r"[\r\n\v\f\x85\u2028\u2029]+")
def sanitize_csv_cell_value(value: Any) -> Any:
"""Reemplaza saltos de línea en valores de celda por un espacio (una fila física por registro).
Conserva ``None`` y escalares numéricos/booleanos sin tocar. Cadenas y demás
tipos se normalizan solo si la representación en texto contiene saltos.
"""
if value is None:
return value
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return value
if isinstance(value, str):
if not _LINE_BREAK_RE.search(value):
return value
return _LINE_BREAK_RE.sub(" ", value).strip()
s = str(value)
if not _LINE_BREAK_RE.search(s):
return value
return _LINE_BREAK_RE.sub(" ", s).strip()
class CSVManager:
def __init__(self):
self._lock = threading.Lock()
def append_rows(self, csv_path: str, rows: List[Dict[str, Any]], header: List[str]) -> None:
"""Anexa filas a un CSV, creando encabezado si el archivo no existe."""
os.makedirs(os.path.dirname(csv_path) or ".", exist_ok=True)
file_exists = os.path.exists(csv_path) and os.path.getsize(csv_path) > 0
with self._lock:
with open(csv_path, "a", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=header, extrasaction="ignore", delimiter="|")
if not file_exists:
writer.writeheader()
for r in rows:
sanitized = {k: sanitize_csv_cell_value(v) for k, v in r.items()}
writer.writerow(sanitized)