fintecia 0.0.0-stage → 0.1.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.
Files changed (82) hide show
  1. package/README.md +134 -2
  2. package/dist/auth/deviceFlow.d.ts +53 -0
  3. package/dist/auth/deviceFlow.js +83 -0
  4. package/dist/auth/deviceFlow.js.map +1 -0
  5. package/dist/auth/firebaseConfig.d.ts +40 -0
  6. package/dist/auth/firebaseConfig.js +54 -0
  7. package/dist/auth/firebaseConfig.js.map +1 -0
  8. package/dist/auth/session.d.ts +20 -0
  9. package/dist/auth/session.js +25 -0
  10. package/dist/auth/session.js.map +1 -0
  11. package/dist/fiel/decode.d.ts +10 -0
  12. package/dist/fiel/decode.js +17 -0
  13. package/dist/fiel/decode.js.map +1 -0
  14. package/dist/fiel/fiel.d.ts +35 -0
  15. package/dist/fiel/fiel.js +39 -0
  16. package/dist/fiel/fiel.js.map +1 -0
  17. package/dist/fiel/store.d.ts +33 -0
  18. package/dist/fiel/store.js +61 -0
  19. package/dist/fiel/store.js.map +1 -0
  20. package/dist/fin/read.d.ts +19 -0
  21. package/dist/fin/read.js +54 -0
  22. package/dist/fin/read.js.map +1 -0
  23. package/dist/glossary.d.ts +17 -0
  24. package/dist/glossary.js +160 -0
  25. package/dist/glossary.js.map +1 -0
  26. package/dist/handlers.d.ts +38 -0
  27. package/dist/handlers.js +302 -0
  28. package/dist/handlers.js.map +1 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +121 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/keychain/keychain.d.ts +14 -0
  33. package/dist/keychain/keychain.js +150 -0
  34. package/dist/keychain/keychain.js.map +1 -0
  35. package/dist/onboard/cli.d.ts +1 -0
  36. package/dist/onboard/cli.js +13 -0
  37. package/dist/onboard/cli.js.map +1 -0
  38. package/dist/onboard/server.d.ts +8 -0
  39. package/dist/onboard/server.js +395 -0
  40. package/dist/onboard/server.js.map +1 -0
  41. package/dist/public/data/debts.fixture.json +4 -0
  42. package/dist/public/data/efos.fixture.json +8 -0
  43. package/dist/public/efos.d.ts +28 -0
  44. package/dist/public/efos.js +45 -0
  45. package/dist/public/efos.js.map +1 -0
  46. package/dist/public/lookup.d.ts +35 -0
  47. package/dist/public/lookup.js +45 -0
  48. package/dist/public/lookup.js.map +1 -0
  49. package/dist/public/verify.d.ts +37 -0
  50. package/dist/public/verify.js +61 -0
  51. package/dist/public/verify.js.map +1 -0
  52. package/dist/sat/artifacts.d.ts +33 -0
  53. package/dist/sat/artifacts.js +69 -0
  54. package/dist/sat/artifacts.js.map +1 -0
  55. package/dist/sat/descarga.d.ts +44 -0
  56. package/dist/sat/descarga.js +108 -0
  57. package/dist/sat/descarga.js.map +1 -0
  58. package/dist/sat/fielProvider.d.ts +28 -0
  59. package/dist/sat/fielProvider.js +22 -0
  60. package/dist/sat/fielProvider.js.map +1 -0
  61. package/dist/sat/jobs.d.ts +47 -0
  62. package/dist/sat/jobs.js +40 -0
  63. package/dist/sat/jobs.js.map +1 -0
  64. package/dist/sat/keychainFielProvider.d.ts +17 -0
  65. package/dist/sat/keychainFielProvider.js +36 -0
  66. package/dist/sat/keychainFielProvider.js.map +1 -0
  67. package/dist/sat/parse.d.ts +33 -0
  68. package/dist/sat/parse.js +55 -0
  69. package/dist/sat/parse.js.map +1 -0
  70. package/dist/sat/upload.d.ts +42 -0
  71. package/dist/sat/upload.js +67 -0
  72. package/dist/sat/upload.js.map +1 -0
  73. package/dist/telemetry/client.d.ts +32 -0
  74. package/dist/telemetry/client.js +53 -0
  75. package/dist/telemetry/client.js.map +1 -0
  76. package/dist/telemetry/installId.d.ts +1 -0
  77. package/dist/telemetry/installId.js +36 -0
  78. package/dist/telemetry/installId.js.map +1 -0
  79. package/dist/tools.d.ts +24 -0
  80. package/dist/tools.js +189 -0
  81. package/dist/tools.js.map +1 -0
  82. package/package.json +64 -4
