parche-cli 0.1.1 → 0.6.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 ADDED
@@ -0,0 +1,106 @@
1
+ # Cambios
2
+
3
+ Todas las versiones de `parche-cli` (npm) y de la Action `Pepo08/parche@v0`.
4
+
5
+ ## 0.6.0 — 2026-10-17
6
+
7
+ ### Nuevo
8
+ - **ARCA con `DocTipo` en una variable: PR parcial (consumidor final).** Antes se reportaba como "falta un dato" y no se tocaba nada. Ahora Parche aplica la regla 99 → 5 **al ejecutarse** y, con otro `DocTipo`, el pedido sale igual que hoy (la clave ni existe). En JS agrega `...(Number(docTipo) === 99 ? { CondicionIVAReceptorId: 5 } : {})`; en PHP, un `if ((int) $data['DocTipo'] === 99)` después de armar el array, nunca `null`. El PR sale borrador, con "(parcial: consumidor final)" en el título y, en "Lo que este PR NO resuelve", los casos que no cubre con los códigos válidos. No se aplica a facturas A, a expresiones con efectos (llamadas, `await`) ni a arrays de PHP dentro de la llamada. Si se corre de nuevo después del merge, no cambia nada y sigue avisando lo que falta.
9
+ - El ledger registra cuántos arreglos parciales lleva cada PR (`partial`).
10
+
11
+ ### Arreglado
12
+ - ARCA en JS: la clave nueva usa las mismas comillas que las claves del objeto. Antes copiaba las del resto del archivo y, en un repo real con claves `'...'` en un archivo con `"..."`, quedaba distinta a sus vecinas.
13
+
14
+ ## 0.5.1 — 2026-10-17
15
+
16
+ Lo que apareció en el primer PR real (lunamarie11/obraya#1).
17
+
18
+ ### Arreglado
19
+ - **Los commits ya no se firman con `bot@parche.dev`**, un dominio que no es nuestro. Ahora firma `PARCHE_GIT_NAME`/`PARCHE_GIT_EMAIL` si están; si no, la identidad de git de quien lo corre; y en GitHub Actions sin identidad, `github-actions[bot]`. Sin ninguna, `parche pr` frena antes de tocar el repo.
20
+ - **`parche pr` chequea la sintaxis siempre**, también sin `--verify` (no ejecuta nada del repo). El PR ya no dice "Sin verificar": dice que Parche no corrió los tests y nombra los workflows del repo que los corren en cada pull request (`pull_request`; `pull_request_target` no cuenta).
21
+ - El texto para un issue (sin cambios de código) ya no lleva la sección de verificación.
22
+
23
+ ## 0.5.0 — 2026-10-16
24
+
25
+ Lo que encontró el frente de tracción al correr Parche sobre 175 repos públicos reales.
26
+
27
+ ### Nuevo
28
+ - **La versión de la librería de ARCA importa:** con `afip.js`/`afip.php` 0.x (o `eesnaola/afip.php`) el campo no llega a ARCA (WSDL de 2021). Parche lee la versión instalada y el arreglo pasa a "revisar" con el aviso de actualizar a 1.x.
29
+ - Detección de **`.wsdl` de WSFEv1 sin el campo** en el repo.
30
+ - **Python, C#, Java, Go y VB.NET:** detección de integraciones SOAP propias y de clientes SOAP generados sin el campo.
31
+ - `parche pr --push-remote <remote>`: abre PRs **desde un fork**.
32
+ - **Pack de IA al día con las páginas oficiales:** `claude-sonnet-4-5-20250929` se retira el 30/11/2026; OpenAI retira `gpt-3.5-turbo`, `gpt-4`, `gpt-4-turbo`, `o1`, `o3-mini`, `o4-mini`, `gpt-4.1-nano`, `gpt-image-1` y otros el **23/10/2026**, y los snapshots de `gpt-5-*-2025-08-07` y `o3` el 11/12/2026; suma los ya retirados (DALL·E, `gpt-3.5-turbo-instruct`, `chatgpt-4o-latest`, Claude 2/3.5…). Cada aviso trae el reemplazo recomendado.
33
+
34
+ ### Arreglado
35
+ - `parche pr` dejaba el archivo modificado en la rama base cuando fallaba la verificación; ahora el repo queda como estaba y la rama se borra. Tampoco quedan ramas vacías.
36
+ - No se abre un PR cuyo único cambio sea un test o un mock.
37
+ - ARCA en JS: se reconocen `(afip.ElectronicBilling as any).createVoucher(...)`, `afip.ElectronicBilling!` y alias (`const eb = afip.ElectronicBilling`). Si un archivo de ARCA no parsea, se avisa (antes se descartaba en silencio).
38
+ - Menos ruido: la detección de SOAP propio ya no marca tests, mocks, tipos, constantes ni copias de la librería; si el repo ya usa el campo en otro archivo, lo que no se pudo leer pasa a "revisar".
39
+ - Pack de IA: un modelo en una lista o catálogo se avisa como mención, no como uso; en Azure OpenAI no se da por cerrada la Assistants API.
40
+ - Mucho más rápido en repos grandes: los codemods de ARCA no parsean archivos que no mencionan ARCA.
41
+ - El texto para un issue (sin cambios de código) ya no habla de "este PR".
42
+ - `detect.packages` de ARCA: fuera `afip.js` (no existe en npm); suma `@arcasdk/core`, `afip.ts`, `ts-afip-ws` y `facturajs`.
43
+
44
+ ### Cambiado
45
+ - `docs/arquitectura.md` sale del repo: era documentación interna del equipo. El formato del inventario sigue en `docs/inventario.md`.
46
+
47
+ ## 0.4.1 — 2026-10-16
48
+
49
+ Arreglos encontrados al correr Parche sobre los primeros repos públicos reales con ARCA y Mercado Pago.
50
+
51
+ ### Arreglado
52
+ - **ARCA:** si el mismo objeto de comprobante se pasaba a dos llamadas, el campo se insertaba dos veces (clave duplicada: TypeScript no compila). Ahora se inserta una sola vez, en JS y en PHP.
53
+ - **Decoradores** (NestJS, Angular, TypeORM): el chequeo de sintaxis fallaba y `--verify`/`--dry-run` frenaban el PR; el inventario no veía nada en esos archivos.
54
+ - **Inventario en monorepos:** se leen todos los `package.json`, `composer.json` y `requirements.txt` del repo, no solo los de la raíz (`manifestFiles` dice cuáles).
55
+ - **Inventario:** se siguen los clientes guardados en clases (`private x = new X()`, `this.x = new X()`).
56
+ - `http://localhost:${PORT}` ya no aparece como un host externo.
57
+ - `socket.io-client`, `@prisma/client`, `@trpc/client`, `pg`, `ws`, `@aws-sdk/types` y similares ya no se listan como APIs; toda la familia `@aws-sdk/` cuenta como AWS.
58
+
59
+ ### Mejorado
60
+ - **ARCA:** cuando el comprobante lo arma otra función o llega como parámetro, el hallazgo dice dónde agregar el dato (por ejemplo: "lo arma `buildVoucherPayload()` (de `@/lib/afip/voucher-builder`)").
61
+
62
+ ## 0.4.0 — 2026-10-16
63
+
64
+ ### Nuevo
65
+ - **Qué cambia en las versiones nuevas de tus SDKs** (`parche scan --upgrades`): para cada SDK de npm que el repo usa, compara la versión instalada con la última publicada leyendo sus tipos (`.d.ts`) y dice qué operaciones **del repo** desaparecen, piden parámetros obligatorios nuevos, pierden parámetros, cambian de tipo o pasan a deprecadas, con archivo y línea. Solo avisa (detect-only).
66
+ - **`parche diff <paquete> --from <v> --to <v>`**: el mismo análisis para cualquier paquete, sin repo (acepta `latest` y versiones parciales; `--all`, `--json`).
67
+ - Tarballs bajados del registry con verificación de integridad y cacheados en `~/.parche/cache` (`PARCHE_CACHE`, `PARCHE_OFFLINE=1`). Si el paquete no trae tipos, usa `@types/<paquete>`.
68
+ - Ejemplo nuevo: `examples/legacy-ia` (SDK de OpenAI v3: `OpenAIApi` desaparece en las versiones nuevas, como dice la guía oficial de migración).
69
+ - Dependencia nueva: `typescript ~6.0.3` (la última con el compiler API de JavaScript), cargada solo para el diff.
70
+
71
+ ## 0.3.0 — 2026-10-16
72
+
73
+ ### Nuevo
74
+ - **Inventario de cualquier API** (nivel 1): `parche scan` lista todas las APIs de terceros del repo, tengan pack o no.
75
+ - **SDKs** de `package.json`, `composer.json` y `requirements.txt`, clasificados con una lista curada (`data/known-sdks.json`: pagos, facturación, IA, mensajería, email, nube, datos y más) y una heurística por nombre, con nivel de confianza.
76
+ - En JS/TS, **qué operaciones** se usan de cada SDK (`stripe.paymentIntents.create`, `client.messages.create`…), siguiendo imports, `require` e instancias.
77
+ - **Llamadas HTTP directas** con `fetch`, `axios` (incluido `axios.create({ baseURL })`), `got`, `undici` y `node-fetch`: método, host y ruta. Las URLs dinámicas se reportan aparte; lo local y lo relativo se descarta.
78
+ - **Modelos de IA** nombrados en el código (OpenAI, Anthropic, Gemini, Mistral, Cohere).
79
+ - `.parche/inventory.json` con `schemaVersion: 1`, documentado en `docs/inventario.md` (los campos anteriores se mantienen).
80
+ - La salida de `scan` y `.parche/report.md` muestran el inventario completo.
81
+ - Ejemplo nuevo: `examples/saas-demo` (Stripe, Twilio, OpenAI, GitHub, Mercado Libre y HTTP directo).
82
+ - `docs/arquitectura.md`: qué hace cada parte del código y por qué.
83
+
84
+ ## 0.2.0 — 2026-10-16
85
+
86
+ ### Nuevo
87
+ - **Pack ARCA para PHP**: `afipsdk/afip.php` y sus forks (`fertandil87/afip.php`, `ivanalemunioz/afip.php`, `eesnaola/afip.php`). Agrega `CondicionIVAReceptorId` a `$afip->ElectronicBilling->CreateVoucher($data)` y `CreateNextVoucher($data)` con las mismas reglas que en JS: DocTipo 99 → automático; DNI con comprobante B o C → para revisar; factura A o CUIT → pide el dato y muestra los códigos válidos.
88
+ - Lee el array en la llamada (`array()` o `[]`) o en una variable definida una vez en la misma función; si no puede leerlo con certeza, lo reporta y no toca nada.
89
+ - Copia el estilo del cliente: comillas, indentación (espacios o tabs), `=>` alineados, coma final y fin de línea.
90
+ - El motor escanea archivos `.php`, lee `composer.json` y saltea `vendor/`.
91
+ - `--verify` corre `composer test` o `vendor/bin/phpunit` cuando cambian archivos PHP (y `npm test` cuando cambian archivos JS/TS).
92
+ - Ejemplo nuevo: `examples/tienda-php`.
93
+
94
+ ### Arreglado
95
+ - El cuerpo del PR decía "✅ pasan" cuando el repo no tenía tests; ahora dice que no hubo tests y que se validó la sintaxis.
96
+ - Mercado Pago: el `import { randomUUID }` nuevo queda junto a los otros imports.
97
+ - ARCA (JS): si la última propiedad no tenía coma final, la propiedad nueva tampoco la lleva.
98
+
99
+ ## 0.1.1 — 2026-10-15
100
+
101
+ - Publicado en npm como `parche-cli` (npm no acepta `parche`). El comando instalado sigue siendo `parche`.
102
+
103
+ ## 0.1.0 — 2026-10-15
104
+
105
+ - Primera versión: CLI (`packs`, `scan`, `fix`, `pr`, `feedback`), GitHub Action y packs de ARCA (afip.js), Mercado Pago Payments → Orders y retiros de modelos de IA.
106
+ - CI en Linux y Windows. Arreglados 4 bugs que solo aparecían en Windows (offsets de recast, CRLF, rutas con `\`, `npm test` sin shell).
package/README.md CHANGED
@@ -2,24 +2,31 @@
2
2
 
3
3
  [![CI](https://github.com/Pepo08/parche/actions/workflows/ci.yml/badge.svg)](https://github.com/Pepo08/parche/actions/workflows/ci.yml)
4
4
 
5
- Parche lee tu código, arma un inventario de qué APIs de terceros usás (Mercado Pago, ARCA, modelos de IA…) y lo cruza contra una base de **cambios de API verificados**. Cuando un proveedor cambia algo que te afecta, Parche abre un PR con el arreglo, explica qué cambió y por qué, y marca lo que necesita una persona. Ningún PR sale sin pasar la verificación.
5
+ Parche lee tu código y arma el inventario de **todas** las APIs de terceros que usás: SDKs (Stripe, Twilio, OpenAI, AWS, Mercado Pago, ARCA…), llamadas HTTP directas y modelos de IA. Lo cruza contra una base de **cambios de API verificados** y, cuando un proveedor cambia algo que te afecta, te avisa o abre un PR con el arreglo, explica qué cambió y por qué, y marca lo que necesita una persona. Ningún PR sale sin pasar la verificación. Las APIs de pagos y facturación de Latinoamérica son el fuerte, pero el inventario cubre cualquier API.
6
6
 
7
7
  ```
8
8
  $ parche scan .
9
- APIs encontradas:
10
- mercadopago 4 usos en 4 archivo(s) [Payment.create, Payment.get]
11
- arca 3 usos en 1 archivo(s) [ElectronicBilling.createNextVoucher]
12
- ia 2 usos en 1 archivo(s) [modelo claude-3-5-haiku-20241022, OpenAI Assistants API]
9
+ APIs del repo (6):
10
+ SDK Anthropic · @anthropic-ai/sdk alta messages.create · con pack
11
+ SDK ARCA (ex AFIP) · @afipsdk/afip.js alta ElectronicBilling.createNextVoucher ×3 · con pack
12
+ SDK Mercado Pago · mercadopago alta Payment.create ×3, Payment.get · con pack
13
+ SDK OpenAI · openai alta beta.assistants.create · con pack
14
+ IA Anthropic · modelos alta claude-3-5-haiku-20241022 · con pack
15
+ IA OpenAI · modelos alta gpt-4o · con pack
13
16
 
14
17
  ■ Condición frente al IVA del receptor obligatoria en WSFEv1
15
- ARCA (ex AFIP) · severidad critical · vence 2026-12-01 (47 días)
18
+ ARCA (ex AFIP) · severidad critical · vence 2026-12-01 (46 días)
16
19
  ✅ automático src/facturacion.js:14 Agregado CondicionIVAReceptorId: 5 (Consumidor Final)…
17
20
  👀 revisar src/facturacion.js:56 Agregado CondicionIVAReceptorId: 5 por inferencia (DNI + comprobante B/C)…
18
21
  ✋ falta un dato src/facturacion.js:62 Falta CondicionIVAReceptorId y no se puede inferir… (comprobante clase A).
22
+
23
+ ■ Modelos y endpoints de IA retirados o por retirarse
24
+ 🔎 detectado src/ia.js:9 El modelo `claude-3-5-haiku-20241022` fue retirado el 2026-02-19: las llamadas ya fallan…
19
25
  …
20
- ✅ automático: 5 👀 revisar: 8 ✋ falta un dato: 1 ⛔ bloqueado: 1 🔎 detectado: 2
21
26
  ```
22
27
 
28
+ El inventario completo (con operaciones, ubicaciones y nivel de confianza) queda en `.parche/inventory.json`; el formato está en [`docs/inventario.md`](docs/inventario.md). Para ver un repo con APIs de todo tipo, desde este repo: `node bin/parche.js scan examples/saas-demo`.
29
+
23
30
  ## Por qué packs y no "un LLM que arregla todo"
24
31
 
25
32
  Ya hay varios prototipos que le piden a un modelo que lea un changelog y escriba el fix. El problema es que el modelo solo sabe lo que aprendió en su entrenamiento, y una migración de pagos o de facturación no admite "casi bien". Parche separa las dos cosas:
@@ -36,6 +43,8 @@ Requiere Node 22 o más nuevo. El paquete de npm se llama `parche-cli`; si lo in
36
43
  ```bash
37
44
  npx parche-cli packs # cambios que Parche conoce
38
45
  npx parche-cli scan . # inventario + qué te afecta (.parche/report.md)
46
+ npx parche-cli scan . --upgrades # + qué cambia en las versiones nuevas de tus SDKs
47
+ npx parche-cli diff <paquete> --from <v> --to <v> # qué cambia en la API de un paquete
39
48
  npx parche-cli fix . # aplica los arreglos en tu working copy
40
49
  npx parche-cli pr . --verify # una rama + commit + PR por cambio
41
50
  npx parche-cli feedback . --pack <id> --outcome merged|edited|rejected
@@ -43,6 +52,27 @@ npx parche-cli feedback . --pack <id> --outcome merged|edited|rejected
43
52
 
44
53
  `parche pr` abre el PR por la API de GitHub si hay `GITHUB_TOKEN` y un remote `origin` en github.com; si no (o con `--dry-run`), deja la rama, el commit y el cuerpo del PR en `.parche/pr-<pack>.md`. Si hay ítems para revisar, el PR se abre como borrador.
45
54
 
55
+ - **Desde un fork:** `parche pr . --push-remote fork` pushea la rama al remote `fork` (tu fork, con tus credenciales de git) y abre el PR contra `origin`.
56
+ - **Nunca deja el repo sucio:** si la verificación falla, descarta lo que escribió y borra su rama. Si no hay cambios de código, o solo cambiarían tests o mocks, no abre PR: deja el texto como issue en `.parche/`.
57
+
58
+ ### Qué cambia en las versiones nuevas de tus SDKs
59
+
60
+ `--upgrades` compara, para cada SDK de npm que usás, la versión instalada con la última publicada. Lee los tipos (`.d.ts`) de las dos versiones y te dice qué operaciones **de tu código** desaparecen, piden parámetros nuevos, cambian de tipo o pasan a deprecadas. Solo avisa: no toca código.
61
+
62
+ ```
63
+ $ npx parche-cli scan . --upgrades
64
+ …
65
+ Versiones nuevas de tus SDKs de npm:
66
+ openai 3.3.0 → 7.34.0 ⚠ 2 de tus operaciones cambian o desaparecen
67
+ OpenAIApi.createChatCompletion (src/asistente.js:8)
68
+ `OpenAIApi` ya no existe.
69
+ OpenAIApi.createEmbedding (src/asistente.js:16)
70
+ `OpenAIApi` ya no existe.
71
+ stripe 17.0.0 → 23.0.0 ✓ lo que usás no cambia (22 cambios en el SDK)
72
+ ```
73
+
74
+ Y para un paquete cualquiera, sin repo: `npx parche-cli diff stripe --from 17 --to latest` (acepta versiones parciales y `latest`; `--all` para ver todo, `--json` para procesarlo). Los tarballs quedan en `~/.parche/cache` (cambiable con `PARCHE_CACHE`); con `PARCHE_OFFLINE=1` usa solo el caché.
75
+
46
76
  ### En GitHub Actions
47
77
 
48
78
  Para que corra todas las semanas, copiá [`templates/github-workflow.yml`](templates/github-workflow.yml) a `.github/workflows/parche.yml`. El paso clave es:
@@ -61,10 +91,30 @@ El workflow necesita `permissions: contents: write` y `pull-requests: write`.
61
91
 
62
92
  ```bash
63
93
  npm install
64
- npm test # 13 tests; el CI los corre en Linux y Windows
65
- bash demo.sh # demo completa sobre examples/tienda-demo
94
+ npm test # el CI los corre en Linux y Windows
95
+ bash demo.sh # demo completa sobre examples/tienda-demo (JS)
96
+ node bin/parche.js scan examples/tienda-php # ejemplo PHP con afip.php
97
+ node bin/parche.js scan examples/saas-demo # SaaS con Stripe, Twilio, OpenAI y HTTP directo
98
+ node bin/parche.js scan examples/legacy-ia --upgrades # OpenAI v3: qué se rompe al actualizar (usa la red)
99
+ ```
100
+
101
+ #### Sacar una versión
102
+
103
+ Las versiones se publican solas desde GitHub Actions ([`release.yml`](.github/workflows/release.yml)) con [trusted publishing](https://docs.npmjs.com/trusted-publishers): npm confía en este repo y en ese workflow, sin tokens ni 2FA a mano. Desde `main` actualizado:
104
+
105
+ ```bash
106
+ npm version 0.1.2 --no-git-tag-version # cambia package.json y package-lock.json
107
+ git commit -am "Parche 0.1.2"
108
+ git tag -a v0.1.2 -m "Parche 0.1.2: qué cambió" # el mensaje del tag es el texto del GitHub Release
109
+ git push origin main v0.1.2
66
110
  ```
67
111
 
112
+ Con el tag, el workflow corre los tests, controla que la versión de `package.json` coincida con el tag y que el commit esté en `main`, publica `parche-cli` en npm (con provenance), mueve `v0` a esta versión (es la que usan los workflows con `Pepo08/parche@v0`) y crea el GitHub Release. Para notas largas, `git tag -a v0.1.2 -F notas.md`.
113
+
114
+ - Solo dispara con versiones estables (`vX.Y.Z`); un tag como `v0.2.0-beta.1` no hace nada.
115
+ - Si falla un paso después de publicar, "Re-run failed jobs" retoma: lo que ya está hecho se saltea.
116
+ - Requiere, una sola vez, el Trusted Publisher en npmjs.com: `parche-cli` → Settings → Trusted publishing → GitHub Actions, con `Pepo08` / `parche` / `release.yml` y permiso para `npm publish`.
117
+
68
118
  ## Estados de cada hallazgo
69
119
 
70
120
  | Estado | Qué significa |
@@ -79,18 +129,63 @@ bash demo.sh # demo completa sobre examples/tienda-demo
79
129
 
80
130
  | Pack | Proveedor | Tipo | Fecha límite | Arreglo |
81
131
  |---|---|---|---|---|
82
- | `arca.wsfe.condicion-iva-receptor` | ARCA | rompe | 2026-12-01 | sí (consumidor final y DNI B/C) |
132
+ | `arca.wsfe.condicion-iva-receptor` | ARCA | rompe | 2026-12-01 | sí, en JS (`afip.js`) y PHP (`afipsdk/afip.php` y forks): consumidor final y DNI B/C; con `DocTipo` en una variable, consumidor final al ejecutarse (PR parcial) |
83
133
  | `mercadopago.payments-to-orders` | Mercado Pago | migración | — | sí (creación de pagos en SDK Node v2) |
84
134
  | `ai.model-retirements` | Anthropic, OpenAI | retiro | varias | solo detección |
85
135
 
86
136
  ## Verificación
87
137
 
88
- Antes de commitear, `--verify` parsea cada archivo modificado (JS/TS) y corre el `npm test` del repo. Si algo falla, no hay PR. El commit lleva la versión del pack que lo generó, para poder rastrear qué regla produjo cada cambio.
138
+ Antes de commitear, Parche parsea **siempre** cada archivo modificado (JS/TS o PHP): eso no ejecuta nada del repo. Con `--verify`, además corre los tests del repo que correspondan a lo que cambió: `npm test` para JS/TS; para PHP, `composer test` o `vendor/bin/phpunit`. Si no hay tests, queda el chequeo de sintaxis y el PR lo dice. Sin `--verify` (por ejemplo, en un PR a un repo de terceros, donde no se ejecuta su código), el PR dice que Parche no corrió los tests y nombra los workflows del repo que los corren en cada pull request. Si algo falla, no hay PR. El commit lleva la versión del pack que lo generó, para poder rastrear qué regla produjo cada cambio.
139
+
140
+ **Quién firma el commit:** `PARCHE_GIT_NAME` y `PARCHE_GIT_EMAIL` si están; si no, tu identidad de git (`user.name` y `user.email`, también la que configure tu workflow); en GitHub Actions sin identidad, `github-actions[bot]`, el mismo que abre el PR. Si no hay ninguna, `parche pr` frena antes de tocar el repo.
141
+
142
+ ## ARCA en PHP
143
+
144
+ Para `afipsdk/afip.php` y sus forks (`fertandil87/afip.php`, `ivanalemunioz/afip.php`, `eesnaola/afip.php`), Parche busca `$afip->ElectronicBilling->CreateVoucher($data)` y `CreateNextVoucher($data)` (también en minúsculas: PHP no distingue) y agrega una línea por factura:
145
+
146
+ ```php
147
+ 'DocNro' => 0,
148
+ // parche: 5 = Consumidor Final (DocTipo 99, sin identificar)
149
+ 'CondicionIVAReceptorId' => 5,
150
+ ```
151
+
152
+ - Lee el array cuando está en la llamada (`array(...)` o `[...]`) o en una variable definida una sola vez, antes de la llamada y en la misma función. Si los datos llegan de otro lado, se reasignan o se completan en varios pasos (`$data['DocTipo'] = ...`), lo reporta como "falta un dato" y no toca nada.
153
+ - Copia el estilo del array: comillas de las claves, indentación (espacios o tabs), alineación de los `=>`, coma final y fin de línea (LF o CRLF).
154
+ - Saltea `vendor/`.
155
+
156
+ **La versión de la librería importa.** Con `afip.js`/`afip.php` 0.x (y `eesnaola/afip.php`) el pedido se arma con un WSDL de 2021 que no tiene `CondicionIVAReceptorId`: aunque el campo esté en el código, no llega a ARCA. Parche lee la versión instalada (`composer.lock`, `package-lock.json`, `node_modules`) y, en esos casos, el arreglo sale como **"revisar"** con el aviso de actualizar a 1.x. También avisa si el repo tiene un `.wsdl` de WSFEv1 sin el campo.
157
+
158
+ **Otros lenguajes:** en Python, C#, Java, Go y VB.NET, Parche detecta integraciones SOAP propias y clientes SOAP generados sin el campo (no los reescribe).
159
+
160
+ ## ARCA con `DocTipo` en una variable: PR parcial
161
+
162
+ Cuando `DocTipo` no es un número fijo (`DocTipo: docTipo`), Parche no puede saber la condición frente al IVA de cada cliente. Lo único que se sabe sin datos del negocio es que **99 (consumidor final sin identificar) va con 5**, así que aplica esa regla **al ejecutarse** y, con cualquier otro `DocTipo`, el pedido sale exactamente igual que hoy:
163
+
164
+ ```js
165
+ DocNro: docNro,
166
+ // parche: 5 = Consumidor Final solo si DocTipo es 99; con otro DocTipo falta la condición frente al IVA del cliente
167
+ ...(Number(docTipo) === 99 ? { CondicionIVAReceptorId: 5 } : {}),
168
+ ```
169
+
170
+ ```php
171
+ );
172
+ // parche: 5 = Consumidor Final solo si DocTipo es 99; con otro DocTipo falta la condición frente al IVA del cliente
173
+ if ((int) $data['DocTipo'] === 99) {
174
+ $data['CondicionIVAReceptorId'] = 5;
175
+ }
176
+ ```
177
+
178
+ - El PR sale **borrador**, con "(parcial: consumidor final)" en el título. En "Lo que este PR NO resuelve" dice qué falta: los comprobantes con otro `DocTipo` (CUIT, DNI y otros) siguen sin el campo y desde el 1/12/2026 ARCA los va a rechazar. Ahí también están los códigos válidos para la clase del comprobante.
179
+ - Con otro `DocTipo` la clave ni existe (no queda en `undefined` ni en `null`): el pedido no depende de cómo lo serialice la librería y no aparece un rechazo que hoy no existe.
180
+ - `Number(...)` y `(int)` hacen que `'99'` como texto también cuente.
181
+ - En JS solo se aplica si `DocTipo` se puede releer sin efectos (`docTipo`, `cliente.docTipo`, `this.datos['DocTipo']`), no si es una llamada o un `await`. En PHP se lee del array ya armado, así que sirve cualquier expresión, pero el array tiene que estar en una variable (`$data = [...]`): con el array dentro de la llamada no hay dónde poner el `if`.
182
+ - No se aplica a facturas A ni a otras clases conocidas que no sean B o C.
183
+ - Si se vuelve a correr después del merge, no cambia nada y sigue avisando lo que falta.
89
184
 
90
185
  ## Límites del MVP
91
186
 
92
- - Solo Node.js (JS/TS, ESM y CommonJS). Siguen PHP (la mayoría de los sistemas de facturación locales), Python y Java.
93
- - ARCA: cubre `@afipsdk/afip.js`. Las integraciones SOAP propias se detectan pero no se reescriben.
187
+ - JS/TS (ESM y CommonJS) y, para ARCA, PHP. Siguen Python y Java.
188
+ - ARCA: cubre `@afipsdk/afip.js` y `afipsdk/afip.php` (con sus forks). Las integraciones SOAP propias (en cualquier lenguaje) se detectan pero no se reescriben.
94
189
  - Mercado Pago: migra la creación de pagos con el SDK v2. Las consultas, capturas y reembolsos de pagos existentes se dejan a propósito en Payments API, como indica la guía oficial.
95
190
  - Se distribuye como CLI (`npx parche-cli`) + GitHub Action (`Pepo08/parche@v0`). La GitHub App (instalación en un click, PRs automáticos al publicarse un pack nuevo) es el próximo paso.
96
191
  - La capa de LLM (redactar packs desde changelogs y resolver casos "falta un dato" leyendo el modelo de datos del cliente) todavía no está incluida; cuando entre, va a pasar por la misma verificación.
package/bin/parche.js CHANGED
@@ -4,20 +4,27 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { run } from '../src/engine.js';
6
6
  import { loadPacks } from '../src/packs.js';
7
- import { scanReport, prBody, counts, LABEL, daysUntil } from '../src/report.js';
7
+ import { scanReport, prBody, counts, LABEL, daysUntil, partialCovers } from '../src/report.js';
8
8
  import { verify } from '../src/verify.js';
9
9
  import * as git from '../src/git.js';
10
10
  import { parseGithubRemote, openPullRequest } from '../src/github.js';
11
11
  import { record } from '../src/ledger.js';
12
+ import { apiLabel } from '../src/inventory/index.js';
13
+ import { isTestFile } from '../src/paths.js';
12
14
 
13
15
  const HELP = `parche — Dependabot para APIs
14
16
 
15
17
  Uso:
16
18
  parche packs Lista los cambios de API que Parche conoce
17
- parche scan <dir> [--json] Inventario de APIs + qué te afecta
19
+ parche scan <dir> [--json] [--upgrades]
20
+ Inventario de APIs + qué te afecta; con --upgrades,
21
+ qué cambia en las versiones nuevas de tus SDKs de npm
22
+ parche diff <paquete> --from <v> --to <v> [--json] [--all]
23
+ Qué cambia en la API de un paquete de npm entre dos versiones
18
24
  parche fix <dir> [--pack <id>] Aplica los arreglos en el código
19
- parche pr <dir> [--pack <id>] [--dry-run] [--verify]
20
- Rama + commit + PR por cada cambio
25
+ parche pr <dir> [--pack <id>] [--dry-run] [--verify] [--push-remote <remote>]
26
+ Rama + commit + PR por cada cambio. Con --push-remote,
27
+ pushea a ese remote (tu fork) y abre el PR contra origin
21
28
  parche feedback <dir> --pack <id> --outcome merged|edited|rejected [--note "..."]
22
29
  `;
23
30
 
@@ -49,13 +56,75 @@ function ensureLocalDir(root) {
49
56
  }
50
57
  }
51
58
 
59
+ // Inventario de cualquier API (nivel 1): todo lo que el repo usa, tenga pack o no.
60
+ function printInventory(inventory) {
61
+ const KIND = { sdk: 'SDK ', http: 'HTTP', 'ai-model': 'IA ' };
62
+ console.log(`APIs del repo (${inventory.apis.length}):`);
63
+ if (!inventory.apis.length) console.log(' (no se encontraron APIs de terceros)');
64
+ for (const api of inventory.apis) {
65
+ const ops = api.operations.map((o) => (o.count > 1 ? `${o.op} ×${o.count}` : o.op));
66
+ const shown = ops.length > 4 ? [...ops.slice(0, 4), `+${ops.length - 4} más`] : ops;
67
+ const packs = api.packs.length ? ' · con pack' : '';
68
+ console.log(` ${KIND[api.kind]} ${apiLabel(api).padEnd(38)} ${api.confidence.padEnd(5)} ${shown.join(', ') || '(declarada, sin usos encontrados)'}${packs}`);
69
+ }
70
+ if (inventory.unresolved.length) {
71
+ console.log(` + ${inventory.unresolved.length} llamada(s) HTTP con URL dinámica (no se sabe a qué host van; detalle en el reporte)`);
72
+ }
73
+ }
74
+
75
+ // Versiones nuevas de los SDKs de npm (scan --upgrades): qué operaciones del repo cambian o desaparecen.
76
+ function printUpgrades(results) {
77
+ console.log('\nVersiones nuevas de tus SDKs de npm:');
78
+ if (!results.length) console.log(' (no hay SDKs de npm con operaciones para comparar)');
79
+ for (const r of results) {
80
+ const head = ` ${r.package.padEnd(22)} ${r.installed ? `${r.installed} → ${r.latest}`.padEnd(20) : ''.padEnd(20)}`;
81
+ if (r.status === 'afectado') {
82
+ console.log(`${head} ⚠ ${r.affected.length} de tus operaciones cambian o desaparecen`);
83
+ for (const a of r.affected) {
84
+ const where = a.locations.map((l) => `${l.file}:${l.line}`).join(', ');
85
+ console.log(` ${a.op} (${where})`);
86
+ for (const c of a.changes) console.log(` ${c.message}`);
87
+ }
88
+ } else if (r.status === 'sin-impacto') {
89
+ console.log(`${head} ✓ lo que usás no cambia (${r.totalChanges} cambios en el SDK)${r.unverified.length ? ` · ${r.unverified.length} operación(es) sin verificar` : ''}`);
90
+ } else if (r.status === 'al-dia') {
91
+ console.log(`${head} ✓ al día`);
92
+ } else if (r.status === 'sin-verificar') {
93
+ console.log(`${head} ? tus operaciones no aparecen en los tipos de la versión instalada: no se puede verificar`);
94
+ } else {
95
+ console.log(`${head} — ${r.error || 'no se pudo determinar la versión instalada'}`);
96
+ }
97
+ }
98
+ }
99
+
100
+ // Salida de `parche diff`.
101
+ function printDiff(d, { all = false } = {}) {
102
+ const groups = [
103
+ ['Desaparecen', d.changes.filter((c) => c.type === 'removed')],
104
+ ['Cambian los parámetros', d.changes.filter((c) => ['new-required', 'param-removed', 'type-changed'].includes(c.type))],
105
+ ['Pasan a deprecados', d.changes.filter((c) => c.type === 'deprecated')],
106
+ ];
107
+ console.log(`${d.pkg} ${d.from} → ${d.to} (tipos: ${d.typesFrom} → ${d.typesTo})`);
108
+ if (!d.changes.length) console.log(' No hay cambios incompatibles en la superficie pública.');
109
+ for (const [title, list] of groups) {
110
+ if (!list.length) continue;
111
+ console.log(`\n${title} (${list.length}):`);
112
+ const shown = all ? list : list.slice(0, 25);
113
+ for (const c of shown) console.log(` - ${c.message}`);
114
+ if (shown.length < list.length) console.log(` … y ${list.length - shown.length} más (--all para verlos todos)`);
115
+ }
116
+ }
117
+
52
118
  function printFindings(result) {
53
119
  for (const pack of result.packs) {
54
120
  const fs2 = result.findings.filter((f) => f.pack === pack.id);
55
121
  if (!fs2.length) continue;
56
122
  const d = pack.deadline ? ` · vence ${pack.deadline} (${daysUntil(pack.deadline)} días)` : '';
57
123
  console.log(`\n■ ${pack.title}\n ${pack.provider} · severidad ${pack.severity}${d}`);
58
- for (const f of fs2) console.log(` ${LABEL[f.status].padEnd(16)} ${f.file}${f.line ? ':' + f.line : ''} ${f.message}`);
124
+ for (const f of fs2) {
125
+ console.log(` ${LABEL[f.status].padEnd(16)} ${f.file}${f.line ? ':' + f.line : ''} ${f.message}`);
126
+ if (f.partial) console.log(` ${''.padEnd(16)} No cubre: ${f.partial.message}`);
127
+ }
59
128
  }
60
129
  }
61
130
 
@@ -71,24 +140,44 @@ async function main() {
71
140
  return 0;
72
141
  }
73
142
 
143
+ if (cmd === 'diff') {
144
+ const pkg = dirArg;
145
+ if (!pkg || !args.from || !args.to) throw new Error('Uso: parche diff <paquete> --from <versión> --to <versión>');
146
+ // TypeScript se carga recién acá: el scan común no lo necesita.
147
+ const { createRegistry } = await import('../src/sdk-diff/registry.js');
148
+ const { diffVersions } = await import('../src/sdk-diff/upgrades.js');
149
+ const d = await diffVersions(createRegistry(), pkg, String(args.from), String(args.to));
150
+ if (d.error) throw new Error(d.error);
151
+ if (args.json) { console.log(JSON.stringify({ pkg, from: d.from, to: d.to, changes: d.changes }, null, 2)); return 0; }
152
+ printDiff(d, { all: !!args.all });
153
+ return 0;
154
+ }
155
+
74
156
  const root = path.resolve(dirArg || '.');
75
157
  if (!fs.existsSync(root)) throw new Error(`No existe ${root}`);
76
158
  const packIds = args.pack ? [args.pack] : null;
77
159
 
78
160
  if (cmd === 'scan') {
79
161
  const result = run(root, { write: false, packIds });
162
+ let upgrades = null;
163
+ if (args.upgrades) {
164
+ if (!args.json) console.error('Buscando versiones nuevas de tus SDKs en npm...');
165
+ const { createRegistry } = await import('../src/sdk-diff/registry.js');
166
+ const { checkUpgrades } = await import('../src/sdk-diff/upgrades.js');
167
+ upgrades = await checkUpgrades(result.inventory, root, createRegistry());
168
+ result.inventory.upgrades = upgrades;
169
+ }
80
170
  ensureLocalDir(root);
81
171
  fs.writeFileSync(path.join(root, '.parche', 'inventory.json'), JSON.stringify(result.inventory, null, 2));
82
- fs.writeFileSync(path.join(root, '.parche', 'report.md'), scanReport({ ...result, changed: [] }));
172
+ fs.writeFileSync(path.join(root, '.parche', 'report.md'), scanReport({ ...result, changed: [], upgrades }));
83
173
  record(root, { type: 'scan', findings: counts(result.findings) });
84
174
  if (args.json) { console.log(JSON.stringify({ inventory: result.inventory, findings: result.findings }, null, 2)); return 0; }
85
175
  console.log(`Parche · scan de ${path.basename(root)}\n`);
86
- console.log('APIs encontradas:');
87
- for (const [prov, p] of Object.entries(result.inventory.providers)) {
88
- console.log(` ${prov.padEnd(12)} ${String(p.callSites).padStart(2)} usos en ${p.files.length} archivo(s) [${Object.keys(p.ops).join(', ')}]`);
89
- }
176
+ printInventory(result.inventory);
177
+ if (upgrades) printUpgrades(upgrades);
90
178
  printFindings(result);
91
- console.log(`\n${summary(result.findings)}`);
179
+ const s = summary(result.findings);
180
+ console.log(s ? `\n${s}` : '\nNinguno de los packs verificados tiene cambios que te afecten hoy.');
92
181
  console.log(`Reporte: ${path.join(root, '.parche', 'report.md')}`);
93
182
  return 0;
94
183
  }
@@ -107,49 +196,68 @@ async function main() {
107
196
  if (cmd === 'pr') {
108
197
  if (!git.isRepo(root)) throw new Error('`parche pr` necesita un repositorio git.');
109
198
  if (!git.isClean(root)) throw new Error('Hay cambios sin commitear. Commiteá o guardá (stash) antes de correr `parche pr`.');
199
+ // Quién firma el commit se resuelve antes de escribir nada: si falla al commitear, el repo quedaría sucio.
200
+ git.commitIdentity(root);
110
201
  ensureLocalDir(root);
111
202
  const base = git.currentBranch(root);
112
203
  const packs = loadPacks().filter((p) => p.codemod && (!packIds || packIds.includes(p.id)));
204
+ // A dónde se pushea: origin, o el fork de quien abre el PR (--push-remote fork). El PR se abre contra origin.
205
+ const pushRemote = typeof args['push-remote'] === 'string' ? args['push-remote'] : 'origin';
113
206
  for (const pack of packs) {
114
207
  const preview = run(root, { write: false, packIds: [pack.id] });
115
208
  if (!preview.findings.length) continue;
116
209
  const branch = `parche/${pack.id}`;
210
+ // Un arreglo parcial lo dice en el título: "(parcial: consumidor final)".
211
+ const covers = partialCovers(preview.findings);
212
+ const title = `Parche: ${pack.title}${covers.length ? ` (parcial: ${covers.join(', ')})` : ''}`;
117
213
  git.checkoutNewBranch(root, branch, base);
118
- const result = run(root, { write: true, packIds: [pack.id] });
119
- const changedFiles = result.changed.map((c) => c.file);
120
- const verification = args.verify || args['dry-run'] ? verify(root, changedFiles, { runTests: !!args.verify }) : null;
121
- if (verification && !verification.ok) {
214
+ let keepBranch = false;
215
+ try {
216
+ const result = run(root, { write: true, packIds: [pack.id] });
217
+ const changedFiles = result.changed.map((c) => c.file);
218
+ const c = counts(result.findings);
219
+ const bodyPath = path.join(root, '.parche', `pr-${pack.id}.md`);
220
+ console.log(`\n■ ${title}`);
221
+ // La sintaxis se chequea siempre (no ejecuta nada del repo); los tests, solo con --verify.
222
+ const verification = changedFiles.length ? verify(root, changedFiles, { runTests: !!args.verify }) : null;
223
+ if (verification && !verification.ok) {
224
+ // Nada de lo que escribió Parche queda en el repo: se descarta y se borra la rama.
225
+ git.discardChanges(root);
226
+ throw new Error(`La verificación falló para ${pack.id}; no se abre el PR y el repo queda como estaba.\n${JSON.stringify(verification, null, 2)}`);
227
+ }
228
+ fs.writeFileSync(bodyPath, prBody({ pack, findings: result.findings, changed: result.changed, verification }));
229
+ if (!changedFiles.length || changedFiles.every(isTestFile)) {
230
+ const why = changedFiles.length ? 'solo cambiaría tests o mocks: no se abre un PR' : 'sin cambios de código';
231
+ git.discardChanges(root);
232
+ console.log(` ${summary(result.findings)}`);
233
+ console.log(` ${why[0].toUpperCase()}${why.slice(1)}; queda como issue o reporte: ${bodyPath}`);
234
+ record(root, { type: 'report-only', pack: pack.id, findings: c, reason: why });
235
+ continue;
236
+ }
237
+ const draft = !!(c['needs-review'] || c['needs-input'] || c.blocked);
238
+ const sha = git.commitAll(root, `${title}\n\n${pack.summary_es}\nPack ${pack.id} v${pack.version}.`);
239
+ keepBranch = true;
240
+ console.log(` Rama ${branch} · commit ${sha} · ${changedFiles.length} archivo(s)${verification ? ` · verificación ${verification.ok ? 'OK' : 'FALLÓ'}` : ''}`);
241
+ console.log(` ${summary(result.findings)}`);
242
+ console.log(` Cuerpo del PR: ${bodyPath}`);
243
+ const target = parseGithubRemote(git.originUrl(root));
244
+ const pushTo = parseGithubRemote(git.remoteUrl(root, pushRemote));
245
+ let url = null;
246
+ if (!args['dry-run'] && process.env.GITHUB_TOKEN && target && pushTo) {
247
+ git.push(root, branch, pushRemote);
248
+ const head = pushTo.owner !== target.owner ? `${pushTo.owner}:${branch}` : branch;
249
+ url = await openPullRequest({ token: process.env.GITHUB_TOKEN, ...target, head, base, title, body: fs.readFileSync(bodyPath, 'utf8'), draft });
250
+ console.log(` PR abierto${draft ? ' (borrador)' : ''}: ${url}`);
251
+ } else {
252
+ console.log(` Modo prueba: no se abrió el PR${draft ? ' (iría como borrador por tener ítems para revisar)' : ''}.`);
253
+ }
254
+ record(root, { type: 'pr', pack: pack.id, packVersion: pack.version, branch, sha, url, draft, files: changedFiles, findings: c,
255
+ partial: result.findings.filter((f) => f.partial).length });
256
+ } finally {
257
+ // Pase lo que pase, se vuelve a la rama base; la rama de Parche solo queda si tiene un commit.
122
258
  git.checkout(root, base);
123
- throw new Error(`La verificación falló para ${pack.id}; no se abre el PR.\n${JSON.stringify(verification, null, 2)}`);
124
- }
125
- const body = prBody({ pack, findings: result.findings, changed: result.changed, verification });
126
- const bodyPath = path.join(root, '.parche', `pr-${pack.id}.md`);
127
- fs.writeFileSync(bodyPath, body);
128
- const c = counts(result.findings);
129
- const draft = !!(c['needs-review'] || c['needs-input'] || c.blocked);
130
- const title = `Parche: ${pack.title}`;
131
- console.log(`\n■ ${title}`);
132
- if (!changedFiles.length) {
133
- console.log(' Sin cambios de código: queda como issue/reporte (ver cuerpo del PR).');
134
- git.checkout(root, base);
135
- record(root, { type: 'report-only', pack: pack.id, findings: c });
136
- continue;
137
- }
138
- const sha = git.commitAll(root, `${title}\n\n${pack.summary_es}\nPack ${pack.id} v${pack.version}.`);
139
- console.log(` Rama ${branch} · commit ${sha} · ${changedFiles.length} archivo(s)${verification ? ` · verificación ${verification.ok ? 'OK' : 'FALLÓ'}` : ''}`);
140
- console.log(` ${summary(result.findings)}`);
141
- console.log(` Cuerpo del PR: ${bodyPath}`);
142
- const remote = parseGithubRemote(git.originUrl(root));
143
- let url = null;
144
- if (!args['dry-run'] && process.env.GITHUB_TOKEN && remote) {
145
- git.push(root, branch);
146
- url = await openPullRequest({ token: process.env.GITHUB_TOKEN, ...remote, head: branch, base, title, body, draft });
147
- console.log(` PR abierto${draft ? ' (borrador)' : ''}: ${url}`);
148
- } else {
149
- console.log(` Modo prueba: no se abrió el PR${draft ? ' (iría como borrador por tener ítems para revisar)' : ''}.`);
259
+ if (!keepBranch) git.deleteBranch(root, branch);
150
260
  }
151
- record(root, { type: 'pr', pack: pack.id, packVersion: pack.version, branch, sha, url, draft, files: changedFiles, findings: c });
152
- git.checkout(root, base);
153
261
  }
154
262
  return 0;
155
263
  }