Diiegoal commited on
Commit
3e950b6
·
1 Parent(s): a58e6dc

Actualizo la app

Browse files
Files changed (1) hide show
  1. README.md +895 -251
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Oraculo Adult Income API
3
- emoji: 🚀
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: docker
@@ -9,380 +9,1024 @@ base_path: /docs
9
  pinned: false
10
  ---
11
 
12
- # Oraculo Adult Income API
13
 
14
- API REST profesional para inferencia del dataset Adult Census Income, reconstruida con enfoque de clean code, seguridad por capas, pruebas agresivas y contrato estable entre notebook y producción.
 
 
 
 
 
 
 
 
15
 
16
- ## Objetivo
 
 
 
17
 
18
- Esta API resuelve tres problemas reales del proyecto:
19
 
20
- 1. Exponer inferencia de modelo con un contrato HTTP limpio, autenticado y auditable.
21
- 2. Blindar el salto entre `EDA_For_All_Tree_clean.ipynb` y el artefacto `pipeline_produccion.pkl`.
22
- 3. Dejar una base escalable para crecer a más endpoints, más usuarios y despliegue en Render.
23
 
24
- ## Stack elegido
25
 
26
- Tecnologías aplicadas en la implementación final:
27
 
28
- - `FastAPI`: framework principal, OpenAPI/Swagger, validación HTTP y alto rendimiento.
29
- - `Python`: lenguaje base del servicio, notebook y pipeline.
30
- - `Pydantic v2`: DTOs, validaciones estrictas, aliases y contratos de entrada/salida.
31
- - `SQLAlchemy 2.0`: ORM principal y capa de persistencia.
32
- - `Alembic`: migraciones versionadas de base de datos.
33
- - `SQLite` por defecto y `PostgreSQL` listo por `DATABASE_URL`: desarrollo local y despliegue escalable.
34
- - `JWT + bcrypt`: autenticación stateless y hashing de contraseñas.
35
- - `Swagger/OpenAPI`: documentación viva de endpoints.
36
- - `Pytest + TestClient`: pruebas HTTP, seguridad, errores y dominio.
37
- - `Uvicorn`: servidor ASGI para local y producción.
38
- - `Starlette middlewares`: CORS, GZip, Trusted Hosts, request id, límites de payload, rate limiting básico.
39
- - `joblib + LightGBM/sklearn pipeline`: artefacto de inferencia.
40
 
41
- Tecnología no seleccionada deliberadamente:
42
 
43
- - `SQLModel`: no se usó en esta versión porque superpone responsabilidades con SQLAlchemy + Pydantic. Para este nivel de control y separación entre ORM y DTOs, SQLAlchemy 2.0 fue una mejor decisión.
 
 
 
 
 
 
 
44
 
45
- Tecnologías adicionales que faltaban en la lista original y son importantes:
46
 
47
- - `pydantic-settings` para configuración por entorno.
48
- - `bcrypt` para hashing directo y estable.
49
- - `httpx/TestClient` para pruebas HTTP.
50
- - `Request ID / security headers / rate limiting` para endurecimiento operativo.
51
 
52
- ## Arquitectura
53
 
54
- La API quedó organizada por capas:
55
 
56
- - `app/main.py`: app factory, lifespan, middlewares y bootstrap.
57
- - `app/api/`: routers, versionado y dependencias.
58
- - `app/core/`: configuración, seguridad, middleware, logging, errores.
59
- - `app/db/`: base ORM, sesión, modelos, repositorios, seeds.
60
- - `app/services/`: reglas de negocio.
61
- - `app/ml/`: carga del artefacto y contrato con el pipeline.
62
- - `app/schemas/`: DTOs HTTP.
63
- - `alembic/`: migraciones.
64
- - `tests/`: pruebas HTTP, seguridad, esquemas y modelo.
65
 
66
- ## Funcionalidades incluidas
67
 
68
- - Registro y login con JWT.
69
- - Endpoint autenticado de predicción.
70
- - Historial de predicciones por usuario.
71
- - Consulta puntual por `prediction_id`.
72
- - Health checks `live` y `ready`.
73
- - Seeds de administrador por variables de entorno.
74
- - Manejador de errores unificado.
75
- - Headers de seguridad y request id.
76
- - Protección por tamaño máximo de payload.
77
- - Rate limiting in-memory.
78
- - Compatibilidad con el artefacto actual del modelo.
79
 
80
- ## Endpoints
81
 
82
- ### Salud
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- - `GET /`
85
- - `GET /api/v1/health/live`
86
- - `GET /api/v1/health/ready`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  ### Autenticación
89
 
90
- - `POST /api/v1/auth/register`
91
- - `POST /api/v1/auth/login`
92
- - `GET /api/v1/auth/me`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- ### Predicciones
95
 
96
- - `POST /api/v1/predictions`
97
- - `GET /api/v1/predictions`
98
- - `GET /api/v1/predictions/{prediction_id}`
99
 
100
- ## Seguridad aplicada
101
 
102
- ### OWASP / API hardening
103
 
104
- - JWT firmado y validado.
105
- - Contraseñas hasheadas con `bcrypt`.
106
- - DTOs con `extra="forbid"` para bloquear campos sorpresa.
107
- - Validación fuerte de tipos, rangos y longitudes.
108
- - `TrustedHostMiddleware` para rechazar hosts no permitidos.
109
- - Headers de seguridad (`CSP`, `X-Frame-Options`, `nosniff`, `Cache-Control`).
110
- - Límite de tamaño de payload.
111
- - Rate limiting básico por IP.
112
- - Errores controlados sin exponer stacktrace al cliente.
113
- - Persistencia auditada de cada predicción.
114
 
115
- ### Vulnerabilidades orientadas a LLM
116
 
117
- Tu lista incluía amenazas como `many-shot jailbreaking`, `indirect prompt injection`, `context hijacking`, `context poisoning`, `lost in the middle` y `context overflow`.
118
 
119
- Punto importante:
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- - Esta API no expone un endpoint LLM conversacional, así que esas amenazas no aplican de forma directa al plano HTTP actual.
122
- - Sí aplican al notebook y a cualquier automatización futura que use prompts, agentes o generación asistida.
123
 
124
- Mitigaciones prácticas adoptadas o recomendadas:
125
 
126
- - Tratar todo texto externo como entrada no confiable.
127
- - No ejecutar prompts del usuario dentro del backend de inferencia.
128
- - Mantener separación entre features del modelo y texto libre.
129
- - Exportar el artefacto desde el notebook con validación previa.
130
- - Generar `model_manifest.json` junto con el `.pkl` para trazabilidad.
131
- - Evitar que la API acepte instrucciones ejecutables o plantillas arbitrarias.
132
 
133
- ## Contrato Notebook -> API
134
 
135
- El notebook limpio `EDA_For_All_Tree_clean.ipynb` quedó orientado a producción:
136
 
137
- - Exporta `pipeline_produccion.pkl`.
138
- - Valida el pipeline con una muestra real antes de serializar.
139
- - Genera `model_manifest.json`.
140
- - Reúne artefactos serializables y modelos de forma explícita.
 
141
 
142
- El backend, a través de `ModelManager` y `PipelineProduccionMLOps`, puede:
143
 
144
- - Cargar el artefacto.
145
- - Reconstruir artefactos faltantes si el notebook exportó algo incompleto.
146
- - Leer el `model_manifest.json` cuando exista.
147
 
148
- ## Base de datos
149
 
150
- Entidades incluidas:
 
 
 
 
 
 
 
 
 
 
151
 
152
- - `users`
153
- - `prediction_logs`
154
 
