@ntlinkmx/nt 0.1.0 → 0.2.2

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/README.md CHANGED
@@ -1,450 +1,473 @@
1
- # nt · CLI del backend Go
2
-
3
- ## Responsabilidad
4
-
5
- Cliente Node/TypeScript dentro de este monorepo para comunicarse por HTTP con el
6
- backend Go de NTLink IA Invoice. Ejecutable **`nt`**, compatible con Windows y
7
- Linux, Node **22.12 o posterior** y npm. No necesita React abierto ni acceso directo
8
- a SQL, certificados, Azure o claves de IA: esas dependencias las gestiona Go.
9
-
10
-
11
- ## Contrato
12
-
13
- ### Instalar y comenzar
14
-
15
- Instala el paquete publicado y úsalo en PowerShell o Bash:
16
-
17
- ```sh
18
- npm install --global @ntlinkmx/nt
19
- nt --version
20
- nt --help
21
- nt mode
22
- nt mode production
23
- nt client info
24
- nt client balance
25
- ```
26
-
27
- `npm ci` compila automáticamente; después de cambiar fuentes usa `npm run build`.
28
- El paquete se publicará como `@ntlinkmx/nt`; instala o actualiza con `npm install --global @ntlinkmx/nt`.
29
- Para preparar el primer lanzamiento, desde la cuenta npm que será dueña del
30
- paquete ejecuta `npm login` y `npm publish` dentro de `cli/`. Es un paso inicial:
31
- npm requiere que el paquete exista antes de activar su trusted publisher.
32
- Después, en npmjs.com abre `Packages` → `@ntlinkmx/nt` → `Settings` →
33
- `Trusted Publisher`; elige GitHub Actions y configura
34
- organización/usuario `NTlink`, repositorio `ntlink-ia-invoice`, workflow
35
- `publish-invoice-cli.yml` y permite `npm publish`. Para las versiones siguientes,
36
- cambia la versión del paquete y ejecuta manualmente el workflow desde la rama
37
- `main` en la pestaña Actions del repositorio. OIDC evita guardar un `NPM_TOKEN`.
38
- El paquete pertenece al scope `@ntlinkmx`.
39
- El workflow tiene que estar primero en `main` para poder seleccionarlo en npm.
40
- Para instalar desde un archivo: ejecuta `npm pack` y luego
41
- `npm install --global ./ntlinkmx-nt-0.1.0.tgz` desde `cli/`.
42
- Para retirar el comando global: `npm uninstall --global @ntlinkmx/nt`.
43
- El perfil predeterminado es `pruebas`. Las instalaciones existentes conservan su configuración guardada.
44
- El arranque de Go se describe en [desarrollo local](../doc/guides/LOCAL-DEVELOPMENT.md).
45
-
1
+ # nt · CLI del backend Go
2
+
3
+ ## Responsabilidad
4
+
5
+ Cliente Node/TypeScript dentro de este monorepo para comunicarse por HTTP con el
6
+ backend Go de NTLink IA Invoice. Ejecutable **`nt`**, compatible con Windows y
7
+ Linux, Node **22.12 o posterior** y npm. No necesita React abierto ni acceso directo
8
+ a SQL, certificados, Azure o claves de IA: esas dependencias las gestiona Go.
9
+
10
+
11
+ ## Contrato
12
+
13
+ ### Instalar y comenzar
14
+
15
+ Instala el paquete publicado y úsalo en PowerShell o Bash:
16
+
17
+ ```sh
18
+ npm install --global @ntlinkmx/nt
19
+ nt --version
20
+ nt --help
21
+ nt mode
22
+ nt mode production
23
+ nt client info
24
+ nt client balance
25
+ ```
26
+
27
+ `npm ci` compila automáticamente; después de cambiar fuentes usa `npm run build`.
28
+ El paquete se publicará como `@ntlinkmx/nt`; instala o actualiza con `npm install --global @ntlinkmx/nt`.
29
+ Configura una vez Trusted Publisher en npmjs.com: `Packages` -> `@ntlinkmx/nt` ->
30
+ `Settings` -> `Trusted Publisher`; proveedor GitHub Actions, organizaci?n/usuario
31
+ `NTlink`, repositorio `ntlink-ia-invoice`, workflow
32
+ `publish-invoice-cli.yml`, con permiso `npm publish`. El paquete ya existe, as?
33
+ que no hace falta un lanzamiento manual con token. Despu?s ejecuta una vez el
34
+ workflow `Publish Invoice CLI to npm` desde `main` para publicar la versi?n
35
+ pendiente. Para las siguientes versiones, actualiza `version` en `package.json`,
36
+ integra el cambio en `main` y crea/pushea el tag `cli-vX.Y.Z` correspondiente;
37
+ GitHub ejecuta verificaciones y publica autom?ticamente con OIDC. El workflow
38
+ no guarda un `NPM_TOKEN`. `nt --version` lee la versi?n del mismo `package.json`.
39
+ El paquete pertenece al scope `@ntlinkmx`.
40
+ El workflow tiene que estar primero en `main` para poder seleccionarlo en npm.
41
+ Para instalar desde un archivo: ejecuta `npm pack` y luego
42
+ `npm install --global ./ntlinkmx-nt-0.2.0.tgz` desde `cli/`.
43
+ Para retirar el comando global: `npm uninstall --global @ntlinkmx/nt`.
44
+ El perfil predeterminado es `pruebas` y apunta al servidor remoto de pruebas. Las instalaciones existentes conservan su configuración guardada.
45
+ El arranque de Go se describe en [desarrollo local](../doc/guides/LOCAL-DEVELOPMENT.md).
46
+
46
47
  ### Usar en pruebas
47
48
 
48
49
  Al instalar, el CLI anuncia que queda en modo `pruebas`. La primera operación protegida inicia sesión automáticamente con la cuenta pública de demostración.
49
50
 
50
51
  ### Cuenta pública de demostración
51
52
 
52
- Estas credenciales son para el modo `pruebas`:
53
+ Estas credenciales pertenecen al entorno compartido de pruebas:
53
54
 
54
55
  - Correo: `URE180429TM6@ntlink.com.mx`
55
56
  - Contraseña: `NTPruebas.2021*?`
56
57
 
58
+ Úsalas solo con el perfil `pruebas`. Para autenticar otra cuenta, ejecuta `nt auth login`.
59
+
57
60
  ### Modos y perfiles
58
61
 
59
62
  `nt mode` muestra el modo activo sin imprimir el host. `nt mode pruebas` vuelve al entorno de pruebas. `nt mode production` informa que producción estará disponible próximamente y conserva el modo actual.
