diff --git a/.gitignore b/.gitignore index 7fea241..d5f0915 100644 --- a/.gitignore +++ b/.gitignore @@ -72,5 +72,6 @@ FACTUGES.FDB # =========================== # Otros # =========================== +out last_execution*.txt *.json diff --git a/Dockerfile b/Dockerfile index 89535bd..122f453 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,31 @@ # syntax=docker/dockerfile:1.4 # Usa una imagen base de Python -FROM python:3.12.6-slim-bookworm AS python_script +FROM python:3.11-slim +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + APP_HOME="/app" + + +WORKDIR ${APP_HOME} + +# Instalar librerías cliente Firebird RUN apt-get update RUN apt-get install libfbclient2 -y -# Establece el directorio de trabajo dentro del contenedor -WORKDIR /opt/uecko_sync_app +# Copiamos solo lo necesario para instalar el paquete +COPY pyproject.toml setup.cfg README.md ./ +COPY app ./app -# Copia los archivos del proyecto al contenedor -COPY . . -COPY ./.env.production ./.env +RUN pip install --no-cache-dir . -# Instala las dependencias de Python -RUN pip install --no-cache-dir -r requirements.txt +# Copiar enviroment (se sobreescribe en compose) +#COPY enviroment/ ./enviroment -# Instala cron en el contenedor -RUN apt-get update && apt-get install -y cron nano +# Volumen para logs persistentes +#VOLUME ["/app/logs"] -# Copia el archivo de cron dentro del contenedor -COPY cronjob /etc/cron.d/cronjob - -# Da permisos de ejecución al archivo cronjob -RUN chmod 0644 /etc/cron.d/cronjob - -# Aplica la configuración de cronjob -RUN crontab /etc/cron.d/cronjob - -# Crea un archivo log para cron -RUN touch /var/log/cron.log - -#RUN mkdir -p /var/log/uecko_sync_app -#RUN touch /var/log/uecko_sync_app/uecko_sync_app.log - -# Comando para iniciar cron y mantener el contenedor en ejecución -CMD cron && tail -f /var/log/cron.log +# Entrypoint genérico +#CMD ["python", "-m", "sync_factuges_main"] +CMD ["factuges-sync"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..acc724b --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# Factuges Sync +Proceso ETL para sincronizar datos desde una base de datos Legacy hacia MariaDB. +Incluye dos modos de sincronización: + +- `factuges` +- `verifactu` + +En desarrollo **no se utiliza Docker**. +En producción **sí se utiliza Docker + cron**. + +--- + +# 1. Requisitos previos + +- Python 3.11 o superior +- Git instalado +- Acceso a las bases de datos necesarias para las pruebas +- (Opcional) Librerías/cliente de Firebird si la BD legacy las requiere + +--- + +# 2. Clonar el repositorio + +```bash +git clone factuges-sync +cd factuges-sync +``` + +# 3. Crear y activar entorno virtual + +```bash +python3 -m venv venv +source venv/bin/activate +``` + +En Windows: + +```powershell +venv\Scripts\activate +``` + +# 4. Instalar dependencias del proyecto + +Este proyecto se instala mediante setup.cfg + pyproject.toml. + + +```bash +pip install --upgrade pip +pip install -e . +``` + +Esto crea el comando factuges-sync disponible en tu entorno virtual. + +# 5. Configurar entorno de desarrollo + +El código carga automáticamente: + +* environment/dev.env cuando ENV=dev +* Variables del contenedor en producción (ENV=prod) + +Por tanto, **en desarrollo**: + +```bash +export ENV=dev +``` + +(En Windows: set ENV=dev) + +Revisa/edita: + +```bash +environment/dev.env +``` + +Ejemplo: + +```ini +DB_HOST=localhost +DB_USER=root +DB_PASS=123 +SYNC_INTERVAL_MINUTES=5 +``` + +# 6. Ejecutar el job en desarrollo + +Puedes ejecutar cualquiera de los dos modos. + +**Opción A: usando el CLI** + +```bash +# Modo FACTUGES +factuges-sync factuges + +# Modo VERIFACTU +factuges-sync verifactu +``` + +**Opción B: llamando directamente a los módulos Python** + +```bash +python -m app.sync_factuges_main +python -m app.sync_verifactu_main +``` + +# 7. Logs en desarrollo + +Si el proyecto escribe logs en logs/: + +```bash +tail -f logs/job.log +``` + +# 8. Producción (resumen) + +En producción se usa Docker y cron. +Build: + +```bash +./scripts/build.sh +``` + +Cron job: + +```bash +*/5 * * * * docker run --rm -e ENV=prod -e SYNC_MODE=factuges myimage:latest +``` diff --git a/app.last_run.txt b/app.last_run.txt index 2f66024..0833840 100644 --- a/app.last_run.txt +++ b/app.last_run.txt @@ -1 +1 @@ -2025-11-28 10:37:49 \ No newline at end of file +2025-11-30 09:07:15 \ No newline at end of file diff --git a/app/cli.py b/app/cli.py new file mode 100644 index 0000000..08b70e5 --- /dev/null +++ b/app/cli.py @@ -0,0 +1,45 @@ +# app/cli.py +# CLI simple para seleccionar el entrypoint correcto. +# Permite: +# factuges-sync factuges +# factuges-sync verifactu +# SYNC_MODE=factuges factuges-sync + +import argparse +import os +import subprocess +import sys + + +def main(): + """Selector de modos de sincronización.""" + parser = argparse.ArgumentParser(description="Factuges Sync Dispatcher") + parser.add_argument( + "mode", + nargs="?", + choices=["factuges", "verifactu"], + help="Modo de sincronización", + ) + args = parser.parse_args() + + mode = args.mode or os.getenv("SYNC_MODE") + + if mode not in ("factuges", "verifactu"): + print("Error: debes indicar modo: 'factuges' o 'verifactu'") + sys.exit(1) + + if os.getenv("ENV") == "dev": + print("Running in development mode (no docker)") + + module = ( + "app.sync_factuges_main" + if mode == "factuges" + else "app.sync_verifactu_main" + ) + + # Ejecuta el módulo Python correspondiente + subprocess.run([sys.executable, "-m", module], check=True) + + +if __name__ == "__main__": + main() diff --git a/app/config/settings.py b/app/config/settings.py index 59a8179..122db16 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -1,48 +1,81 @@ +import logging import os from os.path import join, dirname +from typing import Any, Dict, Optional + from dotenv import load_dotenv -def load_config(): - dotenv_path = join(dirname(__file__), '../../.env') - load_dotenv(dotenv_path) +def _required(name: str) -> str: + """ + Devuelve el valor de la variable de entorno `name`. + Lanza RuntimeError si no existe o está vacía. + """ + value = os.getenv(name) + if value is None or value == "": + # Comentario: fail fast con mensaje claro + raise RuntimeError(f"Missing required environment variable: {name}") + return value - return { - 'ENVIRONMENT': os.getenv('ENVIRONMENT'), - 'LOCAL_TZ': os.getenv('LOCAL_TZ', 'Europe/Madrid'), - 'LAST_RUN_PATH': os.getenv('LAST_RUN_PATH'), - # 'LOG_PATH': os.getenv('LOG_PATH', 'app.log'), - 'FACTUGES_HOST': os.getenv('FACTUGES_HOST'), - 'FACTUGES_PORT': os.getenv('FACTUGES_PORT'), - 'FACTUGES_DATABASE': os.getenv('FACTUGES_DATABASE'), - 'FACTUGES_USER': os.getenv('FACTUGES_USER'), - 'FACTUGES_PASSWORD': os.getenv('FACTUGES_PASSWORD'), +def load_config() -> Dict[str, Any]: + """ + Carga la configuración desde variables de entorno. - 'UECKO_MYSQL_HOST': os.getenv('UECKO_MYSQL_HOST'), - 'UECKO_MYSQL_PORT': os.getenv('UECKO_MYSQL_PORT', 3306), - 'UECKO_MYSQL_DATABASE': os.getenv('UECKO_MYSQL_DATABASE'), - 'UECKO_MYSQL_USER': os.getenv('UECKO_MYSQL_USER'), - 'UECKO_MYSQL_PASSWORD': os.getenv('UECKO_MYSQL_PASSWORD'), + - En dev: carga dev.env y luego valida. + - En prod: NO carga ningún .env, solo usa entorno del sistema/contendor. + - Si falta alguna variable requerida -> RuntimeError. + """ - 'CTE_COMPANY_ID': os.getenv('CTE_COMPANY_ID'), - 'CTE_SERIE': os.getenv('CTE_SERIE'), - 'CTE_STATUS_INVOICE': os.getenv('CTE_STATUS_INVOICE'), - 'CTE_IS_PROFORMA': os.getenv('CTE_IS_PROFORMA'), - 'CTE_STATUS_VERIFACTU': os.getenv('CTE_STATUS_VERIFACTU'), - 'CTE_LANGUAGE_CODE': os.getenv('CTE_LANGUAGE_CODE'), - 'CTE_COUNTRY_CODE': os.getenv('CTE_COUNTRY_CODE'), - 'CTE_IS_COMPANY': os.getenv('CTE_IS_COMPANY'), - 'CTE_SYNC_RESULT_OK': os.getenv('CTE_SYNC_RESULT_OK'), - 'CTE_SYNC_RESULT_FAIL': os.getenv('CTE_SYNC_RESULT_FAIL'), + env = os.getenv("ENV", "dev") - 'VERIFACTU_BASE_URL': os.getenv('VERIFACTU_BASE_URL'), - 'VERIFACTU_API_KEY': os.getenv('VERIFACTU_API_KEY'), - 'VERIFACTU_NIFS_API_KEY': os.getenv('VERIFACTU_NIFS_API_KEY'), + if env == "dev": + dotenv_path = join(dirname(__file__), "../../enviroment/dev.env") + load_dotenv(dotenv_path) + elif env == "prod": + # En producción NO se carga archivo .env + # Las variables vienen del contenedor (docker run -e VAR=...) + pass + else: + raise RuntimeError(f"Unknown ENV: {env}") - # 'BREVO_API_KEY': os.getenv('BREVO_API_KEY'), - # 'BREVO_EMAIL_TEMPLATE': os.getenv("BREVO_EMAIL_TEMPLATE"), - # 'MAIL_FROM': os.getenv('MAIL_FROM'), - # 'MAIL_TO': os.getenv('MAIL_TO'), + config: Dict[str, Any] = { + # Opcionales (con valor por defecto) + "ENV": env, + "ENVIRONMENT": os.getenv("ENVIRONMENT", env), + "LOCAL_TZ": os.getenv("LOCAL_TZ", "Europe/Madrid"), + "LAST_RUN_PATH": _required("LAST_RUN_PATH"), + # FACTUGES (requeridas) + "FACTUGES_HOST": _required("FACTUGES_HOST"), + "FACTUGES_PORT": _required("FACTUGES_PORT"), + "FACTUGES_DATABASE": _required("FACTUGES_DATABASE"), + "FACTUGES_USER": _required("FACTUGES_USER"), + "FACTUGES_PASSWORD": _required("FACTUGES_PASSWORD"), + + # UECKO MySQL (requeridas salvo puerto) + "UECKO_MYSQL_HOST": _required("UECKO_MYSQL_HOST"), + "UECKO_MYSQL_PORT": os.getenv("UECKO_MYSQL_PORT", "3306"), + "UECKO_MYSQL_DATABASE": _required("UECKO_MYSQL_DATABASE"), + "UECKO_MYSQL_USER": _required("UECKO_MYSQL_USER"), + "UECKO_MYSQL_PASSWORD": _required("UECKO_MYSQL_PASSWORD"), + + # Constantes/CTE (requeridas) + "CTE_COMPANY_ID": _required("CTE_COMPANY_ID"), + "CTE_SERIE": _required("CTE_SERIE"), + "CTE_STATUS_INVOICE": _required("CTE_STATUS_INVOICE"), + "CTE_IS_PROFORMA": _required("CTE_IS_PROFORMA"), + "CTE_STATUS_VERIFACTU": _required("CTE_STATUS_VERIFACTU"), + "CTE_LANGUAGE_CODE": _required("CTE_LANGUAGE_CODE"), + "CTE_COUNTRY_CODE": _required("CTE_COUNTRY_CODE"), + "CTE_IS_COMPANY": _required("CTE_IS_COMPANY"), + "CTE_SYNC_RESULT_OK": _required("CTE_SYNC_RESULT_OK"), + "CTE_SYNC_RESULT_FAIL": _required("CTE_SYNC_RESULT_FAIL"), + + # Verifactu (requeridas) + "VERIFACTU_BASE_URL": _required("VERIFACTU_BASE_URL"), + "VERIFACTU_API_KEY": _required("VERIFACTU_API_KEY"), + "VERIFACTU_NIFS_API_KEY": _required("VERIFACTU_NIFS_API_KEY"), } + + return config diff --git a/app/db/normalizations.py b/app/db/normalizations.py index 4b99041..01c51ac 100644 --- a/app/db/normalizations.py +++ b/app/db/normalizations.py @@ -1,10 +1,10 @@ import logging from datetime import date -from config import load_config +from app.config import load_config import textwrap from typing import Dict, Any, Optional from decimal import Decimal, ROUND_HALF_UP -from utils import limpiar_cadena, normalizar_telefono_con_plus, corregir_y_validar_email, normalizar_url_para_insert, map_tax_code, cents, cents4, money_round, tax_fraction_from_code +from app.utils import limpiar_cadena, normalizar_telefono_con_plus, corregir_y_validar_email, normalizar_url_para_insert, map_tax_code, cents, cents4, money_round, tax_fraction_from_code from striprtf.striprtf import rtf_to_text diff --git a/app/db/sync_invoices_factuges.py b/app/db/sync_invoices_factuges.py index 4b6865b..e815b59 100644 --- a/app/db/sync_invoices_factuges.py +++ b/app/db/sync_invoices_factuges.py @@ -1,10 +1,10 @@ import logging from typing import Dict, Any from uuid6 import uuid7 -from config import load_config +from app.config import load_config from . import sql_sentences as SQL from . import normalizations as NORMALIZA -from utils import validar_nif +from app.utils import validar_nif def sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date): diff --git a/app/db/sync_invoices_verifactu.py b/app/db/sync_invoices_verifactu.py index 5d69be3..fc05612 100644 --- a/app/db/sync_invoices_verifactu.py +++ b/app/db/sync_invoices_verifactu.py @@ -1,9 +1,8 @@ import logging -from uuid import uuid4 from typing import Dict, Any, Tuple, Optional, List, Iterable -from config import load_config +from app.config import load_config from decimal import Decimal -from utils import validar_nif, estado_factura, crear_factura, TaxCatalog, unscale_to_str +from app.utils import validar_nif, estado_factura, crear_factura, TaxCatalog, unscale_to_str from . import sql_sentences as SQL diff --git a/app/sync_factuges_main.py b/app/sync_factuges_main.py index e583c9a..c8fb486 100644 --- a/app/sync_factuges_main.py +++ b/app/sync_factuges_main.py @@ -1,12 +1,12 @@ import sys import logging -from __version_sync_factuges__ import __version__ +from app.__version_sync_factuges__ import __version__ from datetime import datetime from dateutil import tz -from config import setup_logging, load_config -from db import get_mysql_connection, get_factuges_connection, sync_invoices_factuges -from utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail +from app.config import setup_logging, load_config +from app.db import get_mysql_connection, get_factuges_connection, sync_invoices_factuges +from app.utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion, log_system_metrics, send_orders_mail def main(): diff --git a/app/sync_verifactu_main.py b/app/sync_verifactu_main.py index 51310ac..7a2d257 100644 --- a/app/sync_verifactu_main.py +++ b/app/sync_verifactu_main.py @@ -1,7 +1,7 @@ import sys import logging -from __version_sync_factuges__ import __version__ +from app.__version_sync_factuges__ import __version__ from datetime import datetime from dateutil import tz from config import setup_logging, load_config diff --git a/app/utils/last_execution_helper.py b/app/utils/last_execution_helper.py index ecafb14..215eab1 100644 --- a/app/utils/last_execution_helper.py +++ b/app/utils/last_execution_helper.py @@ -1,9 +1,13 @@ from __future__ import annotations -from datetime import datetime, timezone -from typing import Optional -import os -DEFAULT_PATH = "./last_execution.txt" +from datetime import datetime, timezone +from pathlib import Path +import os +from typing import Optional + + +DEFAULT_PATH = Path("last_execution.txt") +DEFAULT_FALLBACK = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc) FMT = "%Y-%m-%d %H:%M:%S" @@ -14,22 +18,33 @@ def obtener_fecha_ultima_ejecucion( ) -> datetime: """ Lee la última fecha de ejecución desde `path` y la devuelve como aware (UTC). - Si no existe o hay error de parseo, devuelve `fallback` (por defecto 2024-01-01 UTC). + + - Si el fichero no existe o el contenido es inválido, devuelve `fallback`. + - Si `fallback` es None, usa DEFAULT_FALLBACK (2024-01-01T00:00:00Z). """ - if fallback is None: - fallback = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + + # Comentario: fallback explícito para evitar lógica duplicada en llamadas + effective_fallback = fallback or DEFAULT_FALLBACK + + if path is None: + return effective_fallback + + # 2. Convertimos str -> Path + path = Path(path) try: - with open(path, "r", encoding="utf8") as f: - fecha_str = f.read().strip() - # Se guarda como texto sin tz; interpretamos como UTC - dt_naive = datetime.strptime(fecha_str, FMT) + text = path.read_text(encoding="utf-8").strip() + if not text: + # Comentario: fichero vacío -> usamos fallback + return effective_fallback + + dt_naive = datetime.strptime(text, FMT) return dt_naive.replace(tzinfo=timezone.utc) except FileNotFoundError: - return fallback + return effective_fallback except ValueError: - # Formato inválido en el archivo -> usar fallback - return fallback + # Comentario: formato inválido en el archivo -> fallback + return effective_fallback def actualizar_fecha_ultima_ejecucion( diff --git a/app/utils/send_orders_mail.py b/app/utils/send_orders_mail.py index 323bb6b..2955e42 100644 --- a/app/utils/send_orders_mail.py +++ b/app/utils/send_orders_mail.py @@ -2,28 +2,29 @@ import logging import brevo_python from brevo_python.rest import ApiException -from config import setup_brevo +from app.config import setup_brevo from brevo_python.rest import ApiException -from config import load_config +from app.config import load_config + def send_orders_mail(inserted_orders): config = load_config() - + try: configuration = setup_brevo(config) - api_instance = brevo_python.TransactionalEmailsApi(brevo_python.ApiClient(configuration)) + api_instance = brevo_python.TransactionalEmailsApi(brevo_python.ApiClient(configuration)) - for order in inserted_orders: + for order in inserted_orders: send_smtp_email = brevo_python.SendSmtpEmail( - to=[{'email':config['MAIL_TO']}], + to=[{'email': config['MAIL_TO']}], subject=f"Nuevo pedido del distribuidor {order["dealer_name"]}", template_id=int(config["BREVO_EMAIL_TEMPLATE"]), params={ - "customer_reference": order["customer_reference"], + "customer_reference": order["customer_reference"], "dealer_name": order["dealer_name"] }, - ) - api_response = api_instance.send_transac_email(send_smtp_email) + ) + api_response = api_instance.send_transac_email(send_smtp_email) logging.info(msg=api_response) except ApiException as e: logging.error(msg=e) diff --git a/cronjob b/cronjob index 9272ab1..ad18897 100644 --- a/cronjob +++ b/cronjob @@ -1 +1,6 @@ */5 * * * * /usr/local/bin/python /opt/uecko_sync_app/app/main.py >> /var/log/cron.log 2>&1 + +*/5 * * * * docker run --rm \ + --env-file /opt/my_project/config/prod.env \ + -v /opt/my_project/logs:/app/logs \ + my_project:latest diff --git a/.env b/enviroment/.env similarity index 100% rename from .env rename to enviroment/.env diff --git a/.env.development b/enviroment/.env.development similarity index 100% rename from .env.development rename to enviroment/.env.development diff --git a/.env.production.sync.factuges b/enviroment/.env.production.sync.factuges similarity index 100% rename from .env.production.sync.factuges rename to enviroment/.env.production.sync.factuges diff --git a/.env_V b/enviroment/.env_V similarity index 100% rename from .env_V rename to enviroment/.env_V diff --git a/enviroment/dev.env b/enviroment/dev.env new file mode 100644 index 0000000..dabd2d5 --- /dev/null +++ b/enviroment/dev.env @@ -0,0 +1,50 @@ +ENVIRONMENT = development +LOCAL_TZ = Europe/Madrid +LAST_RUN_PATH = ./app.last_run.txt +#LOG_PATH = ./app.log + +#DESARROLLO ACANA +#FACTUGES_HOST = 192.168.0.105 +#FACTUGES_PORT = 3050 +#FACTUGES_DATABASE = C:\Codigo Acana\Output\Debug\Database\FACTUGES.FDB +#FACTUGES_USER = sysdba +#FACTUGES_PASSWORD = masterkey +#CONFIGURACION ACANA +#CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a' +#VERIFACTU_API_KEY = vf_test_ei8WYAvEq5dhSdEyQVjgCS8NZaNpEK2BljSHSUXf+Y0= + +#DESARROLLO ALONSO Y SAL +FACTUGES_HOST = 192.168.0.146 +FACTUGES_PORT = 3050 +FACTUGES_DATABASE = C:\Codigo Arribas2\Output\Debug\Database\FACTUGES.FDB +FACTUGES_USER = sysdba +FACTUGES_PASSWORD = masterkey +#CONFIGURACION ACANA +CTE_COMPANY_ID = '019a9667-6a65-767a-a737-48234ee50a3a' +#CTE_COMPANY_ID_PRODUCCION = '019ac4f2-9502-731a-a6db-525475e85bc7' +VERIFACTU_API_KEY = vf_test_C03HL2F0X5OXSDRunjNFoMxD4IrRfK3kCC8PfcvCENI= + +#DESARROLLO +UECKO_MYSQL_HOST = localhost +UECKO_MYSQL_PORT = 3306 +UECKO_MYSQL_DATABASE = uecko_erp_sync +UECKO_MYSQL_USER = rodax +UECKO_MYSQL_PASSWORD = rodax + +CTE_SERIE = 'F25/' +CTE_STATUS_INVOICE = 'issued' +CTE_IS_PROFORMA = 0 +CTE_STATUS_VERIFACTU = 'Pendiente' +CTE_LANGUAGE_CODE = 'es' #En uecko vendrá de su ficha +CTE_COUNTRY_CODE = 'es' #En uecko vendrá de su ficha +CTE_IS_COMPANY = 1 +CTE_SYNC_RESULT_OK = 1 +CTE_SYNC_RESULT_FAIL = 2 + +VERIFACTU_BASE_URL = https://api.verifacti.com/ +VERIFACTU_NIFS_API_KEY = vfn_osYpNdqSzAdTAHpazXG2anz4F3o0gfbSb5FFrCBZcno= + +#BREVO_API_KEY = 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 +#BREVO_EMAIL_TEMPLATE = 1 +#MAIL_FROM = 'no-reply@presupuestos.uecko.com' +#MAIL_TO = 'soporte@rodax-software.com' \ No newline at end of file diff --git a/enviroment/prod.env b/enviroment/prod.env new file mode 100644 index 0000000..6c21ba2 --- /dev/null +++ b/enviroment/prod.env @@ -0,0 +1,4 @@ +DB_HOST=prod-db +DB_USER=prod_user +DB_PASS=supersecret +RUN_INTERVAL_MINUTES=5 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4a85092 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/readme.md b/readme.md deleted file mode 100644 index 6983546..0000000 --- a/readme.md +++ /dev/null @@ -1,83 +0,0 @@ -Instalar Python en Ubuntu (alias de Python3) --------------------------------------------- -sudo apt-get install python-is-python3 -sudo apt install python3.12-venv - -Instalar el cliente de FirebirdSQL 2.0/2.1 en Ubuntu ----------------------------------------------------- -- Opción 1. Revisar si el cliente Firebird 2.1 (libfbclient2) está en repositorios. Si aparece, se puede instalar directamente: -sudo apt-get update -apt-cache search firebird | grep client -sudo apt-get install libfbclient2 - -- Opción 2. No aparece en el repositorio de Ubuntu. Instalación manual -> investigar - - -Crear el entorno por primera vez: ---------------------------------- -python -m venv venv -source venv/bin/activate <-- en linux -.\venv\Scripts\activat <-- en Windows - -Meter librerias requeridas al entorno creado --------------------------------------------- -pip3 install -r requirements.txt - - -Lanzar el entorno para hacer pruebas del script: ------------------------------------------------ -Linux: - source venv/bin/activate - python app/main.py - -Windows: - .\venv\Scripts\activat - python app\main.py - - - ----- - -git clone ssh://git@wopr.rodax-software.com:30001/uecko/presupuestador-web---scripts-sync.git uecko-sync-scripts -cd uecko-sync-scripts/ -cp .env-sample .env -pip install -r requirements.txt -python3 -m venv env -sudo apt install python3.11-venv -python3 -m venv env -source venv/bin/activate -pip3 install -r requirements.txt -python3 factuges_catalog_to_json_file.py - -> Reconstruir imagen docker -docker compose up --build -d - - -> Instalar Firebird 2.1 -1. Descargar paquete: https://master.dl.sourceforge.net/project/firebird/firebird-linux-amd64/2.1.7-Release/FirebirdSS-2.1.7.18553-0.amd64.tar.gz?viasf=1 -2. Descomprimir: tar -xvf -3. Lanzar instalación: sudo ./install.sh -4. Si da error, da igual. El caso es que en /opt/firebird estén los ficheros y en las librerías. -5. Crear enlaces simbólicos: - Busque la librería libfbclient.so.2.m.n (m.n es el nro. menor de versión más el nro. de actualización) en /opt/firebird/lib del equipo donde está instalado el servidor Firebird. Cópiela a /usr/lib en el cliente. - - Cree enlaces simbólicos usando los siguientes comandos: - - ln -s /usr/lib/libfbclient.so.2.m.n /usr/lib/libfbclient.so.2 - - ln -s /usr/lib/libfbclient.so.2 /usr/lib/libfbclient.so - - reemplazando 2.m.n con su número de versión, por ejemplo 2.1.7 - - Si Ud. está ejecutando aplicaciones que esperan que las librerías antiguas estén presentes, cree también los siguientes enlaces simbólicos: - - ln -s /usr/lib/libfbclient.so /usr/lib/libgds.so.0 - - ln -s /usr/lib/libfbclient.so /usr/lib/libgds.so - - Copie el archivo firebird.msg a /opt/firebird - - En el perfil por defecto del sistema, o usando setenv() desde una consola, cree la variable de entorno FIREBIRD y apúntela al directorio /opt/firebird, para permitir a las rutinas de la API localizar los mensajes. - Para ello, editar con sudo nano /etc/profile y añadir FIREBIRD=/opt/firebird - -6. sudo apt-get install libncurses5 diff --git a/requeriments.txt b/requeriments.txt deleted file mode 100644 index 4916e1f..0000000 --- a/requeriments.txt +++ /dev/null @@ -1,27 +0,0 @@ -about-time==4.2.1 -alive-progress==3.1.5 -bcrypt==4.1.3 -black==24.8.0 -brevo-python==1.1.2 -certifi==2024.8.30 -cffi==1.16.0 -click==8.1.7 -colorama==0.4.6 -cryptography==42.0.8 -fdb==2.0.2 -future==0.18.3 -grapheme==0.6.0 -mypy-extensions==1.0.0 -mysql-connector-python==8.4.0 -packaging==24.1 -paramiko==3.4.0 -pathspec==0.12.1 -platformdirs==4.3.6 -psutil==6.0.0 -pycparser==2.22 -PyNaCl==1.5.0 -python-dateutil==2.9.0.post0 -python-dotenv==1.0.0 -six==1.16.0 -sshtunnel==0.4.0 -urllib3==2.2.3 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..80bceb2 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_VERSION="1.0.0" + +# ================================================ +# FACTUGES SYNC - Docker Build Script +# ----------------------------------------------- +# Uso: +# ./build.sh [--load] +# ================================================ + +# ---------- 1. Validación de parámetros ---------- +if [[ $# -eq 0 || "$1" == --* ]]; then + echo "❌ ERROR: Falta el parámetro " + echo "Uso: ./build.sh [--load]" + exit 1 +fi + +COMPANY="$1" +LOAD=false + +if [[ "${2:-}" == "--load" ]]; then + LOAD=true +fi + +# ---------- 2. Directorios ---------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(realpath "${SCRIPT_DIR}/..")" +OUT_DIR="${PROJECT_DIR}/out/${COMPANY}" + +mkdir -p "$OUT_DIR" +cd "$PROJECT_DIR" + +# ---------- 3. Versión del proyecto ---------- +IMAGE_NAME="factuges-sync" + +IMAGE_VERSION=$(sed -n 's/^version[[:space:]]*=[[:space:]]*\(.*\)$/\1/p' setup.cfg | head -n1) +IMAGE_VERSION="${IMAGE_VERSION:-0.0.0}" + +IMAGE_TAG_VERSION="${IMAGE_NAME}:${COMPANY}-${IMAGE_VERSION}" +IMAGE_TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest" + +# ---------- 4. Info del sistema ---------- +DATE=$(date +'%Y%m%d-%H%M%S') +ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +USER_NAME=$(whoami) + +echo "" +echo "-------------------------------------------------------" +echo " FACTUGES SYNC Build Script v${SCRIPT_VERSION}" +echo " Compañía: ${COMPANY}" +echo " Version: ${IMAGE_VERSION}" +echo " Imagen: ${IMAGE_TAG_VERSION}" +echo " Latest tag: ${IMAGE_TAG_LATEST}" +echo " Load: ${LOAD}" +echo "-------------------------------------------------------" +echo "" + +# ---------- 5. Build de la imagen Docker ---------- +echo "📦 Construyendo imagen Docker..." +docker build \ + -t "${IMAGE_TAG_VERSION}" \ + -t "${IMAGE_TAG_LATEST}" \ + . + +echo "✔ Build OK: ${IMAGE_TAG_VERSION}" +echo "" + +# ---------- 6. Generar manifest JSON ---------- +MANIFEST_FILE="${OUT_DIR}/manifest-${IMAGE_VERSION}-${DATE}.json" + +cat > "$MANIFEST_FILE" </dev/null || echo "0.0.0") + +IFS='.' read -r MAJ MIN PAT <<< "$CURRENT" + +# Incremento patch por defecto +PAT=$((PAT + 1)) + +NEXT="${MAJ}.${MIN}.${PAT}" + +echo "Creating release tag: ${NEXT}" + +git tag "${NEXT}" +git push origin "${NEXT}" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..489e165 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,70 @@ +[metadata] +name = factuges-sync +version = 0.1.0 +description = ETL job to sync data from legacy DB to MariaDB +author = Rodax Software +author_email = info@rodax-software.com +long_description = file: README.md +long_description_content_type = text/markdown +url = https://factuges.app +license = "Propietaria" +classifiers = + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.11 + License :: OSI Approved :: MIT License + Operating System :: OS Independent + +[options] +packages = find: +package_dir = + = . +python_requires = >=3.11 +install_requires = + about-time==4.2.1 + alive-progress==3.1.5 + bcrypt==4.1.3 + brevo-python==1.1.2 + certifi==2024.8.30 + cffi==1.16.0 + click==8.1.7 + colorama==0.4.6 + cryptography==42.0.8 + fdb==2.0.2 + future==0.18.3 + grapheme==0.6.0 + mysql-connector-python==8.4.0 + paramiko==3.4.0 + psutil==6.0.0 + pycparser==2.22 + PyNaCl==1.5.0 + python-dateutil==2.9.0.post0 + python-dotenv==1.0.0 + six==1.16.0 + sshtunnel==0.4.0 + urllib3==2.2.3 + uuid6 + +[options.extras_require] +dev = + black==24.8.0 + mypy-extensions==1.0.0 + packaging==24.1 + pathspec==0.12.1 + platformdirs==4.3.6 + +[options.entry_points] +console_scripts = + factuges-sync = app.cli:main + +[options.packages.find] +where = . + +[tool:pytest] +testpaths = tests + +[flake8] +max-line-length = 88 +exclude = .git,__pycache__,build,dist + +[isort] +profile = black