# Capability: sql-connect

Conexión directa y consulta a un **Microsoft SQL Server** desde el registry, con el caso prioritario de **Navision** (el ERP corre sobre SQL Server). Las funciones Python usan el driver **pymssql** (más simple en Linux/WSL que pyodbc: trae FreeTDS embebido, no necesita ODBC driver manager).

Existe para **eliminar el ida y vuelta manual** con Navision: en vez de escribir una query, que el usuario la ejecute en su SGBD y pegue el CSV, estas funciones se conectan al servidor y devuelven las filas — iteración rápida sobre una query en un solo comando.

## Funciones

| ID | Firma | Que hace |
|---|---|---|
| `mssql_connect_py_infra` | `mssql_connect(host, database, user, password, port=1433, login_timeout=15, query_timeout=30) -> pymssql.Connection` | Abre una conexión a SQL Server vía pymssql. Credenciales por argumento (nunca hardcodeadas). `login_timeout` acota la fase de login para que un host inalcanzable no cuelgue. Devuelve la conexión abierta; el caller la cierra con `.close()`. Lanza `RuntimeError` claro (host:port/db) si falla. |
| `mssql_query_py_infra` | `mssql_query(conn, sql, params=None, max_rows=None) -> dict` | Ejecuta una SELECT parametrizada sobre una conexión abierta y mapea las filas a dicts. Binding seguro del driver (placeholders `%s`/`%(nombre)s`, sin inyección). Devuelve `{columns, rows:[{col:val}], row_count}`. 0 filas → lista vacía sin error. `max_rows` limita con `fetchmany`. Read-only (no commit), no cierra la conexión. |
| `run_mssql_query_py_pipelines` | `run_mssql_query(host, database, user, password, sql, params=None, port=1433, max_rows=None, login_timeout=15, query_timeout=30) -> dict` | **Pipeline one-shot**: compone `mssql_connect` + `mssql_query` y cierra siempre la conexión (try/finally). CLI imprime JSON o CSV. Para iterar sobre una query de Navision en un solo `fn run`. |

## Ejemplo canónico

One-shot para iterar sobre Navision (la contraseña se lee de una env var, nunca se pasa por la línea de comandos):

```bash
cd /home/egutierrez/fn_registry
MSSQL_PASSWORD=$(pass navision/password) \
  ./fn run run_mssql_query \
    --host 10.0.0.5 --database navdb --user sa \
    --sql "SELECT TOP 5 [No_], [Amount] FROM [dbo].[Cartera] WHERE [Customer No_] = %s" \
    --param CLI-0001 \
    --format csv
```

Conexión persistente para muchas queries seguidas (abrir una vez, consultar N veces):

```python
import os, sys
sys.path.insert(0, "python/functions")
from infra.mssql_connect import mssql_connect
from infra.mssql_query import mssql_query

conn = mssql_connect("10.0.0.5", "navdb", "sa", os.environ["MSSQL_PASSWORD"])
try:
    abiertos = mssql_query(
        conn,
        "SELECT [No_], [Amount] FROM [dbo].[Cartera] WHERE [Open] = 1 AND [Customer No_] = %s",
        params=("CLI-0001",),
    )
    print(abiertos["row_count"], abiertos["columns"])
    posted = mssql_query(conn, "SELECT TOP 10 [Document No_], [Amount] FROM [dbo].[Posted Cartera]")
    print(posted["rows"])
finally:
    conn.close()
```

## Gotchas del grupo

- **Conectividad WSL2 → Windows**: el `host` debe ser la **IP LAN del Windows** que corre SQL Server, NO `localhost` (desde WSL2 localhost no alcanza al host Windows). Ver memoria `wsl2-localhost-forwarding`. Probablemente el servidor real de Navision no sea alcanzable desde un entorno aislado sin red a la oficina + credenciales.
- **Credenciales desde `pass`, nunca hardcodeadas.** Patrón: `MSSQL_PASSWORD=$(pass navision/password) ./fn run run_mssql_query ...`. La función recibe la contraseña como argumento; el caller la resuelve. `--password` literal existe pero queda visible en la lista de procesos — usa `--password-env`.
- **Placeholders pymssql** son `%s` (posicional) y `%(nombre)s` (nombrado), NO `?` (eso es pyodbc). Pasa los valores como `params`, jamás concatenados en el SQL (inyección).
- **`mssql_query` no abre ni cierra la conexión** — la toma prestada. Para ráfagas de queries, abre con `mssql_connect` una vez y reúsala; el pipeline `run_mssql_query` abre y cierra por llamada (cómodo, no eficiente en ráfaga).
- **Read-only por uso**: pensado para SELECT (Navision: cartera, posted cartera, movimientos). No hace commit.
- **Requiere `pymssql`** instalado en el venv (`uv add pymssql`). Import perezoso: el módulo carga sin la dependencia, pero la llamada falla con `RuntimeError` claro si falta.
- **Datos sintéticos en ejemplos** [POL-MMNSEG-001-1.0]: los `No_`/`Customer No_` de los ejemplos son ficticios. Sobre datos reales de Navision aplica la política de protección de datos.

## Fronteras

- **Solo SQL Server (Navision)**. No es una capa SQL genérica: para PostgreSQL usa el grupo `postgres`; para DuckDB el grupo `duckdb`. Generalizar a MySQL/otros engines sería especulativo (KISS) hasta que haya un caso real.
- **No es ETL ni BI**: solo conecta y devuelve filas. Para llevar datos de Navision a un destino analítico, compón con los grupos `duckdb`/`postgres` (cargar las filas) o léelas en un notebook.
- **No gestiona el servidor** (no crea bases, no administra logins). Solo cliente de lectura.

## Relación con otros grupos

- `postgres` / `duckdb` — capas CRUD para otros engines; mismo espíritu (conectar + consultar), distinto motor. SQL Server (Navision) es la fuente; esos son destinos analíticos/BI.
- `metabase` / `bigquery` — el trabajo Aurgi consume datos ya en BigQuery/Metabase; este grupo abre la puerta a leer Navision en origen para iterar queries antes de modelarlas.
