@bi-analytics/arness-back 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +156 -2
- package/bin/arness.mjs +710 -0
- package/harness/AGENTS.md +234 -0
- package/harness/agents/implementador.md +42 -0
- package/harness/agents/lider.md +71 -0
- package/harness/agents/revisor.md +62 -0
- package/harness/decisiones/README.md +17 -0
- package/harness/decisiones/_plantilla.md +24 -0
- package/harness/init.sh +696 -0
- package/harness/playbooks/add-database.md +16 -0
- package/harness/playbooks/new-endpoint.md +88 -0
- package/harness/playbooks/new-module.md +21 -0
- package/harness/playbooks/new-monitor-widget.md +31 -0
- package/harness/playbooks/review.md +40 -0
- package/harness/reference/configuracion.md +104 -0
- package/harness/reference/contrato-http.md +138 -0
- package/harness/reference/novedades.json +107 -0
- package/harness/reference/progress-plantilla.md +50 -0
- package/harness/skills/commit-hygiene/SKILL.md +137 -0
- package/harness/skills/nest-base/SKILL.md +167 -0
- package/harness/skills/nest-base/templates/config/configuration.spec.ts +84 -0
- package/harness/skills/nest-base/templates/config/configuration.ts +143 -0
- package/harness/skills/nest-base/templates/cors/cors.spec.ts +53 -0
- package/harness/skills/nest-base/templates/cors/cors.ts +75 -0
- package/harness/skills/nest-base/templates/docs/EVOLUTIVO.md +22 -0
- package/harness/skills/nest-base/templates/docs/PENDIENTES.md +20 -0
- package/harness/skills/nest-base/templates/docs/TECNICO.md +43 -0
- package/harness/skills/nest-base/templates/e2e/cors.e2e-spec.ts +60 -0
- package/harness/skills/nest-base/templates/e2e/monitor.e2e-spec.ts +134 -0
- package/harness/skills/nest-base/templates/editorconfig +12 -0
- package/harness/skills/nest-base/templates/eslint.boundaries.mjs +65 -0
- package/harness/skills/nest-base/templates/eslint.config.mjs +82 -0
- package/harness/skills/nest-base/templates/logging/logger-options.spec.ts +57 -0
- package/harness/skills/nest-base/templates/logging/logger-options.ts +85 -0
- package/harness/skills/nest-base/templates/monitor/monitor-login-redirect.filter.ts +16 -0
- package/harness/skills/nest-base/templates/monitor/monitor-login.dto.ts +8 -0
- package/harness/skills/nest-base/templates/monitor/monitor-page.spec.ts +41 -0
- package/harness/skills/nest-base/templates/monitor/monitor-page.ts +190 -0
- package/harness/skills/nest-base/templates/monitor/monitor-session.guard.spec.ts +46 -0
- package/harness/skills/nest-base/templates/monitor/monitor-session.guard.ts +36 -0
- package/harness/skills/nest-base/templates/monitor/monitor-session.service.spec.ts +57 -0
- package/harness/skills/nest-base/templates/monitor/monitor-session.service.ts +77 -0
- package/harness/skills/nest-base/templates/monitor/monitor-widget.interface.ts +18 -0
- package/harness/skills/nest-base/templates/monitor/monitor.constants.ts +25 -0
- package/harness/skills/nest-base/templates/monitor/monitor.controller.ts +63 -0
- package/harness/skills/nest-base/templates/monitor/monitor.decorators.ts +11 -0
- package/harness/skills/nest-base/templates/monitor/monitor.module.ts +30 -0
- package/harness/skills/nest-base/templates/monitor/monitor.service.spec.ts +55 -0
- package/harness/skills/nest-base/templates/monitor/monitor.service.ts +49 -0
- package/harness/skills/nest-base/templates/monitor/widgets/endpoints-total.widget.spec.ts +51 -0
- package/harness/skills/nest-base/templates/monitor/widgets/endpoints-total.widget.ts +53 -0
- package/harness/skills/nest-base/templates/prettierignore +6 -0
- package/harness/skills/nest-base/templates/prettierrc.json +8 -0
- package/package.json +44 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Playbook: agregar una conexión de BD
|
|
2
|
+
|
|
3
|
+
Referencia: `AGENTS.md` §6 y §13.
|
|
4
|
+
|
|
5
|
+
1. **Confirmar con el humano:** el nombre lógico de la conexión (UPPER_SNAKE, ej. `REPORTS`) y qué módulos la usarán. Nunca pidas ni escribas credenciales reales en el código ni en el chat.
|
|
6
|
+
2. Agrega el nombre a `DATABASES` y el bloque de variables `DB_<NOMBRE>_*` en:
|
|
7
|
+
- `.env.example`, con valores de ejemplo;
|
|
8
|
+
- `.env.local`, con valores locales de desarrollo.
|
|
9
|
+
- **No toques `.env`.**
|
|
10
|
+
3. Verifica que la validación de `src/config/` exige las variables de cada nombre listado en `DATABASES`, y que `parseDatabases` de `src/config/configuration.ts` arma su bloque en `AppConfig.databases`. Normalmente no requiere cambios de código, porque ambas son dinámicas. Nunca leas `DB_<NOMBRE>_*` con `process.env` fuera de `src/config/`.
|
|
11
|
+
4. En los repositories que la usen, inyecta el pool con `@Inject(getPoolToken('<NOMBRE>'))`.
|
|
12
|
+
5. Confirma que `/health` reporta la nueva conexión.
|
|
13
|
+
6. Documenta la conexión en el `README.md` de cada módulo que la use y en la tabla de conexiones de `docs/TECNICO.md` (solo nombre lógico y origen, nunca credenciales), y agrega una entrada en `docs/EVOLUTIVO.md`.
|
|
14
|
+
7. Corre `./harness/init.sh`.
|
|
15
|
+
|
|
16
|
+
Recuerda que el usuario de BD debe ser de solo lectura. El pool fuerza `default_transaction_read_only = on` como segunda barrera, no como única.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Playbook: crear un endpoint (reporte)
|
|
2
|
+
|
|
3
|
+
Referencia de reglas: `AGENTS.md` §5 (patrón), §6 (BD), §7 (caché), §9 (contrato), §12 (documentación).
|
|
4
|
+
|
|
5
|
+
## 0. Confirmar el contrato (antes de escribir código)
|
|
6
|
+
|
|
7
|
+
Presenta esto al humano y espera su confirmación:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Endpoint: GET /api/v1/<modulo>/<segmento>
|
|
11
|
+
Ubicación: src/modules/<cliente>/<modulo>/<endpoint>/
|
|
12
|
+
Conexión BD: <NOMBRE> Origen: <schema.tabla | vista>
|
|
13
|
+
Columnas: <col1: tipo>, <col2: tipo>, ...
|
|
14
|
+
Filtros: <nombre: tipo, obligatorio|opcional, default>
|
|
15
|
+
Paginación: sí|no (pageSize default N, máx M)
|
|
16
|
+
Orden: <columnas permitidas>, default <col DESC>
|
|
17
|
+
Caché TTL: <segundos>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Si falta información, pregunta. No inventes columnas, tablas ni reglas.
|
|
21
|
+
|
|
22
|
+
## 1. Archivos, en este orden
|
|
23
|
+
|
|
24
|
+
1. `<endpoint>.columns.ts`: `export const X_COLUMNS = Object.freeze([...] as const)`.
|
|
25
|
+
2. `<endpoint>.entity.ts`: el tipo de la fila, derivado de las columnas.
|
|
26
|
+
3. `<endpoint>-query.dto.ts`: los params HTTP con `class-validator` (`@IsOptional`, `@IsDateString`, `@IsInt`, `@Min`, `@Max`, `@IsIn`).
|
|
27
|
+
4. `<endpoint>-filters.dto.ts`: los filtros normalizados (fechas como `Date`, defaults aplicados, `page`/`pageSize` resueltos).
|
|
28
|
+
5. `<endpoint>.sql.ts`: funciones puras `buildXQuery(filters): { text: string; values: unknown[] }` y, si pagina, `buildXCountQuery(filters)`. Todo valor va como `$n`, y el ORDER BY sale de una allowlist de `columns.ts`.
|
|
29
|
+
6. `<endpoint>.repository.ts`: el token `X_REPOSITORY`, la interfaz `XRepository` y la clase `PgXRepository`, que inyecta el pool de la conexión nombrada.
|
|
30
|
+
7. `<endpoint>.service.ts`: Cache-Aside con `CacheService`, y retorna `{ data, meta? }` junto al estado de caché.
|
|
31
|
+
8. `<endpoint>.controller.ts`: `@Get('<segmento>')`, recibe el DTO, lo convierte en filtros, llama al service y fija `X-Cache`.
|
|
32
|
+
9. Registrar controller, service y provider del repository (`{ provide: X_REPOSITORY, useClass: PgXRepository }`) en `<modulo>.module.ts`.
|
|
33
|
+
|
|
34
|
+
Aplica la regla de agrupación (más de 2 archivos del mismo tipo en una carpeta → carpeta por tipo). `init.sh` verifica que el endpoint tenga todos estos archivos y los specs de la sección 2.
|
|
35
|
+
|
|
36
|
+
## 2. Tests
|
|
37
|
+
|
|
38
|
+
- `*.sql.spec.ts`: valida `text` y `values` para cada combinación de filtros, y que ningún valor aparezca interpolado en `text`.
|
|
39
|
+
- `*.service.spec.ts`: HIT, MISS, BYPASS (caché lanza error o está deshabilitada), con y sin paginación.
|
|
40
|
+
- `*.controller.spec.ts`: delega al service y fija `X-Cache`.
|
|
41
|
+
- `*.repository.spec.ts`: con pool mockeado, verifica que ejecuta el SQL del builder y mapea las filas.
|
|
42
|
+
- `*-query.dto.spec.ts`: casos válidos e inválidos.
|
|
43
|
+
- E2E en `test/`: 401 sin token, 200 con token y envelope correcto, 400 con filtro inválido.
|
|
44
|
+
|
|
45
|
+
## 3. Documentación
|
|
46
|
+
|
|
47
|
+
### Postman
|
|
48
|
+
|
|
49
|
+
Agrega la request en `postman/arness-back.postman_collection.json`, dentro de la carpeta `<cliente>/<modulo>`:
|
|
50
|
+
- `GET {{baseUrl}}/api/v1/<modulo>/<segmento>?param=ejemplo`
|
|
51
|
+
- Auth: heredada de la colección (`Bearer {{accessToken}}`)
|
|
52
|
+
- Sin descripciones largas.
|
|
53
|
+
|
|
54
|
+
### FRONT.md
|
|
55
|
+
|
|
56
|
+
Agrega una sección con esta plantilla:
|
|
57
|
+
|
|
58
|
+
````markdown
|
|
59
|
+
### <Nombre legible del reporte>
|
|
60
|
+
|
|
61
|
+
`GET /api/v1/<modulo>/<segmento>` · requiere `Authorization: Bearer <accessToken>`
|
|
62
|
+
|
|
63
|
+
Qué devuelve: <una frase>.
|
|
64
|
+
|
|
65
|
+
| Parámetro | Tipo | Obligatorio | Ejemplo |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| desde | fecha ISO | sí | 2026-01-01 |
|
|
68
|
+
| page | número | no (default 1) | 1 |
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "data": [{ "col1": "…", "col2": 0 }], "meta": { "pagination": { "page": 1, "pageSize": 50, "total": 120, "totalPages": 3 } } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Consumo recomendado:** <paginar con pageSize ≤ N | traer completo>; <filtros a enviar siempre>; ante `401 TOKEN_EXPIRED`, llamar a `/auth/refresh` y reintentar una vez; datos cacheados en servidor por <TTL>, así que no hace falta hacer polling más frecuente.
|
|
75
|
+
````
|
|
76
|
+
|
|
77
|
+
### README del módulo
|
|
78
|
+
|
|
79
|
+
Agrega el endpoint, su conexión y el origen de datos.
|
|
80
|
+
|
|
81
|
+
### docs/
|
|
82
|
+
|
|
83
|
+
- `docs/EVOLUTIVO.md`: agrega al final una entrada `## <fecha> · <cliente>/<modulo>` con lo entregado en lenguaje de negocio (qué reporte quedó disponible), el endpoint y la referencia. Solo avance.
|
|
84
|
+
- `docs/PENDIENTES.md`: lo que quedó abierto (por ejemplo, una vista que aún no existe en la BD o un filtro aplazado).
|
|
85
|
+
|
|
86
|
+
## 4. Cierre
|
|
87
|
+
|
|
88
|
+
Corre `./harness/init.sh` y revisa el Definition of Done de `AGENTS.md` §15.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Playbook: crear un módulo o cliente
|
|
2
|
+
|
|
3
|
+
Referencia: `AGENTS.md` §4 y §5.
|
|
4
|
+
|
|
5
|
+
1. **Confirmar con el humano:** nombre del cliente (carpeta de primer nivel), nombre del módulo, segmento base de la URL y conexión de BD por defecto.
|
|
6
|
+
2. **Cliente nuevo:** crea `src/modules/<cliente>/`. Si se quiere agrupar sus módulos, agrega `<cliente>.module.ts` que los importe, y regístralo en `app.module.ts`.
|
|
7
|
+
3. **Módulo nuevo:** crea `src/modules/<cliente>/<modulo>/` con:
|
|
8
|
+
- `<modulo>.module.ts`: `@Module({ controllers: [], providers: [] })`, sin imports de otros módulos de negocio. El código del módulo solo importa de su propia carpeta, de `src/common/` y de `src/config/` (ESLint lo verifica).
|
|
9
|
+
- `README.md` con esta plantilla:
|
|
10
|
+
```markdown
|
|
11
|
+
# <Módulo>
|
|
12
|
+
Propósito: <una frase>.
|
|
13
|
+
Conexión BD: <NOMBRE>.
|
|
14
|
+
| Endpoint | Origen de datos | Caché TTL |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
```
|
|
17
|
+
4. Registra el módulo en su padre (el módulo del cliente o `app.module.ts`).
|
|
18
|
+
5. Crea la carpeta `<cliente>/<modulo>` en la colección de Postman y un encabezado `## <Módulo>` en `FRONT.md`.
|
|
19
|
+
6. Agrega endpoints con `new-endpoint.md`.
|
|
20
|
+
7. Agrega el módulo a la tabla de módulos de `docs/TECNICO.md` y una entrada en `docs/EVOLUTIVO.md` (solo avance; lo abierto va a `docs/PENDIENTES.md`).
|
|
21
|
+
8. Corre `./harness/init.sh`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Playbook: agregar un widget al monitor
|
|
2
|
+
|
|
3
|
+
Referencia: `AGENTS.md` §10 y §13, y `harness/reference/contrato-http.md` (Monitor). El monitor ya existe en `src/common/monitor/` (skill `nest-base`).
|
|
4
|
+
|
|
5
|
+
## 0. Confirmar con el humano
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Widget: <id-kebab> · Título: <texto corto> · Orden: <número>
|
|
9
|
+
Muestra: <qué cifra o estado técnico> · Desglose: <sí/no, por qué>
|
|
10
|
+
Origen: <de dónde sale el dato: Nest, pool, caché, process>
|
|
11
|
+
Costo: <consultas o llamadas por carga de página>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Qué puede mostrar:** conteos, estados (`up`/`down`), versiones, uptime, tamaños de pool y latencias agregadas.
|
|
15
|
+
**Qué nunca muestra:** secretos o variables de entorno, SQL, mensajes de error de PostgreSQL o Redis, hosts o usuarios de BD, y datos de negocio (filas, montos, nombres de clientes). Si el humano pide algo de esta lista, detente: requiere una decisión en `harness/decisiones/`.
|
|
16
|
+
|
|
17
|
+
## 1. Implementar
|
|
18
|
+
|
|
19
|
+
1. Crea `src/common/monitor/widgets/<id>.widget.ts`: una clase `@Injectable()` que implementa `MonitorWidget` (`id`, `title`, `order`, `collect(): Promise<MonitorValue>`).
|
|
20
|
+
2. `collect()` debe ser barato y de solo lectura: sin escrituras, sin SQL de negocio y sin depender de Redis. Si necesita la BD, usa una consulta técnica en un `*.sql.ts` y el repository de `common/`; si falla, deja que lance: el monitor lo muestra como "No disponible" y registra solo el tipo de error.
|
|
21
|
+
3. Regístrala en `src/common/monitor/monitor.module.ts`, agregándola al arreglo `WIDGETS`. El multi-provider `MONITOR_WIDGETS` la recoge sola.
|
|
22
|
+
4. `src/common/` no importa de `src/modules/`. Si el dato es de un módulo de negocio, no va al monitor base: regístralo como decisión.
|
|
23
|
+
|
|
24
|
+
## 2. Tests
|
|
25
|
+
|
|
26
|
+
- `<id>.widget.spec.ts`: el valor y el desglose esperados, y que un error del origen se propaga (no se traga ni se formatea).
|
|
27
|
+
- Si cambia el HTML, ajusta `test/monitor.e2e-spec.ts` para que siga comprobando que no hay `<script>` y que un widget con error no rompe la página.
|
|
28
|
+
|
|
29
|
+
## 3. Cierre
|
|
30
|
+
|
|
31
|
+
Agrega una entrada en `docs/EVOLUTIVO.md` (qué muestra ahora el monitor). Corre `./harness/init.sh` y revisa el checklist de `harness/playbooks/review.md` (un widget con datos sensibles es bloqueante).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Playbook: revisar un cambio
|
|
2
|
+
|
|
3
|
+
Revisa el diff contra `AGENTS.md` y reporta cada hallazgo con archivo y línea, del más grave al menos grave.
|
|
4
|
+
|
|
5
|
+
## Bloqueantes
|
|
6
|
+
|
|
7
|
+
- [ ] SQL fuera de `*.sql.ts`, o valores concatenados o interpolados en SQL
|
|
8
|
+
- [ ] `ORDER BY` o nombres de columna que no salen de una allowlist
|
|
9
|
+
- [ ] Cualquier escritura, DDL o migración
|
|
10
|
+
- [ ] Un endpoint nuevo sin guard JWT, o un `@Public()` fuera de `login`, `refresh` o `health`
|
|
11
|
+
- [ ] CORS abierto: `enableCors()` sin opciones, `origin: true`, `'*'`, reflejar el `Origin`, `credentials: true`, o `CORS_ORIGINS` con comodín
|
|
12
|
+
- [ ] El monitor sin `MonitorSessionGuard`, marcado `@Public()`, dentro de `/api/v1`, con `<script>`, o un widget que muestra secretos, SQL, errores crudos o datos de negocio
|
|
13
|
+
- [ ] Tokens, contraseñas o secretos en logs, respuestas, código o commits
|
|
14
|
+
- [ ] `pino-pretty` en `dependencies` (va en devDependencies) o `LOG_FORMAT=pretty` para producción
|
|
15
|
+
- [ ] Cambios en `.env`, o `.env.local` incluido en el commit
|
|
16
|
+
- [ ] `process.env` fuera de `src/config/` en código nuevo (salvo tests), o un secreto con valor por defecto en `configuration.ts`
|
|
17
|
+
- [ ] Una dependencia nueva sin aprobación
|
|
18
|
+
- [ ] La API falla si Redis está caído o deshabilitado
|
|
19
|
+
- [ ] Se editó la base del harness (`AGENTS.md`, puentes o playbooks) en lugar de registrar la decisión en `harness/decisiones/`
|
|
20
|
+
- [ ] El cambio contradice una decisión `Aceptada` de `harness/decisiones/`, o toma una decisión nueva sin registrarla
|
|
21
|
+
- [ ] `./harness/init.sh` termina en 1 (crítico, `--strict` o `required`), o el cambio agrega advertencias nuevas respecto a la línea base
|
|
22
|
+
- [ ] Se silenció un chequeo en `harness/arness.config.json` sin aprobación humana o sin motivo
|
|
23
|
+
|
|
24
|
+
## Contrato y arquitectura
|
|
25
|
+
|
|
26
|
+
- [ ] Estructura del endpoint según §5, y regla de agrupación (más de 2 archivos del mismo tipo en una carpeta)
|
|
27
|
+
- [ ] Fronteras: `common/` no importa de `modules/` y ningún módulo importa de otro módulo o cliente
|
|
28
|
+
- [ ] El service depende de la interfaz del repository, no de `pg`
|
|
29
|
+
- [ ] Envelope `{ data, meta? }` y errores `{ error: {...} }` con el status correcto
|
|
30
|
+
- [ ] Si cambió `.columns.ts` o los filtros, `FRONT.md` está actualizado y la versión de la clave de caché subió
|
|
31
|
+
|
|
32
|
+
## Completitud
|
|
33
|
+
|
|
34
|
+
- [ ] Un spec por archivo, incluidos los casos HIT/MISS/BYPASS
|
|
35
|
+
- [ ] Postman, `FRONT.md` y el README del módulo actualizados
|
|
36
|
+
- [ ] Entrada nueva al final de `docs/EVOLUTIVO.md` (sin reescribir las anteriores) y lo abierto en `docs/PENDIENTES.md`
|
|
37
|
+
- [ ] `docs/EVOLUTIVO.md` no contiene pendientes, bloqueos, stoppers ni riesgos
|
|
38
|
+
- [ ] Variables nuevas en `.env.example`, `.env.local`, la validación y `src/config/configuration.ts` (`AppConfig`)
|
|
39
|
+
- [ ] `./harness/init.sh` termina en 0 sin advertencias nuevas; las preexistentes se reportan, no se corrigen fuera de alcance
|
|
40
|
+
- [ ] Código en inglés, docs en español, commit con formato Conventional Commits
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Referencia: variables de entorno
|
|
2
|
+
|
|
3
|
+
Parte de la base del harness (AGENTS.md §6 a §10). Todas las variables:
|
|
4
|
+
- se declaran en `.env.example` con valores de ejemplo y en `.env.local` con valores locales;
|
|
5
|
+
- se validan en `src/config/` al arrancar.
|
|
6
|
+
|
|
7
|
+
`.env` nunca lo toca un agente. Si falta una variable requerida, la app falla al arrancar con un mensaje claro.
|
|
8
|
+
|
|
9
|
+
## Dónde se leen las variables
|
|
10
|
+
|
|
11
|
+
`process.env` se lee **solo** en `src/config/configuration.ts` (plantilla `harness/skills/nest-base/templates/config/`). Ese archivo agrupa las variables por tema y convierte sus tipos; el resto del código recibe la configuración ya tipada.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// src/config/configuration.ts (extracto)
|
|
15
|
+
export function buildConfiguration(env: Env): AppConfig {
|
|
16
|
+
return {
|
|
17
|
+
app: { nodeEnv: text(env, 'NODE_ENV', 'development'), port: int(env, 'PORT', 3000) },
|
|
18
|
+
databases: parseDatabases(env), // DATABASES=MAIN,REPORTS → { MAIN: { host, port, ... }, REPORTS: {...} }
|
|
19
|
+
auth: { accessToken: required(env, 'ACCESS_TOKEN'), jwt: { accessSecret: required(env, 'JWT_ACCESS_SECRET'), ... } },
|
|
20
|
+
// cache, cors, monitor...
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export default (): AppConfig => buildConfiguration(process.env);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **Registro:** `ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env.local', '.env'], validate, load: [configuration] })`. La validación (class-validator) revisa las variables crudas; `configuration.ts` solo agrupa y convierte.
|
|
27
|
+
- **Consumo:** `ConfigService<AppConfig, true>` con rutas tipadas, por ejemplo `config.get('cache.enabled', { infer: true })`. También en `main.ts` (`app.get(ConfigService)`): ni siquiera el puerto se lee de `process.env`.
|
|
28
|
+
- **Secretos sin valor por defecto** (`ACCESS_TOKEN`, `JWT_*_SECRET`, `DB_*_PASSWORD`): si faltan, la app no arranca. El error nombra la variable, nunca su valor. Los defaults son solo para valores no sensibles.
|
|
29
|
+
- **Tests:** los specs y `test/` pueden preparar `process.env`; `buildConfiguration(env)` es una función pura que se prueba con un objeto propio.
|
|
30
|
+
- **Verificación:** ESLint (`configBoundaries` de `eslint.boundaries.mjs`, regla `no-restricted-properties`) e `init.sh` (`env.process-env`, `env.configuration-file`, `env.eslint`).
|
|
31
|
+
|
|
32
|
+
**Agregar una variable** es, en el mismo cambio: `.env.example` + `.env.local` + validación + `configuration.ts` (y su tipo `AppConfig`) + esta referencia.
|
|
33
|
+
|
|
34
|
+
## App
|
|
35
|
+
|
|
36
|
+
```dotenv
|
|
37
|
+
NODE_ENV=development # development | test | production (default development)
|
|
38
|
+
PORT=3000
|
|
39
|
+
LOG_LEVEL=info # en desarrollo se recomienda debug
|
|
40
|
+
LOG_FORMAT=json # json (default, producción) | pretty (solo desarrollo local)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `LOG_FORMAT=pretty` requiere `pino-pretty` en devDependencies y no se acepta con `NODE_ENV=production`. `.env.local` usa `pretty`; `.env.example` documenta `json`.
|
|
44
|
+
|
|
45
|
+
## Bases de datos (conexiones nombradas)
|
|
46
|
+
|
|
47
|
+
```dotenv
|
|
48
|
+
DATABASES=MAIN,REPORTS # una sola BD: DATABASES=MAIN
|
|
49
|
+
|
|
50
|
+
DB_MAIN_HOST=localhost
|
|
51
|
+
DB_MAIN_PORT=5432
|
|
52
|
+
DB_MAIN_NAME=analytics
|
|
53
|
+
DB_MAIN_USER=readonly_user
|
|
54
|
+
DB_MAIN_PASSWORD=change-me
|
|
55
|
+
DB_MAIN_SSL=false
|
|
56
|
+
DB_MAIN_POOL_MAX=10
|
|
57
|
+
DB_MAIN_STATEMENT_TIMEOUT_MS=30000
|
|
58
|
+
|
|
59
|
+
DB_REPORTS_HOST=... # mismo bloque por cada nombre en DATABASES
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `common/database` crea un `Pool` por nombre y lo expone con `getPoolToken('<NOMBRE>')`.
|
|
63
|
+
- Requeridas por conexión: `HOST`, `PORT`, `NAME`, `USER` y `PASSWORD`. `init.sh` las verifica en `.env.example`.
|
|
64
|
+
|
|
65
|
+
## Caché
|
|
66
|
+
|
|
67
|
+
```dotenv
|
|
68
|
+
CACHE_ENABLED=true
|
|
69
|
+
REDIS_URL=redis://localhost:6379
|
|
70
|
+
CACHE_DEFAULT_TTL_SECONDS=300
|
|
71
|
+
CACHE_KEY_PREFIX=arness
|
|
72
|
+
CACHE_OP_TIMEOUT_MS=200
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Autenticación
|
|
76
|
+
|
|
77
|
+
```dotenv
|
|
78
|
+
ACCESS_TOKEN=example123 # contraseña compartida para /auth/login
|
|
79
|
+
JWT_ACCESS_SECRET=change-me
|
|
80
|
+
JWT_ACCESS_EXPIRES_IN=15m
|
|
81
|
+
JWT_REFRESH_SECRET=change-me-too # distinto de JWT_ACCESS_SECRET
|
|
82
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CORS
|
|
86
|
+
|
|
87
|
+
```dotenv
|
|
88
|
+
CORS_ORIGINS=http://localhost:4200,https://reportes.example.com # obligatoria; orígenes exactos separados por comas
|
|
89
|
+
CORS_MAX_AGE_SECONDS=600 # opcional; caché del preflight (0–86400)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `CORS_ORIGINS` es **obligatoria**: sin ella la app no arranca. No admite `*` ni comodines, solo orígenes (`esquema://host[:puerto]`, sin ruta), y exige `https://` salvo `localhost`, `127.0.0.1` y `[::1]`.
|
|
93
|
+
- Cada entorno define su lista (desarrollo, QA, producción). `init.sh` verifica que `.env.example` la define y que no lleva `*`.
|
|
94
|
+
|
|
95
|
+
## Monitor
|
|
96
|
+
|
|
97
|
+
```dotenv
|
|
98
|
+
MONITOR_ENABLED=false # true para habilitar GET /monitor; con false responde 404
|
|
99
|
+
MONITOR_SESSION_EXPIRES_IN=15m # duración de la cookie de sesión del monitor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- Por defecto `false`. Habilítalo solo donde el equipo lo necesite; en producción requiere aprobación.
|
|
103
|
+
- El login usa `ACCESS_TOKEN` y la sesión se firma con `JWT_ACCESS_SECRET`: no hay secretos nuevos.
|
|
104
|
+
- Si existe `src/common/monitor/`, `init.sh` verifica que `.env.example` define ambas variables.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Referencia: contrato HTTP
|
|
2
|
+
|
|
3
|
+
Parte de la base del harness (AGENTS.md §8, §9 y §10).
|
|
4
|
+
|
|
5
|
+
## Respuesta exitosa
|
|
6
|
+
|
|
7
|
+
`meta` aparece **solo** cuando el endpoint pagina o filtra.
|
|
8
|
+
|
|
9
|
+
```jsonc
|
|
10
|
+
// Sin paginación ni filtros
|
|
11
|
+
{ "data": [ /* filas */ ] }
|
|
12
|
+
|
|
13
|
+
// Con paginación y/o filtros
|
|
14
|
+
{
|
|
15
|
+
"data": [ /* filas */ ],
|
|
16
|
+
"meta": {
|
|
17
|
+
"pagination": { "page": 1, "pageSize": 50, "total": 1234, "totalPages": 25 },
|
|
18
|
+
"filters": { "desde": "2026-01-01", "regional": "ANT" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Respuesta de error
|
|
24
|
+
|
|
25
|
+
Siempre con el status HTTP correcto. `details` es opcional.
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"error": {
|
|
30
|
+
"code": "VALIDATION_ERROR",
|
|
31
|
+
"message": "El parámetro 'desde' debe ser una fecha ISO",
|
|
32
|
+
"details": [{ "field": "desde", "issue": "isDateString" }],
|
|
33
|
+
"requestId": "b1f2c3...",
|
|
34
|
+
"path": "/api/v1/cartera/resumen",
|
|
35
|
+
"timestamp": "2026-09-25T14:00:00.000Z"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Status | `code` | Origen típico |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| 400 | `VALIDATION_ERROR` | DTO inválido |
|
|
43
|
+
| 401 | `UNAUTHORIZED` / `TOKEN_EXPIRED` / `INVALID_CREDENTIALS` | guard o login |
|
|
44
|
+
| 403 | `FORBIDDEN` | — |
|
|
45
|
+
| 404 | `NOT_FOUND` | ruta o recurso |
|
|
46
|
+
| 503 | `DATABASE_UNAVAILABLE` | pg: conexión rechazada, `08*`, `57P01` |
|
|
47
|
+
| 504 | `QUERY_TIMEOUT` | pg: `57014` (statement_timeout) |
|
|
48
|
+
| 500 | `INTERNAL_ERROR` | cualquier otro; sin stack ni SQL |
|
|
49
|
+
|
|
50
|
+
## Autenticación
|
|
51
|
+
|
|
52
|
+
| Endpoint | Body | Respuesta |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `POST /api/v1/auth/login` | `{ "password": "..." }` | `{ data: { accessToken, refreshToken, expiresIn } }` |
|
|
55
|
+
| `POST /api/v1/auth/refresh` | `{ "refreshToken": "..." }` | `{ data: { accessToken, refreshToken, expiresIn } }` |
|
|
56
|
+
|
|
57
|
+
- `password` se compara con `ACCESS_TOKEN` en tiempo constante: `crypto.timingSafeEqual` sobre el sha256 de ambos valores.
|
|
58
|
+
- Los tokens usan secretos distintos; el claim `type` (`access` o `refresh`) se valida.
|
|
59
|
+
- Los clientes envían `Authorization: Bearer <accessToken>`. Ante `401 TOKEN_EXPIRED`, llaman a `/auth/refresh` y reintentan una vez.
|
|
60
|
+
|
|
61
|
+
## CORS
|
|
62
|
+
|
|
63
|
+
Allowlist por entorno: solo los orígenes de `CORS_ORIGINS` reciben `Access-Control-Allow-Origin`. Implementación de referencia: `harness/skills/nest-base/templates/cors/cors.ts` → `src/config/cors.ts`.
|
|
64
|
+
|
|
65
|
+
| Opción de `app.enableCors()` | Valor |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `origin` | función que acepta solo los orígenes exactos de `CORS_ORIGINS` (sin `Origin` → sin cabeceras CORS) |
|
|
68
|
+
| `methods` | `GET, POST, OPTIONS` |
|
|
69
|
+
| `allowedHeaders` | `Authorization, Content-Type, X-Request-Id` |
|
|
70
|
+
| `exposedHeaders` | `X-Cache, X-Request-Id` |
|
|
71
|
+
| `credentials` | `false` (la API usa `Bearer`, no cookies) |
|
|
72
|
+
| `maxAge` | `CORS_MAX_AGE_SECONDS` (default 600) |
|
|
73
|
+
| `optionsSuccessStatus` | `204` |
|
|
74
|
+
|
|
75
|
+
- **Validación al arrancar** (`src/config/`): `CORS_ORIGINS` es obligatoria; se rechazan `*` y comodines, valores que no son un origen (`esquema://host[:puerto]`, sin ruta) y `http://` fuera de `localhost`, `127.0.0.1` o `[::1]`.
|
|
76
|
+
- **Prohibido:** `enableCors()` sin opciones, `origin: true`, `origin: '*'`, reflejar el `Origin` recibido o `credentials: true`.
|
|
77
|
+
- **Tests:** `src/config/cors.spec.ts` (parseo y validación) y `test/cors.e2e-spec.ts`: preflight permitido → `204` con `Access-Control-Allow-Origin`, métodos, cabeceras y `max-age`; preflight de un origen no permitido → sin `Access-Control-Allow-Origin`.
|
|
78
|
+
- `init.sh` advierte si `.env.example` no define `CORS_ORIGINS` o lo define con `*`, y si `src/main.ts` no llama a `enableCors` o lo abre a cualquier origen.
|
|
79
|
+
|
|
80
|
+
## Caché
|
|
81
|
+
|
|
82
|
+
- Header `X-Cache: HIT | MISS | BYPASS` en toda respuesta cacheable.
|
|
83
|
+
- Clave: `<prefix>:<cliente>:<modulo>:<endpoint>:v<version>:<hash-estable-de-filtros>`. El hash se calcula con sha256 sobre el JSON de los filtros con las claves ordenadas.
|
|
84
|
+
|
|
85
|
+
## Logging
|
|
86
|
+
|
|
87
|
+
- JSON estructurado (`nestjs-pino`). `requestId` desde `X-Request-Id` (si es seguro: `[A-Za-z0-9._-]`, máximo 128) o UUID generado, devuelto en la respuesta.
|
|
88
|
+
- Por request: método, ruta, status, duración, resultado de caché y conexión de BD.
|
|
89
|
+
- Redact: `authorization`, cookies (`cookie` y `set-cookie`), `password`, `accessToken`, `refreshToken`, `*_SECRET` y `*_PASSWORD`.
|
|
90
|
+
- Plantilla: `harness/skills/nest-base/templates/logging/logger-options.ts` (`buildLoggerParams`, `parseLogFormat`, `REDACT_PATHS`).
|
|
91
|
+
|
|
92
|
+
### Formato por entorno (`LOG_FORMAT`)
|
|
93
|
+
|
|
94
|
+
| Entorno | `LOG_FORMAT` | Ejemplo |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| Producción, QA, CI (y por defecto) | `json` | `{"level":30,"requestId":"a1b2c3","req":{"method":"GET","url":"/api/v1/cartera/resumen"},"res":{"statusCode":200},"responseTime":42,"msg":"GET /api/v1/cartera/resumen 200 42ms"}` |
|
|
97
|
+
| Desarrollo local | `pretty` | `10:21:03 INFO: [a1b2c3] GET /api/v1/cartera/resumen 200 42ms {"cache":"HIT"}` |
|
|
98
|
+
|
|
99
|
+
- Ambos formatos llevan los mismos campos y la **misma redacción**: el formato legible nunca muestra secretos.
|
|
100
|
+
- `pretty` usa `pino-pretty`, que es **devDependency**: la imagen de producción no lo incluye. Si se pide `pretty` y no está instalado, el logger vuelve a JSON.
|
|
101
|
+
- La validación rechaza `LOG_FORMAT=pretty` con `NODE_ENV=production`, y cualquier valor distinto de `json` o `pretty`.
|
|
102
|
+
- Los logs no incluyen el SQL ejecutado ni sus parámetros en ningún formato.
|
|
103
|
+
|
|
104
|
+
## Health
|
|
105
|
+
|
|
106
|
+
`GET /health` es público y usa terminus. Indicadores:
|
|
107
|
+
- `db_<nombre en minúsculas>`, uno por conexión;
|
|
108
|
+
- `cache`, con estado `up`, `down` o `disabled`.
|
|
109
|
+
|
|
110
|
+
| Situación | Respuesta |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Todas las BDs arriba | `200` |
|
|
113
|
+
| Alguna BD caída | `503` |
|
|
114
|
+
| Redis caído o deshabilitado | sigue en `200`; `cache: down / disabled` |
|
|
115
|
+
|
|
116
|
+
`init.sh` consume este formato para informar qué base está caída.
|
|
117
|
+
|
|
118
|
+
## Monitor
|
|
119
|
+
|
|
120
|
+
Tablero de estado para el equipo, **fuera de `/api/v1`** y apagado por defecto. Implementación de referencia: `harness/skills/nest-base/templates/monitor/` → `src/common/monitor/`.
|
|
121
|
+
|
|
122
|
+
| Ruta | Sesión | Respuesta |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `GET /monitor` | cookie | HTML con los widgets; sin sesión → `303` a `/monitor/login` |
|
|
125
|
+
| `GET /monitor/login` | — | formulario HTML (`password`) |
|
|
126
|
+
| `POST /monitor/login` | — | correcta → `303` a `/monitor` y `Set-Cookie`; incorrecta → `401` con el formulario y sin cookie |
|
|
127
|
+
| `POST /monitor/logout` | — | borra la cookie (`Max-Age=0`) y `303` a `/monitor/login` |
|
|
128
|
+
|
|
129
|
+
- **Activación:** `MONITOR_ENABLED=true`. Con cualquier otro valor, todas las rutas responden `404`.
|
|
130
|
+
- **Render:** `react-dom/server` (`renderToStaticMarkup`) con `createElement`, sin JSX, sin hidratación, sin bundler y sin `<script>`. Estilos inline.
|
|
131
|
+
- **Login:** la contraseña se compara con `ACCESS_TOKEN` en tiempo constante, igual que `/auth/login`. Al acertar se emite un JWT con `type: "monitor"` (firmado con `JWT_ACCESS_SECRET`, dura `MONITOR_SESSION_EXPIRES_IN`) en la cookie `arness_monitor`: `HttpOnly; Secure; SameSite=Strict; Path=/monitor; Max-Age=<duración>`. Un access token (`type: "access"`) no sirve como cookie ni la cookie sirve como `Bearer`.
|
|
132
|
+
- **Guards:** el controller lleva `@MonitorRoute()`, que `JwtAuthGuard` omite, y `MonitorSessionGuard`, que exige la cookie salvo en los handlers `@MonitorLogin()` (formulario, login y logout). No es `@Public()`.
|
|
133
|
+
- **Rate limiting:** cuando el harness incorpore límite de intentos para `/auth/login`, el login del monitor lo comparte.
|
|
134
|
+
- **Cabeceras en toda respuesta:** `Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`, `Cache-Control: no-store`, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff` y `Referrer-Policy: no-referrer`.
|
|
135
|
+
- **Widgets:** `MonitorWidget { id; title; order; collect(): Promise<MonitorValue> }`, registrados en el multi-provider `MONITOR_WIDGETS` de `MonitorModule`. Un widget que lanza o tarda más de 2 s se muestra como "No disponible"; el log registra solo el tipo de error, nunca su mensaje.
|
|
136
|
+
- **Widget base `endpoints-total`:** cuenta las rutas HTTP con `DiscoveryService` y `MetadataScanner`, excluye `monitor`, `health` y `auth`, y desglosa por el primer segmento de la ruta (el módulo).
|
|
137
|
+
- **Nunca se muestran:** secretos, variables de entorno, SQL, errores de PostgreSQL ni datos de negocio (filas, montos, nombres de clientes). Solo conteos y estados técnicos.
|
|
138
|
+
- **Tests:** unit de servicio, página, sesión, guard y widget; e2e `test/monitor.e2e-spec.ts` (redirección sin cookie, `401` sin cookie con contraseña incorrecta, login y conteo correcto con controllers de prueba, widget con error que no rompe la página, sin `<script>`, `404` con `MONITOR_ENABLED=false`).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"description": "Novedades adoptables por versión del harness. Las lee `arness doctor` (qué falta adoptar) y `arness sync` (qué trae la versión). Tipos: seguridad | recomendada | opcional. Una novedad está adoptada cuando existen sus `paths` y init.sh no reporta ninguno de sus `checks`.",
|
|
4
|
+
"novedades": [
|
|
5
|
+
{
|
|
6
|
+
"id": "cors-allowlist",
|
|
7
|
+
"since": "0.2.0",
|
|
8
|
+
"type": "seguridad",
|
|
9
|
+
"title": "CORS con allowlist por entorno",
|
|
10
|
+
"why": "Sin allowlist, cualquier sitio web puede llamar la API desde el navegador de un usuario autenticado.",
|
|
11
|
+
"checks": ["security.cors-env", "security.cors", "security.cors-open"],
|
|
12
|
+
"paths": [],
|
|
13
|
+
"approval": null,
|
|
14
|
+
"steps": [
|
|
15
|
+
"Copia harness/skills/nest-base/templates/cors/ (cors.ts y su spec) a src/config/ y valida CORS_ORIGINS al arrancar.",
|
|
16
|
+
"En src/main.ts usa app.enableCors(buildCorsOptions(...)) con los valores validados.",
|
|
17
|
+
"Agrega CORS_ORIGINS y CORS_MAX_AGE_SECONDS a .env.example y .env.local, y define los orígenes de cada entorno antes de desplegar."
|
|
18
|
+
],
|
|
19
|
+
"adr": "0006",
|
|
20
|
+
"reference": "harness/reference/contrato-http.md (CORS)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "eslint-boundaries",
|
|
24
|
+
"since": "0.2.0",
|
|
25
|
+
"type": "recomendada",
|
|
26
|
+
"title": "Fronteras de arquitectura verificadas por ESLint",
|
|
27
|
+
"why": "Evita que common/ dependa de los módulos y que un módulo o cliente importe de otro.",
|
|
28
|
+
"checks": ["architecture.eslint-boundaries"],
|
|
29
|
+
"paths": ["eslint.boundaries.mjs"],
|
|
30
|
+
"approval": null,
|
|
31
|
+
"steps": [
|
|
32
|
+
"Copia harness/skills/nest-base/templates/eslint.boundaries.mjs a la raíz.",
|
|
33
|
+
"Impórtala en eslint.config.mjs y agrega ...architectureBoundaries.",
|
|
34
|
+
"Si aparecen imports entre módulos, mueve lo compartido a src/common/ cuando toques ese módulo."
|
|
35
|
+
],
|
|
36
|
+
"adr": "0007",
|
|
37
|
+
"reference": "harness/AGENTS.md §4"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "monitor",
|
|
41
|
+
"since": "0.2.0",
|
|
42
|
+
"type": "opcional",
|
|
43
|
+
"title": "Tablero monitor (GET /monitor)",
|
|
44
|
+
"why": "Muestra el estado técnico del backend (endpoints, salud) sin exponer datos de negocio.",
|
|
45
|
+
"checks": ["security.monitor-env", "security.monitor-guard"],
|
|
46
|
+
"paths": ["src/common/monitor"],
|
|
47
|
+
"approval": "react y react-dom (aprobadas en AGENTS.md §2 solo para el monitor); registra la adopción en harness/decisiones/ si el proyecto aún no las usa.",
|
|
48
|
+
"steps": [
|
|
49
|
+
"Copia harness/skills/nest-base/templates/monitor/ a src/common/monitor/ y registra MonitorModule.",
|
|
50
|
+
"Excluye monitor del prefijo global y haz que JwtAuthGuard omita IS_MONITOR_ROUTE_KEY.",
|
|
51
|
+
"Agrega MONITOR_ENABLED=false y MONITOR_SESSION_EXPIRES_IN=15m a .env.example y .env.local."
|
|
52
|
+
],
|
|
53
|
+
"adr": "0008",
|
|
54
|
+
"reference": "harness/playbooks/new-monitor-widget.md"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "env-config",
|
|
58
|
+
"since": "0.2.0",
|
|
59
|
+
"type": "recomendada",
|
|
60
|
+
"title": "Configuración centralizada en src/config/configuration.ts",
|
|
61
|
+
"why": "Toda la configuración tipada en un solo lugar, sin process.env disperso ni secretos con valor por defecto.",
|
|
62
|
+
"checks": ["env.configuration-file", "env.process-env", "env.eslint"],
|
|
63
|
+
"paths": [],
|
|
64
|
+
"approval": null,
|
|
65
|
+
"steps": [
|
|
66
|
+
"Copia harness/skills/nest-base/templates/config/ (configuration.ts y su spec) a src/config/ y regístrala con ConfigModule.forRoot({ load: [configuration] }).",
|
|
67
|
+
"Cambia, módulo por módulo, cada process.env.X por ConfigService<AppConfig, true>.",
|
|
68
|
+
"Actualiza eslint.boundaries.mjs desde la plantilla y agrega ...configBoundaries en eslint.config.mjs."
|
|
69
|
+
],
|
|
70
|
+
"adr": "0009",
|
|
71
|
+
"reference": "harness/reference/configuracion.md (Dónde se leen las variables)"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "log-format",
|
|
75
|
+
"since": "0.2.0",
|
|
76
|
+
"type": "opcional",
|
|
77
|
+
"title": "Logs legibles en desarrollo (LOG_FORMAT=pretty)",
|
|
78
|
+
"why": "Una línea legible por petición en la terminal local; producción sigue en JSON.",
|
|
79
|
+
"checks": ["logging.format-env"],
|
|
80
|
+
"paths": [],
|
|
81
|
+
"approval": "pino-pretty, solo como devDependency (aprobada en AGENTS.md §2).",
|
|
82
|
+
"steps": [
|
|
83
|
+
"pnpm add -D pino-pretty",
|
|
84
|
+
"Copia harness/skills/nest-base/templates/logging/ a src/common/logging/ y usa buildLoggerParams en LoggerModule.forRootAsync.",
|
|
85
|
+
"Agrega LOG_FORMAT=json a .env.example y LOG_FORMAT=pretty a .env.local."
|
|
86
|
+
],
|
|
87
|
+
"adr": "0010",
|
|
88
|
+
"reference": "harness/reference/contrato-http.md (Logging)"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "project-docs",
|
|
92
|
+
"since": "0.2.0",
|
|
93
|
+
"type": "recomendada",
|
|
94
|
+
"title": "Documentación del proyecto en docs/ (técnico, evolutivo y pendientes)",
|
|
95
|
+
"why": "Negocio ve lo entregado en EVOLUTIVO.md y lo abierto queda en PENDIENTES.md, sin mezclarse.",
|
|
96
|
+
"checks": ["docs.files"],
|
|
97
|
+
"paths": [],
|
|
98
|
+
"approval": null,
|
|
99
|
+
"steps": [
|
|
100
|
+
"Copia harness/skills/nest-base/templates/docs/ a docs/.",
|
|
101
|
+
"Completa docs/TECNICO.md con lo que ya existe y, si quieres, resume lo entregado en docs/EVOLUTIVO.md."
|
|
102
|
+
],
|
|
103
|
+
"adr": "0011",
|
|
104
|
+
"reference": "harness/AGENTS.md §12"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Progress
|
|
2
|
+
|
|
3
|
+
> Estado compartido del trabajo en curso. **Solo lo escribe el `lider`**; el `implementador` y el `revisor` lo leen.
|
|
4
|
+
> Aquí van resúmenes, no diffs ni logs completos. Mantenlo por debajo de ~150 líneas: las tareas cerradas se reducen a una línea en la tabla.
|
|
5
|
+
|
|
6
|
+
## Objetivo
|
|
7
|
+
|
|
8
|
+
<Qué pidió el humano, en una o dos frases.>
|
|
9
|
+
|
|
10
|
+
## Estado
|
|
11
|
+
|
|
12
|
+
- **Estado:** EN CURSO
|
|
13
|
+
- **Actualizado:** AAAA-MM-DD HH:MM
|
|
14
|
+
- **init.sh (última ejecución):** <fecha> · <exit 0 + N advertencias | exit 1: resumen de fallos>
|
|
15
|
+
- **Línea base de advertencias (inicio de la sesión):** <ids de init.sh --json, o "ninguna">
|
|
16
|
+
|
|
17
|
+
## Contrato confirmado
|
|
18
|
+
|
|
19
|
+
<Lo confirmado con el humano (AGENTS.md §15.1): cliente/módulo/segmento, conexión y origen, columnas, filtros, paginación, TTL. Escribe "—" si no aplica.>
|
|
20
|
+
|
|
21
|
+
## Decisiones de esta sesión
|
|
22
|
+
|
|
23
|
+
- <Enlaces a harness/decisiones/NNNN-*.md aceptadas o propuestas, y acuerdos del humano que aún no son decisión formal.>
|
|
24
|
+
|
|
25
|
+
## Tareas
|
|
26
|
+
|
|
27
|
+
| ID | Tarea | Responsable | Estado | Ronda | Veredicto |
|
|
28
|
+
|---|---|---|---|---|---|
|
|
29
|
+
| T-001 | <título> | implementador | PENDIENTE | 0 | — |
|
|
30
|
+
|
|
31
|
+
Estados: `PENDIENTE` · `EN CURSO` · `EN REVISIÓN` · `CAMBIOS REQUERIDOS` · `APROBADA` · `BLOQUEADA`.
|
|
32
|
+
|
|
33
|
+
## Detalle por tarea
|
|
34
|
+
|
|
35
|
+
### T-001 · <título>
|
|
36
|
+
|
|
37
|
+
- **Encargo:** objetivo, playbook, archivos y criterios de aceptación.
|
|
38
|
+
- **Fuera de alcance:** <...>
|
|
39
|
+
- **Última entrega:** <resumen del reporte del implementador>
|
|
40
|
+
- **Último veredicto:** <resumen de los hallazgos del revisor, con archivo:línea>
|
|
41
|
+
|
|
42
|
+
## Bloqueos y preguntas abiertas
|
|
43
|
+
|
|
44
|
+
- <Pregunta o bloqueo · a quién le toca responder>
|
|
45
|
+
|
|
46
|
+
## Bitácora
|
|
47
|
+
|
|
48
|
+
<Append-only: una línea por evento.>
|
|
49
|
+
|
|
50
|
+
- AAAA-MM-DD HH:MM · lider · <evento>
|