---
name: resolve_pg_dsn
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def resolve_pg_dsn(project: str) -> dict"
description: "Resuelve el DSN de PostgreSQL de un proyecto conocido del ecosistema (captacion_clientes, seo_analytics) sin lanzar. Centraliza el patron inline repetido por el agente: leer el DSN desde la variable de entorno del proyecto (CAPTACION_DSN, SEO_DSN), caer a la linea <ENV_VAR>= del .env del proyecto, y como ultimo recurso construirlo desde el secreto de pass (password en runtime, user/host/port/db fijos por proyecto). Cada proyecto declara su politica de resolucion en un mapa interno explicito (_PROJECTS) con alias para el nombre largo. Orden de resolucion: (1) env var, (2) .env, (3) pass. Devuelve {status:'ok', project, dsn, source} con source='env'|'dotenv'|'pass', o {status:'error', error} si el proyecto es desconocido o no se pudo construir el DSN. NUNCA hardcodea el password: lo lee de pass via pass_get_secret en runtime."
tags: [postgres, postgresql, dsn, credential, infra]
uses_functions: [pass_get_secret_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: [os]
tested: true
tests: ["env var seteada gana y source es env", "proyecto desconocido devuelve error sin lanzar", "alias largo resuelve a la clave canonica", "fallback a .env cuando no hay env var"]
test_file_path: "python/functions/infra/resolve_pg_dsn_test.py"
file_path: "python/functions/infra/resolve_pg_dsn.py"
params:
  - name: project
    desc: "Nombre del proyecto. Acepta la clave canonica ('captacion', 'seo') o el alias largo ('captacion_clientes', 'seo_analytics'). Un nombre no registrado devuelve {status:'error'} con la lista de proyectos conocidos."
output: "dict. En exito: {status:'ok', project:str (clave canonica), dsn:str (cadena postgresql://...), source:str ('env'|'dotenv'|'pass')}. En error (sin lanzar): {status:'error', error:str} para proyecto desconocido o DSN no resoluble."
---

## Ejemplo

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

# Por nombre corto o largo, da igual.
res = resolve_pg_dsn("captacion")
print(res["status"])   # ok
print(res["source"])   # 'dotenv' (lee CAPTACION_DSN del .env del proyecto)
# res["dsn"] -> "postgresql://captacion:***@localhost:5433/trends"

# La env var, si esta seteada, gana sobre el .env y sobre pass.
os.environ["SEO_DSN"] = "postgresql://captacion:x@localhost:5433/seo"
print(resolve_pg_dsn("seo_analytics")["source"])  # env
```

## Cuando usarla

Usala antes de cualquier `psql`/`psycopg2`/`pg_query` contra el Postgres de un
proyecto del ecosistema, en vez de reescribir a mano la resolucion del DSN
(grep al .env + fallback a pass). Es el unico sitio que sabe como se llama la
env var de cada proyecto, donde vive su .env y de que entry de pass sale el
password. Si vas a lanzar varias queries seguidas, resuelve el DSN una vez y
reusalo; para el caso comun de "una query a un proyecto" usa el pipeline
`query_project_pg_py_pipelines` que ya compone esta resolucion con `pg_query`.

## Gotchas

- Impura: lee variables de entorno, el `.env` del proyecto en disco y ejecuta
  `pass show` como subproceso. El resultado depende del entorno de la maquina.
- El `dsn` devuelto **contiene el password en claro**. NO lo logees ni lo
  imprimas en produccion (el `## Ejemplo` lo redacta a proposito).
- La ruta del `.env` se resuelve relativa a `FN_REGISTRY_ROOT` si esa env var
  esta seteada; si no, relativa al cwd. Lanza desde la raiz del registry o
  exporta `FN_REGISTRY_ROOT` para que el paso (2) `.env` funcione.
- Solo conoce los proyectos del mapa `_PROJECTS`. Anadir uno nuevo = una entrada
  de diccionario (env_var + dotenv_path + pass_path + pg fijos), no otro bloque
  de bash inline.
- El fallback de `seo` apunta hoy al mismo entry de pass que `captacion`
  (mismo contenedor Postgres, distinta db `seo`). Si seo_analytics pasa a tener
  credenciales propias, actualiza `_PROJECTS['seo']`.