155
- Persistencia incluida:
156
 
157
- - usuarios autenticados
158
- - historial de predicciones
159
- - payload original
160
- - payload normalizado
161
- - request id
162
- - latencia
163
- - versión del modelo
164
- - hash del payload
165
 
166
- ## Migraciones Alembic
167
 
168
- Inicialización incluida:
169
 
170
- - `alembic.ini`
171
- - `alembic/env.py`
172
- - migración inicial `initial_api_schema`
173
 
174
- Comandos útiles:
175
 
176
- ```bash
177
- alembic upgrade head
178
- alembic revision --autogenerate -m "descripcion"
179
- alembic downgrade -1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  ```
181
 
182
- ## Seeds
183
 
184
- Si defines:
185
 
186
- - `ORACULO_SEED_ADMIN_EMAIL`
187
- - `ORACULO_SEED_ADMIN_PASSWORD`
188
- - `ORACULO_AUTO_SEED_ADMIN=true`
189
 
190
- la aplicación crea un administrador por bootstrap si no existe.
 
 
 
191
 
192
- ## Configuración
193
 
194
- Variables principales:
195
 
196
- - `ORACULO_DATABASE_URL`
197
- - `ORACULO_MODEL_PATH`
198
- - `ORACULO_JWT_SECRET_KEY`
199
- - `ORACULO_ALLOWED_HOSTS`
200
- - `ORACULO_CORS_ALLOW_ORIGINS`
201
- - `ORACULO_RATE_LIMIT_REQUESTS`
202
- - `ORACULO_RATE_LIMIT_WINDOW_SECONDS`
203
- - `ORACULO_MAX_REQUEST_SIZE_BYTES`
204
- - `ORACULO_DOCS_ENABLED`
205
- - `ORACULO_SECURITY_HEADERS_ENABLED`
206
 
207
- Uso recomendado por entorno:
208
 
209
- - Local: crea un archivo `.env` en la raíz del proyecto.
210
- - Hugging Face Spaces: configura estas variables desde `Settings > Variables and secrets`.
211
- - Producción tradicional: usa variables de entorno del proveedor, no hardcodes secretos en el repositorio.
 
212
 
213
- ## Ejecución local
214
 
215
  ```bash
216
- python -m venv venv
217
- venv\Scripts\activate
218
  pip install -r requirements.txt
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  alembic upgrade head
 
 
 
 
 
220
  uvicorn app.main:app --reload
221
  ```
222
 
223
- Swagger:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- - `http://127.0.0.1:8000/docs`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- ## Tests
 
 
 
 
 
228
 
229
- La suite prueba:
230
 
231
- - esquemas
232
- - autenticación
233
- - autorización
234
- - predicción
235
- - historial
236
- - aislamiento de datos entre usuarios
237
- - health checks
238
- - middlewares de seguridad
239
- - payload demasiado grande
240
- - rate limit
241
- - modelo real (`pipeline_produccion.pkl`)
242
 
243
- Ejecución:
244
 
245
  ```bash
246
- venv\Scripts\pytest -q
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  ```
248
 
249
- Estado actual de la suite:
250
 
251
- - `35 passed`
 
252
 
253
- ## Despliegue en Render
254
 
255
- Recomendación:
 
 
 
 
 
 
256
 
257
- 1. Subir el proyecto con `requirements.txt`.
258
- 2. Configurar `Start Command`:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  ```bash
261
- alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORT
262
  ```
263
 
264
- 3. Definir variables de entorno:
 
 
265
 
266
- - `ORACULO_ENVIRONMENT=production`
267
- - `ORACULO_DATABASE_URL=<postgres-url>`
268
- - `ORACULO_JWT_SECRET_KEY=<secret-largo>`
269
- - `ORACULO_ALLOWED_HOSTS=<tu-dominio-onrender>`
270
- - `ORACULO_DOCS_ENABLED=false`
271
 
272
- 4. Subir `pipeline_produccion.pkl` y, cuando exista, `model_manifest.json`.
 
 
 
 
 
 
 
 
273
 
274
- ## Despliegue en Hugging Face Spaces
275
 
276
- Este repositorio ya quedó preparado para un `Docker Space`.
 
 
277
 
278
- Archivos listos para eso:
279
 
280
- - `Dockerfile`
281
- - `.dockerignore`
282
- - front matter de Spaces al inicio de este `README.md`
283
 
284
- Pasos:
285
 
286
- 1. Crea un nuevo Space en Hugging Face.
287
- 2. Selecciona `Docker` como SDK.
288
- 3. Sube este proyecto completo.
289
- 4. En `Settings > Variables and secrets`, configura como mínimo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  - `ORACULO_JWT_SECRET_KEY`
 
 
 
292
  - `ORACULO_SEED_ADMIN_EMAIL`
293
  - `ORACULO_SEED_ADMIN_PASSWORD`
294
- - `ORACULO_ALLOWED_HOSTS`
295
- - `ORACULO_DOCS_ENABLED=true`
296
 
297
- 5. Si quieres persistencia real para SQLite, usa almacenamiento persistente y define:
298
 
299
- ```bash
 
 
300
  ORACULO_DATABASE_URL=sqlite:////data/oraculo.db
301
  ```
302
 
303
- Si no activas almacenamiento persistente, la base será efímera y se reiniciará con el Space.
304
 
305
- Notas importantes para Spaces:
306
 
307
- - Swagger abrirá en `/docs` porque el Space usa `base_path: /docs`.
308
- - El contenedor escucha en `7860`, que es el puerto esperado por el Space.
309
- - El `Dockerfile` arranca con `alembic upgrade head` y luego `uvicorn app.main:app --host 0.0.0.0 --port 7860`.
310
- - No necesitas un archivo `.env` dentro del Space si ya definiste las variables en `Settings > Variables and secrets`.
311
- - Si quieres ocultar Swagger más adelante, cambia `ORACULO_DOCS_ENABLED=false`.
312
- - El modelo `pipeline_produccion.pkl`, `adult.csv` y el código backend deben permanecer en el repositorio o en el contexto del contenedor.
313
 
314
- ### Troubleshooting en Spaces
 
 
315
 
316
- #### El contenedor arrancó, pero el `App` tab dice "ha rechazado la conexión"
317
 
318
- Si en los logs ves algo como esto:
 
 
 
 
 
 
319
 
320
- - `Uvicorn running on http://0.0.0.0:7860`
321
- - `GET /docs HTTP/1.1" 200`
322
 
323
- entonces el contenedor sí arrancó bien y el problema no suele ser el `Dockerfile`.
324
 
325
- La causa más común en este proyecto era de seguridad de embebido:
326
 
327
- - Hugging Face renderiza el `App` tab dentro de un `iframe`.
328
- - Si la API responde con `X-Frame-Options: DENY` o `Content-Security-Policy` con `frame-ancestors 'none'`, el navegador bloquea la interfaz aunque `/docs` responda `200`.
329
 
330
- La versión actual del proyecto ya quedó ajustada para ese escenario:
 
 
 
331
 
332
- - mantiene headers estrictos para tráfico normal
333
- - permite embebido únicamente cuando la petición llega desde `*.hf.space`
334
- - conserva Swagger funcional en `/docs`
335
 
336
- Checklist rápida cuando pase esto:
337
 
338
- 1. Abre primero la URL directa del Space, por ejemplo `https://tu-space.hf.space/docs`.
339
- 2. Si la URL directa carga y el `App` tab no, haz `Factory reboot` del Space.
340
- 3. Verifica que tu Space realmente tenga la última versión del repositorio.
341
- 4. Revisa que `ORACULO_ALLOWED_HOSTS` incluya `*.hf.space,*.huggingface.co`.
342
- 5. Confirma que `ORACULO_DOCS_ENABLED=true` si quieres exponer Swagger.
343
 
