You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4ebcd55
Browse filesBrowse the repository at this point in the historyBrowse files
Versión 0.2.0: identificadores estables (schema 1.1)
- statement_id, transactions[].id e installment_plans[].id: HMAC-SHA256 con la
llave de seudónimos sobre datos crudos estables; el número de cuenta solo
entra al HMAC
- Ordinal de ocurrencia para movimientos idénticos el mismo día
- Escáner residual: exención exacta por campo y formato de los ids
- Aviso en stderr al procesar varios archivos o NDJSON sin llave
- Documentación, skill y guía para agentes explican cómo reimportar sin duplicar
- Pruebas: estabilidad, privacidad, duplicados, planes entre meses y colisiones
Si `mxbank` no está en el PATH: `uv tool install git+https://github.com/proteo5/mx-bank-statement-reader` (pide permiso antes de instalar), o desde un clon del repo: `uv run --project <ruta-del-repo> mxbank …`.
29
29
30
30
-**PDF con contraseña** (código 11): pide al usuario que la ponga en una variable de entorno y usa `--password-env NOMBRE`. Nunca la pases como argumento ni la escribas en la conversación.
31
-
-**Varios meses**: usa la misma llave de seudónimos en todas las ejecuciones (`--pseudonym-key-env NOMBRE`), para que la misma cuenta o contraparte tenga el mismo alias.
31
+
-**Varios meses o importación**: usa **siempre la misma llave**(`--pseudonym-key-env NOMBRE`) para que la misma cuenta, contraparte, movimiento y plan a meses tengan el mismo alias e id en todas las ejecuciones. Sin llave, la CLI avisa en stderr y los ids no sirven fuera de esa ejecución.
32
32
-`--include-concepts` agrega los conceptos de transferencias (redactados); úsalo solo si el usuario lo pide.
33
33
34
34
| Código | Significado / qué hacer |
@@ -48,6 +48,11 @@ Si `mxbank` no está en el PATH: `uv tool install git+https://github.com/proteo5
48
48
-`installment_plans[]`: compras a meses con `pending_balance` y `installment_number`/`installments_total`.
49
49
-`original_currency` / `original_amount` / `exchange_rate`: compras en otra moneda.
50
50
-`merchant`: nombre canónico cuando el comercio es conocido; si no, usa `description`.
-**Para no duplicar** al combinar estados que se enciman o reimportados, agrupa o deduplica por `transactions[].id`, nunca por fecha + monto: dos compras idénticas el mismo día tienen ids distintos y ambas son reales.
53
+
- Sigue una compra a meses entre estados con `installment_plans[].id`.
54
+
- Los ids solo son comparables si `privacy.pseudonym_scope == "key"` y se usó la misma llave; con `"run"` no los guardes ni los compares entre ejecuciones.
55
+
- No intentes deducir nada de un id: no contiene datos.
51
56
52
57
## Cómo calcular bien (lecciones de estados reales)
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,6 +3,18 @@
3
3
Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/); versionado [SemVer](https://semver.org/lang/es/).
4
4
El esquema JSON de salida tiene su propia versión (`schema_version`).
5
5
6
+
## [0.2.0] - 2026-10-10
7
+
8
+
Esquema de salida **1.1** (cambio aditivo; los consumidores de 1.0 siguen funcionando).
9
+
10
+
### Añadido
11
+
12
+
-`statement_id` (raíz), `transactions[].id` e `installment_plans[].id`: identificadores estables calculados con HMAC-SHA256 y la llave de seudónimos, para importar o reimportar estados (incluso estados que se enciman) sin duplicar movimientos ni planes a meses.
13
+
- Con `--pseudonym-key-env` son iguales entre ejecuciones, PDFs y perfiles de privacidad; sin llave solo valen dentro de la ejecución, y la CLI lo advierte en stderr al procesar varios archivos o con `--ndjson`.
14
+
- Dos movimientos idénticos el mismo día se distinguen con un ordinal de ocurrencia dentro del estado.
15
+
- El escáner residual exime solo esos campos y solo con su formato exacto.
16
+
- Documentación de cómo se calculan y cuándo son estables (docs/03 §7, docs/04), y cómo usarlos para no duplicar (docs/AGENT.md, skill `mxbank`).
17
+
6
18
## [0.1.0] - 2026-10-10
7
19
8
20
Primera versión pública. Esquema de salida **1.0**.
@@ -25,4 +37,5 @@ Primera versión pública. Esquema de salida **1.0**.
25
37
- Herramientas de desarrollo: volcado enmascarado de layouts (`tools/layout_dump.py`), métricas sobre PDFs reales sin exponer contenido (`tools/corpus_check.py`) y guardia de git contra PDFs y datos personales (`tools/check_no_pii.py`).
26
38
- PDFs sintéticos con datos ficticios para las pruebas.
@@ -148,6 +149,7 @@ Extracto real generado a partir del PDF **sintético** de las pruebas, con datos
148
149
149
150
- Montos como **texto decimal** (`"1200.00"`), para no perder precisión. `amount` negativo = salida de dinero.
150
151
- Fechas ISO-8601. `null` significa que el dato no viene en el documento; nunca se inventa.
152
+
-`statement_id`, `transactions[].id` e `installment_plans[].id` son **identificadores estables** (HMAC con tu llave): con la misma `--pseudonym-key-env` puedes reimportar estados, incluso los que se enciman, sin duplicar movimientos. Ver [docs/03 §7](docs/03-esquema-salida.md#7-identificadores-estables-schema-11).
151
153
- El esquema completo se obtiene con `mxbank schema`; la semántica de cada campo está en [docs/03-esquema-salida.md](docs/03-esquema-salida.md).
Copy file name to clipboardExpand all lines: docs/01-analisis.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -159,4 +159,4 @@ Conclusión de diseño: **procesamiento 100 % local, sin telemetría, sin red**,
159
159
|**F2**| Tarjetas de crédito de los mismos bancos, lote/directorio, consolidado multi‑mes, categorización básica de movimientos. |
160
160
|**F3**| Más bancos, OCR, servidor MCP que expone las mismas herramientas, empaquetado como binario único. |
161
161
162
-
> **Estado en v0.1.0**: F0 y F1 completas salvo el modo `generic` (un banco no soportado se rechaza con código 20/21 en lugar de extraerse en modo de respaldo). De F2 ya están las tarjetas de crédito BBVA; consolidado y categorización siguen pendientes. Ver [02 §11](02-especificacion-tecnica.md#11-estado-de-implementación-v010).
162
+
> **Estado en v0.1.0**: F0 y F1 completas salvo el modo `generic` (un banco no soportado se rechaza con código 20/21 en lugar de extraerse en modo de respaldo). De F2 ya están las tarjetas de crédito BBVA; consolidado y categorización siguen pendientes. Ver [02 §11](02-especificacion-tecnica.md#11-estado-de-implementación-v020).
Copy file name to clipboardExpand all lines: docs/02-especificacion-tecnica.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -238,7 +238,7 @@ Detalle completo en [04-privacidad-y-redaccion.md](04-privacidad-y-redaccion.md)
238
238
-**Servidor MCP (F3)**: `mxbank mcp` expone `extract_statement`, `detect_statement`, `audit_text`, `get_schema` reutilizando el mismo núcleo. El servidor recibe rutas locales, no contenido.
239
239
- La salida incluye `schema_version` y un bloque `privacy` para que la IA sepa qué fue omitido y no "invente" datos.
240
240
241
-
## 11. Estado de implementación (v0.1.0)
241
+
## 11. Estado de implementación (v0.2.0)
242
242
243
243
| Componente | Estado |
244
244
|---|---|
@@ -247,6 +247,7 @@ Detalle completo en [04-privacidad-y-redaccion.md](04-privacidad-y-redaccion.md)
247
247
| BanCoppel cuenta de débito (layout 2025.1) | ✅ 1/1 estado real conciliado, totales y saldos por renglón correctos |
248
248
| BBVA tarjeta de crédito (layout 2025.1) | ✅ 4/4 estados reales conciliados con el saldo de cargos regulares; bloque `credit`, planes a meses, tipo de cambio, parcialidades |
Copy file name to clipboardExpand all lines: docs/03-esquema-salida.md
+29-2Lines changed: 29 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,9 +1,11 @@
1
-
# 03 · Esquema de salida JSON (v1, borrador)
1
+
# 03 · Esquema de salida JSON (schema 1.1)
2
2
3
3
El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/statement.py`) y se obtiene con `mxbank schema`; **ese es la referencia autoritativa**. Este documento describe la forma y la semántica.
4
4
5
5
> Schema 1.0 (v0.1.0), además de lo que muestra el ejemplo: `transactions[].balance_operation`, `value_date`, `counterparty_account_ref`, `concept` (solo con `--include-concepts`); `summary.minimum_average_balance`, `fees_vat`; bloque `rates`; `validation.running_balance_checked`, `bank_totals_match`; `privacy.include_concepts`. También `credit` e `installment_plans` (con `start_date` en lugar de `date`) y en movimientos `original_currency`, `original_amount`, `exchange_rate`, `installment_number`, `installments_total`, `card_type`. Pendiente: `fees[]`.
6
6
7
+
> **Schema 1.1** (cambio aditivo): `statement_id` en la raíz, `transactions[].id` e `installment_plans[].id`. Ver §7.
8
+
7
9
## 1. Principios
8
10
9
11
-**Allowlist**: solo existen en la salida los campos definidos aquí. Cualquier campo nuevo requiere revisión de privacidad.
@@ -16,8 +18,9 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
16
18
17
19
```json
18
20
{
19
-
"schema_version": "1.0",
21
+
"schema_version": "1.1",
20
22
"ok": true,
23
+
"statement_id": "ST_5c0e9a41d27b8f36",
21
24
"source": {
22
25
"file_name": "estado_2026_09.pdf",
23
26
"file_sha256": "9f2c…",
@@ -58,6 +61,7 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
58
61
},
59
62
"transactions": [
60
63
{
64
+
"id": "TX_8d41f0b27c9e3a65",
61
65
"index": 0,
62
66
"date": "2026-08-15",
63
67
"posting_date": "2026-08-15",
@@ -73,6 +77,7 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
73
77
"page": 2
74
78
},
75
79
{
80
+
"id": "TX_f20c6b9e41a7d385",
76
81
"index": 1,
77
82
"date": "2026-08-16",
78
83
"posting_date": "2026-08-16",
@@ -136,6 +141,7 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
136
141
},
137
142
"installment_plans": [
138
143
{
144
+
"id": "PLAN_3e7a90c5b1f4d268",
139
145
"description": "LIVERPOOL MSI",
140
146
"original_amount": "12000.00",
141
147
"monthly_amount": "1000.00",
@@ -152,6 +158,7 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
152
158
| Campo | Significado | Nota de privacidad |
153
159
|---|---|---|
154
160
|`account_ref`| Token estable del número de cuenta (HMAC) | Permite a la IA agrupar meses de la misma cuenta sin conocer el número. |
161
+
|`statement_id`, `transactions[].id`, `installment_plans[].id`| Identificadores estables (HMAC), §7 | No contienen ni dejan derivar la cuenta, montos ni descripciones sin la llave. |
155
162
|`account_last4`| Últimos 4 dígitos | Omitido en perfil `strict`. |
156
163
|`counterparty_ref`| Token de la contraparte (`PERSONA_xxxx` / `EMPRESA_xxxx`) | Misma contraparte ⇒ mismo token dentro del ámbito (`run` o `key`). |
157
164
|`merchant`| Comercio normalizado | Se conserva: es útil y no identifica al titular. |
@@ -167,3 +174,23 @@ El JSON Schema formal se genera desde los modelos Pydantic (`src/mxbank/models/s
167
174
168
175
-`schema_version` sigue SemVer: cambios aditivos ⇒ menor; cambios que rompen ⇒ mayor.
169
176
-`mxbank schema --version N` imprime cualquier versión soportada.
177
+
178
+
## 7. Identificadores estables (schema 1.1)
179
+
180
+
Sirven para que un importador (p. ej. una base de datos personal de solo agregado) pueda **reimportar** un estado o importar **estados que se enciman** sin duplicar nada: basta con insertar solo los `id` que aún no tiene.
181
+
182
+
| Campo | Formato | Entradas del HMAC | Igual cuando… |
183
+
|---|---|---|---|
184
+
|`statement_id` (raíz) |`ST_<16 hex>`| banco, producto, número de cuenta, inicio y fin del periodo | es el mismo estado de cuenta (mismo periodo de la misma cuenta), aunque sea otro PDF o se descargue de nuevo |
185
+
|`transactions[].id`|`TX_<16 hex>`| banco, producto, número de cuenta, `date`, `amount`, descripción **cruda** del banco normalizada (`text.norm`), ordinal de ocurrencia | es el mismo movimiento, aunque aparezca en dos estados que se enciman o en otra posición/página |
186
+
|`installment_plans[].id`|`PLAN_<16 hex>`| banco, producto, número de cuenta, descripción cruda normalizada, `start_date`, `original_amount`, ordinal de ocurrencia | es la misma compra a meses, en todos los estados donde aparezca (cambian la parcialidad y el saldo, el id no) |
187
+
188
+
Reglas:
189
+
190
+
-**Estables solo con llave**: con `--pseudonym-key-env` (`privacy.pseudonym_scope: "key"`) y siempre la misma llave, los ids son iguales entre ejecuciones, PDFs y perfiles. **Sin llave** (`"run"`) la llave es aleatoria y los ids solo valen dentro de esa ejecución; la CLI lo advierte en stderr al procesar varios archivos o usar `--ndjson`.
191
+
-**No dependen** de `--privacy`, `--include-concepts`, `index`, `page`, el orden de extracción, `type`, `merchant` ni del texto redactado: se calculan con los datos crudos antes de redactar.
192
+
-**Duplicados legítimos** (dos compras idénticas el mismo día): el ordinal de ocurrencia (0, 1, …) entre movimientos con la misma fecha, monto y descripción **dentro del mismo estado** los distingue. Si un estado que se encima trae solo una de dos compras idénticas, se le asigna el ordinal 0.
193
+
-**Sin periodo** (el parser no lo encontró), `statement_id` se calcula con el hash del PDF: solo es estable para ese archivo exacto.
194
+
-**Sin número de cuenta**, los ids no distinguen entre cuentas distintas; la salida lo avisa en `validation.warnings`.
195
+
- Formato: 16 hex (64 bits). Si dos ids chocaran dentro de una ejecución con datos distintos, el hash se alarga de 2 en 2.
196
+
- El HMAC usa un separador de dominio versionado (`mxbank-id/v1`): si algún día cambian las entradas, cambiará el prefijo de dominio y se documentará como cambio de esquema.
`id = PREFIJO + "_" + hex(HMAC-SHA256(llave, "mxbank-id/v1" | PREFIJO | banco | producto | número de cuenta | …))[:16]` (detalle de las entradas en [03 §7](03-esquema-salida.md#7-identificadores-estables-schema-11)).
71
+
72
+
**Por qué HMAC y no un hash simple.** Las entradas son datos que un atacante puede conocer o adivinar: el número de cuenta tiene 10–11 dígitos, la fecha está acotada al periodo y el monto a unos cuantos miles de valores. Con un hash sin llave (`sha256(cuenta | fecha | monto | …)`) bastaría probar combinaciones hasta reproducir el id para **confirmar o recuperar el número de cuenta**, o para saber si alguien hizo un pago concreto. Con HMAC, sin la llave cada intento es indistinguible del azar, así que el id no deja derivar nada.
73
+
74
+
- La llave es la misma de los seudónimos (`--pseudonym-key-env`). Guárdala como cualquier secreto: quien la tenga puede verificar suposiciones sobre los ids.
75
+
- El número de cuenta entra **solo al HMAC**; nunca aparece en la salida.
76
+
- El escáner residual exime estos campos **solo** si se llaman `id` o `statement_id`**y** su valor coincide exactamente con `^(TX|ST|PLAN)_[0-9a-f]{16,64}$`; cualquier otro contenido en esos campos, o ese formato en otro campo, se escanea normalmente.
-`credit` (solo tarjetas): límite, disponible, fecha de corte, fecha límite de pago, pago mínimo, pago para no generar intereses, CAT y tasa.
46
46
-`installment_plans[]` (solo tarjetas): compras a meses con su saldo pendiente.
47
47
-`privacy`: qué se eliminó o seudonimizó (conteos, nunca valores).
48
+
-`statement_id`, `transactions[].id`, `installment_plans[].id`: identificadores estables (schema 1.1). Ver abajo.
49
+
50
+
## Importar sin duplicar
51
+
52
+
Para guardar movimientos en una base propia (p. ej. SQLite de solo agregado) y poder reimportar estados o importar estados que se enciman:
53
+
54
+
1. Usa **siempre la misma llave**: `--pseudonym-key-env MXBANK_KEY`. Confirma que `privacy.pseudonym_scope` sea `"key"`; si es `"run"`, los ids cambian en cada ejecución y **no deben guardarse**.
55
+
2. Usa `transactions[].id` como llave primaria del movimiento e inserta solo los que no existan (`INSERT OR IGNORE`). El mismo movimiento conserva su id aunque venga en otro PDF, en otra página o con otro `--privacy`.
56
+
3. Usa `statement_id` para saber si un estado ya se importó, e `installment_plans[].id` para seguir una compra a meses de un mes a otro: cambian la parcialidad y el saldo, no el id.
57
+
4. Dos movimientos idénticos el mismo día tienen ids distintos: no los fusiones.
58
+
5. Si cambias o pierdes la llave, los ids nuevos no coincidirán con los guardados. Trátala como un secreto y respáldala.
<h1>Tus estados de cuenta, <spanclass="hl">listos para la IA</span>. Sin tus datos personales.</h1>
197
197
<pclass="lead">mxbank lee estados de cuenta de bancos mexicanos en PDF <b>en tu computadora</b>, extrae saldos y movimientos, elimina todo lo que te identifica y entrega un JSON que ya puedes compartir con cualquier asistente.</p>
0 commit comments