""" Anexo76 - Aplicación SaaS para gestión de comercio exterior Backend API con FastAPI + Keycloak + SQLAlchemy """ import logging import subprocess # Importar modelos para registrar con SQLAlchemy # Reference Data (Dependencies) from api.v1.modules.public.reference_data.countries.models import Country from api.v1.modules.public.reference_data.currency_types.models import CurrencyType from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection from api.v1.modules.public.reference_data.customs_warehouses.models import CustomsWarehouse from api.v1.modules.public.reference_data.incoterms.models import Incoterm from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType from api.v1.modules.public.reference_data.material_types.models import MaterialType from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod # Orden: PedimentoCode y RegimenPedimento antes de CodePedimentoRegimen para que # SQLAlchemy resuelva los nombres en relationship() al configurar el mapper from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento from api.v1.modules.public.reference_data.code_pedimento_regimens.models import ( CodePedimentoRegimen, ) from api.v1.modules.public.reference_data.sectors.models import Sector from api.v1.modules.public.reference_data.states.models import State from api.v1.modules.public.reference_data.transport_modes.models import TransportMode from api.v1.modules.public.reference_data.transport_types.models import TransportType from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate from api.v1.modules.a76.general_catalogs.identifiers.models import Identifier from api.v1.modules.a76.classes.models import Class from api.v1.modules.a76.general_catalogs.classification_concepts.models import ClassificationConcept from api.v1.modules.a76.general_catalogs.concepts.models import Concept from api.v1.modules.a76.general_catalogs.customs_broker_concepts.models import CustomsBrokerConcept from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import DepreciationCatalog from api.v1.modules.a76.general_catalogs.doda.models import Doda from api.v1.modules.a76.general_catalogs.electronic_notices.models import ElectronicNotice from api.v1.modules.a76.general_catalogs.equivalencies.models import Equivalency from api.v1.modules.a76.general_catalogs.error_catalogs.models import ErrorCatalog from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog from api.v1.modules.a76.general_catalogs.inpc.models import INPC from api.v1.modules.a76.general_catalogs.legends.models import Legend from api.v1.modules.a76.general_catalogs.multi_currency_types.models import MultiCurrencyType from api.v1.modules.a76.general_catalogs.packages.models import Package from api.v1.modules.a76.general_catalogs.ports.models import Port from api.v1.modules.a76.general_catalogs.prevalidators.models import Prevalidator from api.v1.modules.a76.general_catalogs.seal.models import Seal from api.v1.modules.a76.general_catalogs.signatures.models import Signature from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import ( TariffFraction, ) from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import ( USTariffFraction, ) # Core Modules & Reference Data (Dependencies) from api.v1.modules.a76.clients_and_providers.models import ClientProvider from api.v1.modules.a76.customs_brokers.models import CustomsBroker from api.v1.modules.a76.general_catalogs.company.models import Company # Transportation Modules from api.v1.modules.a76.transportation.trailers.models import Trailer from api.v1.modules.a76.transportation.transporters.models import Transporter from api.v1.modules.a76.transportation.vehicles.models import Vehicle # Core Modules & Transactional Models from api.v1.modules.a76.items.models import LineItem from api.v1.modules.a76.items.series.models import Serie from api.v1.modules.a76.parts.models import Part from api.v1.modules.a24.fa.fa_parts.models import FaPart from api.v1.modules.a24.inv.inv_parts.models import InvPart from api.v1.modules.a76.manifests.manifest.models import Manifest from api.v1.modules.a76.manifests.concept_manifestation.models import ConceptManifestation from api.v1.modules.a76.manifests.value_manifestation.models import ValueManifestation # Transactional Primary Models from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceSalesDetails from api.v1.modules.a76.audit_log.events import register_audit_listeners # Core Modules (Secondary) import core.celery_app # Initialize Celery App from api.v1.router import router as api_v1_router from core.config import settings from core.database import init_db from core.error_handlers import register_exception_handlers from core.middleware import ( LicenseValidationMiddleware, RequestLoggingMiddleware, TenantMiddleware, ) from fastapi import FastAPI, Request, status, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from pathlib import Path # Importar modelos para registrar con SQLAlchemy # IMPORTANT: Import FaLineItem BEFORE LineItem for relationship resolution from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem from api.v1.modules.a76.items.models import LineItem from api.v1.modules.a76.items.series.models import Serie from api.v1.modules.a76.parts.models import Part from api.v1.modules.a24.fa.fa_parts.models import FaPart from api.v1.modules.a24.inv.inv_parts.models import InvPart from api.v1.modules.a76.manifests.manifest.models import Manifest from api.v1.modules.a76.manifests.concept_manifestation.models import ( ConceptManifestation, ) from api.v1.modules.a76.manifests.value_manifestation.models import ValueManifestation # Configurar logging logging.basicConfig( level=logging.INFO if not settings.DEBUG else logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) # Crear aplicación FastAPI app = FastAPI( title="Anexo76 API", version=settings.APP_VERSION, description="Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT", docs_url="/api/docs" if settings.DEBUG else None, redoc_url="/api/redoc" if settings.DEBUG else None, openapi_url="/api/openapi.json" if settings.DEBUG else None, ) logger = logging.getLogger(__name__) # Registrar manejadores de excepciones register_exception_handlers(app) def _cors_headers_for_request(request: Request): """Return CORS headers if request Origin is allowed (so error responses don't get blocked by browser).""" origin = request.headers.get("origin") if not origin: return {} allowed = settings.cors_origins_list if origin in allowed: return { "Access-Control-Allow-Origin": origin, "Access-Control-Allow-Credentials": "true", } return {} for k, v in _cors_headers_for_request(request).items(): response.headers[k] = v return response def run_migrations(): subprocess.run(["alembic", "upgrade", "head"], check=True) # Inicializar la base de datos @app.on_event("startup") async def on_startup(): """Evento de inicio de la aplicación""" logger.info("Iniciando la aplicación Anexo76...") init_db() run_migrations() logger.info("Base de datos inicializada correctamente.") # Configurar CORS app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Agregar middlewares personalizados if settings.DEBUG: app.add_middleware(RequestLoggingMiddleware) app.add_middleware(LicenseValidationMiddleware) app.add_middleware(TenantMiddleware) # Middleware de Contexto de Usuario (Audit Log) from api.v1.modules.a76.audit_log.middleware import UserContextMiddleware app.add_middleware(UserContextMiddleware) # Importar modelos para Audit Log from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceSalesDetails from api.v1.modules.a76.audit_log.events import register_audit_listeners # Core Modules from api.v1.modules.a76.clients_and_providers.models import ClientProvider from api.v1.modules.a76.customs_brokers.models import CustomsBroker from api.v1.modules.a76.parts.models import Part from api.v1.modules.a76.items.models import LineItem from api.v1.modules.a76.general_catalogs.company.models import Company # Reference Data from api.v1.modules.public.reference_data.countries.models import Country from api.v1.modules.public.reference_data.currency_types.models import CurrencyType from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection from api.v1.modules.public.reference_data.customs_warehouses.models import ( CustomsWarehouse, ) from api.v1.modules.public.reference_data.incoterms.models import Incoterm from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType from api.v1.modules.public.reference_data.material_types.models import MaterialType from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode from api.v1.modules.public.reference_data.pedimento_regimens.models import ( RegimenPedimento, ) from api.v1.modules.public.reference_data.sectors.models import Sector from api.v1.modules.public.reference_data.states.models import State from api.v1.modules.public.reference_data.transport_modes.models import TransportMode from api.v1.modules.public.reference_data.transport_types.models import TransportType from api.v1.modules.public.reference_data.valuation_methods.models import ( ValuationMethod, ) from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate from api.v1.modules.a76.general_catalogs.identifiers.models import Identifier from api.v1.modules.a76.classes.models import Class from api.v1.modules.a76.general_catalogs.classification_concepts.models import ( ClassificationConcept, ) from api.v1.modules.a76.general_catalogs.concepts.models import Concept from api.v1.modules.a76.general_catalogs.customs_broker_concepts.models import ( CustomsBrokerConcept, ) from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import ( DepreciationCatalog, ) from api.v1.modules.a76.general_catalogs.doda.models import Doda from api.v1.modules.a76.general_catalogs.electronic_notices.models import ( ElectronicNotice, ) from api.v1.modules.a76.general_catalogs.equivalencies.models import Equivalency from api.v1.modules.a76.general_catalogs.error_catalogs.models import ErrorCatalog from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog from api.v1.modules.a76.general_catalogs.inpc.models import INPC from api.v1.modules.a76.general_catalogs.legends.models import Legend from api.v1.modules.a76.general_catalogs.multi_currency_types.models import ( MultiCurrencyType, ) from api.v1.modules.a76.general_catalogs.packages.models import Package from api.v1.modules.a76.general_catalogs.ports.models import Port from api.v1.modules.a76.general_catalogs.prevalidators.models import Prevalidator from api.v1.modules.a76.general_catalogs.seal.models import Seal from api.v1.modules.a76.general_catalogs.signatures.models import Signature from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import ( TariffFraction, ) from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import ( USTariffFraction, ) # Registrar Listeners de Auditoría @app.on_event("startup") def register_audit(): register_audit_listeners( [ # Core Transactions Pedimentos, InvoiceHeader, InvoiceSalesDetails, LineItem, # Sidebar Core Modules ClientProvider, CustomsBroker, Part, Company, # Transportation Modules Trailer, Transporter, Vehicle, # Reference Data Country, CurrencyType, CustomsSection, CustomsWarehouse, Incoterm, InvoiceType, MaterialType, PaymentMethod, PedimentoCode, RegimenPedimento, Sector, State, TransportMode, TransportType, ValuationMethod, UnitOfMeasure, ExchangeRate, Identifier, Class, ClassificationConcept, Concept, CustomsBrokerConcept, DepreciationCatalog, Doda, ElectronicNotice, Equivalency, ErrorCatalog, FDACatalog, INPC, Legend, MultiCurrencyType, Package, Port, Prevalidator, Seal, Signature, TariffFraction, UnitConversion, USTariffFraction, ] ) # Crear directorio de uploads si no existe y montar archivos estáticos uploads_dir = Path("uploads").resolve() uploads_dir.mkdir(parents=True, exist_ok=True) app.mount("/api/uploads", StaticFiles(directory=str(uploads_dir)), name="uploads") # Registrar routers app.include_router(api_v1_router, prefix="/api/v1") @app.get("/api/") async def root(): """Root endpoint""" return { "name": "Anexo76 API", "version": settings.APP_VERSION, "status": "running", "docs": "/api/docs" if settings.DEBUG else "disabled in production", } @app.get("/api/health") async def health_check(): """Health check endpoint""" return {"status": "healthy", "environment": settings.ENVIRONMENT} @app.get("/api/version") async def get_version(): """ Endpoint de versión de la aplicación Retorna la versión de la aplicación que fue incrustada en la imagen Docker durante el proceso de CI/CD. La versión se genera automáticamente según la rama: - development: YY.MM.1. - main: YY.MM.0. Returns: dict: Información de versión y entorno """ return { "service": settings.APP_NAME, "version": settings.APP_VERSION, "environment": settings.ENVIRONMENT, "debug": settings.DEBUG, }