fintecia 0.1.0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fintecia",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "MCP local de Fintecia — descarga de CFDI con e-firma, conciliación banco-CFDI, riesgo 69-B/EFOS, deducibilidad fiscal. SAT · CFDI · Conciliación · Seguridad · México · agente AI",
6
6
  "license": "MIT",
@@ -1,53 +0,0 @@
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
- }
@@ -1,83 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"deviceFlow.js","sourceRoot":"","sources":["../../src/auth/deviceFlow.ts"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- [
2
- { "rfc": "GIM150525L37", "nombre": "GIMORU, S.A. DE C.V.", "situacion": "credito_firme" },
3
- { "rfc": "XAXX010101000", "nombre": "PÚBLICO EN GENERAL", "situacion": "credito_firme" }
4
- ]
@@ -1,8 +0,0 @@
1
- [
2
- { "rfc": "AAA080808HL8", "nombre": "ASESORES EN AVALÚOS Y ACTIVOS, S.A. DE C.V.", "situacion": "sentencia_favorable", "oficio": "500-05-2018-16632" },
3
- { "rfc": "AAA091014835", "nombre": "AQUAERIS ACUACULTURA Y ARQUITECTURA SUSTENTABLE, S.C.", "situacion": "desvirtuado", "oficio": "500-05-2016-38728" },
4
- { "rfc": "AAA100303L51", "nombre": "INGENIOS SANTOS, S.A. DE C.V.", "situacion": "desvirtuado", "oficio": "500-05-2017-38736" },
5
- { "rfc": "AAA120730823", "nombre": "ASESORES Y ADMINISTRADORES AGRICOLAS, S. DE R.L. DE C.V.", "situacion": "definitivo", "oficio": "500-05-2016-38728" },
6
- { "rfc": "AAC120810NCA", "nombre": "AAA ASESORIA Y CAPACITACION EMPRESARIAL, S.C.", "situacion": "definitivo", "oficio": "500-05-2018-29883" },
7
- { "rfc": "AAC1805044A8", "nombre": "ASESORES ACOST, S.C.", "situacion": "presunto", "oficio": "500-05-00-00-00-2026-21664" }
8
- ]
@@ -1,28 +0,0 @@
1
- /**
2
- * SAT 69-B (EFOS) lookup — contribuyentes con operaciones presuntamente
3
- * inexistentes. Public, no auth. The full list is refreshed from the SAT open
4
- * data (or a mirror); a small fixture is bundled so the tool works out of the
5
- * box for testing.
6
- *
7
- * Correctness-critical: a false "not on the list" is dangerous. Normalize the
8
- * RFC (uppercase, no whitespace) before lookup.
9
- */
10
- export type EfosSituacion = "presunto" | "definitivo" | "desvirtuado" | "sentencia_favorable";
11
- export interface EfosEntry {
12
- rfc: string;
13
- nombre: string;
14
- situacion: EfosSituacion;
15
- /** Oficio de notificación (SAT), si se conoce. */
16
- oficio?: string;
17
- /** Fecha de publicación (ISO YYYY-MM-DD), si se conoce. */
18
- fechaPublicacion?: string;
19
- }
20
- export declare function normalizeRfc(rfc: string): string;
21
- export declare class EfosLookup {
22
- private readonly byRfc;
23
- constructor(entries: EfosEntry[]);
24
- check(rfc: string): EfosEntry | null;
25
- get size(): number;
26
- /** Build an EfosLookup from raw JSON (array of EfosEntry). */
27
- static fromJson(json: unknown): EfosLookup;
28
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * SAT 69-B (EFOS) lookup — contribuyentes con operaciones presuntamente
3
- * inexistentes. Public, no auth. The full list is refreshed from the SAT open
4
- * data (or a mirror); a small fixture is bundled so the tool works out of the
5
- * box for testing.
6
- *
7
- * Correctness-critical: a false "not on the list" is dangerous. Normalize the
8
- * RFC (uppercase, no whitespace) before lookup.
9
- */
10
- export function normalizeRfc(rfc) {
11
- return rfc.trim().toUpperCase();
12
- }
13
- export class EfosLookup {
14
- byRfc;
15
- constructor(entries) {
16
- this.byRfc = new Map(entries.map((e) => [normalizeRfc(e.rfc), e]));
17
- }
18
- check(rfc) {
19
- return this.byRfc.get(normalizeRfc(rfc)) ?? null;
20
- }
21
- get size() {
22
- return this.byRfc.size;
23
- }
24
- /** Build an EfosLookup from raw JSON (array of EfosEntry). */
25
- static fromJson(json) {
26
- if (!Array.isArray(json)) {
27
- throw new Error("EFOS data must be an array");
28
- }
29
- const entries = json.map((e) => {
30
- const item = e;
31
- if (typeof item.rfc !== "string" || typeof item.situacion !== "string") {
32
- throw new Error("EFOS entry missing rfc/situacion");
33
- }
34
- return {
35
- rfc: item.rfc,
36
- nombre: item.nombre ?? "",
37
- situacion: item.situacion,
38
- oficio: item.oficio,
39
- fechaPublicacion: item.fechaPublicacion,
40
- };
41
- });
42
- return new EfosLookup(entries);
43
- }
44
- }
45
- //# sourceMappingURL=efos.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"efos.js","sourceRoot":"","sources":["../../src/public/efos.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkBH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,UAAU;IACJ,KAAK,CAAyB;IAE/C,YAAY,OAAoB;QAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,GAAW;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IACnD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,8DAA8D;IAC9D,MAAM,CAAC,QAAQ,CAAC,IAAa;QAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,OAAO,GAAgB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC1C,MAAM,IAAI,GAAG,CAAuB,CAAC;YACrC,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;gBACvE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACtD,CAAC;YACD,OAAO;gBACL,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;gBACzB,SAAS,EAAE,IAAI,CAAC,SAA0B;gBAC1C,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;aACxC,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;CACF"}