@pimia/sdk 0.20.0 → 0.27.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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ *
5
+ * Generado con `npm run generate:types` (scripts/generate-types.mjs), que
6
+ * añade UN transform al generador: `format: binary` sale como `Blob` y no
7
+ * como `string`. El porqué, en ese fichero.
8
+ */
9
+ export {};
@@ -0,0 +1,607 @@
1
+ /**
2
+ * Cliente del PLANO CENTRAL de Pimia para el integrador (cuenta de
3
+ * desarrollador): su cartera de clientes, los vínculos con cada instancia, sus
4
+ * clients OAuth, las invitaciones, el patrocinio y el traspaso de propiedad.
5
+ *
6
+ * No es el cliente de un tenant ({@link PimiaClient}, que habla con
7
+ * `https://{tenant}.pimia.es/api/v1` con un token OAuth por instancia): este
8
+ * habla con el ÁPICE (`https://pimia.es/api`) con el **token personal de
9
+ * Sanctum** de la cuenta de desarrollador, acotado por plano
10
+ * (galeote/factSaas#731): `desarrollador` abre `/desarrollador/*` y `central`
11
+ * abre invitaciones, patrocinio y traspaso. Cada operación del contrato dice
12
+ * cuál exige (`x-pimia-required-ability`); un token sin ella recibe un
13
+ * {@link MissingAbilityError} con la habilidad que falta.
14
+ *
15
+ * Lo que NO hace, a propósito: refrescar nada (el token personal no caduca ni
16
+ * rota; se revoca desde el panel) ni leer contenido fiscal de ningún cliente —
17
+ * a los datos de una instancia se llega por OAuth consentido, con el otro
18
+ * cliente.
19
+ *
20
+ * Los tipos salen de `spec/pimia-central-v1.json` (`./central-api`).
21
+ */
22
+ import type { operations } from './central-api.js';
23
+ /** El cuerpo JSON que pide una operación del contrato. */
24
+ type Body<O extends keyof operations> = operations[O] extends {
25
+ requestBody: {
26
+ content: {
27
+ 'application/json': infer B;
28
+ };
29
+ };
30
+ } ? B : operations[O] extends {
31
+ requestBody?: {
32
+ content: {
33
+ 'application/json': infer B;
34
+ };
35
+ };
36
+ } ? B : never;
37
+ /**
38
+ * Cuerpo de `POST /billing/sponsorship`: la instancia y el plan de canal con
39
+ * que se paga; `return_url` (1.4.0) es a dónde vuelve el integrador desde el
40
+ * Checkout del canal —solo se acepta el origen del panel central o el del
41
+ * ápice; lo demás vuelve al panel Vue—.
42
+ */
43
+ export type SponsorshipRequest = Body<'billing.sponsor'>;
44
+ /** Cuerpo de `POST /billing/portal` (1.4.0): `return_url`, con la misma regla que el patrocinio. */
45
+ export type BillingPortalRequest = Body<'billing.portal'>;
46
+ /** Cuerpo de `POST /tenant-invitations`: a quién se invita y quién paga (`billing`). */
47
+ export type TenantInvitationRequest = Body<'tenantInvitation.store'>;
48
+ /** Cuerpo de `POST /tenants/{slug}/transfer-ownership`: el usuario de la instancia que pasa a ser dueño. */
49
+ export type TransferOwnershipRequest = Body<'tenant.transferOwnership'>;
50
+ /**
51
+ * Cuerpo de `PUT /desarrollador/catalogo`: el catálogo del integrador ENTERO —
52
+ * cabecera (nombre comercial, soporte, moneda ISO 4217, enlace de contratación)
53
+ * y filas (`kind` ∈ `base|module|app`, `slug`, `price_cents` en subunidades de
54
+ * esa moneda, `contract_url` propio opcional, `enabled`)—. Lo que no venga deja
55
+ * de existir.
56
+ */
57
+ export type CatalogoDelIntegradorRequest = Body<'integradorCatalogo.update'>;
58
+ /**
59
+ * Cuerpo de `POST /desarrollador/tenants/{slug}/activaciones`: qué se activa al
60
+ * cliente (`kind` ∈ `base|module|app`, `slug`; `plan_id` solo si hay varios
61
+ * planes de canal).
62
+ */
63
+ export type ActivacionMayoristaRequest = Body<'integradorActivacion.store'>;
64
+ export type IntegradorDominioRequest = Body<'integradorDominio.store'>;
65
+ export type IntegradorTokenRequest = Body<'integradorToken.store'>;
66
+ export interface PimiaCentralClientOptions {
67
+ /** El ápice, sin `/api`: `https://pimia.es` (o `https://taskai.work` en dev). */
68
+ baseUrl: string;
69
+ /**
70
+ * El token personal de la cuenta de desarrollador, o una función que lo
71
+ * devuelva (para leerlo de un secreto en cada llamada). Se manda como
72
+ * `Authorization: Bearer`.
73
+ */
74
+ token: string | (() => string | Promise<string>);
75
+ /** `fetch` a usar (por defecto, el global). Útil para tests y proxies. */
76
+ fetch?: typeof globalThis.fetch;
77
+ /** Cabeceras fijas para todas las llamadas. */
78
+ headers?: Record<string, string>;
79
+ }
80
+ export interface CentralRequestOptions {
81
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
82
+ query?: Record<string, string | number | boolean | undefined | null | Array<string | number>>;
83
+ body?: unknown;
84
+ headers?: Record<string, string>;
85
+ signal?: AbortSignal;
86
+ }
87
+ export interface CentralResponseMeta {
88
+ status: number;
89
+ requestId?: string;
90
+ }
91
+ export interface CentralResponseWithMeta<T> {
92
+ data: T;
93
+ meta: CentralResponseMeta;
94
+ }
95
+ export declare class PimiaCentralClient {
96
+ private readonly baseUrl;
97
+ private readonly token;
98
+ private readonly doFetch;
99
+ private readonly extraHeaders;
100
+ constructor(options: PimiaCentralClientOptions);
101
+ /** `GET /desarrollador/overview`: la cartera, con la atribución de cada alta. */
102
+ overview(): Promise<{
103
+ data: {
104
+ cartera: {
105
+ id: string;
106
+ name: string;
107
+ status: string;
108
+ plan: string | null;
109
+ created_at: string | null;
110
+ trial_ends_at: string | null;
111
+ relacion: string;
112
+ billing_mode: string | null;
113
+ la_pago_yo: boolean;
114
+ marca_blanca: boolean;
115
+ origen: {
116
+ client_id: string;
117
+ name: string;
118
+ atribuido: boolean;
119
+ } | null;
120
+ }[];
121
+ resumen: {
122
+ total: number;
123
+ activos: number;
124
+ suspendidos: number;
125
+ };
126
+ cuota: {
127
+ usada: number;
128
+ limite: number;
129
+ ilimitada: boolean;
130
+ };
131
+ };
132
+ }>;
133
+ /** `GET /desarrollador/salud`: clients, webhooks y propuestas, solo los propios. */
134
+ salud(): Promise<{
135
+ data: {
136
+ clients: {
137
+ client_id: string;
138
+ name: string;
139
+ revoked_at: string | null;
140
+ tokens_vivos: number;
141
+ ultimo_uso: string | null;
142
+ }[];
143
+ webhooks: {
144
+ id: number;
145
+ tenant_id: string | null;
146
+ url: string;
147
+ events: string[];
148
+ disabled_at: string | null;
149
+ consecutive_failures: number;
150
+ entregas_7d: {
151
+ delivered: number;
152
+ pending: number;
153
+ dead: number;
154
+ };
155
+ }[];
156
+ approvals: {
157
+ abiertas: number;
158
+ por_tenant: {
159
+ tenant_id: string;
160
+ abiertas: number;
161
+ }[];
162
+ };
163
+ };
164
+ }>;
165
+ /** `GET /desarrollador/facturacion`: lo que el integrador paga a Pimia (canal y asientos). */
166
+ facturacion(): Promise<{
167
+ data: {
168
+ canal: {
169
+ estado: string;
170
+ asientos: number;
171
+ quantity: number;
172
+ descuadre: boolean;
173
+ termina_en: string | null;
174
+ } | null;
175
+ asientos: {
176
+ tenant_id: string;
177
+ rol: string;
178
+ en_mora: boolean;
179
+ gracia_hasta: string | null;
180
+ suspendido_desde: string | null;
181
+ }[];
182
+ en_mora: number;
183
+ anadidos: {
184
+ activaciones: number;
185
+ total_cents: number;
186
+ total: string;
187
+ por_tenant: {
188
+ tenant_id: string;
189
+ activaciones: number;
190
+ total_cents: number;
191
+ }[];
192
+ };
193
+ };
194
+ }>;
195
+ get links(): {
196
+ list: () => Promise<{
197
+ data: {
198
+ id: number;
199
+ link_code: string;
200
+ status: string;
201
+ tenant: {
202
+ id: string;
203
+ name: string;
204
+ status: string;
205
+ } | null;
206
+ requester: {
207
+ id: number;
208
+ name: string;
209
+ email: string;
210
+ } | null;
211
+ requested_at: string | null;
212
+ accepted_at: string | null;
213
+ revoked_at: string | null;
214
+ created_at: string | null;
215
+ }[];
216
+ }>;
217
+ /** Un código `DEV-XXXX-XXXX` que el cliente teclea en su instancia. */
218
+ generateCode: () => Promise<{
219
+ message: "C\u00F3digo de vinculaci\u00F3n generado.";
220
+ data: {
221
+ id: number;
222
+ link_code: string;
223
+ status: string;
224
+ tenant: {
225
+ id: string;
226
+ name: string;
227
+ status: string;
228
+ } | null;
229
+ requester: {
230
+ id: number;
231
+ name: string;
232
+ email: string;
233
+ } | null;
234
+ requested_at: string | null;
235
+ accepted_at: string | null;
236
+ revoked_at: string | null;
237
+ created_at: string | null;
238
+ };
239
+ }>;
240
+ accept: (id: number | string) => Promise<{
241
+ message: "Vinculaci\u00F3n aceptada.";
242
+ data: {
243
+ id: number;
244
+ link_code: string;
245
+ status: string;
246
+ tenant: {
247
+ id: string;
248
+ name: string;
249
+ status: string;
250
+ } | null;
251
+ requester: {
252
+ id: number;
253
+ name: string;
254
+ email: string;
255
+ } | null;
256
+ requested_at: string | null;
257
+ accepted_at: string | null;
258
+ revoked_at: string | null;
259
+ created_at: string | null;
260
+ };
261
+ }>;
262
+ reject: (id: number | string) => Promise<{
263
+ message: "Solicitud rechazada.";
264
+ }>;
265
+ };
266
+ get clients(): {
267
+ list: () => Promise<{
268
+ data: {
269
+ client_id: string;
270
+ name: string;
271
+ confidential: boolean;
272
+ scope: string;
273
+ registration_tenant_id: string;
274
+ revoked_at: string;
275
+ created_at: string;
276
+ }[];
277
+ }>;
278
+ /**
279
+ * Reclamar un client confidencial CON su secreto: la única prueba de
280
+ * propiedad que el núcleo acepta (RFC 7591). El secreto no se guarda.
281
+ */
282
+ claim: (body: Body<"desarrolladorLink.claimClient">) => Promise<{
283
+ message: string;
284
+ }>;
285
+ };
286
+ get catalogo(): {
287
+ /**
288
+ * `GET /desarrollador/catalogo`: el catálogo propio (`perfil`, `currency`,
289
+ * `items`) y lo que se puede revender (`disponibles`: Pimia base, los
290
+ * módulos opcionales ofrecidos y las apps integradas activas).
291
+ */
292
+ get: () => Promise<{
293
+ data: {
294
+ perfil: {
295
+ nombre_comercial: string;
296
+ soporte_url: string | null;
297
+ soporte_email: string | null;
298
+ } | null;
299
+ currency: string | null;
300
+ contract_url: string | null;
301
+ items: {
302
+ kind: string;
303
+ slug: string;
304
+ name: string;
305
+ description: string | null;
306
+ price_cents: number;
307
+ price: string | null;
308
+ contract_url: string | null;
309
+ enabled: boolean;
310
+ }[];
311
+ disponibles: {
312
+ base: {
313
+ slug: string;
314
+ name: string;
315
+ description: string | null;
316
+ wholesale_price_cents: number | null;
317
+ wholesale_price: string | null;
318
+ }[];
319
+ modules: {
320
+ slug: string;
321
+ name: string;
322
+ description: string | null;
323
+ wholesale_price_cents: number | null;
324
+ wholesale_price: string | null;
325
+ }[];
326
+ apps: {
327
+ slug: string;
328
+ name: string;
329
+ description: string | null;
330
+ wholesale_price_cents: number | null;
331
+ wholesale_price: string | null;
332
+ }[];
333
+ };
334
+ };
335
+ }>;
336
+ /**
337
+ * `PUT /desarrollador/catalogo`: reemplaza el catálogo ENTERO. Es lo que
338
+ * el cliente del integrador ve en la pantalla de plan de su instancia en
339
+ * vez de los precios de Pimia; el precio es minorista y no toca el
340
+ * dinero de Pimia.
341
+ */
342
+ replace: (body: CatalogoDelIntegradorRequest) => Promise<{
343
+ data: {
344
+ perfil: {
345
+ nombre_comercial: string;
346
+ soporte_url: string | null;
347
+ soporte_email: string | null;
348
+ } | null;
349
+ currency: string | null;
350
+ contract_url: string | null;
351
+ items: {
352
+ kind: string;
353
+ slug: string;
354
+ name: string;
355
+ description: string | null;
356
+ price_cents: number;
357
+ price: string | null;
358
+ contract_url: string | null;
359
+ enabled: boolean;
360
+ }[];
361
+ disponibles: {
362
+ base: {
363
+ slug: string;
364
+ name: string;
365
+ description: string | null;
366
+ wholesale_price_cents: number | null;
367
+ wholesale_price: string | null;
368
+ }[];
369
+ modules: {
370
+ slug: string;
371
+ name: string;
372
+ description: string | null;
373
+ wholesale_price_cents: number | null;
374
+ wholesale_price: string | null;
375
+ }[];
376
+ apps: {
377
+ slug: string;
378
+ name: string;
379
+ description: string | null;
380
+ wholesale_price_cents: number | null;
381
+ wholesale_price: string | null;
382
+ }[];
383
+ };
384
+ };
385
+ }>;
386
+ };
387
+ get activaciones(): {
388
+ /**
389
+ * `GET /desarrollador/tenants/{slug}/activaciones`: la base (su asiento),
390
+ * los módulos y apps activos y lo que le cuestan al integrador al mes.
391
+ */
392
+ list: (tenantSlug: string) => Promise<{
393
+ data: {
394
+ base: {
395
+ active: boolean;
396
+ price_cents: number | null;
397
+ price: string | null;
398
+ plan: string | null;
399
+ };
400
+ items: {
401
+ kind: string;
402
+ slug: string;
403
+ name: string;
404
+ active_since: string;
405
+ price_cents: number | null;
406
+ price: string | null;
407
+ }[];
408
+ total_cents: number;
409
+ total: string;
410
+ };
411
+ }>;
412
+ /**
413
+ * `POST /desarrollador/tenants/{slug}/activaciones`: activar la base, un
414
+ * módulo o una app. La base es el asiento (la primera vez devuelve
415
+ * `checkout_url`); un módulo o una app exigen la base viva y se cobran
416
+ * al integrador como partida de su canal, sin prorrateo, en la factura
417
+ * del mes. Idempotente (`already_active`). Es lo que llama el webhook del
418
+ * integrador cuando su cliente le compra algo.
419
+ */
420
+ activate: (tenantSlug: string, body: ActivacionMayoristaRequest) => Promise<string | {
421
+ message: "Completa el alta de tu suscripci\u00F3n de canal para activar la licencia de este cliente." | "El cliente ya lo ten\u00EDa encendido por su cuenta: no se te cobra ni se le toca." | "Ya estaba activo." | "Activado: se cobra en tu canal desde la pr\u00F3xima factura.";
422
+ data: {
423
+ already_active: boolean;
424
+ inherited: boolean;
425
+ checkout_url: string | null;
426
+ quantity: number | null;
427
+ activation: {
428
+ [key: string]: unknown;
429
+ } | null;
430
+ };
431
+ }>;
432
+ /**
433
+ * `DELETE /desarrollador/tenants/{slug}/activaciones/{kind}/{item}`: dar de
434
+ * baja. El módulo se apaga y deja de cobrarse; la app se desinstala de
435
+ * todas las empresas; la base suelta el asiento.
436
+ */
437
+ deactivate: (tenantSlug: string, kind: "base" | "module" | "app", item: string) => Promise<{
438
+ message: string;
439
+ data: {
440
+ quantity: number | null;
441
+ };
442
+ }>;
443
+ };
444
+ get dominios(): {
445
+ /** `GET /desarrollador/dominios`: sus nombres de login, con el `upstream` y el bloque de proxy de cada uno. */
446
+ list: () => Promise<{
447
+ data: {
448
+ slug: string;
449
+ host: string;
450
+ enabled: boolean;
451
+ login_url: string;
452
+ upstream: string;
453
+ proxy_secret: string;
454
+ proxy: string;
455
+ created_at: string | null;
456
+ }[];
457
+ }>;
458
+ /**
459
+ * `POST /desarrollador/dominios`: declarar el nombre público que el
460
+ * integrador sirve (`host`, p. ej. `login.erpstudio.es`) y su etiqueta
461
+ * interna (`slug`). Pimia no emite certificados ni toca DNS: devuelve
462
+ * `upstream` (`https://login-<slug>.<central>`), a donde su proxy tiene
463
+ * que reenviar con `Host` interno y el nombre público en
464
+ * `X-Forwarded-Host`, y `proxy`, el bloque de Caddy listo para pegar.
465
+ */
466
+ declare: (body: IntegradorDominioRequest) => Promise<{
467
+ message: "Nombre declarado. Apunta tu proxy al upstream y reenv\u00EDa el nombre p\u00FAblico en X-Forwarded-Host.";
468
+ data: {
469
+ slug: string;
470
+ host: string;
471
+ enabled: boolean;
472
+ login_url: string;
473
+ upstream: string;
474
+ proxy_secret: string;
475
+ proxy: string;
476
+ created_at: string | null;
477
+ };
478
+ }>;
479
+ /** `DELETE /desarrollador/dominios/{slug}`: retirar el nombre; el host interno deja de contestar. */
480
+ remove: (slug: string) => Promise<{
481
+ message: string;
482
+ }>;
483
+ };
484
+ get tokens(): {
485
+ /** `GET /desarrollador/tokens`: los tokens de máquina vivos (nunca los de una sesión del panel). */
486
+ list: () => Promise<{
487
+ data: {
488
+ id: number;
489
+ name: string;
490
+ abilities: string[];
491
+ last_used_at: string | null;
492
+ expires_at: string | null;
493
+ created_at: string | null;
494
+ }[];
495
+ }>;
496
+ /**
497
+ * `POST /desarrollador/tokens`: acuñar un token de máquina, acotado a la
498
+ * habilidad `desarrollador` y nada más. El token en claro (`data.token`)
499
+ * se devuelve UNA vez: guárdalo en tu servidor.
500
+ */
501
+ create: (body: IntegradorTokenRequest) => Promise<{
502
+ message: "Token creado. Gu\u00E1rdalo ahora: no se vuelve a ense\u00F1ar.";
503
+ data: {
504
+ token: string;
505
+ id: number;
506
+ name: string;
507
+ abilities: unknown[];
508
+ last_used_at: string | null;
509
+ expires_at: string | null;
510
+ created_at: string | null;
511
+ };
512
+ }>;
513
+ /** `DELETE /desarrollador/tokens/{id}`: revocarlo. */
514
+ revoke: (id: number | string) => Promise<{
515
+ message: string;
516
+ }>;
517
+ };
518
+ get invitations(): {
519
+ list: () => Promise<{
520
+ data: {
521
+ id: string;
522
+ email: string;
523
+ company_name: string;
524
+ billing: string;
525
+ status: string;
526
+ expires_at: string;
527
+ accepted_tenant_id: string;
528
+ accepted_at: string;
529
+ created_at: string;
530
+ }[];
531
+ }>;
532
+ /** El cliente se registra y nace dueño; `billing` dice quién paga. */
533
+ create: (body: TenantInvitationRequest) => Promise<{
534
+ message: string;
535
+ data: {
536
+ id: string;
537
+ email: string;
538
+ company_name: string;
539
+ billing: string;
540
+ status: string;
541
+ expires_at: string;
542
+ accepted_tenant_id: string;
543
+ accepted_at: string;
544
+ created_at: string;
545
+ };
546
+ }>;
547
+ revoke: (id: number | string) => Promise<{
548
+ message: "Invitaci\u00F3n revocada.";
549
+ }>;
550
+ };
551
+ get billing(): {
552
+ /**
553
+ * `POST /billing/portal` (1.4.0): la URL del portal de Stripe de la
554
+ * cuenta del integrador —las facturas del canal y el método de pago
555
+ * viven allí, no en Pimia—. 404 si la cuenta no tiene cliente de Stripe
556
+ * todavía (nunca patrocinó a nadie).
557
+ */
558
+ portal: (body?: BillingPortalRequest) => Promise<{
559
+ data: {
560
+ portal_url: string;
561
+ };
562
+ }>;
563
+ };
564
+ get sponsorship(): {
565
+ /** Asumir la licencia de un cliente: un asiento más en el plan de canal. */
566
+ sponsor: (body: SponsorshipRequest) => Promise<string | {
567
+ message: "Completa el alta de tu suscripci\u00F3n de canal para activar el patrocinio." | "Asumida la licencia de la instancia.";
568
+ data: {
569
+ checkout_url: string | null;
570
+ };
571
+ }>;
572
+ /** Soltar un cliente patrocinado (queda en gracia y puede rescatarse). */
573
+ release: (tenantSlug: string) => Promise<string | {
574
+ message: "Has dejado de pagar la licencia de esta instancia.";
575
+ }>;
576
+ };
577
+ get tenants(): {
578
+ /**
579
+ * `GET /tenants/{slug}/users` (1.4.0): quién pertenece a la instancia
580
+ * —nombre, correo, rol, `is_owner`—. Es a quién se le puede traspasar:
581
+ * `transferOwnership` pide el `user_id` de alguien que ya está dentro.
582
+ */
583
+ users: (slug: string) => Promise<{
584
+ data: {
585
+ id: number;
586
+ name: string;
587
+ email: string;
588
+ role: string;
589
+ invited_at: string | null;
590
+ is_owner: boolean;
591
+ }[];
592
+ }>;
593
+ /** Traspasar la propiedad de la instancia a un usuario de la misma, antes de entregarla. */
594
+ transferOwnership: (slug: string, body: TransferOwnershipRequest) => Promise<{
595
+ message: string;
596
+ data: {
597
+ tenant_id: string;
598
+ owner_id: number;
599
+ billing_mode: "sponsor" | "self";
600
+ };
601
+ }>;
602
+ };
603
+ request<T = unknown>(path: string, options?: CentralRequestOptions): Promise<T>;
604
+ requestWithMeta<T = unknown>(path: string, options?: CentralRequestOptions): Promise<CentralResponseWithMeta<T>>;
605
+ private urlFor;
606
+ }
607
+ export {};