@tinya-code/gas-admin-ui 0.1.0-alpha.5 → 0.2.0-alpha.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 (40) hide show
  1. package/README.md +538 -117
  2. package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs → tinya-code-gas-admin-ui-calendar-view-FfMmRiix.mjs} +4 -4
  3. package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs.map → tinya-code-gas-admin-ui-calendar-view-FfMmRiix.mjs.map} +1 -1
  4. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs → tinya-code-gas-admin-ui-condition-evaluator-B1it00js.mjs} +2 -2
  5. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs.map → tinya-code-gas-admin-ui-condition-evaluator-B1it00js.mjs.map} +1 -1
  6. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs → tinya-code-gas-admin-ui-data-source.service-CvL9YHyu.mjs} +2 -3
  7. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs.map → tinya-code-gas-admin-ui-data-source.service-CvL9YHyu.mjs.map} +1 -1
  8. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs → tinya-code-gas-admin-ui-error-mapper.service-CFYzwAbD.mjs} +2 -2
  9. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs.map → tinya-code-gas-admin-ui-error-mapper.service-CFYzwAbD.mjs.map} +1 -1
  10. package/fesm2022/{tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs → tinya-code-gas-admin-ui-file-Cc2DUvgR.mjs} +3 -3
  11. package/fesm2022/{tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs.map → tinya-code-gas-admin-ui-file-Cc2DUvgR.mjs.map} +1 -1
  12. package/fesm2022/{tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs → tinya-code-gas-admin-ui-form-view-Dyp3oWnC.mjs} +9 -10
  13. package/fesm2022/{tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs.map → tinya-code-gas-admin-ui-form-view-Dyp3oWnC.mjs.map} +1 -1
  14. package/fesm2022/{tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs → tinya-code-gas-admin-ui-image-BlDRZe-F.mjs} +3 -3
  15. package/fesm2022/{tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs.map → tinya-code-gas-admin-ui-image-BlDRZe-F.mjs.map} +1 -1
  16. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs → tinya-code-gas-admin-ui-kanban-view-zlQLxOlJ.mjs} +4 -4
  17. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs.map → tinya-code-gas-admin-ui-kanban-view-zlQLxOlJ.mjs.map} +1 -1
  18. package/fesm2022/{tinya-code-gas-admin-ui-list-CMzl9MkX.mjs → tinya-code-gas-admin-ui-list-D6Z4fVTb.mjs} +2 -2
  19. package/fesm2022/{tinya-code-gas-admin-ui-list-CMzl9MkX.mjs.map → tinya-code-gas-admin-ui-list-D6Z4fVTb.mjs.map} +1 -1
  20. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs → tinya-code-gas-admin-ui-multiselect-fNpo9uMF.mjs} +3 -3
  21. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs.map → tinya-code-gas-admin-ui-multiselect-fNpo9uMF.mjs.map} +1 -1
  22. package/fesm2022/{tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs → tinya-code-gas-admin-ui-relation-BSqbQB28.mjs} +3 -3
  23. package/fesm2022/{tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs.map → tinya-code-gas-admin-ui-relation-BSqbQB28.mjs.map} +1 -1
  24. package/fesm2022/{tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs → tinya-code-gas-admin-ui-render-registry-CIOMJ68Y.mjs} +10 -10
  25. package/fesm2022/{tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs.map → tinya-code-gas-admin-ui-render-registry-CIOMJ68Y.mjs.map} +1 -1
  26. package/fesm2022/{tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs → tinya-code-gas-admin-ui-schema-catalog-C9BqNPt8.mjs} +161 -7
  27. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-C9BqNPt8.mjs.map +1 -0
  28. package/fesm2022/{tinya-code-gas-admin-ui-seed-CNbQtREA.mjs → tinya-code-gas-admin-ui-seed-DD6zUd4v.mjs} +2 -2
  29. package/fesm2022/{tinya-code-gas-admin-ui-seed-CNbQtREA.mjs.map → tinya-code-gas-admin-ui-seed-DD6zUd4v.mjs.map} +1 -1
  30. package/fesm2022/{tinya-code-gas-admin-ui-select-DzaRfHXO.mjs → tinya-code-gas-admin-ui-select-BUbb36-T.mjs} +3 -3
  31. package/fesm2022/{tinya-code-gas-admin-ui-select-DzaRfHXO.mjs.map → tinya-code-gas-admin-ui-select-BUbb36-T.mjs.map} +1 -1
  32. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs → tinya-code-gas-admin-ui-upload.service-BWZkVOBg.mjs} +2 -2
  33. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs.map → tinya-code-gas-admin-ui-upload.service-BWZkVOBg.mjs.map} +1 -1
  34. package/fesm2022/tinya-code-gas-admin-ui.mjs +65 -11
  35. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -1
  36. package/package.json +8 -2
  37. package/types/tinya-code-gas-admin-ui.d.ts +69 -3
  38. package/fesm2022/tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs.map +0 -1
  39. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs +0 -131
  40. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs.map +0 -1
package/README.md CHANGED
@@ -1,61 +1,59 @@
1
1
  # @tinya-code/gas-admin-ui
2
2
 
