@tinya-code/gas-admin-ui 0.2.0-alpha.1 → 0.2.0-alpha.10

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 (48) hide show
  1. package/README.md +300 -2
  2. package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-CRTEx4f5.mjs → tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs} +12 -7
  3. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +1 -0
  4. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +107 -0
  5. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +1 -0
  6. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-DL15SFPt.mjs → tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs} +2 -2
  7. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-DL15SFPt.mjs.map → tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map} +1 -1
  8. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-BtZqPzmP.mjs → tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs} +2 -2
  9. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-BtZqPzmP.mjs.map → tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map} +1 -1
  10. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +122 -0
  11. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +1 -0
  12. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-pEOAeAzK.mjs → tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs} +2 -2
  13. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-pEOAeAzK.mjs.map → tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map} +1 -1
  14. package/fesm2022/{tinya-code-gas-admin-ui-file-B6_vveO5.mjs → tinya-code-gas-admin-ui-file-Dmxioog7.mjs} +3 -3
  15. package/fesm2022/{tinya-code-gas-admin-ui-file-B6_vveO5.mjs.map → tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map} +1 -1
  16. package/fesm2022/{tinya-code-gas-admin-ui-form-view-D1QLZWnG.mjs → tinya-code-gas-admin-ui-form-view-C709rc57.mjs} +13 -10
  17. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +1 -0
  18. package/fesm2022/{tinya-code-gas-admin-ui-image-lUe8nrUf.mjs → tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs} +3 -3
  19. package/fesm2022/{tinya-code-gas-admin-ui-image-lUe8nrUf.mjs.map → tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map} +1 -1
  20. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-B1rUNjlp.mjs → tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs} +4 -4
  21. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-B1rUNjlp.mjs.map → tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map} +1 -1
  22. package/fesm2022/{tinya-code-gas-admin-ui-list-CH7b7dKh.mjs → tinya-code-gas-admin-ui-list-C8ijJHAx.mjs} +2 -2
  23. package/fesm2022/{tinya-code-gas-admin-ui-list-CH7b7dKh.mjs.map → tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map} +1 -1
  24. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-Bg4cnf6N.mjs → tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs} +3 -3
  25. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-Bg4cnf6N.mjs.map → tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map} +1 -1
  26. package/fesm2022/{tinya-code-gas-admin-ui-relation-CDEuUpKU.mjs → tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs} +3 -3
  27. package/fesm2022/{tinya-code-gas-admin-ui-relation-CDEuUpKU.mjs.map → tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map} +1 -1
  28. package/fesm2022/{tinya-code-gas-admin-ui-render-registry-mG0BDGZI.mjs → tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs} +59 -29
  29. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +1 -0
  30. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +161 -0
  31. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +1 -0
  32. package/fesm2022/{tinya-code-gas-admin-ui-schema-catalog-Bb0WJRwc.mjs → tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs} +249 -58
  33. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +1 -0
  34. package/fesm2022/{tinya-code-gas-admin-ui-seed-C_tWI-Jr.mjs → tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs} +2 -2
  35. package/fesm2022/{tinya-code-gas-admin-ui-seed-C_tWI-Jr.mjs.map → tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map} +1 -1
  36. package/fesm2022/{tinya-code-gas-admin-ui-select-CL2kj7pM.mjs → tinya-code-gas-admin-ui-select-CDwX9yNO.mjs} +57 -53
  37. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +1 -0
  38. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-D700Obgh.mjs → tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs} +2 -2
  39. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-D700Obgh.mjs.map → tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map} +1 -1
  40. package/fesm2022/tinya-code-gas-admin-ui.mjs +98 -46
  41. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -1
  42. package/package.json +1 -1
  43. package/types/tinya-code-gas-admin-ui.d.ts +364 -49
  44. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-CRTEx4f5.mjs.map +0 -1
  45. package/fesm2022/tinya-code-gas-admin-ui-form-view-D1QLZWnG.mjs.map +0 -1
  46. package/fesm2022/tinya-code-gas-admin-ui-render-registry-mG0BDGZI.mjs.map +0 -1
  47. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-Bb0WJRwc.mjs.map +0 -1
  48. package/fesm2022/tinya-code-gas-admin-ui-select-CL2kj7pM.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinya-code/gas-admin-ui",
3
- "version": "0.2.0-alpha.1",
3
+ "version": "0.2.0-alpha.10",
4
4
  "description": "Motor de render Angular para admin CRUD: declará ResourceSchema y el motor dibuja vistas, campos y widgets.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -2,7 +2,7 @@ import * as i0 from "@angular/core";
2
2
  import { EnvironmentProviders, InjectionToken, Provider, Signal, TemplateRef, Type } from "@angular/core";
3
3
  import { CanActivateFn, Route, Router, Routes } from "@angular/router";
4
4
  import { Observable } from "rxjs";
5
- import { HttpEvent, HttpInterceptorFn } from "@angular/common/http";
5
+ import { HttpClient, HttpEvent, HttpHeaders, HttpInterceptorFn, HttpParams } from "@angular/common/http";
6
6
  import { CdkDragDrop } from "@angular/cdk/drag-drop";