344
- #### No tengo `.env` en Hugging Face
345
 
346
- Eso es normal.
347
 
348
- - En Spaces no necesitas subir `.env`.
349
- - Hugging Face inyecta variables y secretos desde la configuración del Space.
350
- - Localmente puedes usar `.env` para desarrollar.
 
351
 
352
- #### SQLite se reinicia sola en Spaces
353
 
354
- Eso también es normal si no configuraste almacenamiento persistente.
355
 
356
- - Sin volumen persistente, la base local es efímera.
357
- - Si quieres conservar usuarios, historial y seeds entre reinicios, usa `/data` y configura:
358
 
359
- ```bash
360
- ORACULO_DATABASE_URL=sqlite:////data/oraculo.db
361
- ```
362
 
363
- ## Qué falta para una versión todavía más dura
 
 
 
 
 
 
 
364
 
365
- Si quieres llevarla más arriba todavía, las siguientes mejoras son naturales:
366
 
367
- - rate limiting distribuido con Redis
368
- - refresh tokens
369
- - roles más finos (`admin`, `analyst`, `service`)
370
- - observabilidad con Prometheus / OpenTelemetry
371
- - CI con lint, type-check y cobertura
372
- - separación formal entre API pública e interna
373
- - Postgres nativo en desarrollo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- ## Resumen ejecutivo
376
 
377
- Esta versión ya no es una API improvisada alrededor de un notebook. Ahora tienes una base con:
378
 
379
- - arquitectura limpia
380
- - autenticación
381
- - persistencia
382
- - auditoría
383
- - migraciones
384
- - seguridad razonable
385
- - tests HTTP exhaustivos
386
- - contrato más sano entre notebook y producción
387
 
388
- Es una base seria para seguir construyendo.
 
1
  ---
2
  title: Oraculo Adult Income API
3
+ emoji: 🧠
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: docker
 
9
  pinned: false
10
  ---
11
 
12
+ # 🧠 Oráculo Adult Income API
13
 
14
+ <p align="center">
15
+ <img alt="Python" src="https://img.shields.io/badge/Python-3.11+-3776AB?logo=python&logoColor=white">
16
+ <img alt="FastAPI" src="https://img.shields.io/badge/FastAPI-API%20REST-009688?logo=fastapi&logoColor=white">
17
+ <img alt="SQLAlchemy" src="https://img.shields.io/badge/SQLAlchemy-2.0-red?logo=sqlalchemy&logoColor=white">
18
+ <img alt="Alembic" src="https://img.shields.io/badge/Alembic-Migrations-4B5563">
19
+ <img alt="JWT" src="https://img.shields.io/badge/Auth-JWT-black">
20
+ <img alt="LightGBM" src="https://img.shields.io/badge/Model-LightGBM-7CB342">
21
+ <img alt="Docker" src="https://img.shields.io/badge/Deploy-Docker-2496ED?logo=docker&logoColor=white">
22
+ </p>
23
 
24
+ <p align="center">
25
+ API REST para inferencia del problema <strong>Adult Census Income</strong>, con autenticación, persistencia, trazabilidad, validaciones estrictas,
26
+ middlewares de seguridad y una capa de compatibilidad entre el notebook de entrenamiento y el backend de producción.
27
+ </p>
28
 
29
+ ---
30
 
31
+ ## Qué es este proyecto
 
 
32
 
33
+ `oraculo_api` es el backend de inferencia estructurada del ecosistema Oráculo.
34
 
35
+ Su responsabilidad no es solamente “cargar un `.pkl` y responder una predicción”, sino construir una frontera sólida entre:
36
 
37
+ - el trabajo analítico hecho en notebook,
38
+ - el artefacto serializado del modelo,
39
+ - el contrato HTTP consumido por otras capas,
40
+ - la autenticación de usuarios,
41
+ - y la trazabilidad persistente de cada inferencia.
 
 
 
 
 
 
 
42
 
43
+ En términos prácticos, este servicio permite:
44
 
45
+ - registrar usuarios;
46
+ - autenticar con JWT;
47
+ - validar payloads del dataset Adult Income;
48
+ - ejecutar predicciones sobre el pipeline cargado;
49
+ - guardar historial por usuario;
50
+ - consultar predicciones anteriores;
51
+ - verificar salud de la API, del modelo y de la base de datos;
52
+ - desplegar localmente, en Docker, Render o Hugging Face Spaces.
53
 
54
+ > ⚠️ Además, el backend incluye una capa de compatibilidad para artefactos exportados desde notebook. Si el `.pkl` no serializa todas las recetas de feature engineering, la clase `PipelineProduccionMLOps` puede reconstruir parte de esas reglas a partir de artefactos y del dataset de referencia `adult.csv`.
55
 
56
+ ---
 
 
 
57
 
58
+ ## 🎯 Objetivo del backend
59
 
60
+ Esta API está diseñada para resolver cuatro necesidades del proyecto:
61
 
62
+ 1. **Exponer inferencia de modelo como servicio HTTP estable y autenticado.**
63
+ 2. **Blindar el salto notebook → backend**, evitando que el modelo quede atrapado en un entorno puramente exploratorio.
64
+ 3. **Persistir evidencia operativa** de cada solicitud de predicción: payload, versión del modelo, request id, latencia y usuario.
65
+ 4. **Servir como capa fuente de verdad** para otras aplicaciones del ecosistema, como el agente IA y la interfaz web.
 
 
 
 
 
66
 
67
+ ---
68
 
69
+ ## 🧱 Stack tecnológico
 
 
 
 
 
 
 
 
 
 
70
 
71
+ ### Backend y servidor
72
 