60
-
61
- Cada perfil conserva URL, timeout y token independientes. Los nombres de perfiles
62
- son etiquetas locales; no cambian las banderas fiscales del servidor.
63
-
64
- ```sh
65
- nt --profile local config set --base-url http://localhost:8092 --timeout 120
66
- nt --profile pruebas config set --base-url http://pruebas.ntlink.com.mx:8092
67
- nt --profile pruebas auth login --email URE180429TM6@ntlink.com.mx
68
- nt config use pruebas
69
- nt config show
70
- nt config list
71
- nt --profile local health ready
72
- ```
73
-
74
- La URL debe contener protocolo y host/puerto, sin `/api`, usuario, contraseña, query ni fragmento.
75
- También existe `config set --url URL`. Cambiar la URL guardada de un perfil borra
76
- su token anterior. Un `--base-url` temporal a otro origen tampoco reutiliza su token.
77
-
78
- | Ajuste | Variable | Opción global |
79
- |---|---|---|
80
- | Perfil | `NTLINK_PROFILE` | `--profile` |
81
- | Origen Go | `NTLINK_BASE_URL` | `--base-url` |
82
- | Bearer | `NTLINK_TOKEN` | `--token` |
83
- | Timeout en segundos | `NTLINK_TIMEOUT` | `--timeout` |
84
-
85
- Prioridad: opción > variable > perfil guardado > valor inicial. No se leen los
86
- tokens ni el archivo del CLI Proxy. La configuración está en
87
- `~/.ntlink-invoice/config.json` (Windows: `%USERPROFILE%\.ntlink-invoice\config.json`);
88
- `NTLINK_INVOICE_CONFIG_DIR` permite cambiar su carpeta.
89
- Los tokens se almacenan localmente sin cifrar; los archivos nuevos usan modo 0600
90
- en POSIX y en Windows heredan las ACL de la carpeta del usuario.
91
- `config show/list` solo muestran el modo y los perfiles; no imprimen el host ni los tokens.
92
-
93
- ### Autenticación
94
-
95
- ```sh
96
- nt auth login
97
- nt auth login --email usuario@example.invalid
98
- nt auth status
99
- nt auth accept-terms
100
- nt auth logout
101
- ```
102
-
103
- El login solicita la contraseña sin mostrarla en terminal. Usa
104
- `POST /api/v2/auth/login` con `email/password` y guarda el campo `token` devuelto
105
- por Membership Go. **Go no expone refresh token**: un 401 requiere iniciar sesión
106
- otra vez. `auth status` consulta el perfil real, no solo la existencia del token.
107
- `logout` borra el token local del perfil; no revoca JWT ya emitidos ni variables
108
- de entorno que sigan definidas.
109
-
110
- Para automatizar, usa `NTLINK_PASSWORD` o `--password-stdin` con `--email`:
111
-
112
- ```sh
113
- nt auth login --email usuario@example.invalid --password-stdin
114
- ```
115
-
116
- Conecta stdin desde tu gestor de secretos o tubería. No hay opción `--password`
117
- para evitar dejarla en los argumentos. `auth register registro.json` envía
118
- `email`, `name`, `phone`, `rfc`, `postalCode`; puede crear una cuenta y enviar correo.
119
- `auth verify-code codigo.json` envía `email` y `verificationCode`.
120
-
121
- ### Cuenta, directorios, estadísticas y reportes
122
-
123
- ```sh
124
- nt client info
125
- nt client balance
126
- nt client companies
127
- nt client usage --year 2026
128
- nt client blacklist AAA010101AAA
129
- nt customers list
130
- nt customers get 123
131
- nt customers create cliente.json
132
- nt customers update 123 cliente.json
133
- nt customers delete 123
134
- nt companies list
135
- nt companies get 10
136
- nt companies create emisor.json
137
- nt companies update 10 emisor.json
138
- nt users list
139
- nt users create usuario.json
140
- nt users update usuario@example.invalid usuario.json
141
- nt dashboard daily --range LAST_MONTH
142
- nt dashboard types
143
- nt dashboard status
144
- nt dashboard top-receivers
145
- nt dashboard top-transmitters
146
- nt reports pending-ppd --company 10 --page 0 --size 50
147
- nt reports retentions --from 2026-01-01 --to 2026-01-31
148
- ```
149
-
150
- `users update` recibe el **correo/username** como identificador, conforme a Go.
151
- La autorización de usuarios exige administrador. Crear emisor no instala su CSD.
152
- El CLI envía los JSON sin renombrar campos: los directorios usan los DTO del
153
- [contrato Portal](../backend/doc/modules/Portal.md) y
154
- [frontend/src/api/portal.ts](../frontend/src/api/portal.ts).
155
- Ejemplo sintético de cliente (adapta empresa y datos fiscales antes de guardar):
156
-
157
- ```json
158
- {
159
- "companyId": 10,
160
- "rfc": "AAA010101AAA",
161
- "legalName": "CLIENTE DE EJEMPLO",
162
- "taxRegime": "601",
163
- "postalCode": "06000",
164
- "email": "cliente@example.invalid"
165
- }
166
- ```
167
-
168
- El saldo viene de `/customer/balances`, con canales de portal y web service.
169
- PPD conserva `outstandingBalance` y `balanceSource`; consultar no registra pagos.
170
- Los listados CFDI/reportes/borradores aceptan `--page`, `--size`, `--company`,
171
- `--customer`, `--search`, `--from`, `--to`, `--filter KEY=VALUE` y
172
- `-q KEY=VALUE`. Cada endpoint aplica solo sus filtros admitidos: para borradores,
173
- por ejemplo, usa `--filter estado=sin-timbrar` o `--filter folio=eq:BOR-000000001`.
174
- La paginación del portal comienza en cero y admite hasta 200 filas por solicitud;
175
- no descarga automáticamente las páginas restantes. La salida conserva el sobre
176
- con `content`, `totalElements`, `totalPages`, `page` y `size`.
177
-
178
- ### Catálogos, CFDI y borradores
179
-
180
- ```sh
181
- nt catalogs list
182
- nt catalogs get moneda --id MXN
183
- nt catalogs get ClaveProdServ --description consultoria
184
- nt cfdi list --from 2026-01-01 --to 2026-01-31 --size 100
185
- nt cfdi list --filter uuid=eq:00000000-0000-0000-0000-000000000000
186
- nt cfdi get UUID
187
- nt cfdi status UUID
188
- nt cfdi validate factura.xml
189
- nt cfdi download UUID --format xml --output factura.xml
190
- nt cfdi download UUID --format pdf --output factura.pdf --force
191
- nt cfdi prepare cfdi.json
192
- nt drafts create cfdi.json
193
- nt drafts list --filter estado=sin-timbrar
194
- nt drafts get ID
195
- nt drafts update ID cfdi.json
196
- nt drafts reserve-folio
197
- ```
198
-
199
- Los nombres de catálogo se normalizan al nombre Go, por ejemplo `moneda` →
200
- `Moneda`. La búsqueda envía `q`; `--id` busca identificador exacto. El listado
201
- incluye los 15 catálogos del portal; los catálogos nativos adicionales están en
202
- `native catalogo`. Que un nombre exista no garantiza que la base tenga sus datos.
203
-
204
- `cfdi prepare` normaliza/recalcula en Go sin guardar ni emitir. `drafts create`
205
- y `update` guardan en SQL. `drafts get` devuelve un sobre con `cfdi`, metadatos y
206
- revisión: para actualizar, envía el **objeto `cfdi` interior**, no todo ese sobre.
207
- `reserve-folio` reserva un folio provisional BOR, no el consecutivo fiscal.
208
- Nómina y complementos viajan en el JSON del portal; el CLI no calcula ISR ni
209
- subsidio. Consulta [emisión](../doc/flows/ISSUANCE.md) y
210
- [nómina](../backend/doc/parity/payroll.md) para el alcance de cada variante.
211
-
212
- Las operaciones explícitas siguientes también tienen comando:
213
-
214
- ```sh
215
- nt cfdi stamp cfdi.json
216
- nt drafts stamp ID cfdi.json
217
- nt cfdi cancel UUID --reason 02
218
- nt cfdi cancel UUID --reason 01 --replacement UUID_SUSTITUTO
219
- nt cfdi email UUID --to facturacion@example.invalid --to archivo@example.invalid
220
- ```
221
-
222
- Estos comandos pueden emitir, cancelar o enviar realmente si el servidor lo
223
- permite. No tienen un modo simulado; nombrar un perfil `pruebas` no garantiza
224
- que su PAC sea sandbox. Las banderas y permisos de Go siguen siendo obligatorios.
225
- Las descargas del portal usan `/api/v2/cfdis/{uuid}/xml|pdf`, distintas de las
226
- rutas de descarga del CLI Proxy, y no sobrescriben archivos sin `--force`.
227
-
228
- ### Chat y análisis de PDF/XML/JSON
229
-
230
- ```sh
231
- nt chat models
232
- nt chat sessions create
233
- nt chat sessions list
234
- nt chat sessions get SESSION_ID
235
- nt chat attach documento.pdf
236
- nt chat send SESSION_ID "Analiza las discrepancias de este documento" --attach documento.pdf
237
- nt chat send SESSION_ID "Consulta mis comprobantes" --provider google --model MODELO_HABILITADO
238
- nt --timeout 360 chat events RUN_ID
239
- nt --timeout 360 chat events RUN_ID --last-event-id 3
240
- nt chat cancel RUN_ID
241
- nt chat sessions delete SESSION_ID
242
- ```
243
-
244
- `send` devuelve `sessionId`, `requestId` y `runId`; consulta sus eventos con
245
- `chat events`. Se pueden adjuntar hasta tres archivos repitiendo `--attach`.
246
- `chat attach` extrae el contenido por sí solo. Go conserva los límites de PDF
247
- (5 MB, 30 páginas) y XML/JSON (1 MB), OCR selectivo y advertencias.
248
- Los proveedores/modelos deben figurar en `chat models`.
249
-
250
- `send --request-id UUID` permite reutilizar un identificador conocido sin crear
251
- una nueva petición de chat; si falta, se genera uno. Los eventos salen como SSE
252
- original (`id`, `event`, `data`), no como un único JSON. Un evento `error` o
253
- `cancelled` es parte del protocolo SSE y debe interpretarlo el consumidor.
254
- Cerrar el CLI o vencer su timeout no cancela la ejecución del servidor;
255
- usa `chat cancel`, recupera la sesión y revisa borradores antes de repetir.
256
- No hay reconexión SSE automática: utiliza `--last-event-id`.
257
- `chat complete solicitud.json` conserva acceso al contrato síncrono de Go.
258
-
259
- Analizar no autoriza guardar: solicita explícitamente la creación de un borrador
260
- cuando quieras persistirlo. El chat puede consultar y preparar borradores; sus
261
- herramientas no timbran, cancelan, cobran ni envían correos. Esas capacidades
262
- pertenecen a los comandos explícitos y rutas nativas correspondientes.
263
-
264
- ### API manual y rutas nativas
265
-
266
- ```sh
267
- nt health live
268
- nt health ready
269
- nt parity
270
- nt api GET /api/v2/customer/profile
271
- nt api GET /api/v2/cfdis -q page=0 -q size=20
272
- nt api POST /api/v2/cfdis/prepare --data cfdi.json
273
- nt api POST /api/v2/cfdis/validate --raw factura.xml --content-type application/xml
274
- nt native routes
275
- nt native routes --controller Empresa
276
- nt native empresa --help
277
- nt native empresa get-empresa -q id=10
278
- nt native cfdi descargar-acuse --param id=123 --output acuse.xml
279
- nt native validarxml validar --file archivoXml=factura.xml
280
- ```
281
-
282
- `native` mapea las 87 rutas del registro nativo: catálogos, clientes, empresas,
283
- usuarios, sesión, CSF, reportes, CFDI/REP, retenciones, plantillas, CSD/FIEL/convenios,
284
- entrega y compras según el controlador. Todas conservan el método y path de Go.
285
- `--param KEY=VALUE` sustituye parámetros de ruta, `-q KEY=VALUE` agrega query,
286
- `--data archivo.json` envía JSON, `--raw archivo` envía bytes y
287
- `--file CAMPO=RUTA`/`--field CLAVE=VALOR` construyen multipart.
288
- `--header NOMBRE=VALOR` permite enviar cabeceras adicionales, por ejemplo las
289
- requeridas por contratos con firma; sus valores no se imprimen en verbose.
290
- Las tres formas de cuerpo son excluyentes. `--output` guarda cualquier respuesta
291
- exitosa como bytes; `--stream` muestra SSE original y excluye `--output`.
292
-
293
- Para conocer los campos particulares, abre el módulo del controlador desde
294
- [backend/doc/INDEX.md](../backend/doc/INDEX.md), y consulta los nombres exactos con
295
- `native GRUPO COMANDO --help`. Los comandos nativos siguen nombres derivados de
296
- las acciones originales y sus DTO nativos; no reciben automáticamente los DTO del
297
- portal. Por ejemplo, el `id` nativo de CFDI es el ID SQL, no su UUID.
298
- La tabla siguiente se sincroniza con `npm run routes:sync`; es un inventario de
299
- acceso, no una certificación de operación externa.
300
-
301
- <!-- native-routes:start -->
302
- | Comando (`nt native …`) | HTTP | Ruta Go |
303
- |---|---|---|
304
- | `catalogo get-clave-unidad` | GET | `/api/Catalogo/ClaveUnidad` |
305
- | `catalogo get-clave-unidad-filtro` | GET | `/api/Catalogo/ClaveUnidadFiltro` |
306
- | `catalogo get-clave-unidad-peso` | GET | `/api/Catalogo/ClaveUnidadPeso` |
307
- | `catalogo buscar-estaciones` | GET | `/api/Catalogo/Estaciones` |
308
- | `catalogo buscar-clave-prod-serv-cp` | GET | `/api/Catalogo/ClaveProdServCP` |
309
- | `catalogo buscar-fraccion-arancelaria` | GET | `/api/Catalogo/FraccionArancelaria` |
310
- | `catalogo buscar-prod-serv` | GET | `/api/Catalogo/ClaveProdServ` |
311
- | `catalogo get-estados` | GET | `/api/Catalogo/GetEstados` |
312
- | `catalogo get-paises` | GET | `/api/Catalogo/GetPaises` |
313
- | `catalogo get-localidad` | GET | `/api/Catalogo/GetLocalidad` |
314
- | `catalogo get-municipios` | GET | `/api/Catalogo/GetMunicipios` |
315
- | `catalogo get-cps` | GET | `/api/Catalogo/GetCPs` |
316
- | `catalogo existe-cp` | GET | `/api/Catalogo/ExisteCP` |
317
- | `catalogo get-tasa-cuota` | GET | `/api/Catalogo/TasaCuota` |
318
- | `catalogo get-monedas` | GET | `/api/Catalogo/Monedas` |
319
- | `cfdi timbrar` | POST | `/api/CFDI/Timbrar` |
320
- | `cfdi timbrado-previo` | POST | `/api/CFDI/TimbradoPrevio` |
321
- | `cfdi guardar-plantilla` | POST | `/api/CFDI/Plantilla` |
322
- | `cfdi listar-plantillas` | GET | `/api/CFDI/Plantillas` |
323
- | `cfdi obtener-plantilla` | GET | `/api/CFDI/Plantilla/{id:long}` |
324
- | `cfdi eliminar-plantilla` | POST | `/api/CFDI/Plantilla/{id:long}` |
325
- | `cfdi descargar-xml` | GET | `/api/CFDI/xml/{id:int}` |
326
- | `cfdi verificar-pdf` | HEAD | `/api/CFDI/pdf/{id:int}` |
327
- | `cfdi descargar-pdf` | GET | `/api/CFDI/pdf/{id:int}` |
328
- | `cfdi descargar-acuse` | GET | `/api/CFDI/acuse/{id:int}` |
329
- | `cfdi enviar-correo` | POST | `/api/CFDI/correo/{id:int}` |
330
- | `cfdi enviar-whats-app` | POST | `/api/CFDI/whatsapp/{id:int}` |
331
- | `cfdi descargar-pdf-whats-app` | GET | `/api/CFDI/wa/{token}/{archivo}` |
332
- | `cfdi cancelar` | POST | `/api/CFDI/cancelar/{id:int}` |
333
- | `cliente get-clientes` | GET | `/api/Cliente/GetClientes` |
334
- | `cliente get-by-id` | GET | `/api/Cliente/GetById` |
335
- | `cliente guardar` | POST | `/api/Cliente/Guardar` |
336
- | `config get-session-config` | GET | `/api/Config/session` |
337
- | `empresa get-empresas` | GET | `/api/Empresa/GetEmpresas` |
338
- | `empresa get-sucursales` | GET | `/api/Empresa/GetSucursales` |
339
- | `empresa get-empresa` | GET | `/api/Empresa/GetEmpresa` |
340
- | `empresa get-sucursales-lista` | GET | `/api/Empresa/GetSucursalesLista` |
341
- | `empresa get-sucursal` | GET | `/api/Empresa/GetSucursal` |
342
- | `empresa guardar` | POST | `/api/Empresa/Guardar` |
343
- | `empresa validar-csd` | POST | `/api/Empresa/ValidarCSD` |
344
- | `empresa tiene-csd` | GET | `/api/Empresa/TieneCsd` |
345
- | `empresa subir-fiel` | POST | `/api/Empresa/SubirFiel` |
346
- | `empresa guardar-sucursal` | POST | `/api/Empresa/GuardarSucursal` |
347
- | `empresa parse-constancia` | POST | `/api/Empresa/ParseConstancia` |
348
- | `homedashboard get-stats` | GET | `/api/HomeDashboard/stats` |
349
- | `listanegra consultar-xml` | POST | `/api/ListaNegra/consultar-xml` |
350
- | `pagos config` | GET | `/api/Pagos/Config` |
351
- | `pagos crear-cargo2` | POST | `/api/Pagos/CrearCargo2` |
352
- | `pagos validar-sesion` | GET | `/api/Pagos/ValidarSesion` |
353
- | `pagos webhook` | POST | `/api/Pagos/Webhook` |
354
- | `pagos generar-pre-factura-externa` | POST | `/api/Pagos/GenerarPreFacturaExterna` |
355
- | `renovarfolios obtener-datos-empresa` | GET | `/api/RenovarFolios/ObtenerDatosEmpresa` |
356
- | `renovarfolios renovar` | POST | `/api/RenovarFolios/Renovar` |
357
- | `reporte get-cfdi` | GET | `/api/Reporte/GetCFDI` |
358
- | `reporte lista-timbrado` | GET | `/api/Reporte/lista-timbrado` |
359
- | `reporte reporte-anual` | GET | `/api/Reporte/reporte-anual` |
360
- | `reporte reporte-empresa` | GET | `/api/Reporte/reporte-empresa` |
361
- | `reporte anios` | GET | `/api/Reporte/anios` |
362
- | `retenciones reporte` | GET | `/api/Retenciones/Reporte` |
363
- | `retenciones enviar-correo` | POST | `/api/Retenciones/correo/{id:int}` |
364
- | `retenciones cancelar` | POST | `/api/Retenciones/cancelar/{id:int}` |
365
- | `retenciones descargar-acuse` | GET | `/api/Retenciones/acuse/{id:int}` |
366
- | `retenciones timbrar` | POST | `/api/Retenciones/Timbrar` |
367
- | `retenciones timbrado-previo` | POST | `/api/Retenciones/TimbradoPrevio` |
368
- | `retenciones descargar-xml` | GET | `/api/Retenciones/xml/{id:int}` |
369
- | `retenciones verificar-pdf` | HEAD | `/api/Retenciones/pdf/{id:int}` |
370
- | `retenciones descargar-pdf` | GET | `/api/Retenciones/pdf/{id:int}` |
371
- | `retenciones enviar-whats-app` | POST | `/api/Retenciones/whatsapp/{id:int}` |
372
- | `retenciones descargar-pdf-whats-app` | GET | `/api/Retenciones/wa/{token}/{archivo}` |
373
- | `session login` | POST | `/api/Session/Login` |
374
- | `session generar-codigo` | POST | `/api/Session/GenerarCodigo` |
375
- | `session generar-codigo-correo` | POST | `/api/Session/GenerarCodigoCorreo` |
376
- | `session validar-codigo` | POST | `/api/Session/ValidarCodigo` |
377
- | `session heartbeat` | POST | `/api/Session/Heartbeat` |
378
- | `session logout` | POST | `/api/Session/Logout` |
379
- | `session recuperar-contrasena` | POST | `/api/Session/RecuperarContrasena` |
380
- | `session upload-csf` | POST | `/api/Session/UploadCSF` |
381
- | `session registro` | POST | `/api/Session/Registro` |
382
- | `session cambiar-contrasena` | POST | `/api/Session/CambiarContrasena` |
383
- | `usuarioadmin get-usuarios` | GET | `/api/UsuarioAdmin/GetUsuarios` |
384
- | `usuarioadmin debug-profile` | GET | `/api/UsuarioAdmin/DebugProfile` |
385
- | `usuarioadmin guardar` | POST | `/api/UsuarioAdmin/Guardar` |
386
- | `usuarioadmin editar` | POST | `/api/UsuarioAdmin/Editar` |
387
- | `usuarioadmin cambiar-password` | POST | `/api/UsuarioAdmin/CambiarPassword` |
388
- | `usuarioadmin bloquear` | POST | `/api/UsuarioAdmin/Bloquear/{userId}` |
389
- | `usuarioadmin eliminar` | POST | `/api/UsuarioAdmin/Eliminar/{userId}` |
390
- | `validarxml validar` | POST | `/api/ValidarXML/validar` |
391
- <!-- native-routes:end -->
392
-
393
- ## Flujo y fallos
394
-
395
- CLI → perfil/variables/opciones → HTTP Bearer a Go → permisos y reglas Go →
396
- SQL/servicios según la ruta → JSON o archivo. No copia reglas fiscales al cliente.
397
- Las peticiones anónimas solo se usan en login/registro/verificación, salud/paridad
398
- y rutas nativas marcadas así por el registro; los catálogos del portal sí llevan JWT.
399
- `api --anonymous` permite omitirlo deliberadamente.
400
-
401
- La salida normal es JSON en stdout (`--json` es compatible y opcional). Los
402
- errores van a stderr y terminan con código 1; el éxito usa 0. Ayuda/versión son
403
- texto. `--verbose` imprime método, origen, ruta y estado en stderr; omite tokens,
404
- query y cuerpos. Con `--output`, stdout devuelve `{ "path": "...", "bytes": 123 }`.
405
- En PowerShell, evita convertir XML/PDF a texto por tubería: usa `--output`.
406
-
407
- No se reintenta ninguna petición automáticamente, tampoco después de 401. Un
408
- timeout, 502 o fallo de red durante una escritura puede tener resultado incierto:
409
- consulta su estado antes de repetir. Un timbrado externo y su persistencia SQL
410
- pueden tener éxito parcial; sigue la recuperación documentada en Go.
411
- Los errores HTTP conservan estado y mensaje; `exito:false` nativo también falla.
412
- Go puede devolver 403 por alcance, 409 por conflicto, 501 por módulo pendiente o
413
- 503 por configuración/servicio/catálogo ausente. El CLI no habilita esas funciones.
414
-
415
- ## Reglas y trazabilidad
416
-
417
- | Regla | Implementación y evidencia |
418
- |---|---|
419
- | Token separado por perfil y origen | `src/config.ts`; pruebas de perfiles, override y logout |
420
- | Sin refresh ni repetición automática | `src/http.ts`, `auth login`; pruebas 401/503 y escrituras |
421
- | Descarga exacta y sin sobrescritura implícita | `src/http.ts`; prueba de bytes y `--force` |
422
- | Rutas reales sin inventar equivalentes Proxy | `src/native-routes.json`, `scripts/sync-routes.mjs`; prueba contra registro Go |
423
- | Chat conserva requestId y SSE | `src/cli.ts`; prueba de adjuntos, modelo, eventos y cancelación |
424
-
425
- Fuentes: [Portal](../backend/doc/modules/Portal.md),
426
- [router Go](../backend/internal/api/server.go),
427
- [registro nativo](../backend/internal/legacy/routes.json),
428
- [chat Go](../backend/internal/api/ai.go) y
429
- [capacidades y límites](../doc/FEATURES.md).
430
-
431
- ## Estado y verificación
432
-
433
- Implementación 27/oct/2026. Comprobaciones locales:
434
-
435
- ```sh
436
- npm ci
437
- npm run typecheck
438
- npm test
439
- npm run routes:sync
440
- npm run build
441
- ```
442
-
443
- Las pruebas lanzan el ejecutable real contra un servidor HTTP sintético local;
444
- comprueban comandos, login, perfiles, contratos de transporte, descargas, multipart,
445
- chat y errores. No equivalen a integración Go/SQL/PAC ni prueban timbrados,
446
- cancelaciones, correos, compras o disponibilidad productiva. No se modificó Go.
447
- Las 11 pruebas pasaron en Windows (Node 24) y Ubuntu mediante WSL (Node 22.22.0).
448
- También se consultaron `/health/live` y `/health/ready` del Go real en
449
- `localhost:8092`: estado `ok`, `ready: true`, conexiones main/sat/catalogs `ok`.
450
- Esas consultas no comprueban login real ni escrituras de negocio.
63
+
64
+ Cada perfil conserva URL, timeout y token independientes. Los nombres de perfiles
65
+ son etiquetas locales; no cambian las banderas fiscales del servidor.
66
+
67
+ ```sh
68
+ nt --profile local config set --base-url http://localhost:8092 --timeout 120
69
+ nt --profile pruebas config set --base-url http://pruebas.ntlink.com.mx:8092
70
+ nt config set --allow-insecure-http
71
+ nt auth login
72
+ nt config use pruebas
73
+ nt config show
74
+ nt config list
75
+ nt --profile local health ready
76
+ ```
77
+
78
+ La URL debe contener protocolo y host/puerto, sin `/api`, usuario, contraseña, query ni fragmento. El origen configurado del perfil `pruebas` está permitido por HTTP para la cuenta compartida del entorno de demostración. Para otros orígenes HTTP, habilita explícitamente `nt config set --allow-insecure-http`; el CLI mostrará una advertencia en cada solicitud. Puedes quitarlo con `nt config set --https-only`.
79
+ También existe `config set --url URL`. Cambiar la URL guardada de un perfil borra
80
+ su token anterior. Un `--base-url` temporal a otro origen tampoco reutiliza su token.
81
+
82
+ | Ajuste | Variable | Opción global |
83
+ |---|---|---|
84
+ | Perfil | `NTLINK_PROFILE` | `--profile` |
85
+ | Origen Go | `NTLINK_BASE_URL` | `--base-url` |
86
+ | Bearer | `NTLINK_TOKEN` | `--token` |
87
+ | Timeout en segundos | `NTLINK_TIMEOUT` | `--timeout` |
88
+
89
+ Prioridad: opción > variable > perfil guardado > valor inicial. No se leen los
90
+ tokens ni el archivo del CLI Proxy. La configuración está en
91
+ `~/.ntlink-invoice/config.json` (Windows: `%USERPROFILE%\.ntlink-invoice\config.json`);
92
+ `NTLINK_INVOICE_CONFIG_DIR` permite cambiar su carpeta.
93
+ Los tokens se almacenan localmente sin cifrar; los archivos nuevos usan modo 0600
94
+ en POSIX y en Windows heredan las ACL de la carpeta del usuario.
95
+ `config show/list` solo muestran el modo y los perfiles; no imprimen el host ni los tokens.
96
+
97
+ ### Autenticación
98
+
99
+ ```sh
100
+ nt auth login
101
+ nt auth login --email usuario@example.invalid
102
+ nt auth status
103
+ nt auth accept-terms
104
+ nt auth logout
105
+ ```
106
+
107
+ El login solicita la contraseña sin mostrarla en terminal. Usa `POST /api/v2/auth/login` con `email/password` y guarda el campo `token` devuelto por Membership Go. **Go no expone refresh token**: un 401 requiere iniciar sesión otra vez. Si el perfil contiene un token vencido, el CLI responde 401 y no repite la petición; vuelve a autenticar y comprueba el efecto de una escritura antes de reintentar. `auth status` consulta el perfil real, no solo la existencia del token.
108
+ `logout` borra el token local del perfil; no revoca JWT ya emitidos ni variables
109
+ de entorno que sigan definidas.
110
+
111
+ Las operaciones genéricas `api` y `native` están deshabilitadas salvo que se pase
112
+ `--unsafe-api` antes del comando (por ejemplo, `nt --unsafe-api api GET /api/v2/customer/profile`).
113
+ Úsalas solo si conoces el contrato exacto: permiten invocar rutas del backend fuera
114
+ de los comandos estables del CLI.
115
+
116
+ Para automatizar, usa `NTLINK_PASSWORD` o `--password-stdin` con `--email`:
117
+
118
+ ```sh
119
+ nt auth login --email usuario@example.invalid --password-stdin
120
+ ```
121
+
122
+ Conecta stdin desde tu gestor de secretos o tubería. No hay opción `--password`
123
+ para evitar dejarla en los argumentos. `auth register registro.json` envía
124
+ `email`, `name`, `phone`, `rfc`, `postalCode`; puede crear una cuenta y enviar correo.
125
+ `auth verify-code codigo.json` envía `email` y `verificationCode`.
126
+
127
+ ### Cuenta, directorios, estadísticas y reportes
128
+
129
+ ```sh
130
+ nt client info
131
+ nt client balance
132
+ nt client companies
133
+ nt client usage --year 2026
134
+ nt client blacklist AAA010101AAA
135
+ nt customers list
136
+ nt customers get 123
137
+ nt customers create cliente.json
138
+ nt customers update 123 cliente.json
139
+ nt customers delete 123
140
+ nt companies list
141
+ nt companies get 10
142
+ nt companies create emisor.json
143
+ nt companies update 10 emisor.json
144
+ nt users list
145
+ nt users create usuario.json
146
+ nt users update usuario@example.invalid usuario.json
147
+ nt dashboard daily --range LAST_MONTH
148
+ nt dashboard types
149
+ nt dashboard status
150
+ nt dashboard top-receivers
151
+ nt dashboard top-transmitters
152
+ nt reports pending-ppd --company 10 --page 0 --size 50
153
+ nt reports retentions --from 2026-01-01 --to 2026-01-31
154
+ ```
155
+
156
+ `users update` recibe el **correo/username** como identificador, conforme a Go.
157
+ La autorización de usuarios exige administrador. Crear emisor no instala su CSD.
158
+ El CLI envía los JSON sin renombrar campos: los directorios usan los DTO del
159
+ [contrato Portal](../backend/doc/modules/Portal.md) y
160
+ [frontend/src/api/portal.ts](../frontend/src/api/portal.ts).
161
+ Ejemplo sintético de cliente (adapta empresa y datos fiscales antes de guardar):
162
+
163
+ ```json
164
+ {
165
+ "companyId": 10,
166
+ "rfc": "AAA010101AAA",
167
+ "legalName": "CLIENTE DE EJEMPLO",
168
+ "taxRegime": "601",
169
+ "postalCode": "06000",
170
+ "email": "cliente@example.invalid"
171
+ }
172
+ ```
173
+
174
+ El saldo viene de `/customer/balances`, con canales de portal y web service.
175
+ PPD conserva `outstandingBalance` y `balanceSource`; consultar no registra pagos.
176
+ Los listados CFDI/reportes/borradores aceptan `--page`, `--size`, `--company`,
177
+ `--customer`, `--search`, `--from`, `--to`, `--filter KEY=VALUE` y
178
+ `-q KEY=VALUE`. Cada endpoint aplica solo sus filtros admitidos: para borradores,
179
+ por ejemplo, usa `--filter estado=sin-timbrar` o `--filter folio=eq:BOR-000000001`.
180
+ La paginación del portal comienza en cero y admite hasta 200 filas por solicitud;
181
+ no descarga automáticamente las páginas restantes. La salida conserva el sobre
182
+ con `content`, `totalElements`, `totalPages`, `page` y `size`.
183
+
184
+ ### Catálogos, CFDI y borradores
185
+
186
+ ```sh
187
+ nt catalogs list
188
+ nt catalogs get moneda --id MXN
189
+ nt catalogs get ClaveProdServ --description consultoria
190
+ nt cfdi list --from 2026-01-01 --to 2026-01-31 --size 100
191
+ nt cfdi list --filter uuid=eq:00000000-0000-0000-0000-000000000000
192
+ nt cfdi get UUID
193
+ nt cfdi status UUID
194
+ nt cfdi validate factura.xml
195
+ nt cfdi download UUID --format xml --output factura.xml
196
+ nt cfdi download UUID --format pdf --output factura.pdf --force
197
+ nt cfdi prepare cfdi.json
198
+ nt drafts create cfdi.json
199
+ nt drafts list --filter estado=sin-timbrar
200
+ nt drafts get ID
201
+ nt drafts download ID --format pdf --output PRE-CFDI.pdf
202
+ nt drafts download ID --format xml --output PRE-CFDI.xml
203
+ nt drafts update ID cfdi.json
204
+ nt drafts reserve-folio
205
+ nt drafts next-folio
206
+ ```
207
+
208
+ `nt drafts create cfdi.json` guarda el borrador y descarga automáticamente su PDF
209
+ como `PRE-CFDI-ID.pdf`; la salida conserva `metadata.id` y añade `pdf.path` y
210
+ `pdf.bytes`. Usa `--pdf-output archivo.pdf` para elegir destino, `--no-pdf` para
211
+ omitir la descarga y `--force` para reemplazar el archivo. Si devuelve `pdfError`,
212
+ el borrador ya existe: recupera el PDF con `drafts download`, sin repetir create.
213
+ Después de actualizar, usa `drafts download` para obtener la versión guardada.
214
+ PDF y XML proceden del PRE-CFDI de la tabla; no se timbran ni se suben a S3.
215
+
216
+ Al cambiar el CLI, actualizar también la [skill cfdi](../.agents/skills/cfdi/SKILL.md)
217
+ y sus copias instaladas en la misma entrega.
218
+
219
+
220
+ Los nombres de catálogo se normalizan al nombre Go, por ejemplo `moneda` →
221
+ `Moneda`. La búsqueda envía `q`; `--id` busca identificador exacto. El listado
222
+ incluye los 15 catálogos del portal; los catálogos nativos adicionales están en
223
+ `native catalogo`. Que un nombre exista no garantiza que la base tenga sus datos.
224
+
225
+ `cfdi prepare` normaliza/recalcula en Go sin guardar ni emitir. `drafts create`
226
+ y `update` guardan en SQL. `drafts get` devuelve un sobre con `cfdi`, metadatos y
227
+ revisión: para actualizar, envía el **objeto `cfdi` interior**, no todo ese sobre.
228
+ `reserve-folio` reserva un folio provisional BOR, no el consecutivo fiscal.
229
+ `next-folio` consulta el siguiente consecutivo fiscal de la empresa sin reservarlo;
230
+ puede cambiar antes de emitir.
231
+ Nómina y complementos viajan en el JSON del portal; el CLI no calcula ISR ni
232
+ subsidio. Consulta [emisión](../doc/flows/ISSUANCE.md) y
233
+ [nómina](../backend/doc/parity/payroll.md) para el alcance de cada variante.
234
+
235
+ Las operaciones explícitas siguientes también tienen comando:
236
+
237
+ ```sh
238
+ nt cfdi stamp cfdi.json
239
+ nt drafts stamp ID cfdi.json
240
+ nt cfdi cancel UUID --reason 02
241
+ nt cfdi cancel UUID --reason 01 --replacement UUID_SUSTITUTO
242
+ nt cfdi email UUID --to facturacion@example.invalid --to archivo@example.invalid
243
+ ```
244
+
245
+ Estos comandos pueden emitir, cancelar o enviar realmente si el servidor lo
246
+ permite. No tienen un modo simulado; nombrar un perfil `pruebas` no garantiza
247
+ que su PAC sea sandbox. Las banderas y permisos de Go siguen siendo obligatorios.
248
+ Las descargas del portal usan `/api/v2/cfdis/{uuid}/xml|pdf`, distintas de las
249
+ rutas de descarga del CLI Proxy, y no sobrescriben archivos sin `--force`.
250
+
251
+ ### Chat y análisis de PDF/XML/JSON
252
+
253
+ ```sh
254
+ nt chat models
255
+ nt chat sessions create
256
+ nt chat sessions list
257
+ nt chat sessions get SESSION_ID
258
+ nt chat attach documento.pdf
259
+ nt chat send SESSION_ID "Analiza las discrepancias de este documento" --attach documento.pdf
260
+ nt chat send SESSION_ID "Consulta mis comprobantes" --provider google --model MODELO_HABILITADO
261
+ nt --timeout 360 chat events RUN_ID
262
+ nt --timeout 360 chat events RUN_ID --last-event-id 3
263
+ nt chat cancel RUN_ID
264
+ nt chat sessions delete SESSION_ID
265
+ ```
266
+
267
+ `send` devuelve `sessionId`, `requestId` y `runId`; consulta sus eventos con
268
+ `chat events`. Se pueden adjuntar hasta tres archivos repitiendo `--attach`.
269
+ `chat attach` extrae el contenido por sí solo. Go conserva los límites de PDF
270
+ (5 MB, 30 páginas) y XML/JSON (1 MB), OCR selectivo y advertencias.
271
+ Los proveedores/modelos deben figurar en `chat models`.
272
+
273
+ `send --request-id UUID` permite reutilizar un identificador conocido sin crear
274
+ una nueva petición de chat; si falta, se genera uno. Los eventos salen como SSE
275
+ original (`id`, `event`, `data`), no como un único JSON. Un evento `error` o
276
+ `cancelled` es parte del protocolo SSE y debe interpretarlo el consumidor.
277
+ Cerrar el CLI o vencer su timeout no cancela la ejecución del servidor;
278
+ usa `chat cancel`, recupera la sesión y revisa borradores antes de repetir.
279
+ No hay reconexión SSE automática: utiliza `--last-event-id`.
280
+ `chat complete solicitud.json` conserva acceso al contrato síncrono de Go.
281
+
282
+ Analizar no autoriza guardar: solicita explícitamente la creación de un borrador
283
+ cuando quieras persistirlo. El chat puede consultar y preparar borradores; sus
284
+ herramientas no timbran, cancelan, cobran ni envían correos. Esas capacidades
285
+ pertenecen a los comandos explícitos y rutas nativas correspondientes.
286
+
287
+ ### API manual y rutas nativas
288
+
289
+ ```sh
290
+ nt health live
291
+ nt health ready
292
+ nt parity
293
+ nt --unsafe-api api GET /api/v2/customer/profile
294
+ nt --unsafe-api api GET /api/v2/cfdis -q page=0 -q size=20
295
+ nt --unsafe-api api POST /api/v2/cfdis/prepare --data cfdi.json
296
+ nt --unsafe-api api POST /api/v2/cfdis/validate --raw factura.xml --content-type application/xml
297
+ nt native routes
298
+ nt native routes --controller Empresa
299
+ nt --unsafe-api native empresa --help
300
+ nt --unsafe-api native empresa get-empresa -q id=10
301
+ nt --unsafe-api native cfdi descargar-acuse --param id=123 --output acuse.xml
302
+ nt --unsafe-api native validarxml validar --file archivoXml=factura.xml
303
+ ```
304
+
305
+ `native` mapea las 87 rutas del registro nativo: catálogos, clientes, empresas,
306
+ usuarios, sesión, CSF, reportes, CFDI/REP, retenciones, plantillas, CSD/FIEL/convenios,
307
+ entrega y compras según el controlador. Todas conservan el método y path de Go.
308
+ `--param KEY=VALUE` sustituye parámetros de ruta, `-q KEY=VALUE` agrega query,
309
+ `--data archivo.json` envía JSON, `--raw archivo` envía bytes y
310
+ `--file CAMPO=RUTA`/`--field CLAVE=VALOR` construyen multipart.
311
+ `--header NOMBRE=VALOR` permite enviar cabeceras adicionales, por ejemplo las
312
+ requeridas por contratos con firma; sus valores no se imprimen en verbose.
313
+ Las tres formas de cuerpo son excluyentes. `--output` guarda cualquier respuesta
314
+ exitosa como bytes; `--stream` muestra SSE original y excluye `--output`.
315
+
316
+ Para conocer los campos particulares, abre el módulo del controlador desde
317
+ [backend/doc/INDEX.md](../backend/doc/INDEX.md), y consulta los nombres exactos con
318
+ `native GRUPO COMANDO --help`. Los comandos nativos siguen nombres derivados de
319
+ las acciones originales y sus DTO nativos; no reciben automáticamente los DTO del
320
+ portal. Por ejemplo, el `id` nativo de CFDI es el ID SQL, no su UUID.
321
+ La tabla siguiente se sincroniza con `npm run routes:sync`; es un inventario de
322
+ acceso, no una certificación de operación externa.
323
+
324
+ <!-- native-routes:start -->
325
+ | Comando (`nt native …`) | HTTP | Ruta Go |
326
+ |---|---|---|
327
+ | `catalogo get-clave-unidad` | GET | `/api/Catalogo/ClaveUnidad` |
328
+ | `catalogo get-clave-unidad-filtro` | GET | `/api/Catalogo/ClaveUnidadFiltro` |
329
+ | `catalogo get-clave-unidad-peso` | GET | `/api/Catalogo/ClaveUnidadPeso` |
330
+ | `catalogo buscar-estaciones` | GET | `/api/Catalogo/Estaciones` |
331
+ | `catalogo buscar-clave-prod-serv-cp` | GET | `/api/Catalogo/ClaveProdServCP` |
332
+ | `catalogo buscar-fraccion-arancelaria` | GET | `/api/Catalogo/FraccionArancelaria` |
333
+ | `catalogo buscar-prod-serv` | GET | `/api/Catalogo/ClaveProdServ` |
334
+ | `catalogo get-estados` | GET | `/api/Catalogo/GetEstados` |
335
+ | `catalogo get-paises` | GET | `/api/Catalogo/GetPaises` |
336
+ | `catalogo get-localidad` | GET | `/api/Catalogo/GetLocalidad` |
337
+ | `catalogo get-municipios` | GET | `/api/Catalogo/GetMunicipios` |
338
+ | `catalogo get-cps` | GET | `/api/Catalogo/GetCPs` |
339
+ | `catalogo existe-cp` | GET | `/api/Catalogo/ExisteCP` |
340
+ | `catalogo get-tasa-cuota` | GET | `/api/Catalogo/TasaCuota` |
341
+ | `catalogo get-monedas` | GET | `/api/Catalogo/Monedas` |
342
+ | `cfdi timbrar` | POST | `/api/CFDI/Timbrar` |
343
+ | `cfdi timbrado-previo` | POST | `/api/CFDI/TimbradoPrevio` |
344
+ | `cfdi guardar-plantilla` | POST | `/api/CFDI/Plantilla` |
345
+ | `cfdi listar-plantillas` | GET | `/api/CFDI/Plantillas` |
346
+ | `cfdi obtener-plantilla` | GET | `/api/CFDI/Plantilla/{id:long}` |
347
+ | `cfdi eliminar-plantilla` | POST | `/api/CFDI/Plantilla/{id:long}` |
348
+ | `cfdi descargar-xml` | GET | `/api/CFDI/xml/{id:int}` |
349
+ | `cfdi verificar-pdf` | HEAD | `/api/CFDI/pdf/{id:int}` |
350
+ | `cfdi descargar-pdf` | GET | `/api/CFDI/pdf/{id:int}` |
351
+ | `cfdi descargar-acuse` | GET | `/api/CFDI/acuse/{id:int}` |
352
+ | `cfdi enviar-correo` | POST | `/api/CFDI/correo/{id:int}` |
353
+ | `cfdi enviar-whats-app` | POST | `/api/CFDI/whatsapp/{id:int}` |
354
+ | `cfdi descargar-pdf-whats-app` | GET | `/api/CFDI/wa/{token}/{archivo}` |
355
+ | `cfdi cancelar` | POST | `/api/CFDI/cancelar/{id:int}` |
356
+ | `cliente get-clientes` | GET | `/api/Cliente/GetClientes` |
357
+ | `cliente get-by-id` | GET | `/api/Cliente/GetById` |
358
+ | `cliente guardar` | POST | `/api/Cliente/Guardar` |
359
+ | `config get-session-config` | GET | `/api/Config/session` |
360
+ | `empresa get-empresas` | GET | `/api/Empresa/GetEmpresas` |
361
+ | `empresa get-sucursales` | GET | `/api/Empresa/GetSucursales` |
362
+ | `empresa get-empresa` | GET | `/api/Empresa/GetEmpresa` |
363
+ | `empresa get-sucursales-lista` | GET | `/api/Empresa/GetSucursalesLista` |
364
+ | `empresa get-sucursal` | GET | `/api/Empresa/GetSucursal` |
365
+ | `empresa guardar` | POST | `/api/Empresa/Guardar` |
366
+ | `empresa validar-csd` | POST | `/api/Empresa/ValidarCSD` |
367
+ | `empresa tiene-csd` | GET | `/api/Empresa/TieneCsd` |
368
+ | `empresa subir-fiel` | POST | `/api/Empresa/SubirFiel` |
369
+ | `empresa guardar-sucursal` | POST | `/api/Empresa/GuardarSucursal` |
370
+ | `empresa parse-constancia` | POST | `/api/Empresa/ParseConstancia` |
371
+ | `homedashboard get-stats` | GET | `/api/HomeDashboard/stats` |
372
+ | `listanegra consultar-xml` | POST | `/api/ListaNegra/consultar-xml` |
373
+ | `pagos config` | GET | `/api/Pagos/Config` |
374
+ | `pagos crear-cargo2` | POST | `/api/Pagos/CrearCargo2` |
375
+ | `pagos validar-sesion` | GET | `/api/Pagos/ValidarSesion` |
376
+ | `pagos webhook` | POST | `/api/Pagos/Webhook` |
377
+ | `pagos generar-pre-factura-externa` | POST | `/api/Pagos/GenerarPreFacturaExterna` |
378
+ | `renovarfolios obtener-datos-empresa` | GET | `/api/RenovarFolios/ObtenerDatosEmpresa` |
379
+ | `renovarfolios renovar` | POST | `/api/RenovarFolios/Renovar` |
380
+ | `reporte get-cfdi` | GET | `/api/Reporte/GetCFDI` |
381
+ | `reporte lista-timbrado` | GET | `/api/Reporte/lista-timbrado` |
382
+ | `reporte reporte-anual` | GET | `/api/Reporte/reporte-anual` |
383
+ | `reporte reporte-empresa` | GET | `/api/Reporte/reporte-empresa` |
384
+ | `reporte anios` | GET | `/api/Reporte/anios` |
385
+ | `retenciones reporte` | GET | `/api/Retenciones/Reporte` |
386
+ | `retenciones enviar-correo` | POST | `/api/Retenciones/correo/{id:int}` |
387
+ | `retenciones cancelar` | POST | `/api/Retenciones/cancelar/{id:int}` |
388
+ | `retenciones descargar-acuse` | GET | `/api/Retenciones/acuse/{id:int}` |
389
+ | `retenciones timbrar` | POST | `/api/Retenciones/Timbrar` |
390
+ | `retenciones timbrado-previo` | POST | `/api/Retenciones/TimbradoPrevio` |
391
+ | `retenciones descargar-xml` | GET | `/api/Retenciones/xml/{id:int}` |
392
+ | `retenciones verificar-pdf` | HEAD | `/api/Retenciones/pdf/{id:int}` |
393
+ | `retenciones descargar-pdf` | GET | `/api/Retenciones/pdf/{id:int}` |
394
+ | `retenciones enviar-whats-app` | POST | `/api/Retenciones/whatsapp/{id:int}` |
395
+ | `retenciones descargar-pdf-whats-app` | GET | `/api/Retenciones/wa/{token}/{archivo}` |
396
+ | `session login` | POST | `/api/Session/Login` |
397
+ | `session generar-codigo` | POST | `/api/Session/GenerarCodigo` |
398
+ | `session generar-codigo-correo` | POST | `/api/Session/GenerarCodigoCorreo` |
399
+ | `session validar-codigo` | POST | `/api/Session/ValidarCodigo` |
400
+ | `session heartbeat` | POST | `/api/Session/Heartbeat` |
401
+ | `session logout` | POST | `/api/Session/Logout` |
402
+ | `session recuperar-contrasena` | POST | `/api/Session/RecuperarContrasena` |
403
+ | `session upload-csf` | POST | `/api/Session/UploadCSF` |
404
+ | `session registro` | POST | `/api/Session/Registro` |
405
+ | `session cambiar-contrasena` | POST | `/api/Session/CambiarContrasena` |
406
+ | `usuarioadmin get-usuarios` | GET | `/api/UsuarioAdmin/GetUsuarios` |
407
+ | `usuarioadmin debug-profile` | GET | `/api/UsuarioAdmin/DebugProfile` |
408
+ | `usuarioadmin guardar` | POST | `/api/UsuarioAdmin/Guardar` |
409
+ | `usuarioadmin editar` | POST | `/api/UsuarioAdmin/Editar` |
410
+ | `usuarioadmin cambiar-password` | POST | `/api/UsuarioAdmin/CambiarPassword` |
411
+ | `usuarioadmin bloquear` | POST | `/api/UsuarioAdmin/Bloquear/{userId}` |
412
+ | `usuarioadmin eliminar` | POST | `/api/UsuarioAdmin/Eliminar/{userId}` |
413
+ | `validarxml validar` | POST | `/api/ValidarXML/validar` |
414
+ <!-- native-routes:end -->
415
+
416
+ ## Flujo y fallos
417
+
418
+ CLI → perfil/variables/opciones → HTTP Bearer a Go → permisos y reglas Go →
419
+ SQL/servicios según la ruta → JSON o archivo. No copia reglas fiscales al cliente.
420
+ Las peticiones anónimas solo se usan en login/registro/verificación, salud/paridad
421
+ y rutas nativas marcadas así por el registro; los catálogos del portal sí llevan JWT.
422
+ `api --anonymous` permite omitirlo deliberadamente.
423
+
424
+ La salida normal es JSON en stdout (`--json` es compatible y opcional). Los
425
+ errores van a stderr y terminan con código 1; el éxito usa 0. Ayuda/versión son
426
+ texto. `--verbose` imprime método, origen, ruta y estado en stderr; omite tokens,
427
+ query y cuerpos. Con `--output`, stdout devuelve `{ "path": "...", "bytes": 123 }`.
428
+ En PowerShell, evita convertir XML/PDF a texto por tubería: usa `--output`.
429
+
430
+ No se reintenta ninguna petición automáticamente, tampoco después de 401. Un
431
+ timeout, 502 o fallo de red durante una escritura puede tener resultado incierto:
432
+ consulta su estado antes de repetir. Un timbrado externo y su persistencia SQL
433
+ pueden tener éxito parcial; sigue la recuperación documentada en Go.
434
+ Los errores HTTP conservan estado y mensaje; `exito:false` nativo también falla.
435
+ Go puede devolver 403 por alcance, 409 por conflicto, 501 por módulo pendiente o
436
+ 503 por configuración/servicio/catálogo ausente. El CLI no habilita esas funciones.
437
+
438
+ ## Reglas y trazabilidad
439
+
440
+ | Regla | Implementación y evidencia |
441
+ |---|---|
442
+ | Token separado por perfil y origen | `src/config.ts`; pruebas de perfiles, override y logout |
443
+ | Sin refresh ni repetición automática | `src/http.ts`, `auth login`; pruebas 401/503 y escrituras |
444
+ | Descarga exacta y sin sobrescritura implícita | `src/http.ts`; prueba de bytes y `--force` |
445
+ | Rutas reales sin inventar equivalentes Proxy | `src/native-routes.json`, `scripts/sync-routes.mjs`; prueba contra registro Go |
446
+ | Chat conserva requestId y SSE | `src/cli.ts`; prueba de adjuntos, modelo, eventos y cancelación |
447
+
448
+ Fuentes: [Portal](../backend/doc/modules/Portal.md),
449
+ [router Go](../backend/internal/api/server.go),
450
+ [registro nativo](../backend/internal/legacy/routes.json),
451
+ [chat Go](../backend/internal/api/ai.go) y
452
+ [capacidades y límites](../doc/FEATURES.md).
453
+
454
+ ## Estado y verificación
455
+
456
+ Implementación 27/oct/2026. Comprobaciones locales:
457
+
458
+ ```sh
459
+ npm ci
460
+ npm run typecheck
461
+ npm test
462
+ npm run routes:sync
463
+ npm run build
464
+ ```
465
+
466
+ Las pruebas lanzan el ejecutable real contra un servidor HTTP sintético local;
467
+ comprueban comandos, login, perfiles, contratos de transporte, descargas, multipart,
468
+ chat y errores. No equivalen a integración Go/SQL/PAC ni prueban timbrados,
469
+ cancelaciones, correos, compras o disponibilidad productiva. No se modificó Go.
470
+ Las 11 pruebas pasaron en Windows (Node 24) y Ubuntu mediante WSL (Node 22.22.0).
471
+ También se consultaron `/health/live` y `/health/ready` del Go real en
472
+ `localhost:8092`: estado `ok`, `ready: true`, conexiones main/sat/catalogs `ok`.
473
+ Esas consultas no comprueban login real ni escrituras de negocio.