v0.1-sprint0: Esqueleto completo AR-ShipDesign

- Estructura completa de carpetas (236 módulos stub + implementados)
- pyproject.toml, requirements, .gitignore, LICENSE (propietario)
- core/project.py: serialización .arsd (ZIP con JSON)
- core/units.py: conversiones SI <-> imperial completas
- ui/main_window.py: layout DELFTship-style con todos los paneles
  - Árbol de proyecto (dock izquierda)
  - Tabs de módulos (centro)
  - Panel de propiedades (dock derecha)
  - Panel hidrostáticos en vivo (inferior, fijo)
- ui/i18n: español e inglés
- ui/themes: tema claro y oscuro
- utils/logger.py, settings.py, validation.py
- data/liquids.json: 15 líquidos navales
- data/stability_criteria.json: IMO IS Code 2008, A.749(18), USCG
- tests/test_startup.py: 12 tests, todos PASSED
- Módulo scantling/ ISO 12215 (stubs Sprint 2.5)
- Módulo fabrication/molds/ para moldes FRP (stubs Sprint 13B)
- Módulo fabrication/ para CNC plasma/router/laser (stubs Sprint 13)
This commit is contained in:
2026-05-26 22:10:18 -04:00
commit 0dbc2a4518
266 changed files with 4249 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
"""
Configuración global de pytest para AR-ShipDesign.
"""
import sys
from pathlib import Path
# Asegurar que el proyecto raíz está en sys.path
sys.path.insert(0, str(Path(__file__).parent.parent))
+166
View File
@@ -0,0 +1,166 @@
"""
Tests de Sprint 0 — Validación del esqueleto del proyecto.
"""
import json
from pathlib import Path
import pytest
def test_version_importable():
"""La versión del paquete debe ser importable."""
from arshipdesign import __version__
assert __version__ == "0.1.0"
def test_units_conversions():
"""Conversiones de unidades deben ser correctas."""
from arshipdesign.core.units import (
m_to_ft, ft_to_m,
kn_to_ms, ms_to_kn,
kg_to_ton_metric,
w_to_kw,
c_to_f,
)
# Longitud
assert abs(m_to_ft(1.0) - 3.280839895) < 1e-6
assert abs(ft_to_m(3.280839895) - 1.0) < 1e-6
# Velocidad
assert abs(kn_to_ms(1.0) - 0.514444) < 1e-4
assert abs(ms_to_kn(0.514444) - 1.0) < 1e-3
# Masa
assert abs(kg_to_ton_metric(1000.0) - 1.0) < 1e-9
# Potencia
assert abs(w_to_kw(1000.0) - 1.0) < 1e-9
# Temperatura
assert abs(c_to_f(0.0) - 32.0) < 1e-6
assert abs(c_to_f(100.0) - 212.0) < 1e-6
def test_units_roundtrip():
"""Las conversiones deben ser reversibles (roundtrip)."""
from arshipdesign.core.units import m_to_ft, ft_to_m, kn_to_ms, ms_to_kn
for val in [1.0, 5.5, 100.0, 0.01]:
assert abs(ft_to_m(m_to_ft(val)) - val) < 1e-9
assert abs(ms_to_kn(kn_to_ms(val)) - val) < 1e-9
def test_project_new():
"""Crear un proyecto nuevo debe generar metadatos correctos."""
from arshipdesign.core.project import Project
p = Project.new("Test Boat", author="Test Author")
assert p.name == "Test Boat"
assert p.metadata.author == "Test Author"
assert p.is_modified is True
assert p.path is None
def test_project_save_load(tmp_path):
"""Guardar y cargar un proyecto debe preservar los datos."""
from arshipdesign.core.project import Project
project = Project.new("Mi Velero de Prueba", author="Álvaro")
save_path = tmp_path / "test_project.arsd"
project.save(save_path)
assert save_path.exists()
assert not project.is_modified
# Recargar
loaded = Project.load(save_path)
assert loaded.name == "Mi Velero de Prueba"
assert loaded.metadata.author == "Álvaro"
assert loaded.metadata.format_version == "1.0"
assert not loaded.is_modified
def test_project_save_creates_valid_zip(tmp_path):
"""El archivo .arsd debe ser un ZIP válido con manifest.json."""
import zipfile
from arshipdesign.core.project import Project
p = Project.new("Zip Test")
path = tmp_path / "ziptest.arsd"
p.save(path)
assert zipfile.is_zipfile(path)
with zipfile.ZipFile(path) as zf:
assert "manifest.json" in zf.namelist()
assert "ship.json" in zf.namelist()
def test_project_load_nonexistent():
"""Cargar un archivo inexistente debe lanzar FileNotFoundError."""
from arshipdesign.core.project import Project
with pytest.raises(FileNotFoundError):
Project.load(Path("/ruta/que/no/existe.arsd"))
def test_project_load_wrong_extension(tmp_path):
"""Cargar un archivo con extensión incorrecta debe lanzar ValueError."""
from arshipdesign.core.project import Project
fake = tmp_path / "archivo.txt"
fake.write_text("hola")
with pytest.raises(ValueError):
Project.load(fake)
def test_i18n_files_valid():
"""Los archivos de i18n deben ser JSON válido con claves básicas."""
base = Path(__file__).parent.parent / "arshipdesign" / "ui" / "i18n"
for lang in ("es", "en"):
f = base / f"{lang}.json"
assert f.exists(), f"Falta el archivo i18n/{lang}.json"
data = json.loads(f.read_text(encoding="utf-8"))
assert "app_title" in data
assert "menu_file" in data
assert "hydro_draft" in data
def test_liquids_json_valid():
"""El archivo de líquidos debe tener las propiedades requeridas."""
liq_path = Path(__file__).parent.parent / "data" / "liquids.json"
assert liq_path.exists()
liquids = json.loads(liq_path.read_text(encoding="utf-8"))
required_codes = {"FW", "SW", "MGO", "HFO"}
for code in required_codes:
assert code in liquids, f"Líquido {code} faltante"
assert "density_kg_m3" in liquids[code]
assert liquids[code]["density_kg_m3"] > 0
def test_stability_criteria_json_valid():
"""El archivo de criterios de estabilidad debe tener IMO IS Code 2008."""
crit_path = Path(__file__).parent.parent / "data" / "stability_criteria.json"
assert crit_path.exists()
criteria = json.loads(crit_path.read_text(encoding="utf-8"))
assert "IMO_IS_Code_2008" in criteria
imo = criteria["IMO_IS_Code_2008"]["criteria"]
assert "area_0_30" in imo
assert imo["area_0_30"]["min"] == 0.055
def test_directory_structure():
"""Los módulos principales deben existir como paquetes Python."""
root = Path(__file__).parent.parent / "arshipdesign"
required_packages = [
"core", "geometry", "hydrostatics", "stability",
"scantling", "resistance", "propulsion",
"fabrication", "fabrication/molds",
"sailing", "seakeeping", "systems",
"ui", "ui/widgets", "ui/dialogs", "utils",
"reports", "parametric", "tanks", "engines", "io",
]
for pkg in required_packages:
init_file = root / pkg / "__init__.py"
assert init_file.exists(), f"Falta {pkg}/__init__.py"