7
7
  import { LucideIcon } from "@lucide/angular";
8
8
  import { FieldTree, PathKind, SchemaFn, SchemaPath, SchemaPathRules } from "@angular/forms/signals";
@@ -98,6 +98,9 @@ export interface TextField extends FieldBase<string> {
98
98
  maxLength?: number;
99
99
  pattern?: string;
100
100
  }
101
+ export interface ColorField extends FieldBase<string> {
102
+ type: 'color';
103
+ }
101
104
  export interface TextareaField extends FieldBase<string> {
102
105
  type: 'textarea';
103
106
  rows?: number;
@@ -169,6 +172,55 @@ export type SelectField = FieldBase<string> & {
169
172
  export type MultiselectField = FieldBase<string[]> & {
170
173
  type: 'multiselect';
171
174
  } & SelectData;
175
+ /**
176
+ * Desplegable nativo (`<select>`) que SIEMPRE se renderiza como lista
177
+ * desplegable, independientemente de cuántas opciones tenga.
178
+ *
179
+ * A diferencia de `select` (que usa radio buttons para 2–4 opciones
180
+ * estáticas por la guía de control §1), este tipo nunca muta a radios.
181
+ * Úsalo cuando necesites un dropdown verdadero aunque tenga pocas opciones.
182
+ */
183
+ export interface DropdownField extends FieldBase<string> {
184
+ type: 'dropdown';
185
+ options: SelectOption[];
186
+ }
187
+ /**
188
+ * Opción de un selector visual enriquecido (`rich-select`).
189
+ *
190
+ * Además de `value`/`label`, cada opción puede declarar una `preview` con
191
+ * HTML seguro que el motor renderiza como tarjeta visual (badge, chip con
192
+ * icono, color swatch, etc.). El HTML viaja en el schema y el motor lo
193
+ * pinta tal cual tras pasar por el sanitizador de Angular.
194
+ */
195
+ export interface RichSelectOption {
196
+ value: string;
197
+ label: string;
198
+ disabled?: boolean;
199
+ /** HTML seguro para pintar como preview de la opción (ej. `<span class="chip">…</span>`). */
200
+ preview?: string;
201
+ }
202
+ /**
203
+ * Selector visual enriquecido: cada opción se renderiza como tarjeta con su
204
+ * `preview` HTML en vez de un `<select>` nativo o radio buttons planos.
205
+ * Sólo opciones estáticas (sin `source`).
206
+ */
207
+ export interface RichSelectField extends FieldBase<string> {
208
+ type: 'rich-select';
209
+ options: RichSelectOption[];
210
+ /**
211
+ * Ancho de cada tarjeta de opción en la grilla (1–12), igual que `width`
212
+ * de un campo. Por defecto `4` (3 tarjetas por fila en escritorio).
213
+ * Úsalo para opciones con preview larga (menos tarjetas por fila) o
214
+ * preview corta (más tarjetas).
215
+ */
216
+ cardWidth?: GridWidth;
217
+ /**
218
+ * Cuántas tarjetas mostrar por fila en el punto de ruptura `lg`.
219
+ * Sobrescribe el cálculo automático basado en `cardWidth`.
220
+ * Ejemplo: `2` obliga a 2 columnas grandes sin importar el ancho.
221
+ */
222
+ cardCols?: 1 | 2 | 3 | 4 | 5 | 6;
223
+ }
172
224
  export interface UrlField extends FieldBase<string> {
173
225
  type: 'url';
174
226
  }
@@ -322,6 +374,12 @@ export interface WidgetBase {
322
374
  help?: string;
323
375
  /** Ancho en la grilla del panel (1–12), igual que un campo. */
324
376
  width?: GridWidth;
377
+ /**
378
+ * Ancho en la grilla en mobile (1–12). Por defecto `12` (ancho completo,
379
+ * apilado). Declararlo permite controlar el layout responsive en pantallas
380
+ * chicas sin tocar el ancho de escritorio (`width`).
381
+ */
382
+ mobileWidth?: GridWidth;
325
383
  /**
326
384
  * De dónde saca sus datos (F8 · R6.5).
327
385
  *
@@ -400,7 +458,7 @@ export interface StatusProgressWidget extends WidgetBase {
400
458
  }
401
459
  export type WidgetSchema = MetricCardWidget | BarChartWidget | ChartLineWidget | RecordListWidget | QuickActionsWidget | StatusProgressWidget;
402
460
  export type WidgetType = WidgetSchema['type'];
403
- export type FieldSchema = TextField | TextareaField | SlugField | NumberField | CurrencyField | BooleanField | SelectField | MultiselectField | UrlField | EmailField | PhoneField | DateField | TimeField | ReadonlyTextField | RelationField | ImageField | FileField | GroupField | ListField | StringListField | KeyValueField;
461
+ export type FieldSchema = TextField | TextareaField | SlugField | NumberField | CurrencyField | BooleanField | SelectField | MultiselectField | RichSelectField | DropdownField | UrlField | EmailField | PhoneField | DateField | TimeField | ReadonlyTextField | RelationField | ImageField | FileField | GroupField | ListField | StringListField | KeyValueField | ColorField;
404
462
  export type FieldType = FieldSchema['type'];
405
463
  export type ListColumnFormat = 'text' | 'badge' | 'thumbnail' | 'boolean' | 'number' | 'currency' | 'date';
406
464
  export interface ListColumn {
@@ -496,8 +554,19 @@ export interface ResourcePermissions {
496
554
  }
497
555
  /**
498
556
  * Rutas de la API relativas a `API_URL` (base.md §3, api.md §6 Opción A).
499
- * Pueden incluir el marcador `{key}`. Los singleton solo usan
500
- * `get` / `update`.
557
+ *
558
+ * Marcadores que un path acepta — se resuelven ANTES de despachar la petición
559
+ * (y para ambos drivers):
560
+ *
561
+ * - `{key}` — clave del registro actual; sólo aplica a las operaciones con
562
+ * clave (`get`, `update`, `remove`). Si llega sin resolver, es error.
563
+ * - `{<fuente>}` / `{<fuente>.<ruta.anidada>}` — valor de una fuente de
564
+ * contexto: las construidas `auth` (sesión) y `config` (config global), o
565
+ * cualquier fuente registrada en `provideGasAdmin({ valueSources })`.
566
+ *
567
+ * El valor se codifica como segmento de URL. Un marcador sin valor, o con una
568
+ * fuente no registrada, lanza un error que nombra el path y el marcador: nunca
569
+ * se envía un segmento vacío. Los singleton solo usan `get` / `update`.
501
570
  */
502
571
  export interface ResourceEndpoints {
503
572
  list?: string;
@@ -505,6 +574,79 @@ export interface ResourceEndpoints {
505
574
  create?: string;
506
575
  update?: string;
507
576
  remove?: string;
577
+ /**
578
+ * Declaración opcional de cómo unwrappear la respuesta de esta operación.
579
+ * Cuando se declara, el motor extrae el contenido de `dataKey` antes de
580
+ * devolverlo al consumidor (ej.: `{ data: [...] }` → `[...]`).
581
+ * Si no se declara, el motor mantiene el comportamiento actual:
582
+ * respuesta pasada tal cual (array plano, `{ items }`, o objeto crudo).
583
+ * Esta declaración es por operación; si se desea un valor global, usarlo
584
+ * en cada operación o considerar un default a nivel de config en futuro.
585
+ */
586
+ responseUnwrap?: ResponseUnwrap;
587
+ }
588
+ /**
589
+ * Cómo el motor debe desempaquetar la respuesta HTTP antes de entregarla.
590
+ * Opcional y opt-in: schemas sin este campo siguen el contrato actual.
591
+ *
592
+ * - `dataKey`: clave en el body que contiene el payload real (ej. `data`,
593
+ * `items`, `result`). Si el body no tiene esta clave, se devuelve `undefined`
594
+ * para lista / objeto vacío según corresponda, y se lanza error en modo
595
+ * estricto si se configura.
596
+ * - `metaKey`: clave con metadatos (paginación, total, etc.). Se ignora en
597
+ * el valor devuelto al consumidor pero puede leerse si el motor la expone
598
+ * en futuro.
599
+ * - `apply`: `'list' | 'get' | 'create' | 'update' | 'remove' | 'all'`.
600
+ * Qué operaciones aplican el unwrap. Por defecto `'all'` cuando se declara.
601
+ */
602
+ export interface ResponseUnwrap {
603
+ dataKey: string;
604
+ metaKey?: string;
605
+ apply?: 'list' | 'get' | 'create' | 'update' | 'remove' | 'all';
606
+ /**
607
+ * Claves de metadata de paginación dentro del sobre. Usadas por
608
+ * `ApiService.listPage()` para armar `PageResult<T>`. Opcional: sin
609
+ * declaración, el motor intenta auto-detectar claves comunes
610
+ * (`nextCursor`, `total`, `hasMore`) y, si no hay, `hasMore = false`.
611
+ */
612
+ pagination?: {
613
+ /** Clave del cursor siguiente dentro de `meta` (o del body si no hay `metaKey`). */
614
+ cursorKey?: string;
615
+ /** Clave del total de registros. */
616
+ totalKey?: string;
617
+ /** Clave del flag booleano "hay más páginas". */
618
+ hasMoreKey?: string;
619
+ };
620
+ }
621
+ /**
622
+ * Método HTTP soportado por endpoints personalizados.
623
+ * Declarado aquí (y no en api.service.ts) para evitar ciclos de importación
624
+ * entre modelos y servicios.
625
+ */
626
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
627
+ /**
628
+ * Definición de un endpoint personalizado más allá de los cinco estándar
629
+ * (list/get/create/update/remove). Permite al consumidor declarar rutas con
630
+ * verbos arbitrarios, segmentos extra, payloads JSON o multipart, y unwrap de
631
+ * respuesta propio.
632
+ *
633
+ * Ejemplo:
634
+ * name: 'recalculate-ranks'
635
+ * method: 'POST'
636
+ * path: '/admin/businesses/{businessId}/ranks/recalculate'
637
+ * payloadMode: 'json'
638
+ */
639
+ export interface CustomEndpointDefinition {
640
+ /** Nombre único del endpoint dentro del recurso. Se usa para invocarlo. */
641
+ name: string;
642
+ /** Método HTTP de la petición. */
643
+ method: HttpMethod;
644
+ /** Ruta relativa a `API_URL`. Puede incluir marcadores {key}, {auth.user.id}, etc. */
645
+ path: string;
646
+ /** Modo de payload: 'json' (default) o 'multipart' para FormData. */
647
+ payloadMode?: 'json' | 'multipart';
648
+ /** Unwrap de respuesta override para este endpoint. Si no se declara, se usa el del recurso. */
649
+ responseUnwrap?: ResponseUnwrap;
508
650
  }
509
651
  export interface ResourceSchema {
510
652
  /** Identificador interno (`categories`). */
@@ -522,6 +664,8 @@ export interface ResourceSchema {
522
664
  keyField: string;
523
665
  /** Campo que se muestra como nombre del registro. */
524
666
  titleField: string;
667
+ /** Endpoints personalizados más allá de list/get/create/update/remove. */
668
+ customEndpoints?: Record<string, CustomEndpointDefinition>;
525
669
  /** Si el listado se reordena (requiere `positionField`). */
526
670
  sortable?: boolean;
527
671
  positionField?: string;
@@ -799,24 +943,99 @@ export interface AuthSchema {
799
943
  firebase?: FirebaseAuthConfig;
800
944
  }
801
945
  /**
802
- * Narrow surface the library uses from Firebase Auth.
803
- * Implemented by the consumer app; injected via FIREBASE_AUTH_ADAPTER token.
804
- * Keeping the interface thin avoids coupling to any specific Firebase version.
946
+ * URL base de la API (baseapi §2.3): es la URL directa del Web App de Apps
947
+ * Script; ahí se hace el `POST` del envelope `{ token, method, path, payload }`.
948
+ * OBLIGATORIO proporcionarlo en `app.config.ts`:
949
+ * `{ provide: API_URL, useValue: 'https://…' }` (Fase 3). Vacío = mismo origen.
950
+ */
951
+ export declare const API_URL: InjectionToken<string>;
952
+ /**
953
+ * Superficie mínima que `gas-admin-ui` usa de Firebase Auth.
954
+ * Implementada por la aplicación consumidora; inyectada vía `FIREBASE_AUTH_ADAPTER`.
955
+ * Mantener la interfaz fina evita acoplar la librería a una versión específica de Firebase.
805
956
  */
806
957
  interface FirebaseAuthAdapter {
807
958
  /**
808
- * Opens a Google sign-in popup and resolves to the Firebase ID token.
809
- * Rejects on cancellation or error.
959
+ * Abre un popup de Google sign-in y resuelve al ID token de Firebase.
960
+ * Rechaza si el usuario cancela o hay error.
810
961
  */
811
962
  signInWithGoogle(): Promise<string>;
812
963
  /**
813
- * Signs in with email and password via Firebase and resolves to the ID token.
814
- * Used when provider === 'password' AND firebase is configured (Firebase email auth).
964
+ * Inicia sesión con email y contraseña vía Firebase y resuelve al ID token.
965
+ * Se usa cuando provider === 'password' Y firebase está configurado (Firebase email auth).
815
966
  */
816
967
  signInWithEmailAndPassword(email: string, password: string): Promise<string>;
817
- /** Signs out from Firebase (called alongside the app session clear). */
968
+ /**
969
+ * Cierra sesión en Firebase (llamado junto al clear de la sesión de la app).
970
+ */
818
971
  signOut(): Promise<void>;
972
+ /**
973
+ * Obtiene un ID token de Firebase FRESCO para usarlo en la cabecera
974
+ * `Authorization: Bearer <token>` de las peticiones REST.
975
+ *
976
+ * OPCIONAL: si el adaptador no lo implementa, `gas-admin-ui` envía el token
977
+ * de sesión almacenado tal cual (comportamiento actual). Implementarlo
978
+ * permite refrescar el ID token antes de cada petición REST, evitando
979
+ * errores "ID token inválido" tras la expiración de Firebase (1 hora).
980
+ *
981
+ * El adaptador decide cuándo refrescar (p. ej. `getIdToken(true)` de Firebase
982
+ * SDK fuerza la renovación; sin argumento devuelve el cacheado si aún es válido).
983
+ */
984
+ getIdToken?(): Promise<string>;
819
985
  }
986
+ /**
987
+ * Token DI opcional para el adaptador de Firebase Auth.
988
+ * Proporcionado por la aplicación consumidora cuando usa proveedores federados o Firebase Auth.
989
+ */
990
+ export declare const FIREBASE_AUTH_ADAPTER: InjectionToken<FirebaseAuthAdapter>;
991
+ /**
992
+ * Driver de transporte HTTP del motor.
993
+ *
994
+ * Vive acá (capa de infraestructura) y NO en `ResourceSchema` (capa de dominio)
995
+ * porque es una decisión de red, no de modelo de datos. Cambiar el driver
996
+ * no debería requerir tocar ningún schema de recursos.
997
+ *
998
+ * - `'gas'` (default): tunnel `POST text/plain` con envelope
999
+ * `{ token, method, path, payload }` — contrato de Google Apps Script.
1000
+ * - `'rest'`: verbos HTTP reales (`GET`, `POST`, `PUT`, `DELETE`) con
1001
+ * `Authorization: Bearer <token>` en cabeceras y params en la URL (GET/DELETE)
1002
+ * o en el body JSON (POST/PUT/PATCH).
1003
+ *
1004
+ * Se fija en `provideGasAdmin({ driver })`. Sin declaración explícita el
1005
+ * motor usa `'gas'` — cero impacto para consumidores existentes.
1006
+ */
1007
+ export declare const API_DRIVER: InjectionToken<"gas" | "rest">;
1008
+ /**
1009
+ * Registro de FUENTES DE CONTEXTO para marcadores en paths de endpoints.
1010
+ *
1011
+ * Cada clave es el nombre de una fuente — `{ <fuente> }` o
1012
+ * `{ <fuente>.<ruta.anidada> }` dentro de un path declarado en
1013
+ * `ResourceEndpoints` — y cada valor es un getter sin argumentos que devuelve
1014
+ * el dato ACTUAL en el momento de la petición (no al registrar).
1015
+ *
1016
+ * El motor ya aporta dos fuentes construidas y éstas NO se declaran acá:
1017
+ *
1018
+ * - `auth` → la sesión que dueña `AuthService` (usuario y token).
1019
+ * - `config` → el mapa global que puebla `ConfigService` desde los singletons.
1020
+ *
1021
+ * Este registro es sólo para fuentes que aporta el consumidor, para que el
1022
+ * motor no conozca nombres de dominio: qué campo se lee lo decide el schema,
1023
+ * no el engine.
1024
+ *
1025
+ * Un marcador que apunte a una fuente no registrada, o a un valor ausente,
1026
+ * revienta con un error que nombra el path y el marcador — jamás se manda un
1027
+ * segmento vacío al backend.
1028
+ */
1029
+ type ValueSources = Readonly<Record<string, () => unknown>>;
1030
+ export declare const VALUE_SOURCES: InjectionToken<Readonly<Record<string, () => unknown>>>;
1031
+ /**
1032
+ * Unwrap de respuesta global por defecto (`GasAdminConfig.defaultResponseUnwrap`).
1033
+ *
1034
+ * Se fusiona con la declaración `endpoint.responseUnwrap` de cada recurso:
1035
+ * la del recurso tiene prioridad en las claves que declara. Sin token, sólo
1036
+ * aplica lo declarado por recurso — cero impacto en schemas existentes.
1037
+ */
1038
+ export declare const DEFAULT_RESPONSE_UNWRAP: InjectionToken<ResponseUnwrap>;
820
1039
  /** Fija el catálogo (uso interno de `provideGasAdmin`; testable directo). */
821
1040
  export declare function setSchemaCatalog(schemas: readonly ResourceSchema[], auth?: AuthSchema): void;
822
1041
  /** Busca un schema por `id`; `undefined` si no existe. */
@@ -842,6 +1061,23 @@ interface GasAdminConfig {
842
1061
  * Sin declaración el motor usa `'gas'` — cero impacto en consumidores existentes.
843
1062
  */
844
1063
  driver?: 'gas' | 'rest';
1064
+ /**
1065
+ * Fuentes de contexto que el consumidor aporta para marcadores en paths de
1066
+ * endpoints: `{ <fuente> }` / `{ <fuente>.<ruta> }` se resuelven en el
1067
+ * momento de la petición contra este registro más las fuentes construidas
1068
+ * `auth` (sesión) y `config` (config global).
1069
+ *
1070
+ * Opcional: sin él, sólo existen `auth` y `config`. Declararlo no cambia
1071
+ * ningún path que no lleve marcadores — cero impacto en schemas existentes.
1072
+ */
1073
+ valueSources?: ValueSources;
1074
+ /**
1075
+ * Unwrap de respuesta global por defecto. Se fusiona con la declaración
1076
+ * `endpoint.responseUnwrap` de cada recurso (la del recurso tiene prioridad
1077
+ * en claves declaradas). Opcional: sin él, sólo aplica lo declarado por
1078
+ * recurso — cero impacto en schemas existentes (R2).
1079
+ */
1080
+ defaultResponseUnwrap?: ResponseUnwrap;
845
1081
  }
846
1082
  /**
847
1083
  * Wiring completo del motor: catálogo + HTTP con el interceptor de
@@ -926,34 +1162,22 @@ export declare class ApiError extends Error {
926
1162
  */
927
1163
  export declare function renewedTokenOf(body: unknown): string | null;
928
1164
  /**
929
- * URL base de la API (baseapi §2.3): es la URL directa del Web App de Apps
930
- * Script; ahí se hace el `POST` del envelope `{ token, method, path, payload }`.
931
- * OBLIGATORIO proporcionarlo en `app.config.ts`:
932
- * `{ provide: API_URL, useValue: 'https://…' }` (Fase 3). Vacío = mismo origen.
933
- */
934
- export declare const API_URL: InjectionToken<string>;
935
- /**
936
- * Token DI opcional para el adaptador de Firebase Auth.
937
- * Proporcionado por la aplicación consumidora cuando usa proveedores federados o Firebase Auth.
938
- */
939
- export declare const FIREBASE_AUTH_ADAPTER: InjectionToken<FirebaseAuthAdapter>;
940
- /**
941
- * Driver de transporte HTTP del motor.
942
- *
943
- * Vive acá (capa de infraestructura) y NO en `ResourceSchema` (capa de dominio)
944
- * porque es una decisión de red, no de modelo de datos. Cambiar el driver
945
- * no debería requerir tocar ningún schema de recursos.
946
- *
947
- * - `'gas'` (default): tunnel `POST text/plain` con envelope
948
- * `{ token, method, path, payload }` — contrato de Google Apps Script.
949
- * - `'rest'`: verbos HTTP reales (`GET`, `POST`, `PUT`, `DELETE`) con
950
- * `Authorization: Bearer <token>` en cabeceras y params en la URL (GET/DELETE)
951
- * o en el body JSON (POST/PUT/PATCH).
1165
+ * Resultado de una página para `ApiService.listPage()`.
952
1166
  *
953
- * Se fija en `provideGasAdmin({ driver })`. Sin declaración explícita el
954
- * motor usa `'gas'` — cero impacto para consumidores existentes.
1167
+ * El backend envía la metadata de paginación dentro del sobre (típicamente
1168
+ * en `meta`); el motor la extrae según la declaración `responseUnwrap.pagination`
1169
+ * y la entrega en este shape. `list()` NO usa este tipo: sigue devolviendo `T[]`.
955
1170
  */
956
- export declare const API_DRIVER: InjectionToken<"gas" | "rest">;
1171
+ export interface PageResult<T> {
1172
+ /** Registros de la página actual. */
1173
+ items: T[];
1174
+ /** Cursor para pedir la página siguiente; `null` cuando no hay más. */
1175
+ nextCursor: string | null;
1176
+ /** Total de registros del conjunto, si el backend lo informa; `0` si no. */
1177
+ total: number;
1178
+ /** `true` si hay al menos una página más. `false` sin cursor informativo. */
1179
+ hasMore: boolean;
1180
+ }
957
1181
  /**
958
1182
  * Guards de sesión (auth.md §5.3).
959
1183
  *
@@ -1062,6 +1286,40 @@ export declare class NotificationService {
1062
1286
  }
1063
1287
  /** Métodos que entiende el envelope del backend (baseapi §2.3). */
1064
1288
  type EnvelopeMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1289
+ /** Dependencias del transporte, aportadas por la fachada (no hay singleton acá). */
1290
+ interface TransportDeps {
1291
+ http: HttpClient;
1292
+ /** URL base (`API_URL`): el Web App de Apps Script o el origen REST. */
1293
+ apiUrl: string;
1294
+ /** Getter del token de SESIÓN, leído en CADA petición (R2.8). */
1295
+ sessionToken: () => string | null;
1296
+ /**
1297
+ * Refresco opcional de ID token (Fase Firebase): si existe, se llama antes
1298
+ * de armar la cabecera Authorization. Un rechazo viaja por el canal de
1299
+ * errores — nunca se manda una petición con token inválido.
1300
+ */
1301
+ getIdToken?: () => Promise<string>;
1302
+ }
1303
+ /** `POST text/plain` a `apiUrl` con el envelope GAS. */
1304
+ declare function sendGas<T>(deps: TransportDeps, path: string, method: EnvelopeMethod, payload?: Record<string, unknown> | FormData, multipart?: boolean): Observable<T>;
1305
+ /**
1306
+ * Verbo HTTP real con `Authorization: Bearer`. Si el adaptador provee
1307
+ * `getIdToken()`, se resuelve el token fresco ANTES de la cabecera; si no,
1308
+ * se usa el de sesión almacenado (fallback). Cualquier error de `getIdToken()`
1309
+ * se propaga por el canal de errores.
1310
+ */
1311
+ declare function sendRest<T>(deps: TransportDeps, path: string, method: EnvelopeMethod, payload?: Record<string, unknown> | FormData, multipart?: boolean): Observable<T>;
1312
+ /** Despacho REST concreto sobre una URL ya construida. */
1313
+ declare function doRestRequest<T>(deps: TransportDeps, url: string, method: EnvelopeMethod, payload: Record<string, unknown> | FormData | undefined, headers?: HttpHeaders, multipart?: boolean): Observable<T>;
1314
+ /** `${apiUrl}${path}` normalizada: sin dobles slashes ni path sin slash inicial. */
1315
+ declare function buildUrl(apiUrl: string, path: string): string;
1316
+ /**
1317
+ * Convierte un payload en HttpParams para peticiones GET y DELETE en modo REST.
1318
+ * Si un valor es un objeto plano (ej: `{ view: 'calendar', params: { orden: 'es' } }`),
1319
+ * lo aplana un nivel para que sus claves viajen como query params reales
1320
+ * en lugar de serializarse como `params=[object Object]`.
1321
+ */
1322
+ declare function toHttpParams(payload?: Record<string, unknown>): HttpParams;
1065
1323
  /**
1066
1324
  * CRUD genérico sobre el contrato v2 (baseapi §2.3) con soporte dual-driver:
1067
1325
  *
@@ -1077,6 +1335,28 @@ export declare class ApiService {
1077
1335
  private readonly apiUrl;
1078
1336
  private readonly auth;
1079
1337
  private readonly driver;
1338
+ /** Fuentes que aporta el consumidor (`provideGasAdmin({ valueSources })`). */
1339
+ private readonly valueSources;
1340
+ /** Adaptador opcional de Firebase Auth para refrescar el ID token antes de peticiones REST. */
1341
+ private readonly firebaseAuthAdapter;
1342
+ /**
1343
+ * Acceso PEREZOSO a `ConfigService` (R2 · plan doc/context-sources-plan.md).
1344
+ *
1345
+ * `config` es fuente construida del motor, pero inyectarla en el constructor
1346
+ * formaría un ciclo: `ConfigService` inyecta `ApiService` (opcional) para
1347
+ * cargar los singletons ⇒ NG0200 en el arranque. Se pide en el momento de la
1348
+ * petición, cuando ambos servicios ya están vivos. Si no existe (TestBed
1349
+ * parcial), devuelve `null` y el resolver falla ruidoso con «sin valor».
1350
+ */
1351
+ private readonly injector;
1352
+ /** Unwrap global por defecto (`GasAdminConfig.defaultResponseUnwrap`); opcional. */
1353
+ private readonly defaultUnwrap;
1354
+ /**
1355
+ * Declaración de unwrap EFECTIVA para un endpoint: fusión del default
1356
+ * global con la declaración por recurso (recurso gana en las claves que
1357
+ * declara). Sin ninguna de las dos ⇒ `undefined` (comportamiento actual).
1358
+ */
1359
+ private effectiveUnwrap;
1080
1360
  /**
1081
1361
  * Listado. El backend envuelve la respuesta cuando la operación declara
1082
1362
  * `shape.envelope: 'list'` (F5) ⇒ `{ items, nextCursor, total }` (R6.4);
@@ -1092,6 +1372,21 @@ export declare class ApiService {
1092
1372
  * llamada es la de siempre — la lectura llana del recurso.
1093
1373
  */
1094
1374
  list<T>(endpoint: ResourceEndpoints, payload?: Record<string, unknown>): Observable<T[]>;
1375
+ /**
1376
+ * Listado paginado OPT-IN (plan doc/engine-refactor-plan.md · Fase 3).
1377
+ *
1378
+ * Misma petición que `list()` pero devuelve `PageResult<T>` con la metadata
1379
+ * de paginación que el backend envía en el sobre (`{ items, nextCursor,
1380
+ * total }` o dentro de `meta` con unwrap declarado). Las claves se leen
1381
+ * según `responseUnwrap.pagination` (cursorKey/totalKey/hasMoreKey) o se
1382
+ * auto-detectan (`nextCursor`, `total`, `hasMore`).
1383
+ *
1384
+ * `payload` acepta normalmente `{ cursor, limit }` para pedir la página
1385
+ * siguiente; viaja como cualquier otro payload (query params en GET REST,
1386
+ * campo `payload` del envelope en GAS). `list()` no cambia: sigue
1387
+ * devolviendo `T[]` (R11).
1388
+ */
1389
+ listPage<T>(endpoint: ResourceEndpoints, payload?: Record<string, unknown>): Observable<PageResult<T>>;
1095
1390
  /** `key` solo aplica a colecciones (`{key}` en la plantilla); los singleton no la llevan. */
1096
1391
  get<T>(endpoint: ResourceEndpoints, key?: string): Observable<T>;
1097
1392
  create<T>(endpoint: ResourceEndpoints, payload: Record<string, unknown>): Observable<T>;
@@ -1103,10 +1398,34 @@ export declare class ApiService {
1103
1398
  * `/admin/upload-signature`, etc.
1104
1399
  */
1105
1400
  request<T>(method: EnvelopeMethod, path: string | undefined, payload?: Record<string, unknown>): Observable<T>;
1401
+ /**
1402
+ * Invoca un endpoint personalizado declarado por el consumidor.
1403
+ *
1404
+ * Acepta bien la definición directa (`CustomEndpointDefinition`) o un schema
1405
+ * + nombre (resuelve en `schema.customEndpoints`). El despacho respeta el
1406
+ * método, la ruta, el modo de payload (json/multipart) y el unwrap propios
1407
+ * de la declaración.
1408
+ */
1409
+ invokeCustomEndpoint<T>(definitionOrSchema: CustomEndpointDefinition | ResourceSchema, nameOrPayload?: string | Record<string, unknown> | FormData, payload?: Record<string, unknown> | FormData, key?: string): Observable<T>;
1410
+ /** Alias corto de `invokeCustomEndpoint`. */
1411
+ invokeCustom<T>(schemaOrDefinition: ResourceSchema | CustomEndpointDefinition, nameOrPayload?: string | Record<string, unknown> | FormData, payload?: Record<string, unknown> | FormData, key?: string): Observable<T>;
1412
+ /** Alias adicional por compatibilidad. */
1413
+ custom<T>(schemaOrDefinition: ResourceSchema | CustomEndpointDefinition, nameOrPayload?: string | Record<string, unknown> | FormData, payload?: Record<string, unknown> | FormData, key?: string): Observable<T>;
1106
1414
  private send;
1107
- private sendGas;
1108
- private sendRest;
1109
- private buildUrl;
1415
+ /**
1416
+ * Dependencias del transporte (Fase 4): la fachada sólo arma este objeto y
1417
+ * delega en `transport.ts`. El token de sesión y el ID token fresco se
1418
+ * resuelven vía getters en el momento de la petición (R2.8 / refresh).
1419
+ */
1420
+ private transportDeps;
1421
+ /**
1422
+ * Fuentes de contexto de ESTA petición: getters, no valores — se evalúan
1423
+ * sólo si el path trae marcadores.
1424
+ *
1425
+ * Las construidas del motor (`auth`, `config`) van ÚLTIMAS: el motor es
1426
+ * dueño de sus fuentes y el registro del consumidor no las pisa.
1427
+ */
1428
+ private buildSources;
1110
1429
  private withKey;
1111
1430
  static ɵfac: i0.ɵɵFactoryDeclaration<ApiService, never>;
1112
1431
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
@@ -1204,10 +1523,6 @@ export declare class AuthService {
1204
1523
  * que deja una sesión muerta en el servidor.
1205
1524
  */
1206
1525
  logout(): Observable<void>;
1207
- /** Revoca las sesiones del usuario en todos sus dispositivos. */
1208
- logoutAll(): Observable<{
1209
- effectiveWithinSeconds: number;
1210
- }>;
1211
1526
  /** Revalida la sesión restaurada contra `/auth/me`; sin token ⇒ sin viaje. */
1212
1527
  restore(): Observable<void>;
1213
1528
  /**
@@ -1454,14 +1769,14 @@ export declare class Sidebar {
1454
1769
  private readonly auth;
1455
1770
  private readonly notifications;
1456
1771
  readonly isAuthenticated: import("@angular/core").Signal<boolean>;
1457
- readonly closingAllSessions: import("@angular/core").WritableSignal<boolean>;
1772
+ readonly loggingOut: import("@angular/core").WritableSignal<boolean>;
1458
1773
  /**
1459
1774
  * R2.10 — el menú se corta dos veces: `isVisible` (permisos declarados
1460
1775
  * en el schema) y `canAccess` (auth.md §5.3, `access` de la sesión).
1461
1776
  * Sólo UX: filtrar acá no protege nada, el back re-verifica en R2.6.
1462
1777
  */
1463
1778
  readonly menuItems: import("@angular/core").Signal<MenuEntry[]>;
1464
- closeAllSessions(): Promise<void>;
1779
+ closeSession(): Promise<void>;
1465
1780
  static ɵfac: i0.ɵɵFactoryDeclaration<Sidebar, never>;
1466
1781
  static ɵcmp: i0.ɵɵComponentDeclaration<Sidebar, "app-sidebar", never, {
1467
1782
  "open": {
@@ -2949,7 +3264,7 @@ export declare class Toast {
2949
3264
  * aserción en el **mismo archivo**: añadir un tipo toca una carpeta y una
2950
3265
  * línea de este manifiesto, y el host no se entera.
2951
3266
  */
2952
- export declare const SUPPORTED_FIELD_TYPE_LIST: readonly ["text", "textarea", "slug", "number", "currency", "boolean", "select", "multiselect", "url", "email", "phone", "date", "time", "readonly-text", "group", "list", "string-list", "key-value", "relation", "image", "file"];
3267
+ export declare const SUPPORTED_FIELD_TYPE_LIST: readonly ["text", "textarea", "slug", "number", "currency", "boolean", "select", "multiselect", "url", "email", "phone", "date", "time", "readonly-text", "group", "list", "string-list", "key-value", "relation", "image", "file", "color", "rich-select", "dropdown"];
2953
3268
  /**
2954
3269
  * Manifiesto de descriptores de campo (R3.3 · registro único de render).
2955
3270
  *
@@ -3186,4 +3501,4 @@ export declare function actionsFor(surface: ActionSurface): ActionDescriptor[];
3186
3501
  export declare function actionType(action: ResourceAction): string;
3187
3502
  /** Tipos registrados. Sólo para comprobaciones y tests. */
3188
3503
  export declare function actionTypes(): string[];
3189
- export { type ComponentRenderKind, type FirebaseAuthAdapter, type GasAdminConfig, type RenderKind, type ResolvedShare, type ValidatorName, get as getRenderDescriptor, isSupported as isRenderSupported, register as registerRenderDescriptor, unregister as unregisterRenderDescriptor };
3504
+ export { type ComponentRenderKind, type FirebaseAuthAdapter, type GasAdminConfig, type RenderKind, type ResolvedShare, type ValidatorName, type ValueSources, get as getRenderDescriptor, isSupported as isRenderSupported, register as registerRenderDescriptor, unregister as unregisterRenderDescriptor };