---
name: query_project_pg
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def query_project_pg(project: str, sql: str, max_rows: int = 10000) -> dict"
description: "Pipeline one-shot que ejecuta un SELECT contra el Postgres de un proyecto conocido del ecosistema (captacion_clientes, seo_analytics) sin reescribir la resolucion del DSN ni la conexion a mano. Compone resolve_pg_dsn(project) (resuelve el DSN desde env var / .env / pass) con pg_query(dsn, sql, max_rows) (SELECT read-only via psycopg2 que devuelve filas como list[dict]). Elimina el patron inline que el agente repetia: grep al .env + fallback a pass + psql crudo. El caller solo pasa el nombre del proyecto y el SQL; el password sale de pass en runtime, nunca hardcodeado. Devuelve lo que devuelve pg_query en exito {status:'ok', columns, rows, row_count, truncated}, o propaga el {status:'error', error} de resolve_pg_dsn si falla la resolucion del DSN (sin tocar Postgres). Sin lanzar."
tags: [postgres, postgresql, sql, query, pipelines]
uses_functions: [resolve_pg_dsn_py_infra, pg_query_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/query_project_pg.py"
params:
  - name: project
    desc: "Nombre del proyecto conocido. Acepta clave canonica ('captacion', 'seo') o alias largo ('captacion_clientes', 'seo_analytics'). Se pasa tal cual a resolve_pg_dsn; un proyecto desconocido propaga su {status:'error'}."
  - name: sql
    desc: "Sentencia SQL a ejecutar (pensada para SELECT). Este pipeline no expone parametros posicionales: interpola solo valores constantes y de confianza. Para entradas no confiables usa pg_query directamente con su argumento params (%s)."
  - name: max_rows
    desc: "Numero maximo de filas a materializar en memoria (default 10000). Se pasa tal cual a pg_query; si la query produce mas, el resultado se trunca y truncated queda en True."
output: "dict. En exito (propaga pg_query): {status:'ok', columns:[str,...], rows:[{col:val,...},...], row_count:int, truncated:bool}. En error: si la resolucion del DSN falla, {status:'error', error:str} de resolve_pg_dsn; si la query falla, {status:'error', error:str} de pg_query. Sin lanzar."
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from pipelines.query_project_pg import query_project_pg

# Una sola llamada: resuelve el DSN de captacion_clientes y cuenta filas.
res = query_project_pg("captacion", "SELECT COUNT(*) FROM product_opportunities")
print(res["status"])        # ok
print(res["rows"][0])       # {'count': 42}

# Lanzable directo desde la CLI del registry (corre la demo del __main__):
#   ./fn run query_project_pg
```

## Cuando usarla

Usala cada vez que necesites leer datos del Postgres de un proyecto del
ecosistema (captacion_clientes, seo_analytics) en un solo paso, en vez de
resolver el DSN a mano y abrir la conexion tu mismo. Es el reemplazo directo del
bloque inline `DSN=$(grep ... .env) ; psql "$DSN" -c "SELECT ..."`. Para varias
queries con el mismo proyecto, o si necesitas parametros posicionales seguros
(%s), resuelve el DSN una vez con `resolve_pg_dsn` y llama a `pg_query`
directamente reusando el DSN.

## Gotchas

- Impuro: resuelve el DSN (lee env / .env / pass) y abre una conexion a
  Postgres. Depende del entorno de la maquina y de que el contenedor del
  proyecto este levantado.
- Solo lectura: `pg_query` marca la transaccion `SET TRANSACTION READ ONLY`.
  No uses este pipeline para INSERT/UPDATE/DELETE.
- No expone `params` posicionales: el SQL se ejecuta tal cual. NO interpoles
  entradas no confiables en el string (riesgo de inyeccion); para eso usa
  `pg_query` con su argumento `params`.
- El resultado se trunca a `max_rows` filas (default 10000) para proteger
  memoria; revisa `truncated` en la salida.
- La ruta del `.env` del proyecto se resuelve relativa a `FN_REGISTRY_ROOT` o,
  en su defecto, al cwd. Lanza desde la raiz del registry o exporta esa env var.