73
+ - **FastAPI**: framework principal de la API.
74
+ - **Uvicorn**: servidor ASGI.
75
+ - **Starlette middlewares**: capa de compresión, hosts confiables y middleware base.
76
+ - **python-multipart**: soporte de cuerpos multipart cuando sea necesario.
77
+
78
+ ### Configuración y seguridad
79
+
80
+ - **Pydantic v2**: validaciones, contratos de entrada y salida.
81
+ - **pydantic-settings**: configuración por variables de entorno.
82
+ - **python-dotenv**: soporte para `.env` local.
83
+ - **bcrypt**: hashing de contraseñas.
84
+ - **PyJWT**: creación y validación de tokens JWT.
85
+
86
+ ### Persistencia y migraciones
87
+
88
+ - **SQLAlchemy 2.0**: ORM y acceso a base de datos.
89
+ - **Alembic**: migraciones versionadas.
90
+ - **SQLite** por defecto, con soporte para cambiar a **PostgreSQL** vía `DATABASE_URL`.
91
+
92
+ ### ML y datos
93
+
94
+ - **joblib**: carga del artefacto serializado.
95
+ - **LightGBM**: modelo principal del pipeline.
96
+ - **numpy**, **pandas**, **scikit-learn**, **scipy**: base del pipeline tabular y de las transformaciones.
97
+
98
+ ### Testing
99
+
100
+ - **pytest**: framework de pruebas.
101
+ - **httpx / TestClient**: validación de endpoints y contrato HTTP.
102
+ - **pytest-asyncio**: soporte adicional para contextos asíncronos de prueba.
103
+
104
+ ---
105
+
106
+ ## 📦 Dependencias exactas (`requirements.txt`)
107
+
108
+ ### API / Web
109
+
110
+ - `fastapi==0.135.3`
111
+ - `uvicorn==0.44.0`
112
+ - `httptools==0.7.1`
113
+ - `watchfiles==1.1.1`
114
+ - `websockets==16.0`
115
+ - `python-multipart==0.0.26`
116
+
117
+ ### Configuración / Seguridad
118
+
119
+ - `bcrypt==5.0.0`
120
+ - `PyJWT==2.12.1`
121
+ - `pydantic==2.12.5`
122
+ - `pydantic-settings==2.13.1`
123
+ - `python-dotenv==1.2.2`
124
+
125
+ ### Base de datos / ORM / Migraciones
126
+
127
+ - `SQLAlchemy==2.0.49`
128
+ - `alembic==1.18.4`
129
+
130
+ ### Machine Learning / Data
131
+
132
+ - `joblib==1.5.3`
133
+ - `lightgbm==4.6.0`
134
+ - `numpy==2.4.4`
135
+ - `pandas==3.0.2`
136
+ - `scikit-learn==1.8.0`
137
+ - `scipy==1.17.1`
138
+
139
+ ### Testing
140
+
141
+ - `httpx==0.28.1`
142
+ - `pytest==9.0.3`
143
+ - `pytest-asyncio==1.3.0`
144
+
145
+ ---
146
+
147
+ ## 🗂️ Estructura real del proyecto
148
+
149
+ ```text
150
+ oraculo_api/
151
+ ├── .dockerignore
152
+ ├── .env.example
153
+ ├── Dockerfile
154
+ ├── README.md
155
+ ├── requirements.txt
156
+ ├── alembic.ini
157
+ ├── adult.csv
158
+ ├── compas-scores-raw.csv
159
+ ├── EDA_For_All_Tree_clean.ipynb
160
+ ├── oraculo.db
161
+ ├── alembic/
162
+ │ └── env.py
163
+ ├── app/
164
+ │ ├── main.py
165
+ │ ├── api/
166
+ │ │ ├── dependencies.py
167
+ │ │ ├── router.py
168
+ │ │ └── v1/
169
+ │ │ ├── router.py
170
+ │ │ └── endpoints/
171
+ │ │ ├── auth.py
172
+ │ │ ├── health.py
173
+ │ │ └── predictions.py
174
+ │ ├── core/
175
+ │ │ ├── config.py
176
+ │ │ ├── error_handlers.py
177
+ │ │ ├── exceptions.py
178
+ │ │ ├── logging.py
179
+ │ │ ├── middleware.py
180
+ │ │ └── security.py
181
+ │ ├── db/
182
+ │ │ ├── __init__.py
183
+ │ │ ├── base.py
184
+ │ │ ├── session.py
185
+ │ │ ├── seeds.py
186
+ │ │ ├── models/
187
+ │ │ │ ├── __init__.py
188
+ │ │ │ ├── user.py
189
+ │ │ │ └── prediction_log.py
190
+ │ │ └── repositories/
191
+ │ │ ├── __init__.py
192
+ │ │ ├── users.py
193
+ │ │ └── predictions.py
194
+ │ ├── ml/
195
+ │ │ ├── custom_transformers.py
196
+ │ │ ├── model_manager.py
197
+ │ │ └── pipeline_produccion.pkl
198
+ │ ├── schemas/
199
+ │ │ ├── auth.py
200
+ │ │ ├── common.py
201
+ │ │ ├── health.py
202
+ │ │ └── prediction.py
203
+ │ └── services/
204
+ │ ├── __init__.py
205
+ │ ├── auth.py
206
+ │ ├── health.py
207
+ │ └── prediction.py
208
+ └── tests/
209
+ ├── __init__.py
210
+ ├── conftest.py
211
+ └── api/
212
+ ├── test_auth_api.py
213
+ ├── test_health_api.py
214
+ └── test_prediction_api.py
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 🧭 Arquitectura por capas
220
+
221
+ ```mermaid
222
+ flowchart TD
223
+ A[Cliente / Swagger / Web / Agente] --> B[FastAPI Routers]
224
+ B --> C[Dependencies]
225
+ C --> D[Services]
226
+ D --> E1[Repositories / SQLAlchemy]
227
+ D --> E2[ModelManager / PipelineProduccionMLOps]
228
+ E1 --> F[(SQLite / PostgreSQL)]
229
+ E2 --> G[pipeline_produccion.pkl]
230
+ E2 --> H[model_manifest.json]
231
+ E2 --> I[adult.csv]
232
+ ```
233
+
234
+ ### Flujo interno de una predicción
235
+
236
+ 1. El cliente envía un `POST /api/v1/predictions`.
237
+ 2. FastAPI valida el payload con `PredictionInput`.
238
+ 3. `get_current_user` exige un JWT válido.
239
+ 4. `PredictionService` transforma el payload en dos versiones:
240
+ - **input payload** con aliases del contrato HTTP;
241
+ - **normalized payload** con nombres internos Python-friendly.
242
+ 5. `ModelManager.predict_one()` ejecuta el pipeline cargado.
243
+ 6. Se calcula la latencia.
244
+ 7. `PredictionRepository.create()` persiste el evento completo en `prediction_logs`.
245
+ 8. La respuesta vuelve con `id`, `prediction`, `probability`, `request_id`, `model_version` y payloads trazables.
246
+
247
+ ---
248
+
249
+ ## 🧠 Qué hace cada capa
250
+
251
+ ### `app/main.py`
252
+
253
+ Es el punto de entrada de la aplicación.
254
+
255
+ Responsabilidades:
256
+
257
+ - crear la instancia FastAPI;
258
+ - configurar logging;
259
+ - inicializar `engine` y `session_factory`;
260
+ - crear tablas automáticamente si está habilitado;
261
+ - seedear un administrador inicial;
262
+ - cargar el modelo al arrancar;
263
+ - instalar middlewares;
264
+ - registrar handlers globales de error;
265
+ - exponer el endpoint raíz `/`.
266
+
267
+ ### `app/api/`
268
+
269
+ Contiene la superficie HTTP del sistema.
270
+
271
+ - `router.py`: compone el router principal.
272
+ - `v1/router.py`: agrupa los endpoints bajo `/api/v1`.
273
+ - `v1/endpoints/auth.py`: registro, login y `me`.
274
+ - `v1/endpoints/health.py`: salud `live` y `ready`.
275
+ - `v1/endpoints/predictions.py`: creación, listado y consulta de predicciones.
276
+
277
+ ### `app/api/dependencies.py`
278
+
279
+ Resuelve dependencias reutilizables de FastAPI:
280
+
281
+ - settings actuales;
282
+ - model manager cargado en `app.state`;
283
+ - sesiones de base de datos;
284
+ - repositorios;
285
+ - servicios;
286
+ - usuario autenticado;
287
+ - usuario administrador.
288
+
289
+ ### `app/core/`
290
+
291
+ Es la capa transversal del backend.
292
+
293
+ - `config.py`: define `Settings`, lee variables de entorno y normaliza listas como `ALLOWED_HOSTS` y `CORS_ALLOW_ORIGINS`.
294
+ - `security.py`: hashing, verificación de passwords, creación y decodificación de JWT, extracción del bearer token.
295
+ - `middleware.py`: request id, client ip, headers de seguridad, límite de tamaño y rate limiting.
296
+ - `exceptions.py`: errores tipados de dominio y operación.
297
+ - `error_handlers.py`: respuestas JSON consistentes para errores esperados e inesperados.
298
+ - `logging.py`: configura logging estructurado a nivel global.
299
+
300
+ ### `app/db/`
301
+
302
+ Contiene persistencia y modelo relacional.
303
+
304
+ - `base.py`: `DeclarativeBase`, naming convention y `TimestampMixin`.
305
+ - `session.py`: engine, factory de sesiones, chequeo de conexión y manejo transaccional.
306
+ - `models/user.py`: entidad `users`.
307
+ - `models/prediction_log.py`: entidad `prediction_logs`.
308
+ - `repositories/users.py`: consultas y creación de usuarios.
309
+ - `repositories/predictions.py`: creación, listado filtrado y consulta de predicciones por usuario.
310
+ - `seeds.py`: creación opcional del admin bootstrap.
311
+
312
+ ### `app/services/`
313
+
314
+ Aquí está la lógica de negocio, separada del transporte HTTP.
315
 
316
+ - `AuthService`: registro, login y recuperación de usuario.
317
+ - `HealthService`: estado de vida y readiness real.
318
+ - `PredictionService`: inferencia, hashing de payload, persistencia y respuestas paginadas.
319
+
320
+ ### `app/ml/`
321
+
322
+ Es la capa de integración con el artefacto del modelo.
323
+
324
+ - `model_manager.py`: carga el `.pkl`, lee `model_manifest.json`, verifica estado y expone `predict`, `predict_proba` y `predict_one`.
325
+ - `custom_transformers.py`: define `PipelineProduccionMLOps`, que actúa como puente de compatibilidad entre el notebook exportado y el backend.
326
+
327
+ ### `app/schemas/`
328
+
329
+ Modelos Pydantic del contrato público.
330
+
331
+ - `auth.py`: DTOs de registro, login, token y usuario.
332
+ - `health.py`: respuestas de health.
333
+ - `prediction.py`: payload de entrada, detalle de predicción y lista paginada.
334
+ - `common.py`: base schema y metadatos de paginación.
335
+
336
+ ### `tests/`
337
+
338
+ La suite prueba el contrato HTTP desacoplándolo del modelo real cuando conviene.
339
+
340
+ - `conftest.py` crea `FakeModelManager`, app de prueba, token, headers y payload válido.
341
+ - `test_auth_api.py` valida registro, conflicto, login y `me`.
342
+ - `test_health_api.py` valida raíz, `live` y `ready`.
343
+ - `test_prediction_api.py` valida autenticación, creación, filtros, aislamiento por usuario, payload grande, errores y recuperación por id.
344
+
345
+ ---
346
+
347
+ ## 🔐 Seguridad implementada
348
 
349
  ### Autenticación
350
 
351
+ - Login con email y password.
352
+ - Emisión de **JWT** firmado con `HS256` por defecto.
353
+ - Expiración configurable (`ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES`).
354
+ - Endpoint `/api/v1/auth/me` para resolver el usuario autenticado.
355
+
356
+ ### Contraseñas
357
+
358
+ - Hash con **bcrypt**.
359
+ - Si la contraseña supera el límite interno de bcrypt (72 bytes), el código hace **pre-hashing SHA-256** antes de `bcrypt.hashpw`, evitando truncamientos silenciosos.
360
+
361
+ ### Validación de entrada
362
+
363
+ - `extra="forbid"` en esquemas sensibles para bloquear campos sorpresa.
364
+ - Validaciones de longitud, tipo y rango.
365
+ - Passwords fuertes obligatorias en registro:
366
+ - mayúscula,
367
+ - minúscula,
368
+ - número,
369
+ - carácter especial,
370
+ - longitud mínima de 12.
371
+
372
+ ### Middlewares defensivos
373
+
374
+ - **TrustedHostMiddleware** para hosts permitidos.
375
+ - **GZipMiddleware** para compresión de respuestas.
376
+ - **SecurityHeadersMiddleware** con:
377
+ - `X-Content-Type-Options: nosniff`
378
+ - `Referrer-Policy: no-referrer`
379
+ - `Permissions-Policy`
380
+ - `Cache-Control: no-store`
381
+ - `Pragma: no-cache`
382
+ - `Content-Security-Policy`
383
+ - **MaxRequestSizeMiddleware** para rechazar payloads demasiado grandes.
384
+ - **RateLimitMiddleware** in-memory por IP.
385
+ - **RequestContextMiddleware** para generar `X-Request-ID` y `X-Process-Time-MS`.
386
+
387
+ ### Errores controlados
388
+
389
+ Todos los errores retornan JSON consistente bajo la forma:
390
+
391
+ ```json
392
+ {
393
+ "error": {
394
+ "code": "validation_error",
395
+ "message": "Request validation failed.",
396
+ "detail": {},
397
+ "request_id": "..."
398
+ }
399
+ }
400
+ ```
401
 
402
+ ---
403
 
404
+ ## 🗃️ Modelo de datos
 
 
405
 
406
+ ### Tabla `users`
407
 
408
+ Campos principales:
409
 
410
+ - `id`
411
+ - `email`
412
+ - `full_name`
413
+ - `password_hash`
414
+ - `role`
415
+ - `is_active`
416
+ - `created_at`
417
+ - `updated_at`
 
 
418
 
419
+ ### Tabla `prediction_logs`
420
 
421
+ Campos principales:
422
 
423
+ - `id`
424
+ - `user_id`
425
+ - `request_id`
426
+ - `ip_address`
427
+ - `label`
428
+ - `probability`
429
+ - `latency_ms`
430
+ - `model_version`
431
+ - `payload_hash`
432
+ - `input_payload`
433
+ - `normalized_payload`
434
+ - `notes`
435
+ - `created_at`
436
+ - `updated_at`
437
 
438
+ Esto convierte cada inferencia en un evento auditable y recuperable.
 
439
 
440
+ ---
441
 
442
+ ## 🤖 Integración con el modelo
 
 
 
 
 
443
 
444
+ ### `ModelManager`
445
 
446
+ El `ModelManager` es el punto central de carga e inferencia. Se encarga de:
447
 
448
+ - cargar el artefacto `pipeline_produccion.pkl`;
449
+ - registrar una clase puente en `__main__` para que `joblib` pueda deserializar objetos definidos originalmente en notebook;
450
+ - intentar leer `model_manifest.json` si existe;
451
+ - exponer `predict`, `predict_proba` y `predict_one`;
452
+ - encapsular fallas como `ServiceUnavailableError` o `ModelInferenceError`.
453
 
454
+ ### `PipelineProduccionMLOps`
455
 
456
+ Esta clase existe para hacer el artefacto más robusto en producción.
 
 
457
 
458
+ Responsabilidades principales:
459
 
460
+ - normalizar nombres de columnas (`snake_case`, puntos, caracteres raros);
461
+ - limpiar texto categórico;
462
+ - reconstruir recetas faltantes si el notebook no serializó todo correctamente;
463
+ - aplicar rare labeling;
464
+ - aplicar target encoding y mapeos binarios;
465
+ - reconstruir fórmulas derivadas;
466
+ - generar ratios matemáticos;
467
+ - aplicar winsorización;
468
+ - reusar escaladores si existen;
469
+ - eliminar columnas de fuga o basura si están declaradas;
470
+ - realinear el DataFrame final con las features esperadas por el modelo entrenado.
471
 
472
+ ### Artefactos esperados
 
473
 
474
+ El backend espera, como mínimo:
475
 
476
+ - `app/ml/pipeline_produccion.pkl`
 
 
 
 
 
 
 
477
 
478
+ Y opcionalmente:
479
 
480
+ - `app/ml/model_manifest.json`
481
 
482
+ Adicionalmente, puede usar:
 
 
483
 
484
+ - `adult.csv` como dataset de referencia para recomponer recetas faltantes de ingeniería de features.
485
 
486
+ ---
487
+
488
+ ## 🌱 Variables de entorno
489
+
490
+ Crea un archivo `.env` local a partir de `.env.example`.
491
+
492
+ ### Variables principales
493
+
494
+ | Variable | Descripción |
495
+ | ------------------------------------- | ------------------------------------------------------------------ |
496
+ | `ORACULO_APP_NAME` | Nombre público del servicio. |
497
+ | `ORACULO_APP_VERSION` | Versión de la API. |
498
+ | `ORACULO_ENVIRONMENT` | Entorno (`local`, `development`, `test`, `staging`, `production`). |
499
+ | `ORACULO_DEBUG` | Activa modo debug. |
500
+ | `ORACULO_DATABASE_URL` | URL de conexión a BD. Por defecto SQLite local. |
501
+ | `ORACULO_DATABASE_ECHO` | Log SQL de SQLAlchemy. |
502
+ | `ORACULO_AUTO_CREATE_TABLES` | Crea tablas automáticamente al arrancar. |
503
+ | `ORACULO_AUTO_SEED_ADMIN` | Activa siembra automática de admin. |
504
+ | `ORACULO_SEED_ADMIN_EMAIL` | Email del admin bootstrap. |
505
+ | `ORACULO_SEED_ADMIN_PASSWORD` | Password del admin bootstrap. |
506
+ | `ORACULO_SEED_ADMIN_NAME` | Nombre visible del admin bootstrap. |
507
+ | `ORACULO_MODEL_PATH` | Ruta al `.pkl` de producción. |
508
+ | `ORACULO_JWT_SECRET_KEY` | Clave secreta JWT. |
509
+ | `ORACULO_JWT_ALGORITHM` | Algoritmo JWT. |
510
+ | `ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES` | Duración del token. |
511
+ | `ORACULO_ALLOWED_HOSTS` | Hosts permitidos. |
512
+ | `ORACULO_CORS_ALLOW_ORIGINS` | Orígenes CORS permitidos. |
513
+ | `ORACULO_MAX_REQUEST_SIZE_BYTES` | Tamaño máximo del body. |
514
+ | `ORACULO_RATE_LIMIT_ENABLED` | Activa rate limit. |
515
+ | `ORACULO_RATE_LIMIT_REQUESTS` | Número máximo de requests por ventana. |
516
+ | `ORACULO_RATE_LIMIT_WINDOW_SECONDS` | Duración de la ventana. |
517
+ | `ORACULO_DOCS_ENABLED` | Activa/desactiva `/docs`, `/redoc` y `/openapi.json`. |
518
+
519
+ ### Ejemplo `.env`
520
+
521
+ ```env
522
+ ORACULO_APP_NAME=Oraculo Adult Income API
523
+ ORACULO_APP_VERSION=2.0.0
524
+ ORACULO_ENVIRONMENT=development
525
+ ORACULO_DEBUG=false
526
+
527
+ ORACULO_DATABASE_URL=sqlite:///./oraculo.db
528
+ ORACULO_DATABASE_ECHO=false
529
+ ORACULO_AUTO_CREATE_TABLES=true
530
+ ORACULO_AUTO_SEED_ADMIN=true
531
+ ORACULO_SEED_ADMIN_EMAIL=admin@example.com
532
+ ORACULO_SEED_ADMIN_PASSWORD=ChangeMe!12345
533
+ ORACULO_SEED_ADMIN_NAME=Administrator
534
+
535
+ ORACULO_MODEL_PATH=app/ml/pipeline_produccion.pkl
536
+
537
+ ORACULO_JWT_SECRET_KEY=replace-this-with-a-long-random-secret-at-least-32-chars
538
+ ORACULO_JWT_ALGORITHM=HS256
539
+ ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES=60
540
+
541
+ ORACULO_ALLOWED_HOSTS=localhost,127.0.0.1,*.hf.space,*.huggingface.co
542
+ ORACULO_CORS_ALLOW_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
543
+
544
+ ORACULO_MAX_REQUEST_SIZE_BYTES=32768
545
+ ORACULO_RATE_LIMIT_ENABLED=true
546
+ ORACULO_RATE_LIMIT_REQUESTS=60
547
+ ORACULO_RATE_LIMIT_WINDOW_SECONDS=60
548
+
549
+ ORACULO_DOCS_ENABLED=true
550
  ```
551
 
552
+ ---
553
 
554
+ ## 🚀 Instalación local paso a paso
555
 
556
+ ### 1) Clonar el proyecto
 
 
557
 
558
+ ```bash
559
+ git clone https://github.com/DiiegoA/Proyecto_modelo_IA.git
560
+ cd Proyecto_modelo_IA/oraculo_api
561
+ ```
562
 
563
+ ### 2) Crear entorno virtual
564
 
565
+ #### Linux / macOS
566
 
567
+ ```bash
568
+ python -m venv .venv
569
+ source .venv/bin/activate
570
+ ```
 
 
 
 
 
 
571
 
572
+ #### Windows (PowerShell)
573
 
574
+ ```powershell
575
+ python -m venv .venv
576
+ .\.venv\Scripts\Activate.ps1
577
+ ```
578
 
579
+ ### 3) Instalar dependencias
580
 
581
  ```bash
