[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-/blog/tutorial/apis-rest-python-fastapi":3,"prev-/blog/tutorial/apis-rest-python-fastapi":5845,"next-/blog/tutorial/apis-rest-python-fastapi":5848,"related-/blog/tutorial/apis-rest-python-fastapi":5849},{"id":4,"title":5,"author":6,"authorUrl":7,"body":8,"category":5813,"cta":5814,"date":5817,"dateModified":5817,"description":5818,"draft":5819,"extension":5820,"faq":5821,"featured":5819,"image":5834,"imageAlt":5835,"meta":5836,"navigation":169,"path":5837,"readingTime":401,"seo":5838,"stem":5839,"tags":5840,"__hash__":5844},"blog/blog/tutorial/apis-rest-python-fastapi.md","Crear APIs REST con Python y FastAPI para integraciones empresariales","Syswork México","/nosotros",{"type":9,"value":10,"toc":5798},"minimark",[11,38,51,56,64,102,106,114,117,211,214,233,237,418,458,461,465,762,766,778,1242,1796,1804,1808,2267,2271,4238,4240,4244,5012,5016,5082,5089,5094,5306,5308,5312,5431,5438,5716,5720,5723,5785,5794],[12,13,14,15,20,21,25,26,30,31,37],"p",{},"La mayoría de los problemas de integración en empresas no se resuelven con un conector pre-construido. Tu ",[16,17,19],"a",{"href":18},"/soluciones/erp","ERP"," tiene una estructura de datos específica, tu ",[16,22,24],{"href":23},"/soluciones/e-commerce","e-commerce"," maneja su propio formato de pedidos y tu ",[16,27,29],{"href":28},"/soluciones/crm","CRM"," almacena clientes de forma diferente. Lo que necesitas es una API que traduzca entre ellos — y ",[16,32,36],{"href":33,"rel":34},"https://fastapi.tiangolo.com/",[35],"nofollow","FastAPI"," es la herramienta perfecta para construirla.",[12,39,40,41,45,46,50],{},"En esta guía vas a crear una API REST profesional con ",[16,42,44],{"href":43},"/tecnologias/python","Python"," y FastAPI que incluye validación de datos, autenticación JWT, conexión a ",[16,47,49],{"href":48},"/tecnologias/postgresql","PostgreSQL",", manejo de errores y documentación automática — lista para producción.",[52,53,55],"h2",{"id":54},"requisitos-previos","Requisitos previos",[12,57,58,59,63],{},"Python 3.11+ y PostgreSQL instalados. Si necesitas instalar PostgreSQL, sigue nuestra ",[16,60,62],{"href":61},"/blog/instalar-postgresql-en-ubuntu-24","guía de instalación",".",[65,66,71],"pre",{"className":67,"code":68,"language":69,"meta":70,"style":70},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","python3 --version   # 3.11+\npsql --version      # 16+\n","bash","",[72,73,74,91],"code",{"__ignoreMap":70},[75,76,79,83,87],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"sbgvK","python3",[75,84,86],{"class":85},"stzsN"," --version",[75,88,90],{"class":89},"sutJx","   # 3.11+\n",[75,92,94,97,99],{"class":77,"line":93},2,[75,95,96],{"class":81},"psql",[75,98,86],{"class":85},[75,100,101],{"class":89},"      # 16+\n",[52,103,105],{"id":104},"paso-1-estructura-del-proyecto","Paso 1: Estructura del proyecto",[65,107,112],{"className":108,"code":110,"language":111},[109],"language-text","mi-api/\n├── app/\n│   ├── __init__.py\n│   ├── main.py           # Punto de entrada de la API\n│   ├── config.py          # Configuración y variables de entorno\n│   ├── database.py        # Conexión a PostgreSQL\n│   ├── auth.py            # Autenticación JWT\n│   ├── models/\n│   │   ├── __init__.py\n│   │   └── producto.py    # Modelo SQLAlchemy\n│   ├── schemas/\n│   │   ├── __init__.py\n│   │   └── producto.py    # Schemas Pydantic (validación)\n│   └── routers/\n│       ├── __init__.py\n│       └── productos.py   # Endpoints de productos\n├── .env\n├── requirements.txt\n└── Dockerfile\n","text",[72,113,110],{"__ignoreMap":70},[12,115,116],{},"Crea el entorno virtual e instala dependencias:",[65,118,120],{"className":67,"code":119,"language":69,"meta":70,"style":70},"mkdir mi-api && cd mi-api\npython3 -m venv venv\nsource venv/bin/activate\n\npip install fastapi uvicorn[standard] sqlalchemy[asyncio] asyncpg \\\n  pydantic-settings python-jose[cryptography] passlib[bcrypt] python-multipart\n",[72,121,122,142,155,164,171,196],{"__ignoreMap":70},[75,123,124,127,131,135,139],{"class":77,"line":78},[75,125,126],{"class":81},"mkdir",[75,128,130],{"class":129},"s_sjI"," mi-api",[75,132,134],{"class":133},"sP7_E"," &&",[75,136,138],{"class":137},"sptTA"," cd",[75,140,141],{"class":129}," mi-api\n",[75,143,144,146,149,152],{"class":77,"line":93},[75,145,82],{"class":81},[75,147,148],{"class":85}," -m",[75,150,151],{"class":129}," venv",[75,153,154],{"class":129}," venv\n",[75,156,158,161],{"class":77,"line":157},3,[75,159,160],{"class":137},"source",[75,162,163],{"class":129}," venv/bin/activate\n",[75,165,167],{"class":77,"line":166},4,[75,168,170],{"emptyLinePlaceholder":169},true,"\n",[75,172,174,177,180,183,186,189,192],{"class":77,"line":173},5,[75,175,176],{"class":81},"pip",[75,178,179],{"class":129}," install",[75,181,182],{"class":129}," fastapi",[75,184,185],{"class":129}," uvicorn[standard]",[75,187,188],{"class":129}," sqlalchemy[asyncio]",[75,190,191],{"class":129}," asyncpg",[75,193,195],{"class":194},"s_hVV"," \\\n",[75,197,199,202,205,208],{"class":77,"line":198},6,[75,200,201],{"class":129},"  pydantic-settings",[75,203,204],{"class":129}," python-jose[cryptography]",[75,206,207],{"class":129}," passlib[bcrypt]",[75,209,210],{"class":129}," python-multipart\n",[12,212,213],{},"Guarda las dependencias:",[65,215,217],{"className":67,"code":216,"language":69,"meta":70,"style":70},"pip freeze > requirements.txt\n",[72,218,219],{"__ignoreMap":70},[75,220,221,223,226,230],{"class":77,"line":78},[75,222,176],{"class":81},[75,224,225],{"class":129}," freeze",[75,227,229],{"class":228},"smGrS"," >",[75,231,232],{"class":129}," requirements.txt\n",[52,234,236],{"id":235},"paso-2-configuración","Paso 2: Configuración",[65,238,242],{"className":239,"code":240,"language":241,"meta":70,"style":70},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# app/config.py\nfrom pydantic_settings import BaseSettings\n\nclass Settings(BaseSettings):\n    app_name: str = \"Mi API\"\n    database_url: str = \"postgresql+asyncpg://app:password@localhost:5432/miapi\"\n    secret_key: str = \"cambiar-en-produccion-con-valor-seguro\"\n    access_token_expire_minutes: int = 60\n\n    class Config:\n        env_file = \".env\"\n\nsettings = Settings()\n","python",[72,243,244,249,265,269,287,312,330,349,366,371,383,399,404],{"__ignoreMap":70},[75,245,246],{"class":77,"line":78},[75,247,248],{"class":89},"# app/config.py\n",[75,250,251,255,259,262],{"class":77,"line":93},[75,252,254],{"class":253},"sVHd0","from",[75,256,258],{"class":257},"su5hD"," pydantic_settings ",[75,260,261],{"class":253},"import",[75,263,264],{"class":257}," BaseSettings\n",[75,266,267],{"class":77,"line":157},[75,268,170],{"emptyLinePlaceholder":169},[75,270,271,275,278,281,284],{"class":77,"line":166},[75,272,274],{"class":273},"sbsja","class",[75,276,277],{"class":81}," Settings",[75,279,280],{"class":133},"(",[75,282,283],{"class":81},"BaseSettings",[75,285,286],{"class":133},"):\n",[75,288,289,292,295,299,302,306,309],{"class":77,"line":173},[75,290,291],{"class":257},"    app_name",[75,293,294],{"class":133},":",[75,296,298],{"class":297},"sZMiF"," str",[75,300,301],{"class":228}," =",[75,303,305],{"class":304},"sjJ54"," \"",[75,307,308],{"class":129},"Mi API",[75,310,311],{"class":304},"\"\n",[75,313,314,317,319,321,323,325,328],{"class":77,"line":198},[75,315,316],{"class":257},"    database_url",[75,318,294],{"class":133},[75,320,298],{"class":297},[75,322,301],{"class":228},[75,324,305],{"class":304},[75,326,327],{"class":129},"postgresql+asyncpg://app:password@localhost:5432/miapi",[75,329,311],{"class":304},[75,331,333,336,338,340,342,344,347],{"class":77,"line":332},7,[75,334,335],{"class":257},"    secret_key",[75,337,294],{"class":133},[75,339,298],{"class":297},[75,341,301],{"class":228},[75,343,305],{"class":304},[75,345,346],{"class":129},"cambiar-en-produccion-con-valor-seguro",[75,348,311],{"class":304},[75,350,352,355,357,360,362],{"class":77,"line":351},8,[75,353,354],{"class":257},"    access_token_expire_minutes",[75,356,294],{"class":133},[75,358,359],{"class":297}," int",[75,361,301],{"class":228},[75,363,365],{"class":364},"srdBf"," 60\n",[75,367,369],{"class":77,"line":368},9,[75,370,170],{"emptyLinePlaceholder":169},[75,372,374,377,380],{"class":77,"line":373},10,[75,375,376],{"class":273},"    class",[75,378,379],{"class":81}," Config",[75,381,382],{"class":133},":\n",[75,384,386,389,392,394,397],{"class":77,"line":385},11,[75,387,388],{"class":257},"        env_file ",[75,390,391],{"class":228},"=",[75,393,305],{"class":304},[75,395,396],{"class":129},".env",[75,398,311],{"class":304},[75,400,402],{"class":77,"line":401},12,[75,403,170],{"emptyLinePlaceholder":169},[75,405,407,410,412,415],{"class":77,"line":406},13,[75,408,409],{"class":257},"settings ",[75,411,391],{"class":228},[75,413,277],{"class":414},"slqww",[75,416,417],{"class":133},"()\n",[65,419,421],{"className":67,"code":420,"language":69,"meta":70,"style":70},"# .env\nDATABASE_URL=postgresql+asyncpg://app:tu_contraseña@localhost:5432/miapi\nSECRET_KEY=genera-un-valor-aleatorio-aqui-de-32-chars\nACCESS_TOKEN_EXPIRE_MINUTES=60\n",[72,422,423,428,438,448],{"__ignoreMap":70},[75,424,425],{"class":77,"line":78},[75,426,427],{"class":89},"# .env\n",[75,429,430,433,435],{"class":77,"line":93},[75,431,432],{"class":257},"DATABASE_URL",[75,434,391],{"class":228},[75,436,437],{"class":129},"postgresql+asyncpg://app:tu_contraseña@localhost:5432/miapi\n",[75,439,440,443,445],{"class":77,"line":157},[75,441,442],{"class":257},"SECRET_KEY",[75,444,391],{"class":228},[75,446,447],{"class":129},"genera-un-valor-aleatorio-aqui-de-32-chars\n",[75,449,450,453,455],{"class":77,"line":166},[75,451,452],{"class":257},"ACCESS_TOKEN_EXPIRE_MINUTES",[75,454,391],{"class":228},[75,456,457],{"class":129},"60\n",[459,460],"ad-banner",{},[52,462,464],{"id":463},"paso-3-conexión-a-postgresql","Paso 3: Conexión a PostgreSQL",[65,466,468],{"className":239,"code":467,"language":241,"meta":70,"style":70},"# app/database.py\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker\nfrom sqlalchemy.orm import DeclarativeBase\n\nfrom app.config import settings\n\nengine = create_async_engine(settings.database_url, echo=False)\nasync_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n\nclass Base(DeclarativeBase):\n    pass\n\nasync def get_db():\n    \"\"\"Dependency que provee una sesión de DB a cada request.\"\"\"\n    async with async_session() as session:\n        try:\n            yield session\n            await session.commit()\n        except Exception:\n            await session.rollback()\n            raise\n",[72,469,470,475,508,524,528,545,549,584,620,624,638,643,647,662,676,699,707,716,731,742,756],{"__ignoreMap":70},[75,471,472],{"class":77,"line":78},[75,473,474],{"class":89},"# app/database.py\n",[75,476,477,479,482,484,487,489,492,494,497,500,503,505],{"class":77,"line":93},[75,478,254],{"class":253},[75,480,481],{"class":257}," sqlalchemy",[75,483,63],{"class":133},[75,485,486],{"class":257},"ext",[75,488,63],{"class":133},[75,490,491],{"class":257},"asyncio ",[75,493,261],{"class":253},[75,495,496],{"class":257}," create_async_engine",[75,498,499],{"class":133},",",[75,501,502],{"class":257}," AsyncSession",[75,504,499],{"class":133},[75,506,507],{"class":257}," async_sessionmaker\n",[75,509,510,512,514,516,519,521],{"class":77,"line":157},[75,511,254],{"class":253},[75,513,481],{"class":257},[75,515,63],{"class":133},[75,517,518],{"class":257},"orm ",[75,520,261],{"class":253},[75,522,523],{"class":257}," DeclarativeBase\n",[75,525,526],{"class":77,"line":166},[75,527,170],{"emptyLinePlaceholder":169},[75,529,530,532,535,537,540,542],{"class":77,"line":173},[75,531,254],{"class":253},[75,533,534],{"class":257}," app",[75,536,63],{"class":133},[75,538,539],{"class":257},"config ",[75,541,261],{"class":253},[75,543,544],{"class":257}," settings\n",[75,546,547],{"class":77,"line":198},[75,548,170],{"emptyLinePlaceholder":169},[75,550,551,554,556,558,560,563,565,569,571,575,577,581],{"class":77,"line":332},[75,552,553],{"class":257},"engine ",[75,555,391],{"class":228},[75,557,496],{"class":414},[75,559,280],{"class":133},[75,561,562],{"class":414},"settings",[75,564,63],{"class":133},[75,566,568],{"class":567},"skxfh","database_url",[75,570,499],{"class":133},[75,572,574],{"class":573},"s99_P"," echo",[75,576,391],{"class":228},[75,578,580],{"class":579},"s39Yj","False",[75,582,583],{"class":133},")\n",[75,585,586,589,591,594,596,599,601,604,606,609,611,614,616,618],{"class":77,"line":351},[75,587,588],{"class":257},"async_session ",[75,590,391],{"class":228},[75,592,593],{"class":414}," async_sessionmaker",[75,595,280],{"class":133},[75,597,598],{"class":414},"engine",[75,600,499],{"class":133},[75,602,603],{"class":573}," class_",[75,605,391],{"class":228},[75,607,608],{"class":414},"AsyncSession",[75,610,499],{"class":133},[75,612,613],{"class":573}," expire_on_commit",[75,615,391],{"class":228},[75,617,580],{"class":579},[75,619,583],{"class":133},[75,621,622],{"class":77,"line":368},[75,623,170],{"emptyLinePlaceholder":169},[75,625,626,628,631,633,636],{"class":77,"line":373},[75,627,274],{"class":273},[75,629,630],{"class":81}," Base",[75,632,280],{"class":133},[75,634,635],{"class":81},"DeclarativeBase",[75,637,286],{"class":133},[75,639,640],{"class":77,"line":385},[75,641,642],{"class":253},"    pass\n",[75,644,645],{"class":77,"line":401},[75,646,170],{"emptyLinePlaceholder":169},[75,648,649,652,655,659],{"class":77,"line":406},[75,650,651],{"class":273},"async",[75,653,654],{"class":273}," def",[75,656,658],{"class":657},"sGLFI"," get_db",[75,660,661],{"class":133},"():\n",[75,663,665,669,673],{"class":77,"line":664},14,[75,666,668],{"class":667},"s2W-s","    \"\"\"",[75,670,672],{"class":671},"sithA","Dependency que provee una sesión de DB a cada request.",[75,674,675],{"class":667},"\"\"\"\n",[75,677,679,682,685,688,691,694,697],{"class":77,"line":678},15,[75,680,681],{"class":253},"    async",[75,683,684],{"class":253}," with",[75,686,687],{"class":414}," async_session",[75,689,690],{"class":133},"()",[75,692,693],{"class":253}," as",[75,695,696],{"class":257}," session",[75,698,382],{"class":133},[75,700,702,705],{"class":77,"line":701},16,[75,703,704],{"class":253},"        try",[75,706,382],{"class":133},[75,708,710,713],{"class":77,"line":709},17,[75,711,712],{"class":253},"            yield",[75,714,715],{"class":257}," session\n",[75,717,719,722,724,726,729],{"class":77,"line":718},18,[75,720,721],{"class":253},"            await",[75,723,696],{"class":257},[75,725,63],{"class":133},[75,727,728],{"class":414},"commit",[75,730,417],{"class":133},[75,732,734,737,740],{"class":77,"line":733},19,[75,735,736],{"class":253},"        except",[75,738,739],{"class":297}," Exception",[75,741,382],{"class":133},[75,743,745,747,749,751,754],{"class":77,"line":744},20,[75,746,721],{"class":253},[75,748,696],{"class":257},[75,750,63],{"class":133},[75,752,753],{"class":414},"rollback",[75,755,417],{"class":133},[75,757,759],{"class":77,"line":758},21,[75,760,761],{"class":253},"            raise\n",[52,763,765],{"id":764},"paso-4-modelo-y-schema","Paso 4: Modelo y schema",[12,767,768,769,773,774,777],{},"El ",[770,771,772],"strong",{},"modelo"," define la estructura en la base de datos. El ",[770,775,776],{},"schema"," define la estructura de la API (lo que el cliente envía y recibe):",[65,779,781],{"className":239,"code":780,"language":241,"meta":70,"style":70},"# app/models/producto.py\nfrom sqlalchemy import String, Numeric, Boolean, DateTime, func\nfrom sqlalchemy.orm import Mapped, mapped_column\nfrom app.database import Base\n\nclass Producto(Base):\n    __tablename__ = \"productos\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    sku: Mapped[str] = mapped_column(String(50), unique=True, index=True)\n    nombre: Mapped[str] = mapped_column(String(200))\n    descripcion: Mapped[str | None] = mapped_column(String(1000))\n    precio: Mapped[float] = mapped_column(Numeric(12, 2))\n    stock: Mapped[int] = mapped_column(default=0)\n    activo: Mapped[bool] = mapped_column(Boolean, default=True)\n    created_at: Mapped[str] = mapped_column(DateTime, server_default=func.now())\n    updated_at: Mapped[str] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())\n",[72,782,783,788,820,840,856,860,874,888,892,927,978,1009,1045,1082,1113,1149,1191],{"__ignoreMap":70},[75,784,785],{"class":77,"line":78},[75,786,787],{"class":89},"# app/models/producto.py\n",[75,789,790,792,795,797,800,802,805,807,810,812,815,817],{"class":77,"line":93},[75,791,254],{"class":253},[75,793,794],{"class":257}," sqlalchemy ",[75,796,261],{"class":253},[75,798,799],{"class":257}," String",[75,801,499],{"class":133},[75,803,804],{"class":257}," Numeric",[75,806,499],{"class":133},[75,808,809],{"class":257}," Boolean",[75,811,499],{"class":133},[75,813,814],{"class":257}," DateTime",[75,816,499],{"class":133},[75,818,819],{"class":257}," func\n",[75,821,822,824,826,828,830,832,835,837],{"class":77,"line":157},[75,823,254],{"class":253},[75,825,481],{"class":257},[75,827,63],{"class":133},[75,829,518],{"class":257},[75,831,261],{"class":253},[75,833,834],{"class":257}," Mapped",[75,836,499],{"class":133},[75,838,839],{"class":257}," mapped_column\n",[75,841,842,844,846,848,851,853],{"class":77,"line":166},[75,843,254],{"class":253},[75,845,534],{"class":257},[75,847,63],{"class":133},[75,849,850],{"class":257},"database ",[75,852,261],{"class":253},[75,854,855],{"class":257}," Base\n",[75,857,858],{"class":77,"line":173},[75,859,170],{"emptyLinePlaceholder":169},[75,861,862,864,867,869,872],{"class":77,"line":198},[75,863,274],{"class":273},[75,865,866],{"class":81}," Producto",[75,868,280],{"class":133},[75,870,871],{"class":81},"Base",[75,873,286],{"class":133},[75,875,876,879,881,883,886],{"class":77,"line":332},[75,877,878],{"class":257},"    __tablename__ ",[75,880,391],{"class":228},[75,882,305],{"class":304},[75,884,885],{"class":129},"productos",[75,887,311],{"class":304},[75,889,890],{"class":77,"line":351},[75,891,170],{"emptyLinePlaceholder":169},[75,893,894,897,899,901,904,907,910,912,915,917,920,922,925],{"class":77,"line":368},[75,895,896],{"class":137},"    id",[75,898,294],{"class":133},[75,900,834],{"class":257},[75,902,903],{"class":133},"[",[75,905,906],{"class":297},"int",[75,908,909],{"class":133},"]",[75,911,301],{"class":228},[75,913,914],{"class":414}," mapped_column",[75,916,280],{"class":133},[75,918,919],{"class":573},"primary_key",[75,921,391],{"class":228},[75,923,924],{"class":579},"True",[75,926,583],{"class":133},[75,928,929,932,934,936,938,941,943,945,947,949,952,954,957,960,963,965,967,969,972,974,976],{"class":77,"line":373},[75,930,931],{"class":257},"    sku",[75,933,294],{"class":133},[75,935,834],{"class":257},[75,937,903],{"class":133},[75,939,940],{"class":297},"str",[75,942,909],{"class":133},[75,944,301],{"class":228},[75,946,914],{"class":414},[75,948,280],{"class":133},[75,950,951],{"class":414},"String",[75,953,280],{"class":133},[75,955,956],{"class":364},"50",[75,958,959],{"class":133},"),",[75,961,962],{"class":573}," unique",[75,964,391],{"class":228},[75,966,924],{"class":579},[75,968,499],{"class":133},[75,970,971],{"class":573}," index",[75,973,391],{"class":228},[75,975,924],{"class":579},[75,977,583],{"class":133},[75,979,980,983,985,987,989,991,993,995,997,999,1001,1003,1006],{"class":77,"line":385},[75,981,982],{"class":257},"    nombre",[75,984,294],{"class":133},[75,986,834],{"class":257},[75,988,903],{"class":133},[75,990,940],{"class":297},[75,992,909],{"class":133},[75,994,301],{"class":228},[75,996,914],{"class":414},[75,998,280],{"class":133},[75,1000,951],{"class":414},[75,1002,280],{"class":133},[75,1004,1005],{"class":364},"200",[75,1007,1008],{"class":133},"))\n",[75,1010,1011,1014,1016,1018,1020,1022,1025,1028,1030,1032,1034,1036,1038,1040,1043],{"class":77,"line":401},[75,1012,1013],{"class":257},"    descripcion",[75,1015,294],{"class":133},[75,1017,834],{"class":257},[75,1019,903],{"class":133},[75,1021,940],{"class":297},[75,1023,1024],{"class":228}," |",[75,1026,1027],{"class":579}," None",[75,1029,909],{"class":133},[75,1031,301],{"class":228},[75,1033,914],{"class":414},[75,1035,280],{"class":133},[75,1037,951],{"class":414},[75,1039,280],{"class":133},[75,1041,1042],{"class":364},"1000",[75,1044,1008],{"class":133},[75,1046,1047,1050,1052,1054,1056,1059,1061,1063,1065,1067,1070,1072,1075,1077,1080],{"class":77,"line":406},[75,1048,1049],{"class":257},"    precio",[75,1051,294],{"class":133},[75,1053,834],{"class":257},[75,1055,903],{"class":133},[75,1057,1058],{"class":297},"float",[75,1060,909],{"class":133},[75,1062,301],{"class":228},[75,1064,914],{"class":414},[75,1066,280],{"class":133},[75,1068,1069],{"class":414},"Numeric",[75,1071,280],{"class":133},[75,1073,1074],{"class":364},"12",[75,1076,499],{"class":133},[75,1078,1079],{"class":364}," 2",[75,1081,1008],{"class":133},[75,1083,1084,1087,1089,1091,1093,1095,1097,1099,1101,1103,1106,1108,1111],{"class":77,"line":664},[75,1085,1086],{"class":257},"    stock",[75,1088,294],{"class":133},[75,1090,834],{"class":257},[75,1092,903],{"class":133},[75,1094,906],{"class":297},[75,1096,909],{"class":133},[75,1098,301],{"class":228},[75,1100,914],{"class":414},[75,1102,280],{"class":133},[75,1104,1105],{"class":573},"default",[75,1107,391],{"class":228},[75,1109,1110],{"class":364},"0",[75,1112,583],{"class":133},[75,1114,1115,1118,1120,1122,1124,1127,1129,1131,1133,1135,1138,1140,1143,1145,1147],{"class":77,"line":678},[75,1116,1117],{"class":257},"    activo",[75,1119,294],{"class":133},[75,1121,834],{"class":257},[75,1123,903],{"class":133},[75,1125,1126],{"class":297},"bool",[75,1128,909],{"class":133},[75,1130,301],{"class":228},[75,1132,914],{"class":414},[75,1134,280],{"class":133},[75,1136,1137],{"class":414},"Boolean",[75,1139,499],{"class":133},[75,1141,1142],{"class":573}," default",[75,1144,391],{"class":228},[75,1146,924],{"class":579},[75,1148,583],{"class":133},[75,1150,1151,1154,1156,1158,1160,1162,1164,1166,1168,1170,1173,1175,1178,1180,1183,1185,1188],{"class":77,"line":701},[75,1152,1153],{"class":257},"    created_at",[75,1155,294],{"class":133},[75,1157,834],{"class":257},[75,1159,903],{"class":133},[75,1161,940],{"class":297},[75,1163,909],{"class":133},[75,1165,301],{"class":228},[75,1167,914],{"class":414},[75,1169,280],{"class":133},[75,1171,1172],{"class":414},"DateTime",[75,1174,499],{"class":133},[75,1176,1177],{"class":573}," server_default",[75,1179,391],{"class":228},[75,1181,1182],{"class":414},"func",[75,1184,63],{"class":133},[75,1186,1187],{"class":414},"now",[75,1189,1190],{"class":133},"())\n",[75,1192,1193,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1229,1232,1234,1236,1238,1240],{"class":77,"line":709},[75,1194,1195],{"class":257},"    updated_at",[75,1197,294],{"class":133},[75,1199,834],{"class":257},[75,1201,903],{"class":133},[75,1203,940],{"class":297},[75,1205,909],{"class":133},[75,1207,301],{"class":228},[75,1209,914],{"class":414},[75,1211,280],{"class":133},[75,1213,1172],{"class":414},[75,1215,499],{"class":133},[75,1217,1177],{"class":573},[75,1219,391],{"class":228},[75,1221,1182],{"class":414},[75,1223,63],{"class":133},[75,1225,1187],{"class":414},[75,1227,1228],{"class":133},"(),",[75,1230,1231],{"class":573}," onupdate",[75,1233,391],{"class":228},[75,1235,1182],{"class":414},[75,1237,63],{"class":133},[75,1239,1187],{"class":414},[75,1241,1190],{"class":133},[65,1243,1245],{"className":239,"code":1244,"language":241,"meta":70,"style":70},"# app/schemas/producto.py\nfrom pydantic import BaseModel, Field\nfrom datetime import datetime\n\nclass ProductoBase(BaseModel):\n    sku: str = Field(..., min_length=1, max_length=50, examples=[\"SKU-001\"])\n    nombre: str = Field(..., min_length=1, max_length=200)\n    descripcion: str | None = None\n    precio: float = Field(..., gt=0, examples=[299.99])\n    stock: int = Field(default=0, ge=0)\n    activo: bool = True\n\nclass ProductoCreate(ProductoBase):\n    pass\n\nclass ProductoUpdate(BaseModel):\n    nombre: str | None = None\n    descripcion: str | None = None\n    precio: float | None = Field(default=None, gt=0)\n    stock: int | None = Field(default=None, ge=0)\n    activo: bool | None = None\n\nclass ProductoResponse(ProductoBase):\n    id: int\n    created_at: datetime\n    updated_at: datetime\n\n    class Config:\n        from_attributes = True\n\nclass PaginatedResponse(BaseModel):\n    items: list[ProductoResponse]\n    total: int\n    page: int\n    per_page: int\n    pages: int\n",[72,1246,1247,1252,1269,1281,1285,1299,1356,1390,1407,1446,1477,1491,1495,1509,1513,1517,1530,1546,1562,1597,1631,1647,1652,1666,1676,1685,1694,1699,1708,1718,1723,1737,1756,1766,1776,1786],{"__ignoreMap":70},[75,1248,1249],{"class":77,"line":78},[75,1250,1251],{"class":89},"# app/schemas/producto.py\n",[75,1253,1254,1256,1259,1261,1264,1266],{"class":77,"line":93},[75,1255,254],{"class":253},[75,1257,1258],{"class":257}," pydantic ",[75,1260,261],{"class":253},[75,1262,1263],{"class":257}," BaseModel",[75,1265,499],{"class":133},[75,1267,1268],{"class":257}," Field\n",[75,1270,1271,1273,1276,1278],{"class":77,"line":157},[75,1272,254],{"class":253},[75,1274,1275],{"class":257}," datetime ",[75,1277,261],{"class":253},[75,1279,1280],{"class":257}," datetime\n",[75,1282,1283],{"class":77,"line":166},[75,1284,170],{"emptyLinePlaceholder":169},[75,1286,1287,1289,1292,1294,1297],{"class":77,"line":173},[75,1288,274],{"class":273},[75,1290,1291],{"class":81}," ProductoBase",[75,1293,280],{"class":133},[75,1295,1296],{"class":81},"BaseModel",[75,1298,286],{"class":133},[75,1300,1301,1303,1305,1307,1309,1312,1314,1317,1319,1322,1324,1327,1329,1332,1334,1336,1338,1341,1343,1345,1348,1351,1353],{"class":77,"line":198},[75,1302,931],{"class":257},[75,1304,294],{"class":133},[75,1306,298],{"class":297},[75,1308,301],{"class":228},[75,1310,1311],{"class":414}," Field",[75,1313,280],{"class":133},[75,1315,1316],{"class":137},"...",[75,1318,499],{"class":133},[75,1320,1321],{"class":573}," min_length",[75,1323,391],{"class":228},[75,1325,1326],{"class":364},"1",[75,1328,499],{"class":133},[75,1330,1331],{"class":573}," max_length",[75,1333,391],{"class":228},[75,1335,956],{"class":364},[75,1337,499],{"class":133},[75,1339,1340],{"class":573}," examples",[75,1342,391],{"class":228},[75,1344,903],{"class":133},[75,1346,1347],{"class":304},"\"",[75,1349,1350],{"class":129},"SKU-001",[75,1352,1347],{"class":304},[75,1354,1355],{"class":133},"])\n",[75,1357,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388],{"class":77,"line":332},[75,1359,982],{"class":257},[75,1361,294],{"class":133},[75,1363,298],{"class":297},[75,1365,301],{"class":228},[75,1367,1311],{"class":414},[75,1369,280],{"class":133},[75,1371,1316],{"class":137},[75,1373,499],{"class":133},[75,1375,1321],{"class":573},[75,1377,391],{"class":228},[75,1379,1326],{"class":364},[75,1381,499],{"class":133},[75,1383,1331],{"class":573},[75,1385,391],{"class":228},[75,1387,1005],{"class":364},[75,1389,583],{"class":133},[75,1391,1392,1394,1396,1398,1400,1402,1404],{"class":77,"line":351},[75,1393,1013],{"class":257},[75,1395,294],{"class":133},[75,1397,298],{"class":297},[75,1399,1024],{"class":228},[75,1401,1027],{"class":579},[75,1403,301],{"class":228},[75,1405,1406],{"class":579}," None\n",[75,1408,1409,1411,1413,1416,1418,1420,1422,1424,1426,1429,1431,1433,1435,1437,1439,1441,1444],{"class":77,"line":368},[75,1410,1049],{"class":257},[75,1412,294],{"class":133},[75,1414,1415],{"class":297}," float",[75,1417,301],{"class":228},[75,1419,1311],{"class":414},[75,1421,280],{"class":133},[75,1423,1316],{"class":137},[75,1425,499],{"class":133},[75,1427,1428],{"class":573}," gt",[75,1430,391],{"class":228},[75,1432,1110],{"class":364},[75,1434,499],{"class":133},[75,1436,1340],{"class":573},[75,1438,391],{"class":228},[75,1440,903],{"class":133},[75,1442,1443],{"class":364},"299.99",[75,1445,1355],{"class":133},[75,1447,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1471,1473,1475],{"class":77,"line":373},[75,1449,1086],{"class":257},[75,1451,294],{"class":133},[75,1453,359],{"class":297},[75,1455,301],{"class":228},[75,1457,1311],{"class":414},[75,1459,280],{"class":133},[75,1461,1105],{"class":573},[75,1463,391],{"class":228},[75,1465,1110],{"class":364},[75,1467,499],{"class":133},[75,1469,1470],{"class":573}," ge",[75,1472,391],{"class":228},[75,1474,1110],{"class":364},[75,1476,583],{"class":133},[75,1478,1479,1481,1483,1486,1488],{"class":77,"line":385},[75,1480,1117],{"class":257},[75,1482,294],{"class":133},[75,1484,1485],{"class":297}," bool",[75,1487,301],{"class":228},[75,1489,1490],{"class":579}," True\n",[75,1492,1493],{"class":77,"line":401},[75,1494,170],{"emptyLinePlaceholder":169},[75,1496,1497,1499,1502,1504,1507],{"class":77,"line":406},[75,1498,274],{"class":273},[75,1500,1501],{"class":81}," ProductoCreate",[75,1503,280],{"class":133},[75,1505,1506],{"class":81},"ProductoBase",[75,1508,286],{"class":133},[75,1510,1511],{"class":77,"line":664},[75,1512,642],{"class":253},[75,1514,1515],{"class":77,"line":678},[75,1516,170],{"emptyLinePlaceholder":169},[75,1518,1519,1521,1524,1526,1528],{"class":77,"line":701},[75,1520,274],{"class":273},[75,1522,1523],{"class":81}," ProductoUpdate",[75,1525,280],{"class":133},[75,1527,1296],{"class":81},[75,1529,286],{"class":133},[75,1531,1532,1534,1536,1538,1540,1542,1544],{"class":77,"line":709},[75,1533,982],{"class":257},[75,1535,294],{"class":133},[75,1537,298],{"class":297},[75,1539,1024],{"class":228},[75,1541,1027],{"class":579},[75,1543,301],{"class":228},[75,1545,1406],{"class":579},[75,1547,1548,1550,1552,1554,1556,1558,1560],{"class":77,"line":718},[75,1549,1013],{"class":257},[75,1551,294],{"class":133},[75,1553,298],{"class":297},[75,1555,1024],{"class":228},[75,1557,1027],{"class":579},[75,1559,301],{"class":228},[75,1561,1406],{"class":579},[75,1563,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1587,1589,1591,1593,1595],{"class":77,"line":733},[75,1565,1049],{"class":257},[75,1567,294],{"class":133},[75,1569,1415],{"class":297},[75,1571,1024],{"class":228},[75,1573,1027],{"class":579},[75,1575,301],{"class":228},[75,1577,1311],{"class":414},[75,1579,280],{"class":133},[75,1581,1105],{"class":573},[75,1583,391],{"class":228},[75,1585,1586],{"class":579},"None",[75,1588,499],{"class":133},[75,1590,1428],{"class":573},[75,1592,391],{"class":228},[75,1594,1110],{"class":364},[75,1596,583],{"class":133},[75,1598,1599,1601,1603,1605,1607,1609,1611,1613,1615,1617,1619,1621,1623,1625,1627,1629],{"class":77,"line":744},[75,1600,1086],{"class":257},[75,1602,294],{"class":133},[75,1604,359],{"class":297},[75,1606,1024],{"class":228},[75,1608,1027],{"class":579},[75,1610,301],{"class":228},[75,1612,1311],{"class":414},[75,1614,280],{"class":133},[75,1616,1105],{"class":573},[75,1618,391],{"class":228},[75,1620,1586],{"class":579},[75,1622,499],{"class":133},[75,1624,1470],{"class":573},[75,1626,391],{"class":228},[75,1628,1110],{"class":364},[75,1630,583],{"class":133},[75,1632,1633,1635,1637,1639,1641,1643,1645],{"class":77,"line":758},[75,1634,1117],{"class":257},[75,1636,294],{"class":133},[75,1638,1485],{"class":297},[75,1640,1024],{"class":228},[75,1642,1027],{"class":579},[75,1644,301],{"class":228},[75,1646,1406],{"class":579},[75,1648,1650],{"class":77,"line":1649},22,[75,1651,170],{"emptyLinePlaceholder":169},[75,1653,1655,1657,1660,1662,1664],{"class":77,"line":1654},23,[75,1656,274],{"class":273},[75,1658,1659],{"class":81}," ProductoResponse",[75,1661,280],{"class":133},[75,1663,1506],{"class":81},[75,1665,286],{"class":133},[75,1667,1669,1671,1673],{"class":77,"line":1668},24,[75,1670,896],{"class":137},[75,1672,294],{"class":133},[75,1674,1675],{"class":297}," int\n",[75,1677,1679,1681,1683],{"class":77,"line":1678},25,[75,1680,1153],{"class":257},[75,1682,294],{"class":133},[75,1684,1280],{"class":257},[75,1686,1688,1690,1692],{"class":77,"line":1687},26,[75,1689,1195],{"class":257},[75,1691,294],{"class":133},[75,1693,1280],{"class":257},[75,1695,1697],{"class":77,"line":1696},27,[75,1698,170],{"emptyLinePlaceholder":169},[75,1700,1702,1704,1706],{"class":77,"line":1701},28,[75,1703,376],{"class":273},[75,1705,379],{"class":81},[75,1707,382],{"class":133},[75,1709,1711,1714,1716],{"class":77,"line":1710},29,[75,1712,1713],{"class":257},"        from_attributes ",[75,1715,391],{"class":228},[75,1717,1490],{"class":579},[75,1719,1721],{"class":77,"line":1720},30,[75,1722,170],{"emptyLinePlaceholder":169},[75,1724,1726,1728,1731,1733,1735],{"class":77,"line":1725},31,[75,1727,274],{"class":273},[75,1729,1730],{"class":81}," PaginatedResponse",[75,1732,280],{"class":133},[75,1734,1296],{"class":81},[75,1736,286],{"class":133},[75,1738,1740,1743,1745,1748,1750,1753],{"class":77,"line":1739},32,[75,1741,1742],{"class":257},"    items",[75,1744,294],{"class":133},[75,1746,1747],{"class":257}," list",[75,1749,903],{"class":133},[75,1751,1752],{"class":257},"ProductoResponse",[75,1754,1755],{"class":133},"]\n",[75,1757,1759,1762,1764],{"class":77,"line":1758},33,[75,1760,1761],{"class":257},"    total",[75,1763,294],{"class":133},[75,1765,1675],{"class":297},[75,1767,1769,1772,1774],{"class":77,"line":1768},34,[75,1770,1771],{"class":257},"    page",[75,1773,294],{"class":133},[75,1775,1675],{"class":297},[75,1777,1779,1782,1784],{"class":77,"line":1778},35,[75,1780,1781],{"class":257},"    per_page",[75,1783,294],{"class":133},[75,1785,1675],{"class":297},[75,1787,1789,1792,1794],{"class":77,"line":1788},36,[75,1790,1791],{"class":257},"    pages",[75,1793,294],{"class":133},[75,1795,1675],{"class":297},[1797,1798,1801],"alert",{"title":1799,"type":1800},"¿Por qué separar modelo y schema?","info",[12,1802,1803],{},"El modelo define cómo se almacenan los datos en PostgreSQL. El schema define cómo la API recibe y devuelve datos. Esto te permite exponer solo los campos que el cliente necesita, validar inputs con reglas de negocio (precio positivo, SKU único) y tener diferentes schemas para crear, actualizar y responder.",[52,1805,1807],{"id":1806},"paso-5-autenticación-jwt","Paso 5: Autenticación JWT",[65,1809,1811],{"className":239,"code":1810,"language":241,"meta":70,"style":70},"# app/auth.py\nfrom datetime import datetime, timedelta, timezone\nfrom jose import JWTError, jwt\nfrom fastapi import Depends, HTTPException, status\nfrom fastapi.security import HTTPBearer, HTTPAuthorizationCredentials\nfrom app.config import settings\n\nsecurity = HTTPBearer()\n\ndef create_access_token(data: dict) -> str:\n    to_encode = data.copy()\n    expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)\n    to_encode.update({\"exp\": expire})\n    return jwt.encode(to_encode, settings.secret_key, algorithm=\"HS256\")\n\ndef verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:\n    token = credentials.credentials\n    try:\n        payload = jwt.decode(token, settings.secret_key, algorithms=[\"HS256\"])\n        return payload\n    except JWTError:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Token inválido o expirado\",\n        )\n",[72,1812,1813,1818,1839,1856,1878,1899,1913,1917,1927,1931,1960,1977,2023,2051,2095,2099,2134,2149,2156,2200,2208,2217,2227,2246,2262],{"__ignoreMap":70},[75,1814,1815],{"class":77,"line":78},[75,1816,1817],{"class":89},"# app/auth.py\n",[75,1819,1820,1822,1824,1826,1829,1831,1834,1836],{"class":77,"line":93},[75,1821,254],{"class":253},[75,1823,1275],{"class":257},[75,1825,261],{"class":253},[75,1827,1828],{"class":257}," datetime",[75,1830,499],{"class":133},[75,1832,1833],{"class":257}," timedelta",[75,1835,499],{"class":133},[75,1837,1838],{"class":257}," timezone\n",[75,1840,1841,1843,1846,1848,1851,1853],{"class":77,"line":157},[75,1842,254],{"class":253},[75,1844,1845],{"class":257}," jose ",[75,1847,261],{"class":253},[75,1849,1850],{"class":257}," JWTError",[75,1852,499],{"class":133},[75,1854,1855],{"class":257}," jwt\n",[75,1857,1858,1860,1863,1865,1868,1870,1873,1875],{"class":77,"line":166},[75,1859,254],{"class":253},[75,1861,1862],{"class":257}," fastapi ",[75,1864,261],{"class":253},[75,1866,1867],{"class":257}," Depends",[75,1869,499],{"class":133},[75,1871,1872],{"class":257}," HTTPException",[75,1874,499],{"class":133},[75,1876,1877],{"class":257}," status\n",[75,1879,1880,1882,1884,1886,1889,1891,1894,1896],{"class":77,"line":173},[75,1881,254],{"class":253},[75,1883,182],{"class":257},[75,1885,63],{"class":133},[75,1887,1888],{"class":257},"security ",[75,1890,261],{"class":253},[75,1892,1893],{"class":257}," HTTPBearer",[75,1895,499],{"class":133},[75,1897,1898],{"class":257}," HTTPAuthorizationCredentials\n",[75,1900,1901,1903,1905,1907,1909,1911],{"class":77,"line":198},[75,1902,254],{"class":253},[75,1904,534],{"class":257},[75,1906,63],{"class":133},[75,1908,539],{"class":257},[75,1910,261],{"class":253},[75,1912,544],{"class":257},[75,1914,1915],{"class":77,"line":332},[75,1916,170],{"emptyLinePlaceholder":169},[75,1918,1919,1921,1923,1925],{"class":77,"line":351},[75,1920,1888],{"class":257},[75,1922,391],{"class":228},[75,1924,1893],{"class":414},[75,1926,417],{"class":133},[75,1928,1929],{"class":77,"line":368},[75,1930,170],{"emptyLinePlaceholder":169},[75,1932,1933,1936,1939,1941,1945,1947,1950,1953,1956,1958],{"class":77,"line":373},[75,1934,1935],{"class":273},"def",[75,1937,1938],{"class":657}," create_access_token",[75,1940,280],{"class":133},[75,1942,1944],{"class":1943},"sFwrP","data",[75,1946,294],{"class":133},[75,1948,1949],{"class":297}," dict",[75,1951,1952],{"class":133},")",[75,1954,1955],{"class":133}," ->",[75,1957,298],{"class":297},[75,1959,382],{"class":133},[75,1961,1962,1965,1967,1970,1972,1975],{"class":77,"line":385},[75,1963,1964],{"class":257},"    to_encode ",[75,1966,391],{"class":228},[75,1968,1969],{"class":257}," data",[75,1971,63],{"class":133},[75,1973,1974],{"class":414},"copy",[75,1976,417],{"class":133},[75,1978,1979,1982,1984,1986,1988,1990,1992,1995,1997,2000,2002,2005,2007,2009,2012,2014,2016,2018,2021],{"class":77,"line":401},[75,1980,1981],{"class":257},"    expire ",[75,1983,391],{"class":228},[75,1985,1828],{"class":257},[75,1987,63],{"class":133},[75,1989,1187],{"class":414},[75,1991,280],{"class":133},[75,1993,1994],{"class":414},"timezone",[75,1996,63],{"class":133},[75,1998,1999],{"class":567},"utc",[75,2001,1952],{"class":133},[75,2003,2004],{"class":228}," +",[75,2006,1833],{"class":414},[75,2008,280],{"class":133},[75,2010,2011],{"class":573},"minutes",[75,2013,391],{"class":228},[75,2015,562],{"class":414},[75,2017,63],{"class":133},[75,2019,2020],{"class":567},"access_token_expire_minutes",[75,2022,583],{"class":133},[75,2024,2025,2028,2030,2033,2036,2038,2041,2043,2045,2048],{"class":77,"line":406},[75,2026,2027],{"class":257},"    to_encode",[75,2029,63],{"class":133},[75,2031,2032],{"class":414},"update",[75,2034,2035],{"class":133},"({",[75,2037,1347],{"class":304},[75,2039,2040],{"class":129},"exp",[75,2042,1347],{"class":304},[75,2044,294],{"class":133},[75,2046,2047],{"class":414}," expire",[75,2049,2050],{"class":133},"})\n",[75,2052,2053,2056,2059,2061,2064,2066,2069,2071,2074,2076,2079,2081,2084,2086,2088,2091,2093],{"class":77,"line":664},[75,2054,2055],{"class":253},"    return",[75,2057,2058],{"class":257}," jwt",[75,2060,63],{"class":133},[75,2062,2063],{"class":414},"encode",[75,2065,280],{"class":133},[75,2067,2068],{"class":414},"to_encode",[75,2070,499],{"class":133},[75,2072,2073],{"class":414}," settings",[75,2075,63],{"class":133},[75,2077,2078],{"class":567},"secret_key",[75,2080,499],{"class":133},[75,2082,2083],{"class":573}," algorithm",[75,2085,391],{"class":228},[75,2087,1347],{"class":304},[75,2089,2090],{"class":129},"HS256",[75,2092,1347],{"class":304},[75,2094,583],{"class":133},[75,2096,2097],{"class":77,"line":678},[75,2098,170],{"emptyLinePlaceholder":169},[75,2100,2101,2103,2106,2108,2111,2113,2116,2118,2120,2122,2125,2128,2130,2132],{"class":77,"line":701},[75,2102,1935],{"class":273},[75,2104,2105],{"class":657}," verify_token",[75,2107,280],{"class":133},[75,2109,2110],{"class":1943},"credentials",[75,2112,294],{"class":133},[75,2114,2115],{"class":257}," HTTPAuthorizationCredentials ",[75,2117,391],{"class":228},[75,2119,1867],{"class":414},[75,2121,280],{"class":133},[75,2123,2124],{"class":414},"security",[75,2126,2127],{"class":133},"))",[75,2129,1955],{"class":133},[75,2131,1949],{"class":297},[75,2133,382],{"class":133},[75,2135,2136,2139,2141,2144,2146],{"class":77,"line":709},[75,2137,2138],{"class":257},"    token ",[75,2140,391],{"class":228},[75,2142,2143],{"class":257}," credentials",[75,2145,63],{"class":133},[75,2147,2148],{"class":567},"credentials\n",[75,2150,2151,2154],{"class":77,"line":718},[75,2152,2153],{"class":253},"    try",[75,2155,382],{"class":133},[75,2157,2158,2161,2163,2165,2167,2170,2172,2175,2177,2179,2181,2183,2185,2188,2190,2192,2194,2196,2198],{"class":77,"line":733},[75,2159,2160],{"class":257},"        payload ",[75,2162,391],{"class":228},[75,2164,2058],{"class":257},[75,2166,63],{"class":133},[75,2168,2169],{"class":414},"decode",[75,2171,280],{"class":133},[75,2173,2174],{"class":414},"token",[75,2176,499],{"class":133},[75,2178,2073],{"class":414},[75,2180,63],{"class":133},[75,2182,2078],{"class":567},[75,2184,499],{"class":133},[75,2186,2187],{"class":573}," algorithms",[75,2189,391],{"class":228},[75,2191,903],{"class":133},[75,2193,1347],{"class":304},[75,2195,2090],{"class":129},[75,2197,1347],{"class":304},[75,2199,1355],{"class":133},[75,2201,2202,2205],{"class":77,"line":744},[75,2203,2204],{"class":253},"        return",[75,2206,2207],{"class":257}," payload\n",[75,2209,2210,2213,2215],{"class":77,"line":758},[75,2211,2212],{"class":253},"    except",[75,2214,1850],{"class":257},[75,2216,382],{"class":133},[75,2218,2219,2222,2224],{"class":77,"line":1649},[75,2220,2221],{"class":253},"        raise",[75,2223,1872],{"class":414},[75,2225,2226],{"class":133},"(\n",[75,2228,2229,2232,2234,2237,2239,2243],{"class":77,"line":1654},[75,2230,2231],{"class":573},"            status_code",[75,2233,391],{"class":228},[75,2235,2236],{"class":414},"status",[75,2238,63],{"class":133},[75,2240,2242],{"class":2241},"swQdS","HTTP_401_UNAUTHORIZED",[75,2244,2245],{"class":133},",\n",[75,2247,2248,2251,2253,2255,2258,2260],{"class":77,"line":1668},[75,2249,2250],{"class":573},"            detail",[75,2252,391],{"class":228},[75,2254,1347],{"class":304},[75,2256,2257],{"class":129},"Token inválido o expirado",[75,2259,1347],{"class":304},[75,2261,2245],{"class":133},[75,2263,2264],{"class":77,"line":1678},[75,2265,2266],{"class":133},"        )\n",[52,2268,2270],{"id":2269},"paso-6-endpoints-crud","Paso 6: Endpoints CRUD",[65,2272,2274],{"className":239,"code":2273,"language":241,"meta":70,"style":70},"# app/routers/productos.py\nfrom fastapi import APIRouter, Depends, HTTPException, Query\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy import select, func\n\nfrom app.database import get_db\nfrom app.auth import verify_token\nfrom app.models.producto import Producto\nfrom app.schemas.producto import (\n    ProductoCreate, ProductoUpdate, ProductoResponse, PaginatedResponse\n)\n\nrouter = APIRouter(prefix=\"/api/v1/productos\", tags=[\"Productos\"])\n\n\n@router.get(\"\", response_model=PaginatedResponse)\nasync def listar_productos(\n    page: int = Query(1, ge=1),\n    per_page: int = Query(20, ge=1, le=100),\n    activo: bool | None = None,\n    buscar: str | None = None,\n    db: AsyncSession = Depends(get_db),\n    _: dict = Depends(verify_token),\n):\n    \"\"\"Listar productos con paginación, filtros y búsqueda.\"\"\"\n    query = select(Producto)\n\n    if activo is not None:\n        query = query.where(Producto.activo == activo)\n    if buscar:\n        query = query.where(Producto.nombre.ilike(f\"%{buscar}%\"))\n\n    # Total\n    count_query = select(func.count()).select_from(query.subquery())\n    total = (await db.execute(count_query)).scalar() or 0\n\n    # Paginación\n    query = query.offset((page - 1) * per_page).limit(per_page)\n    result = await db.execute(query)\n    items = result.scalars().all()\n\n    return PaginatedResponse(\n        items=items, total=total, page=page,\n        per_page=per_page, pages=-(-total // per_page),\n    )\n\n\n@router.get(\"/{sku}\", response_model=ProductoResponse)\nasync def obtener_producto(\n    sku: str,\n    db: AsyncSession = Depends(get_db),\n    _: dict = Depends(verify_token),\n):\n    \"\"\"Obtener un producto por SKU.\"\"\"\n    result = await db.execute(select(Producto).where(Producto.sku == sku))\n    producto = result.scalar_one_or_none()\n    if not producto:\n        raise HTTPException(status_code=404, detail=f\"Producto {sku} no encontrado\")\n    return producto\n\n\n@router.post(\"\", response_model=ProductoResponse, status_code=201)\nasync def crear_producto(\n    data: ProductoCreate,\n    db: AsyncSession = Depends(get_db),\n    _: dict = Depends(verify_token),\n):\n    \"\"\"Crear un nuevo producto.\"\"\"\n    # Verificar SKU único\n    existing = await db.execute(select(Producto).where(Producto.sku == data.sku))\n    if existing.scalar_one_or_none():\n        raise HTTPException(status_code=409, detail=f\"SKU {data.sku} ya existe\")\n\n    producto = Producto(**data.model_dump())\n    db.add(producto)\n    await db.flush()\n    await db.refresh(producto)\n    return producto\n\n\n@router.patch(\"/{sku}\", response_model=ProductoResponse)\nasync def actualizar_producto(\n    sku: str,\n    data: ProductoUpdate,\n    db: AsyncSession = Depends(get_db),\n    _: dict = Depends(verify_token),\n):\n    \"\"\"Actualizar campos de un producto existente.\"\"\"\n    result = await db.execute(select(Producto).where(Producto.sku == sku))\n    producto = result.scalar_one_or_none()\n    if not producto:\n        raise HTTPException(status_code=404, detail=f\"Producto {sku} no encontrado\")\n\n    update_data = data.model_dump(exclude_unset=True)\n    for field, value in update_data.items():\n        setattr(producto, field, value)\n\n    await db.flush()\n    await db.refresh(producto)\n    return producto\n\n\n@router.delete(\"/{sku}\", status_code=204)\nasync def eliminar_producto(\n    sku: str,\n    db: AsyncSession = Depends(get_db),\n    _: dict = Depends(verify_token),\n):\n    \"\"\"Eliminar un producto por SKU.\"\"\"\n    result = await db.execute(select(Producto).where(Producto.sku == sku))\n    producto = result.scalar_one_or_none()\n    if not producto:\n        raise HTTPException(status_code=404, detail=f\"Producto {sku} no encontrado\")\n\n    await db.delete(producto)\n",[72,2275,2276,2281,2305,2324,2339,2343,2358,2374,2395,2415,2433,2437,2441,2482,2486,2490,2521,2532,2560,2597,2615,2634,2655,2675,2679,2688,2704,2708,2726,2758,2767,2815,2819,2824,2860,2900,2904,2910,2957,2980,3004,3009,3018,3051,3083,3089,3094,3099,3132,3144,3155,3174,3193,3198,3208,3252,3269,3281,3321,3329,3334,3339,3375,3387,3399,3418,3437,3442,3452,3458,3504,3518,3560,3565,3588,3605,3620,3638,3645,3650,3655,3687,3699,3710,3721,3740,3759,3764,3774,3815,3830,3841,3876,3881,3906,3932,3953,3958,3971,3988,3995,4000,4005,4038,4050,4061,4080,4099,4104,4114,4155,4170,4181,4216,4221],{"__ignoreMap":70},[75,2277,2278],{"class":77,"line":78},[75,2279,2280],{"class":89},"# app/routers/productos.py\n",[75,2282,2283,2285,2287,2289,2292,2294,2296,2298,2300,2302],{"class":77,"line":93},[75,2284,254],{"class":253},[75,2286,1862],{"class":257},[75,2288,261],{"class":253},[75,2290,2291],{"class":257}," APIRouter",[75,2293,499],{"class":133},[75,2295,1867],{"class":257},[75,2297,499],{"class":133},[75,2299,1872],{"class":257},[75,2301,499],{"class":133},[75,2303,2304],{"class":257}," Query\n",[75,2306,2307,2309,2311,2313,2315,2317,2319,2321],{"class":77,"line":157},[75,2308,254],{"class":253},[75,2310,481],{"class":257},[75,2312,63],{"class":133},[75,2314,486],{"class":257},[75,2316,63],{"class":133},[75,2318,491],{"class":257},[75,2320,261],{"class":253},[75,2322,2323],{"class":257}," AsyncSession\n",[75,2325,2326,2328,2330,2332,2335,2337],{"class":77,"line":166},[75,2327,254],{"class":253},[75,2329,794],{"class":257},[75,2331,261],{"class":253},[75,2333,2334],{"class":257}," select",[75,2336,499],{"class":133},[75,2338,819],{"class":257},[75,2340,2341],{"class":77,"line":173},[75,2342,170],{"emptyLinePlaceholder":169},[75,2344,2345,2347,2349,2351,2353,2355],{"class":77,"line":198},[75,2346,254],{"class":253},[75,2348,534],{"class":257},[75,2350,63],{"class":133},[75,2352,850],{"class":257},[75,2354,261],{"class":253},[75,2356,2357],{"class":257}," get_db\n",[75,2359,2360,2362,2364,2366,2369,2371],{"class":77,"line":332},[75,2361,254],{"class":253},[75,2363,534],{"class":257},[75,2365,63],{"class":133},[75,2367,2368],{"class":257},"auth ",[75,2370,261],{"class":253},[75,2372,2373],{"class":257}," verify_token\n",[75,2375,2376,2378,2380,2382,2385,2387,2390,2392],{"class":77,"line":351},[75,2377,254],{"class":253},[75,2379,534],{"class":257},[75,2381,63],{"class":133},[75,2383,2384],{"class":257},"models",[75,2386,63],{"class":133},[75,2388,2389],{"class":257},"producto ",[75,2391,261],{"class":253},[75,2393,2394],{"class":257}," Producto\n",[75,2396,2397,2399,2401,2403,2406,2408,2410,2412],{"class":77,"line":368},[75,2398,254],{"class":253},[75,2400,534],{"class":257},[75,2402,63],{"class":133},[75,2404,2405],{"class":257},"schemas",[75,2407,63],{"class":133},[75,2409,2389],{"class":257},[75,2411,261],{"class":253},[75,2413,2414],{"class":133}," (\n",[75,2416,2417,2420,2422,2424,2426,2428,2430],{"class":77,"line":373},[75,2418,2419],{"class":257},"    ProductoCreate",[75,2421,499],{"class":133},[75,2423,1523],{"class":257},[75,2425,499],{"class":133},[75,2427,1659],{"class":257},[75,2429,499],{"class":133},[75,2431,2432],{"class":257}," PaginatedResponse\n",[75,2434,2435],{"class":77,"line":385},[75,2436,583],{"class":133},[75,2438,2439],{"class":77,"line":401},[75,2440,170],{"emptyLinePlaceholder":169},[75,2442,2443,2446,2448,2450,2452,2455,2457,2459,2462,2464,2466,2469,2471,2473,2475,2478,2480],{"class":77,"line":406},[75,2444,2445],{"class":257},"router ",[75,2447,391],{"class":228},[75,2449,2291],{"class":414},[75,2451,280],{"class":133},[75,2453,2454],{"class":573},"prefix",[75,2456,391],{"class":228},[75,2458,1347],{"class":304},[75,2460,2461],{"class":129},"/api/v1/productos",[75,2463,1347],{"class":304},[75,2465,499],{"class":133},[75,2467,2468],{"class":573}," tags",[75,2470,391],{"class":228},[75,2472,903],{"class":133},[75,2474,1347],{"class":304},[75,2476,2477],{"class":129},"Productos",[75,2479,1347],{"class":304},[75,2481,1355],{"class":133},[75,2483,2484],{"class":77,"line":664},[75,2485,170],{"emptyLinePlaceholder":169},[75,2487,2488],{"class":77,"line":678},[75,2489,170],{"emptyLinePlaceholder":169},[75,2491,2492,2496,2499,2501,2504,2506,2509,2511,2514,2516,2519],{"class":77,"line":701},[75,2493,2495],{"class":2494},"stp6e","@",[75,2497,2498],{"class":657},"router",[75,2500,63],{"class":2494},[75,2502,2503],{"class":657},"get",[75,2505,280],{"class":133},[75,2507,2508],{"class":304},"\"\"",[75,2510,499],{"class":133},[75,2512,2513],{"class":573}," response_model",[75,2515,391],{"class":228},[75,2517,2518],{"class":414},"PaginatedResponse",[75,2520,583],{"class":133},[75,2522,2523,2525,2527,2530],{"class":77,"line":709},[75,2524,651],{"class":273},[75,2526,654],{"class":273},[75,2528,2529],{"class":657}," listar_productos",[75,2531,2226],{"class":133},[75,2533,2534,2536,2538,2540,2542,2545,2547,2549,2551,2553,2555,2557],{"class":77,"line":718},[75,2535,1771],{"class":1943},[75,2537,294],{"class":133},[75,2539,359],{"class":297},[75,2541,301],{"class":228},[75,2543,2544],{"class":414}," Query",[75,2546,280],{"class":133},[75,2548,1326],{"class":364},[75,2550,499],{"class":133},[75,2552,1470],{"class":573},[75,2554,391],{"class":228},[75,2556,1326],{"class":364},[75,2558,2559],{"class":133},"),\n",[75,2561,2562,2564,2566,2568,2570,2572,2574,2577,2579,2581,2583,2585,2587,2590,2592,2595],{"class":77,"line":733},[75,2563,1781],{"class":1943},[75,2565,294],{"class":133},[75,2567,359],{"class":297},[75,2569,301],{"class":228},[75,2571,2544],{"class":414},[75,2573,280],{"class":133},[75,2575,2576],{"class":364},"20",[75,2578,499],{"class":133},[75,2580,1470],{"class":573},[75,2582,391],{"class":228},[75,2584,1326],{"class":364},[75,2586,499],{"class":133},[75,2588,2589],{"class":573}," le",[75,2591,391],{"class":228},[75,2593,2594],{"class":364},"100",[75,2596,2559],{"class":133},[75,2598,2599,2601,2603,2605,2607,2609,2611,2613],{"class":77,"line":744},[75,2600,1117],{"class":1943},[75,2602,294],{"class":133},[75,2604,1485],{"class":297},[75,2606,1024],{"class":228},[75,2608,1027],{"class":579},[75,2610,301],{"class":228},[75,2612,1027],{"class":579},[75,2614,2245],{"class":133},[75,2616,2617,2620,2622,2624,2626,2628,2630,2632],{"class":77,"line":758},[75,2618,2619],{"class":1943},"    buscar",[75,2621,294],{"class":133},[75,2623,298],{"class":297},[75,2625,1024],{"class":228},[75,2627,1027],{"class":579},[75,2629,301],{"class":228},[75,2631,1027],{"class":579},[75,2633,2245],{"class":133},[75,2635,2636,2639,2641,2644,2646,2648,2650,2653],{"class":77,"line":1649},[75,2637,2638],{"class":1943},"    db",[75,2640,294],{"class":133},[75,2642,2643],{"class":257}," AsyncSession ",[75,2645,391],{"class":228},[75,2647,1867],{"class":414},[75,2649,280],{"class":133},[75,2651,2652],{"class":414},"get_db",[75,2654,2559],{"class":133},[75,2656,2657,2660,2662,2664,2666,2668,2670,2673],{"class":77,"line":1654},[75,2658,2659],{"class":1943},"    _",[75,2661,294],{"class":133},[75,2663,1949],{"class":297},[75,2665,301],{"class":228},[75,2667,1867],{"class":414},[75,2669,280],{"class":133},[75,2671,2672],{"class":414},"verify_token",[75,2674,2559],{"class":133},[75,2676,2677],{"class":77,"line":1668},[75,2678,286],{"class":133},[75,2680,2681,2683,2686],{"class":77,"line":1678},[75,2682,668],{"class":667},[75,2684,2685],{"class":671},"Listar productos con paginación, filtros y búsqueda.",[75,2687,675],{"class":667},[75,2689,2690,2693,2695,2697,2699,2702],{"class":77,"line":1687},[75,2691,2692],{"class":257},"    query ",[75,2694,391],{"class":228},[75,2696,2334],{"class":414},[75,2698,280],{"class":133},[75,2700,2701],{"class":414},"Producto",[75,2703,583],{"class":133},[75,2705,2706],{"class":77,"line":1696},[75,2707,170],{"emptyLinePlaceholder":169},[75,2709,2710,2713,2716,2719,2722,2724],{"class":77,"line":1701},[75,2711,2712],{"class":253},"    if",[75,2714,2715],{"class":257}," activo ",[75,2717,2718],{"class":228},"is",[75,2720,2721],{"class":228}," not",[75,2723,1027],{"class":579},[75,2725,382],{"class":133},[75,2727,2728,2731,2733,2736,2738,2741,2743,2745,2747,2750,2753,2756],{"class":77,"line":1710},[75,2729,2730],{"class":257},"        query ",[75,2732,391],{"class":228},[75,2734,2735],{"class":257}," query",[75,2737,63],{"class":133},[75,2739,2740],{"class":414},"where",[75,2742,280],{"class":133},[75,2744,2701],{"class":414},[75,2746,63],{"class":133},[75,2748,2749],{"class":567},"activo",[75,2751,2752],{"class":228}," ==",[75,2754,2755],{"class":414}," activo",[75,2757,583],{"class":133},[75,2759,2760,2762,2765],{"class":77,"line":1720},[75,2761,2712],{"class":253},[75,2763,2764],{"class":257}," buscar",[75,2766,382],{"class":133},[75,2768,2769,2771,2773,2775,2777,2779,2781,2783,2785,2788,2790,2793,2795,2798,2801,2804,2807,2810,2813],{"class":77,"line":1725},[75,2770,2730],{"class":257},[75,2772,391],{"class":228},[75,2774,2735],{"class":257},[75,2776,63],{"class":133},[75,2778,2740],{"class":414},[75,2780,280],{"class":133},[75,2782,2701],{"class":414},[75,2784,63],{"class":133},[75,2786,2787],{"class":567},"nombre",[75,2789,63],{"class":133},[75,2791,2792],{"class":414},"ilike",[75,2794,280],{"class":133},[75,2796,2797],{"class":273},"f",[75,2799,2800],{"class":129},"\"%",[75,2802,2803],{"class":364},"{",[75,2805,2806],{"class":414},"buscar",[75,2808,2809],{"class":364},"}",[75,2811,2812],{"class":129},"%\"",[75,2814,1008],{"class":133},[75,2816,2817],{"class":77,"line":1739},[75,2818,170],{"emptyLinePlaceholder":169},[75,2820,2821],{"class":77,"line":1758},[75,2822,2823],{"class":89},"    # Total\n",[75,2825,2826,2829,2831,2833,2835,2837,2839,2842,2845,2848,2850,2853,2855,2858],{"class":77,"line":1768},[75,2827,2828],{"class":257},"    count_query ",[75,2830,391],{"class":228},[75,2832,2334],{"class":414},[75,2834,280],{"class":133},[75,2836,1182],{"class":414},[75,2838,63],{"class":133},[75,2840,2841],{"class":414},"count",[75,2843,2844],{"class":133},"()).",[75,2846,2847],{"class":414},"select_from",[75,2849,280],{"class":133},[75,2851,2852],{"class":414},"query",[75,2854,63],{"class":133},[75,2856,2857],{"class":414},"subquery",[75,2859,1190],{"class":133},[75,2861,2862,2865,2867,2870,2873,2876,2878,2881,2883,2886,2889,2892,2894,2897],{"class":77,"line":1778},[75,2863,2864],{"class":257},"    total ",[75,2866,391],{"class":228},[75,2868,2869],{"class":133}," (",[75,2871,2872],{"class":253},"await",[75,2874,2875],{"class":257}," db",[75,2877,63],{"class":133},[75,2879,2880],{"class":414},"execute",[75,2882,280],{"class":133},[75,2884,2885],{"class":414},"count_query",[75,2887,2888],{"class":133},")).",[75,2890,2891],{"class":414},"scalar",[75,2893,690],{"class":133},[75,2895,2896],{"class":228}," or",[75,2898,2899],{"class":364}," 0\n",[75,2901,2902],{"class":77,"line":1788},[75,2903,170],{"emptyLinePlaceholder":169},[75,2905,2907],{"class":77,"line":2906},37,[75,2908,2909],{"class":89},"    # Paginación\n",[75,2911,2913,2915,2917,2919,2921,2924,2927,2930,2933,2936,2938,2941,2944,2947,2950,2952,2955],{"class":77,"line":2912},38,[75,2914,2692],{"class":257},[75,2916,391],{"class":228},[75,2918,2735],{"class":257},[75,2920,63],{"class":133},[75,2922,2923],{"class":414},"offset",[75,2925,2926],{"class":133},"((",[75,2928,2929],{"class":414},"page ",[75,2931,2932],{"class":228},"-",[75,2934,2935],{"class":364}," 1",[75,2937,1952],{"class":133},[75,2939,2940],{"class":228}," *",[75,2942,2943],{"class":414}," per_page",[75,2945,2946],{"class":133},").",[75,2948,2949],{"class":414},"limit",[75,2951,280],{"class":133},[75,2953,2954],{"class":414},"per_page",[75,2956,583],{"class":133},[75,2958,2960,2963,2965,2968,2970,2972,2974,2976,2978],{"class":77,"line":2959},39,[75,2961,2962],{"class":257},"    result ",[75,2964,391],{"class":228},[75,2966,2967],{"class":253}," await",[75,2969,2875],{"class":257},[75,2971,63],{"class":133},[75,2973,2880],{"class":414},[75,2975,280],{"class":133},[75,2977,2852],{"class":414},[75,2979,583],{"class":133},[75,2981,2983,2986,2988,2991,2993,2996,2999,3002],{"class":77,"line":2982},40,[75,2984,2985],{"class":257},"    items ",[75,2987,391],{"class":228},[75,2989,2990],{"class":257}," result",[75,2992,63],{"class":133},[75,2994,2995],{"class":414},"scalars",[75,2997,2998],{"class":133},"().",[75,3000,3001],{"class":414},"all",[75,3003,417],{"class":133},[75,3005,3007],{"class":77,"line":3006},41,[75,3008,170],{"emptyLinePlaceholder":169},[75,3010,3012,3014,3016],{"class":77,"line":3011},42,[75,3013,2055],{"class":253},[75,3015,1730],{"class":414},[75,3017,2226],{"class":133},[75,3019,3021,3024,3026,3029,3031,3034,3036,3039,3041,3044,3046,3049],{"class":77,"line":3020},43,[75,3022,3023],{"class":573},"        items",[75,3025,391],{"class":228},[75,3027,3028],{"class":414},"items",[75,3030,499],{"class":133},[75,3032,3033],{"class":573}," total",[75,3035,391],{"class":228},[75,3037,3038],{"class":414},"total",[75,3040,499],{"class":133},[75,3042,3043],{"class":573}," page",[75,3045,391],{"class":228},[75,3047,3048],{"class":414},"page",[75,3050,2245],{"class":133},[75,3052,3054,3057,3059,3061,3063,3066,3069,3071,3073,3076,3079,3081],{"class":77,"line":3053},44,[75,3055,3056],{"class":573},"        per_page",[75,3058,391],{"class":228},[75,3060,2954],{"class":414},[75,3062,499],{"class":133},[75,3064,3065],{"class":573}," pages",[75,3067,3068],{"class":228},"=-",[75,3070,280],{"class":133},[75,3072,2932],{"class":228},[75,3074,3075],{"class":414},"total ",[75,3077,3078],{"class":228},"//",[75,3080,2943],{"class":414},[75,3082,2559],{"class":133},[75,3084,3086],{"class":77,"line":3085},45,[75,3087,3088],{"class":133},"    )\n",[75,3090,3092],{"class":77,"line":3091},46,[75,3093,170],{"emptyLinePlaceholder":169},[75,3095,3097],{"class":77,"line":3096},47,[75,3098,170],{"emptyLinePlaceholder":169},[75,3100,3102,3104,3106,3108,3110,3112,3114,3117,3120,3122,3124,3126,3128,3130],{"class":77,"line":3101},48,[75,3103,2495],{"class":2494},[75,3105,2498],{"class":657},[75,3107,63],{"class":2494},[75,3109,2503],{"class":657},[75,3111,280],{"class":133},[75,3113,1347],{"class":304},[75,3115,3116],{"class":129},"/",[75,3118,3119],{"class":364},"{sku}",[75,3121,1347],{"class":304},[75,3123,499],{"class":133},[75,3125,2513],{"class":573},[75,3127,391],{"class":228},[75,3129,1752],{"class":414},[75,3131,583],{"class":133},[75,3133,3135,3137,3139,3142],{"class":77,"line":3134},49,[75,3136,651],{"class":273},[75,3138,654],{"class":273},[75,3140,3141],{"class":657}," obtener_producto",[75,3143,2226],{"class":133},[75,3145,3147,3149,3151,3153],{"class":77,"line":3146},50,[75,3148,931],{"class":1943},[75,3150,294],{"class":133},[75,3152,298],{"class":297},[75,3154,2245],{"class":133},[75,3156,3158,3160,3162,3164,3166,3168,3170,3172],{"class":77,"line":3157},51,[75,3159,2638],{"class":1943},[75,3161,294],{"class":133},[75,3163,2643],{"class":257},[75,3165,391],{"class":228},[75,3167,1867],{"class":414},[75,3169,280],{"class":133},[75,3171,2652],{"class":414},[75,3173,2559],{"class":133},[75,3175,3177,3179,3181,3183,3185,3187,3189,3191],{"class":77,"line":3176},52,[75,3178,2659],{"class":1943},[75,3180,294],{"class":133},[75,3182,1949],{"class":297},[75,3184,301],{"class":228},[75,3186,1867],{"class":414},[75,3188,280],{"class":133},[75,3190,2672],{"class":414},[75,3192,2559],{"class":133},[75,3194,3196],{"class":77,"line":3195},53,[75,3197,286],{"class":133},[75,3199,3201,3203,3206],{"class":77,"line":3200},54,[75,3202,668],{"class":667},[75,3204,3205],{"class":671},"Obtener un producto por SKU.",[75,3207,675],{"class":667},[75,3209,3211,3213,3215,3217,3219,3221,3223,3225,3228,3230,3232,3234,3236,3238,3240,3242,3245,3247,3250],{"class":77,"line":3210},55,[75,3212,2962],{"class":257},[75,3214,391],{"class":228},[75,3216,2967],{"class":253},[75,3218,2875],{"class":257},[75,3220,63],{"class":133},[75,3222,2880],{"class":414},[75,3224,280],{"class":133},[75,3226,3227],{"class":414},"select",[75,3229,280],{"class":133},[75,3231,2701],{"class":414},[75,3233,2946],{"class":133},[75,3235,2740],{"class":414},[75,3237,280],{"class":133},[75,3239,2701],{"class":414},[75,3241,63],{"class":133},[75,3243,3244],{"class":567},"sku",[75,3246,2752],{"class":228},[75,3248,3249],{"class":414}," sku",[75,3251,1008],{"class":133},[75,3253,3255,3258,3260,3262,3264,3267],{"class":77,"line":3254},56,[75,3256,3257],{"class":257},"    producto ",[75,3259,391],{"class":228},[75,3261,2990],{"class":257},[75,3263,63],{"class":133},[75,3265,3266],{"class":414},"scalar_one_or_none",[75,3268,417],{"class":133},[75,3270,3272,3274,3276,3279],{"class":77,"line":3271},57,[75,3273,2712],{"class":253},[75,3275,2721],{"class":228},[75,3277,3278],{"class":257}," producto",[75,3280,382],{"class":133},[75,3282,3284,3286,3288,3290,3293,3295,3298,3300,3303,3305,3307,3310,3312,3314,3316,3319],{"class":77,"line":3283},58,[75,3285,2221],{"class":253},[75,3287,1872],{"class":414},[75,3289,280],{"class":133},[75,3291,3292],{"class":573},"status_code",[75,3294,391],{"class":228},[75,3296,3297],{"class":364},"404",[75,3299,499],{"class":133},[75,3301,3302],{"class":573}," detail",[75,3304,391],{"class":228},[75,3306,2797],{"class":273},[75,3308,3309],{"class":129},"\"Producto ",[75,3311,2803],{"class":364},[75,3313,3244],{"class":414},[75,3315,2809],{"class":364},[75,3317,3318],{"class":129}," no encontrado\"",[75,3320,583],{"class":133},[75,3322,3324,3326],{"class":77,"line":3323},59,[75,3325,2055],{"class":253},[75,3327,3328],{"class":257}," producto\n",[75,3330,3332],{"class":77,"line":3331},60,[75,3333,170],{"emptyLinePlaceholder":169},[75,3335,3337],{"class":77,"line":3336},61,[75,3338,170],{"emptyLinePlaceholder":169},[75,3340,3342,3344,3346,3348,3351,3353,3355,3357,3359,3361,3363,3365,3368,3370,3373],{"class":77,"line":3341},62,[75,3343,2495],{"class":2494},[75,3345,2498],{"class":657},[75,3347,63],{"class":2494},[75,3349,3350],{"class":657},"post",[75,3352,280],{"class":133},[75,3354,2508],{"class":304},[75,3356,499],{"class":133},[75,3358,2513],{"class":573},[75,3360,391],{"class":228},[75,3362,1752],{"class":414},[75,3364,499],{"class":133},[75,3366,3367],{"class":573}," status_code",[75,3369,391],{"class":228},[75,3371,3372],{"class":364},"201",[75,3374,583],{"class":133},[75,3376,3378,3380,3382,3385],{"class":77,"line":3377},63,[75,3379,651],{"class":273},[75,3381,654],{"class":273},[75,3383,3384],{"class":657}," crear_producto",[75,3386,2226],{"class":133},[75,3388,3390,3393,3395,3397],{"class":77,"line":3389},64,[75,3391,3392],{"class":1943},"    data",[75,3394,294],{"class":133},[75,3396,1501],{"class":257},[75,3398,2245],{"class":133},[75,3400,3402,3404,3406,3408,3410,3412,3414,3416],{"class":77,"line":3401},65,[75,3403,2638],{"class":1943},[75,3405,294],{"class":133},[75,3407,2643],{"class":257},[75,3409,391],{"class":228},[75,3411,1867],{"class":414},[75,3413,280],{"class":133},[75,3415,2652],{"class":414},[75,3417,2559],{"class":133},[75,3419,3421,3423,3425,3427,3429,3431,3433,3435],{"class":77,"line":3420},66,[75,3422,2659],{"class":1943},[75,3424,294],{"class":133},[75,3426,1949],{"class":297},[75,3428,301],{"class":228},[75,3430,1867],{"class":414},[75,3432,280],{"class":133},[75,3434,2672],{"class":414},[75,3436,2559],{"class":133},[75,3438,3440],{"class":77,"line":3439},67,[75,3441,286],{"class":133},[75,3443,3445,3447,3450],{"class":77,"line":3444},68,[75,3446,668],{"class":667},[75,3448,3449],{"class":671},"Crear un nuevo producto.",[75,3451,675],{"class":667},[75,3453,3455],{"class":77,"line":3454},69,[75,3456,3457],{"class":89},"    # Verificar SKU único\n",[75,3459,3461,3464,3466,3468,3470,3472,3474,3476,3478,3480,3482,3484,3486,3488,3490,3492,3494,3496,3498,3500,3502],{"class":77,"line":3460},70,[75,3462,3463],{"class":257},"    existing ",[75,3465,391],{"class":228},[75,3467,2967],{"class":253},[75,3469,2875],{"class":257},[75,3471,63],{"class":133},[75,3473,2880],{"class":414},[75,3475,280],{"class":133},[75,3477,3227],{"class":414},[75,3479,280],{"class":133},[75,3481,2701],{"class":414},[75,3483,2946],{"class":133},[75,3485,2740],{"class":414},[75,3487,280],{"class":133},[75,3489,2701],{"class":414},[75,3491,63],{"class":133},[75,3493,3244],{"class":567},[75,3495,2752],{"class":228},[75,3497,1969],{"class":414},[75,3499,63],{"class":133},[75,3501,3244],{"class":567},[75,3503,1008],{"class":133},[75,3505,3507,3509,3512,3514,3516],{"class":77,"line":3506},71,[75,3508,2712],{"class":253},[75,3510,3511],{"class":257}," existing",[75,3513,63],{"class":133},[75,3515,3266],{"class":414},[75,3517,661],{"class":133},[75,3519,3521,3523,3525,3527,3529,3531,3534,3536,3538,3540,3542,3545,3547,3549,3551,3553,3555,3558],{"class":77,"line":3520},72,[75,3522,2221],{"class":253},[75,3524,1872],{"class":414},[75,3526,280],{"class":133},[75,3528,3292],{"class":573},[75,3530,391],{"class":228},[75,3532,3533],{"class":364},"409",[75,3535,499],{"class":133},[75,3537,3302],{"class":573},[75,3539,391],{"class":228},[75,3541,2797],{"class":273},[75,3543,3544],{"class":129},"\"SKU ",[75,3546,2803],{"class":364},[75,3548,1944],{"class":414},[75,3550,63],{"class":133},[75,3552,3244],{"class":567},[75,3554,2809],{"class":364},[75,3556,3557],{"class":129}," ya existe\"",[75,3559,583],{"class":133},[75,3561,3563],{"class":77,"line":3562},73,[75,3564,170],{"emptyLinePlaceholder":169},[75,3566,3568,3570,3572,3574,3576,3579,3581,3583,3586],{"class":77,"line":3567},74,[75,3569,3257],{"class":257},[75,3571,391],{"class":228},[75,3573,866],{"class":414},[75,3575,280],{"class":133},[75,3577,3578],{"class":228},"**",[75,3580,1944],{"class":414},[75,3582,63],{"class":133},[75,3584,3585],{"class":414},"model_dump",[75,3587,1190],{"class":133},[75,3589,3591,3593,3595,3598,3600,3603],{"class":77,"line":3590},75,[75,3592,2638],{"class":257},[75,3594,63],{"class":133},[75,3596,3597],{"class":414},"add",[75,3599,280],{"class":133},[75,3601,3602],{"class":414},"producto",[75,3604,583],{"class":133},[75,3606,3608,3611,3613,3615,3618],{"class":77,"line":3607},76,[75,3609,3610],{"class":253},"    await",[75,3612,2875],{"class":257},[75,3614,63],{"class":133},[75,3616,3617],{"class":414},"flush",[75,3619,417],{"class":133},[75,3621,3623,3625,3627,3629,3632,3634,3636],{"class":77,"line":3622},77,[75,3624,3610],{"class":253},[75,3626,2875],{"class":257},[75,3628,63],{"class":133},[75,3630,3631],{"class":414},"refresh",[75,3633,280],{"class":133},[75,3635,3602],{"class":414},[75,3637,583],{"class":133},[75,3639,3641,3643],{"class":77,"line":3640},78,[75,3642,2055],{"class":253},[75,3644,3328],{"class":257},[75,3646,3648],{"class":77,"line":3647},79,[75,3649,170],{"emptyLinePlaceholder":169},[75,3651,3653],{"class":77,"line":3652},80,[75,3654,170],{"emptyLinePlaceholder":169},[75,3656,3658,3660,3662,3664,3667,3669,3671,3673,3675,3677,3679,3681,3683,3685],{"class":77,"line":3657},81,[75,3659,2495],{"class":2494},[75,3661,2498],{"class":657},[75,3663,63],{"class":2494},[75,3665,3666],{"class":657},"patch",[75,3668,280],{"class":133},[75,3670,1347],{"class":304},[75,3672,3116],{"class":129},[75,3674,3119],{"class":364},[75,3676,1347],{"class":304},[75,3678,499],{"class":133},[75,3680,2513],{"class":573},[75,3682,391],{"class":228},[75,3684,1752],{"class":414},[75,3686,583],{"class":133},[75,3688,3690,3692,3694,3697],{"class":77,"line":3689},82,[75,3691,651],{"class":273},[75,3693,654],{"class":273},[75,3695,3696],{"class":657}," actualizar_producto",[75,3698,2226],{"class":133},[75,3700,3702,3704,3706,3708],{"class":77,"line":3701},83,[75,3703,931],{"class":1943},[75,3705,294],{"class":133},[75,3707,298],{"class":297},[75,3709,2245],{"class":133},[75,3711,3713,3715,3717,3719],{"class":77,"line":3712},84,[75,3714,3392],{"class":1943},[75,3716,294],{"class":133},[75,3718,1523],{"class":257},[75,3720,2245],{"class":133},[75,3722,3724,3726,3728,3730,3732,3734,3736,3738],{"class":77,"line":3723},85,[75,3725,2638],{"class":1943},[75,3727,294],{"class":133},[75,3729,2643],{"class":257},[75,3731,391],{"class":228},[75,3733,1867],{"class":414},[75,3735,280],{"class":133},[75,3737,2652],{"class":414},[75,3739,2559],{"class":133},[75,3741,3743,3745,3747,3749,3751,3753,3755,3757],{"class":77,"line":3742},86,[75,3744,2659],{"class":1943},[75,3746,294],{"class":133},[75,3748,1949],{"class":297},[75,3750,301],{"class":228},[75,3752,1867],{"class":414},[75,3754,280],{"class":133},[75,3756,2672],{"class":414},[75,3758,2559],{"class":133},[75,3760,3762],{"class":77,"line":3761},87,[75,3763,286],{"class":133},[75,3765,3767,3769,3772],{"class":77,"line":3766},88,[75,3768,668],{"class":667},[75,3770,3771],{"class":671},"Actualizar campos de un producto existente.",[75,3773,675],{"class":667},[75,3775,3777,3779,3781,3783,3785,3787,3789,3791,3793,3795,3797,3799,3801,3803,3805,3807,3809,3811,3813],{"class":77,"line":3776},89,[75,3778,2962],{"class":257},[75,3780,391],{"class":228},[75,3782,2967],{"class":253},[75,3784,2875],{"class":257},[75,3786,63],{"class":133},[75,3788,2880],{"class":414},[75,3790,280],{"class":133},[75,3792,3227],{"class":414},[75,3794,280],{"class":133},[75,3796,2701],{"class":414},[75,3798,2946],{"class":133},[75,3800,2740],{"class":414},[75,3802,280],{"class":133},[75,3804,2701],{"class":414},[75,3806,63],{"class":133},[75,3808,3244],{"class":567},[75,3810,2752],{"class":228},[75,3812,3249],{"class":414},[75,3814,1008],{"class":133},[75,3816,3818,3820,3822,3824,3826,3828],{"class":77,"line":3817},90,[75,3819,3257],{"class":257},[75,3821,391],{"class":228},[75,3823,2990],{"class":257},[75,3825,63],{"class":133},[75,3827,3266],{"class":414},[75,3829,417],{"class":133},[75,3831,3833,3835,3837,3839],{"class":77,"line":3832},91,[75,3834,2712],{"class":253},[75,3836,2721],{"class":228},[75,3838,3278],{"class":257},[75,3840,382],{"class":133},[75,3842,3844,3846,3848,3850,3852,3854,3856,3858,3860,3862,3864,3866,3868,3870,3872,3874],{"class":77,"line":3843},92,[75,3845,2221],{"class":253},[75,3847,1872],{"class":414},[75,3849,280],{"class":133},[75,3851,3292],{"class":573},[75,3853,391],{"class":228},[75,3855,3297],{"class":364},[75,3857,499],{"class":133},[75,3859,3302],{"class":573},[75,3861,391],{"class":228},[75,3863,2797],{"class":273},[75,3865,3309],{"class":129},[75,3867,2803],{"class":364},[75,3869,3244],{"class":414},[75,3871,2809],{"class":364},[75,3873,3318],{"class":129},[75,3875,583],{"class":133},[75,3877,3879],{"class":77,"line":3878},93,[75,3880,170],{"emptyLinePlaceholder":169},[75,3882,3884,3887,3889,3891,3893,3895,3897,3900,3902,3904],{"class":77,"line":3883},94,[75,3885,3886],{"class":257},"    update_data ",[75,3888,391],{"class":228},[75,3890,1969],{"class":257},[75,3892,63],{"class":133},[75,3894,3585],{"class":414},[75,3896,280],{"class":133},[75,3898,3899],{"class":573},"exclude_unset",[75,3901,391],{"class":228},[75,3903,924],{"class":579},[75,3905,583],{"class":133},[75,3907,3909,3912,3915,3917,3920,3923,3926,3928,3930],{"class":77,"line":3908},95,[75,3910,3911],{"class":253},"    for",[75,3913,3914],{"class":257}," field",[75,3916,499],{"class":133},[75,3918,3919],{"class":257}," value ",[75,3921,3922],{"class":253},"in",[75,3924,3925],{"class":257}," update_data",[75,3927,63],{"class":133},[75,3929,3028],{"class":414},[75,3931,661],{"class":133},[75,3933,3935,3938,3940,3942,3944,3946,3948,3951],{"class":77,"line":3934},96,[75,3936,3937],{"class":137},"        setattr",[75,3939,280],{"class":133},[75,3941,3602],{"class":414},[75,3943,499],{"class":133},[75,3945,3914],{"class":414},[75,3947,499],{"class":133},[75,3949,3950],{"class":414}," value",[75,3952,583],{"class":133},[75,3954,3956],{"class":77,"line":3955},97,[75,3957,170],{"emptyLinePlaceholder":169},[75,3959,3961,3963,3965,3967,3969],{"class":77,"line":3960},98,[75,3962,3610],{"class":253},[75,3964,2875],{"class":257},[75,3966,63],{"class":133},[75,3968,3617],{"class":414},[75,3970,417],{"class":133},[75,3972,3974,3976,3978,3980,3982,3984,3986],{"class":77,"line":3973},99,[75,3975,3610],{"class":253},[75,3977,2875],{"class":257},[75,3979,63],{"class":133},[75,3981,3631],{"class":414},[75,3983,280],{"class":133},[75,3985,3602],{"class":414},[75,3987,583],{"class":133},[75,3989,3991,3993],{"class":77,"line":3990},100,[75,3992,2055],{"class":253},[75,3994,3328],{"class":257},[75,3996,3998],{"class":77,"line":3997},101,[75,3999,170],{"emptyLinePlaceholder":169},[75,4001,4003],{"class":77,"line":4002},102,[75,4004,170],{"emptyLinePlaceholder":169},[75,4006,4008,4010,4012,4014,4017,4019,4021,4023,4025,4027,4029,4031,4033,4036],{"class":77,"line":4007},103,[75,4009,2495],{"class":2494},[75,4011,2498],{"class":657},[75,4013,63],{"class":2494},[75,4015,4016],{"class":657},"delete",[75,4018,280],{"class":133},[75,4020,1347],{"class":304},[75,4022,3116],{"class":129},[75,4024,3119],{"class":364},[75,4026,1347],{"class":304},[75,4028,499],{"class":133},[75,4030,3367],{"class":573},[75,4032,391],{"class":228},[75,4034,4035],{"class":364},"204",[75,4037,583],{"class":133},[75,4039,4041,4043,4045,4048],{"class":77,"line":4040},104,[75,4042,651],{"class":273},[75,4044,654],{"class":273},[75,4046,4047],{"class":657}," eliminar_producto",[75,4049,2226],{"class":133},[75,4051,4053,4055,4057,4059],{"class":77,"line":4052},105,[75,4054,931],{"class":1943},[75,4056,294],{"class":133},[75,4058,298],{"class":297},[75,4060,2245],{"class":133},[75,4062,4064,4066,4068,4070,4072,4074,4076,4078],{"class":77,"line":4063},106,[75,4065,2638],{"class":1943},[75,4067,294],{"class":133},[75,4069,2643],{"class":257},[75,4071,391],{"class":228},[75,4073,1867],{"class":414},[75,4075,280],{"class":133},[75,4077,2652],{"class":414},[75,4079,2559],{"class":133},[75,4081,4083,4085,4087,4089,4091,4093,4095,4097],{"class":77,"line":4082},107,[75,4084,2659],{"class":1943},[75,4086,294],{"class":133},[75,4088,1949],{"class":297},[75,4090,301],{"class":228},[75,4092,1867],{"class":414},[75,4094,280],{"class":133},[75,4096,2672],{"class":414},[75,4098,2559],{"class":133},[75,4100,4102],{"class":77,"line":4101},108,[75,4103,286],{"class":133},[75,4105,4107,4109,4112],{"class":77,"line":4106},109,[75,4108,668],{"class":667},[75,4110,4111],{"class":671},"Eliminar un producto por SKU.",[75,4113,675],{"class":667},[75,4115,4117,4119,4121,4123,4125,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145,4147,4149,4151,4153],{"class":77,"line":4116},110,[75,4118,2962],{"class":257},[75,4120,391],{"class":228},[75,4122,2967],{"class":253},[75,4124,2875],{"class":257},[75,4126,63],{"class":133},[75,4128,2880],{"class":414},[75,4130,280],{"class":133},[75,4132,3227],{"class":414},[75,4134,280],{"class":133},[75,4136,2701],{"class":414},[75,4138,2946],{"class":133},[75,4140,2740],{"class":414},[75,4142,280],{"class":133},[75,4144,2701],{"class":414},[75,4146,63],{"class":133},[75,4148,3244],{"class":567},[75,4150,2752],{"class":228},[75,4152,3249],{"class":414},[75,4154,1008],{"class":133},[75,4156,4158,4160,4162,4164,4166,4168],{"class":77,"line":4157},111,[75,4159,3257],{"class":257},[75,4161,391],{"class":228},[75,4163,2990],{"class":257},[75,4165,63],{"class":133},[75,4167,3266],{"class":414},[75,4169,417],{"class":133},[75,4171,4173,4175,4177,4179],{"class":77,"line":4172},112,[75,4174,2712],{"class":253},[75,4176,2721],{"class":228},[75,4178,3278],{"class":257},[75,4180,382],{"class":133},[75,4182,4184,4186,4188,4190,4192,4194,4196,4198,4200,4202,4204,4206,4208,4210,4212,4214],{"class":77,"line":4183},113,[75,4185,2221],{"class":253},[75,4187,1872],{"class":414},[75,4189,280],{"class":133},[75,4191,3292],{"class":573},[75,4193,391],{"class":228},[75,4195,3297],{"class":364},[75,4197,499],{"class":133},[75,4199,3302],{"class":573},[75,4201,391],{"class":228},[75,4203,2797],{"class":273},[75,4205,3309],{"class":129},[75,4207,2803],{"class":364},[75,4209,3244],{"class":414},[75,4211,2809],{"class":364},[75,4213,3318],{"class":129},[75,4215,583],{"class":133},[75,4217,4219],{"class":77,"line":4218},114,[75,4220,170],{"emptyLinePlaceholder":169},[75,4222,4224,4226,4228,4230,4232,4234,4236],{"class":77,"line":4223},115,[75,4225,3610],{"class":253},[75,4227,2875],{"class":257},[75,4229,63],{"class":133},[75,4231,4016],{"class":414},[75,4233,280],{"class":133},[75,4235,3602],{"class":414},[75,4237,583],{"class":133},[459,4239],{},[52,4241,4243],{"id":4242},"paso-7-punto-de-entrada-principal","Paso 7: Punto de entrada principal",[65,4245,4247],{"className":239,"code":4246,"language":241,"meta":70,"style":70},"# app/main.py\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom app.config import settings\nfrom app.database import engine, Base\nfrom app.auth import create_access_token\nfrom app.routers import productos\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Crear tablas al arrancar (en producción usa Alembic para migraciones)\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    await engine.dispose()\n\n\napp = FastAPI(\n    title=settings.app_name,\n    description=\"API de integración para sincronización de productos entre sistemas\",\n    version=\"1.0.0\",\n    lifespan=lifespan,\n)\n\n# CORS — ajustar en producción\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"*\"],\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# Routers\napp.include_router(productos.router)\n\n\n@app.get(\"/health\", tags=[\"Sistema\"])\nasync def health_check():\n    \"\"\"Verificar que la API está funcionando.\"\"\"\n    return {\"status\": \"ok\", \"app\": settings.app_name}\n\n\n@app.post(\"/auth/token\", tags=[\"Autenticación\"])\nasync def login(api_key: str):\n    \"\"\"\n    Generar un token JWT.\n    En producción, valida contra una base de datos de usuarios/API keys.\n    \"\"\"\n    # Ejemplo simplificado — en producción valida contra DB\n    if api_key != \"mi-api-key-secreta\":\n        from fastapi import HTTPException\n        raise HTTPException(status_code=401, detail=\"API key inválida\")\n\n    token = create_access_token({\"sub\": \"sistema-ecommerce\", \"role\": \"integration\"})\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n",[72,4248,4249,4254,4266,4277,4298,4302,4316,4335,4350,4366,4370,4374,4381,4402,4407,4429,4457,4462,4475,4479,4483,4494,4510,4526,4542,4554,4558,4562,4567,4578,4585,4604,4621,4638,4642,4646,4651,4670,4674,4678,4714,4725,4734,4775,4779,4783,4819,4839,4844,4849,4854,4858,4863,4882,4894,4924,4928,4974],{"__ignoreMap":70},[75,4250,4251],{"class":77,"line":78},[75,4252,4253],{"class":89},"# app/main.py\n",[75,4255,4256,4258,4261,4263],{"class":77,"line":93},[75,4257,254],{"class":253},[75,4259,4260],{"class":257}," contextlib ",[75,4262,261],{"class":253},[75,4264,4265],{"class":257}," asynccontextmanager\n",[75,4267,4268,4270,4272,4274],{"class":77,"line":157},[75,4269,254],{"class":253},[75,4271,1862],{"class":257},[75,4273,261],{"class":253},[75,4275,4276],{"class":257}," FastAPI\n",[75,4278,4279,4281,4283,4285,4288,4290,4293,4295],{"class":77,"line":166},[75,4280,254],{"class":253},[75,4282,182],{"class":257},[75,4284,63],{"class":133},[75,4286,4287],{"class":257},"middleware",[75,4289,63],{"class":133},[75,4291,4292],{"class":257},"cors ",[75,4294,261],{"class":253},[75,4296,4297],{"class":257}," CORSMiddleware\n",[75,4299,4300],{"class":77,"line":173},[75,4301,170],{"emptyLinePlaceholder":169},[75,4303,4304,4306,4308,4310,4312,4314],{"class":77,"line":198},[75,4305,254],{"class":253},[75,4307,534],{"class":257},[75,4309,63],{"class":133},[75,4311,539],{"class":257},[75,4313,261],{"class":253},[75,4315,544],{"class":257},[75,4317,4318,4320,4322,4324,4326,4328,4331,4333],{"class":77,"line":332},[75,4319,254],{"class":253},[75,4321,534],{"class":257},[75,4323,63],{"class":133},[75,4325,850],{"class":257},[75,4327,261],{"class":253},[75,4329,4330],{"class":257}," engine",[75,4332,499],{"class":133},[75,4334,855],{"class":257},[75,4336,4337,4339,4341,4343,4345,4347],{"class":77,"line":351},[75,4338,254],{"class":253},[75,4340,534],{"class":257},[75,4342,63],{"class":133},[75,4344,2368],{"class":257},[75,4346,261],{"class":253},[75,4348,4349],{"class":257}," create_access_token\n",[75,4351,4352,4354,4356,4358,4361,4363],{"class":77,"line":368},[75,4353,254],{"class":253},[75,4355,534],{"class":257},[75,4357,63],{"class":133},[75,4359,4360],{"class":257},"routers ",[75,4362,261],{"class":253},[75,4364,4365],{"class":257}," productos\n",[75,4367,4368],{"class":77,"line":373},[75,4369,170],{"emptyLinePlaceholder":169},[75,4371,4372],{"class":77,"line":385},[75,4373,170],{"emptyLinePlaceholder":169},[75,4375,4376,4378],{"class":77,"line":401},[75,4377,2495],{"class":2494},[75,4379,4380],{"class":657},"asynccontextmanager\n",[75,4382,4383,4385,4387,4390,4392,4395,4397,4400],{"class":77,"line":406},[75,4384,651],{"class":273},[75,4386,654],{"class":273},[75,4388,4389],{"class":657}," lifespan",[75,4391,280],{"class":133},[75,4393,4394],{"class":1943},"app",[75,4396,294],{"class":133},[75,4398,4399],{"class":257}," FastAPI",[75,4401,286],{"class":133},[75,4403,4404],{"class":77,"line":664},[75,4405,4406],{"class":89},"    # Crear tablas al arrancar (en producción usa Alembic para migraciones)\n",[75,4408,4409,4411,4413,4415,4417,4420,4422,4424,4427],{"class":77,"line":678},[75,4410,681],{"class":253},[75,4412,684],{"class":253},[75,4414,4330],{"class":257},[75,4416,63],{"class":133},[75,4418,4419],{"class":414},"begin",[75,4421,690],{"class":133},[75,4423,693],{"class":253},[75,4425,4426],{"class":257}," conn",[75,4428,382],{"class":133},[75,4430,4431,4434,4436,4438,4441,4443,4445,4447,4450,4452,4455],{"class":77,"line":701},[75,4432,4433],{"class":253},"        await",[75,4435,4426],{"class":257},[75,4437,63],{"class":133},[75,4439,4440],{"class":414},"run_sync",[75,4442,280],{"class":133},[75,4444,871],{"class":414},[75,4446,63],{"class":133},[75,4448,4449],{"class":567},"metadata",[75,4451,63],{"class":133},[75,4453,4454],{"class":567},"create_all",[75,4456,583],{"class":133},[75,4458,4459],{"class":77,"line":709},[75,4460,4461],{"class":253},"    yield\n",[75,4463,4464,4466,4468,4470,4473],{"class":77,"line":718},[75,4465,3610],{"class":253},[75,4467,4330],{"class":257},[75,4469,63],{"class":133},[75,4471,4472],{"class":414},"dispose",[75,4474,417],{"class":133},[75,4476,4477],{"class":77,"line":733},[75,4478,170],{"emptyLinePlaceholder":169},[75,4480,4481],{"class":77,"line":744},[75,4482,170],{"emptyLinePlaceholder":169},[75,4484,4485,4488,4490,4492],{"class":77,"line":758},[75,4486,4487],{"class":257},"app ",[75,4489,391],{"class":228},[75,4491,4399],{"class":414},[75,4493,2226],{"class":133},[75,4495,4496,4499,4501,4503,4505,4508],{"class":77,"line":1649},[75,4497,4498],{"class":573},"    title",[75,4500,391],{"class":228},[75,4502,562],{"class":414},[75,4504,63],{"class":133},[75,4506,4507],{"class":567},"app_name",[75,4509,2245],{"class":133},[75,4511,4512,4515,4517,4519,4522,4524],{"class":77,"line":1654},[75,4513,4514],{"class":573},"    description",[75,4516,391],{"class":228},[75,4518,1347],{"class":304},[75,4520,4521],{"class":129},"API de integración para sincronización de productos entre sistemas",[75,4523,1347],{"class":304},[75,4525,2245],{"class":133},[75,4527,4528,4531,4533,4535,4538,4540],{"class":77,"line":1668},[75,4529,4530],{"class":573},"    version",[75,4532,391],{"class":228},[75,4534,1347],{"class":304},[75,4536,4537],{"class":129},"1.0.0",[75,4539,1347],{"class":304},[75,4541,2245],{"class":133},[75,4543,4544,4547,4549,4552],{"class":77,"line":1678},[75,4545,4546],{"class":573},"    lifespan",[75,4548,391],{"class":228},[75,4550,4551],{"class":414},"lifespan",[75,4553,2245],{"class":133},[75,4555,4556],{"class":77,"line":1687},[75,4557,583],{"class":133},[75,4559,4560],{"class":77,"line":1696},[75,4561,170],{"emptyLinePlaceholder":169},[75,4563,4564],{"class":77,"line":1701},[75,4565,4566],{"class":89},"# CORS — ajustar en producción\n",[75,4568,4569,4571,4573,4576],{"class":77,"line":1710},[75,4570,4394],{"class":257},[75,4572,63],{"class":133},[75,4574,4575],{"class":414},"add_middleware",[75,4577,2226],{"class":133},[75,4579,4580,4583],{"class":77,"line":1720},[75,4581,4582],{"class":414},"    CORSMiddleware",[75,4584,2245],{"class":133},[75,4586,4587,4590,4592,4594,4596,4599,4601],{"class":77,"line":1725},[75,4588,4589],{"class":573},"    allow_origins",[75,4591,391],{"class":228},[75,4593,903],{"class":133},[75,4595,1347],{"class":304},[75,4597,4598],{"class":129},"*",[75,4600,1347],{"class":304},[75,4602,4603],{"class":133},"],\n",[75,4605,4606,4609,4611,4613,4615,4617,4619],{"class":77,"line":1739},[75,4607,4608],{"class":573},"    allow_methods",[75,4610,391],{"class":228},[75,4612,903],{"class":133},[75,4614,1347],{"class":304},[75,4616,4598],{"class":129},[75,4618,1347],{"class":304},[75,4620,4603],{"class":133},[75,4622,4623,4626,4628,4630,4632,4634,4636],{"class":77,"line":1758},[75,4624,4625],{"class":573},"    allow_headers",[75,4627,391],{"class":228},[75,4629,903],{"class":133},[75,4631,1347],{"class":304},[75,4633,4598],{"class":129},[75,4635,1347],{"class":304},[75,4637,4603],{"class":133},[75,4639,4640],{"class":77,"line":1768},[75,4641,583],{"class":133},[75,4643,4644],{"class":77,"line":1778},[75,4645,170],{"emptyLinePlaceholder":169},[75,4647,4648],{"class":77,"line":1788},[75,4649,4650],{"class":89},"# Routers\n",[75,4652,4653,4655,4657,4660,4662,4664,4666,4668],{"class":77,"line":2906},[75,4654,4394],{"class":257},[75,4656,63],{"class":133},[75,4658,4659],{"class":414},"include_router",[75,4661,280],{"class":133},[75,4663,885],{"class":414},[75,4665,63],{"class":133},[75,4667,2498],{"class":567},[75,4669,583],{"class":133},[75,4671,4672],{"class":77,"line":2912},[75,4673,170],{"emptyLinePlaceholder":169},[75,4675,4676],{"class":77,"line":2959},[75,4677,170],{"emptyLinePlaceholder":169},[75,4679,4680,4682,4684,4686,4688,4690,4692,4695,4697,4699,4701,4703,4705,4707,4710,4712],{"class":77,"line":2982},[75,4681,2495],{"class":2494},[75,4683,4394],{"class":657},[75,4685,63],{"class":2494},[75,4687,2503],{"class":657},[75,4689,280],{"class":133},[75,4691,1347],{"class":304},[75,4693,4694],{"class":129},"/health",[75,4696,1347],{"class":304},[75,4698,499],{"class":133},[75,4700,2468],{"class":573},[75,4702,391],{"class":228},[75,4704,903],{"class":133},[75,4706,1347],{"class":304},[75,4708,4709],{"class":129},"Sistema",[75,4711,1347],{"class":304},[75,4713,1355],{"class":133},[75,4715,4716,4718,4720,4723],{"class":77,"line":3006},[75,4717,651],{"class":273},[75,4719,654],{"class":273},[75,4721,4722],{"class":657}," health_check",[75,4724,661],{"class":133},[75,4726,4727,4729,4732],{"class":77,"line":3011},[75,4728,668],{"class":667},[75,4730,4731],{"class":671},"Verificar que la API está funcionando.",[75,4733,675],{"class":667},[75,4735,4736,4738,4741,4743,4745,4747,4749,4751,4754,4756,4758,4760,4762,4764,4766,4768,4770,4772],{"class":77,"line":3020},[75,4737,2055],{"class":253},[75,4739,4740],{"class":133}," {",[75,4742,1347],{"class":304},[75,4744,2236],{"class":129},[75,4746,1347],{"class":304},[75,4748,294],{"class":133},[75,4750,305],{"class":304},[75,4752,4753],{"class":129},"ok",[75,4755,1347],{"class":304},[75,4757,499],{"class":133},[75,4759,305],{"class":304},[75,4761,4394],{"class":129},[75,4763,1347],{"class":304},[75,4765,294],{"class":133},[75,4767,2073],{"class":257},[75,4769,63],{"class":133},[75,4771,4507],{"class":567},[75,4773,4774],{"class":133},"}\n",[75,4776,4777],{"class":77,"line":3053},[75,4778,170],{"emptyLinePlaceholder":169},[75,4780,4781],{"class":77,"line":3085},[75,4782,170],{"emptyLinePlaceholder":169},[75,4784,4785,4787,4789,4791,4793,4795,4797,4800,4802,4804,4806,4808,4810,4812,4815,4817],{"class":77,"line":3091},[75,4786,2495],{"class":2494},[75,4788,4394],{"class":657},[75,4790,63],{"class":2494},[75,4792,3350],{"class":657},[75,4794,280],{"class":133},[75,4796,1347],{"class":304},[75,4798,4799],{"class":129},"/auth/token",[75,4801,1347],{"class":304},[75,4803,499],{"class":133},[75,4805,2468],{"class":573},[75,4807,391],{"class":228},[75,4809,903],{"class":133},[75,4811,1347],{"class":304},[75,4813,4814],{"class":129},"Autenticación",[75,4816,1347],{"class":304},[75,4818,1355],{"class":133},[75,4820,4821,4823,4825,4828,4830,4833,4835,4837],{"class":77,"line":3096},[75,4822,651],{"class":273},[75,4824,654],{"class":273},[75,4826,4827],{"class":657}," login",[75,4829,280],{"class":133},[75,4831,4832],{"class":1943},"api_key",[75,4834,294],{"class":133},[75,4836,298],{"class":297},[75,4838,286],{"class":133},[75,4840,4841],{"class":77,"line":3101},[75,4842,4843],{"class":667},"    \"\"\"\n",[75,4845,4846],{"class":77,"line":3134},[75,4847,4848],{"class":671},"    Generar un token JWT.\n",[75,4850,4851],{"class":77,"line":3146},[75,4852,4853],{"class":671},"    En producción, valida contra una base de datos de usuarios/API keys.\n",[75,4855,4856],{"class":77,"line":3157},[75,4857,4843],{"class":667},[75,4859,4860],{"class":77,"line":3176},[75,4861,4862],{"class":89},"    # Ejemplo simplificado — en producción valida contra DB\n",[75,4864,4865,4867,4870,4873,4875,4878,4880],{"class":77,"line":3195},[75,4866,2712],{"class":253},[75,4868,4869],{"class":257}," api_key ",[75,4871,4872],{"class":228},"!=",[75,4874,305],{"class":304},[75,4876,4877],{"class":129},"mi-api-key-secreta",[75,4879,1347],{"class":304},[75,4881,382],{"class":133},[75,4883,4884,4887,4889,4891],{"class":77,"line":3200},[75,4885,4886],{"class":253},"        from",[75,4888,1862],{"class":257},[75,4890,261],{"class":253},[75,4892,4893],{"class":257}," HTTPException\n",[75,4895,4896,4898,4900,4902,4904,4906,4909,4911,4913,4915,4917,4920,4922],{"class":77,"line":3210},[75,4897,2221],{"class":253},[75,4899,1872],{"class":414},[75,4901,280],{"class":133},[75,4903,3292],{"class":573},[75,4905,391],{"class":228},[75,4907,4908],{"class":364},"401",[75,4910,499],{"class":133},[75,4912,3302],{"class":573},[75,4914,391],{"class":228},[75,4916,1347],{"class":304},[75,4918,4919],{"class":129},"API key inválida",[75,4921,1347],{"class":304},[75,4923,583],{"class":133},[75,4925,4926],{"class":77,"line":3254},[75,4927,170],{"emptyLinePlaceholder":169},[75,4929,4930,4932,4934,4936,4938,4940,4943,4945,4947,4949,4952,4954,4956,4958,4961,4963,4965,4967,4970,4972],{"class":77,"line":3271},[75,4931,2138],{"class":257},[75,4933,391],{"class":228},[75,4935,1938],{"class":414},[75,4937,2035],{"class":133},[75,4939,1347],{"class":304},[75,4941,4942],{"class":129},"sub",[75,4944,1347],{"class":304},[75,4946,294],{"class":133},[75,4948,305],{"class":304},[75,4950,4951],{"class":129},"sistema-ecommerce",[75,4953,1347],{"class":304},[75,4955,499],{"class":133},[75,4957,305],{"class":304},[75,4959,4960],{"class":129},"role",[75,4962,1347],{"class":304},[75,4964,294],{"class":133},[75,4966,305],{"class":304},[75,4968,4969],{"class":129},"integration",[75,4971,1347],{"class":304},[75,4973,2050],{"class":133},[75,4975,4976,4978,4980,4982,4985,4987,4989,4992,4994,4996,4999,5001,5003,5005,5008,5010],{"class":77,"line":3283},[75,4977,2055],{"class":253},[75,4979,4740],{"class":133},[75,4981,1347],{"class":304},[75,4983,4984],{"class":129},"access_token",[75,4986,1347],{"class":304},[75,4988,294],{"class":133},[75,4990,4991],{"class":257}," token",[75,4993,499],{"class":133},[75,4995,305],{"class":304},[75,4997,4998],{"class":129},"token_type",[75,5000,1347],{"class":304},[75,5002,294],{"class":133},[75,5004,305],{"class":304},[75,5006,5007],{"class":129},"bearer",[75,5009,1347],{"class":304},[75,5011,4774],{"class":133},[52,5013,5015],{"id":5014},"paso-8-ejecutar-y-probar","Paso 8: Ejecutar y probar",[65,5017,5019],{"className":67,"code":5018,"language":69,"meta":70,"style":70},"# Crear la base de datos\nsudo -u postgres psql -c \"CREATE DATABASE miapi OWNER app;\"\n\n# Arrancar la API\nuvicorn app.main:app --host 0.0.0.0 --port 8000 --reload\n",[72,5020,5021,5026,5050,5054,5059],{"__ignoreMap":70},[75,5022,5023],{"class":77,"line":78},[75,5024,5025],{"class":89},"# Crear la base de datos\n",[75,5027,5028,5031,5034,5037,5040,5043,5045,5048],{"class":77,"line":93},[75,5029,5030],{"class":81},"sudo",[75,5032,5033],{"class":85}," -u",[75,5035,5036],{"class":129}," postgres",[75,5038,5039],{"class":129}," psql",[75,5041,5042],{"class":85}," -c",[75,5044,305],{"class":304},[75,5046,5047],{"class":129},"CREATE DATABASE miapi OWNER app;",[75,5049,311],{"class":304},[75,5051,5052],{"class":77,"line":157},[75,5053,170],{"emptyLinePlaceholder":169},[75,5055,5056],{"class":77,"line":166},[75,5057,5058],{"class":89},"# Arrancar la API\n",[75,5060,5061,5064,5067,5070,5073,5076,5079],{"class":77,"line":173},[75,5062,5063],{"class":81},"uvicorn",[75,5065,5066],{"class":129}," app.main:app",[75,5068,5069],{"class":85}," --host",[75,5071,5072],{"class":364}," 0.0.0.0",[75,5074,5075],{"class":85}," --port",[75,5077,5078],{"class":364}," 8000",[75,5080,5081],{"class":85}," --reload\n",[12,5083,5084,5085,5088],{},"Abre ",[72,5086,5087],{},"http://localhost:8000/docs"," — verás la documentación interactiva de Swagger generada automáticamente con todos tus endpoints, schemas y la opción de probar cada uno desde el navegador.",[5090,5091,5093],"h3",{"id":5092},"probar-con-curl","Probar con curl",[65,5095,5097],{"className":67,"code":5096,"language":69,"meta":70,"style":70},"# 1. Obtener token\nTOKEN=$(curl -s -X POST \"http://localhost:8000/auth/token?api_key=mi-api-key-secreta\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['access_token'])\")\n\n# 2. Crear producto\ncurl -X POST http://localhost:8000/api/v1/productos \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sku\":\"LAPTOP-001\",\"nombre\":\"Laptop Empresarial\",\"precio\":18999.00,\"stock\":50}'\n\n# 3. Listar productos\ncurl -H \"Authorization: Bearer $TOKEN\" \"http://localhost:8000/api/v1/productos?page=1&per_page=10\"\n\n# 4. Actualizar stock\ncurl -X PATCH http://localhost:8000/api/v1/productos/LAPTOP-001 \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"stock\":45}'\n",[72,5098,5099,5104,5149,5153,5158,5171,5188,5201,5215,5219,5224,5246,5250,5255,5269,5283,5295],{"__ignoreMap":70},[75,5100,5101],{"class":77,"line":78},[75,5102,5103],{"class":89},"# 1. Obtener token\n",[75,5105,5106,5109,5111,5114,5117,5120,5123,5126,5128,5131,5133,5135,5138,5140,5142,5145,5147],{"class":77,"line":93},[75,5107,5108],{"class":257},"TOKEN",[75,5110,391],{"class":228},[75,5112,5113],{"class":133},"$(",[75,5115,5116],{"class":81},"curl",[75,5118,5119],{"class":85}," -s",[75,5121,5122],{"class":85}," -X",[75,5124,5125],{"class":129}," POST",[75,5127,305],{"class":304},[75,5129,5130],{"class":129},"http://localhost:8000/auth/token?api_key=mi-api-key-secreta",[75,5132,1347],{"class":304},[75,5134,1024],{"class":228},[75,5136,5137],{"class":81}," python3",[75,5139,5042],{"class":85},[75,5141,305],{"class":304},[75,5143,5144],{"class":129},"import sys,json; print(json.load(sys.stdin)['access_token'])",[75,5146,1347],{"class":304},[75,5148,583],{"class":133},[75,5150,5151],{"class":77,"line":157},[75,5152,170],{"emptyLinePlaceholder":169},[75,5154,5155],{"class":77,"line":166},[75,5156,5157],{"class":89},"# 2. Crear producto\n",[75,5159,5160,5162,5164,5166,5169],{"class":77,"line":173},[75,5161,5116],{"class":81},[75,5163,5122],{"class":85},[75,5165,5125],{"class":129},[75,5167,5168],{"class":129}," http://localhost:8000/api/v1/productos",[75,5170,195],{"class":194},[75,5172,5173,5176,5178,5181,5184,5186],{"class":77,"line":198},[75,5174,5175],{"class":85},"  -H",[75,5177,305],{"class":304},[75,5179,5180],{"class":129},"Authorization: Bearer ",[75,5182,5183],{"class":257},"$TOKEN",[75,5185,1347],{"class":304},[75,5187,195],{"class":194},[75,5189,5190,5192,5194,5197,5199],{"class":77,"line":332},[75,5191,5175],{"class":85},[75,5193,305],{"class":304},[75,5195,5196],{"class":129},"Content-Type: application/json",[75,5198,1347],{"class":304},[75,5200,195],{"class":194},[75,5202,5203,5206,5209,5212],{"class":77,"line":351},[75,5204,5205],{"class":85},"  -d",[75,5207,5208],{"class":304}," '",[75,5210,5211],{"class":129},"{\"sku\":\"LAPTOP-001\",\"nombre\":\"Laptop Empresarial\",\"precio\":18999.00,\"stock\":50}",[75,5213,5214],{"class":304},"'\n",[75,5216,5217],{"class":77,"line":368},[75,5218,170],{"emptyLinePlaceholder":169},[75,5220,5221],{"class":77,"line":373},[75,5222,5223],{"class":89},"# 3. Listar productos\n",[75,5225,5226,5228,5231,5233,5235,5237,5239,5241,5244],{"class":77,"line":385},[75,5227,5116],{"class":81},[75,5229,5230],{"class":85}," -H",[75,5232,305],{"class":304},[75,5234,5180],{"class":129},[75,5236,5183],{"class":257},[75,5238,1347],{"class":304},[75,5240,305],{"class":304},[75,5242,5243],{"class":129},"http://localhost:8000/api/v1/productos?page=1&per_page=10",[75,5245,311],{"class":304},[75,5247,5248],{"class":77,"line":401},[75,5249,170],{"emptyLinePlaceholder":169},[75,5251,5252],{"class":77,"line":406},[75,5253,5254],{"class":89},"# 4. Actualizar stock\n",[75,5256,5257,5259,5261,5264,5267],{"class":77,"line":664},[75,5258,5116],{"class":81},[75,5260,5122],{"class":85},[75,5262,5263],{"class":129}," PATCH",[75,5265,5266],{"class":129}," http://localhost:8000/api/v1/productos/LAPTOP-001",[75,5268,195],{"class":194},[75,5270,5271,5273,5275,5277,5279,5281],{"class":77,"line":678},[75,5272,5175],{"class":85},[75,5274,305],{"class":304},[75,5276,5180],{"class":129},[75,5278,5183],{"class":257},[75,5280,1347],{"class":304},[75,5282,195],{"class":194},[75,5284,5285,5287,5289,5291,5293],{"class":77,"line":701},[75,5286,5175],{"class":85},[75,5288,305],{"class":304},[75,5290,5196],{"class":129},[75,5292,1347],{"class":304},[75,5294,195],{"class":194},[75,5296,5297,5299,5301,5304],{"class":77,"line":709},[75,5298,5205],{"class":85},[75,5300,5208],{"class":304},[75,5302,5303],{"class":129},"{\"stock\":45}",[75,5305,5214],{"class":304},[459,5307],{},[52,5309,5311],{"id":5310},"paso-9-containerizar-con-docker","Paso 9: Containerizar con Docker",[65,5313,5317],{"className":5314,"code":5315,"language":5316,"meta":70,"style":70},"language-dockerfile shiki shiki-themes material-theme-lighter github-light github-dark","# Dockerfile\nFROM python:3.12-slim\n\nWORKDIR /app\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY app/ app/\n\nEXPOSE 8000\n\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n","dockerfile",[72,5318,5319,5324,5333,5337,5345,5349,5357,5365,5369,5376,5380,5388,5392],{"__ignoreMap":70},[75,5320,5321],{"class":77,"line":78},[75,5322,5323],{"class":89},"# Dockerfile\n",[75,5325,5326,5330],{"class":77,"line":93},[75,5327,5329],{"class":5328},"sw1J6","FROM",[75,5331,5332],{"class":257}," python:3.12-slim\n",[75,5334,5335],{"class":77,"line":157},[75,5336,170],{"emptyLinePlaceholder":169},[75,5338,5339,5342],{"class":77,"line":166},[75,5340,5341],{"class":5328},"WORKDIR",[75,5343,5344],{"class":257}," /app\n",[75,5346,5347],{"class":77,"line":173},[75,5348,170],{"emptyLinePlaceholder":169},[75,5350,5351,5354],{"class":77,"line":198},[75,5352,5353],{"class":5328},"COPY",[75,5355,5356],{"class":257}," requirements.txt .\n",[75,5358,5359,5362],{"class":77,"line":332},[75,5360,5361],{"class":5328},"RUN",[75,5363,5364],{"class":257}," pip install --no-cache-dir -r requirements.txt\n",[75,5366,5367],{"class":77,"line":351},[75,5368,170],{"emptyLinePlaceholder":169},[75,5370,5371,5373],{"class":77,"line":368},[75,5372,5353],{"class":5328},[75,5374,5375],{"class":257}," app/ app/\n",[75,5377,5378],{"class":77,"line":373},[75,5379,170],{"emptyLinePlaceholder":169},[75,5381,5382,5385],{"class":77,"line":385},[75,5383,5384],{"class":5328},"EXPOSE",[75,5386,5387],{"class":257}," 8000\n",[75,5389,5390],{"class":77,"line":401},[75,5391,170],{"emptyLinePlaceholder":169},[75,5393,5394,5397,5400,5403,5406,5409,5411,5414,5416,5419,5421,5424,5426,5429],{"class":77,"line":406},[75,5395,5396],{"class":5328},"CMD",[75,5398,5399],{"class":257}," [",[75,5401,5402],{"class":129},"\"uvicorn\"",[75,5404,5405],{"class":257},", ",[75,5407,5408],{"class":129},"\"app.main:app\"",[75,5410,5405],{"class":257},[75,5412,5413],{"class":129},"\"--host\"",[75,5415,5405],{"class":257},[75,5417,5418],{"class":129},"\"0.0.0.0\"",[75,5420,5405],{"class":257},[75,5422,5423],{"class":129},"\"--port\"",[75,5425,5405],{"class":257},[75,5427,5428],{"class":129},"\"8000\"",[75,5430,1755],{"class":257},[12,5432,5433,5434,294],{},"Con ",[16,5435,5437],{"href":5436},"/blog/docker-compose-guia-completa","Docker Compose",[65,5439,5443],{"className":5440,"code":5441,"language":5442,"meta":70,"style":70},"language-yaml shiki shiki-themes material-theme-lighter github-light github-dark","# docker-compose.yml\nservices:\n  api:\n    build: .\n    restart: unless-stopped\n    ports:\n      - \"8000:8000\"\n    environment:\n      DATABASE_URL: postgresql+asyncpg://app:${DB_PASSWORD}@db:5432/miapi\n      SECRET_KEY: ${SECRET_KEY}\n    depends_on:\n      db:\n        condition: service_healthy\n\n  db:\n    image: postgres:16-alpine\n    restart: unless-stopped\n    environment:\n      POSTGRES_DB: miapi\n      POSTGRES_USER: app\n      POSTGRES_PASSWORD: ${DB_PASSWORD}\n    volumes:\n      - pg_data:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U app -d miapi\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\nvolumes:\n  pg_data:\n","yaml",[72,5444,5445,5450,5458,5465,5475,5485,5492,5504,5511,5521,5531,5538,5545,5555,5559,5566,5576,5584,5590,5600,5610,5620,5627,5634,5641,5668,5678,5688,5698,5702,5709],{"__ignoreMap":70},[75,5446,5447],{"class":77,"line":78},[75,5448,5449],{"class":89},"# docker-compose.yml\n",[75,5451,5452,5456],{"class":77,"line":93},[75,5453,5455],{"class":5454},"sQzsp","services",[75,5457,382],{"class":133},[75,5459,5460,5463],{"class":77,"line":157},[75,5461,5462],{"class":5454},"  api",[75,5464,382],{"class":133},[75,5466,5467,5470,5472],{"class":77,"line":166},[75,5468,5469],{"class":5454},"    build",[75,5471,294],{"class":133},[75,5473,5474],{"class":364}," .\n",[75,5476,5477,5480,5482],{"class":77,"line":173},[75,5478,5479],{"class":5454},"    restart",[75,5481,294],{"class":133},[75,5483,5484],{"class":129}," unless-stopped\n",[75,5486,5487,5490],{"class":77,"line":198},[75,5488,5489],{"class":5454},"    ports",[75,5491,382],{"class":133},[75,5493,5494,5497,5499,5502],{"class":77,"line":332},[75,5495,5496],{"class":133},"      -",[75,5498,305],{"class":304},[75,5500,5501],{"class":129},"8000:8000",[75,5503,311],{"class":304},[75,5505,5506,5509],{"class":77,"line":351},[75,5507,5508],{"class":5454},"    environment",[75,5510,382],{"class":133},[75,5512,5513,5516,5518],{"class":77,"line":368},[75,5514,5515],{"class":5454},"      DATABASE_URL",[75,5517,294],{"class":133},[75,5519,5520],{"class":129}," postgresql+asyncpg://app:${DB_PASSWORD}@db:5432/miapi\n",[75,5522,5523,5526,5528],{"class":77,"line":373},[75,5524,5525],{"class":5454},"      SECRET_KEY",[75,5527,294],{"class":133},[75,5529,5530],{"class":129}," ${SECRET_KEY}\n",[75,5532,5533,5536],{"class":77,"line":385},[75,5534,5535],{"class":5454},"    depends_on",[75,5537,382],{"class":133},[75,5539,5540,5543],{"class":77,"line":401},[75,5541,5542],{"class":5454},"      db",[75,5544,382],{"class":133},[75,5546,5547,5550,5552],{"class":77,"line":406},[75,5548,5549],{"class":5454},"        condition",[75,5551,294],{"class":133},[75,5553,5554],{"class":129}," service_healthy\n",[75,5556,5557],{"class":77,"line":664},[75,5558,170],{"emptyLinePlaceholder":169},[75,5560,5561,5564],{"class":77,"line":678},[75,5562,5563],{"class":5454},"  db",[75,5565,382],{"class":133},[75,5567,5568,5571,5573],{"class":77,"line":701},[75,5569,5570],{"class":5454},"    image",[75,5572,294],{"class":133},[75,5574,5575],{"class":129}," postgres:16-alpine\n",[75,5577,5578,5580,5582],{"class":77,"line":709},[75,5579,5479],{"class":5454},[75,5581,294],{"class":133},[75,5583,5484],{"class":129},[75,5585,5586,5588],{"class":77,"line":718},[75,5587,5508],{"class":5454},[75,5589,382],{"class":133},[75,5591,5592,5595,5597],{"class":77,"line":733},[75,5593,5594],{"class":5454},"      POSTGRES_DB",[75,5596,294],{"class":133},[75,5598,5599],{"class":129}," miapi\n",[75,5601,5602,5605,5607],{"class":77,"line":744},[75,5603,5604],{"class":5454},"      POSTGRES_USER",[75,5606,294],{"class":133},[75,5608,5609],{"class":129}," app\n",[75,5611,5612,5615,5617],{"class":77,"line":758},[75,5613,5614],{"class":5454},"      POSTGRES_PASSWORD",[75,5616,294],{"class":133},[75,5618,5619],{"class":129}," ${DB_PASSWORD}\n",[75,5621,5622,5625],{"class":77,"line":1649},[75,5623,5624],{"class":5454},"    volumes",[75,5626,382],{"class":133},[75,5628,5629,5631],{"class":77,"line":1654},[75,5630,5496],{"class":133},[75,5632,5633],{"class":129}," pg_data:/var/lib/postgresql/data\n",[75,5635,5636,5639],{"class":77,"line":1668},[75,5637,5638],{"class":5454},"    healthcheck",[75,5640,382],{"class":133},[75,5642,5643,5646,5648,5650,5652,5655,5657,5659,5661,5664,5666],{"class":77,"line":1678},[75,5644,5645],{"class":5454},"      test",[75,5647,294],{"class":133},[75,5649,5399],{"class":133},[75,5651,1347],{"class":304},[75,5653,5654],{"class":129},"CMD-SHELL",[75,5656,1347],{"class":304},[75,5658,499],{"class":133},[75,5660,305],{"class":304},[75,5662,5663],{"class":129},"pg_isready -U app -d miapi",[75,5665,1347],{"class":304},[75,5667,1755],{"class":133},[75,5669,5670,5673,5675],{"class":77,"line":1687},[75,5671,5672],{"class":5454},"      interval",[75,5674,294],{"class":133},[75,5676,5677],{"class":129}," 10s\n",[75,5679,5680,5683,5685],{"class":77,"line":1696},[75,5681,5682],{"class":5454},"      timeout",[75,5684,294],{"class":133},[75,5686,5687],{"class":129}," 5s\n",[75,5689,5690,5693,5695],{"class":77,"line":1701},[75,5691,5692],{"class":5454},"      retries",[75,5694,294],{"class":133},[75,5696,5697],{"class":364}," 5\n",[75,5699,5700],{"class":77,"line":1710},[75,5701,170],{"emptyLinePlaceholder":169},[75,5703,5704,5707],{"class":77,"line":1720},[75,5705,5706],{"class":5454},"volumes",[75,5708,382],{"class":133},[75,5710,5711,5714],{"class":77,"line":1725},[75,5712,5713],{"class":5454},"  pg_data",[75,5715,382],{"class":133},[52,5717,5719],{"id":5718},"siguientes-pasos","Siguientes pasos",[12,5721,5722],{},"Con la API funcionando, puedes extenderla:",[5724,5725,5726,5733,5747,5753,5767,5776],"ul",{},[5727,5728,5729,5732],"li",{},[770,5730,5731],{},"Alembic"," — migraciones de base de datos versionadas (indispensable para producción)",[5727,5734,5735,5738,5739,5743,5744],{},[770,5736,5737],{},"Rate limiting"," — proteger la API con ",[16,5740,5742],{"href":5741},"/tecnologias/redis","Redis"," y ",[72,5745,5746],{},"slowapi",[5727,5748,5749,5752],{},[770,5750,5751],{},"Webhooks"," — notificar a otros sistemas cuando un producto cambia (patrón pub/sub)",[5727,5754,5755,5758,5759,5762,5763,5766],{},[770,5756,5757],{},"Tests"," — ",[72,5760,5761],{},"pytest"," + ",[72,5764,5765],{},"httpx"," para tests de integración de cada endpoint",[5727,5768,5769,5775],{},[770,5770,5771],{},[16,5772,5774],{"href":5773},"/blog/nginx-reverse-proxy-ssl","Nginx reverse proxy"," — exponer la API con HTTPS en producción",[5727,5777,5778,5784],{},[770,5779,5780],{},[16,5781,5783],{"href":5782},"/servicios/desarrollo-software","Desarrollo a medida"," — si necesitas una integración más compleja, te ayudamos",[5786,5787],"call-to-action",{"description":5788,"eyebrow":5789,"icon":5790,"label":5791,"title":5792,"to":5793},"Desarrollamos APIs e integraciones que sincronizan datos entre tu ERP, CRM, e-commerce y cualquier plataforma en tiempo real.","Integraciones a medida","i-lucide-plug","Platiquemos de tu proyecto","¿Necesitas conectar tu ERP con otros sistemas?","/contacto",[5795,5796,5797],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s2W-s, html code.shiki .s2W-s{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sithA, html code.shiki .sithA{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":70,"searchDepth":93,"depth":157,"links":5799},[5800,5801,5802,5803,5804,5805,5806,5807,5808,5811,5812],{"id":54,"depth":93,"text":55},{"id":104,"depth":93,"text":105},{"id":235,"depth":93,"text":236},{"id":463,"depth":93,"text":464},{"id":764,"depth":93,"text":765},{"id":1806,"depth":93,"text":1807},{"id":2269,"depth":93,"text":2270},{"id":4242,"depth":93,"text":4243},{"id":5014,"depth":93,"text":5015,"children":5809},[5810],{"id":5092,"depth":157,"text":5093},{"id":5310,"depth":93,"text":5311},{"id":5718,"depth":93,"text":5719},"tutorial",{"title":5815,"description":5816,"label":5791,"to":5793,"icon":5790},"¿Necesitas conectar sistemas que no se hablan entre sí?","Desarrollamos APIs e integraciones a medida que conectan tu ERP, CRM, e-commerce y cualquier plataforma en tiempo real.","2026-03-04","Guía paso a paso para construir una API REST profesional con Python y FastAPI que conecte tu ERP, CRM o cualquier sistema con validación, autenticación y documentación automática.",false,"md",[5822,5825,5828,5831],{"question":5823,"answer":5824},"¿Por qué FastAPI y no Flask o Django REST Framework?","FastAPI combina lo mejor de ambos mundos: la simplicidad de Flask con la robustez de Django REST. Además, es significativamente más rápido (basado en Starlette y uvicorn), genera documentación OpenAPI automáticamente, tiene validación de datos integrada con Pydantic y soporta async/await nativamente. Para APIs de integración empresarial, es la mejor opción en Python hoy.",{"question":5826,"answer":5827},"¿FastAPI soporta conexión con PostgreSQL?","Sí. Puedes usar SQLAlchemy como ORM o asyncpg para consultas asíncronas directas. En esta guía usamos SQLAlchemy con el driver async de PostgreSQL para obtener rendimiento óptimo sin sacrificar la comodidad del ORM.",{"question":5829,"answer":5830},"¿Cómo protejo mi API de acceso no autorizado?","FastAPI tiene soporte nativo para OAuth2, JWT, API keys y autenticación básica. En esta guía implementamos JWT (JSON Web Tokens) que es el estándar para APIs REST — el cliente se autentica una vez y recibe un token que envía en cada petición.",{"question":5832,"answer":5833},"¿Puedo usar FastAPI para conectar mi ERP con mi tienda en línea?","Sí, ese es exactamente el caso de uso que cubrimos. La API actúa como puente entre sistemas — recibe peticiones del e-commerce (consultar stock, crear pedido), las procesa contra el ERP y devuelve la respuesta. Con FastAPI puedes tener esta integración corriendo en producción en días, no meses.","/images/blog/fastapi-api-rest.jpg","Editor de código mostrando una API FastAPI con documentación Swagger generada automáticamente",{},"/blog/tutorial/apis-rest-python-fastapi",{"title":5,"description":5818},"blog/tutorial/apis-rest-python-fastapi",[241,5841,5842,5843,5813],"fastapi","apis","integraciones","8HAhSRRUOGtzGri8jIEfzlLTGIKTq6Z3QdVeTV7JCVI",{"path":5846,"title":5847},"/blog/tutorial/configurar-firewall-ufw-linux","Configurar firewall en Linux con UFW — reglas esenciales",null,[5850,5855,5862],{"path":5846,"title":5847,"description":5851,"date":5852,"category":5813,"image":5853,"imageAlt":5854,"readingTime":351},"Guía paso a paso para configurar UFW (Uncomplicated Firewall) en Ubuntu y Debian con las reglas esenciales para proteger servidores de producción.","2026-03-01","/images/blog/ufw-firewall-linux.jpg","Terminal de Linux mostrando reglas de firewall UFW activas protegiendo un servidor de producción",{"path":5856,"title":5857,"description":5858,"date":5859,"category":5813,"image":5860,"imageAlt":5861,"readingTime":373},"/blog/tutorial/traefik-reverse-proxy-docker","Configurar Traefik como reverse proxy para contenedores Docker","Guía paso a paso para instalar Traefik como reverse proxy con descubrimiento automático de contenedores Docker, SSL con Let's Encrypt y dashboard de monitoreo.","2026-02-28","/images/blog/traefik-docker.jpg","Dashboard de Traefik mostrando rutas automáticas hacia múltiples contenedores Docker con SSL activo",{"path":5863,"title":5864,"description":5865,"date":5866,"category":5813,"image":5867,"imageAlt":5868,"readingTime":368},"/blog/tutorial/nginx-reverse-proxy-ssl","Configurar Nginx como reverse proxy con SSL gratuito","Guía paso a paso para configurar Nginx como proxy inverso con certificados SSL de Let's Encrypt para exponer aplicaciones web de forma segura en producción.","2026-02-25","/images/blog/nginx-reverse-proxy.jpg","Diagrama de arquitectura mostrando Nginx como reverse proxy con SSL frente a múltiples aplicaciones backend"]