3
- Motor de render del ecosistema `@tinya-code`: el front que **sólo declara** su
4
- dominio (`schemas/*.schema.ts`) y consume el motor compilado — mismo contrato
5
- que `@tinya-code/gas-admin` en el back, pero para Angular. El paquete dibuja
6
- vistas, campos, acciones, shell y rutas; vos no tocás componentes a mano.
3
+ Angular rendering engine for the `@tinya-code` ecosystem. You declare your domain in typed schema files — the engine renders views, forms, lists, actions, shell, and routes. You never write components by hand.
7
4
 
8
- ## Requisitos
5
+ ## Requirements
9
6
 
10
- | Requisito | Versión / detalle |
7
+ | Requirement | Version / detail |
11
8
  | --- | --- |
12
9
  | Angular | `22.2` (standalone, signals) |
13
- | Peers de Angular | `@angular/{cdk,common,core,forms,platform-browser,router} ^22.2` |
14
- | Iconos / charts | `@lucide/angular ^1.51`, `chart.js ^4.5` |
15
- | CSS | Tailwind v4 — con el `@source` del motor ([paso 2](#2-declará-el-source-de-tailwind)) |
16
- | Back | [`@tinya-code/gas-admin`](https://www.npmjs.com/package/@tinya-code/gas-admin) — su CLI genera y valida los schemas del front |
10
+ | Angular peers | `@angular/{cdk,common,core,forms,platform-browser,router} ^22.2` |
11
+ | Icons / charts | `@lucide/angular ^1.51`, `chart.js ^4.5` |
12
+ | CSS | Tailwind v4 — with the engine's `@source` ([step 2](#2-declare-the-tailwind-source)) |
13
+ | Backend | [`@tinya-code/gas-admin`](https://www.npmjs.com/package/@tinya-code/gas-admin) — its CLI generates and validates front-end schemas |
17
14
 
18
- ## Camino rápido desde cero
15
+ ---
19
16
 
20
- ### 1. Instalá el paquete
17
+ ## Quick start from scratch
18
+
19
+ ### 1. Install the package
21
20
 
22
21
  ```bash
23
22
  npm i @tinya-code/gas-admin-ui
24
23
  ```
25
24
 
26
- Un proyecto Angular 22.2 ya trae los `@angular/*`; verificá que estén los
27
- demás peers (`@lucide/angular`, `chart.js` y los `@angular/*` que falten).
25
+ An Angular 22.2 project already has `@angular/*` — verify the remaining peers (`@lucide/angular`, `chart.js`).
28
26
 
29
- ### 2. Declará el `@source` de Tailwind
27
+ ### 2. Declare the Tailwind source
30
28
 
31
- En `src/styles.css`:
29
+ In `src/styles.css`:
32
30
 
33
31
  ```css
32
+ /* Required: lets Tailwind scan the engine's templates for utility classes */
34
33
  @source '../node_modules/@tinya-code/gas-admin-ui/fesm2022';
35
- ```
36
-
37
- Sin esto la app compila pero la UI del motor sale sin clases (visual roto,
38
- tests en verde).
39
-
40
- ### 3. Generá los schemas desde el back
41
34
 
42
- Con el CLI del back instalado en el proyecto (`npm i -D @tinya-code/gas-admin`)
43
- y el dominio declarado en `api/schema/resources/`:
44
-
45
- ```bash
46
- npx gas-admin scaffold
35
+ @import 'tailwindcss';
36
+ @import './styles/theme.css'; /* your design tokens */
37
+ @import './styles/base.css'; /* your base resets */
47
38
  ```
48
39
 
49
- Escribe 9 ficheros en `src/app/schemas/` ([estructura](#estructura-generada-en-srcappschemas)).
50
- Si la carpeta ya tiene `*.ts` falla con exit 1; `--force` regenera.
40
+ Without this the app compiles and tests pass, but the engine UI renders with no styles.
51
41
 
52
- ### 4. Cableá el motor — `app.config.ts`
42
+ ### 3. Wire the engine — `app.config.ts`
53
43
 
54
44
  ```ts
45
+ // src/app/app.config.ts
46
+ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
47
+ import { provideRouter } from '@angular/router';
55
48
  import { API_URL, provideGasAdmin } from '@tinya-code/gas-admin-ui';
49
+ import { routes } from './app.routes';
50
+ import { schemas } from './schemas/registry';
51
+ import { authSchema } from './schemas/auth.schema';
52
+ import { environment } from '../environments/environment';
56
53
 
57
54
  export const appConfig: ApplicationConfig = {
58
55
  providers: [
56
+ provideBrowserGlobalErrorListeners(),
59
57
  provideRouter(routes),
60
58
  provideGasAdmin({ schemas, auth: authSchema }),
61
59
  { provide: API_URL, useValue: environment.apiUrl },
@@ -63,145 +61,568 @@ export const appConfig: ApplicationConfig = {
63
61
  };
64
62
  ```
65
63
 
66
- El motor absorbe HTTP (interceptor del envelope), los loaders de
67
- config/sesión y el catálogo de schemas; la app conserva router, environment
68
- y sus schemas de dominio.
64
+ ```ts
65
+ // src/environments/environment.ts
66
+ export const environment = {
67
+ production: false,
68
+ apiUrl: 'http://localhost:3000/api/v1',
69
+ };
70
+ ```
69
71
 
70
- ### 5. Rutas — `app.routes.ts`
72
+ ### 4. Declare routes — `app.routes.ts`
71
73
 
72
74
  ```ts
75
+ // src/app/app.routes.ts
76
+ import { Routes } from '@angular/router';
73
77
  import { authGuard, shellRoutes } from '@tinya-code/gas-admin-ui';
78
+ import { schemas } from './schemas/registry';
79
+
80
+ const firstResource = schemas[0]?.id || 'dashboard';
74
81
 
75
82
  export const routes: Routes = [
76
83
  { path: '', pathMatch: 'full', redirectTo: firstResource },
77
- { path: 'login', loadComponent: () => import('@tinya-code/gas-admin-ui').then((m) => m.Login) },
78
- { path: '', canActivate: [authGuard], loadChildren: () => shellRoutes },
84
+ {
85
+ path: 'login',
86
+ loadComponent: () => import('@tinya-code/gas-admin-ui').then((m) => m.Login),
87
+ },
88
+ {
89
+ path: '',
90
+ canActivate: [authGuard],
91
+ loadChildren: () => shellRoutes,
92
+ },
79
93
  { path: '**', redirectTo: firstResource },
80
94
  ];
81
95
  ```
82
96
 
83
- `firstResource` lo definís en el archivo:
84
- `const firstResource = schemas[0].id;` (primer recurso del catálogo). El login
85
- queda fuera del shell a propósito: si viviera dentro, `authGuard` lo
86
- bloquearía a él mismo.
97
+ `login` lives outside the shell on purpose — if it were inside, `authGuard` would block it.
98
+
99
+ ### 5. Declare auth — `auth.schema.ts`
100
+
101
+ ```ts
102
+ // src/app/schemas/auth.schema.ts
103
+ import type { AuthSchema } from '@tinya-code/gas-admin-ui';
104
+
105
+ export const authSchema: AuthSchema = {
106
+ providers: ['password'],
107
+ loginFields: [
108
+ { key: 'email', type: 'email', required: true },
109
+ { key: 'password', type: 'password', required: true },
110
+ ],
111
+ redirectAfterLogin: '/dashboard',
112
+ rememberMe: false,
113
+ };
114
+ ```
115
+
116
+ ### 6. Declare your schemas — `registry.ts`
87
117
 
88
- ### 6. Revisá los labels de cada `*.schema.ts`
118
+ ```ts
119
+ // src/app/schemas/registry.ts
120
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
121
+ import { dashboardSchema } from './dashboard.schema';
122
+ import { businessSchema } from './business.schema';
123
+ import { clientsSchema } from './clients.schema';
124
+ import { ranksSchema } from './ranks.schema';
125
+ import { rewardsSchema } from './rewards.schema';
126
+ import { couponsSchema } from './coupons.schema';
127
+ import { feedbackSchema } from './feedback.schema';
128
+
129
+ export const schemas: ResourceSchema[] = [
130
+ dashboardSchema,
131
+ businessSchema,
132
+ clientsSchema,
133
+ ranksSchema,
134
+ rewardsSchema,
135
+ couponsSchema,
136
+ feedbackSchema,
137
+ ];
138
+ ```
89
139
 
90
- Los `label` salen de las keys: son copy de demo. Reemplazalos por los del
91
- dominio y sumá las vistas/filtros que quieras (el recorrido de capacidades
92
- está en la cabecera de `registry.ts`). También podés escribir toda la
93
- estructura a mano si preferís no usar el scaffold.
140
+ The array order determines both menu order and the default redirect route.
94
141
 
95
- ### 7. Levantá la app
142
+ ### 7. Start the app
96
143
 
97
144
  ```bash
98
145
  npm start
99
146
  ```
100
147
 
101
- Verificación: el shell arranca en `/login` y, con sesión, muestra el menú y
102
- las vistas derivadas de tus schemas.
148
+ The shell starts at `/login` and, once authenticated, shows the sidebar menu and views derived from your schemas.
103
149
 
104
- ### 8. Validá el contrato back ↔ front
150
+ ---
105
151
 
106
- Sólo después de tener el back con el doc al día (`npx gas-admin doc`):
152
+ ## Schema examples (real implementations)
153
+
154
+ The examples below are taken from a production consumer of this library.
155
+
156
+ ### Dashboard — `kind: 'dashboard'`
157
+
158
+ A read-only panel with metric cards. No list, no form.
159
+
160
+ ```ts
161
+ // src/app/schemas/dashboard.schema.ts
162
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
163
+
164
+ export const dashboardSchema: ResourceSchema = {
165
+ id: 'dashboard',
166
+ label: 'Panel',
167
+ labelPlural: 'Dashboard',
168
+ kind: 'dashboard',
169
+ uiViews: {
170
+ overview: { type: 'dashboard' },
171
+ },
172
+ endpoint: {
173
+ get: '/admin/dashboard',
174
+ },
175
+ keyField: 'id',
176
+ titleField: 'label',
177
+ listColumns: [],
178
+ fields: [],
179
+ widgets: [
180
+ { type: 'metric-card', key: 'totalClients', label: 'Total Clients', hint: 'Registered memberships', width: 4 },
181
+ { type: 'metric-card', key: 'activeClients30d', label: 'Active (30d)', hint: 'Visited in last 30 days', width: 4 },
182
+ { type: 'metric-card', key: 'visitsThisMonth', label: 'Visits This Month', hint: 'Current month', width: 4 },
183
+ { type: 'metric-card', key: 'averageRating', label: 'Average Rating', hint: 'Historical satisfaction', width: 4 },
184
+ { type: 'metric-card', key: 'issuedCoupons', label: 'Issued Coupons', hint: 'All time', width: 4 },
185
+ { type: 'metric-card', key: 'usedCoupons', label: 'Redeemed Coupons', hint: 'Effective rewards', width: 4 },
186
+ ],
187
+ };
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Singleton — `kind: 'singleton'` with sections layout
193
+
194
+ One record, no list. The form is divided into labelled sections.
195
+
196
+ ```ts
197
+ // src/app/schemas/business.schema.ts
198
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
199
+
200
+ export const businessSchema: ResourceSchema = {
201
+ id: 'business',
202
+ label: 'My Business',
203
+ labelPlural: 'My Business',
204
+ kind: 'singleton',
205
+ uiViews: {
206
+ settings: { type: 'form' },
207
+ },
208
+ endpoint: {
209
+ get: '/admin/businesses/current',
210
+ update: '/admin/businesses/current',
211
+ },
212
+ keyField: 'key',
213
+ titleField: 'name',
214
+ listColumns: [],
215
+ layout: {
216
+ mode: 'sections',
217
+ sections: [
218
+ { id: 'general', label: 'General' },
219
+ { id: 'branding', label: 'Design & Card' },
220
+ { id: 'social', label: 'Social & Links' },
221
+ ],
222
+ },
223
+ fields: [
224
+ { key: 'name', type: 'text', label: 'Business Name', required: true, validators: { minLength: 2, maxLength: 120 }, section: 'general', width: 6 },
225
+ { key: 'slug', type: 'slug', from: 'name', label: 'Slug', required: true, lockAfterCreate: true, section: 'general', width: 6 },
226
+ { key: 'timezone', type: 'text', label: 'Timezone', required: true, help: 'e.g. America/Lima', section: 'general', width: 6 },
227
+ {
228
+ key: 'visitResetPolicy',
229
+ type: 'select',
230
+ label: 'Visit Reset Policy',
231
+ section: 'general',
232
+ width: 6,
233
+ options: [
234
+ { value: 'none', label: 'None' },
235
+ { value: 'monthly', label: 'Monthly' },
236
+ { value: 'yearly', label: 'Yearly' },
237
+ ],
238
+ },
239
+ { key: 'colorPrimary', type: 'text', label: 'Primary Color (HEX)', placeholder: '#4A2C11', section: 'branding', width: 6 },
240
+ { key: 'colorSecondary', type: 'text', label: 'Secondary Color (HEX)', placeholder: '#D4A373', section: 'branding', width: 6 },
241
+ { key: 'logoUrl', type: 'image', label: 'Logo', readonly: true, section: 'branding', width: 12 },
242
+ { key: 'googleReviewUrl', type: 'url', label: 'Google Reviews URL', section: 'social', width: 6 },
243
+ { key: 'instagramUrl', type: 'url', label: 'Instagram', section: 'social', width: 6 },
244
+ ],
245
+ };
246
+ ```
247
+
248
+ ---
249
+
250
+ ### Collection with full CRUD — `kind: 'collection'`
251
+
252
+ List + create + edit. Includes search, filters, and field validators.
253
+
254
+ ```ts
255
+ // src/app/schemas/ranks.schema.ts
256
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
257
+
258
+ export const ranksSchema: ResourceSchema = {
259
+ id: 'ranks',
260
+ label: 'Rank',
261
+ labelPlural: 'Ranks',
262
+ kind: 'collection',
263
+ uiViews: {
264
+ list: { type: 'list' },
265
+ create: { type: 'form', path: 'new' },
266
+ edit: { type: 'form', path: ':key/edit' },
267
+ },
268
+ endpoint: {
269
+ list: '/admin/businesses/current/ranks',
270
+ get: '/admin/businesses/current/ranks/{key}',
271
+ create: '/admin/businesses/current/ranks',
272
+ update: '/admin/businesses/current/ranks/{key}',
273
+ remove: '/admin/businesses/current/ranks/{key}',
274
+ },
275
+ keyField: 'id',
276
+ titleField: 'name',
277
+ search: ['name'],
278
+ filters: [
279
+ { key: 'includeInactive', label: 'Include Inactive', type: 'boolean' },
280
+ ],
281
+ listColumns: [
282
+ { key: 'level', label: 'Level' },
283
+ { key: 'name', label: 'Name' },
284
+ { key: 'minVisits', label: 'Min Visits' },
285
+ { key: 'benefitType', label: 'Benefit Type' },
286
+ { key: 'benefitValue',label: 'Value' },
287
+ { key: 'active', label: 'Active', format: 'boolean' },
288
+ ],
289
+ fields: [
290
+ { key: 'name', type: 'text', label: 'Rank Name', required: true, validators: { minLength: 1, maxLength: 80 } },
291
+ { key: 'level', type: 'number', label: 'Level', required: true, min: 1, help: 'Must be unique among active ranks' },
292
+ { key: 'minVisits',type: 'number', label: 'Minimum Visits', required: true, min: 0 },
293
+ {
294
+ key: 'benefitType',
295
+ type: 'select',
296
+ label: 'Benefit Type',
297
+ required: true,
298
+ options: [
299
+ { value: 'none', label: 'None' },
300
+ { value: 'percentage_discount', label: 'Percentage Discount' },
301
+ { value: 'fixed_discount', label: 'Fixed Discount' },
302
+ { value: 'free_item', label: 'Free Item' },
303
+ { value: 'priority', label: 'Priority Service' },
304
+ ],
305
+ },
306
+ { key: 'benefitValue', type: 'number', label: 'Benefit Value' },
307
+ { key: 'benefitDescription', type: 'textarea', label: 'Benefit Description', validators: { maxLength: 200 } },
308
+ { key: 'color', type: 'text', label: 'Color (HEX)', placeholder: '#CD7F32' },
309
+ { key: 'iconUrl', type: 'image', label: 'Icon', readonly: true },
310
+ { key: 'active', type: 'boolean', label: 'Active', readonlyOn: 'create' },
311
+ ],
312
+ };
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Read-only collection with detail view
318
+
319
+ List + detail only — no create, no edit form.
320
+
321
+ ```ts
322
+ // src/app/schemas/clients.schema.ts
323
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
324
+
325
+ export const clientsSchema: ResourceSchema = {
326
+ id: 'clients',
327
+ label: 'Client',
328
+ labelPlural: 'Clients',
329
+ kind: 'collection',
330
+ uiViews: {
331
+ list: { type: 'list' },
332
+ detail: { type: 'detail', path: ':key' },
333
+ edit: { type: 'form', path: ':key/edit' },
334
+ },
335
+ endpoint: {
336
+ list: '/admin/businesses/current/clients',
337
+ get: '/admin/businesses/current/clients/{key}',
338
+ update: '/admin/businesses/current/clients/{key}/status',
339
+ // no create / remove → those actions won't appear in the UI
340
+ },
341
+ keyField: 'membershipId',
342
+ titleField: 'name',
343
+ search: ['name', 'phone'],
344
+ filters: [
345
+ { key: 'active', label: 'Active', type: 'boolean' },
346
+ ],
347
+ listColumns: [
348
+ { key: 'name', label: 'Name' },
349
+ { key: 'phone', label: 'Phone' },
350
+ { key: 'email', label: 'Email' },
351
+ { key: 'totalVisits', label: 'Total Visits' },
352
+ { key: 'lastVisitAt', label: 'Last Visit' },
353
+ { key: 'active', label: 'Active', format: 'boolean' },
354
+ { key: 'registeredAt',label: 'Registered' },
355
+ ],
356
+ detail: {
357
+ enabled: true,
358
+ headerFields: ['name', 'phone', 'active'],
359
+ },
360
+ fields: [
361
+ { key: 'membershipId', type: 'readonly-text', label: 'Membership ID', readonly: true },
362
+ { key: 'name', type: 'text', label: 'Name', readonly: true },
363
+ { key: 'phone', type: 'phone', label: 'Phone', readonly: true },
364
+ { key: 'email', type: 'email', label: 'Email', readonly: true },
365
+ { key: 'active', type: 'boolean', label: 'Active Membership' },
366
+ { key: 'totalVisits', type: 'number', label: 'Total Visits', readonly: true },
367
+ { key: 'currentPeriodVisits', type: 'number', label: 'Period Visits', readonly: true },
368
+ { key: 'registeredAt', type: 'date', label: 'Registered At', readonly: true },
369
+ { key: 'lastVisitAt', type: 'date', label: 'Last Visit', readonly: true },
370
+ ],
371
+ };
372
+ ```
373
+
374
+ ---
375
+
376
+ ### Read-only collection with select filter
377
+
378
+ ```ts
379
+ // src/app/schemas/coupons.schema.ts
380
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
381
+
382
+ export const couponsSchema: ResourceSchema = {
383
+ id: 'coupons',
384
+ label: 'Coupon',
385
+ labelPlural: 'Coupons',
386
+ kind: 'collection',
387
+ uiViews: {
388
+ list: { type: 'list' },
389
+ detail: { type: 'detail', path: ':key' },
390
+ },
391
+ endpoint: {
392
+ list: '/admin/businesses/current/coupons',
393
+ get: '/admin/businesses/current/coupons/{key}',
394
+ },
395
+ keyField: 'id',
396
+ titleField: 'code',
397
+ search: ['code'],
398
+ filters: [
399
+ {
400
+ key: 'status',
401
+ label: 'Status',
402
+ type: 'select',
403
+ options: [
404
+ { value: 'issued', label: 'Issued' },
405
+ { value: 'used', label: 'Redeemed' },
406
+ { value: 'expired', label: 'Expired' },
407
+ ],
408
+ },
409
+ ],
410
+ listColumns: [
411
+ { key: 'code', label: 'Code' },
412
+ { key: 'status', label: 'Status' },
413
+ { key: 'issuedAt', label: 'Issued' },
414
+ { key: 'expiresAt',label: 'Expires' },
415
+ { key: 'usedAt', label: 'Redeemed' },
416
+ ],
417
+ detail: {
418
+ enabled: true,
419
+ headerFields: ['code', 'status', 'issuedAt'],
420
+ },
421
+ fields: [
422
+ { key: 'id', type: 'readonly-text', label: 'ID', readonly: true },
423
+ { key: 'code', type: 'text', label: 'Code', readonly: true },
424
+ { key: 'status', type: 'text', label: 'Status', readonly: true },
425
+ { key: 'issuedAt', type: 'date', label: 'Issued At', readonly: true },
426
+ { key: 'expiresAt', type: 'date', label: 'Expires At', readonly: true },
427
+ { key: 'usedAt', type: 'date', label: 'Redeemed At',readonly: true },
428
+ ],
429
+ };
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Firebase Auth
435
+
436
+ The package supports federated authentication with Google and Firebase Email/Password via the decoupled `FirebaseAuthAdapter`. Firebase is declared as an optional `peerDependency` — the library does not bundle it.
437
+
438
+ ### 1. Install Firebase in the consumer app
107
439
 
108
440
  ```bash
109
- npx gas-admin validate --with-front
441
+ npm i firebase
442
+ ```
443
+
444
+ ### 2. Declare providers in `AuthSchema`
445
+
446
+ ```ts
447
+ // src/app/schemas/auth.schema.ts
448
+ import type { AuthSchema } from '@tinya-code/gas-admin-ui';
449
+
450
+ // Option A — password only (default, no Firebase required)
451
+ export const authSchema: AuthSchema = {
452
+ providers: ['password'],
453
+ loginFields: [
454
+ { key: 'email', type: 'email', required: true },
455
+ { key: 'password', type: 'password', required: true },
456
+ ],
457
+ redirectAfterLogin: '/dashboard',
458
+ rememberMe: false,
459
+ };
460
+
461
+ // Option B — Google only
462
+ export const authSchema: AuthSchema = {
463
+ providers: ['google'],
464
+ loginFields: [],
465
+ redirectAfterLogin: '/dashboard',
466
+ rememberMe: false,
467
+ firebase: { exchangeEndpoint: '/auth/firebase' },
468
+ };
469
+
470
+ // Option C — Google + Password (both)
471
+ export const authSchema: AuthSchema = {
472
+ providers: ['google', 'password'],
473
+ loginFields: [
474
+ { key: 'email', type: 'email', required: true },
475
+ { key: 'password', type: 'password', required: true },
476
+ ],
477
+ redirectAfterLogin: '/dashboard',
478
+ rememberMe: false,
479
+ firebase: { exchangeEndpoint: '/auth/firebase' },
480
+ };
110
481
  ```
111
482
 
112
- Corre el `contract-check` de `@tinya-code/gas-admin`: exit 0 = los schemas del
113
- front siguen coincidiendo con la proyección del back.
483
+ ### 3. Implement the adapter and wire it in `app.config.ts`
114
484
 
115
- ## Requisitos especiales del consumidor
485
+ ```ts
486
+ // src/app/app.config.ts
487
+ import { initializeApp } from 'firebase/app';
488
+ import {
489
+ getAuth,
490
+ GoogleAuthProvider,
491
+ signInWithPopup,
492
+ signInWithEmailAndPassword,
493
+ signOut,
494
+ } from 'firebase/auth';
495
+ import {
496
+ API_URL,
497
+ provideGasAdmin,
498
+ provideFirebaseAdapter,
499
+ } from '@tinya-code/gas-admin-ui';
500
+ import type { FirebaseAuthAdapter } from '@tinya-code/gas-admin-ui';
501
+ import { environment } from '../environments/environment';
502
+
503
+ const firebaseApp = initializeApp(environment.firebase);
504
+ const firebaseAuth = getAuth(firebaseApp);
505
+
506
+ class AppFirebaseAuthAdapter implements FirebaseAuthAdapter {
507
+ async signInWithGoogle(): Promise<string> {
508
+ const cred = await signInWithPopup(firebaseAuth, new GoogleAuthProvider());
509
+ return cred.user.getIdToken();
510
+ }
511
+ async signInWithEmailAndPassword(email: string, password: string): Promise<string> {
512
+ const cred = await signInWithEmailAndPassword(firebaseAuth, email, password);
513
+ return cred.user.getIdToken();
514
+ }
515
+ async signOut(): Promise<void> {
516
+ await signOut(firebaseAuth);
517
+ }
518
+ }
116
519
 
117
- ### Tailwind v4 — `@source` obligatorio
520
+ export const appConfig: ApplicationConfig = {
521
+ providers: [
522
+ provideRouter(routes),
523
+ provideGasAdmin({ schemas, auth: authSchema }),
524
+ provideFirebaseAdapter(new AppFirebaseAuthAdapter()), // ← add this
525
+ { provide: API_URL, useValue: environment.apiUrl },
526
+ ],
527
+ };
528
+ ```
118
529
 
119
- Tailwind genera las utilities escaneando **tus** fuentes; el motor vive en
120
- `node_modules` y Tailwind no lo escanea solo. El `@source` del paso 2 es
121
- obligatorio: sin él la app compila y los tests quedan en verde, pero la UI
122
- del motor se renderiza sin estilos.
530
+ ### 4. Backend endpoint — `POST /auth/firebase`
123
531
 
124
- ### Tests del consumidor — desde `node_modules` (sin alias)
532
+ The library sends the Firebase ID token using the **same envelope transport** as every other request. Your backend must:
125
533
 
126
- Los specs del consumidor deben resolver `@tinya-code/gas-admin-ui` desde
127
- `node_modules` (la versión publicada en `dependencies`), **sin** alias de
128
- `tsconfig.spec.json` ni clone del repo como directorio hermano en CI: la app
129
- y los tests usan la MISMA copia compilada ⇒ Angular se instancia una sola
130
- vez (verificado en `admin-schema`: 116/116 sin alias). Para probar un cambio
131
- del motor, publicá un alpha y bumpeá la dependencia.
534
+ 1. Receive `{ idToken: string }` in the payload.
535
+ 2. Verify it with the Firebase Admin SDK (`getAuth().verifyIdToken(idToken)`).
536
+ 3. Return `{ token, user }` — identical shape to `/auth/login`.
132
537
 
133
- Si alguna combinación de versiones llegara a reproducir `NG0203`/`NG01908`
134
- (doble instancia de Angular: vitest prebundlea `@angular/*` pero la lib corre
135
- en ESM nativo), el workaround es compilar la lib desde source en el grafo de
136
- tests (`paths` en `tsconfig.spec.json` apuntando al source del paquete)
137
- junto con un clone del repo como directorio hermano en CI.
538
+ ```json
539
+ // Request body (Content-Type: text/plain)
540
+ { "token": null, "method": "POST", "path": "/auth/firebase", "payload": { "idToken": "..." } }
138
541
 
139
- ## Estructura generada en `src/app/schemas/`
542
+ // Response
543
+ { "token": "<your-app-jwt>", "user": { "id": "...", "email": "...", "name": "...", "role": "..." }, "meta": {} }
544
+ ```
545
+
546
+ For a full consumer integration guide including a backend handler example, environment config, and a pre-production checklist, see [`doc/firebase-consumer-example.md`](../../doc/firebase-consumer-example.md).
547
+
548
+ ---
549
+
550
+ ## Consumer requirements
551
+
552
+ ### Tailwind v4 — mandatory `@source`
553
+
554
+ Tailwind generates utilities by scanning **your** source files. The engine lives in `node_modules` and is not scanned by default. The `@source` directive in `styles.css` (step 2) is required — without it the app compiles and tests pass green, but the engine UI renders without any styles.
140
555
 
141
- `npx gas-admin scaffold` aplaná la plantilla de las vitrinas (catálogo,
142
- agenda, panel) en una carpeta plana:
556
+ ### Tests — resolve from `node_modules` (no alias)
557
+
558
+ Consumer specs must resolve `@tinya-code/gas-admin-ui` from `node_modules` (the published, compiled version), without `tsconfig.spec.json` aliases. This guarantees Angular is instantiated exactly once. To test a library change, publish an alpha and bump the dependency.
559
+
560
+ ---
561
+
562
+ ## Generated schema structure (`npx gas-admin scaffold`)
143
563
 
144
564
  ```text
145
565
  src/app/schemas/
146
- ├── auth.schema.ts # claves exactas del input de /auth/login
147
- ├── registry.ts # array ordenado que alimenta menú y rutas
148
- ├── seed-catalog.ts # siembra el catálogo para los specs
149
- ├── productos.schema.ts # vitrina catálogo
150
- ├── categorias.schema.ts # vitrina catálogo
151
- ├── pedidos.schema.ts # vitrina agenda
152
- ├── dashboard.schema.ts # vitrina panel
153
- ├── usuarios.schema.ts # vitrina panel
154
- └── sitio.schema.ts # vitrina panel
566
+ ├── auth.schema.ts # auth provider and login fields
567
+ ├── registry.ts # ordered array that feeds the menu and routes
568
+ ├── seed-catalog.ts # seeds the catalog for specs
569
+ ├── dashboard.schema.ts # kind: dashboard
570
+ ├── business.schema.ts # kind: singleton
571
+ ├── clients.schema.ts # kind: collection (read-only with detail)
572
+ ├── ranks.schema.ts # kind: collection (full CRUD)
573
+ ├── rewards.schema.ts # kind: collection (full CRUD)
574
+ ├── coupons.schema.ts # kind: collection (read-only with filters)
575
+ └── feedback.schema.ts # kind: collection (read-only with search)
155
576
  ```
156
577
 
157
- Por cada recurso, el `*.schema.ts` trae campos, validators, enums y endpoint
158
- derivados de la proyección back.
578
+ `scaffold` fails with exit 1 if the target folder already contains `*.ts` files — use `--force` to regenerate.
159
579
 
160
- ## API pública
580
+ ---
161
581
 
162
- Sólo lo exportado por `public-api.ts` es contrato; el resto es privado y
163
- puede cambiar entre alphas.
582
+ ## Public API
164
583
 
165
- | Grupo | Exportaciones |
584
+ Only what `public-api.ts` exports is stable contract; everything else is private and may change between alphas.
585
+
586
+ | Group | Exports |
166
587
  | --- | --- |
167
- | Wiring y catálogo | `provideGasAdmin`, `GAS_SCHEMAS`, `setSchemaCatalog`, `getSchema`, `allSchemas`, `getAuth`, tipo `GasAdminConfig` |
168
- | Token | `API_URL` |
169
- | Rutas y guard | `authGuard`, `shellRoutes`, `resourceRoutes` |
170
- | Páginas | `Login`, `Dashboard`, `ListView`, `FormView`, `ResourcePage` |
171
- | Componentes | `Sidebar`, `ComposerHost`, `FieldHost`, `ConfirmDialog`, `Toast` |
172
- | Servicios | `ApiService`, `AuthService` (+ `SESSION_TOKEN_KEY`, `SESSION_USER_KEY`), `NotificationService`, `DataSourceService`, `UploadService`, `ErrorMapperService`, `apiErrorInterceptor` |
588
+ | Wiring & catalog | `provideGasAdmin`, `provideFirebaseAdapter`, `GAS_SCHEMAS`, `setSchemaCatalog`, `getSchema`, `allSchemas`, `getAuth`, type `GasAdminConfig` |
589
+ | Tokens & adapters | `API_URL`, `FIREBASE_AUTH_ADAPTER`, type `FirebaseAuthAdapter` |
590
+ | Routes & guard | `authGuard`, `shellRoutes`, `resourceRoutes` |
591
+ | Pages | `Login`, `Dashboard`, `ListView`, `FormView`, `ResourcePage` |
592
+ | Components | `Sidebar`, `ComposerHost`, `FieldHost`, `ConfirmDialog`, `Toast` |
593
+ | Services | `ApiService`, `AuthService` (+ `SESSION_TOKEN_KEY`, `SESSION_USER_KEY`), `NotificationService`, `DataSourceService`, `UploadService`, `ErrorMapperService`, `apiErrorInterceptor` |
173
594
  | Forms | `buildFormSchema`, `seedModel`, `buildPayload` |
174
- | Modelos de schema | `ResourceSchema`, `AuthSchema`, `ResourceAction`, `ApiError` y todo el vocabulario de fields/widgets/views/layouts (`FieldSchema`, `FieldType`, `ListColumn`, `WidgetSchema`, `ResourceLayout`, `ViewDescriptor`, vistas tabs/calendar/kanban…) |
175
- | Registros de render | `fieldDescriptors`, `SUPPORTED_FIELD_TYPE_LIST`, `columnFormatDescriptors`, `COLUMN_FORMAT_TYPES`, `viewDescriptors`, `layoutDescriptors`, `renderKinds` |
176
- | Render custom | `registerRenderDescriptor`, `unregisterRenderDescriptor`, `provideRenderDescriptors`, `getRenderDescriptor`, `isSupported`, `isRenderSupported`, tipos `RenderKind`, `ComponentRenderKind` |
177
- | Composición | `validateComposition`, `MAX_DEPTH`, `SchemaCompositionError` |
178
- | Acciones custom | `registerAction`, `getAction`, `actionsFor`, `actionType`, `actionTypes`, tipos `ActionDescriptor`, `ActionSurface`, `ActionContext`, `ActionResult` |
179
- | Validadores | tipo `ValidatorName` (vocabulario del contract-check back ↔ front) |
180
- | Utilidades | `interpolateTemplate` (interpolación de templates en schemas) |
595
+ | Schema models | `ResourceSchema`, `AuthSchema`, `AuthProvider`, `FirebaseAuthConfig`, `ResourceAction`, `ApiError` and the full field/widget/view/layout vocabulary |
596
+ | Render registries | `fieldDescriptors`, `columnFormatDescriptors`, `viewDescriptors`, `layoutDescriptors`, `renderKinds` |
597
+ | Custom render | `registerRenderDescriptor`, `unregisterRenderDescriptor`, `provideRenderDescriptors`, `getRenderDescriptor`, `isSupported`, `isRenderSupported`, types `RenderKind`, `ComponentRenderKind` |
598
+ | Composition | `validateComposition`, `MAX_DEPTH`, `SchemaCompositionError` |
599
+ | Custom actions | `registerAction`, `getAction`, `actionsFor`, `actionType`, `actionTypes`, types `ActionDescriptor`, `ActionSurface`, `ActionContext`, `ActionResult` |
600
+ | Validators | type `ValidatorName` |
601
+ | Utilities | `interpolateTemplate` |
602
+
603
+ ---
604
+
605
+ ## See also
181
606
 
182
- ## Ver también
607
+ - **Backend** — domain declaration, `doc`, `validate`:
608
+ [`@tinya-code/gas-admin`](https://github.com/Tinya-Code/gas-admin) → `docs/guia-de-uso.md`.
609
+ - **Firebase consumer guide** — full wiring, backend contract, pre-production checklist:
610
+ [`doc/firebase-consumer-example.md`](../../doc/firebase-consumer-example.md).
183
611
 
184
- - **Back del ecosistema** — `init`, declaración de dominio, `doc`, `validate`:
185
- guía de `@tinya-code/gas-admin` en
186
- [`Tinya-Code/gas-admin`](https://github.com/Tinya-Code/gas-admin)
187
- (`docs/guia-de-uso.md`).
188
- - **Consumidor de referencia** — `admin-schema`: declara el dominio e integra
189
- este paquete (116 tests sin alias, specs de integración).
612
+ ---
190
613
 
191
- ## Desarrollo (este repo)
614
+ ## Development (this repo)
192
615
 
193
616
  ```bash
194
- ng build gas-admin-ui # ng-packagr → dist/gas-admin-ui (fesm2022 + types)
195
- ng test gas-admin-ui # vitest: tests del motor (fixtures congelados)
617
+ ng build gas-admin-ui # ng-packagr → dist/gas-admin-ui
618
+ ng test gas-admin-ui # vitest: engine tests
196
619
  ```
197
620
 
198
- Publicación (npm **exige** `--tag` explícito en prereleases):
621
+ Publish (npm requires explicit `--tag` for pre-releases):
199
622
 
200
623
  ```bash
201
624
  npm publish ./dist/gas-admin-ui --tag alpha
202
625
  ```
203
626
 
204
- - `dist/` es gitignored: el build es siempre local y no se commitea.
205
- - Los specs que renderizan los schemas **reales** de un consumidor no viven
206
- acá (la lib no puede importar al consumidor): viven en el repo del
207
- consumidor importando desde este paquete.
627
+ - `dist/` is gitignored — the build is always local and never committed.
628
+ - Specs that render a consumer's real schemas live in the consumer repo, not here.