582
+ pip install --upgrade pip
 
583
  pip install -r requirements.txt
584
+ ```
585
+
586
+ ### 4) Configurar variables de entorno
587
+
588
+ ```bash
589
+ cp .env.example .env
590
+ ```
591
+
592
+ En Windows, copia el archivo manualmente o usa el explorador.
593
+
594
+ ### 5) Ejecutar migraciones
595
+
596
+ ```bash
597
  alembic upgrade head
598
+ ```
599
+
600
+ ### 6) Iniciar el servidor
601
+
602
+ ```bash
603
  uvicorn app.main:app --reload
604
  ```
605
 
606
+ ### 7) Abrir documentación interactiva
607
+
608
+ - Swagger UI: `http://127.0.0.1:8000/docs`
609
+ - ReDoc: `http://127.0.0.1:8000/redoc`
610
+
611
+ ---
612
+
613
+ ## 🧪 Cómo probar la API
614
+
615
+ ### Health checks
616
+
617
+ ```bash
618
+ curl http://127.0.0.1:8000/
619
+ curl http://127.0.0.1:8000/api/v1/health/live
620
+ curl http://127.0.0.1:8000/api/v1/health/ready
621
+ ```
622
+
623
+ ### Registrar usuario
624
+
625
+ ```bash
626
+ curl -X POST http://127.0.0.1:8000/api/v1/auth/register \
627
+ -H "Content-Type: application/json" \
628
+ -d '{
629
+ "email": "user@example.com",
630
+ "full_name": "Test User",
631
+ "password": "StrongPass!123"
632
+ }'
633
+ ```
634
+
635
+ ### Login
636
+
637
+ ```bash
638
+ curl -X POST http://127.0.0.1:8000/api/v1/auth/login \
639
+ -H "Content-Type: application/json" \
640
+ -d '{
641
+ "email": "user@example.com",
642
+ "password": "StrongPass!123"
643
+ }'
644
+ ```
645
+
646
+ ### Crear predicción
647
 