package/README.md CHANGED
@@ -1,3 +1,135 @@
1
- # Temporary Holding Version
1
+ # fintecia
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/Fintecia/fintecia-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Fintecia/fintecia-mcp/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-green.svg)](https://nodejs.org)
6
+
7
+ Servidor MCP **local** de [Fintecia](https://fintecia.com.mx/?utm_source=mcp&utm_medium=referral&utm_campaign=fintecia_mcp&utm_content=readme) para agentes de IA
8
+ (Claude, Cursor, ChatGPT, OpenCode y cualquier cliente MCP). Le da a tu agente acceso a
9
+ tus facturas (CFDI), conciliación bancaria, riesgo fiscal (69-B / art. 69) y
10
+ deducibilidad — descargando tus facturas directamente del SAT con tu **e-firma local**,
11
+ que **nunca sale de tu máquina**.
12
+
13
+ ## Qué puede hacer tu agente
14
+
15
+ - **Descargar CFDIs del SAT** (emitidos / recibidos) por periodo, con tu e-firma local.
16
+ - **Consultar tus finanzas** ya calculadas por Fintecia (balances, impuestos, conciliación).
17
+ - **Verificar el riesgo fiscal** de cualquier RFC (listas 69-B y art. 69) y el estatus de una factura.
18
+ - **Evaluar la deducibilidad** de tus gastos, con la evidencia de cada veredicto.
19
+
20
+ ## Estado del proyecto
21
+
22
+ Esta es una **primera versión (0.1.0)**. Lo disponible hoy:
23
+
24
+ - ✅ Descarga de CFDI del SAT con e-firma local + materialización en Fintecia.
25
+ - ✅ Consultas materializadas: `fintecia_financials` (estados NIF B-3/B-6),
26
+ `fintecia_tax_summary`, `fintecia_risk_report`/`_check`, `fintecia_deductibility`.
27
+ - ✅ `fintecia_reconciliation` (estado de conciliación banco↔CFDI, **solo lectura**).
28
+
29
+ **Pendiente (próximas versiones):**
30
+
31
+ - ⏳ **Carga del PDF de estado de cuenta bancario** para conciliar contra CFDI
32
+ (la reconciliación hoy solo reporta datos ya importados; no acepta el PDF todavía).
33
+
34
+ ## Requisitos
35
+
36
+ - **Node.js 20 o superior.**
37
+ - **Cuenta de Fintecia** para las herramientas `fintecia_*` (login con Google).
38
+ - **e-firma** (`.cer` + `.key` + contraseña) solo si querés descargar CFDIs del SAT.
39
+ Sin e-firma, las consultas públicas y las de tu cuenta siguen funcionando.
40
+
41
+ ## Instalación
42
+
43
+ ### 1. Instalar el servidor
44
+
45
+ ```bash
46
+ npm install -g fintecia
47
+ ```
48
+
49
+ Esto deja el binario **`fintecia-mcp`** en tu `PATH` (apuntando a `dist/index.js`,
50
+ definido en `package.json` `bin`). No hace falta clonar el repo ni compilar.
51
+
52
+ ### 2. Registrar el servidor en tu cliente MCP
53
+
54
+ Agregá una entrada `fintecia` apuntando al binario `fintecia-mcp`. En **Claude
55
+ Desktop** (`claude_desktop_config.json`):
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "fintecia": {
61
+ "command": "fintecia-mcp",
62
+ "args": []
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ **Cursor** usa el mismo formato en `.cursor/mcp.json`; otros clientes (OpenCode,
69
+ Windsurf, etc.) aceptan una estructura equivalente. Consultá la documentación de tu
70
+ cliente para la sintaxis exacta.
71
+
72
+ ### 3. Primer uso: login + e-firma
73
+
74
+ 1. Pedile a tu agente que ejecute la herramienta **`fintecia_login`**.
75
+ 2. Abrí en tu navegador la URL local que devuelve.
76
+ 3. Iniciá sesión con Google y cargá tu e-firma (`.cer` + `.key` + contraseña).
77
+ 4. Listo: tu agente queda autenticado y vinculado a tu RFC.
78
+
79
+ ## Herramientas
80
+
81
+ ### Públicas del SAT (sin cuenta, sin e-firma)
82
+
83
+ | Herramienta | Qué hace |
84
+ |---|---|
85
+ | `sat_efos_check` | ¿Un RFC está en la lista 69-B (EFOS, operaciones presuntamente inexistentes)? |
86
+ | `sat_debts_check` | ¿Un RFC tiene créditos fiscales firmes (art. 69)? |
87
+ | `cfdi_verify` | Estatus de una factura (vigente / cancelado) + Validación EFOS. |
88
+
89
+ ### Descarga del SAT (e-firma local)
90
+
91
+ | Herramienta | Qué hace |
92
+ |---|---|
93
+ | `fintecia_efirmas` | Lista las e-firmas registradas en esta máquina. |
94
+ | `cfdi_download` | Descarga CFDIs del SAT por periodo (async → devuelve un `jobId`). |
95
+ | `cfdi_download_status` | Consulta el estado de una descarga en curso. |
96
+ | `cfdi_sync_status` | Qué tan al día está tu información local. |
97
+
98
+ ### Fintecia (requiere login)
99
+
100
+ | Herramienta | Qué hace |
101
+ |---|---|
102
+ | `fintecia_profile` | Tu usuario y los contribuyentes a los que estás vinculado. |
103
+ | `fintecia_financials` | Balances y razones materializados — usá esto para responder "cuánto". |
104
+ | `fintecia_tax_summary` | Resumen de ISR/IVA por periodo. |
105
+ | `fintecia_reconciliation` | Estado de la conciliación banco ↔ CFDI. |
106
+ | `fintecia_transactions` | Ingresos/gastos (dato crudo, solo para inspección). |
107
+ | `fintecia_cfdi` | Buscar / ver facturas (dato crudo, solo para inspección). |
108
+ | `fintecia_risk_report` | Cruce de tus contrapartes contra las listas 69-B / 69. |
109
+ | `fintecia_risk_check` | Ficha de riesgo de un proveedor (RFC puntual). |
110
+ | `fintecia_deductibility` | Veredicto de deducibilidad por gasto, con evidencia. |
111
+
112
+ > **El agente razona, Fintecia calcula.** Para responder "cuánto" usá siempre las
113
+ > herramientas materializadas (`fintecia_financials`, `fintecia_tax_summary`); las
114
+ > herramientas `_transactions` / `_cfdi` son solo para inspeccionar qué hay detrás de
115
+ > un número, nunca para re-sumarlo.
116
+
117
+ ### Glosario de campos (estática, sin login)
118
+
119
+ | Herramienta | Qué hace |
120
+ |---|---|
121
+ | `fintecia_glosario` | Diccionario de campos de las tools `fintecia_*` (money objects, NIF B-3/B-6, impuestos vs ISR prepagado, IVA en el balance). Estática y sin auth — devuelve el glosario para interpretar `fintecia_financials`. |
122
+
123
+ ## Seguridad y privacidad
124
+
125
+ - Tu **e-firma nunca sale de tu máquina**: se guarda en el keychain del sistema
126
+ (macOS Keychain / Windows Credential Manager / Linux libsecret). Fintecia solo
127
+ recibe los CFDI.
128
+ - El servidor es **local** (corre por stdio, en tu máquina) y **sin secretos**:
129
+ la autenticación es OAuth de *loopback* — tu navegador hace el login y el token
130
+ queda en memoria, no en disco.
131
+ - Todo acceso a tus datos pasa por tu sesión autenticada.
132
+
133
+ ## Licencia
134
+
135
+ MIT
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Device authorization flow client (RFC 8628-style) + Firebase token exchange.
3
+ *
4
+ * The MCP is a PUBLIC client: no client secret. Auth chain:
5
+ * 1. `start()` → Fintecia `/device/start` returns device_code + user_code +
6
+ * verification_uri.
7
+ * 2. The user opens verification_uri in the browser, signs in with Google+MFA
8
+ * (the existing Fintecia auth), and enters the user_code.
9
+ * 3. `poll()` → Fintecia `/device/token` returns a Firebase custom token once
10
+ * the user authorized.
11
+ * 4. `exchange()` → Firebase REST `accounts:signInWithCustomToken` swaps the
12
+ * custom token for an ID token + refresh token.
13
+ *
14
+ * The custom token is minted server-side (firebase-admin `createCustomToken`)
15
+ * and MUST preserve the MFA step-up claim (see `functions/src/utils/callable.ts`).
16
+ *
17
+ * Contract is documented here for the backend implementation (T3). No secrets
18
+ * live in this client: `firebaseApiKey` is a public Web API key.
19
+ */
20
+ export interface DeviceStartResponse {
21
+ deviceCode: string;
22
+ userCode: string;
23
+ verificationUri: string;
24
+ expiresIn: number;
25
+ interval: number;
26
+ }
27
+ export interface DeviceTokenResponse {
28
+ status: "pending" | "authorized" | "denied" | "expired";
29
+ customToken?: string;
30
+ }
31
+ export interface AuthSession {
32
+ idToken: string;
33
+ refreshToken: string;
34
+ }
35
+ export interface DeviceFlowConfig {
36
+ /** Fintecia backend base URL (no trailing slash). */
37
+ baseUrl: string;
38
+ /** Firebase Web API key (public, used only for the token exchange). */
39
+ firebaseApiKey: string;
40
+ /** Injectable fetch for tests. */
41
+ fetchFn?: typeof fetch;
42
+ }
43
+ export declare class DeviceFlowClient {
44
+ private readonly config;
45
+ constructor(config: DeviceFlowConfig);
46
+ private fetch;
47
+ start(): Promise<DeviceStartResponse>;
48
+ poll(deviceCode: string): Promise<DeviceTokenResponse>;
49
+ /** Exchange a Firebase custom token for an ID token + refresh token. */
50
+ exchange(customToken: string): Promise<AuthSession>;
51
+ /** Refresh an expired ID token via the Firebase secure-token endpoint. */
52
+ refresh(refreshToken: string): Promise<AuthSession>;
53
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Device authorization flow client (RFC 8628-style) + Firebase token exchange.
3
+ *
4
+ * The MCP is a PUBLIC client: no client secret. Auth chain:
5
+ * 1. `start()` → Fintecia `/device/start` returns device_code + user_code +
6
+ * verification_uri.
7
+ * 2. The user opens verification_uri in the browser, signs in with Google+MFA
8
+ * (the existing Fintecia auth), and enters the user_code.
9
+ * 3. `poll()` → Fintecia `/device/token` returns a Firebase custom token once
10
+ * the user authorized.
11
+ * 4. `exchange()` → Firebase REST `accounts:signInWithCustomToken` swaps the
12
+ * custom token for an ID token + refresh token.
13
+ *
14
+ * The custom token is minted server-side (firebase-admin `createCustomToken`)
15
+ * and MUST preserve the MFA step-up claim (see `functions/src/utils/callable.ts`).
16
+ *
17
+ * Contract is documented here for the backend implementation (T3). No secrets
18
+ * live in this client: `firebaseApiKey` is a public Web API key.
19
+ */
20
+ export class DeviceFlowClient {
21
+ config;
22
+ constructor(config) {
23
+ this.config = config;
24
+ }
25
+ fetch(input, init) {
26
+ const fn = this.config.fetchFn ?? fetch;
27
+ return fn(input, init);
28
+ }
29
+ async start() {
30
+ const res = await this.fetch(`${this.config.baseUrl}/api/v1/device/start`, {
31
+ method: "POST",
32
+ });
33
+ if (!res.ok) {
34
+ throw new Error(`device/start failed: ${res.status}`);
35
+ }
36
+ return (await res.json());
37
+ }
38
+ async poll(deviceCode) {
39
+ const res = await this.fetch(`${this.config.baseUrl}/api/v1/device/token`, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json" },
42
+ body: JSON.stringify({ deviceCode }),
43
+ });
44
+ if (!res.ok) {
45
+ throw new Error(`device/token failed: ${res.status}`);
46
+ }
47
+ return (await res.json());
48
+ }
49
+ /** Exchange a Firebase custom token for an ID token + refresh token. */
50
+ async exchange(customToken) {
51
+ const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken` +
52
+ `?key=${encodeURIComponent(this.config.firebaseApiKey)}`;
53
+ const res = await this.fetch(url, {
54
+ method: "POST",
55
+ headers: { "content-type": "application/json" },
56
+ body: JSON.stringify({ token: customToken, returnSecureToken: true }),
57
+ });
58
+ if (!res.ok) {
59
+ throw new Error(`custom token exchange failed: ${res.status}`);
60
+ }
61
+ const body = (await res.json());
62
+ return { idToken: body.idToken, refreshToken: body.refreshToken };
63
+ }
64
+ /** Refresh an expired ID token via the Firebase secure-token endpoint. */
65
+ async refresh(refreshToken) {
66
+ const url = `https://securetoken.googleapis.com/v1/token` +
67
+ `?key=${encodeURIComponent(this.config.firebaseApiKey)}`;
68
+ const res = await this.fetch(url, {
69
+ method: "POST",
70
+ headers: { "content-type": "application/x-www-form-urlencoded" },
71
+ body: new URLSearchParams({
72
+ grant_type: "refresh_token",
73
+ refresh_token: refreshToken,
74
+ }).toString(),
75
+ });
76
+ if (!res.ok) {
77
+ throw new Error(`refresh failed: ${res.status}`);
78
+ }
79
+ const body = (await res.json());
80
+ return { idToken: body.id_token, refreshToken: body.refresh_token };
81
+ }
82
+ }
83
+ //# sourceMappingURL=deviceFlow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviceFlow.js","sourceRoot":"","sources":["../../src/auth/deviceFlow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Firebase Web config (public). The MCP is a public client: these values are
3
+ * meant to be embedded (the Web API key is not a secret). Overridable via env
4
+ * for staging/local emulator.
5
+ *
6
+ * `FINTECIA_ENV` selects the backend environment. Unlike the Flutter app's
7
+ * `Env` (which defaults to "local" for dev), the MCP is distributed to end
8
+ * users, so it defaults to "production": when local, the onboarding page
9
+ * connects the Auth SDK to the Auth emulator at 127.0.0.1:9099 (same as
10
+ * `lib/main.dart` `useAuthEmulator`).
11
+ */
12
+ export declare const FIREBASE_ENV: string;
13
+ export declare const isLocal: boolean;
14
+ export declare const AUTH_EMULATOR_URL: string;
15
+ /**
16
+ * Fintecia backend base URL (no trailing slash), env-aware.
17
+ *
18
+ * The MCP talks to the backend over plain `fetch` (NOT the Firebase Functions
19
+ * SDK), so it must know the full base URL. `FINTECIA_API_URL` wins when set;
20
+ * otherwise the URL is derived from `FINTECIA_ENV`:
21
+ * - local → Hosting emulator (http://127.0.0.1:5000). The client calls
22
+ * `/api/v1/*` paths, which are Hosting rewrites (firebase.json);
23
+ * the Functions emulator (5001) serves functions BY NAME, so it
24
+ * does NOT serve the `/api/v1/*` rewrite paths.
25
+ * - staging → fintecia-staging.web.app
26
+ * - production → app.fintecia.com.mx
27
+ *
28
+ * L22 bug: previously `FINTECIA_API_URL` was read raw with NO default, so a
29
+ * prod run failed with "uploadBaseUrl no configurado (definí FINTECIA_API_URL)".
30
+ */
31
+ export declare function resolveApiBaseUrl(env: string, explicit?: string, localOverride?: string): string;
32
+ export declare const API_BASE_URL: string;
33
+ export declare const FIREBASE_WEB_CONFIG: {
34
+ apiKey: string;
35
+ authDomain: string;
36
+ projectId: string;
37
+ appId: string;
38
+ storageBucket: string;
39
+ messagingSenderId: string;
40
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Firebase Web config (public). The MCP is a public client: these values are
3
+ * meant to be embedded (the Web API key is not a secret). Overridable via env
4
+ * for staging/local emulator.
5
+ *
6
+ * `FINTECIA_ENV` selects the backend environment. Unlike the Flutter app's
7
+ * `Env` (which defaults to "local" for dev), the MCP is distributed to end
8
+ * users, so it defaults to "production": when local, the onboarding page
9
+ * connects the Auth SDK to the Auth emulator at 127.0.0.1:9099 (same as
10
+ * `lib/main.dart` `useAuthEmulator`).
11
+ */
12
+ export const FIREBASE_ENV = process.env.FINTECIA_ENV ?? 'production';
13
+ export const isLocal = FIREBASE_ENV === 'local';
14
+ export const AUTH_EMULATOR_URL = process.env.FIREBASE_AUTH_EMULATOR ?? 'http://127.0.0.1:9099';
15
+ /**
16
+ * Fintecia backend base URL (no trailing slash), env-aware.
17
+ *
18
+ * The MCP talks to the backend over plain `fetch` (NOT the Firebase Functions
19
+ * SDK), so it must know the full base URL. `FINTECIA_API_URL` wins when set;
20
+ * otherwise the URL is derived from `FINTECIA_ENV`:
21
+ * - local → Hosting emulator (http://127.0.0.1:5000). The client calls
22
+ * `/api/v1/*` paths, which are Hosting rewrites (firebase.json);
23
+ * the Functions emulator (5001) serves functions BY NAME, so it
24
+ * does NOT serve the `/api/v1/*` rewrite paths.
25
+ * - staging → fintecia-staging.web.app
26
+ * - production → app.fintecia.com.mx
27
+ *
28
+ * L22 bug: previously `FINTECIA_API_URL` was read raw with NO default, so a
29
+ * prod run failed with "uploadBaseUrl no configurado (definí FINTECIA_API_URL)".
30
+ */
31
+ export function resolveApiBaseUrl(env, explicit, localOverride) {
32
+ const trimmed = explicit?.trim();
33
+ if (trimmed)
34
+ return trimmed.replace(/\/+$/, '');
35
+ switch (env) {
36
+ case 'local':
37
+ return (localOverride?.trim() ||
38
+ 'http://127.0.0.1:5000');
39
+ case 'staging':
40
+ return 'https://fintecia-staging.web.app';
41
+ default: // production and any unknown value → prod (fail safe to the real API)
42
+ return 'https://app.fintecia.com.mx';
43
+ }
44
+ }
45
+ export const API_BASE_URL = resolveApiBaseUrl(FIREBASE_ENV, process.env.FINTECIA_API_URL, process.env.FINTECIA_LOCAL_API_URL);
46
+ export const FIREBASE_WEB_CONFIG = {
47
+ apiKey: process.env.FIREBASE_API_KEY ?? 'AIzaSyAt1DR0jI8h9GqaPo8kaSwVNL_4Hb3IhL8',
48
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN ?? 'app.fintecia.com.mx',
49
+ projectId: process.env.FIREBASE_PROJECT_ID ?? 'fintecia',
50
+ appId: process.env.FIREBASE_APP_ID ?? '1:608545925207:web:68758d816a6085dce39b12',
51
+ storageBucket: process.env.FIREBASE_STORAGE_BUCKET ?? 'fintecia.firebasestorage.app',
52
+ messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID ?? '608545925207',
53
+ };
54
+ //# sourceMappingURL=firebaseConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebaseConfig.js","sourceRoot":"","sources":["../../src/auth/firebaseConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC;AACrE,MAAM,CAAC,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC;AAChD,MAAM,CAAC,MAAM,iBAAiB,GAC5B,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,uBAAuB,CAAC;AAEhE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAW,EACX,QAAiB,EACjB,aAAsB;IAEtB,MAAM,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjC,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO,CACL,aAAa,EAAE,IAAI,EAAE;gBACrB,uBAAuB,CACxB,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO,kCAAkC,CAAC;QAC5C,SAAS,sEAAsE;YAC7E,OAAO,6BAA6B,CAAC;IACzC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAC3C,YAAY,EACZ,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAC5B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CACnC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,yCAAyC;IACjF,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,qBAAqB;IACrE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,UAAU;IACxD,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,2CAA2C;IACjF,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,8BAA8B;IACpF,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,cAAc;CAC9E,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Auth session (loopback OAuth result). The local web page performs the
3
+ * Firebase sign-in (Google + MFA) and POSTs the tokens to the local server,
4
+ * which stores them here for the process lifetime.
5
+ *
6
+ * In-memory only for now: the MCP is a stdio process that lives while the
7
+ * agent talks to it; the ID token is refreshed via the refresh token. Disk
8
+ * persistence of the refresh token (surviving MCP restarts) is a follow-up.
9
+ */
10
+ export interface AuthSession {
11
+ idToken: string;
12
+ refreshToken: string;
13
+ }
14
+ export declare class SessionStore {
15
+ private session;
16
+ set(session: AuthSession): void;
17
+ get(): AuthSession | null;
18
+ clear(): void;
19
+ get idToken(): string | null;
20
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Auth session (loopback OAuth result). The local web page performs the
3
+ * Firebase sign-in (Google + MFA) and POSTs the tokens to the local server,
4
+ * which stores them here for the process lifetime.
5
+ *
6
+ * In-memory only for now: the MCP is a stdio process that lives while the
7
+ * agent talks to it; the ID token is refreshed via the refresh token. Disk
8
+ * persistence of the refresh token (surviving MCP restarts) is a follow-up.
9
+ */
10
+ export class SessionStore {
11
+ session = null;
12
+ set(session) {
13
+ this.session = session;
14
+ }
15
+ get() {
16
+ return this.session;
17
+ }
18
+ clear() {
19
+ this.session = null;
20
+ }
21
+ get idToken() {
22
+ return this.session?.idToken ?? null;
23
+ }
24
+ }
25
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,OAAO,YAAY;IACf,OAAO,GAAuB,IAAI,CAAC;IAE3C,GAAG,CAAC,OAAoB;QACtB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC;IACvC,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Decode a base64 file (sent by the browser) into the string format nodecfdi's
3
+ * `Fiel.create` expects. The browser reads the .cer/.key as base64 (binary-safe
4
+ * — `readAsDataURL`), unlike `readAsText` which corrupts DER binaries.
5
+ *
6
+ * - PEM → the UTF-8 text as-is.
7
+ * - DER cert → base64 (matches Fintecia's `cerContents` in ws-auth.ts).
8
+ * - DER key → latin1/binary string (matches `keyBinary` in ws-auth.ts).
9
+ */
10
+ export declare function decodeFileForFiel(b64: string, kind: 'cert' | 'key'): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Decode a base64 file (sent by the browser) into the string format nodecfdi's
3
+ * `Fiel.create` expects. The browser reads the .cer/.key as base64 (binary-safe
4
+ * — `readAsDataURL`), unlike `readAsText` which corrupts DER binaries.
5
+ *
6
+ * - PEM → the UTF-8 text as-is.
7
+ * - DER cert → base64 (matches Fintecia's `cerContents` in ws-auth.ts).
8
+ * - DER key → latin1/binary string (matches `keyBinary` in ws-auth.ts).
9
+ */
10
+ export function decodeFileForFiel(b64, kind) {
11
+ const buf = Buffer.from(b64, 'base64');
12
+ const text = buf.toString('utf8');
13
+ if (text.includes('-----BEGIN'))
14
+ return text;
15
+ return kind === 'cert' ? buf.toString('base64') : buf.toString('latin1');
16
+ }
17
+ //# sourceMappingURL=decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.js","sourceRoot":"","sources":["../../src/fiel/decode.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAoB;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { Fiel } from "@nodecfdi/sat-ws-descarga-masiva";
2
+ /**
3
+ * FIEL inspection — thin wrapper over nodecfdi's `Fiel` (the same library the
4
+ * Fintecia backend uses in `functions/src/sat/ws-auth.ts`).
5
+ *
6
+ * The RFC lives in the X.509 `x500UniqueIdentifier` (OID 2.5.4.45), NOT the CN.
7
+ * nodecfdi's `Fiel.create(...).getRfc()` extracts it consistently with Fintecia,
8
+ * so we do NOT re-implement certificate parsing here.
9
+ *
10
+ * The private key (`keyPem`) and password are never logged, never persisted
11
+ * (the password is re-entered per signing session), and never leave the machine.
12
+ */
13
+ export interface FielInfo {
14
+ rfc: string;
15
+ serial: string;
16
+ pem: string;
17
+ /** Legal name (razón social / nombre) from the certificate's CN, if present. */
18
+ razonSocial: string | null;
19
+ isValid: boolean;
20
+ }
21
+ export interface InspectedFiel {
22
+ fiel: Fiel;
23
+ info: FielInfo;
24
+ }
25
+ /**
26
+ * Build a nodecfdi Fiel from cert + key + passPhrase and extract its public
27
+ * identity (RFC, serial). Throws when the FIEL is invalid or expired.
28
+ */
29
+ /**
30
+ * Extract the holder's legal name (razón social / nombre) from the subject's
31
+ * CN. The RFC lives in `x500UniqueIdentifier` (OID 2.5.4.45), NOT the CN —
32
+ * the CN carries the name. Returns null when the CN is absent.
33
+ */
34
+ export declare function extractRazonSocial(certPem: string): string | null;
35
+ export declare function inspectFiel(certPem: string, keyPem: string, passPhrase: string): InspectedFiel;
@@ -0,0 +1,39 @@
1
+ import { X509Certificate } from "node:crypto";
2
+ import { Fiel } from "@nodecfdi/sat-ws-descarga-masiva";
3
+ /**
4
+ * Build a nodecfdi Fiel from cert + key + passPhrase and extract its public
5
+ * identity (RFC, serial). Throws when the FIEL is invalid or expired.
6
+ */
7
+ /**
8
+ * Extract the holder's legal name (razón social / nombre) from the subject's
9
+ * CN. The RFC lives in `x500UniqueIdentifier` (OID 2.5.4.45), NOT the CN —
10
+ * the CN carries the name. Returns null when the CN is absent.
11
+ */
12
+ export function extractRazonSocial(certPem) {
13
+ try {
14
+ const subject = new X509Certificate(certPem).subject;
15
+ const m = subject.match(/^CN=([^\n]+)/m);
16
+ return m ? m[1].trim() : null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export function inspectFiel(certPem, keyPem, passPhrase) {
23
+ const fiel = Fiel.create(certPem, keyPem, passPhrase);
24
+ if (!fiel.isValid()) {
25
+ throw new Error("FIEL inválida o vencida");
26
+ }
27
+ const pem = fiel.getCertificatePemContents();
28
+ return {
29
+ fiel,
30
+ info: {
31
+ rfc: fiel.getRfc(),
32
+ serial: fiel.getCertificateSerial(),
33
+ pem,
34
+ razonSocial: extractRazonSocial(pem),
35
+ isValid: true,
36
+ },
37
+ };
38
+ }
39
+ //# sourceMappingURL=fiel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fiel.js","sourceRoot":"","sources":["../../src/fiel/fiel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAC;AA4BxD;;;GAGG;AACH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;QACrD,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACzC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAe,EACf,MAAc,EACd,UAAkB;IAElB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAEtD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;IAE7C,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE;YAClB,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE;YACnC,GAAG;YACH,WAAW,EAAE,kBAAkB,CAAC,GAAG,CAAC;YACpC,OAAO,EAAE,IAAI;SACd;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * FIEL storage — multi-taxpayer, keyed by RFC.
3
+ *
4
+ * Security model (S2): the .key is the private material and NEVER leaves the
5
+ * machine nor is logged. The stored `.key` PEM is already password-encrypted
6
+ * with the user's FIEL password; the password itself is held ONLY in the OS
7
+ * keychain (never in the file store, never logged, never uploaded). The .cer
8
+ * is public.
9
+ *
10
+ * FileFielStore is the cross-platform base and the Linux fallback. The OS
11
+ * keychain (macOS Keychain / Windows DPAPI / Linux libsecret) is a follow-up
12
+ * refinement behind the same FielStore port (T1b).
13
+ */
14
+ export interface FielCredentials {
15
+ rfc: string;
16
+ certPem: string;
17
+ keyPem: string;
18
+ }
19
+ export interface FielStore {
20
+ store(fiel: FielCredentials): Promise<void>;
21
+ load(rfc: string): Promise<FielCredentials | null>;
22
+ list(): Promise<string[]>;
23
+ remove(rfc: string): Promise<void>;
24
+ }
25
+ export declare class FileFielStore implements FielStore {
26
+ private readonly baseDir;
27
+ constructor(baseDir?: string);
28
+ private dirFor;
29
+ store(fiel: FielCredentials): Promise<void>;
30
+ load(rfc: string): Promise<FielCredentials | null>;
31
+ list(): Promise<string[]>;
32
+ remove(rfc: string): Promise<void>;
33
+ }
@@ -0,0 +1,61 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const CERT_FILE = "certificate.cer";
5
+ const KEY_FILE = "private.key";
6
+ function sanitizeRfc(rfc) {
7
+ // RFC is alphanumeric; defensive guard against path traversal.
8
+ if (!/^[A-ZÑ&0-9]{12,13}$/.test(rfc)) {
9
+ throw new Error(`invalid RFC for storage: ${rfc}`);
10
+ }
11
+ return rfc;
12
+ }
13
+ export class FileFielStore {
14
+ baseDir;
15
+ constructor(baseDir = defaultBaseDir()) {
16
+ this.baseDir = baseDir;
17
+ }
18
+ dirFor(rfc) {
19
+ return path.join(this.baseDir, sanitizeRfc(rfc));
20
+ }
21
+ async store(fiel) {
22
+ const dir = this.dirFor(fiel.rfc);
23
+ await fs.mkdir(dir, { recursive: true });
24
+ // 0600: owner-only — the .key must not be world-readable.
25
+ await fs.writeFile(path.join(dir, CERT_FILE), fiel.certPem, { mode: 0o600 });
26
+ await fs.writeFile(path.join(dir, KEY_FILE), fiel.keyPem, { mode: 0o600 });
27
+ }
28
+ async load(rfc) {
29
+ const dir = this.dirFor(rfc);
30
+ try {
31
+ const [certPem, keyPem] = await Promise.all([
32
+ fs.readFile(path.join(dir, CERT_FILE), "utf8"),
33
+ fs.readFile(path.join(dir, KEY_FILE), "utf8"),
34
+ ]);
35
+ return { rfc, certPem, keyPem };
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ async list() {
42
+ try {
43
+ const entries = await fs.readdir(this.baseDir, { withFileTypes: true });
44
+ return entries
45
+ .filter((e) => e.isDirectory())
46
+ .map((e) => e.name)
47
+ .filter((name) => /^[A-ZÑ&0-9]{12,13}$/.test(name))
48
+ .sort();
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ async remove(rfc) {
55
+ await fs.rm(this.dirFor(rfc), { recursive: true, force: true });
56
+ }
57
+ }
58
+ function defaultBaseDir() {
59
+ return path.join(os.homedir(), ".fintecia", "fiel");
60
+ }
61
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/fiel/store.ts"],"names":[],"mappings":"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"}