# Proyecto final: API de lectura para Sakila

Construye una API local con FastAPI y SQLAlchemy que expone clientes y pagos de la base oficial Sakila. El servicio solo ofrece rutas `GET`; el usuario de MySQL que lo ejecuta debe tener únicamente permiso `SELECT` sobre el esquema de práctica.

## Requisitos y procedencia

- Python 3.11 o posterior.
- MySQL Community Server 8.x instalado localmente.
- La base de muestra oficial [Sakila](https://dev.mysql.com/doc/sakila/en/) instalada en el esquema `sakila`. El [manual de instalación oficial](https://dev.mysql.com/doc/sakila/en/sakila-installation.html) describe cómo obtener el archivo y cargar el esquema y sus datos.
- Los paquetes exactos de Python se fijan en `requirements.txt`.

Este paquete **no contiene** el dump ni filas de Sakila. Los tests crean filas sintéticas temporales en SQLite; sirven para probar contratos HTTP y no se deben presentar como datos del Sakila real. El esquema y los datos oficiales están sujetos al aviso New BSD descrito en la [licencia de Sakila](https://dev.mysql.com/doc/sakila/en/sakila-license.html).

## Archivos

```text
sakila-read-api/
  .env.example
  .gitignore
  README.md
  requirements.txt
  app/
    database.py
    main.py
    models.py
    schemas.py
  starter/README.md
  tests/test_api.py
```

## Preparar y ejecutar en Windows

Abre PowerShell en la carpeta del proyecto:

```powershell
py -3.11 -m venv .venv
.venv\Scripts\Activate.ps1
py -m pip install -r requirements.txt
Copy-Item .env.example .env
```

Edita `.env` con el usuario y la contraseña locales que vas a crear abajo. No quites `.env` del `.gitignore` ni compartas su contenido. En macOS/Linux, usa `python3 -m venv .venv`, activa con `source .venv/bin/activate` y copia el archivo con `cp .env.example .env`.

## Crear una cuenta MySQL de solo lectura

Instala Sakila primero en una instancia **local de práctica** que controles. Entra con una cuenta administradora de esa instancia y crea una cuenta separada para la aplicación:

```sql
CREATE USER 'sakila_reader'@'127.0.0.1'
  IDENTIFIED BY 'reemplaza-esta-clave-local';
GRANT SELECT ON sakila.* TO 'sakila_reader'@'127.0.0.1';
SHOW GRANTS FOR 'sakila_reader'@'127.0.0.1';
```

Reemplaza la clave ficticia por una propia y consérvala solo en `.env`. El grant permite leer el esquema `sakila`; no concede `INSERT`, `UPDATE`, `DELETE`, creación de tablas ni permisos globales. Revisa el resultado de `SHOW GRANTS`. No ejecutes comandos administrativos en una base de producción.

El archivo `.env` usa estos campos:

```text
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=sakila
DB_USER=sakila_reader
DB_PASSWORD=tu-clave-local
```

SQLAlchemy crea la URL con cada campo por separado para manejar caracteres especiales de la contraseña sin concatenación manual. La aplicación comprueba las credenciales cuando una ruta necesita la base; `/health` no verifica MySQL.

## Ejecutar las pruebas

```powershell
py -m pytest -q
```

Las pruebas usan una base SQLite en memoria y datos ficticios creados por los tests; no necesitan MySQL, no cargan el Sakila real y no alteran la base de práctica. Deben comprobar respuestas, filtros, paginación, errores y que no exista un método de escritura.

## Iniciar la API contra Sakila local

Con MySQL activo, Sakila instalado y `.env` listo:

```powershell
py -m uvicorn app.main:app --reload
```

En macOS/Linux usa `python3 -m uvicorn app.main:app --reload`. Abre `http://127.0.0.1:8000/docs` para explorar el contrato. `--reload` es para desarrollo local. La aplicación no configura tablas: Sakila conserva su estructura oficial.

## Endpoints y respuestas esperadas

| Método y ruta | Uso | Respuesta esperada |
| --- | --- | --- |
| `GET /health` | Comprueba que la app responde, sin conectarse a MySQL. | `200` y `{"status":"ok"}` |
| `GET /customers/{customer_id}` | Lee un cliente por su clave primaria. | `200` con `customer_id`, `first_name`, `last_name` y `active`; `404` si no existe. |
| `GET /customers?limit=5&offset=0&active=true` | Lista clientes; `limit` es 1–50, `offset` es 0–10000 y `active` es opcional. | `200` con `items`, `limit` y `offset`, ordenado por `customer_id`. |
| `GET /payments?staff_id=1&min_amount=2&max_amount=10&limit=5` | Filtra pagos con valores numéricos validados y límites. | `200` con `items`, `limit` y `offset`, ordenado por `payment_id`. |
| `GET /payments/summary?staff_id=1` | Resume cantidad e importe por `staff_id`; el filtro es opcional. | `200` con una lista de `staff_id`, `payment_count` y `total_amount`. |

Un cuerpo de cliente tiene esta forma (los nombres y valores salen de tu copia local de Sakila):

```json
{
  "customer_id": 1,
  "first_name": "valor de Sakila",
  "last_name": "valor de Sakila",
  "active": true
}
```

El correo no forma parte de la respuesta. Un `limit=0`, un importe fuera del rango de `DECIMAL(5,2)` o `max_amount` menor que `min_amount` responde `422`. `POST /customers` responde `405`; no existe endpoint de escritura.

## Criterios de aceptación

1. La API inicia con las instrucciones anteriores y consulta el esquema local oficial Sakila.
2. `/health`, cliente individual, lista paginada, pagos filtrados y resumen responden con los campos y estados descritos.
3. Las rutas validan parámetros y limitan las filas por solicitud.
4. Todas las consultas se construyen con expresiones SQLAlchemy; no se acepta texto SQL del visitante.
5. La salida pública excluye `email` y columnas no declaradas en sus esquemas Pydantic.
6. Los tests se ejecutan sin MySQL, usan solo SQLite temporal y limpian la sustitución de dependencia.
7. La API no registra rutas de escritura ni llama a `create_all()` sobre Sakila; el grant de la cuenta es solo `SELECT` sobre `sakila.*`.
8. `.env` queda ignorado por Git; el repositorio y la entrega no contienen credenciales ni el dump original.
9. Los hallazgos distinguen filas Sakila de fixtures sintéticos y no atribuyen desempeño o causalidad a `staff_id`.

## Rúbrica (100 puntos)

| Criterio | Puntos |
| --- | ---: |
| Conexión, configuración y sesión con cierre | 20 |
| Endpoints, paginación, filtros y contratos de respuesta | 25 |
| Permisos de solo lectura y manejo cuidadoso de datos | 20 |
| Pruebas HTTP reproducibles y límites verificados | 25 |
| Instrucciones, respuestas esperadas y límites de interpretación | 10 |

Puntaje sugerido para aprobar: 70. Antes de entregar, corrige cualquier grant de escritura, secreto versionado, respuesta que exponga columnas no requeridas o resultado sintético presentado como Sakila.

## Límites de seguridad

Este es un ejercicio local sin autenticación, TLS, límites por IP ni despliegue productivo. No expongas Uvicorn a internet ni uses credenciales de producción. La cuenta SQL de solo lectura limita el daño posible, pero no sustituye controles de acceso, monitoreo ni una revisión de seguridad para un servicio publicado.