648
+ ```bash
649
+ curl -X POST http://127.0.0.1:8000/api/v1/predictions \
650
+ -H "Content-Type: application/json" \
651
+ -H "Authorization: Bearer TU_TOKEN" \
652
+ -d '{
653
+ "age": 45,
654
+ "workclass": "Private",
655
+ "fnlwgt": 250000,
656
+ "education": "Masters",
657
+ "education.num": 14,
658
+ "marital.status": "Married-civ-spouse",
659
+ "occupation": "Exec-managerial",
660
+ "relationship": "Husband",
661
+ "race": "White",
662
+ "sex": "Male",
663
+ "capital.gain": 15000,
664
+ "capital.loss": 0,
665
+ "hours.per.week": 50,
666
+ "native.country": "United-States"
667
+ }'
668
+ ```
669
 
670
+ ### Listar historial
671
+
672
+ ```bash
673
+ curl -H "Authorization: Bearer TU_TOKEN" \
674
+ "http://127.0.0.1:8000/api/v1/predictions?skip=0&limit=20"
675
+ ```
676
 
677
+ ### Filtrar historial
678
 
679
+ ```bash
680
+ curl -H "Authorization: Bearer TU_TOKEN" \
681
+ "http://127.0.0.1:8000/api/v1/predictions?label=%3E50K&min_probability=0.8"
682
+ ```
 
 
 
 
 
 
 
