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