""" Anexo76 - Aplicación SaaS para gestión de comercio exterior Backend API con FastAPI + Keycloak + SQLAlchemy """ import logging import subprocess 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 from api.v1.modules.a76.items.models import Item 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 # Configurar logging logging.basicConfig( level=logging.INFO if not settings.DEBUG else logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) # 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, ) # Registrar manejadores de excepciones register_exception_handlers(app) # Add validation error handler @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): logger.error( f"Validation error for {request.method} {request.url.path}: {exc.errors()}" ) logger.error(f"Request body: {await request.body()}") return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, content={"detail": exc.errors(), "body": exc.body}, ) # Add HTTP exception handler @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): logger.error( f"HTTP {exc.status_code} for {request.method} {request.url.path}: {exc.detail}" ) return JSONResponse( status_code=exc.status_code, content={"detail": exc.detail}, ) 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 Item 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.tariff_fractions.models import TariffFraction from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion from api.v1.modules.a76.general_catalogs.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, Item, # Sidebar Core Modules ClientProvider, CustomsBroker, Part, Company, # 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}