683
 
684
+ ### Consultar una predicción por id
685
 
686
  ```bash
687
+ curl -H "Authorization: Bearer TU_TOKEN" \
688
+ http://127.0.0.1:8000/api/v1/predictions/PREDICTION_ID
689
+ ```
690
+
691
+ ---
692
+
693
+ ## 📡 Endpoints disponibles
694
+
695
+ ### Salud
696
+
697
+ | Método | Ruta | Descripción |
698
+ | ------ | ---------------------- | ---------------------------------------- |
699
+ | `GET` | `/` | Metadatos básicos del servicio. |
700
+ | `GET` | `/api/v1/health/live` | Verifica que la API está viva. |
701
+ | `GET` | `/api/v1/health/ready` | Verifica base de datos + modelo cargado. |
702
+
703
+ ### Autenticación
704
+
705
+ | Método | Ruta | Descripción |
706
+ | ------ | ----------------------- | --------------------------- |
707
+ | `POST` | `/api/v1/auth/register` | Registro de usuario. |
708
+ | `POST` | `/api/v1/auth/login` | Login y emisión de token. |
709
+ | `GET` | `/api/v1/auth/me` | Usuario autenticado actual. |
710
+
711
+ ### Predicciones
712
+
713
+ | Método | Ruta | Descripción |
714
+ | ------ | ------------------------------------- | -------------------------------------------- |
715
+ | `POST` | `/api/v1/predictions` | Ejecuta una predicción y la persiste. |
716
+ | `GET` | `/api/v1/predictions` | Lista historial paginado del usuario. |
717
+ | `GET` | `/api/v1/predictions/{prediction_id}` | Recupera una predicción puntual del usuario. |
718
+
719
+ ---
720
+
721
+ ## 🧾 Contrato de entrada para predicción
722
+
723
+ El payload que espera la API está alineado con el dataset Adult Income y admite aliases con puntos:
724
+
725
+ | Campo HTTP | Campo normalizado | Tipo |
726
+ | ---------------- | ----------------- | ---------------- |
727
+ | `age` | `age` | `int` |
728
+ | `workclass` | `workclass` | `str` |
729
+ | `fnlwgt` | `fnlwgt` | `int` |
730
+ | `education` | `education` | `str` |
731
+ | `education.num` | `education_num` | `int` |
732
+ | `marital.status` | `marital_status` | `str` |
733
+ | `occupation` | `occupation` | `str` |
734
+ | `relationship` | `relationship` | `str` |
735
+ | `race` | `race` | `str` |
736
+ | `sex` | `sex` | `Male \| Female` |
737
+ | `capital.gain` | `capital_gain` | `int` |
738
+ | `capital.loss` | `capital_loss` | `int` |
739
+ | `hours.per.week` | `hours_per_week` | `int` |
740
+ | `native.country` | `native_country` | `str` |
741
+
742
+ ### Reglas de validación destacadas
743
+
744
+ - `age`: entre `17` y `100`
745
+ - `fnlwgt`: entre `1` y `2_000_000`
746
+ - `education.num`: entre `1` y `16`
747
+ - `capital.gain`: entre `0` y `100_000`
748
+ - `capital.loss`: entre `0` y `10_000`
749
+ - `hours.per.week`: entre `1` y `99`
750
+ - categorías no vacías, sin caracteres de control y con longitud máxima razonable
751
+
752
+ ---
753
+
754
+ ## 📚 Respuesta de predicción
755
+
756
+ Una predicción devuelve información útil tanto para negocio como para auditoría:
757
+
758
+ ```json
759
+ {
760
+ "id": "uuid",
761
+ "prediction": ">50K",
762
+ "probability": 0.91,
763
+ "is_counterfactual_applied": false,
764
+ "execution_time_ms": 12.34,
765
+ "model_version": "1.0.0",
766
+ "request_id": "uuid",
767
+ "created_at": "2026-01-01T00:00:00Z",
768
+ "input_payload": {},
769
+ "normalized_payload": {}
770
+ }
771
  ```
772
 
773
+ ### Diferencia entre `input_payload` y `normalized_payload`
774
 
775
+ - `input_payload`: conserva la forma del contrato HTTP, incluyendo aliases como `education.num`.
776
+ - `normalized_payload`: usa nombres internos Python (`education_num`, `hours_per_week`, etc.).
777
 
778
+ Esto es útil para depuración, auditoría y trazabilidad del contrato público frente al contrato interno.
779
 
780
+ ---
781
+
782
+ ## 🧪 Tests
783
+
784
+ La suite existente valida el comportamiento público del backend con una estrategia pragmática:
785
+
786
+ ### Qué se prueba
787
 
788
+ - registro exitoso;
789
+ - rechazo por usuario duplicado;
790
+ - login correcto;
791
+ - login con credenciales inválidas;
792
+ - endpoint `/me` protegido;
793
+ - raíz `/`;
794
+ - `health/live` y `health/ready`;
795
+ - creación de predicción autenticada;
796
+ - rechazo de payload inválido;
797
+ - rechazo de payload excesivo;
798
+ - filtros por `label` y `min_probability`;
799
+ - consulta por `prediction_id`;
800
+ - aislamiento de historial entre usuarios;
801
+ - mapeo controlado de errores del modelo.
802
+
803
+ ### Cómo se ejecutan
804
 
805
  ```bash
806
+ pytest -q
807
  ```
808
 
809
+ ### Estrategia de pruebas
810
+
811
+ La suite usa un `FakeModelManager` en `tests/conftest.py` para:
812
 
813
+ - desacoplar el contrato HTTP del artefacto real;
814
+ - acelerar las pruebas;
815
+ - validar reglas de negocio y seguridad sin depender siempre del `.pkl`.
 
 
816
 
817
+ ---
818
+
819
+ ## 🛠️ Migraciones con Alembic
820
+
821
+ ### Aplicar migraciones
822
+
823
+ ```bash
824
+ alembic upgrade head
825
+ ```
826
 
827
+ ### Crear una nueva migración
828
 
829
+ ```bash
830
+ alembic revision --autogenerate -m "descripcion"
831
+ ```
832
 
833
+ ### Revertir una migración
834
 
835
+ ```bash
836
+ alembic downgrade -1
837
+ ```
838
 
839
+ ### Qué hace `alembic/env.py`
840
 
