@tinya-code/gas-admin-ui 0.0.0-stage → 0.1.0-alpha.2

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 (136) hide show
  1. package/README.md +125 -2
  2. package/fesm2022/tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs +281 -0
  3. package/fesm2022/tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs.map +1 -0
  4. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +84 -0
  5. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +1 -0
  6. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +140 -0
  7. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +1 -0
  8. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +113 -0
  9. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +1 -0
  10. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +163 -0
  11. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +1 -0
  12. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs +1022 -0
  13. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs.map +1 -0
  14. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +278 -0
  15. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +1 -0
  16. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs +101 -0
  17. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs.map +1 -0
  18. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +141 -0
  19. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +1 -0
  20. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs +89 -0
  21. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs.map +1 -0
  22. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +107 -0
  23. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +1 -0
  24. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +140 -0
  25. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +1 -0
  26. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +119 -0
  27. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +1 -0
  28. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +125 -0
  29. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +1 -0
  30. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +152 -0
  31. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +1 -0
  32. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +211 -0
  33. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +1 -0
  34. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +116 -0
  35. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +1 -0
  36. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +111 -0
  37. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +1 -0
  38. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +107 -0
  39. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +1 -0
  40. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +109 -0
  41. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +1 -0
  42. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs +45 -0
  43. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs.map +1 -0
  44. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +50 -0
  45. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +1 -0
  46. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +24 -0
  47. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +1 -0
  48. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +27 -0
  49. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +1 -0
  50. package/fesm2022/tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs +408 -0
  51. package/fesm2022/tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs.map +1 -0
  52. package/fesm2022/tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs +4514 -0
  53. package/fesm2022/tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs.map +1 -0
  54. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +82 -0
  55. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +1 -0
  56. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +105 -0
  57. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +1 -0
  58. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +65 -0
  59. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +1 -0
  60. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +65 -0
  61. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +1 -0
  62. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +65 -0
  63. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +1 -0
  64. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +59 -0
  65. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +1 -0
  66. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +94 -0
  67. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +1 -0
  68. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +72 -0
  69. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +1 -0
  70. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +238 -0
  71. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +1 -0
  72. package/fesm2022/tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs +416 -0
  73. package/fesm2022/tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs.map +1 -0
  74. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs +439 -0
  75. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs.map +1 -0
  76. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +513 -0
  77. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +1 -0
  78. package/fesm2022/tinya-code-gas-admin-ui-list-CMzl9MkX.mjs +637 -0
  79. package/fesm2022/tinya-code-gas-admin-ui-list-CMzl9MkX.mjs.map +1 -0
  80. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +127 -0
  81. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +1 -0
  82. package/fesm2022/tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs +251 -0
  83. package/fesm2022/tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs.map +1 -0
  84. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +66 -0
  85. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +1 -0
  86. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +114 -0
  87. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +1 -0
  88. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +116 -0
  89. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +1 -0
  90. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +235 -0
  91. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +1 -0
  92. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +8 -0
  93. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +1 -0
  94. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +124 -0
  95. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +1 -0
  96. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +249 -0
  97. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +1 -0
  98. package/fesm2022/tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs +309 -0
  99. package/fesm2022/tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs.map +1 -0
  100. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +39 -0
  101. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +1 -0
  102. package/fesm2022/tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs +546 -0
  103. package/fesm2022/tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs.map +1 -0
  104. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +11 -0
  105. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs +131 -0
  106. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs.map +1 -0
  107. package/fesm2022/tinya-code-gas-admin-ui-seed-CNbQtREA.mjs +80 -0
  108. package/fesm2022/tinya-code-gas-admin-ui-seed-CNbQtREA.mjs.map +1 -0
  109. package/fesm2022/tinya-code-gas-admin-ui-select-DzaRfHXO.mjs +292 -0
  110. package/fesm2022/tinya-code-gas-admin-ui-select-DzaRfHXO.mjs.map +1 -0
  111. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +119 -0
  112. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +1 -0
  113. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +158 -0
  114. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +1 -0
  115. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +26 -0
  116. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +1 -0
  117. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +205 -0
  118. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +1 -0
  119. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +432 -0
  120. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +1 -0
  121. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +36 -0
  122. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +1 -0
  123. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +107 -0
  124. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +1 -0
  125. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +223 -0
  126. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +1 -0
  127. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +107 -0
  128. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +1 -0
  129. package/fesm2022/tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs +60 -0
  130. package/fesm2022/tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs.map +1 -0
  131. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +141 -0
  132. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +1 -0
  133. package/fesm2022/tinya-code-gas-admin-ui.mjs +4403 -0
  134. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -0
  135. package/package.json +31 -3
  136. package/types/tinya-code-gas-admin-ui.d.ts +3099 -0
