parche-cli 0.0.0-stage → 0.5.1

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,97 @@
1
+ # Cambios
2
+
3
+ Todas las versiones de `parche-cli` (npm) y de la Action `Pepo08/parche@v0`.
4
+
5
+ ## 0.5.1 — 2026-10-17
6
+
7
+ Lo que apareció en el primer PR real (lunamarie11/obraya#1).
8
+
9
+ ### Arreglado
10
+ - **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.
11
+ - **`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).
12
+ - El texto para un issue (sin cambios de código) ya no lleva la sección de verificación.
13
+
14
+ ## 0.5.0 — 2026-10-16
15
+
16
+ Lo que encontró el frente de tracción al correr Parche sobre 175 repos públicos reales.
17
+
18
+ ### Nuevo
19
+ - **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.
20
+ - Detección de **`.wsdl` de WSFEv1 sin el campo** en el repo.
21
+ - **Python, C#, Java, Go y VB.NET:** detección de integraciones SOAP propias y de clientes SOAP generados sin el campo.
22
+ - `parche pr --push-remote <remote>`: abre PRs **desde un fork**.
23
+ - **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.
24
+
25
+ ### Arreglado
26
+ - `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.
27
+ - No se abre un PR cuyo único cambio sea un test o un mock.
28
+ - 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).
29
+ - 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".
30
+ - 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.
31
+ - Mucho más rápido en repos grandes: los codemods de ARCA no parsean archivos que no mencionan ARCA.
32
+ - El texto para un issue (sin cambios de código) ya no habla de "este PR".
33
+ - `detect.packages` de ARCA: fuera `afip.js` (no existe en npm); suma `@arcasdk/core`, `afip.ts`, `ts-afip-ws` y `facturajs`.
34
+
35
+ ### Cambiado
36
+ - `docs/arquitectura.md` sale del repo: era documentación interna del equipo. El formato del inventario sigue en `docs/inventario.md`.
37
+
38
+ ## 0.4.1 — 2026-10-16
39
+
40
+ Arreglos encontrados al correr Parche sobre los primeros repos públicos reales con ARCA y Mercado Pago.
41
+
42
+ ### Arreglado
43
+ - **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.
44
+ - **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.
45
+ - **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).
46
+ - **Inventario:** se siguen los clientes guardados en clases (`private x = new X()`, `this.x = new X()`).
47
+ - `http://localhost:${PORT}` ya no aparece como un host externo.
48
+ - `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.
49
+
50
+ ### Mejorado
51
+ - **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`)").
52
+
53
+ ## 0.4.0 — 2026-10-16
54
+
55
+ ### Nuevo
56
+ - **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).
57
+ - **`parche diff <paquete> --from <v> --to <v>`**: el mismo análisis para cualquier paquete, sin repo (acepta `latest` y versiones parciales; `--all`, `--json`).
58
+ - 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>`.
59
+ - Ejemplo nuevo: `examples/legacy-ia` (SDK de OpenAI v3: `OpenAIApi` desaparece en las versiones nuevas, como dice la guía oficial de migración).
60
+ - Dependencia nueva: `typescript ~6.0.3` (la última con el compiler API de JavaScript), cargada solo para el diff.
61
+
62
+ ## 0.3.0 — 2026-10-16
63
+
64
+ ### Nuevo
65
+ - **Inventario de cualquier API** (nivel 1): `parche scan` lista todas las APIs de terceros del repo, tengan pack o no.
66
+ - **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.
67
+ - En JS/TS, **qué operaciones** se usan de cada SDK (`stripe.paymentIntents.create`, `client.messages.create`…), siguiendo imports, `require` e instancias.
68
+ - **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.
69
+ - **Modelos de IA** nombrados en el código (OpenAI, Anthropic, Gemini, Mistral, Cohere).
70
+ - `.parche/inventory.json` con `schemaVersion: 1`, documentado en `docs/inventario.md` (los campos anteriores se mantienen).
71
+ - La salida de `scan` y `.parche/report.md` muestran el inventario completo.
72
+ - Ejemplo nuevo: `examples/saas-demo` (Stripe, Twilio, OpenAI, GitHub, Mercado Libre y HTTP directo).
73
+ - `docs/arquitectura.md`: qué hace cada parte del código y por qué.
74
+
75
+ ## 0.2.0 — 2026-10-16
76
+
77
+ ### Nuevo
78
+ - **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.
79
+ - 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.
80
+ - Copia el estilo del cliente: comillas, indentación (espacios o tabs), `=>` alineados, coma final y fin de línea.
81
+ - El motor escanea archivos `.php`, lee `composer.json` y saltea `vendor/`.
82
+ - `--verify` corre `composer test` o `vendor/bin/phpunit` cuando cambian archivos PHP (y `npm test` cuando cambian archivos JS/TS).
83
+ - Ejemplo nuevo: `examples/tienda-php`.
84
+
85
+ ### Arreglado
86
+ - 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.
87
+ - Mercado Pago: el `import { randomUUID }` nuevo queda junto a los otros imports.
88
+ - ARCA (JS): si la última propiedad no tenía coma final, la propiedad nueva tampoco la lleva.
89
+
90
+ ## 0.1.1 — 2026-10-15
91
+
92
+ - Publicado en npm como `parche-cli` (npm no acepta `parche`). El comando instalado sigue siendo `parche`.
93
+
94
+ ## 0.1.0 — 2026-10-15
95
+
96
+ - 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.
97
+ - 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joaquín González
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,166 @@
1
- # Temporary Holding Version
1
+ # Parche — Dependabot para APIs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/Pepo08/parche/actions/workflows/ci.yml/badge.svg)](https://github.com/Pepo08/parche/actions/workflows/ci.yml)
4
+
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
+
7
+ ```
8
+ $ parche scan .
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
16
+
17
+ ■ Condición frente al IVA del receptor obligatoria en WSFEv1
18
+ ARCA (ex AFIP) · severidad critical · vence 2026-12-01 (46 días)
19
+ ✅ automático src/facturacion.js:14 Agregado CondicionIVAReceptorId: 5 (Consumidor Final)…
20
+ 👀 revisar src/facturacion.js:56 Agregado CondicionIVAReceptorId: 5 por inferencia (DNI + comprobante B/C)…
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…
25
+ …
26
+ ```
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
+
30
+ ## Por qué packs y no "un LLM que arregla todo"
31
+
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:
33
+
34
+ - **El pack es dato, no prompt.** Cada cambio de API es un JSON versionado con fuente, fecha límite, reglas de detección, mapeo de campos, casos bloqueados y checklist de revisión (ver `packs/`). Un LLM puede ayudar a *redactar* un pack a partir de un changelog, pero una persona lo verifica antes de publicarlo.
35
+ - **El arreglo es determinístico.** Los codemods trabajan sobre el AST, respetan el formato del cliente (el diff de ARCA es de 2 líneas por factura) y son idempotentes.
36
+ - **Lo que no se puede inferir, no se inventa.** Si falta un dato del negocio (¿este cliente con CUIT es Responsable Inscripto o Monotributo?), el PR lo dice y muestra los valores válidos.
37
+ - **Cada PR mergeado, editado o rechazado vuelve como señal** (`.parche/ledger.jsonl`, `parche feedback`). Eso es lo que hace que la base mejore con el uso.
38
+
39
+ ## Uso
40
+
41
+ Requiere Node 22 o más nuevo. El paquete de npm se llama `parche-cli`; si lo instalás (`npm i -g parche-cli`), el comando es `parche`.
42
+
43
+ ```bash
44
+ npx parche-cli packs # cambios que Parche conoce
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
48
+ npx parche-cli fix . # aplica los arreglos en tu working copy
49
+ npx parche-cli pr . --verify # una rama + commit + PR por cambio
50
+ npx parche-cli feedback . --pack <id> --outcome merged|edited|rejected
51
+ ```
52
+
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.
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
+
76
+ ### En GitHub Actions
77
+
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:
79
+
80
+ ```yaml
81
+ - uses: Pepo08/parche@v0
82
+ with:
83
+ verify: 'true' # corre tu npm test antes de abrir cada PR
84
+ # pack: arca.wsfe.condicion-iva-receptor # opcional: un solo pack
85
+ # dry-run: 'true' # opcional: armar las ramas sin abrir PRs
86
+ ```
87
+
88
+ El workflow necesita `permissions: contents: write` y `pull-requests: write`.
89
+
90
+ ### Desarrollo
91
+
92
+ ```bash
93
+ npm install
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
110
+ ```
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
+
118
+ ## Estados de cada hallazgo
119
+
120
+ | Estado | Qué significa |
121
+ |---|---|
122
+ | ✅ automático | El arreglo es seguro y queda aplicado. |
123
+ | 👀 revisar | Se aplicó, pero hay una decisión que confirmar (va en el PR, que sale como borrador). |
124
+ | ✋ falta un dato | No se puede arreglar sin información del negocio. El PR dice qué agregar y con qué valores. |
125
+ | ⛔ bloqueado | Migrarlo rompería algo (ej. `application_fee` en Orders). No se toca. |
126
+ | 🔎 detectado | Solo aviso: cambiarlo cambia el comportamiento (ej. modelo de IA retirado). |
127
+
128
+ ## Packs incluidos
129
+
130
+ | Pack | Proveedor | Tipo | Fecha límite | Arreglo |
131
+ |---|---|---|---|---|
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 |
133
+ | `mercadopago.payments-to-orders` | Mercado Pago | migración | — | sí (creación de pagos en SDK Node v2) |
134
+ | `ai.model-retirements` | Anthropic, OpenAI | retiro | varias | solo detección |
135
+
136
+ ## Verificación
137
+
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
+ ## Límites del MVP
161
+
162
+ - JS/TS (ESM y CommonJS) y, para ARCA, PHP. Siguen Python y Java.
163
+ - 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.
164
+ - 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.
165
+ - 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.
166
+ - 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 ADDED
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+ // Parche — Dependabot para APIs.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { run } from '../src/engine.js';
6
+ import { loadPacks } from '../src/packs.js';
7
+ import { scanReport, prBody, counts, LABEL, daysUntil } from '../src/report.js';
8
+ import { verify } from '../src/verify.js';
9
+ import * as git from '../src/git.js';
10
+ import { parseGithubRemote, openPullRequest } from '../src/github.js';
11
+ import { record } from '../src/ledger.js';
12
+ import { apiLabel } from '../src/inventory/index.js';
13
+ import { isTestFile } from '../src/paths.js';
14
+
15
+ const HELP = `parche — Dependabot para APIs
16
+
17
+ Uso:
18
+ parche packs Lista los cambios de API que Parche conoce
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
24
+ parche fix <dir> [--pack <id>] Aplica los arreglos en el código
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
28
+ parche feedback <dir> --pack <id> --outcome merged|edited|rejected [--note "..."]
29
+ `;
30
+
31
+ function parseArgs(argv) {
32
+ const args = { _: [] };
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const a = argv[i];
35
+ if (a.startsWith('--')) {
36
+ const key = a.slice(2);
37
+ const next = argv[i + 1];
38
+ if (next !== undefined && !next.startsWith('--')) { args[key] = next; i++; } else args[key] = true;
39
+ } else args._.push(a);
40
+ }
41
+ return args;
42
+ }
43
+
44
+ function summary(findings) {
45
+ const c = counts(findings);
46
+ return ['auto', 'needs-review', 'needs-input', 'blocked', 'detect-only']
47
+ .filter((k) => c[k]).map((k) => `${LABEL[k]}: ${c[k]}`).join(' ');
48
+ }
49
+
50
+ function ensureLocalDir(root) {
51
+ fs.mkdirSync(path.join(root, '.parche'), { recursive: true });
52
+ const exclude = path.join(root, '.git', 'info', 'exclude');
53
+ if (fs.existsSync(path.dirname(exclude))) {
54
+ const cur = fs.existsSync(exclude) ? fs.readFileSync(exclude, 'utf8') : '';
55
+ if (!cur.includes('.parche/')) fs.appendFileSync(exclude, '\n.parche/\n');
56
+ }
57
+ }
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
+
118
+ function printFindings(result) {
119
+ for (const pack of result.packs) {
120
+ const fs2 = result.findings.filter((f) => f.pack === pack.id);
121
+ if (!fs2.length) continue;
122
+ const d = pack.deadline ? ` · vence ${pack.deadline} (${daysUntil(pack.deadline)} días)` : '';
123
+ console.log(`\n■ ${pack.title}\n ${pack.provider} · severidad ${pack.severity}${d}`);
124
+ for (const f of fs2) console.log(` ${LABEL[f.status].padEnd(16)} ${f.file}${f.line ? ':' + f.line : ''} ${f.message}`);
125
+ }
126
+ }
127
+
128
+ async function main() {
129
+ const args = parseArgs(process.argv.slice(2));
130
+ const [cmd, dirArg] = args._;
131
+ if (!cmd || args.help) { console.log(HELP); return 0; }
132
+
133
+ if (cmd === 'packs') {
134
+ for (const p of loadPacks()) {
135
+ console.log(`${p.id.padEnd(36)} ${p.provider.padEnd(16)} ${p.kind.padEnd(10)} ${p.severity.padEnd(9)} ${p.deadline || '—'} ${p.codemod ? 'con arreglo' : 'solo detección'}`);
136
+ }
137
+ return 0;
138
+ }
139
+
140
+ if (cmd === 'diff') {
141
+ const pkg = dirArg;
142
+ if (!pkg || !args.from || !args.to) throw new Error('Uso: parche diff <paquete> --from <versión> --to <versión>');
143
+ // TypeScript se carga recién acá: el scan común no lo necesita.
144
+ const { createRegistry } = await import('../src/sdk-diff/registry.js');
145
+ const { diffVersions } = await import('../src/sdk-diff/upgrades.js');
146
+ const d = await diffVersions(createRegistry(), pkg, String(args.from), String(args.to));
147
+ if (d.error) throw new Error(d.error);
148
+ if (args.json) { console.log(JSON.stringify({ pkg, from: d.from, to: d.to, changes: d.changes }, null, 2)); return 0; }
149
+ printDiff(d, { all: !!args.all });
150
+ return 0;
151
+ }
152
+
153
+ const root = path.resolve(dirArg || '.');
154
+ if (!fs.existsSync(root)) throw new Error(`No existe ${root}`);
155
+ const packIds = args.pack ? [args.pack] : null;
156
+
157
+ if (cmd === 'scan') {
158
+ const result = run(root, { write: false, packIds });
159
+ let upgrades = null;
160
+ if (args.upgrades) {
161
+ if (!args.json) console.error('Buscando versiones nuevas de tus SDKs en npm...');
162
+ const { createRegistry } = await import('../src/sdk-diff/registry.js');
163
+ const { checkUpgrades } = await import('../src/sdk-diff/upgrades.js');
164
+ upgrades = await checkUpgrades(result.inventory, root, createRegistry());
165
+ result.inventory.upgrades = upgrades;
166
+ }
167
+ ensureLocalDir(root);
168
+ fs.writeFileSync(path.join(root, '.parche', 'inventory.json'), JSON.stringify(result.inventory, null, 2));
169
+ fs.writeFileSync(path.join(root, '.parche', 'report.md'), scanReport({ ...result, changed: [], upgrades }));
170
+ record(root, { type: 'scan', findings: counts(result.findings) });
171
+ if (args.json) { console.log(JSON.stringify({ inventory: result.inventory, findings: result.findings }, null, 2)); return 0; }
172
+ console.log(`Parche · scan de ${path.basename(root)}\n`);
173
+ printInventory(result.inventory);
174
+ if (upgrades) printUpgrades(upgrades);
175
+ printFindings(result);
176
+ const s = summary(result.findings);
177
+ console.log(s ? `\n${s}` : '\nNinguno de los packs verificados tiene cambios que te afecten hoy.');
178
+ console.log(`Reporte: ${path.join(root, '.parche', 'report.md')}`);
179
+ return 0;
180
+ }
181
+
182
+ if (cmd === 'fix') {
183
+ const result = run(root, { write: true, packIds });
184
+ ensureLocalDir(root);
185
+ fs.writeFileSync(path.join(root, '.parche', 'report.md'), scanReport(result));
186
+ record(root, { type: 'fix', packs: packIds, changed: result.changed.map((c) => c.file) });
187
+ console.log(result.changed.length ? 'Archivos modificados:' : 'Nada para modificar.');
188
+ for (const c of result.changed) console.log(` ${c.file} (${c.packs.join(', ')})`);
189
+ console.log(`\n${summary(result.findings)}`);
190
+ return 0;
191
+ }
192
+
193
+ if (cmd === 'pr') {
194
+ if (!git.isRepo(root)) throw new Error('`parche pr` necesita un repositorio git.');
195
+ if (!git.isClean(root)) throw new Error('Hay cambios sin commitear. Commiteá o guardá (stash) antes de correr `parche pr`.');
196
+ // Quién firma el commit se resuelve antes de escribir nada: si falla al commitear, el repo quedaría sucio.
197
+ git.commitIdentity(root);
198
+ ensureLocalDir(root);
199
+ const base = git.currentBranch(root);
200
+ const packs = loadPacks().filter((p) => p.codemod && (!packIds || packIds.includes(p.id)));
201
+ // A dónde se pushea: origin, o el fork de quien abre el PR (--push-remote fork). El PR se abre contra origin.
202
+ const pushRemote = typeof args['push-remote'] === 'string' ? args['push-remote'] : 'origin';
203
+ for (const pack of packs) {
204
+ const preview = run(root, { write: false, packIds: [pack.id] });
205
+ if (!preview.findings.length) continue;
206
+ const branch = `parche/${pack.id}`;
207
+ const title = `Parche: ${pack.title}`;
208
+ git.checkoutNewBranch(root, branch, base);
209
+ let keepBranch = false;
210
+ try {
211
+ const result = run(root, { write: true, packIds: [pack.id] });
212
+ const changedFiles = result.changed.map((c) => c.file);
213
+ const c = counts(result.findings);
214
+ const bodyPath = path.join(root, '.parche', `pr-${pack.id}.md`);
215
+ console.log(`\n■ ${title}`);
216
+ // La sintaxis se chequea siempre (no ejecuta nada del repo); los tests, solo con --verify.
217
+ const verification = changedFiles.length ? verify(root, changedFiles, { runTests: !!args.verify }) : null;
218
+ if (verification && !verification.ok) {
219
+ // Nada de lo que escribió Parche queda en el repo: se descarta y se borra la rama.
220
+ git.discardChanges(root);
221
+ 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)}`);
222
+ }
223
+ fs.writeFileSync(bodyPath, prBody({ pack, findings: result.findings, changed: result.changed, verification }));
224
+ if (!changedFiles.length || changedFiles.every(isTestFile)) {
225
+ const why = changedFiles.length ? 'solo cambiaría tests o mocks: no se abre un PR' : 'sin cambios de código';
226
+ git.discardChanges(root);
227
+ console.log(` ${summary(result.findings)}`);
228
+ console.log(` ${why[0].toUpperCase()}${why.slice(1)}; queda como issue o reporte: ${bodyPath}`);
229
+ record(root, { type: 'report-only', pack: pack.id, findings: c, reason: why });
230
+ continue;
231
+ }
232
+ const draft = !!(c['needs-review'] || c['needs-input'] || c.blocked);
233
+ const sha = git.commitAll(root, `${title}\n\n${pack.summary_es}\nPack ${pack.id} v${pack.version}.`);
234
+ keepBranch = true;
235
+ console.log(` Rama ${branch} · commit ${sha} · ${changedFiles.length} archivo(s)${verification ? ` · verificación ${verification.ok ? 'OK' : 'FALLÓ'}` : ''}`);
236
+ console.log(` ${summary(result.findings)}`);
237
+ console.log(` Cuerpo del PR: ${bodyPath}`);
238
+ const target = parseGithubRemote(git.originUrl(root));
239
+ const pushTo = parseGithubRemote(git.remoteUrl(root, pushRemote));
240
+ let url = null;
241
+ if (!args['dry-run'] && process.env.GITHUB_TOKEN && target && pushTo) {
242
+ git.push(root, branch, pushRemote);
243
+ const head = pushTo.owner !== target.owner ? `${pushTo.owner}:${branch}` : branch;
244
+ url = await openPullRequest({ token: process.env.GITHUB_TOKEN, ...target, head, base, title, body: fs.readFileSync(bodyPath, 'utf8'), draft });
245
+ console.log(` PR abierto${draft ? ' (borrador)' : ''}: ${url}`);
246
+ } else {
247
+ console.log(` Modo prueba: no se abrió el PR${draft ? ' (iría como borrador por tener ítems para revisar)' : ''}.`);
248
+ }
249
+ record(root, { type: 'pr', pack: pack.id, packVersion: pack.version, branch, sha, url, draft, files: changedFiles, findings: c });
250
+ } finally {
251
+ // Pase lo que pase, se vuelve a la rama base; la rama de Parche solo queda si tiene un commit.
252
+ git.checkout(root, base);
253
+ if (!keepBranch) git.deleteBranch(root, branch);
254
+ }
255
+ }
256
+ return 0;
257
+ }
258
+
259
+ if (cmd === 'feedback') {
260
+ if (!args.pack || !['merged', 'edited', 'rejected'].includes(args.outcome)) throw new Error('Uso: parche feedback <dir> --pack <id> --outcome merged|edited|rejected');
261
+ record(root, { type: 'feedback', pack: args.pack, outcome: args.outcome, note: args.note || null });
262
+ console.log(`Registrado: ${args.pack} → ${args.outcome}. Gracias, esto mejora el pack para todos.`);
263
+ return 0;
264
+ }
265
+
266
+ console.log(HELP);
267
+ return 1;
268
+ }
269
+
270
+ main().then((code) => process.exit(code)).catch((err) => {
271
+ console.error(`Error: ${err.message}`);
272
+ process.exit(1);
273
+ });