841
+ - carga `Settings` reales del entorno;
842
+ - inyecta `settings.database_url` en la configuración de Alembic;
843
+ - usa `Base.metadata` como fuente de verdad del esquema;
844
+ - permite migraciones offline y online.
845
+
846
+ ---
847
+
848
+ ## 👤 Seed automático de administrador
849
+
850
+ Si activas estas variables:
851
+
852
+ ```env
853
+ ORACULO_AUTO_SEED_ADMIN=true
854
+ ORACULO_SEED_ADMIN_EMAIL=admin@example.com
855
+ ORACULO_SEED_ADMIN_PASSWORD=ChangeMe!12345
856
+ ORACULO_SEED_ADMIN_NAME=Administrator
857
+ ```
858
+
859
+ al arrancar la aplicación se crea un administrador si no existe uno previo con ese email.
860
+
861
+ Esto es útil para bootstrap local, demos o primeros despliegues.
862
+
863
+ ---
864
+
865
+ ## 🐳 Docker
866
+
867
+ ### Qué hace el `Dockerfile`
868
+
869
+ - usa `python:3.11-slim` como base;
870
+ - instala `libgomp1` para dependencias del stack de ML;
871
+ - crea un usuario no root;
872
+ - instala dependencias desde `requirements.txt`;
873
+ - copia el proyecto completo al contenedor;
874
+ - expone el puerto `7860`;
875
+ - ejecuta `alembic upgrade head` antes de levantar Uvicorn.
876
+
877
+ ### Construcción
878
+
879
+ ```bash
880
+ docker build -t oraculo-api .
881
+ ```
882
+
883
+ ### Ejecución
884
+
885
+ ```bash
886
+ docker run --rm -p 7860:7860 --env-file .env oraculo-api
887
+ ```
888
+
889
+ ### Comando final del contenedor
890
+
891
+ ```bash
892
+ alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 7860
893
+ ```
894
+
895
+ ---
896
+
897
+ ## 🤗 Despliegue en Hugging Face Spaces
898
+
899
+ Este repositorio ya está preparado para un **Docker Space**.
900
+
901
+ ### Puntos importantes
902
+
903
+ - El front matter al inicio del README define `sdk: docker`, `app_port: 7860` y `base_path: /docs`.
904
+ - El contenedor escucha en `7860`.
905
+ - Swagger queda accesible en `/docs`.
906
+ - Los headers de seguridad contemplan el caso especial de embebido en dominios `*.hf.space` y `*.huggingface.co`.
907
+
908
+ ### Variables mínimas recomendadas en Spaces
909
 
910
  - `ORACULO_JWT_SECRET_KEY`
911
+ - `ORACULO_ALLOWED_HOSTS`
912
+ - `ORACULO_DOCS_ENABLED`
913
+ - `ORACULO_DATABASE_URL`
914
  - `ORACULO_SEED_ADMIN_EMAIL`
915
  - `ORACULO_SEED_ADMIN_PASSWORD`
 
 
916
 
917
+ ### Persistencia en Spaces
918
 
919
+ Si quieres que SQLite sobreviva reinicios, usa almacenamiento persistente y configura:
920
+
921
+ ```env
922
  ORACULO_DATABASE_URL=sqlite:////data/oraculo.db
923
  ```
924
 
925
+ ---
926
 
927
+ ## ☁️ Despliegue en Render
928
 
929
+ ### Start Command sugerido
 
 
 
 
 
930
 
931
+ ```bash
932
+ alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORT
933
+ ```
934
 
935
+ ### Variables mínimas recomendadas
936
 
937
+ ```env
938
+ ORACULO_ENVIRONMENT=production
939
+ ORACULO_DATABASE_URL=<postgres-url>
940
+ ORACULO_JWT_SECRET_KEY=<secret-largo>
941
+ ORACULO_ALLOWED_HOSTS=<tu-dominio>
942
+ ORACULO_DOCS_ENABLED=false
943
+ ```
944
 
945
+ ---
 
946
 
947
+ ## 🧯 Troubleshooting
948
 
949
+ ### 1. El modelo no carga
950
 
951
+ Revisa:
 
952
 
953
+ - que `ORACULO_MODEL_PATH` apunte realmente a `app/ml/pipeline_produccion.pkl`;
954
+ - que el archivo exista dentro del contenedor o del entorno local;
955
+ - que `joblib` pueda deserializar el artefacto;
956
+ - que, si usas un manifiesto, `model_manifest.json` esté bien formado.
957
 
958
+ ### 2. `ready` devuelve degradado o error
 
 
959
 
960
+ Revisa:
961
 
962
+ - conexión a la base de datos;
963
+ - permisos del archivo SQLite;
964
+ - existencia del `.pkl`;
965
+ - carga correcta del modelo al arrancar.
 
966
 
967
+ ### 3. El `App` tab de Hugging Face no muestra la app aunque `/docs` responde
968
 
969
+ Esto suele estar relacionado con headers de embebido. El proyecto ya contempla el caso `*.hf.space`, pero vale la pena revisar:
970
 
971
+ - `ORACULO_ALLOWED_HOSTS`;
972
+ - si el Space tiene la última versión desplegada;
973
+ - si hiciste `Factory reboot` tras cambios de seguridad;
974
+ - si `ORACULO_DOCS_ENABLED=true`.
975
 
976
+ ### 4. SQLite se reinicia
977
 
978
+ Si el proveedor no tiene almacenamiento persistente, la BD local es efímera. En producción real, usa Postgres o un volumen persistente.
979
 
980
+ ---
 
981
 
982
+ ## 📈 Mejoras futuras naturales
983
+
984
+ Si quieres endurecer todavía más esta API, las siguientes mejoras son coherentes con la arquitectura actual:
985
 
986
+ - rate limiting distribuido con Redis;
987
+ - refresh tokens;
988
+ - observabilidad con Prometheus u OpenTelemetry;
989
+ - roles más finos (`admin`, `analyst`, `service`);
990
+ - pipeline CI con lint, type-check y cobertura;
991
+ - separación formal entre API pública e interna;
992
+ - Postgres como default de desarrollo colaborativo;
993
+ - versionado explícito del contrato de inferencia y del manifiesto del modelo.
994
 
995
+ ---
996
 
997
+ ## Resumen ejecutivo
998
+
999
+ `oraculo_api` ya no es un backend improvisado alrededor de un notebook.
1000
+
1001
+ Hoy es una base con:
1002
+
1003
+ - arquitectura por capas;
1004
+ - contrato HTTP claro;
1005
+ - autenticación JWT;
1006
+ - hashing robusto de contraseñas;
1007
+ - middlewares de seguridad;
1008
+ - validación estricta de payloads;
1009
+ - persistencia auditada de predicciones;
1010
+ - health checks reales;
1011
+ - migraciones con Alembic;
1012
+ - compatibilidad con artefactos exportados desde notebook;
1013
+ - pruebas automatizadas sobre el contrato principal.
1014
+
1015
+ En otras palabras: esta carpeta es la **capa de inferencia seria y trazable** del ecosistema Oráculo.
1016
+
1017
+ ---
1018
 
1019
+ ## 📌 Recomendación final de uso
1020
 
1021
+ Si alguien entra por primera vez a `oraculo_api`, el orden ideal para entenderlo es:
1022
 
1023
+ 1. `README.md`
1024
+ 2. `app/main.py`
1025
+ 3. `app/api/v1/endpoints/`
1026
+ 4. `app/services/`
1027
+ 5. `app/db/`
1028
+ 6. `app/ml/model_manager.py`
1029
+ 7. `app/ml/custom_transformers.py`
1030
+ 8. `tests/`
1031
 
1032
+ Ese recorrido permite entender primero la interfaz pública, luego la lógica de negocio, después la persistencia y, por último, la capa de compatibilidad del modelo.