@@ -0,0 +1,3099 @@
1
+ import * as i0 from "@angular/core";
2
+ import { EnvironmentProviders, InjectionToken, Provider, Signal, TemplateRef, Type } from "@angular/core";
3
+ import { CanActivateFn, Route, Router, Routes } from "@angular/router";
4
+ import { Observable } from "rxjs";
5
+ import { HttpEvent, HttpInterceptorFn } from "@angular/common/http";
6
+ import { CdkDragDrop } from "@angular/cdk/drag-drop";
7
+ import { LucideIcon } from "@lucide/angular";
8
+ import { FieldTree, PathKind, SchemaFn, SchemaPath, SchemaPathRules } from "@angular/forms/signals";
9
+ /**
10
+ * Modelos del schema de recursos — única fuente de verdad de la UI.
11
+ * Especificación: doc/base.md §3–§7 y §9.
12
+ *
13
+ * Contrato de imágenes (decisión api.md §1): `image_url` en todo el JSON.
14
+ */
15
+ /** Anchos de columna dentro de una fila del layout (base.md §4: 1–12). */
16
+ export type GridWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
17
+ /** Operadores de `visibleWhen` / `readonlyWhen` (base.md §4). */
18
+ export type ConditionOperator = 'eq' | 'ne' | 'empty' | 'contains';
19
+ /**
20
+ * Condición simple sobre otro campo del mismo nivel o del registro raíz.
21
+ * Ej.: `{ field: 'availability', operator: 'eq', value: 'PreOrder' }`.
22
+ */
23
+ export interface FieldCondition {
24
+ field: string;
25
+ operator: ConditionOperator;
26
+ /** Valor de comparación; no aplica para `empty`. */
27
+ value?: string | number | boolean;
28
+ }
29
+ /**
30
+ * Reglas de validación comunes (base.md §4).
31
+ * Nota: algunas se solapan con opciones propias del tipo (§5); el motor de
32
+ * validación (Fase 10) los fusiona y `validators` tiene prioridad.
33
+ */
34
+ export interface FieldValidators {
35
+ minLength?: number;
36
+ maxLength?: number;
37
+ pattern?: string;
38
+ min?: number;
39
+ max?: number;
40
+ minWords?: number;
41
+ maxWords?: number;
42
+ /** El valor debe ser único en el recurso (validación asíncrona). */
43
+ unique?: boolean;
44
+ }
45
+ /** Propiedades comunes a todo campo (base.md §4). */
46
+ export interface FieldBase<T> {
47
+ /** Nombre de la propiedad en el JSON. */
48
+ key: string;
49
+ label: string;
50
+ help?: string;
51
+ placeholder?: string;
52
+ required?: boolean;
53
+ default?: T;
54
+ readonly?: boolean;
55
+ /** Ciclo de vida: bloquea el campo solo en creación, solo en edición o siempre. */
56
+ readonlyOn?: 'create' | 'update' | 'always';
57
+ readonlyWhen?: FieldCondition;
58
+ visibleWhen?: FieldCondition;
59
+ /**
60
+ * Ciclo de vida de la VISIBILIDAD: oculta el campo en creación, en edición
61
+ * o siempre. Símétrico a `readonlyOn`, pero NO lo mismo que `visibleWhen`,
62
+ * que expresa una condición sobre el valor de otro campo.
63
+ *
64
+ * Vale para los campos que LLENA EL BACK (`ref`, `pin`): en alta no existen
65
+ * aún, así que mostrarlos vacíos y solo-lectura con un `help` que dice "se
66
+ * genera solo al guardar" es ruido ocupando la sección. `hidden()` de Signal
67
+ * Forms además los deja FUERA de la validación y del payload
68
+ * (field-host:311 no los dibuja, form-model:52 los salta).
69
+ */
70
+ hiddenOn?: 'create' | 'update' | 'always';
71
+ /**
72
+ * Selección dependiente (guía §1: país → ciudad). Clave del campo padre
73
+ * del MISMO nivel: mientras el padre esté vacío este campo queda
74
+ * `disabled`, y si el padre cambia el hijo se reinicia a su valor por
75
+ * defecto (plan 6.6).
76
+ */
77
+ dependsOn?: string;
78
+ validators?: FieldValidators;
79
+ width?: GridWidth;
80
+ /** Id de la sección/pestaña del layout a la que pertenece (base.md §4). */
81
+ section?: string;
82
+ /**
83
+ * R7.5: acceso por campo (B8 · R7.4b) — sólo UX; el back vuelve a
84
+ * verificar en cada petición.
85
+ */
86
+ access?: {
87
+ read?: string;
88
+ write?: string;
89
+ };
90
+ }
91
+ export interface SelectOption {
92
+ value: string;
93
+ label: string;
94
+ disabled?: boolean;
95
+ }
96
+ export interface TextField extends FieldBase<string> {
97
+ type: 'text';
98
+ maxLength?: number;
99
+ pattern?: string;
100
+ }
101
+ export interface TextareaField extends FieldBase<string> {
102
+ type: 'textarea';
103
+ rows?: number;
104
+ minWords?: number;
105
+ maxWords?: number;
106
+ /**
107
+ * Marcadores insertables como chips bajo el área (plantillas de mensaje).
108
+ * Cada chip escribe `{key}` en la posición del cursor, no al final: el
109
+ * operador escribe, apreta, sigue escribiendo. El motor back no se entera —
110
+ * recibe el string final y lo interpola (`interpolateTemplate`).
111
+ *
112
+ * `label` es el rótulo humano del botón ("PIN"); `key` es la clave que se
113
+ * inserta y que el back resuelve contra `config + registro`.
114
+ */
115
+ tokens?: readonly TemplateToken[];
116
+ }
117
+ /** Chip insertable en un `textarea` de plantilla. */
118
+ export interface TemplateToken {
119
+ /** Clave insertada como `{key}` y resuelta por el motor. */
120
+ key: string;
121
+ /** Rótulo visible en el chip (el token va como tooltip). */
122
+ label: string;
123
+ }
124
+ export interface SlugField extends FieldBase<string> {
125
+ type: 'slug';
126
+ /** Campo del que se autogenera el slug (ej. `name`). */
127
+ from: string;
128
+ /** Bloquea el campo una vez creado el registro (por defecto: sí). */
129
+ lockAfterCreate?: boolean;
130
+ }
131
+ export interface NumberField extends FieldBase<number> {
132
+ type: 'number';
133
+ min?: number;
134
+ max?: number;
135
+ step?: number;
136
+ decimals?: number;
137
+ }
138
+ export interface CurrencyField extends FieldBase<number> {
139
+ type: 'currency';
140
+ /** Campo del registro del que se toma la moneda (ej. `currency`). */
141
+ currencyFrom?: string;
142
+ }
143
+ export interface BooleanField extends FieldBase<boolean> {
144
+ type: 'boolean';
145
+ trueLabel?: string;
146
+ falseLabel?: string;
147
+ }
148
+ /**
149
+ * De dónde salen las opciones de un desplegable: **exactamente una** de las
150
+ * dos formas (XOR en el tipo, D62).
151
+ *
152
+ * - `options` — estáticas en el schema (el caso de las enums: `status`).
153
+ * - `source` — pedidas al fetcher declarado (F8 · R6.5) para listados que
154
+ * viven en otro recurso.
155
+ *
156
+ * Ninguna ⇒ un desplegable mudo; las dos ⇒ dos verdades para el mismo campo.
157
+ * Ambas reglas viven en este tipo, no en un chequeo en runtime.
158
+ */
159
+ type SelectData = {
160
+ options: SelectOption[];
161
+ source?: never;
162
+ } | {
163
+ options?: never;
164
+ source: DataSource;
165
+ };
166
+ export type SelectField = FieldBase<string> & {
167
+ type: 'select';
168
+ } & SelectData;
169
+ export type MultiselectField = FieldBase<string[]> & {
170
+ type: 'multiselect';
171
+ } & SelectData;
172
+ export interface UrlField extends FieldBase<string> {
173
+ type: 'url';
174
+ }
175
+ export interface EmailField extends FieldBase<string> {
176
+ type: 'email';
177
+ }
178
+ export interface PhoneField extends FieldBase<string> {
179
+ type: 'phone';
180
+ /** Normalización del valor guardado. */
181
+ format?: 'e164' | 'digits';
182
+ }
183
+ export interface DateField extends FieldBase<string> {
184
+ type: 'date';
185
+ /** Fecha ISO `YYYY-MM-DD` permitida. */
186
+ min?: string;
187
+ max?: string;
188
+ }
189
+ export interface TimeField extends FieldBase<string> {
190
+ type: 'time';
191
+ }
192
+ export interface ReadonlyTextField extends FieldBase<string> {
193
+ type: 'readonly-text';
194
+ /** Formato de presentación (texto, fecha, moneda…). */
195
+ format?: string;
196
+ }
197
+ /**
198
+ * Fuente de datos declarada (F8 · R6.5): UN vocabulario para relations,
199
+ * selects y widgets en lugar de tres. El fetcher es único
200
+ * (`DataSourceService`) y la clave de su caché es `resource+view+params`.
201
+ *
202
+ * `params` son valores interpolados `{campo}` que el front resuelve contra su
203
+ * contexto (el registro del formulario o la fila del widget) y manda como
204
+ * `payload.params`; el back los inyecta en el ctx de las vistas, donde una
205
+ * condición los lee con el operando `param: '<nombre>'`. Una vista que no
206
+ * referencia ese parámetro no se ve afectada por él — sólo cambia la clave
207
+ * de caché.
208
+ *
209
+ * Los parámetros llegan del navegador, así que son ENTRADA: sólo filtran
210
+ * dentro del recurso que la ruta ya autoriza a leer (nunca ensanchan el
211
+ * acceso) y una vista que pide un parámetro ausente queda fail-closed.
212
+ */
213
+ export interface DataSource {
214
+ /** Recurso declarado (`resource.id`): su listado es el de las opciones. */
215
+ resource: string;
216
+ /** Vista declarada en `resource.views` (opcional): filtra en el back. */
217
+ view?: string;
218
+ /** Valores interpolados para la vista (`{campo}` ← contexto). */
219
+ params?: Record<string, string>;
220
+ /** Etiqueta por defecto de las filas (si no, `titleField` del recurso). */
221
+ labelField?: string;
222
+ /** Valor por defecto de las filas (si no, `keyField` del recurso). */
223
+ valueField?: string;
224
+ }
225
+ export interface RelationField extends FieldBase<string> {
226
+ type: 'relation';
227
+ /**
228
+ * De dónde salen las opciones (F8 · R6.5): `{ resource, view?, params?, … }`.
229
+ * Antes eran tres claves planas (`resource`/`labelField`/`valueField`);
230
+ * D62 las unifica con el resto de consumidores.
231
+ */
232
+ source: DataSource;
233
+ /**
234
+ * Segundo dato a mostrar junto a la etiqueta (guía §1: «Juan Pérez ·
235
+ * DNI 123»). Se concatena con « · » y forma parte de la opción, así que
236
+ * también se busca sobre él.
237
+ */
238
+ secondaryField?: string;
239
+ /** Cargar solo registros activos (lo exige además la FK en el back). */
240
+ onlyActive?: boolean;
241
+ }
242
+ export interface ImageField extends FieldBase<string> {
243
+ type: 'image';
244
+ /** Tipos aceptados (ej. `image/*`, `image/png,image/jpeg`). */
245
+ accept?: string;
246
+ maxSizeMB?: number;
247
+ /** Relación de aspecto de la vista previa (ej. `16/9`). */
248
+ aspectRatio?: string;
249
+ previewSize?: string;
250
+ }
251
+ export interface FileField extends FieldBase<string> {
252
+ type: 'file';
253
+ /** Formatos aceptados (ej. `image/*`, `pdf,docx`). Si falta, usa los del backend. */
254
+ accept?: string;
255
+ maxSizeMB?: number;
256
+ }
257
+ export interface GroupField extends FieldBase<Record<string, unknown>> {
258
+ type: 'group';
259
+ fields: FieldSchema[];
260
+ /** Presentación del grupo (base.md §5.4). */
261
+ display?: 'card' | 'collapsible' | 'inline';
262
+ }
263
+ /** Propiedades comunes a las listas (base.md §6). */
264
+ export interface ListOptions {
265
+ min?: number;
266
+ max?: number;
267
+ /** Si se puede reordenar (por defecto: sí). */
268
+ sortable?: boolean;
269
+ /** Campo donde se guarda el orden dentro de cada item (`position`). */
270
+ positionField?: string;
271
+ addLabel?: string;
272
+ emptyText?: string;
273
+ itemDisplay?: 'card' | 'row' | 'accordion';
274
+ }
275
+ export interface ListField extends FieldBase<Record<string, unknown>[]>, ListOptions {
276
+ type: 'list';
277
+ /** Campos de cada item (cualquier tipo, incluso `group` o `list`). */
278
+ itemFields: FieldSchema[];
279
+ /** Campo del item usado como título cuando está colapsado. */
280
+ itemTitle?: string;
281
+ /** El primer item tiene significado especial (ej. imagen principal). */
282
+ primaryFirst?: boolean;
283
+ }
284
+ export interface StringListField extends FieldBase<string[]>, ListOptions {
285
+ type: 'string-list';
286
+ /** Tipo permitido de cada item (base.md §9: solo text, url o email). */
287
+ itemType?: 'text' | 'url' | 'email';
288
+ }
289
+ export interface KeyValueItem {
290
+ key: string;
291
+ value: string;
292
+ }
293
+ export interface KeyValueField extends FieldBase<KeyValueItem[]>, ListOptions {
294
+ type: 'key-value';
295
+ }
296
+ /**
297
+ * Base de un widget de panel. Se declara en `ResourceSchema.widgets` — no
298
+ * en `fields`, porque un KPI no es un campo: no se valida, no se envía y
299
+ * `field-host` no lo renderiza. Sólo un recurso `kind: 'dashboard'` los
300
+ * dibuja, y agregar un widget es tocar el schema, no el template.
301
+ *
302
+ * Su `key` es además la clave del dato en la respuesta del endpoint del
303
+ * dashboard (`{ key: 'pendientes' }` ↔ `{ pendientes: 3 }`), salvo que el
304
+ * widget declare otra fuente (F8 · R6.5).
305
+ */
306
+ /**
307
+ * Fuente de datos de un widget: el mismo vocabulario declarado de F8 que usan
308
+ * relations y selects (D62), más el caso «payload del panel».
309
+ *
310
+ * - `DataSource` (`{ resource, view?, params? }`): el widget pide sus filas o
311
+ * su agregado al recurso declarado vía `DataSourceService`; deja de depender
312
+ * de que un handler haya armado `data[widget.key]`.
313
+ * - `{ kind: 'payload' }`: lee la respuesta COMPLETA del endpoint del
314
+ * dashboard (varias claves a la vez).
315
+ */
316
+ export type WidgetSource = DataSource | {
317
+ kind: 'payload';
318
+ };
319
+ export interface WidgetBase {
320
+ key: string;
321
+ label: string;
322
+ help?: string;
323
+ /** Ancho en la grilla del panel (1–12), igual que un campo. */
324
+ width?: GridWidth;
325
+ /**
326
+ * De dónde saca sus datos (F8 · R6.5).
327
+ *
328
+ * - **ausente** (por defecto): `data[widget.key]`. Es el contrato normal y
329
+ * el que usan los KPI (`{ key: 'pendientes' }` ↔ `{ pendientes: 3 }`).
330
+ * - **`{ kind: 'payload' }`**: la respuesta COMPLETA, para widgets que se
331
+ * componen de varias claves (`status-progress` leyendo `pendientes` +
332
+ * `entregados` + `cancelados`) sin que el orquestador tenga que saber
333
+ * qué tipo de widget es.
334
+ * - **`DataSource`**: las filas salen del recurso declarado (vista
335
+ * incluida), pedidas al fetcher común.
336
+ */
337
+ source?: WidgetSource;
338
+ }
339
+ /** KPI de una cifra suelta («3 pendientes»). */
340
+ export interface MetricCardWidget extends WidgetBase {
341
+ type: 'metric-card';
342
+ /** Texto secundario bajo la cifra (estático, va en el schema). */
343
+ hint?: string;
344
+ }
345
+ /** Barras por categoría con CSS/SVG — sin librería. */
346
+ export interface BarChartWidget extends WidgetBase {
347
+ type: 'bar-chart';
348
+ /** Sufijo de las cifras del eje («unidades», «S/»). */
349
+ unit?: string;
350
+ }
351
+ /**
352
+ * Serie temporal con Chart.js.
353
+ *
354
+ * La librería (61,4 K gz medidos) vive en el chunk que abre el descriptor
355
+ * (`import('./chart-line')`), así que **no entra en el bundle inicial**; el
356
+ * `@defer (on viewport)` que el propio componente dibuja tarda además la
357
+ * creación del lienzo hasta que el gráfico se ve. Ver
358
+ * `widgets/chart-line/chart-line.ts`.
359
+ */
360
+ export interface ChartLineWidget extends WidgetBase {
361
+ type: 'chart-line';
362
+ /** Sufijo de las cifras del eje («unidades», «S/»). */
363
+ unit?: string;
364
+ }
365
+ /** Tabla embebida de registros operativos (ej. pedidos que requieren atención). */
366
+ export interface RecordListWidget extends WidgetBase {
367
+ type: 'record-list';
368
+ /**
369
+ * Claves de columnas a mostrar en la tabla compacta.
370
+ *
371
+ * No lleva `resource`: si las filas vienen declaradas, ese recurso ya está
372
+ * en `source.resource` (D63 retira la clave duplicada — estaba declarada y
373
+ * nadie la leía).
374
+ */
375
+ columns: string[];
376
+ /** Acción de navegación por fila. */
377
+ action?: {
378
+ label: string;
379
+ routeTemplate: string;
380
+ };
381
+ }
382
+ /** Grid de botones de accesos directos operativos. */
383
+ export interface QuickActionsWidget extends WidgetBase {
384
+ type: 'quick-actions';
385
+ actions: {
386
+ label: string;
387
+ icon?: string;
388
+ navigateTo: string;
389
+ variant?: 'primary' | 'secondary' | 'outline';
390
+ }[];
391
+ }
392
+ /** Barra de distribución porcentual de estados o categorías. */
393
+ export interface StatusProgressWidget extends WidgetBase {
394
+ type: 'status-progress';
395
+ segments: {
396
+ key: string;
397
+ label: string;
398
+ color: 'success' | 'warning' | 'danger' | 'neutral';
399
+ }[];
400
+ }
401
+ export type WidgetSchema = MetricCardWidget | BarChartWidget | ChartLineWidget | RecordListWidget | QuickActionsWidget | StatusProgressWidget;
402
+ 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;
404
+ export type FieldType = FieldSchema['type'];
405
+ export type ListColumnFormat = 'text' | 'badge' | 'thumbnail' | 'boolean' | 'number' | 'currency' | 'date';
406
+ export interface ListColumn {
407
+ key: string;
408
+ label: string;
409
+ /**
410
+ * Sólo para sobreescribir: sin él manda la tabla de
411
+ * `columns/column-defaults` (`FORMATO_POR_DEFECTO`) según el `type` del
412
+ * campo con esta `key` (R4.4).
413
+ */
414
+ format?: ListColumnFormat;
415
+ }
416
+ export interface ListFilter {
417
+ key: string;
418
+ label: string;
419
+ type?: 'text' | 'select' | 'boolean';
420
+ options?: SelectOption[];
421
+ }
422
+ export interface LayoutSection {
423
+ /** Coincide con `field.section`. */
424
+ id: string;
425
+ label: string;
426
+ }
427
+ export type LayoutMode = 'grid' | 'tabs' | 'sections' | 'steps' | 'columns';
428
+ export interface ResourceLayout {
429
+ /** Presentación del formulario. */
430
+ mode?: LayoutMode;
431
+ sections?: LayoutSection[];
432
+ /** Número de columnas para `mode: 'columns'` (por defecto 2). */
433
+ columns?: 2 | 3;
434
+ }
435
+ export interface ResourceActionBase {
436
+ id: string;
437
+ label: string;
438
+ icon?: string;
439
+ variant?: 'primary' | 'secondary' | 'outline' | 'danger';
440
+ visibleWhen?: {
441
+ field: string;
442
+ op: 'eq' | 'neq' | 'in';
443
+ value: unknown;
444
+ }[];
445
+ }
446
+ export interface CopyShareAction extends ResourceActionBase {
447
+ type: 'copy-share';
448
+ urlTemplate: string;
449
+ shareTextTemplate?: string;
450
+ }
451
+ export interface NavigateAction extends ResourceActionBase {
452
+ type?: 'navigate';
453
+ /** Plantilla con marcadores, ej. `/products/{slug}` o `/admin/orders?id={ref}`. */
454
+ urlTemplate: string;
455
+ }
456
+ export interface TriggerAction extends ResourceActionBase {
457
+ /**
458
+ * Acción de dominio **declarada en el back** (B3 `actions.<name>`,
459
+ * publicada como `POST /admin/<recurso>/:id/actions/<name>` — R5.4).
460
+ * El destino no se declara acá: la ruta lógica se deriva en R3.12.
461
+ */
462
+ type: 'trigger';
463
+ /** Nombre de la acción en el back si difiere del `id` de esta declaración. */
464
+ name?: string;
465
+ confirmMessage?: string;
466
+ }
467
+ /** Acciones extra de registro o listado (duplicar, compartir, navegar…). */
468
+ export type ResourceAction = CopyShareAction | NavigateAction | TriggerAction;
469
+ /** Configuración de diálogo/modal o acción tras crear un registro con éxito. */
470
+ export interface PostCreateSchema {
471
+ mode: 'modal' | 'toast' | 'redirect_detail' | 'stay';
472
+ title?: string;
473
+ description?: string;
474
+ summaryFields?: string[];
475
+ actions?: ResourceAction[];
476
+ }
477
+ /** Configuración de Ficha de Detalle (modo lectura estructurado). */
478
+ export interface DetailViewSchema {
479
+ enabled: boolean;
480
+ headerFields?: string[];
481
+ sections?: {
482
+ id: string;
483
+ label: string;
484
+ fields: string[];
485
+ }[];
486
+ actions?: ResourceAction[];
487
+ allowEdit?: boolean;
488
+ allowDelete?: boolean;
489
+ }
490
+ /** Operaciones permitidas (base.md §3). `undefined` = permitido. */
491
+ export interface ResourcePermissions {
492
+ create?: boolean;
493
+ update?: boolean;
494
+ remove?: boolean;
495
+ reorder?: boolean;
496
+ }
497
+ /**
498
+ * 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`.
501
+ */
502
+ export interface ResourceEndpoints {
503
+ list?: string;
504
+ get?: string;
505
+ create?: string;
506
+ update?: string;
507
+ remove?: string;
508
+ }
509
+ export interface ResourceSchema {
510
+ /** Identificador interno (`categories`). */
511
+ id: string;
512
+ label: string;
513
+ labelPlural: string;
514
+ /**
515
+ * `collection` → listado, `singleton` → formulario directo,
516
+ * `dashboard` → panel de sólo lectura (refactormotor.md F4; sus datos llegan
517
+ * un endpoint declarativo, no de CRUD).
518
+ */
519
+ kind: 'collection' | 'singleton' | 'dashboard';
520
+ endpoint: ResourceEndpoints;
521
+ /** Campo que identifica el registro (`slug`). */
522
+ keyField: string;
523
+ /** Campo que se muestra como nombre del registro. */
524
+ titleField: string;
525
+ /** Si el listado se reordena (requiere `positionField`). */
526
+ sortable?: boolean;
527
+ positionField?: string;
528
+ listColumns: ListColumn[];
529
+ filters?: ListFilter[];
530
+ /** Campos buscables en el listado. */
531
+ search?: string[];
532
+ /** Árbol de campos del formulario (base.md §4–§6). */
533
+ fields: FieldSchema[];
534
+ /**
535
+ * Widgets del panel — sólo `kind: 'dashboard'` (refactormotor.md F4).
536
+ *
537
+ * Van aparte de `fields`: un KPI no es un campo y no comparte `FieldBase`
538
+ * (`required`/`visibleWhen`/`validators`…). Decisión tomada tras medir el
539
+ * ripple de unirlos a `FieldSchema`: 18+ errores de tipos en 4 ficheros.
540
+ * `contract-check` sólo recorre `fields`, así que esta propiedad no se
541
+ * compara back↔front — el widget es config de UI, no dato almacenado.
542
+ */
543
+ widgets?: WidgetSchema[];
544
+ layout?: ResourceLayout;
545
+ actions?: ResourceAction[];
546
+ /** Configuración de Ficha de Detalle estructurada para el recurso. */
547
+ detail?: DetailViewSchema;
548
+ /** Configuración de diálogo/modal post-creación. */
549
+ postCreate?: PostCreateSchema;
550
+ permissions?: ResourcePermissions;
551
+ /**
552
+ * Acceso declarado por recurso (auth.md §5.3) — **sólo UX**: oculta
553
+ * ruta, entrada de menú y botones. Aunque el front se manipule, el back
554
+ * vuelve a verificar en cada petición: esto nunca es una barrera.
555
+ *
556
+ * No lo declara ningún schema mientras sólo exista el rol `admin`
557
+ * (§9.4): sin declaración el efecto es «sesión», que es lo correcto hoy.
558
+ * Aparece el día que haya un segundo rol.
559
+ */
560
+ access?: ResourceAccess;
561
+ /**
562
+ * Claves de `fields` que el listado edita en un drawer sin navegar
563
+ * (plan 6.5, guía §5: «edición rápida de pocos campos, panel lateral en
564
+ * lugar de pantalla nueva»). 1–3 campos; si no se declara, no hay atajo.
565
+ */
566
+ quickEdit?: string[];
567
+ /** Rutas efectivas por operación, con la forma pública si la declara. */
568
+ operations?: ResourceOperations;
569
+ /** Vistas declaradas como metadatos (sin operandos internos). */
570
+ views?: Record<string, ViewDescriptor>;
571
+ /** Páginas y rutas del frontend; separado de `views` del backend. */
572
+ uiViews?: Record<string, UiViewDescriptor>;
573
+ /** Lo mínimo del backend para menú/acciones: `access` normalizado. */
574
+ policies?: ResourcePolicies;
575
+ }
576
+ /** Forma pública de la respuesta de una operación (declaración `shape`). */
577
+ export interface OperationShape {
578
+ pick?: 'full' | 'list' | string[];
579
+ include?: string[];
580
+ rename?: Record<string, string>;
581
+ nest?: Record<string, string[]>;
582
+ envelope?: 'plain' | 'list';
583
+ }
584
+ /** Ruta efectiva de una operación: método + path (con `{key}` si aplica). */
585
+ export interface OperationDescriptor {
586
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
587
+ path: string;
588
+ shape?: OperationShape;
589
+ }
590
+ /** Operaciones CRUD efectivas (derivadas de la misma ruta que el router). */
591
+ export interface ResourceOperations {
592
+ list?: OperationDescriptor;
593
+ get?: OperationDescriptor;
594
+ create?: OperationDescriptor;
595
+ update?: OperationDescriptor;
596
+ remove?: OperationDescriptor;
597
+ }
598
+ /**
599
+ * Condición de vista como metadato público: el literal `value` y el `ref`
600
+ * (metadato de relación) viajan; el operando interno `from: '$item.x'` no.
601
+ */
602
+ export interface ViewCondition {
603
+ field: string;
604
+ op: string;
605
+ not?: boolean;
606
+ value?: unknown;
607
+ ref?: string;
608
+ }
609
+ /** Vista declarada publicada como metadato (F7-1). */
610
+ export interface ViewDescriptor {
611
+ where?: ViewCondition[];
612
+ sort?: {
613
+ field: string;
614
+ dir?: 'asc' | 'desc';
615
+ }[];
616
+ limit?: number;
617
+ extends?: string;
618
+ /** Vista con handler propio (escape hatch): su existencia, nada más. */
619
+ custom?: boolean;
620
+ }
621
+ /** Config para una `uiView` de tipo `calendar`. */
622
+ export interface CalendarViewConfig {
623
+ /** Campo `date` (o `datetime` futuro): inicio del evento. Obligatorio. */
624
+ startField: string;
625
+ /** Campo `date`/`time`: fin. Ausente ⇒ evento de 1 día sobre `startField`. */
626
+ endField?: string;
627
+ /** Campo `text`/`relation`: rótulo del evento. Obligatorio. */
628
+ titleField: string;
629
+ /** Campo `select`/`text`: color/estado (token de paleta o valor crudo). */
630
+ colorField?: string;
631
+ /** Vista por defecto al abrir. Por defecto `month`. */
632
+ defaultView?: 'month' | 'week' | 'day' | 'agenda';
633
+ /** Origen de filas; ausente ⇒ la propia instancia del recurso (su listado). */
634
+ source?: DataSource;
635
+ }
636
+ /** Config para una `uiView` de tipo `kanban`. */
637
+ export interface KanbanViewConfig {
638
+ /** Campo `select`/`relation`: columna por valor. Obligatorio. */
639
+ groupField: string;
640
+ /** Orden estable de columnas. Valores no declarados ⇒ columna extra al final. */
641
+ columns: KanbanColumn[];
642
+ /** Rótulo de la tarjeta; ausente ⇒ `keyField` del recurso. */
643
+ titleField?: string;
644
+ /** Segundo renglón de la tarjeta (opcional). */
645
+ subtitleField?: string;
646
+ /** Origen de filas; ausente ⇒ la propia instancia del recurso. */
647
+ source?: DataSource;
648
+ }
649
+ /** Una columna del tablero Kanban. */
650
+ export interface KanbanColumn {
651
+ /** Valor del `groupField` que agrupa esta columna. */
652
+ value: string;
653
+ /** Etiqueta visible de la columna. */
654
+ label: string;
655
+ }
656
+ /**
657
+ * Evento que dispara un `select` declarado (R9.9.4): `open`, `select` o una
658
+ * acción con nombre (`open-detail`). El string libre cubre las acciones con
659
+ * nombre sin perder autocompletado para los dos literales fijos.
660
+ */
661
+ export type SelectionTrigger = 'open' | 'select' | (string & {});
662
+ /**
663
+ * Destino de navegación declarado (R9.9.4 · spec view-navigation): recurso +
664
+ * `uiView` declarada + params de ruta permitidos. Las URLs crudas o
665
+ * externas NO son destinos válidos — el validador las rechaza pre-render.
666
+ */
667
+ export interface NavigationTarget {
668
+ /** Recurso declarado (`schema.id` del front). */
669
+ resource: string;
670
+ /** Clave de `uiViews` en ese recurso. */
671
+ view: string;
672
+ /** Nombres de params de la ruta del destino (`:key` ⇒ `['key']`). */
673
+ params?: string[];
674
+ }
675
+ /**
676
+ * Vinculación selección ⇒ transición declarada en una `uiView` o en un hijo
677
+ * de composición (R9.9.4). `route` exige un `NavigationTarget` declarado;
678
+ * `inline` selecciona en local y `none` es un no-op explícito.
679
+ */
680
+ export type SelectionBinding = {
681
+ on: SelectionTrigger;
682
+ mode: 'none' | 'inline';
683
+ } | {
684
+ on: SelectionTrigger;
685
+ mode: 'route';
686
+ target: NavigationTarget;
687
+ };
688
+ /** Base común de todo hijo de una composición embebida (R9.9.2). */
689
+ export interface ViewChildBase {
690
+ /** Identificador estable y único entre hermanos del mismo contenedor. */
691
+ id: string;
692
+ /** Etiqueta visible (rótulo de la pestaña). */
693
+ label: string;
694
+ /** Conserva el estado al cambiar de pestaña; ausente ⇒ destruir (R9.9.5). */
695
+ retain?: boolean;
696
+ /** Visibilidad declarativa (condiciones sobre campos del formulario). */
697
+ visibleWhen?: FieldCondition[];
698
+ }
699
+ /**
700
+ * Hijo declarado inline (`type` + `config`) o por referencia a una `uiView`
701
+ * del mismo schema (`ref`). Los `ref` hacen posible que un contenedor
702
+ * contenga —directa o transitivamente— a sí mismo: por eso el
703
+ * `composition-validator` es obligatorio antes del render (D2).
704
+ */
705
+ export type ViewChildNode = (ViewChildBase & UiViewDescriptor) | (ViewChildBase & {
706
+ ref: string;
707
+ });
708
+ /** Config de una `uiView` contenedora `tabs` (R9.9.2). */
709
+ export interface TabsConfig {
710
+ /** Lista ordenada de hijos; el orden declarado es el orden de render. */
711
+ children: ViewChildNode[];
712
+ /** `id` del hijo seleccionado al montar; ausente ⇒ el primero. */
713
+ initial?: string;
714
+ }
715
+ /** Vista `tabs` declarada en el schema local del frontend. */
716
+ export interface UiViewTabs extends UiViewBase {
717
+ type: 'tabs';
718
+ config: TabsConfig;
719
+ }
720
+ /** Base común de toda `uiView`: tipo + ruta relativa al recurso. */
721
+ export interface UiViewBase {
722
+ /** Tipo resuelto por `kind: 'view'` en el registro de render. */
723
+ type: string;
724
+ /** Ruta relativa al recurso; ausente o vacía significa la ruta raíz. */
725
+ path?: string;
726
+ /**
727
+ * Binding de selección declarado (R9.9.4). Ausente ⇒ la hoja conserva su
728
+ * comportamiento local y NO navega; una URL cruda nunca es un destino
729
+ * válido (el `composition-validator` la rechaza antes del render, D2).
730
+ */
731
+ select?: SelectionBinding;
732
+ }
733
+ /** Vista `calendar` declarada en el schema local del frontend. */
734
+ export interface UiViewCalendar extends UiViewBase {
735
+ type: 'calendar';
736
+ config: CalendarViewConfig;
737
+ }
738
+ /** Vista `kanban` declarada en el schema local del frontend. */
739
+ export interface UiViewKanban extends UiViewBase {
740
+ type: 'kanban';
741
+ config: KanbanViewConfig;
742
+ }
743
+ /**
744
+ * Una página/ruta declarada en el schema local del frontend, discriminada
745
+ * por `type`. Los tipos base (`list`/`detail`/`form`/`dashboard`) conservan
746
+ * la forma plana sin `config`; `calendar` y `kanban` llevan su config tipada.
747
+ */
748
+ export type UiViewDescriptor = UiViewBase | UiViewCalendar | UiViewKanban | UiViewTabs;
749
+ /** Políticas públicas: sólo `access`, lo mínimo para menú/acciones. */
750
+ export interface ResourcePolicies {
751
+ access: {
752
+ read: string;
753
+ write: string;
754
+ /** R7.5: override por operación (B8 · R7.4a) */
755
+ operations?: Record<string, string>;
756
+ };
757
+ }
758
+ /** §5.3 — `access` de un recurso. `requires` usa el vocabulario de §4.4. */
759
+ export interface ResourceAccess {
760
+ requires?: string;
761
+ roles?: string[];
762
+ }
763
+ /** Un campo de la pantalla de login (auth.md §5.1). */
764
+ export interface AuthFieldDeclaration {
765
+ key: string;
766
+ /** `email` | `password` — tipos primitivos; el registro de render los
767
+ * absorberá en Fase 3 (R3.3), cuando exista el descriptor `password`. */
768
+ type: string;
769
+ required?: boolean;
770
+ }
771
+ /**
772
+ * Pantalla de login declarada (auth.md §5.1): **vive sólo en el schema del
773
+ * front** — el back no conoce estas etiquetas ni este layout, sólo declara
774
+ * el `input` de `/auth/login`. El `contract-check` (R2.11) verifica que las
775
+ * claves de `loginFields` existan en ese `input`.
776
+ */
777
+ export interface AuthSchema {
778
+ /** Proveedores disponibles. §9.1 eligió B: **sólo `password`**. */
779
+ providers: string[];
780
+ loginFields: AuthFieldDeclaration[];
781
+ redirectAfterLogin: string;
782
+ /**
783
+ * D10-3 → **`false` siempre.** La opción «recordarme» con `localStorage`
784
+ * quedó denegada (§9.3): el token vive en `sessionStorage` y muere con la
785
+ * pestaña. Declarar `true` prometería un storage que nadie usa.
786
+ */
787
+ rememberMe: boolean;
788
+ }
789
+ /** Fija el catálogo (uso interno de `provideGasAdmin`; testable directo). */
790
+ export declare function setSchemaCatalog(schemas: readonly ResourceSchema[], auth?: AuthSchema): void;
791
+ /** Busca un schema por `id`; `undefined` si no existe. */
792
+ export declare function getSchema(id: string): ResourceSchema | undefined;
793
+ /** Catálogo completo en orden (menú lateral). */
794
+ export declare function allSchemas(): readonly ResourceSchema[];
795
+ /** Declaración de la pantalla de login; `undefined` sin `auth` en config. */
796
+ export declare function getAuth(): AuthSchema | undefined;
797
+ /** Token DI del catálogo (para inyectarlo donde un `inject()` es posible). */
798
+ export declare const GAS_SCHEMAS: InjectionToken<readonly ResourceSchema[]>;
799
+ /** Configuración del motor. */
800
+ interface GasAdminConfig {
801
+ /** Catálogo ordenado de recursos (menú + rutas + resolución `getSchema`). */
802
+ schemas: readonly ResourceSchema[];
803
+ /** Declaración de la pantalla de login (opcional: sin ella, sin `/login`). */
804
+ auth?: AuthSchema;
805
+ }
806
+ /**
807
+ * Wiring completo del motor: catálogo + HTTP con el interceptor de
808
+ * errores del envelope + loaders de config y sesión en el bootstrap
809
+ * (antes en el `app.config` del consumidor).
810
+ *
811
+ * El consumidor conserva `provideRouter` y `{ provide: API_URL, ... }`
812
+ * (su environment decide la URL del Web App).
813
+ */
814
+ export declare function provideGasAdmin(config: GasAdminConfig): (Provider | EnvironmentProviders)[];
815
+ /**
816
+ * Contrato HTTP con el backend (doc/baseapi.md §8 «Errores»).
817
+ *
818
+ * El backend responde SIEMPRE HTTP 200; los errores viajan en el body
819
+ * con `{ error: { status, message, errors?: [{ path, message }] } }`.
820
+ * El interceptor (Fase 3) los convierte en `ApiError`.
821
+ */
822
+ /**
823
+ * Un error con ruta del422 (auth.md §6): `path` para pintarlo en el campo y
824
+ * `code` MAYUS_SNAKE (`VALIDATION_MIN_LENGTH`) para mapearlo sin leer texto.
825
+ */
826
+ export interface ApiIssue {
827
+ /** Ruta del campo afectado (`faq[2].answer`, `sku`). */
828
+ path: string;
829
+ /** Mensaje en español, listo para pintar. */
830
+ message: string;
831
+ /** Código estable del validador (B11); el mensaje puede cambiar, el código no. */
832
+ code?: string;
833
+ }
834
+ /** Shape del body de error (doc/baseapi.md §8 «Errores»). */
835
+ export interface ApiErrorBody {
836
+ error: {
837
+ status: number;
838
+ message: string;
839
+ /** Ruta del campo afectado cuando el backend la envía (base.md §10). */
840
+ path?: string;
841
+ /**
842
+ * Código de sesión de §4.5 (R2.9): `AUTH_REQUIRED` · `AUTH_INVALID` ·
843
+ * `AUTH_EXPIRED` · `FORBIDDEN` · `RATE_LIMITED`. Aparece SÓLO en esas
844
+ * cinco respuestas — el sobre de error es aditivo (O7 / D10-5).
845
+ */
846
+ code?: string;
847
+ /**
848
+ * Errores de validación con ruta (base.md §5, guía §10): el 422 los manda
849
+ * ACÁ (`error.errors[]`), no en `path`. Sin modelarlos el form veía
850
+ * «Errores de validación» genérico y ningún campo se pintaba (D42).
851
+ */
852
+ errors?: ApiIssue[];
853
+ };
854
+ }
855
+ /** Error de API estandarizado que lanza el interceptor de errores. */
856
+ export declare class ApiError extends Error {
857
+ readonly status: number;
858
+ /** Ruta del campo que provocó el error (ej. `faq[2].answer`), si existe. */
859
+ readonly fieldPath?: string;
860
+ /** Código de sesión de §4.5 si el backend lo envió; `undefined` en el resto. */
861
+ readonly code?: string;
862
+ /** Issues con ruta (422); vacío cuando el error no trae `errors[]`. */
863
+ readonly issues: readonly ApiIssue[];
864
+ constructor(status: number, message: string, fieldPath?: string, code?: string, issues?: readonly ApiIssue[]);
865
+ }
866
+ /**
867
+ * `meta.renewedToken` (auth.md §3.2/§3.4): el back lo adjunta a la respuesta
868
+ * cuando la vida del token bajó del umbral de renovación. Es el ÚNICO modo
869
+ * de que el front se entere — el token nuevo no viaja en cabeceras ni en la
870
+ * URL. Devuelve `null` cuando no hay renovación (que es lo normal).
871
+ *
872
+ * Vive acá y no en el `ApiService` porque lo consumen DOS servicios
873
+ * (`ApiService` y `AuthService`) y éste modelo no importa a ninguno de los
874
+ * dos: sin este archivo no hay ciclo de imports.
875
+ */
876
+ export declare function renewedTokenOf(body: unknown): string | null;
877
+ /**
878
+ * URL base de la API (baseapi §2.3): es la URL directa del Web App de Apps
879
+ * Script; ahí se hace el `POST` del envelope `{ token, method, path, payload }`.
880
+ * OBLIGATORIO proporcionarlo en `app.config.ts`:
881
+ * `{ provide: API_URL, useValue: 'https://…' }` (Fase 3). Vacío = mismo origen.
882
+ */
883
+ export declare const API_URL: InjectionToken<string>;
884
+ /**
885
+ * Guards de sesión (auth.md §5.3).
886
+ *
887
+ * Dos puertas y ninguna es una barrera de seguridad: el back re-verifica
888
+ * `accessLevel` en **cada** petición desde R2.6. Éstas sólo evitan pintar
889
+ * una pantalla que no le toca al usuario — enseñarle un formulario que va
890
+ * a recibir 403 es mala UX, no un agujero.
891
+ *
892
+ * `authGuard` va en la ruta madre (todo el shell); `roleGuard` en cada
893
+ * `:id`, que es donde aparece el recurso que puede tener `access`.
894
+ */
895
+ /** Sin sesión → login. Corre ANTES que cualquier ruta del shell. */
896
+ export declare const authGuard: CanActivateFn;
897
+ /**
898
+ * El recurso declara `access` y esta sesión no lo cumple → se cancela la
899
+ * navegación. No hay pantalla de 403 (tampoco la pide §5.3) y el menú ya
900
+ * oculta la entrada, así que queda sólo el caso de alguien tecleando la URL.
901
+ */
902
+ declare const roleGuard: CanActivateFn;
903
+ /**
904
+ * Rutas de los schemas recibidos (por defecto, la registry completa).
905
+ * Cada schema se valida con `validateComposition` ANTES de generar rutas
906
+ * (D3): una composición inválida revienta al arrancar, no en el render.
907
+ */
908
+ export declare function resourceRoutes(catalog?: readonly ResourceSchema[]): Route[];
909
+ /**
910
+ * Las rutas del shell salen de `uiViews`, no de `ResourceSchema.views` (que
911
+ * conserva los metadatos de consultas del backend). Añadir o cambiar una
912
+ * página requiere una declaración y un descriptor `kind: 'view'`.
913
+ *
914
+ * Los hijos se construyen DENTRO del `loadChildren` (en la primera
915
+ * navegación), nunca en la evaluación del módulo: el consumidor importa
916
+ * este barrel desde `app.routes.ts` ANTES de que `provideGasAdmin` siembre
917
+ * el catálogo (corre en el cuerpo de `app.config`), y el paquete compilado
918
+ * es eager — construir en el import congelaría `children: []` para siempre
919
+ * y toda URL protegida renderizaría vacío (regresión: shell.routes.spec en
920
+ * orden de producción).
921
+ */
922
+ export declare const shellRoutes: Routes;
923
+ /**
924
+ * Login (auth.md §5.1).
925
+ *
926
+ * **Pintada a partir de `authSchema.loginFields`**: si mañana cambian los
927
+ * campos, éste componente no se toca — sólo se mueven claves y etiquetas.
928
+ * No usa `FieldHost` porque el tipo `password` todavía no existe en
929
+ * `FieldSchema` (es pieza nueva de Fase 3, R3.3); el `@switch` de abajo es
930
+ * exactamente el reemplazo que R3.x hará por el registro de render.
931
+ *
932
+ * **No hay casilla «recordarme»**: D10-3 negó `localStorage` (§9.3) y el
933
+ * token muere con la pestaña. Poner un checkbox sería prometer que la
934
+ * sesión sobrevive a cerrar el navegador.
935
+ */
936
+ export declare class Login {
937
+ private readonly auth;
938
+ private readonly router;
939
+ readonly schema: import("@tinya-code/gas-admin-ui").AuthSchema;
940
+ readonly error: import("@angular/core").WritableSignal<string | null>;
941
+ readonly loading: import("@angular/core").WritableSignal<boolean>;
942
+ /** Valores por clave: el set lo pinta el `@for` del schema. */
943
+ private readonly values;
944
+ label(key: string): string;
945
+ value(key: string): string;
946
+ setValue(key: string, next: string): void;
947
+ submit(event: Event): void;
948
+ private goAfterLogin;
949
+ static ɵfac: i0.ɵɵFactoryDeclaration<Login, never>;
950
+ static ɵcmp: i0.ɵɵComponentDeclaration<Login, "app-login", never, {}, {}, never, never, true, never>;
951
+ }
952
+ type ToastKind$1 = 'success' | 'error' | 'info';
953
+ interface Toast$1 {
954
+ id: number;
955
+ kind: ToastKind$1;
956
+ message: string;
957
+ }
958
+ interface ConfirmOptions {
959
+ title: string;
960
+ message: string;
961
+ confirmLabel?: string;
962
+ cancelLabel?: string;
963
+ }
964
+ /**
965
+ * Notificaciones de la app: toasts (señales, se renderizan en `app-toast`,
966
+ * Fase 5) y un diálogo de confirmación con respuesta como `Promise`
967
+ * (base.md §10: borrado con confirmación, aviso de cambios sin guardar).
968
+ */
969
+ export declare class NotificationService {
970
+ readonly toasts: import("@angular/core").WritableSignal<Toast$1[]>;
971
+ /** Confirmación en curso; `null` = sin diálogo. */
972
+ readonly confirmation: import("@angular/core").WritableSignal<ConfirmOptions | null>;
973
+ private nextId;
974
+ private pendingResolve;
975
+ success(message: string): void;
976
+ error(message: string): void;
977
+ info(message: string): void;
978
+ dismiss(id: number): void;
979
+ /** Abre el diálogo de confirmación y espera la respuesta del usuario. */
980
+ confirm(options: ConfirmOptions): Promise<boolean>;
981
+ /** Resuelve la confirmación pendiente (lo llama el componente de diálogo, Fase 5). */
982
+ resolveConfirmation(accepted: boolean): void;
983
+ private push;
984
+ static ɵfac: i0.ɵɵFactoryDeclaration<NotificationService, never>;
985
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
986
+ }
987
+ /** Métodos que entiende el envelope del backend (baseapi §2.3). */
988
+ type EnvelopeMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
989
+ /**
990
+ * CRUD genérico sobre el contrato v2 (baseapi §2.3): TODA llamada a la API
991
+ * es un `POST` a `API_URL` con cuerpo `text/plain` y el JSON
992
+ * `{ token, method, path, payload }`. `method: 'GET'` también para lecturas;
993
+ * el token viaja en el cuerpo (nunca en cabeceras: criterio 11 de §17).
994
+ *
995
+ * `text/plain` en lugar de `application/json` evita el preflight CORS
996
+ * (simple request) — criterio 11: sin cabeceras custom.
997
+ *
998
+ * **R2.8 — convención sobre el snippet.** `auth.md` §5.2 llama a las claves
999
+ * `{ route, op, token, payload }`, pero el backend (`parseEnvelope_`) lee
1000
+ * `path` y `method`: el snippet es un esquema a medias, no un contrato.
1001
+ * Mismo precedente que en R2.5 — **la convención del código gana**. Cambiar
1002
+ * el nombre ahora rompería el único transporte que existe.
1003
+ *
1004
+ * **R2.12 — el token sale de `AuthService`, no de configuración.** El
1005
+ * `ADMIN_TOKEN` (D21-C) se retiró: no hay credencial fija, hay sesión.
1006
+ *
1007
+ * Devuelve el JSON plano tal cual, sin transformaciones ocultas
1008
+ * (base.md §1, principio 4). Los errores del backend (200 + `{ error: {…} }`)
1009
+ * los convierte en `ApiError` el interceptor `apiErrorInterceptor`, que es
1010
+ * también quien pasa los `AUTH_*` a `AuthService.applyAuthCode` (R2.9).
1011
+ */
1012
+ export declare class ApiService {
1013
+ private readonly http;
1014
+ private readonly apiUrl;
1015
+ private readonly auth;
1016
+ /**
1017
+ * Listado. El backend envuelve la respuesta cuando la operación declara
1018
+ * `shape.envelope: 'list'` (F5) ⇒ `{ items, nextCursor, total }` (R6.4);
1019
+ * la UI consume `T[]` en todos lados (F7-3) y este es el ÚNICO punto que
1020
+ * habla de listas, así que el desenvolvimiento vive acá. `nextCursor` es
1021
+ * el token de la página siguiente y `null` en la última — éste servicio
1022
+ * lo descarta hoy porque la lista no pagina todavía (backlog-ui-ux).
1023
+ * Un listado sin envelope (arreglo plano, el contrato histórico) pasa
1024
+ * tal cual.
1025
+ *
1026
+ * `payload` sólo lo usa una fuente declarada (F8 · R6.5): `{ view, params }`
1027
+ * pide el listado filtrado por una vista y sus parámetros. Sin él, la
1028
+ * llamada es la de siempre — la lectura llana del recurso.
1029
+ */
1030
+ list<T>(endpoint: ResourceEndpoints, payload?: Record<string, unknown>): Observable<T[]>;
1031
+ /** `key` solo aplica a colecciones (`{key}` en la plantilla); los singleton no la llevan. */
1032
+ get<T>(endpoint: ResourceEndpoints, key?: string): Observable<T>;
1033
+ create<T>(endpoint: ResourceEndpoints, payload: Record<string, unknown>): Observable<T>;
1034
+ update<T>(endpoint: ResourceEndpoints, key: string | undefined, payload: Record<string, unknown>): Observable<T>;
1035
+ remove(endpoint: ResourceEndpoints, key?: string): Observable<void>;
1036
+ /**
1037
+ * Operación genérica sobre una ruta ya resuelta: reorder de colección
1038
+ * (`PUT /admin/x` con `{ reorder: … }`), `/admin/schema`,
1039
+ * `/admin/upload-signature`, etc.
1040
+ */
1041
+ request<T>(method: EnvelopeMethod, path: string | undefined, payload?: Record<string, unknown>): Observable<T>;
1042
+ private send;
1043
+ private withKey;
1044
+ static ɵfac: i0.ɵɵFactoryDeclaration<ApiService, never>;
1045
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1046
+ }
1047
+ /**
1048
+ * Sesión del admin (auth.md §5.2) — **única dueña** del token y del usuario.
1049
+ *
1050
+ * Qué vive acá y por qué:
1051
+ *
1052
+ * - **Señales** `user` / `token` / `isAuthenticated` / `role`: el único
1053
+ * estado que el resto de la app lee (guards, menú, interceptor).
1054
+ * - **`login()` / `logout()` / `logoutAll()` / `restore()`**: operaciones de sesión.
1055
+ * El `AuthService` NO inyecta `ApiService` — sólo `HttpClient` — porque
1056
+ * `ApiService` inyecta a éste para leer el token, y un ciclo de DI en
1057
+ * Angular es un error en tiempo de arranque, no un warning.
1058
+ * - **`applyAuthCode()` (R2.9): EL único intérprete de la tabla de §4.5.**
1059
+ * Ningún otro archivo decide qué pasa ante `AUTH_*` o `FORBIDDEN`.
1060
+ * - **Almacenamiento (R2.12): `sessionStorage`, siempre.** `rememberMe`
1061
+ * no puede usar `localStorage` porque D10-3 lo negó — ver más abajo.
1062
+ *
1063
+ * La sesión arranca desde el storage y `restore()` la revalida contra
1064
+ * `/auth/me` en el initializer: un usuario desactivado mientras la pestaña
1065
+ * estaba abierta cae al primer reload, no en mitad de una edición.
1066
+ */
1067
+ /** Usuario tal y como lo sirve `publicUser_` (lista blanca, R2.17). */
1068
+ interface SessionUser {
1069
+ id?: string;
1070
+ email?: string;
1071
+ name?: string;
1072
+ role?: string;
1073
+ active?: boolean;
1074
+ last_login_at?: string;
1075
+ created_at?: string;
1076
+ }
1077
+ /** Los cinco códigos de §4.5. El resto de errores no son de sesión. */
1078
+ type AuthErrorCode = 'AUTH_REQUIRED' | 'AUTH_INVALID' | 'AUTH_EXPIRED' | 'FORBIDDEN' | 'RATE_LIMITED';
1079
+ /** Ruta de login: fuera del shell para que ningún guard la proteja. */
1080
+ declare const LOGIN_PATH = "/login";
1081
+ /**
1082
+ * Claves del `sessionStorage`. Exportadas porque **la sesión se restaura
1083
+ * desde ahí en el constructor**: un test que quiera una sesión arrancando
1084
+ * tiene que sembrarlas antes de inyectar el servicio, y el que las use con
1085
+ * otro nombre quebraría el restore en silencio.
1086
+ */
1087
+ export declare const SESSION_TOKEN_KEY = "admin.session.token";
1088
+ export declare const SESSION_USER_KEY = "admin.session.user";
1089
+ export declare class AuthService {
1090
+ private readonly http;
1091
+ private readonly apiUrl;
1092
+ private readonly router;
1093
+ /**
1094
+ * D10-3: **sólo `sessionStorage`.** La opción «recordarme» con
1095
+ * `localStorage` quedó denegada en §9.3 — muere con la pestaña ⇒ cero
1096
+ * token legible por un XSS futuro. `authSchema.rememberMe` es `false`
1097
+ * por la misma razón: declarar `true` sería prometer un storage que
1098
+ * este servicio no usa.
1099
+ *
1100
+ * Nunca en la URL ni en el cuerpo de un GET (§5.4): el token sólo viaja
1101
+ * dentro del envelope `{ token, method, path, payload }`.
1102
+ */
1103
+ private readonly storage;
1104
+ private readonly _user;
1105
+ private readonly _token;
1106
+ /** Ruta previa a un `AUTH_EXPIRED`, para volver tras reloguear (§4.5). */
1107
+ private readonly _returnUrl;
1108
+ readonly user: import("@angular/core").Signal<SessionUser | null>;
1109
+ readonly token: import("@angular/core").Signal<string | null>;
1110
+ readonly returnUrl: import("@angular/core").Signal<string | null>;
1111
+ /** Sólo hay sesión con token Y usuario: uno sin el otro es estado corrupto. */
1112
+ readonly isAuthenticated: import("@angular/core").Signal<boolean>;
1113
+ readonly role: import("@angular/core").Signal<string>;
1114
+ constructor();
1115
+ login(email: string, password: string): Observable<SessionUser>;
1116
+ /**
1117
+ * Revocación best-effort (§9.7): se pide al back que ponga el `sid` en la
1118
+ * lista de revocados, pero **la sesión local se ciere haya o no respuesta**.
1119
+ * Un logout que «falla» por red y deja al usuario dentro es peor que uno
1120
+ * que deja una sesión muerta en el servidor.
1121
+ */
1122
+ logout(): Observable<void>;
1123
+ /** Revoca las sesiones del usuario en todos sus dispositivos. */
1124
+ logoutAll(): Observable<{
1125
+ effectiveWithinSeconds: number;
1126
+ }>;
1127
+ /** Revalida la sesión restaurada contra `/auth/me`; sin token ⇒ sin viaje. */
1128
+ restore(): Observable<void>;
1129
+ /**
1130
+ * Renovación deslizante (§3.2): el back manda `meta.renewedToken` cuando
1131
+ * la vida del token bajó del umbral. Se persiste el MISMO día que el
1132
+ * token original — el `sid` no cambia, sólo se emite una firma nueva.
1133
+ */
1134
+ applyRenewedToken(token: string): void;
1135
+ /**
1136
+ * ÚNICO intérprete de los códigos de sesión (auth.md §4.5 / §5.2).
1137
+ * El interceptor `apiErrorInterceptor` lo llama y nada más: cualquier
1138
+ * archivo que decida «si ve AUTH_EXPIRED hago X» está fuera de contrato.
1139
+ *
1140
+ * | Código | Qué hace |
1141
+ * | --------------- | ------------------------------------------- |
1142
+ * | `AUTH_REQUIRED` | limpia sesión y va a login |
1143
+ * | `AUTH_INVALID` | limpia sesión y va a login |
1144
+ * | `AUTH_EXPIRED` | guarda la ruta previa, limpia, va a login |
1145
+ * | `FORBIDDEN` | **no** cierra sesión (el usuario sigue dentro) |
1146
+ * | `RATE_LIMITED` | no hace nada: lo espera el formulario |
1147
+ */
1148
+ applyAuthCode(code: string | undefined): void;
1149
+ /** ¿El rol de la sesión cubre `required`? (mínimo: admin ≥ staff, §4.4) */
1150
+ hasRole(required: string): boolean;
1151
+ /**
1152
+ * §5.3 — el ÚNICO criterio de visibilidad del front (menú, rutas y
1153
+ * botones). `requires` viene del vocabulario de §4.4: `public` no pide
1154
+ * sesión; todo lo demás sí; `roles` estrecha.
1155
+ *
1156
+ * Es **pura decisión de UI**. El back re-verifica `accessLevel` en cada
1157
+ * petición (R2.6), así que filtrar acá no protege nada: sólo evita
1158
+ * enseñarle a alguien un botón que le va a devolver 403.
1159
+ */
1160
+ canAccess(access?: {
1161
+ requires?: string;
1162
+ roles?: string[];
1163
+ }): boolean;
1164
+ /**
1165
+ * Evalúa si el usuario puede ejecutar una operación específica según
1166
+ * `policies.access.operations` del recurso (B8 · R7.4a).
1167
+ *
1168
+ * El override por operación tiene prioridad sobre el umbral del grupo.
1169
+ * Si no hay override, se usa el umbral `policies.access.read` o
1170
+ * `policies.access.write` según la operación.
1171
+ *
1172
+ * Fallback a `permissions` (legacy) si `policies` no existe: mientras
1173
+ * los schemas locales no tengan `policies`, se mantiene compatibilidad.
1174
+ *
1175
+ * Es **pura decisión de UI**. El back vuelve a verificar en cada petición
1176
+ * (24-crud), así que esto sólo evita mostrar botones que darían 403.
1177
+ */
1178
+ canDoOperation(schema: ResourceSchema, operation: 'list' | 'get' | 'create' | 'update' | 'remove'): boolean;
1179
+ /**
1180
+ * Evalúa si el usuario puede leer un campo según `field.access.read`
1181
+ * (B8 · R7.4b).
1182
+ *
1183
+ * Es **pura decisión de UI**. El back vuelve a filtrar la respuesta en
1184
+ * cada petición (24-crud), así que esto sólo evita mostrar datos que el
1185
+ * usuario no debería ver.
1186
+ */
1187
+ canReadField(field: FieldSchema): boolean;
1188
+ /**
1189
+ * Evalúa si el usuario puede escribir un campo según `field.access.write`
1190
+ * (B8 · R7.4b).
1191
+ *
1192
+ * Es **pura decisión de UI**. El back rechaza la escritura con 422 si el
1193
+ * usuario no tiene permiso, así que esto sólo pone el campo en
1194
+ * solo-lectura en el frontend.
1195
+ */
1196
+ canWriteField(field: FieldSchema): boolean;
1197
+ /**
1198
+ * Verifica si el rol actual cumple un nivel de acceso dado.
1199
+ * Usa la misma jerarquía que `ACCESS_ROLE_RANK` del back (11-auth):
1200
+ * admin ≥ staff.
1201
+ */
1202
+ private meetsLevel;
1203
+ private open;
1204
+ /**
1205
+ * Borra token **y** usuario (§5.4) — en ese orden de importancia: un token
1206
+ * sin usuario no autentica, pero un usuario sin token engaña al menú.
1207
+ */
1208
+ /**
1209
+ * Ruta previa guardada por un `AUTH_EXPIRED` (§4.5), **consumida**: se
1210
+ * devuelve una sola vez y se borra. Sin el consume, cada login futuro
1211
+ * volvería a la misma URL vieja aunque el usuario hubiera llegado al
1212
+ * login desde el menú.
1213
+ */
1214
+ consumeReturnUrl(): string | null;
1215
+ private clear;
1216
+ private goLogin;
1217
+ private currentUrl;
1218
+ private envelope;
1219
+ /**
1220
+ * Mismo transporte que `ApiService` (R2.8): cuerpo JSON con
1221
+ * `Content-Type: text/plain` ⇒ **simple request**, sin preflight CORS.
1222
+ * Los tres endpoints de sesión pasan por acá — si alguno se saltara el
1223
+ * header, el navegador añadiría `application/json` y la llamada empezaría
1224
+ * a pedir permiso al servidor con un OPTIONS.
1225
+ */
1226
+ private post;
1227
+ static ɵfac: i0.ɵɵFactoryDeclaration<AuthService, never>;
1228
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1229
+ }
1230
+ /** Fila cruda de un listado: el motor no impone forma a las columnas. */
1231
+ type SourceRow = Record<string, unknown>;
1232
+ /**
1233
+ * `source.params` con cada `{campo}` resuelto contra `ctx`.
1234
+ *
1235
+ * - Un valor que se resuelve **vacío se OMITE** en vez de mandar `''`: el
1236
+ * back distingue «parámetro ausente» (fail-closed ⇒ sin filas) de un
1237
+ * `eq ''` que podría cruzarse con filas realmente vacías.
1238
+ * - Un parámetro sin marcador `{}` es literal y viaja tal cual.
1239
+ */
1240
+ declare function interpolateParams(params: Record<string, string> | undefined, ctx?: Record<string, unknown>): Record<string, string>;
1241
+ /**
1242
+ * Clave de caché de una fuente: `resource|view|params` (F8 · R6.5).
1243
+ *
1244
+ * Los parámetros van **resueltos** y ordenados, así que dos fuentes que
1245
+ * terminan pidiendo lo mismo comparten entrada, y un `{campo}` que cambia
1246
+ * es otra entrada. Pura: es también el valor que `resource({ params })` de
1247
+ * Angular compara para decidir si vuelve a cargar.
1248
+ */
1249
+ declare function dataSourceKey(source: DataSource, ctx?: Record<string, unknown>): string;
1250
+ /**
1251
+ * Fetcher único de `source` (F8 · R6.5): relations, selects y widgets del
1252
+ * panel piden sus filas ACÁ y no cada uno desde su componente.
1253
+ *
1254
+ * - **Caché por `resource+view+params`** con TTL e invalidación explícita —
1255
+ * es la pieza que evita que seis widgets del panel disparen seis vueltas
1256
+ * iguales a la misma vista.
1257
+ * - **Deduplicación de vuelta en vuelo**: dos consumidores que pidan la misma
1258
+ * clave en el mismo ciclo comparten la promesa (no la respuesta, la vuelta).
1259
+ * - Un error **no queda cacheado**: se borra la entrada para que la recarga
1260
+ * del consumidor vuelva a intentarlo en vez de devolver siempre la misma
1261
+ * rechazada.
1262
+ */
1263
+ export declare class DataSourceService {
1264
+ private readonly api;
1265
+ private readonly cache;
1266
+ /**
1267
+ * Filas de la fuente declarada.
1268
+ *
1269
+ * `fresh` salta la caché (recarga tras una escritura); `ctx` sólo se usa
1270
+ * para resolver `{campo}` en `params` y no cambia la forma de la respuesta.
1271
+ */
1272
+ rows(source: DataSource, ctx?: Record<string, unknown>, fresh?: boolean): Promise<SourceRow[]>;
1273
+ /**
1274
+ * Vacía las filas ya pedidas. Sin argumento, todas; con un `resourceId`,
1275
+ * sólo las de ese recurso (lo que acaba de crear el drawer de relación).
1276
+ */
1277
+ invalidate(resourceId?: string): void;
1278
+ private fetch;
1279
+ static ɵfac: i0.ɵɵFactoryDeclaration<DataSourceService, never>;
1280
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1281
+ }
1282
+ interface MappedError {
1283
+ /** Errores por ruta de campo (ej. `faq[2].answer`); se pintan en el campo exacto. */
1284
+ fields: Record<string, string>;
1285
+ /** Mensaje general; presente cuando el error no tiene ruta de campo (base.md §10). */
1286
+ general?: string;
1287
+ }
1288
+ /**
1289
+ * Convierte un error recibido en el formato que consume la UI:
1290
+ * rutas de campo para pintar el error donde corresponde, o mensaje general.
1291
+ *
1292
+ * Un422 trae VARIOS issues con ruta (`error.errors[]`, auth.md §6); cada uno
1293
+ * se convierte en `fields[path] = mensaje`, que es exactamente la clave que
1294
+ * después resuelve el host como `serverErrors[prefix + key]`. Si al final no
1295
+ * hay ninguna ruta pintable (error sin `errors[]`, issue sin `path`), el
1296
+ * mensaje del sobre queda en `general` como hasta ahora (base.md §10).
1297
+ */
1298
+ export declare class ErrorMapperService {
1299
+ map(error: unknown): MappedError;
1300
+ messageOf(error: unknown): string;
1301
+ static ɵfac: i0.ɵɵFactoryDeclaration<ErrorMapperService, never>;
1302
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1303
+ }
1304
+ /**
1305
+ * Subida de imágenes en DOS PASOS (baseapi §11.2, opción B):
1306
+ * 1. Firma firmada por el backend → `POST /admin/upload-signature`
1307
+ * con `{ resource }` (mismo envelope text/plain que todo `/admin/*`).
1308
+ * 2. El navegador sube directo a Cloudinary (FormData a `upload_url`) con
1309
+ * los parámetros que fijó y firmó el backend; el API secret jamás sale
1310
+ * de acá (§11.1–§11.3).
1311
+ *
1312
+ * Se guarda SOLO `secure_url` (base.md §7: la URL pública es el valor del
1313
+ * campo; la imagen anterior no se borra al reemplazar — §11.6).
1314
+ *
1315
+ * Los errores (firma caducada, rate-limit 429, red) llegan por el canal de
1316
+ * error; el componente conserva la imagen anterior (§11.2 paso 6).
1317
+ */
1318
+ export declare class UploadService {
1319
+ private readonly http;
1320
+ private readonly api;
1321
+ /**
1322
+ * Misma subida con eventos de progreso (base.md §7, estado «Subiendo»):
1323
+ * emite `UploadProgress` durante la subida a Cloudinary y termina con
1324
+ * `Response` llevando la URL en el body. El campo sólo cambia cuando
1325
+ * llega la respuesta.
1326
+ */
1327
+ uploadWithProgress(file: File, resource: string, field?: string): Observable<HttpEvent<string>>;
1328
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadService, never>;
1329
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1330
+ }
1331
+ /**
1332
+ * El backend responde SIEMPRE HTTP 200 y pone los errores en el body
1333
+ * (api.md §7), así que se detectan en el canal de **éxito** y se relanzan
1334
+ * como `ApiError` (con `fieldPath` cuando el backend envía `path`).
1335
+ * Los errores HTTP reales (red caída, proxy) también se normalizan.
1336
+ *
1337
+ * **R2.9 — paso dos de dos.** Éste archivo es el ÚNICO punto donde el
1338
+ * sobre de error se abre; lo que decide qué ocurre con un `AUTH_*` es
1339
+ * `AuthService.applyAuthCode` (la tabla de §4.5). Aquí sólo se le entrega
1340
+ * el código y se propaga el `ApiError` con él — un segundo sitio que
1341
+ * «interprete» el código sería una regla duplicada y, a la larga, una que
1342
+ * se contradiga con la primera.
1343
+ */
1344
+ export declare const apiErrorInterceptor: HttpInterceptorFn;
1345
+ interface MenuEntry {
1346
+ id: string;
1347
+ label: string;
1348
+ }
1349
+ /**
1350
+ * Menú lateral generado desde el catálogo EFECTIVO (F7-4): locales
1351
+ * fusionados + recursos sintetizados del schema remoto — un recurso nuevo
1352
+ * del backend aparece acá con 0 cambios en `src/`. Reactivo a la señal del
1353
+ * schema remoto (el initializer lo resuelve antes del primer render, pero
1354
+ * el `computed` cubre el caso de respuesta tardía).
1355
+ * `labelPlural` + `permissions`: un recurso se oculta solo si declara
1356
+ * create, update y remove explícitamente en falso (no hay permiso de
1357
+ * lectura).
1358
+ */
1359
+ export declare class Sidebar {
1360
+ /** Estado del offcanvas (sólo relevante en mobile; ver `layout.ts`).
1361
+ * `model()`: el padre lo setea (abrir) y el hijo también (cerrar al
1362
+ * navegar) — binding de dos vías `[(open)]`. */
1363
+ readonly open: import("@angular/core").ModelSignal<boolean>;
1364
+ private readonly auth;
1365
+ private readonly notifications;
1366
+ readonly isAuthenticated: import("@angular/core").Signal<boolean>;
1367
+ readonly closingAllSessions: import("@angular/core").WritableSignal<boolean>;
1368
+ /**
1369
+ * R2.10 — el menú se corta dos veces: `isVisible` (permisos declarados
1370
+ * en el schema) y `canAccess` (auth.md §5.3, `access` de la sesión).
1371
+ * Sólo UX: filtrar acá no protege nada, el back re-verifica en R2.6.
1372
+ */
1373
+ readonly menuItems: import("@angular/core").Signal<MenuEntry[]>;
1374
+ closeAllSessions(): Promise<void>;
1375
+ static ɵfac: i0.ɵɵFactoryDeclaration<Sidebar, never>;
1376
+ static ɵcmp: i0.ɵɵComponentDeclaration<Sidebar, "app-sidebar", never, {
1377
+ "open": {
1378
+ "alias": "open";
1379
+ "required": false;
1380
+ "isSignal": true;
1381
+ };
1382
+ }, {
1383
+ "open": "openChange";
1384
+ }, never, never, true, never>;
1385
+ }
1386
+ /**
1387
+ * Panel de métricas — tercer `kind` del registry (refactormotor.md F4).
1388
+ *
1389
+ * Sólo lectura: no hay CRUD y los datos llegan de un endpoint declarativo
1390
+ * (Etapa 2, S4). Los widgets se declaran en `widgets` del schema — no en
1391
+ * `fields`, porque un KPI no es un campo (ver `ResourceSchema.widgets`).
1392
+ *
1393
+ * Dos formas de dato, la que declare cada widget (F8 · R6.5):
1394
+ *
1395
+ * - **payload**: el `key` del widget es su clave en la respuesta del
1396
+ * endpoint; `source: { kind: 'payload' }` lee la respuesta entera.
1397
+ * - **fuente declarada** (`source: { resource, view?, params? }`): el widget
1398
+ * pide sus filas al `DataSourceService` y deja de depender de que un
1399
+ * handler haya armado `data[key]`. Acá se piden TODAS juntas, así que un
1400
+ * panel con seis fuentes iguales hace una vuelta, no seis (la caché del
1401
+ * servicio es compartida con relations y selects).
1402
+ */
1403
+ export declare class Dashboard {
1404
+ private readonly api;
1405
+ private readonly ds;
1406
+ /** Descriptor del dashboard (lo resuelve `ResourcePage` contra el registry). */
1407
+ readonly schema: import("@angular/core").InputSignal<ResourceSchema>;
1408
+ /**
1409
+ * Respuesta del endpoint de métricas indexada por `key` de widget.
1410
+ * Si se pasa explícitamente (como en tests con fixtures), se usa directamente;
1411
+ * de lo contrario se obtienen los datos de forma reactiva desde el endpoint `get`.
1412
+ */
1413
+ readonly data: import("@angular/core").InputSignal<Record<string, unknown> | null>;
1414
+ private readonly fetchedData;
1415
+ protected readonly effectiveData: import("@angular/core").Signal<Record<string, unknown>>;
1416
+ protected readonly widgets: import("@angular/core").Signal<WidgetSchema[]>;
1417
+ /**
1418
+ * Claves de los widgets con fuente declarada, en una cadena: es el
1419
+ * `params` del `resource` y sólo cambia cuando cambia el panel. `''`
1420
+ * (ninguna fuente) ⇒ `undefined` ⇒ el resource queda idle y no pide nada.
1421
+ */
1422
+ private readonly clavesFuente;
1423
+ /**
1424
+ * Filas de todos los widgets con `source`, pedidas de una sola vez.
1425
+ *
1426
+ * Cada fila queda bajo `widget.key`, que es el mismo contrato de lectura
1427
+ * que tiene el payload: `datum()` devuelve filas cuando hay fuente y la
1428
+ * clave de siempre cuando no, y el descriptor del widget traduce la forma
1429
+ * que necesita.
1430
+ */
1431
+ private readonly fuentes;
1432
+ /** ¿Alguna fuente del panel falló? (`status()` no tira; `value()` sí.) */
1433
+ protected readonly falloFuentes: import("@angular/core").Signal<boolean>;
1434
+ /**
1435
+ * Estado de carga del panel — mientras dure, cada tarjeta reserva su
1436
+ * espacio con skeleton (`cargar ≠ vacío`, doc/ux-ui-mobile.md §estados).
1437
+ * Cubre LOS dos caminos de dato: el payload (`null` inicial = sin
1438
+ * respuesta todavía) y las fuentes declaradas (`resource` en loading;
1439
+ * con params vacíos queda idle, que NO es carga).
1440
+ */
1441
+ protected readonly cargando: import("@angular/core").Signal<boolean>;
1442
+ /** `null` explícito cuando la clave no vino: el widget muestra su vacío. */
1443
+ protected datum(widget: WidgetSchema): unknown;
1444
+ static ɵfac: i0.ɵɵFactoryDeclaration<Dashboard, never>;
1445
+ static ɵcmp: i0.ɵɵComponentDeclaration<Dashboard, "app-dashboard", never, {
1446
+ "schema": {
1447
+ "alias": "schema";
1448
+ "required": true;
1449
+ "isSignal": true;
1450
+ };
1451
+ "data": {
1452
+ "alias": "data";
1453
+ "required": false;
1454
+ "isSignal": true;
1455
+ };
1456
+ }, {}, never, never, true, never>;
1457
+ }
1458
+ type Row$1 = Record<string, unknown>;
1459
+ /**
1460
+ * Contexto de una celda de listado (R4.3 · `kind: 'column-format'`).
1461
+ *
1462
+ * Es lo que antes cada `@case` de `list-view` pedía a la vista como
1463
+ * `row`/`column` y lo que el descriptor cuelga de `extras.data`: el
1464
+ * componente de formato no conoce a la vista, sólo a su celda.
1465
+ *
1466
+ * `fields`, `canUpdate`, `saving`, `currency` y `toggle` viajan acá porque
1467
+ * son decisiones de la vista (permisos, moneda del schema, escritura de la
1468
+ * fila); un formato nuevo no necesita tocar `list-view` para leerlas.
1469
+ */
1470
+ interface ColumnCell {
1471
+ /** Fila completa. */
1472
+ row: Row$1;
1473
+ /** Columna que se pinta (`key`, `label`, `format`). */
1474
+ column: ListColumn;
1475
+ /** Campos del recurso: de acá salen `trueLabel`/`falseLabel`. */
1476
+ fields: FieldSchema[];
1477
+ /** `titleField` de la fila, para el `aria-label` del interruptor. */
1478
+ title: string;
1479
+ /** ¿La fila admite escritura? (`ListView.canUpdate`) */
1480
+ canUpdate: boolean;
1481
+ /** ¿La fila está guardando? (`ListView.savingKey === keyOf(row)`) */
1482
+ saving: boolean;
1483
+ /** Moneda del schema, para el formato `currency`. */
1484
+ currency: string;
1485
+ /** Escribe el valor en la fila. Sólo lo usa el interruptor `active`. */
1486
+ toggle?: (checked: boolean) => void;
1487
+ }
1488
+ type Row = Record<string, unknown>;
1489
+ type Status = 'loading' | 'ready' | 'error';
1490
+ /**
1491
+ * Listado genérico de una colección (base.md §10 «Listado»): tabla desde
1492
+ * `listColumns` con los 7 formatos **resueltos por el registro** (R4.3),
1493
+ * búsqueda/filtros **en el cliente** (el
1494
+ * contrato de api.md no define parámetros de consulta: el endpoint admin
1495
+ * devuelve todos los registros), interruptor `active`, acciones de fila y
1496
+ * estados universales (skeleton, vacío, error con reintento).
1497
+ */
1498
+ export declare class ListView {
1499
+ readonly schema: import("@angular/core").InputSignal<ResourceSchema>;
1500
+ /** Emitido al pulsar «Editar» cuando `schema.detail.enabled === true`. */
1501
+ readonly viewRecord: import("@angular/core").OutputEmitterRef<Row>;
1502
+ private readonly api;
1503
+ private readonly auth;
1504
+ private readonly router;
1505
+ readonly notifications: NotificationService;
1506
+ private readonly actionService;
1507
+ readonly status: import("@angular/core").WritableSignal<Status>;
1508
+ readonly rows: import("@angular/core").WritableSignal<Row[]>;
1509
+ readonly query: import("@angular/core").WritableSignal<string>;
1510
+ readonly filterValues: import("@angular/core").WritableSignal<Record<string, string>>;
1511
+ /** Clave del registro cuyo interruptor `active` está guardando. */
1512
+ readonly savingKey: import("@angular/core").WritableSignal<string | null>;
1513
+ /** Fila que se está editando en el drawer (plan 6.5); `null` = cerrado. */
1514
+ readonly quickEditRow: import("@angular/core").WritableSignal<Row | null>;
1515
+ /** Evita que una respuesta lenta pise el recurso ya cambiado. */
1516
+ private loadEpoch;
1517
+ constructor();
1518
+ /** Búsqueda + filtros + orden `positionField` ascendente (cliente). */
1519
+ readonly visibleRows: import("@angular/core").Signal<Row[]>;
1520
+ readonly currencyCode: import("@angular/core").Signal<string>;
1521
+ private readonly permissions;
1522
+ readonly canCreate: import("@angular/core").Signal<boolean>;
1523
+ readonly canUpdate: import("@angular/core").Signal<boolean>;
1524
+ readonly canRemove: import("@angular/core").Signal<boolean>;
1525
+ /** El atajo existe sólo si el schema lo declara y hay permiso de editar. */
1526
+ readonly canQuickEdit: import("@angular/core").Signal<boolean>;
1527
+ /** Subconjunto de campos del drawer, en el orden declarado en el schema. */
1528
+ readonly quickEditFields: import("@angular/core").Signal<readonly FieldSchema[]>;
1529
+ readonly hasRowActions: import("@angular/core").Signal<boolean>;
1530
+ /** Mientras persiste un movimiento: bloquea drag y botones. */
1531
+ readonly reordering: import("@angular/core").WritableSignal<boolean>;
1532
+ /** Sin búsqueda ni filtros aplicados (base.md §10). */
1533
+ private readonly hasActiveFilters;
1534
+ /**
1535
+ * Reordenable sólo si el schema lo declara, el permiso lo permite y no hay
1536
+ * filtros activos: con filtros filtrados, el índice visual no corresponde a
1537
+ * la posición real.
1538
+ */
1539
+ readonly canReorder: import("@angular/core").Signal<boolean>;
1540
+ readonly dropDisabled: import("@angular/core").Signal<boolean>;
1541
+ setQuery(value: string): void;
1542
+ setFilter(key: string, value: string): void;
1543
+ clearFilters(): void;
1544
+ retry(): void;
1545
+ isRowClickable(): boolean;
1546
+ goView(row: Row): void;
1547
+ onRowClick(row: Row): void;
1548
+ goCreate(): void;
1549
+ goEdit(row: Row): void;
1550
+ /** Cerró con guardado: se recarga la fila (la respuesta puede venir achatada). */
1551
+ quickEditSaved(): void;
1552
+ toggleActive(row: Row, checked: boolean): Promise<void>;
1553
+ /** Drop del CDK: `currentIndex` ya es el índice en la lista sin el elemento. */
1554
+ onDrop(event: CdkDragDrop<Row[]>): void;
1555
+ /** Botones subir/bajar (accesibilidad y móvil, base.md §13.10). */
1556
+ moveBy(key: string, delta: -1 | 1): void;
1557
+ /**
1558
+ * Un movimiento → UNA intención al backend (baseapi §2.3 y §9):
1559
+ * `{ reorder: { key, after | toStart | toEnd } }`, nunca posiciones
1560
+ * calculadas. El índice objetivo es la posición FINAL del registro (el
1561
+ * `currentIndex` del CDK ya es el índice de inserción en la lista sin el
1562
+ * elemento, idéntico al índice final; subir/bajar suma/resta 1). El
1563
+ * backend re-balancea, recalcula `position` y devuelve el listado fresco:
1564
+ * sin escrituras optimistas — si falla, no cambió nada. `gap-sorting`
1565
+ * quedó sólo para los campos con listas locales.
1566
+ */
1567
+ private moveRow;
1568
+ removeRow(row: Row): Promise<void>;
1569
+ /**
1570
+ * Ejecuta una acción del schema. El comportamiento vive en el registro de
1571
+ * acciones (R3.9–R3.11): la vista sólo aporta el registro y los puertos.
1572
+ */
1573
+ runAction(action: ResourceAction, row: Row): Promise<void>;
1574
+ /** Icono que el descriptor declara para la acción; sin él, el rótulo. */
1575
+ protected iconOf(action: ResourceAction): LucideIcon | undefined;
1576
+ keyOf(row: Row): string;
1577
+ titleOf(row: Row): string;
1578
+ /**
1579
+ * Formato efectivo de la columna (R4.4 · «declarada, no implícita»).
1580
+ *
1581
+ * `column.format` sólo sobreescribe; si no, manda la tabla de
1582
+ * `column-defaults` según el `type` del campo — nunca el `?? 'text'`
1583
+ * implícito que había.
1584
+ */
1585
+ formatOf(column: ListColumn): ListColumnFormat;
1586
+ /**
1587
+ * Contexto de la celda que recibe el formato (R4.3 · `extras.data`).
1588
+ *
1589
+ * Concentra aquí lo que sólo la vista sabe — permisos, moneda, guardado y
1590
+ * la escritura de la fila — para que «añadir un formato» no la toque.
1591
+ */
1592
+ cellOf(row: Row, column: ListColumn): ColumnCell;
1593
+ filterOptions(filter: ListFilter): SelectOption[];
1594
+ inputValue(event: Event): string;
1595
+ selectValue(event: Event): string;
1596
+ private load;
1597
+ static ɵfac: i0.ɵɵFactoryDeclaration<ListView, never>;
1598
+ static ɵcmp: i0.ɵɵComponentDeclaration<ListView, "app-list-view", never, {
1599
+ "schema": {
1600
+ "alias": "schema";
1601
+ "required": true;
1602
+ "isSignal": true;
1603
+ };
1604
+ }, {
1605
+ "viewRecord": "viewRecord";
1606
+ }, never, never, true, never>;
1607
+ }
1608
+ /** Pedido de «Crear nuevo» lanzado desde el dropdown de una relación. */
1609
+ interface RelationCreateRequest {
1610
+ /** Recurso a crear (el `source.resource` de la relación). */
1611
+ resource: string;
1612
+ /**
1613
+ * Se invoca con la clave recién creada. Vive en la relación porque es ella
1614
+ * la que tiene que recargar sus opciones y preseleccionar el valor.
1615
+ */
1616
+ onCreated: (key: string) => void;
1617
+ }
1618
+ /**
1619
+ * Puente entre el control `relation` y el drawer que abre el formulario.
1620
+ *
1621
+ * Existe como señal inyectable y NO como un `output()` encadenado porque el
1622
+ * camino directo — `relation → DrawerForm → FieldHost → relation` — arma un
1623
+ * ciclo de imports de módulos: `field-host` se decoraría con
1624
+ * `FieldRelation === undefined` y Angular tiraría `Cannot read properties of
1625
+ * undefined (reading 'ɵcmp')`. El campo no puede importar un componente que,
1626
+ * a su vez, lo importa a él.
1627
+ *
1628
+ * Sólo hay un drawer a la vez, así que una única señal alcanza: quien lo
1629
+ * abre lo cierra con `clear()` al recibir `close`/`saved`.
1630
+ */
1631
+ declare class RelationDrawerService {
1632
+ private readonly pending;
1633
+ /** Pedido en curso; `null` cuando no hay panel abierto. */
1634
+ readonly request: import("@angular/core").Signal<RelationCreateRequest | null>;
1635
+ open(resource: string, onCreated: (key: string) => void): void;
1636
+ clear(): void;
1637
+ static ɵfac: i0.ɵɵFactoryDeclaration<RelationDrawerService, never>;
1638
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
1639
+ }
1640
+ type FormMode = 'create' | 'edit' | 'single';
1641
+ type FormStatus = 'loading' | 'ready' | 'error';
1642
+ interface SectionView {
1643
+ id: string;
1644
+ label: string;
1645
+ fields: FieldSchema[];
1646
+ }
1647
+ /**
1648
+ * Un ítem del resumen de errores (§4.11). `id` es el que ya genera
1649
+ * `state().name()`: es a la vez el valor de `data-field` del envoltorio y el
1650
+ * `id` real del control, así que no se inventa ningún ancla nueva.
1651
+ */
1652
+ interface ErrorEntry {
1653
+ id: string;
1654
+ label: string;
1655
+ message: string;
1656
+ /** Sección/pestaña del campo raíz; `undefined` si no está en ninguna. */
1657
+ section?: string;
1658
+ }
1659
+ /**
1660
+ * Formulario genérico: lo construye el schema del recurso (base.md §4–§6,
1661
+ * §10) con `form()` de Signal Forms sobre un modelo `Record<string, unknown>`.
1662
+ *
1663
+ * - **Acceso dinámico**: `form()[field.key]` no compila con claves
1664
+ * arbitrarias; el modelo se tipa como `Record<string, unknown>` y el acceso
1665
+ * vive en `fields/field-node.ts` (`childTree`) y en `form-schema.ts`
1666
+ * (`pathAt`), que centralizan los dos casts.
1667
+ * - **Sin `debounce('blur')`**: difiere `controlValue → value` (el modelo), no
1668
+ * la visibilidad de errores, y con controles custom (`multiselect`, que
1669
+ * nunca emite `touch`) se perderían datos en el payload. La validación al
1670
+ * perder foco la resuelve FieldHost (`invalid() && touched()`).
1671
+ * - `<form novalidate>`: Signal Forms expone `required` como atributo nativo,
1672
+ * y la validación nativa del navegador bloquearía el submit sin mostrar
1673
+ * nada (campos en `<details>` cerrados).
1674
+ */
1675
+ export declare class FormView {
1676
+ /** Puente con el control `relation` (ver `relation-drawer.service`). */
1677
+ readonly relationDrawer: RelationDrawerService;
1678
+ private readonly route;
1679
+ private readonly router;
1680
+ private readonly nav;
1681
+ private readonly api;
1682
+ private readonly auth;
1683
+ private readonly notifications;
1684
+ private readonly errorMapper;
1685
+ /** `form()` puede reconstruirse fuera de contexto (cambio de ruta). */
1686
+ private readonly injector;
1687
+ /** Estabilidad de la app: cambio de sección + carga de chunks (ver `goToError`). */
1688
+ private readonly appRef;
1689
+ private readonly params;
1690
+ private readonly routeData;
1691
+ readonly schema: import("@angular/core").Signal<ResourceSchema | undefined>;
1692
+ /** Recurso actual (lo consumen `image` para pedir la firma, §11.2). */
1693
+ readonly resourceId: import("@angular/core").Signal<string>;
1694
+ /** Registro recién creado para alimentar el modal de resumen post-creación. */
1695
+ readonly createdWithSummary: import("@angular/core").WritableSignal<Record<string, unknown> | null>;
1696
+ readonly mode: import("@angular/core").Signal<FormMode>;
1697
+ readonly heading: import("@angular/core").Signal<string>;
1698
+ readonly status: import("@angular/core").WritableSignal<FormStatus>;
1699
+ /**
1700
+ * D4 — el error ORIGINAL de `load()` con identidad intacta (el mensaje
1701
+ * mostrado es display-only). Se limpia cuando un reintento termina bien.
1702
+ */
1703
+ private errorCarga;
1704
+ errorDeCarga(): unknown;
1705
+ /** Estado «guardando» del botón (además del bloqueo interno de `submit`). */
1706
+ readonly saving: import("@angular/core").WritableSignal<boolean>;
1707
+ /** Errores del backend: rutas de campo + mensaje general (base.md §10). */
1708
+ readonly serverErrors: import("@angular/core").WritableSignal<MappedError>;
1709
+ /** Pestaña activa (solo `layout.mode === 'tabs'`). */
1710
+ readonly activeSection: import("@angular/core").WritableSignal<string | null>;
1711
+ private readonly attemptedSteps;
1712
+ private readonly openSections;
1713
+ private readonly formTree;
1714
+ /** Evita que una respuesta lenta pise el formulario ya reconstruido. */
1715
+ private loadEpoch;
1716
+ /**
1717
+ * Borrador (§4.6 / plan 5.4). `draftOffer` queda en `null` cuando no hay
1718
+ * nada que ofrecer: NUNCA se restaura solo, sólo con un clic del usuario.
1719
+ */
1720
+ private readonly draftOffer;
1721
+ /** Snapshot de lo que vino del servidor; sólo se guarda si el usuario cambió algo. */
1722
+ private loadedSnapshot;
1723
+ /** Registro original, para «Restablecer» (§5). */
1724
+ private loadedRecord;
1725
+ /** Snapshot de los padres de `dependsOn` y el árbol al que pertenece. */
1726
+ private readonly dependsOnSnapshot;
1727
+ private dependsOnTree;
1728
+ /**
1729
+ * Hubo un intento de guardado. Sin este guardia el resumen aparecería de
1730
+ * entrada en cualquier formulario con `required` vacíos: `invalid()` es
1731
+ * cierto desde el primer render, no sólo después de enviar.
1732
+ *
1733
+ * También gobierna el `⚠` de las pestañas y el bloqueo al cambiar de
1734
+ * pestaña (decisión A): antes del primer Guardar un campo `required` vacío
1735
+ * YA es inválido, y andar gritando `⚠` —o trancando la navegación— al
1736
+ * usuario que todavía no le erró a nada es ruido, no ayuda.
1737
+ */
1738
+ readonly submitted: import("@angular/core").WritableSignal<boolean>;
1739
+ constructor();
1740
+ readonly layoutMode: import("@angular/core").Signal<LayoutMode>;
1741
+ readonly layoutColumns: import("@angular/core").Signal<2 | 3>;
1742
+ /** Campos que la UI sabe dibujar (los de Fase 11 se omiten, no se borran del payload). */
1743
+ readonly renderedFields: import("@angular/core").Signal<FieldSchema[]>;
1744
+ /**
1745
+ * Ítems del resumen de errores (§4.11), vacío hasta que se intente guardar.
1746
+ *
1747
+ * Recorre el ÁRBOL, no `serverErrors.fields`: el backend sólo puede
1748
+ * devolver un `path` por respuesta (`api.md §7`), así que ahí nunca hay más
1749
+ * de un ítem. Los errores múltiples los produce la validación del cliente,
1750
+ * que `submit()` deja marcados como tocados de una.
1751
+ */
1752
+ readonly summaryErrors: import("@angular/core").Signal<ErrorEntry[]>;
1753
+ /** Secciones con al menos un campo dibujable. */
1754
+ readonly sectionViews: import("@angular/core").Signal<SectionView[]>;
1755
+ /** Campos sin sección (o con una sección inexistente): siempre visibles. */
1756
+ readonly unsectionedFields: import("@angular/core").Signal<FieldSchema[]>;
1757
+ /** En pestañas solo se dibuja la activa; en secciones, todas (colapsables). */
1758
+ readonly visibleSections: import("@angular/core").Signal<SectionView[]>;
1759
+ /** Indicador _dirty_ + aviso al salir (base.md §10). Agrega descendientes. */
1760
+ readonly dirty: import("@angular/core").Signal<boolean>;
1761
+ /** `slug` con `lockAfterCreate` se bloquea solo si el registro ya existe. */
1762
+ readonly exists: import("@angular/core").Signal<boolean>;
1763
+ readonly canSave: import("@angular/core").Signal<boolean>;
1764
+ readonly canDelete: import("@angular/core").Signal<boolean | undefined>;
1765
+ /** Reconstruye el formulario al cambiar de ruta o de recurso. */
1766
+ private rebuild;
1767
+ private load;
1768
+ /**
1769
+ * Siembra defaults + registro: los campos ausentes en la respuesta conservan
1770
+ * su valor por defecto y los grupos/ítems parciales se fusionan en profundidad
1771
+ * (si no, `childTree` no encontraría nodos y revienta al renderizar).
1772
+ * `value.set` es programático: no marca `dirty`.
1773
+ */
1774
+ private applyRecord;
1775
+ /** `draft:<recurso>:<key|nuevo>` — aislado por recurso Y registro. */
1776
+ private draftStorageKey;
1777
+ /** Best-effort: localStorage puede fallar (modo privado, cuota). */
1778
+ private saveDraft;
1779
+ /**
1780
+ * Ofrece el borrador si es más reciente que el servidor. Si el registro no
1781
+ * trae fecha, cualquier borrador sirve — sólo existe si el usuario cambió
1782
+ * algo, porque `loadedSnapshot` filtra los idénticos.
1783
+ */
1784
+ private offerDraft;
1785
+ /** Restaurar es una acción explícita del usuario (§4.6: nunca en silencio). */
1786
+ restoreDraft(): void;
1787
+ /** Descarta el borrador sin tocar lo cargado. */
1788
+ discardDraft(): void;
1789
+ private clearDraft;
1790
+ /** «hace 1 min» — relativo y discreto (§4.6). */
1791
+ draftAge(savedAt: number): string;
1792
+ /**
1793
+ * Botón «Restablecer» (§5): vuelve a lo que vino del servidor y limpia el
1794
+ * borrador. Es la única vía de descartar cambios en modo edición.
1795
+ */
1796
+ restablecer(): void;
1797
+ retry(): void;
1798
+ /**
1799
+ * D4 — «way back» desde el estado de error: en la ruta directa
1800
+ * (`/:id/new`, `/:id/:key/edit`) no hay output que escape, así que el
1801
+ * servicio declarado navega al origen registrado o a la raíz del recurso.
1802
+ */
1803
+ protected volverDeError(): void;
1804
+ onSubmit(event: Event): Promise<void>;
1805
+ /**
1806
+ * create/update según el modo; nunca lanza (los errores van a `serverErrors`).
1807
+ *
1808
+ * **Regla de éxito por flujo** (guía §6: «toast y volver a la lista o
1809
+ * quedarse según el flujo») — es la misma para los cuatro recursos:
1810
+ *
1811
+ * - **Crear** desde la pantalla completa → **vuelve a la lista** del
1812
+ * recurso: el registro quedó creado y no hay nada más que editar acá.
1813
+ * - **Editar** → **se queda** y recarga lo guardado: el usuario sigue
1814
+ * trabajando sobre el mismo registro.
1815
+ * - **Eliminar** → **vuelve a la lista** (`back()`).
1816
+ * - **Desde un drawer** (crear una relación desde el dropdown, edición
1817
+ * rápida desde la lista) → **se queda** donde estaba: salir destruiría
1818
+ * el contexto que el drawer existía para conservar. Ver
1819
+ * `drawer-form.ts` y `relation-drawer.service.ts`.
1820
+ *
1821
+ * El toast se emite **antes** de navegar y el contenedor vive en el
1822
+ * layout, así que sobrevive al cambio de ruta y el mensaje se lee igual.
1823
+ */
1824
+ private persist;
1825
+ /**
1826
+ * Errores del backend → mapa por ruta; el mensaje se queda en `general`
1827
+ * sólo si su raíz (`address`, `faq[2]` → `faq`) no es un campo dibujado
1828
+ * (base.md §10). Los hijos anidados se resuelven en el host con
1829
+ * `serverErrors[prefix + key]`.
1830
+ */
1831
+ private applyServerErrors;
1832
+ private hasServerErrors;
1833
+ /** Muestra todos los errores: abre secciones, activa la pestaña con problemas. */
1834
+ private revealErrors;
1835
+ /** Scroll + focus al primer campo visible con mensaje de error. */
1836
+ private scrollToFirstError;
1837
+ remove(): Promise<void>;
1838
+ /** Consultado por `unsavedChangesGuard` al salir de la ruta. */
1839
+ canDeactivate(): Promise<boolean>;
1840
+ back(): void;
1841
+ onPostCreateClosed(): void;
1842
+ colSpan(field: FieldSchema): string;
1843
+ /** Id estable de una pestaña: ancla del foco programático. */
1844
+ tabId(sectionId: string): string;
1845
+ /**
1846
+ * `role="tab"` exige moverse con `ArrowLeft`/`ArrowRight`/`Home`/`End`
1847
+ * (WAI-ARIA APG). El foco se reengancha al que quedó ACTIVO, no al que se
1848
+ * pidió: si `selectSection()` bloquea el cambio por validación (§4.9,
1849
+ * decisión A) la selección no se mueve y el foco se queda donde estaba.
1850
+ *
1851
+ * Este handler es también lo que hace correcto el `Tab`: con el roving
1852
+ * `tabindex` sólo la pestaña activa entra en el orden de tabulación, así
1853
+ * que `Tab` va del tablist al primer campo de la sección en lugar de
1854
+ * recorrer las pestañas apagadas.
1855
+ */
1856
+ onTablistKeydown(event: KeyboardEvent): void;
1857
+ tabClass(section: SectionView): string;
1858
+ isFirstStep(): boolean;
1859
+ isLastStep(): boolean;
1860
+ previousStep(): void;
1861
+ nextStep(): void;
1862
+ private unsectionedHasLocalErrors;
1863
+ private revealUnsectionedErrors;
1864
+ private scrollToStep;
1865
+ selectSection(id: string): void;
1866
+ /** Sólo validación local: los errores del backend no bloquean navegar. */
1867
+ private sectionHasLocalErrors;
1868
+ /**
1869
+ * Marca la sección como tocada (los mensajes sólo aparecen una vez tocado)
1870
+ * y devuelve el foco al primer campo con problema, sin cambiar de pestaña.
1871
+ */
1872
+ private revealSectionErrors;
1873
+ isSectionOpen(id: string): boolean;
1874
+ setSectionOpen(id: string, event: Event): void;
1875
+ /**
1876
+ * Índice lateral (§5.3) → sección. Abre el `<details>` primero: si estaba
1877
+ * plegado, saltar antes daría con un elemento sin altura. El foco va al
1878
+ * `<summary>` para que el teclado siga teniendo dónde continuar.
1879
+ */
1880
+ goToSection(id: string): void;
1881
+ /** Invalido (propio, anidado o del backend): marca la pestaña/sección. */
1882
+ fieldHasProblem(field: FieldSchema): boolean;
1883
+ sectionHasProblem(section: SectionView): boolean;
1884
+ /**
1885
+ * Estado de progreso de una sección (§4.3 / decisión A).
1886
+ *
1887
+ * - `complete`: sin problemas → `✓` siempre, es progreso positivo.
1888
+ * - `problem`: con problemas **después** del primer envío → `⚠`.
1889
+ * - `pending`: con problemas pero todavía no se envió → sin marca, para
1890
+ * no amedrentar a quien recién abrió el formulario.
1891
+ */
1892
+ sectionProgress(section: SectionView): 'problem' | 'complete' | 'pending';
1893
+ /**
1894
+ * Un ítem por campo con problema, recorriendo el árbol. Los `group` se
1895
+ * descienden para quedar en la hoja (ahí está el mensaje real); los
1896
+ * compuestos (`list`, `key-value`, `string-list`) se listan como uno,
1897
+ * porque el envoltorio es el único ancla a la que se puede saltar.
1898
+ */
1899
+ private collectErrors;
1900
+ /** Mensaje del backend para una ruta raíz (`faq` cubre `faq[2].answer`). */
1901
+ private serverMessageFor;
1902
+ /**
1903
+ * Enlace del resumen → campo. Cambia de pestaña/sección si hace falta y
1904
+ * sólo después salta: en modo tabs el campo de otra pestaña todavía no
1905
+ * existe en el DOM en este punto.
1906
+ *
1907
+ * Esperar la estabilidad sustituye al `setTimeout` anterior: cubre de una
1908
+ * vez la pasada de change detection del cambio de sección **y** la carga
1909
+ * del chunk perezoso del descriptor (`load()`, R3.4). Sin esa espera el
1910
+ * foco se pierde sobre el envoltorio, que sí existe pero todavía no tiene
1911
+ * control dentro.
1912
+ */
1913
+ goToError(entry: ErrorEntry, event: Event): Promise<void>;
1914
+ /** Scroll + focus al campo con `data-field="id"` (mismo selector que `scrollToFirstError`). */
1915
+ private focusField;
1916
+ /**
1917
+ * Con 2+ errores el foco va al resumen: es la única forma de que el usuario
1918
+ * sepa que hay varios. Con uno solo se lo roba el campo, que ya está a la
1919
+ * vista — por eso el resumen ni se pinta entonces.
1920
+ */
1921
+ private focusSummary;
1922
+ private title;
1923
+ private recordKey;
1924
+ /** Schema destino del panel «Crear nuevo»; `undefined` si no existe. */
1925
+ drawerSchema(resource: string): ResourceSchema | undefined;
1926
+ /**
1927
+ * Cerró el panel con un registro nuevo: se cierra, y si vino con clave se
1928
+ * le pasa a la relación que lo pidió (recarga sus opciones y lo selecciona).
1929
+ */
1930
+ relationCreated(record: Record<string, unknown>, request: RelationCreateRequest): void;
1931
+ static ɵfac: i0.ɵɵFactoryDeclaration<FormView, never>;
1932
+ static ɵcmp: i0.ɵɵComponentDeclaration<FormView, "app-form-view", never, {}, {}, never, never, true, never>;
1933
+ }
1934
+ /**
1935
+ * Reglas del formulario desde el schema (base.md §4, §6, §9, §10).
1936
+ *
1937
+ * Hay DOS clases de reglas y viven en sitios distintos:
1938
+ *
1939
+ * 1. **Ciclo de vida** — `visibleWhen`, `readonlyOn`, `hiddenOn`, `dependsOn`:
1940
+ * decides si el campo se toca, se ve o se manda. Vive acá, con la
1941
+ * estructura del formulario.
1942
+ * 2. **Validadores** — obligatoriedad, longitud, patrón, rangos, únicos…:
1943
+ * vive en `core/registry/field-validators.ts` (F7 + B11), con los MISMOS
1944
+ * nombres que el back. Este módulo sólo RECORRE ese registro por cada
1945
+ * campo: añadir un validador es una entrada allá, no un `case` acá.
1946
+ *
1947
+ * El schema de Signal Forms recibe el path RAÍZ; las reglas se atan a la ruta
1948
+ * dinámica de cada campo. El barrido es **recursivo** — un `group` registra
1949
+ * las reglas de sus hijos bajo su propia ruta y un `list` registra las de
1950
+ * cada ítem con `applyEach`, de modo que las reglas corran también para los
1951
+ * ítems que se agregan después.
1952
+ *
1953
+ * Solo se generan reglas para los tipos que la UI sabe editar (`isSupported('field', …)`):
1954
+ * un campo invisible con `required` bloquearía el guardado para siempre.
1955
+ *
1956
+ * `api` y `excludeKey` alimentan la validación `unique`: se consulta el
1957
+ * listado del recurso y se descarta el propio registro (la clave del registro
1958
+ * en edición, tomada de la ruta).
1959
+ */
1960
+ export declare function buildFormSchema(schema: ResourceSchema, api: ApiService, excludeKey?: string): SchemaFn<Record<string, unknown>>;
1961
+ /** Árbol de un formulario con modelo dinámico. */
1962
+ export type RootTree = FieldTree<Record<string, unknown>>;
1963
+ /**
1964
+ * Contexto que el `ng-template` del `list` recibe por `ngTemplateOutlet`
1965
+ * para renderizar los campos de un ítem. El template vive en el despachador
1966
+ * (`field-host`) porque es el único que puede instanciar `app-field-host`;
1967
+ * el list lo instancia una vez por ítem con este contexto.
1968
+ */
1969
+ export interface ListChildContext {
1970
+ fields: readonly FieldSchema[];
1971
+ tree: RootTree;
1972
+ /** Prefijo de ruta de los errores del backend para este nivel (`faq[2].`). */
1973
+ prefix: string;
1974
+ errors: Record<string, string>;
1975
+ exists: boolean;
1976
+ /** `itemDisplay: 'row'` → los campos van en flujo compacto, no en grilla. */
1977
+ row: boolean;
1978
+ /** Clase `sm:col-span-*` de un campo (literal: Tailwind v4 es JIT). */
1979
+ colSpan: (width?: number) => string;
1980
+ }
1981
+ /** Subárbol del hijo directo bajo `key`. Lanza un error claro si el modelo no
1982
+ * contiene la clave (los modelos se siembran con los `default` del schema
1983
+ * antes de llamar a `form()`).
1984
+ */
1985
+ export declare function childTree<T>(parent: RootTree, key: string): FieldTree<T>;
1986
+ /** Subárbol de un campo `list`/`group` cuyo valor es un objeto. */
1987
+ export declare function groupTree(parent: RootTree, key: string): RootTree;
1988
+ /** Subárbol de un campo `list` (los ítems se acceden por índice). */
1989
+ export declare function listTree<T>(parent: RootTree, key: string): FieldTree<T[]>;
1990
+ /**
1991
+ * Ítem de una lista como árbol navegable.
1992
+ *
1993
+ * **Los nodos de ítem se atan a la identidad del elemento**, no al índice:
1994
+ * tras un cambio del arreglo el nodo viejo queda huérfano y leerlo lanza
1995
+ * `NG01904 «Orphan field»`. Por eso este helper se llama SIEMPRE fresco, en
1996
+ * cada render/computed — nunca se cachea un nodo entre mutaciones.
1997
+ *
1998
+ * El `as unknown` es porque `FieldTree<Item, number>` no es assignable a
1999
+ * `RootTree` (le falta el index signature de `string`), pero se navega igual.
2000
+ */
2001
+ export declare function itemTree<T>(items: FieldTree<T[]>, index: number): RootTree;
2002
+ /**
2003
+ * Ítem escalar de un `string-list` como nodo (`FieldTree<string>`), listo
2004
+ * para `[formField]`. Mismo criterio de frescura que `itemTree`.
2005
+ */
2006
+ export declare function stringItemTree(items: FieldTree<string[]>, index: number): FieldTree<string>;
2007
+ /**
2008
+ * Modelo y payload del formulario genérico (Fase 10, ampliado en Fase 11).
2009
+ *
2010
+ * - `seedModel` siembra un valor inicial para CADA campo declarado en el
2011
+ * schema antes de crear el formulario: el árbol de Signal Forms replica la
2012
+ * forma del modelo, así que un campo ausente no tendría estado (ni valor
2013
+ * que enviar). La siembra vive en `shared/utils/seed.ts`.
2014
+ * - `buildPayload` decide qué viaja al backend (base.md §10): solo las claves
2015
+ * declaradas en el schema, sin los ocultos y sin `positionField` raíz.
2016
+ */
2017
+ /** Valores por defecto del schema, en el mismo orden/shape que el JSON. */
2018
+ export declare function seedModel(schema: ResourceSchema): Record<string, unknown>;
2019
+ /**
2020
+ * Payload del guardado: whitelist de `schema.fields`.
2021
+ *
2022
+ * - Los campos ocultos (`visibleWhen` falso) no se muestran ni se envían
2023
+ * (base.md §10): usar `hidden()` garantiza que no aparezcan acá aunque
2024
+ * tengan valor sembrado — también dentro de `group` y de los ítems de una
2025
+ * `list` (recursivo).
2026
+ * - `positionField` del recurso se excluye aunque esté declarado: el orden
2027
+ * del listado lo gestiona el backend (api.md R3, nunca se expone). El
2028
+ * `positionField` de los ÍTEMS de una lista sí viaja: el orden local lo
2029
+ * calcula el front (base.md §8).
2030
+ * - Los tipos que la UI todavía no edita SÍ viajan: sus valores llegan del
2031
+ * registro leído, así que reenviarlos evita pisarlos.
2032
+ */
2033
+ export declare function buildPayload(schema: ResourceSchema, root: RootTree): Record<string, unknown>;
2034
+ type View = 'list' | 'detail';
2035
+ /**
2036
+ * Página del recurso: resuelve el `id` de la ruta contra el registry y
2037
+ * despacha según `kind` — colección → list view, singleton → form view,
2038
+ * dashboard → panel de sólo lectura — aplicando `permissions` (estado
2039
+ * «sin permisos»).
2040
+ *
2041
+ * Cuando `schema.detail.enabled === true`, intercepta el click de fila del
2042
+ * `ListView` y muestra el `DetailView` en lugar de navegar al formulario.
2043
+ * «Editar» desde el `DetailView` sí navega al formulario de edición.
2044
+ */
2045
+ export declare class ResourcePage {
2046
+ private readonly route;
2047
+ private readonly router;
2048
+ private readonly auth;
2049
+ private readonly params;
2050
+ private readonly routeData;
2051
+ readonly schema: import("@angular/core").Signal<ResourceSchema | undefined>;
2052
+ /** Vista activa: 'list' (por defecto) o 'detail' (al hacer clic en una fila con detail.enabled). */
2053
+ readonly currentView: import("@angular/core").WritableSignal<View>;
2054
+ /** Registro seleccionado para la vista de detalle. */
2055
+ readonly selectedRecord: import("@angular/core").WritableSignal<Record<string, unknown> | null>;
2056
+ constructor();
2057
+ private openFromRoute;
2058
+ /**
2059
+ * Clic de fila. R9.9.4 · decision table: si el schema declara `select`,
2060
+ * el contrato declarado REEMPLAZA el comportamiento local (`route` ⇒
2061
+ * emisión al dispatcher, `inline` ⇒ selección local sin URL, `none` ⇒
2062
+ * no-op); sin declaración, la navegación local actual sigue intacta
2063
+ * (backward compat — R9.9 «existing routes untouched»).
2064
+ */
2065
+ switchToDetail(record: Record<string, unknown>): void;
2066
+ switchToList(): void;
2067
+ goEdit(record: Record<string, unknown>): void;
2068
+ /**
2069
+ * Colección sin ninguna operación posible u singleton sin `update`: no hay
2070
+ * nada que mostrar con sentido (base.md §10 «Sin permisos»).
2071
+ *
2072
+ * R7.5: usa policies cuando existan, fallback a permissions.
2073
+ *
2074
+ * Un dashboard nunca entra aquí: es un panel de sólo lectura, `ResourcePermissions`
2075
+ * no tiene `read` y el acceso real a los datos lo decide el endpoint
2076
+ * (A4: el cliente es interfaz, no autoridad).
2077
+ */
2078
+ hasNoAccess(schema: ResourceSchema): boolean;
2079
+ static ɵfac: i0.ɵɵFactoryDeclaration<ResourcePage, never>;
2080
+ static ɵcmp: i0.ɵɵComponentDeclaration<ResourcePage, "app-resource-page", never, {}, {}, never, never, true, never>;
2081
+ }
2082
+ /**
2083
+ * R9.9 · slice 3 — Host genérico de composición (`uiView` tipo `tabs`).
2084
+ *
2085
+ * Un SOLO componente recursivo renderiza cualquier composición declarada:
2086
+ * resuelve cada hijo por `(kind:'view', type)` dentro del `ViewSlot` (D2,
2087
+ * sin `switch` de dominio y sin importar hojas), monta la validación del
2088
+ * schema ANTES del primer render (fail-loud) y maneja el tablist accesible
2089
+ * con roving tabindex (R9.9.5).
2090
+ *
2091
+ * Decisiones R9.9.5:
2092
+ * - Retención: un hijo `retain:true` visitado queda MONTADO pero oculto
2093
+ * (`hidden`); sin `retain` se destruye al cambiar de pestaña.
2094
+ * - Pestaña activa LOCAL por componente: no hay estado compartido entre
2095
+ * instancias de tabs anidadas (documentado en los docs de slice 5).
2096
+ * - `visibleWhen` se evalúa sin record (el contenedor no es un formulario):
2097
+ * contra registro vacío, TODAS las condiciones deben pasar (AND).
2098
+ * Si la pestaña activa queda oculta, la selección cae visiblemente a la
2099
+ * primera visible — nunca se pierde en silencio.
2100
+ */
2101
+ export declare class ComposerHost {
2102
+ private readonly route;
2103
+ private readonly routeData;
2104
+ /** Schema: explícito en `data` (fixtures/ruta embebida) o vía `resourceId`. */
2105
+ readonly schema: import("@angular/core").Signal<ResourceSchema | undefined>;
2106
+ /** instanceId de ESTE contenedor (raíz ⇒ nombre de la uiView de ruta). */
2107
+ readonly uiViewName: import("@angular/core").Signal<string>;
2108
+ /** Profundidad de ESTE contenedor (raíz ⇒ 0; embebido ⇒ payload.depth). */
2109
+ readonly profundidad: import("@angular/core").Signal<number>;
2110
+ /** Config `tabs` declarada para esta instancia. */
2111
+ readonly vista: import("@angular/core").Signal<UiViewTabs | undefined>;
2112
+ /** Error determinista de configuración (schema/vista ausente o no-tabs). */
2113
+ private readonly errorConfig;
2114
+ /** Error de validación D2 (se recalcula una sola vez, en el constructor). */
2115
+ private readonly errorValidacion;
2116
+ /** Error mostrado: validación del schema primero, luego configuración. */
2117
+ readonly error: import("@angular/core").Signal<string | null>;
2118
+ /** Todos los hijos declarados (orden de declaración). */
2119
+ private readonly hijos;
2120
+ /** Hijos visibles: TODAS sus condiciones `visibleWhen` pasan sin record. */
2121
+ readonly visibles: import("@angular/core").Signal<readonly ViewChildNode[]>;
2122
+ /** Selección del usuario (intención; vacío ⇒ usar `initial`/primera). */
2123
+ private readonly seleccion;
2124
+ /**
2125
+ * Pestaña activa EFECTIVA: derivada pura de la selección y de las
2126
+ * visibles — si la selección dejó de existir (u oculta), cae a `initial`
2127
+ * o a la primera visible de forma SINCRÓNICA y visible (R9.9.5: nunca
2128
+ * perder la vista elegida en silencio). Estado LOCAL por instancia.
2129
+ */
2130
+ readonly activo: import("@angular/core").Signal<string>;
2131
+ /** Ids ya seleccionados alguna vez (sustento de la retención). */
2132
+ private readonly visitados;
2133
+ /** Paneles en DOM: activo + retenidos visitados que siguen visibles. */
2134
+ readonly montados: import("@angular/core").Signal<readonly ViewChildNode[]>;
2135
+ constructor();
2136
+ /** Selecciona una pestaña y, opcionalmente, mueve el foco a su botón. */
2137
+ seleccionar(id: string, conFoco: boolean): void;
2138
+ /** Navegación por teclado del tablist: flechas con wrap, Home/End. */
2139
+ onKeydown(event: KeyboardEvent): void;
2140
+ /** El panel de este hijo está montado (⇒ existe su `aria-controls`). */
2141
+ montado(id: string): boolean;
2142
+ /** Clases del botón de pestaña: activo resaltado con token `primary`. */
2143
+ claseTab(id: string): string;
2144
+ /** Etiqueta a11y del tablist (la del recurso). */
2145
+ etiqueta(): string;
2146
+ static ɵfac: i0.ɵɵFactoryDeclaration<ComposerHost, never>;
2147
+ static ɵcmp: i0.ɵɵComponentDeclaration<ComposerHost, "app-composer-host", never, {}, {}, never, never, true, never>;
2148
+ }
2149
+ /**
2150
+ * Contexto de accesibilidad que `app-field-host` expone a los controles.
2151
+ *
2152
+ * El host conoce la ayuda y el error (ambos viven en SU plantilla), pero el
2153
+ * `<input>` real vive dentro de un componente hijo distinto por cada tipo de
2154
+ * campo. En lugar de pasar dos inputs por cada uno de los 14 controles, el
2155
+ * host provee este contexto y cada control importa `FieldAria`, que lo lee
2156
+ * por DI y lo vuelca en el elemento.
2157
+ *
2158
+ * Selector por `[id]`: casa sólo con el elemento que ya recibe el `for` de la
2159
+ * etiqueta, así el mismo id es ancla de label y de `aria-describedby`. Los
2160
+ * controles fuera de un `app-field-host` inyectan `null` y no emiten nada.
2161
+ */
2162
+ interface FieldAriaContext {
2163
+ /** `id` del texto de ayuda y/o del error, separados por espacio. */
2164
+ readonly describedBy: Signal<string | null>;
2165
+ /** `true` cuando el campo muestra error visible (mismo criterio que el mensaje). */
2166
+ readonly invalid: Signal<boolean>;
2167
+ /**
2168
+ * Requerido por schema. Necesario porque la marca visual `*` desaparece en
2169
+ * los recursos donde mandan los `(opcional)` (§2): sin este atributo el
2170
+ * requerimiento quedaría sólo en pantalla.
2171
+ */
2172
+ readonly required: Signal<boolean>;
2173
+ }
2174
+ declare const FIELD_ARIA: InjectionToken<FieldAriaContext>;
2175
+ /**
2176
+ * Vincula `aria-invalid` y `aria-describedby` a un control de formulario.
2177
+ *
2178
+ * `aria-invalid` se emite SIEMPRE (`"true"`/`"false"`, nunca ausente) para
2179
+ * que el atributo sea inspeccionable en cualquier estado; `aria-describedby`
2180
+ * se omite cuando no hay ni ayuda ni error, que es el caso por defecto.
2181
+ * `aria-required` sigue la misma regla que `aria-invalid`.
2182
+ *
2183
+ * Convención con `role="alert"` en el mensaje de error: el `alert` anuncia el
2184
+ * error en el momento en que aparece (fallo de submit), mientras que
2185
+ * `aria-describedby` lo lee al enfocar el campo. Son momentos distintos; no
2186
+ * compiten.
2187
+ */
2188
+ declare class FieldAria {
2189
+ private readonly ctx;
2190
+ protected readonly ariaInvalid: Signal<string | null>;
2191
+ protected readonly ariaRequired: Signal<string | null>;
2192
+ protected readonly ariaDescribedBy: Signal<string | null>;
2193
+ static ɵfac: i0.ɵɵFactoryDeclaration<FieldAria, never>;
2194
+ static ɵdir: i0.ɵɵDirectiveDeclaration<FieldAria, "input[id], select[id], textarea[id], output[id]", never, {}, {}, never, never, true, never>;
2195
+ }
2196
+ /**
2197
+ * Forma del valor inicial que siembra un campo (D32 · R3.8).
2198
+ *
2199
+ * Son **todos** los valores posibles del dominio: un campo de formulario
2200
+ * nuevo nace como cadena, número, booleano, lista o como contenedor que
2201
+ * siembra sus hijos. Si un tipo llegara a necesitar otra forma, ampliar este
2202
+ * `type` obliga a compilar el `switch` de `seedValue` — que es exactamente
2203
+ * el fail-loud que la regla 4 pide.
2204
+ */
2205
+ type SeedKind = 'string' | 'number' | 'boolean' | 'array' | 'recursive';
2206
+ /**
2207
+ * Siembra del modelo del formulario: un valor por defecto para CADA campo
2208
+ * declarado en el schema (base.md §10).
2209
+ *
2210
+ * Compartido entre `form-model` (crear/recargar) y los componentes de lista
2211
+ * (crear ítems nuevos con la forma de sus `itemFields`).
2212
+ */
2213
+ /**
2214
+ * Valor inicial cuando el schema no declara `default`.
2215
+ *
2216
+ * **D32 · ya no despacha por tipo.** El `switch` de 20 ramas que obligaba a
2217
+ * editar este fichero al añadir un campo — y que §0 contaba como tocar el
2218
+ * motor — pasa a resolver el nombre que declara el propio descriptor
2219
+ * (`RenderDescriptor.seed`). «Añadir un tipo» queda en carpeta + manifiesto
2220
+ * + unión: este archivo no se entera.
2221
+ *
2222
+ * Dos salidas distintas, ninguna silenciosa:
2223
+ *
2224
+ * · **tipo fuera del catálogo** ⇒ `undefined`, la misma degradación
2225
+ * controlada que el render (R1.10): el campo no se pinta, así que su
2226
+ * valor no existe;
2227
+ * · **descriptor sin `seed`** ⇒ lanza. Es un error de programación que
2228
+ * `core/registry/contract.spec.ts` detecta en CI antes de llegar acá.
2229
+ */
2230
+ declare function seedValue(field: FieldSchema): unknown;
2231
+ /** Valor sembrado de un campo, fusionando `default` con la forma del schema. */
2232
+ declare function seedFieldValue(field: FieldSchema): unknown;
2233
+ /**
2234
+ * Siembra recursiva: los `group` anidan sus propios campos. Exportada porque
2235
+ * la lista crea sus ítems nuevos con la misma forma (`base.md §6`).
2236
+ */
2237
+ declare function seedFields(fields: readonly FieldSchema[]): Record<string, unknown>;
2238
+ /**
2239
+ * Fusiona el registro leído con la forma sembrada del schema.
2240
+ *
2241
+ * El backend puede omitir claves (p. ej. un ítem guardado antes de que el
2242
+ * schema agregara un campo): sin esta fusión el árbol no tendría nodo para
2243
+ * ese campo y `childTree` lanzaría al renderizar. Lo ausente toma su
2244
+ * `default` y los extras del registro (p. ej. `position` de los ítems) se
2245
+ * conservan.
2246
+ */
2247
+ declare function mergeRecord(fields: readonly FieldSchema[], record: Record<string, unknown>): Record<string, unknown>;
2248
+ declare function isRecord(value: unknown): value is Record<string, unknown>;
2249
+ /**
2250
+ * Registro único de render (R3.1 · `doc/plan-refactor-motor.md` §3.1).
2251
+ *
2252
+ * Un solo sitio declara qué componentes sabe dibujar la UI: cada tipo de
2253
+ * campo, widget, acción o vista aporta un `RenderDescriptor` y el registro
2254
+ * lo resuelve por `(kind, type)`. Los hosts dejan de mantener `Set`s de tipos
2255
+ * e imports a mano — **en runtime el registro es la única fuente de verdad**.
2256
+ *
2257
+ * Se carga a scope de módulo y no desde DI: `buildFormSchema()` es una
2258
+ * función pura que consulta el registro sin inyectar nada, así que un
2259
+ * registro llenado únicamente por un provider dejaría esa consulta correr
2260
+ * contra un catálogo vacío. `provideRenderDescriptors()` existe igualmente
2261
+ * como punto de entrada explícito para `app.config.ts` (R3.2).
2262
+ *
2263
+ * Autodescubrimiento (D9-1): se opta por **registro manual** — un manifiesto
2264
+ * con una línea por descriptor — porque `import.meta.glob` no está
2265
+ * implementada por el bundler de producción (`@angular/build:application`
2266
+ * usa esbuild) y la llamada cruda sobreviviría en `dist/`, rompiendo el
2267
+ * arranque. Con Vite sólo en el dev-server y en el runner de tests, además,
2268
+ * el comportamiento habría diferido entre `npm test` y `npm run build`.
2269
+ */
2270
+ /** Qué clase de cosa se está resolviendo. Identificador único dentro de su `kind`. */
2271
+ type RenderKind = 'field' | 'column-format' | 'widget' | 'action' | 'view' | 'layout';
2272
+ type ComponentRenderKind = Exclude<RenderKind, 'layout'>;
2273
+ /** Superficies en las que un descriptor puede aparecer. */
2274
+ type RenderSurface = 'form' | 'display' | 'list' | 'filter';
2275
+ /**
2276
+ * Qué puede describir un `RenderDescriptor`: un campo del formulario, un
2277
+ * widget del dashboard o una columna de listado (R4.3).
2278
+ *
2279
+ * Es el tipo del input `config` del host. Los descriptores sólo comparten
2280
+ * `key` y `label`; lo demás lo decide cada `kind`, y quien necesita el resto
2281
+ * lo castea con su propia mirada (`cfgCampo()`, que es lo que el host ya
2282
+ * hacía antes de que existieran las columnas).
2283
+ */
2284
+ type RenderConfig = FieldSchema | WidgetSchema | ListColumn;
2285
+ /**
2286
+ * Contexto de render que el host expone por DI (R3.4 · `doc/plan-refactor-motor.md` §3.4).
2287
+ *
2288
+ * Los controles viven en un componente distinto por cada tipo, así que en vez
2289
+ * de repetir cinco inputs en los 20 el host lo provee una sola vez y cada
2290
+ * control lo inyecta si lo necesita — el mismo criterio que ya usaba
2291
+ * `FIELD_ARIA`.
2292
+ *
2293
+ * `tree` viaja en el contexto y no como input suelto porque es **por
2294
+ * instancia**: los hijos de un `group` o de un `list` navegan su subárbol, no
2295
+ * el del padre.
2296
+ *
2297
+ * Vive acá y no en `render-host.ts` porque el contrato de los descriptores
2298
+ * (`RenderExtras`) necesita nombrarlo: el descriptor de un campo compuesto
2299
+ * decide qué se le pasa al componente y eso sólo es escribible conociendo el
2300
+ * tipo. `render-host.ts` lo re-exporta para no mover a sus consumidores.
2301
+ */
2302
+ interface RenderContext {
2303
+ /** Árbol del formulario del nivel de este campo. */
2304
+ tree: RootTree;
2305
+ /** Errores del backend por ruta (`address.street`, `faq[2].answer`). */
2306
+ serverErrors: Record<string, string>;
2307
+ /** Prefijo de ruta de este campo en el registro raíz (`address.`). */
2308
+ pathPrefix: string;
2309
+ /** Si el registro ya existe (lo consume `slug` con `lockAfterCreate`). */
2310
+ exists: boolean;
2311
+ /** Recurso dueño del formulario (lo consume `image` para la firma, §11.2). */
2312
+ resource: string;
2313
+ }
2314
+ /**
2315
+ * Contexto vacío con el que arranca `RenderHost.ctx`.
2316
+ *
2317
+ * Sólo lo alcanza quien renderiza sin contexto — los widgets, que no forman
2318
+ * parte de un formulario. Un campo montado sin `ctx` es un error de
2319
+ * programación y `childTree` lo dice con su propio mensaje, igual que lo
2320
+ * decía el `input.required` anterior.
2321
+ */
2322
+ declare const RENDER_CONTEXT_VACIO: RenderContext;
2323
+ /**
2324
+ * Segundo argumento de `RenderDescriptor.inputs`.
2325
+ *
2326
+ * Aporta lo que el host conoce y el descriptor no: el contexto de formulario
2327
+ * (sólo para campos), el payload del dato (sólo para widgets) y los dos
2328
+ * templates con los que se componen `group` y `list`.
2329
+ */
2330
+ interface RenderExtras {
2331
+ /** Contexto del nivel; `RENDER_CONTEXT_VACIO` cuando no hay formulario. */
2332
+ ctx: RenderContext;
2333
+ /** Payload del widget (`<app-widget-host [data]>`); `null` en campos. */
2334
+ data: unknown;
2335
+ /** Template del contenido de un `group`. */
2336
+ groupContent?: TemplateRef<unknown>;
2337
+ /** Template de los hijos de un `list`. */
2338
+ childTemplate?: TemplateRef<unknown>;
2339
+ }
2340
+ /**
2341
+ * Declaración de un componente renderizable.
2342
+ *
2343
+ * No lleva `configSchema`: esa cláusula de `refactormotor.md` no existe en
2344
+ * el código y el plan la descartó (D17).
2345
+ */
2346
+ interface RenderDescriptor<C = unknown> {
2347
+ /** Clase de cosa. */
2348
+ kind: ComponentRenderKind;
2349
+ /** Identificador `kebab-case`, único dentro de `kind`. */
2350
+ type: string;
2351
+ /** Versión del contrato en que apareció (`doc/refactormotor.md`). */
2352
+ since: string;
2353
+ /**
2354
+ * Carga perezosa del componente. El registro se resuelve sin arrastrar el
2355
+ * bundle: los descriptores sólo guardan el cierre, no la clase.
2356
+ */
2357
+ load: () => Promise<Type<any>>;
2358
+ /** Superficies en las que se usa. Sólo lo que se pueda evidenciar en el código. */
2359
+ surfaces?: RenderSurface[];
2360
+ /** Valores por defecto de la configuración del descriptor. */
2361
+ defaults?: Partial<C>;
2362
+ /**
2363
+ * Forma del valor inicial que siembra este campo en el modelo del
2364
+ * formulario (D32 · R3.8).
2365
+ *
2366
+ * **Dato, no función**: un nombre resuelto por `shared/utils/seed.ts`, no
2367
+ * una clausura. Así el descriptor sigue siendo puro —`kind`, `type`,
2368
+ * `load`, `surfaces`, `seed`— y `seed.ts` es el único dueño de recorrer
2369
+ * un árbol de campos. La alternativa (`seed: (cfg) => …`) obligaría a que
2370
+ * `group` importara `seedFields`, lo que cerraría un ciclo
2371
+ * registro → manifiesto → descriptor → `seed` → registro.
2372
+ *
2373
+ * Sólo los campos lo declaran. **Obligatorio para `kind: 'field'`** — lo
2374
+ * vigila `core/registry/contract.spec.ts`, porque la exhaustividad de una
2375
+ * unión de strings no puede imponerse en tiempo de compilación.
2376
+ */
2377
+ seed?: SeedKind;
2378
+ /**
2379
+ * Cableado del componente: qué inputs recibe y con qué valor.
2380
+ *
2381
+ * Es **la** pieza que mantiene el host genérico: sin esto el motor tendría
2382
+ * que saber que `metric-card` recibe `label/value/hint` y que `group`
2383
+ * necesita además el template de sus hijos, y «añadir un tipo» volvería a
2384
+ * tocar `render-host.ts` (R3.8). Cada descriptor declara el suyo y el host
2385
+ * sólo hace `ngComponentOutletInputs = descriptor.inputs(config, extras)`.
2386
+ *
2387
+ * No es obligatorio: el 80 % de los tipos comparten la forma estándar de su
2388
+ * `kind` — `{field, tree}` en campos, `{widget, data}` en widgets — y para
2389
+ * ellos el host aplica esa forma por defecto. Se declara sólo cuando el
2390
+ * componente se desvía de ella.
2391
+ */
2392
+ inputs?: (config: C, extras: RenderExtras) => Record<string, unknown>;
2393
+ }
2394
+ /** Declaración de un layout manejado por el host de formularios. */
2395
+ interface LayoutDescriptor {
2396
+ kind: 'layout';
2397
+ type: LayoutMode;
2398
+ since: string;
2399
+ }
2400
+ /**
2401
+ * Registra un descriptor. Idempotente por clave: volver a registrar el mismo
2402
+ * `(kind, type)` lo sobrescribe, lo que permite que un módulo se recargue en
2403
+ * desarrollo sin duplicar entradas.
2404
+ */
2405
+ declare function register(kind: 'layout', descriptor: LayoutDescriptor): void;
2406
+ declare function register(kind: ComponentRenderKind, descriptor: RenderDescriptor): void;
2407
+ /**
2408
+ * Quita un descriptor (R7.6). Simétrico a `register`: los specs que
2409
+ * registran entradas de prueba deben retirarlas, o el catálogo global queda
2410
+ * contaminado para cualquier otro spec que lea `renderKinds()` (flaky por
2411
+ * orden de ejecución).
2412
+ */
2413
+ declare function unregister(kind: RenderKind, type: string): void;
2414
+ /** Descriptor declarado para `(kind, type)`, o `undefined` si nadie lo registró. */
2415
+ declare function get(kind: ComponentRenderKind, type: string): RenderDescriptor | undefined;
2416
+ /** ¿La UI sabe dibujar este `(kind, type)`? Reemplaza a los `Set`s de los hosts. */
2417
+ export declare function isSupported(kind: RenderKind, type: string): boolean;
2418
+ /**
2419
+ * Componente para `(kind, type)`, o `fallback` si el tipo no está en el
2420
+ * catálogo.
2421
+ *
2422
+ * Es **asíncrona** porque `load()` importa el componente bajo demanda; el
2423
+ * `fallback` es síncrono y sólo se usa cuando no hay descriptor. Un fallo de
2424
+ * `load()` NO se traga: propagarlo es lo que hace visible un descriptor mal
2425
+ * escrito, y la política «nunca pantalla rota» la resuelve el llamante con el
2426
+ * `fallback` que ya recibe.
2427
+ */
2428
+ declare function resolve<C = unknown>(kind: ComponentRenderKind, type: string, fallback: Type<C>): Promise<Type<C>>;
2429
+ /**
2430
+ * Punto de entrada para `app.config.ts` (R3.2).
2431
+ *
2432
+ * El catálogo ya quedó cargado al importar este módulo; el provider sirve
2433
+ * para que el arranque sea explícito y para avisar en desarrollo si el
2434
+ * manifiesto vino vacío — que es exactamente lo que pasaría si alguien
2435
+ * borra una línea del manifiesto sin reemplazarla.
2436
+ */
2437
+ export declare function provideRenderDescriptors(): EnvironmentProviders;
2438
+ /** Cuántos descriptores hay registrados. Sólo para comprobaciones y tests. */
2439
+ declare function renderDescriptorCount(): number;
2440
+ /**
2441
+ * `kind`s que hoy tienen al menos un descriptor.
2442
+ *
2443
+ * Existe para el test de exhaustividad (R3.7): él recorre los schemas de los
2444
+ * `kind`s que tienen registro y **falla** si aparece un `kind` con descriptor
2445
+ * que nadie recorre — de lo contrario «tener registro» y «estar cubierto»
2446
+ * serían dos afirmaciones distintas que nadie reconcilia.
2447
+ */
2448
+ export declare function renderKinds(): RenderKind[];
2449
+ /** Token con el que los controles inyectan el `RenderContext` del host. */
2450
+ declare const RENDER_CONTEXT: InjectionToken<RenderContext>;
2451
+ /**
2452
+ * Host genérico de render (R3.4).
2453
+ *
2454
+ * Sustituye el `@switch` de 21 ramas de `FieldHost` por una resolución contra
2455
+ * `core/registry/render-registry.ts`: el tipo llega por `[type]`, el registro
2456
+ * devuelve el componente y `NgComponentOutlet` lo monta. Añadir un tipo deja
2457
+ * de tocar este archivo.
2458
+ *
2459
+ * Conserva el envoltorio que `FieldHost` dibujaba — `<label>`, marca
2460
+ * required/opcional, `data-field`, `colSpanClass`, mensaje de error y ayuda —
2461
+ * porque eso es lo que el operador ve y no depende del tipo de control.
2462
+ *
2463
+ * **Sin imports de los 20 controles**: sólo llegan por `load()` perezoso del
2464
+ * descriptor. Mientras el `import()` resuelve se pinta `Skeleton`, y un tipo
2465
+ * fuera del catálogo degrada a `UnknownRender` en dev (nunca pantalla rota).
2466
+ *
2467
+ * **Modo `display` (R4.1 · R4.2).** El mismo host pinta la ficha de detalle:
2468
+ * con `mode="display"` no hay árbol, ni etiqueta, ni error — sólo el valor
2469
+ * crudo que llega por `[value]` y el componente que elige la tabla
2470
+ * `fields/display/display-defaults.ts`. El descriptor declara la superficie
2471
+ * (`surfaces: ['form', 'display']`) y, si no la declara o el tipo no está en
2472
+ * la tabla, corre la misma degradación controlada de R1.10.
2473
+ */
2474
+ declare class RenderHost implements FieldAriaContext, RenderContext {
2475
+ /** Categoría de render. Resuelve campos y widgets (R3.6). */
2476
+ readonly kind: import("@angular/core").InputSignal<ComponentRenderKind>;
2477
+ /** Identificador del tipo dentro de su `kind`. */
2478
+ readonly type: import("@angular/core").InputSignal<string>;
2479
+ /**
2480
+ * Configuración del descriptor: el `FieldSchema` del campo, el
2481
+ * `WidgetSchema` del widget o la `ListColumn` de la columna (R4.3).
2482
+ */
2483
+ readonly config: import("@angular/core").InputSignal<RenderConfig>;
2484
+ /**
2485
+ * Contexto de render del nivel de este campo.
2486
+ *
2487
+ * Con valor por defecto y no requerido: los widgets no pertenecen a un
2488
+ * formulario y llegan sin árbol (R3.6). El default es un contexto vacío
2489
+ * que nunca se lee en esa rama — un campo montado sin contexto real ya
2490
+ * era un error antes y `childTree` lo sigue siendo.
2491
+ */
2492
+ readonly ctx: import("@angular/core").InputSignal<RenderContext>;
2493
+ /** Payload del widget (`app-widget-host [data]`). `null` en campos. */
2494
+ readonly data: import("@angular/core").InputSignal<unknown>;
2495
+ /**
2496
+ * Superficie de render (R4.1).
2497
+ *
2498
+ * `'form'` (por defecto) es el formulario de siempre: árbol, etiqueta,
2499
+ * marca required, error y ayuda. `'display'` es la ficha de detalle:
2500
+ * valor crudo, sin envoltorio — es el `mode="display"` con el que
2501
+ * `detail-view` deja de dibujar sus propias celdas (R4.2).
2502
+ */
2503
+ readonly mode: import("@angular/core").InputSignal<"form" | "display">;
2504
+ /**
2505
+ * Valor crudo del registro, **sólo para el modo `display`**.
2506
+ *
2507
+ * En el formulario el valor vive en el árbol de Signal Forms y este
2508
+ * input se ignora: un campo de detalle no tiene árbol que consultar.
2509
+ */
2510
+ readonly value: import("@angular/core").InputSignal<unknown>;
2511
+ /**
2512
+ * Presentación en chip, **sólo para el modo `display`** (R4.2).
2513
+ *
2514
+ * La cabecera de la ficha siempre mostró `headerFields` como etiquetas
2515
+ * compactas, aunque el valor fuera texto plano. Sin este matiz el host
2516
+ * pintaría «ORD-42» como texto suelto y la cabecera cambiaría de cara en
2517
+ * los 7 detalles. El aviso va en el contrato de entrada de los display —
2518
+ * uniforme para los 6 — y sólo lo aprovechan los que dibujan texto: los
2519
+ * que ya son chip (`badge`) o no son un valor escalar (`image`, `list`)
2520
+ * lo ignoran a propósito.
2521
+ */
2522
+ readonly chip: import("@angular/core").InputSignal<boolean>;
2523
+ private readonly dev;
2524
+ /** Mantiene `whenStable()` bloqueado mientras el descriptor carga su chunk. */
2525
+ private readonly pendientes;
2526
+ /** `true` cuando se dibuja un campo, `false` para cualquier otro `kind`. */
2527
+ protected readonly esCampo: import("@angular/core").Signal<boolean>;
2528
+ /** `true` en la rama de detalle (R4.1): sin árbol, etiqueta ni error. */
2529
+ protected readonly esDisplay: import("@angular/core").Signal<boolean>;
2530
+ /**
2531
+ * ¿El descriptor declara la superficie `display`? (R4.1)
2532
+ *
2533
+ * Es la respuesta a «cada descriptor de campo declara si sabe pintarse en
2534
+ * `display`»: sin esa declaración el host no pinta el valor en la ficha,
2535
+ * y `contract.spec.ts` vigila que los 20 la traigan.
2536
+ */
2537
+ private readonly declaraDisplay;
2538
+ /**
2539
+ * Componente que pinta este tipo en `display`.
2540
+ *
2541
+ * El descriptor sólo dice **si** sabe pintarse; **qué** componente usa lo
2542
+ * decide la tabla por tipo (`fields/display/display-defaults.ts`), que es
2543
+ * la que garantiza que el mismo `field.type` se pinte igual en todas las
2544
+ * fichas.
2545
+ */
2546
+ private readonly loaderDisplay;
2547
+ /** El modo display tiene componente para este tipo. */
2548
+ protected readonly soportaDisplay: import("@angular/core").Signal<boolean>;
2549
+ /**
2550
+ * Valor vacío, con el mismo criterio que `formatValue` de `detail-view`
2551
+ * antes de R4.2 (`null`, `undefined` o `''`): se pinta un `—` y el
2552
+ * componente ni se monta. `false`, `0` y `[]` **no** están vacíos.
2553
+ */
2554
+ protected readonly vacio: import("@angular/core").Signal<boolean>;
2555
+ /** Configuración leída como campo. Sólo válida en la rama de campo. */
2556
+ private readonly cfgCampo;
2557
+ private readonly node;
2558
+ protected readonly state: import("@angular/core").Signal<import("@angular/forms/signals").FieldState<unknown, string | number>>;
2559
+ /** Nombre del control: ancla del `<label for>` y base de `*-help`/`*-error`. */
2560
+ protected readonly name: import("@angular/core").Signal<string>;
2561
+ /**
2562
+ * Fuera del catálogo o oculto por `visibleWhen`: no se dibuja.
2563
+ *
2564
+ * El `isSupported` va primero y corta antes de leer `state()`: un tipo
2565
+ * desconocido se siembra como `undefined`, Signal Forms no le crea nodo y
2566
+ * `childTree` lanzaría «El formulario no contiene el campo».
2567
+ *
2568
+ * El `esCampo()` corta lo mismo para los widgets: no hay árbol que ocultar
2569
+ * ni nodo que consultar, así que el widget se dibuja si está en el
2570
+ * catálogo y ya.
2571
+ */
2572
+ protected readonly supported: import("@angular/core").Signal<boolean>;
2573
+ /**
2574
+ * Sin renderizador para este tipo **en la superficie actual**.
2575
+ *
2576
+ * En `display`: el descriptor no declara la superficie o el tipo no está
2577
+ * en la tabla de display. En `form`: sigue siendo «fuera del catálogo».
2578
+ * Ninguna de las dos ramas toca `state()`, que en display no existe.
2579
+ */
2580
+ private readonly unsupported;
2581
+ protected readonly showUnknown: import("@angular/core").Signal<boolean>;
2582
+ private readonly _trazaTipoDesconocido;
2583
+ /** Componente montado, o `null` mientras el `load()` perezoso resuelve. */
2584
+ private readonly control;
2585
+ /** Descarta cargas obsoletas cuando el tipo cambia antes de que resuelva. */
2586
+ private carga;
2587
+ private readonly _cargar;
2588
+ /**
2589
+ * Inputs del componente de display.
2590
+ *
2591
+ * Forma fija y uniforme para los 6 componentes de
2592
+ * `fields/display/`: `field` (el `FieldSchema` completo, para los que
2593
+ * necesitan `options`), `value` (el valor crudo) y `chip` (la
2594
+ * presentación de la cabecera). El descriptor no interviene acá — en
2595
+ * display no hay ni control ni árbol que cablear.
2596
+ */
2597
+ private readonly displayInputs;
2598
+ /**
2599
+ * Inputs del componente montado.
2600
+ *
2601
+ * El motor no sabe qué recibe cada tipo: pregunta al descriptor (`inputs`)
2602
+ * y sólo aplica la forma estándar del `kind` cuando el descriptor no la
2603
+ * sobreescribe — `{field, tree}` en campos, `{widget, data}` en widgets.
2604
+ * Ésa es la pieza que hace que «añadir un tipo» no toque este archivo
2605
+ * (R3.8). `multiselect` e `image` no pasan por aquí: se declaran a mano
2606
+ * por D27.
2607
+ */
2608
+ private readonly controlInputs;
2609
+ protected readonly group: import("@angular/core").Signal<GroupField>;
2610
+ protected readonly list: import("@angular/core").Signal<ListField>;
2611
+ private readonly groupTpl;
2612
+ private readonly childTpl;
2613
+ /** Subárbol y prefijo que heredan los hijos del grupo. */
2614
+ protected readonly groupChildCtx: import("@angular/core").Signal<RenderContext>;
2615
+ protected listChildCtx(child: ListChildContext): RenderContext;
2616
+ /** `sm:col-span-*` compartido con el grupo (literales: Tailwind v4 es JIT). */
2617
+ colSpan(width?: number): string;
2618
+ /**
2619
+ * Los tres `FormValueControl` se declaran en la plantilla (D27), así que
2620
+ * su campo necesita el tipo estrecho que exige cada input — el mismo
2621
+ * estrechado que hacía `FieldHost`.
2622
+ */
2623
+ protected readonly multiselectCfg: import("@angular/core").Signal<MultiselectField>;
2624
+ protected readonly imageCfg: import("@angular/core").Signal<ImageField>;
2625
+ protected readonly fileCfg: import("@angular/core").Signal<FileField>;
2626
+ private readonly multiselectNode;
2627
+ private readonly imageNode;
2628
+ private readonly fileNode;
2629
+ /**
2630
+ * Error visible: primero el del backend (si existe para esta ruta) y
2631
+ * después el propio, solo cuando el campo ya se tocó (validación al
2632
+ * perder el foco).
2633
+ */
2634
+ protected readonly errorMessage: import("@angular/core").Signal<string | null>;
2635
+ /**
2636
+ * Compuestos: varios controles internos, ninguno único. Para ellos
2637
+ * `<label for>` no tendría ancla, porque ningún elemento lleva el id del
2638
+ * campo padre (los hijos tienen los suyos propios).
2639
+ */
2640
+ protected readonly composite: import("@angular/core").Signal<boolean>;
2641
+ /**
2642
+ * Ancla de la ayuda, sólo mientras no hay error: §2 dice que el error
2643
+ * **reemplaza** la ayuda, de modo que el id no puede apuntar a un nodo
2644
+ * que ya no está en el DOM.
2645
+ */
2646
+ readonly helpId: import("@angular/core").Signal<string | null>;
2647
+ readonly errorId: import("@angular/core").Signal<string | null>;
2648
+ readonly describedBy: import("@angular/core").Signal<string | null>;
2649
+ /** Mismo criterio que `errorMessage()` — el estado del control no es público. */
2650
+ readonly invalid: import("@angular/core").Signal<boolean>;
2651
+ readonly required: import("@angular/core").Signal<boolean>;
2652
+ private readonly schema;
2653
+ /**
2654
+ * Se decide una vez por recurso y se aplica a todos los niveles: es un
2655
+ * juicio de diseño sobre el formulario, no sobre un campo suelto. Contado
2656
+ * por nivel (sólo top-level) — un conteo plano mezcla los subcampos de
2657
+ * `group`/`list` y da ratios imposibles.
2658
+ */
2659
+ protected readonly markOptionals: import("@angular/core").Signal<boolean>;
2660
+ protected readonly showRequiredMark: import("@angular/core").Signal<boolean | undefined>;
2661
+ protected readonly showOptionalMark: import("@angular/core").Signal<boolean>;
2662
+ get tree(): RootTree;
2663
+ get serverErrors(): Record<string, string>;
2664
+ get pathPrefix(): string;
2665
+ get exists(): boolean;
2666
+ get resource(): string;
2667
+ static ɵfac: i0.ɵɵFactoryDeclaration<RenderHost, never>;
2668
+ static ɵcmp: i0.ɵɵComponentDeclaration<RenderHost, "app-render-host", never, {
2669
+ "kind": {
2670
+ "alias": "kind";
2671
+ "required": false;
2672
+ "isSignal": true;
2673
+ };
2674
+ "type": {
2675
+ "alias": "type";
2676
+ "required": true;
2677
+ "isSignal": true;
2678
+ };
2679
+ "config": {
2680
+ "alias": "config";
2681
+ "required": true;
2682
+ "isSignal": true;
2683
+ };
2684
+ "ctx": {
2685
+ "alias": "ctx";
2686
+ "required": false;
2687
+ "isSignal": true;
2688
+ };
2689
+ "data": {
2690
+ "alias": "data";
2691
+ "required": false;
2692
+ "isSignal": true;
2693
+ };
2694
+ "mode": {
2695
+ "alias": "mode";
2696
+ "required": false;
2697
+ "isSignal": true;
2698
+ };
2699
+ "value": {
2700
+ "alias": "value";
2701
+ "required": false;
2702
+ "isSignal": true;
2703
+ };
2704
+ "chip": {
2705
+ "alias": "chip";
2706
+ "required": false;
2707
+ "isSignal": true;
2708
+ };
2709
+ }, {}, never, never, true, never>;
2710
+ }
2711
+ /**
2712
+ * Entrada estable de los formularios (base.md §5).
2713
+ *
2714
+ * **R3.5 · ya no despacha.** Este host redujo sus 493 líneas —el `@switch`
2715
+ * de 20 ramas, los 21 imports a mano y el envoltorio de label/errores— a un
2716
+ * adaptador que traduce su API histórica (`field` + `tree` + `pathPrefix` …)
2717
+ * al `RenderContext` que espera `<app-render-host>`, el motor único de
2718
+ * render (F1 · `refactormotor.md`).
2719
+ *
2720
+ * El rótulo, las marcas required/opcional, el error, la ayuda,
2721
+ * `FIELD_ARIA` y la **recursión de `group`/`list`** (los `ng-template` con
2722
+ * `ngTemplateOutlet` y `ListChildContext`) viven ahora en `RenderHost`, que
2723
+ * se auto-importa para instanciar sus propios hijos.
2724
+ *
2725
+ * Rutas del backend: `pathPrefix` encadena el prefijo del contenedor
2726
+ * (`address.`, `faq[2].`) y `serverErrors` lleva el mapa completo; el error
2727
+ * propio de este campo se resuelve como `serverErrors[prefix + key]`.
2728
+ */
2729
+ export declare class FieldHost {
2730
+ readonly field: import("@angular/core").InputSignal<FieldSchema>;
2731
+ readonly tree: import("@angular/core").InputSignal<RootTree>;
2732
+ readonly exists: import("@angular/core").InputSignal<boolean>;
2733
+ readonly serverErrors: import("@angular/core").InputSignal<Record<string, string>>;
2734
+ readonly pathPrefix: import("@angular/core").InputSignal<string>;
2735
+ readonly resource: import("@angular/core").InputSignal<string>;
2736
+ protected readonly ctx: import("@angular/core").Signal<RenderContext>;
2737
+ static ɵfac: i0.ɵɵFactoryDeclaration<FieldHost, never>;
2738
+ static ɵcmp: i0.ɵɵComponentDeclaration<FieldHost, "app-field-host", never, {
2739
+ "field": {
2740
+ "alias": "field";
2741
+ "required": true;
2742
+ "isSignal": true;
2743
+ };
2744
+ "tree": {
2745
+ "alias": "tree";
2746
+ "required": true;
2747
+ "isSignal": true;
2748
+ };
2749
+ "exists": {
2750
+ "alias": "exists";
2751
+ "required": false;
2752
+ "isSignal": true;
2753
+ };
2754
+ "serverErrors": {
2755
+ "alias": "serverErrors";
2756
+ "required": false;
2757
+ "isSignal": true;
2758
+ };
2759
+ "pathPrefix": {
2760
+ "alias": "pathPrefix";
2761
+ "required": false;
2762
+ "isSignal": true;
2763
+ };
2764
+ "resource": {
2765
+ "alias": "resource";
2766
+ "required": false;
2767
+ "isSignal": true;
2768
+ };
2769
+ }, {}, never, never, true, never>;
2770
+ }
2771
+ /**
2772
+ * Opciones del diálogo. Es estructuralmente compatible con
2773
+ * `core.NotificationService['confirmation']` (mismos campos) — `shared` no
2774
+ * importa `core`, quien une ambos lados es el shell (Fase 7).
2775
+ */
2776
+ interface ConfirmDialogOptions {
2777
+ title: string;
2778
+ message: string;
2779
+ confirmLabel?: string;
2780
+ cancelLabel?: string;
2781
+ }
2782
+ export declare class ConfirmDialog {
2783
+ /** `null` = oculto (viene de `NotificationService.confirmation`). */
2784
+ readonly options: import("@angular/core").InputSignal<ConfirmDialogOptions | null>;
2785
+ readonly danger: import("@angular/core").InputSignal<boolean>;
2786
+ readonly confirmed: import("@angular/core").OutputEmitterRef<void>;
2787
+ readonly cancelled: import("@angular/core").OutputEmitterRef<void>;
2788
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConfirmDialog, never>;
2789
+ static ɵcmp: i0.ɵɵComponentDeclaration<ConfirmDialog, "app-confirm-dialog", never, {
2790
+ "options": {
2791
+ "alias": "options";
2792
+ "required": false;
2793
+ "isSignal": true;
2794
+ };
2795
+ "danger": {
2796
+ "alias": "danger";
2797
+ "required": false;
2798
+ "isSignal": true;
2799
+ };
2800
+ }, {
2801
+ "confirmed": "confirmed";
2802
+ "cancelled": "cancelled";
2803
+ }, never, never, true, never>;
2804
+ }
2805
+ type ToastKind = 'success' | 'error' | 'info';
2806
+ /** Estructuralmente idéntico a `core`'s `Toast` — no importa `core`. */
2807
+ interface ToastItem {
2808
+ id: number;
2809
+ kind: ToastKind;
2810
+ message: string;
2811
+ }
2812
+ /**
2813
+ * Contenedor visual de toasts: fijo arriba a la derecha, región `status`
2814
+ * viva (polite) para que se anuncien los mensajes. Recibe los toasts por
2815
+ * `input()` desde quien sí tiene `NotificationService` (Fase 7).
2816
+ */
2817
+ export declare class Toast {
2818
+ readonly toasts: import("@angular/core").InputSignal<ToastItem[]>;
2819
+ readonly closeLabel: import("@angular/core").InputSignal<string>;
2820
+ readonly dismissed: import("@angular/core").OutputEmitterRef<number>;
2821
+ static ɵfac: i0.ɵɵFactoryDeclaration<Toast, never>;
2822
+ static ɵcmp: i0.ɵɵComponentDeclaration<Toast, "app-toast", never, {
2823
+ "toasts": {
2824
+ "alias": "toasts";
2825
+ "required": false;
2826
+ "isSignal": true;
2827
+ };
2828
+ "closeLabel": {
2829
+ "alias": "closeLabel";
2830
+ "required": false;
2831
+ "isSignal": true;
2832
+ };
2833
+ }, {
2834
+ "dismissed": "dismissed";
2835
+ }, never, never, true, never>;
2836
+ }
2837
+ /**
2838
+ * Tripwire de compilación (R1.7 · D17): que la unión `FieldType` y los
2839
+ * tipos que la UI sabe dibujar sigan diciendo lo mismo.
2840
+ *
2841
+ * · un valor que no esté en la unión `FieldSchema` ⇒ `satisfies` falla;
2842
+ * · un tipo de la unión que falte acá ⇒ `FaltaEnLaUi` no es `never`.
2843
+ *
2844
+ * **No es una fuente de verdad en runtime.** El catálogo vive en
2845
+ * `core/registry/render-registry.ts`, alimentado por los descriptores de
2846
+ * abajo. Esta lista sólo existe para que `npm run build` y `npm test` vean
2847
+ * la aserción por igual, sin depender de que un test la ejecute.
2848
+ *
2849
+ * **Exportada en R3.7** para que `core/registry/contract.spec.ts` pueda
2850
+ * verificar además el sentido que este archivo no cubre — que cada nombre de
2851
+ * la lista tenga **descriptor**: aquí sólo se comprueba que está en la unión,
2852
+ * y un tipo declarado en la lista pero ausente del manifiesto se vería en el
2853
+ * primer schema que lo use.
2854
+ *
2855
+ * **Mudado acá en R3.8.** Vivía en `field-host/field-host.ts`, y §0 es
2856
+ * explícito: «si añadir un tipo obliga a editar `FieldHost`, el motor no
2857
+ * está terminado». Estaba sin uso en runtime — sólo servía a este tripwire —
2858
+ * así que trasladarlo acá no cambia comportamiento y deja el registro y su
2859
+ * aserción en el **mismo archivo**: añadir un tipo toca una carpeta y una
2860
+ * línea de este manifiesto, y el host no se entera.
2861
+ */
2862
+ 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"];
2863
+ /**
2864
+ * Manifiesto de descriptores de campo (R3.3 · registro único de render).
2865
+ *
2866
+ * Una línea por tipo: el registro los recoge al importar este módulo. Es la
2867
+ * **única** lista manual del catálogo de campos — `import.meta.glob` no
2868
+ * sobrevive a `ng build`, así que D9-1 se resuelve con «registro manual de
2869
+ * una línea». Ver `core/registry/render-registry.ts`.
2870
+ *
2871
+ * Añadir un tipo de campo ⇒ crear la carpeta + su `.descriptor.ts` + una
2872
+ * línea acá. **Ningún host se toca** (R3.8).
2873
+ */
2874
+ export declare const fieldDescriptors: readonly RenderDescriptor[];
2875
+ /** Los 7 formatos del `@switch` que este registro reemplaza (R4.3). */
2876
+ export declare const columnFormatDescriptors: readonly RenderDescriptor[];
2877
+ /**
2878
+ * Los 7 nombres, como dato.
2879
+ *
2880
+ * Va en paralelo a `ListColumnFormat` (unión de `schema.model`) y lo vigila
2881
+ * `core/registry/contract.spec.ts`: la unión vive en tipos y el registro en
2882
+ * runtime, y sólo un test reconcilia los dos — el mismo tripwire que ya
2883
+ * existe para los 20 campos y los 6 widgets.
2884
+ */
2885
+ export declare const COLUMN_FORMAT_TYPES: readonly ["text", "badge", "thumbnail", "boolean", "number", "currency", "date"];
2886
+ export declare const layoutDescriptors: readonly LayoutDescriptor[];
2887
+ export declare const viewDescriptors: readonly RenderDescriptor[];
2888
+ /**
2889
+ * Validador de composición de vistas (R9.9.2 · D2/D3).
2890
+ *
2891
+ * Una composición (un `uiView` contenedor con `config.children`, p. ej.
2892
+ * `tabs`) se valida ANTES del render con esta única función pura y síncrona:
2893
+ *
2894
+ * - identidad de hijos (`id` presente y único entre hermanos);
2895
+ * - tipos resueltos con `get('view', type)` — **nunca** `resolve()`, cuyo
2896
+ * `fallback` silencioso dejaría un hijo inválido pintándose de todos modos;
2897
+ * - `ref` rotos y ciclos (directos o transitivos) que los `ref` permiten;
2898
+ * - profundidad y cantidad de nodos con topes configurables;
2899
+ * - destinos de navegación (`resource` + `view` + params) y fuentes de
2900
+ * filas — una URL cruda nunca es un destino válido;
2901
+ *
2902
+ * El llamante es D3: `resourceRoutes()` en `shell.routes.ts` (fail-loud al
2903
+ * arrancar) y el guardián de montaje del composer. Cualquier violación lanza
2904
+ * `SchemaCompositionError` con un mensaje legible que nombra recurso,
2905
+ * contenedor e hijo: nada se oculta ni se sustituye en silencio.
2906
+ *
2907
+ * El tipo de una `uiView` RAÍZ no se chequea acá: eso ya lo hace
2908
+ * `resourceRoutes()` con su error genérico de descriptor inexistente. Este
2909
+ * validador cubre lo que ese recorrido no ve: la estructura de los hijos.
2910
+ */
2911
+ /** Profundidad máxima de anidamiento de contenedores (hijos raíz = 1). */
2912
+ export declare const MAX_DEPTH = 3;
2913
+ /** Cantidad máxima de nodos-hijo declarados en la composición de un schema. */
2914
+ declare const MAX_NODES = 50;
2915
+ /** Violación del contrato de composición, detectada antes del render. */
2916
+ export declare class SchemaCompositionError extends Error {
2917
+ constructor(message: string);
2918
+ }
2919
+ /**
2920
+ * Valida TODA la composición de un schema. Lanza `SchemaCompositionError`
2921
+ * en la primera violación; no lanzar significa «apto para renderizar».
2922
+ */
2923
+ export declare function validateComposition(schema: ResourceSchema): void;
2924
+ /** Ruta de un campo en el árbol de Signal Forms. Los casts viven acá. */
2925
+ type FieldPath<V> = SchemaPath<V, SchemaPathRules.Supported, PathKind.Child>;
2926
+ /**
2927
+ * Vocabulario compartido con el back (`VALIDATORS_` en 23-validate.js).
2928
+ *
2929
+ * El orden es el de registro de `FIELD_VALIDATORS`, que fija el orden de los
2930
+ * errores en `field.errors()`: el primero es el que pinta el campo.
2931
+ */
2932
+ type ValidatorName = 'required' | 'email' | 'url' | 'min-length' | 'max-length' | 'pattern' | 'min-words' | 'max-words' | 'min' | 'max' | 'min-items' | 'max-items' | 'date-min' | 'date-max' | 'unique';
2933
+ /** Todo lo que un validador necesita para decidir y para registrarse. */
2934
+ interface ValidatorContext {
2935
+ path: FieldPath<unknown>;
2936
+ field: FieldSchema;
2937
+ schema: ResourceSchema;
2938
+ api: ApiService;
2939
+ excludeKey: string | undefined;
2940
+ }
2941
+ interface ValidatorEntry {
2942
+ readonly name: ValidatorName;
2943
+ /** ¿Este campo declara este validador? (tipo + props del schema) */
2944
+ readonly declares: (ctx: ValidatorContext) => boolean;
2945
+ /** Ata la regla de Signal Forms a `ctx.path`. */
2946
+ readonly register: (ctx: ValidatorContext) => void;
2947
+ }
2948
+ /** Formato de URL para una ruta cualquiera (`string-list`, ítems de list). */
2949
+ declare function registerUrlRule(path: FieldPath<unknown>): void;
2950
+ /** Formato de correo para una ruta cualquiera (`string-list`, ítems de list). */
2951
+ declare function registerEmailRule(path: FieldPath<unknown>): void;
2952
+ declare const FIELD_VALIDATORS: readonly ValidatorEntry[];
2953
+ /** Nombres del registro: lo que `contract-check.mjs` compara con el back. */
2954
+ declare const VALIDATOR_NAMES: readonly ValidatorName[];
2955
+ /**
2956
+ * Resuelve una plantilla de texto reemplazando marcadores `{key}` o `{nested.key}`
2957
+ * con los valores presentes en el objeto `data` o en el contexto adicional `extraContext`.
2958
+ *
2959
+ * Si un valor es `null` o `undefined`, se reemplaza por una cadena vacía.
2960
+ * No arroja excepciones ante claves no encontradas ni ante objetos nulos.
2961
+ */
2962
+ export declare function interpolateTemplate(template: string, data?: Record<string, unknown>, extraContext?: Record<string, unknown>): string;
2963
+ interface ParsedRoute {
2964
+ path: string;
2965
+ queryParams: Record<string, string>;
2966
+ }
2967
+ /**
2968
+ * Descompone una ruta relativa o absoluta en path y queryParams para routerLink.
2969
+ */
2970
+ declare function parseNavigationUrl(url: string): ParsedRoute;
2971
+ /**
2972
+ * Descriptores de las tres acciones que hoy declara el modelo
2973
+ * (R3.10 · `doc/plan-refactor-motor.md` §3.2).
2974
+ *
2975
+ * Éste es el fichero que **cierra la triplicación de `copy-share`**: la
2976
+ * resolución de enlace y de mensaje vivía en `list-view.runAction`,
2977
+ * `detail-view.resolveUrl/resolveShareText` y
2978
+ * `post-create-modal.resolvedShareUrl/resolvedShareText`. Aquí se resuelve
2979
+ * una sola vez (`resolveShare`) y las vistas consumen ese resultado en R3.11.
2980
+ *
2981
+ * Se registra al importar, desde `action-registry.ts`, con el mismo criterio
2982
+ * que el registro de render: sin esperar a DI, porque el registro es la única
2983
+ * fuente de verdad en runtime.
2984
+ */
2985
+ /** Enlace y mensaje ya interpolados, listos para copiar o compartir. */
2986
+ interface ResolvedShare {
2987
+ url: string;
2988
+ shareText: string;
2989
+ }
2990
+ /**
2991
+ * `true` cuando la acción es de las que las vistas presentan como botón de
2992
+ * compartir (`detail-view`, `post-create-modal`).
2993
+ *
2994
+ * Vive acá y no en la vista porque es **el único sitio donde se puede nombrar
2995
+ * `'copy-share'`**: fuera de `descriptors.ts` ninguna vista decide por tipo
2996
+ * (criterio de R3.11).
2997
+ */
2998
+ export declare function isShareAction(action: ResourceAction): action is CopyShareAction;
2999
+ /**
3000
+ * `true` cuando la acción es un `trigger` (acción de dominio del back).
3001
+ *
3002
+ * Mismo criterio que `isShareAction`: el único sitio que nombra el tipo,
3003
+ * para que las vistas no decidan por string literal (R3.11). `detail-view`
3004
+ * lo usa en R8.8 para renderizar «Regenerar PIN» junto a los botones de
3005
+ * compartir.
3006
+ */
3007
+ export declare function isTriggerAction(action: ResourceAction): action is TriggerAction;
3008
+ /**
3009
+ * Resuelve `urlTemplate` y el mensaje de compartir contra `vars`
3010
+ * (config global + registro).
3011
+ *
3012
+ * **Única implementación** de esta lógica en el admin. El fallback a
3013
+ * `share_message_template` (la plantilla que declara el singleton de
3014
+ * configuración) lo aplican los tres puntos por igual: hasta ahora la fila
3015
+ * del listado no lo honraba y se quedaba sin saludo.
3016
+ */
3017
+ export declare function resolveShare(action: CopyShareAction, vars: Record<string, unknown>): ResolvedShare;
3018
+ /** Catálogo — una línea por acción, en el orden en que se registran. */
3019
+ declare const actionDescriptors: readonly ActionDescriptor[];
3020
+ /**
3021
+ * Registro único de acciones (R3.9 · `refactormotor.md`).
3022
+ *
3023
+ * Es el gemelo del registro de render (`core/registry/render-registry.ts`):
3024
+ * en lugar de «qué componente pinta un tipo de campo», responde «qué
3025
+ * comportamiento ejecuta un tipo de acción» y permite **migrar el dispatch
3026
+ * if/copiado-pegado de las vistas (R3.11) a datos + un solo `run`**.
3027
+ *
3028
+ * Como los descriptores son objetos planos de módulo (no los inyecta Angular),
3029
+ * todo lo que necesiten llegar por el `ActionContext`: los puertos (`api`,
3030
+ * `router`, `toast`) y los datos (`record`, `resource`, `vars`).
3031
+ */
3032
+ /** Superficies donde una acción puede aparecer. */
3033
+ export type ActionSurface = 'row' | 'detail' | 'header' | 'bulk' | 'post-create';
3034
+ /**
3035
+ * Lo que aporta el llamador al ejecutar una acción: el registro sobre el que
3036
+ * corre y los puertos que el descriptor usará para actuar.
3037
+ *
3038
+ * `record` es `null` en las superficies sin fila (`header`, `bulk`), para que
3039
+ * un descriptor pueda distinguir «sin registro» de «registro vacío».
3040
+ */
3041
+ export interface ActionRequest {
3042
+ record: Record<string, unknown> | null;
3043
+ /** Id del recurso (schema) dueño de la acción. */
3044
+ resource: string;
3045
+ api: ApiService;
3046
+ router: Router;
3047
+ toast: NotificationService;
3048
+ user: SessionUser | null;
3049
+ /**
3050
+ * `keyField` del schema. **R3.12 (desviación nº 3 del contrato):** con
3051
+ * `record` forma la clave que necesita la ruta lógica de `trigger`
3052
+ * (`/admin/<recurso>/<key>/actions/<name>`). Lo aporta la vista, que es
3053
+ * quien tiene el schema.
3054
+ */
3055
+ keyField: string;
3056
+ }
3057
+ /**
3058
+ * Contexto que recibe el descriptor: la petición del llamador más el contexto
3059
+ * de interpolación ya resuelto. `ActionService.run` lo arma, así que ninguna
3060
+ * vista repite `buildInterpolationContext(record)`.
3061
+ */
3062
+ export interface ActionContext extends ActionRequest {
3063
+ /** `{...configGlobal, ...record, public_base_url}` — listo para las plantillas. */
3064
+ vars: Record<string, unknown>;
3065
+ }
3066
+ /** Resultado que la vista puede leer para saber si debe recargar. */
3067
+ export interface ActionResult {
3068
+ /** `false` cuando la acción no llegó a ejecutarse; el error ya se notificó. */
3069
+ ok: boolean;
3070
+ /** `true` cuando tocó datos y la vista debe recargar su listado/detalle. */
3071
+ reload?: boolean;
3072
+ }
3073
+ export interface ActionDescriptor {
3074
+ /** Clave del dispatch: el `type` del schema (`'navigate'` no lo declara). */
3075
+ type: string;
3076
+ /** Superficies donde esta acción puede aparecer. */
3077
+ surfaces: ActionSurface[];
3078
+ run: (ctx: ActionContext, action: ResourceAction) => Promise<ActionResult>;
3079
+ /** `true` si el descriptor exige confirmación antes de ejecutar. */
3080
+ confirm?: boolean;
3081
+ /**
3082
+ * Icono lucide que representa la acción donde el espacio es reducido
3083
+ * (la fila del listado). Sin él, la vista muestra `action.label`.
3084
+ */
3085
+ icon?: LucideIcon;
3086
+ }
3087
+ /** Registra (o reemplaza) un descriptor de acción. */
3088
+ export declare function registerAction(descriptor: ActionDescriptor): void;
3089
+ export declare function getAction(type: string): ActionDescriptor | undefined;
3090
+ /** Descriptores disponibles en una superficie. */
3091
+ export declare function actionsFor(surface: ActionSurface): ActionDescriptor[];
3092
+ /**
3093
+ * Clave de dispatch de una acción. `NavigateAction.type` es **opcional**
3094
+ * (schema.model.ts), así que una acción sin `type` es una navegación.
3095
+ */
3096
+ export declare function actionType(action: ResourceAction): string;
3097
+ /** Tipos registrados. Sólo para comprobaciones y tests. */
3098
+ export declare function actionTypes(): string[];
3099
+ export { type ComponentRenderKind, type GasAdminConfig, type RenderKind, type ResolvedShare, type ValidatorName, get as getRenderDescriptor, isSupported as isRenderSupported, register as registerRenderDescriptor, unregister as unregisterRenderDescriptor };