""" 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 # 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 ]) # 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}