@arquimedes.co/eureka-forms 3.0.71-test → 3.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README +33 -1
  2. package/dist/@Types/AvailabilityFormStep.d.ts +48 -0
  3. package/dist/@Types/AvailabilityFormStep.js +24 -0
  4. package/dist/@Types/BankAccountFormStep.d.ts +67 -0
  5. package/dist/@Types/BankAccountFormStep.js +14 -0
  6. package/dist/@Types/BookingFormStep.d.ts +249 -0
  7. package/dist/@Types/BookingFormStep.js +65 -0
  8. package/dist/@Types/CalendarFormStep.d.ts +209 -0
  9. package/dist/@Types/CalendarFormStep.js +55 -0
  10. package/dist/@Types/CommunicationChannelFormStep.d.ts +51 -0
  11. package/dist/@Types/CommunicationChannelFormStep.js +15 -0
  12. package/dist/@Types/ConsentFormStep.d.ts +36 -0
  13. package/dist/@Types/ConsentFormStep.js +8 -0
  14. package/dist/@Types/EntityFormFormStep.d.ts +66 -0
  15. package/dist/@Types/EntityFormFormStep.js +20 -0
  16. package/dist/@Types/EventFormStep.d.ts +52 -0
  17. package/dist/@Types/EventFormStep.js +13 -0
  18. package/dist/@Types/ExternalReferenceFormStep.d.ts +74 -0
  19. package/dist/@Types/ExternalReferenceFormStep.js +20 -0
  20. package/dist/@Types/FormStep.d.ts +7 -2
  21. package/dist/@Types/FormStep.js +2 -1
  22. package/dist/@Types/LocationFormStep.d.ts +159 -0
  23. package/dist/@Types/LocationFormStep.js +46 -0
  24. package/dist/@Types/NumericFormSteps.d.ts +108 -0
  25. package/dist/@Types/NumericFormSteps.js +20 -0
  26. package/dist/@Types/PredefinedSelectorFormStep.d.ts +51 -0
  27. package/dist/@Types/PredefinedSelectorFormStep.js +39 -0
  28. package/dist/@Types/StepRef.d.ts +135 -0
  29. package/dist/@Types/StepRef.js +49 -0
  30. package/dist/App/App.stories.d.ts +10 -0
  31. package/dist/App/App.stories.js +189 -0
  32. package/dist/App/App.submitError.test.d.ts +1 -0
  33. package/dist/App/App.submitError.test.js +53 -0
  34. package/dist/App/AppHooks.js +8 -0
  35. package/dist/App/AppHooks.test.js +8 -0
  36. package/dist/BookingManage/AppointmentText.d.ts +5 -0
  37. package/dist/BookingManage/AppointmentText.js +20 -0
  38. package/dist/BookingManage/BookingManagePage.d.ts +17 -0
  39. package/dist/BookingManage/BookingManagePage.js +177 -0
  40. package/dist/BookingManage/BookingManagePage.module.css +115 -0
  41. package/dist/BookingManage/BookingManagePage.test.d.ts +1 -0
  42. package/dist/BookingManage/BookingManagePage.test.js +149 -0
  43. package/dist/Form/ConfirmationDialog/ConfirmationDialog.d.ts +14 -5
  44. package/dist/Form/ConfirmationDialog/ConfirmationDialog.js +8 -1
  45. package/dist/Form/ConfirmationDialog/ConfirmationDialog.module.css +12 -0
  46. package/dist/Form/ConfirmationDialog/ConfirmationDialog.test.d.ts +1 -0
  47. package/dist/Form/ConfirmationDialog/ConfirmationDialog.test.js +21 -0
  48. package/dist/Form/Form.d.ts +3 -0
  49. package/dist/Form/Form.js +16 -8
  50. package/dist/Form/FormFunctions.d.ts +17 -0
  51. package/dist/Form/FormFunctions.js +22 -0
  52. package/dist/Form/FormFunctions.test.js +28 -1
  53. package/dist/Form/FormTypes/ColumnForm/ColumnForm.d.ts +1 -1
  54. package/dist/Form/FormTypes/ColumnForm/ColumnForm.js +3 -2
  55. package/dist/Form/FormTypes/StepperForm/StepperForm.d.ts +1 -1
  56. package/dist/Form/FormTypes/StepperForm/StepperForm.js +3 -2
  57. package/dist/Form/SubmitError/SubmitError.d.ts +11 -0
  58. package/dist/Form/SubmitError/SubmitError.js +9 -0
  59. package/dist/Form/SubmitError/SubmitError.module.css +25 -0
  60. package/dist/Form/SubmitError/SubmitError.test.d.ts +1 -0
  61. package/dist/Form/SubmitError/SubmitError.test.js +34 -0
  62. package/dist/Form/SubmitError/SubmitErrorReason.d.ts +13 -0
  63. package/dist/Form/SubmitError/SubmitErrorReason.js +20 -0
  64. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.d.ts +7 -0
  65. package/dist/FormSteps/AvailabilityStep/AvailabilityStep.js +14 -0
  66. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.d.ts +8 -0
  67. package/dist/FormSteps/AvailabilityStep/MaterialAvailabilityStep/MaterialAvailabilityStep.js +32 -0
  68. package/dist/FormSteps/BookingStep/BookingStep.d.ts +17 -0
  69. package/dist/FormSteps/BookingStep/BookingStep.js +14 -0
  70. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.d.ts +21 -0
  71. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +289 -0
  72. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.test.d.ts +1 -0
  73. package/dist/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.test.js +275 -0
  74. package/dist/FormSteps/CheckBoxStep/CheckBoxStep.mobileLabel.test.js +20 -12
  75. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.js +6 -13
  76. package/dist/FormSteps/CheckBoxStep/MaterialCheckBoxStep/MaterialCheckBoxStep.module.css +2 -13
  77. package/dist/FormSteps/ClassifierSelectorStep/ClassifierSelectorStep.lateClassifiers.test.d.ts +1 -0
  78. package/dist/FormSteps/ClassifierSelectorStep/ClassifierSelectorStep.lateClassifiers.test.js +70 -0
  79. package/dist/FormSteps/ClassifierSelectorStep/MaterialClassifierSelectorStep/MaterialClassifierSelectorStep.js +3 -1
  80. package/dist/FormSteps/EntityValueStep/MaterialEntityValuePickerStep/MaterialEntityValuePickerStep.js +26 -26
  81. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.d.ts +12 -0
  82. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.js +35 -0
  83. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.test.d.ts +1 -0
  84. package/dist/FormSteps/EntityValueStep/entityValueDependencyValue.test.js +31 -0
  85. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.d.ts +27 -0
  86. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.js +64 -0
  87. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.test.d.ts +1 -0
  88. package/dist/FormSteps/EntityValueStep/entityValuePickerWatchedSteps.test.js +95 -0
  89. package/dist/FormSteps/LocationStep/LocationStep.d.ts +10 -0
  90. package/dist/FormSteps/LocationStep/LocationStep.js +14 -0
  91. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.d.ts +3 -0
  92. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.js +54 -0
  93. package/dist/FormSteps/LocationStep/MaterialLocationStep/MaterialLocationStep.module.css +18 -0
  94. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.d.ts +3 -0
  95. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.js +170 -0
  96. package/dist/FormSteps/NumberStep/MaterialNumberStep/MaterialNumberStep.module.css +96 -0
  97. package/dist/FormSteps/NumberStep/NumberStep.d.ts +10 -0
  98. package/dist/FormSteps/NumberStep/NumberStep.js +14 -0
  99. package/dist/FormSteps/Step.js +16 -0
  100. package/dist/FormSteps/StepFunctions.js +14 -0
  101. package/dist/FormSteps/StepFunctions.test.js +54 -0
  102. package/dist/FormSteps/StepHooks.d.ts +21 -0
  103. package/dist/FormSteps/StepHooks.js +55 -8
  104. package/dist/FormSteps/StepHooks.test.d.ts +1 -0
  105. package/dist/FormSteps/StepHooks.test.js +92 -0
  106. package/dist/FormSteps/isStepHiddenByCondition.test.d.ts +1 -0
  107. package/dist/FormSteps/isStepHiddenByCondition.test.js +42 -0
  108. package/dist/Icons/AddIcon.d.ts +3 -0
  109. package/dist/Icons/AddIcon.js +7 -0
  110. package/dist/Icons/EditIcon.d.ts +3 -0
  111. package/dist/Icons/EditIcon.js +7 -0
  112. package/dist/Icons/RemoveIcon.d.ts +3 -0
  113. package/dist/Icons/RemoveIcon.js +7 -0
  114. package/dist/Icons/ViewDayIcon.d.ts +3 -0
  115. package/dist/Icons/ViewDayIcon.js +7 -0
  116. package/dist/Icons/ViewWeekIcon.d.ts +3 -0
  117. package/dist/Icons/ViewWeekIcon.js +7 -0
  118. package/dist/Services/BookingService.d.ts +226 -0
  119. package/dist/Services/BookingService.js +264 -0
  120. package/dist/Services/BookingService.test.d.ts +1 -0
  121. package/dist/Services/BookingService.test.js +178 -0
  122. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.d.ts +19 -0
  123. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.js +80 -0
  124. package/dist/Shared/ErkAddressAutocomplete/ErkAddressAutocomplete.module.css +88 -0
  125. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.d.ts +29 -0
  126. package/dist/Shared/ErkAddressAutocomplete/googlePlaces.js +139 -0
  127. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.d.ts +25 -0
  128. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.js +112 -0
  129. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.module.css +88 -0
  130. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.d.ts +1 -0
  131. package/dist/Shared/ErkAvailabilityPicker/ErkAvailabilityPicker.test.js +73 -0
  132. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +105 -0
  133. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +399 -0
  134. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +646 -0
  135. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +36 -0
  136. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +177 -0
  137. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +1 -0
  138. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +47 -0
  139. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +1 -0
  140. package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +440 -0
  141. package/dist/Shared/ErkDatePicker/ErkDatePicker.js +1 -3
  142. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.d.ts +2 -3
  143. package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +1 -2
  144. package/dist/Shared/ErkIconButton/ErkIconButton.d.ts +17 -0
  145. package/dist/Shared/ErkIconButton/ErkIconButton.js +11 -0
  146. package/dist/Shared/ErkIconButton/ErkIconButton.module.css +41 -0
  147. package/dist/Shared/ErkLocationField/ErkLocationField.d.ts +24 -0
  148. package/dist/Shared/ErkLocationField/ErkLocationField.js +128 -0
  149. package/dist/Shared/ErkLocationField/ErkLocationField.module.css +164 -0
  150. package/dist/Shared/ErkLocationField/ErkLocationField.test.d.ts +1 -0
  151. package/dist/Shared/ErkLocationField/ErkLocationField.test.js +82 -0
  152. package/dist/Shared/ErkLocationField/addressFields.d.ts +12 -0
  153. package/dist/Shared/ErkLocationField/addressFields.js +34 -0
  154. package/dist/Shared/ErkLocationField/locationRestrictions.d.ts +6 -0
  155. package/dist/Shared/ErkLocationField/locationRestrictions.js +51 -0
  156. package/dist/Shared/ErkLocationInput/ErkLocationInput.d.ts +15 -0
  157. package/dist/Shared/ErkLocationInput/ErkLocationInput.js +502 -0
  158. package/dist/Shared/ErkLocationInput/ErkLocationInput.module.css +128 -0
  159. package/dist/Shared/ErkNumberField/ErkNumberField.d.ts +42 -0
  160. package/dist/Shared/ErkNumberField/ErkNumberField.js +169 -0
  161. package/dist/Utils/AxiosWidget.js +9 -1
  162. package/dist/constants/FormStepTypes.d.ts +5 -1
  163. package/dist/constants/FormStepTypes.js +4 -0
  164. package/dist/index.lib.d.ts +4 -2
  165. package/dist/index.lib.js +3 -1
  166. package/dist/main.js +4 -1
  167. package/package.json +2 -1
@@ -0,0 +1,440 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { useState } from 'react';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import '@testing-library/jest-dom';
7
+ import { addDays, format } from 'date-fns';
8
+ import { es } from 'date-fns/locale';
9
+ import ErkBookingPicker from './ErkBookingPicker';
10
+ import MaterialProviders from '../../Utils/MaterialProviders';
11
+ import InternalFormStyle from '../../constants/InternalFormStyle';
12
+ import { PersonSelectionMode } from '../../@Types/BookingFormStep';
13
+ const SLOTS = [
14
+ { id: 's-1', startTime: '09:00', endTime: '09:50', available: true },
15
+ { id: 's-3', startTime: '11:00', endTime: '11:50', available: true },
16
+ { id: 's-4', startTime: '14:00', endTime: '14:50', available: true },
17
+ ];
18
+ const TEMPLATES = [
19
+ { id: 't-1', name: 'Valoración', description: 'Primera visita, sin costo.', durationMinutes: 30 },
20
+ { id: 't-2', name: 'Asesoría de crédito', durationMinutes: 45 },
21
+ ];
22
+ const PERSONS = [
23
+ { id: 'p-1', name: 'María González', role: 'Asesora' },
24
+ { id: 'p-2', name: 'Carlos Ruiz', role: 'Técnico' },
25
+ ];
26
+ /** The accessible name of a day button: its full date. */
27
+ function dayName(date) {
28
+ return format(date, "EEEE d 'de' MMMM 'de' yyyy", { locale: es });
29
+ }
30
+ function clickableToday() {
31
+ return screen.getByRole('button', { name: dayName(new Date()) });
32
+ }
33
+ /** A slot button by its start time; the first one of the day also reads "Primera disponible". */
34
+ function hour(time) {
35
+ return screen.findByRole('button', { name: new RegExp(`^${time}`) });
36
+ }
37
+ function renderPicker(props) {
38
+ render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: null, onChange: () => { }, label: "Agendar cita", durationMinutes: 50, personSelectionMode: PersonSelectionMode.NONE, ...props }) }));
39
+ }
40
+ /** Opens the wizard and walks to the confirm step with today's 09:00. */
41
+ async function walkToConfirm(user) {
42
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
43
+ await user.click(clickableToday());
44
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
45
+ await user.click(await hour('09:00'));
46
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
47
+ await screen.findByRole('heading', { name: 'Revisa y reserva tu horario' });
48
+ }
49
+ describe('ErkBookingPicker (wizard)', () => {
50
+ it('drives the full flow: person → day → hour → reserve, asking for name and email', async () => {
51
+ const user = userEvent.setup();
52
+ const onChange = vi.fn();
53
+ const fetchSlots = vi.fn(() => Promise.resolve(SLOTS));
54
+ renderPicker({
55
+ onChange,
56
+ personSelectionMode: PersonSelectionMode.OPTIONAL,
57
+ persons: PERSONS,
58
+ askEmail: true,
59
+ fetchSlots,
60
+ });
61
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
62
+ expect(await screen.findByRole('heading', { name: '¿Con quién quieres agendar?' })).toBeInTheDocument();
63
+ const next = screen.getByRole('button', { name: 'Siguiente' });
64
+ expect(next).toBeDisabled();
65
+ await user.click(screen.getByText('María González'));
66
+ expect(next).toBeEnabled();
67
+ await user.click(next);
68
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
69
+ expect(screen.getByRole('button', { name: 'Siguiente' })).toBeDisabled();
70
+ await user.click(clickableToday());
71
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
72
+ expect(await screen.findByRole('heading', { name: 'Elige una hora' })).toBeInTheDocument();
73
+ expect(fetchSlots).toHaveBeenCalledWith({ personId: 'p-1', date: format(new Date(), 'yyyy-MM-dd') });
74
+ // Grouped by part of the day, with the first one flagged; nothing is shown as taken.
75
+ expect(screen.getByRole('heading', { name: 'Mañana' })).toBeInTheDocument();
76
+ expect(screen.getByRole('heading', { name: 'Tarde' })).toBeInTheDocument();
77
+ expect(await hour('09:00')).toHaveTextContent('Primera disponible');
78
+ expect(screen.queryByText(/Tachados/)).not.toBeInTheDocument();
79
+ await user.click(await hour('09:00'));
80
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
81
+ expect(await screen.findByRole('heading', { name: 'Revisa y reserva tu horario' })).toBeInTheDocument();
82
+ expect(screen.getByText(/La cita se confirma cuando envíes el formulario/)).toBeInTheDocument();
83
+ const reserve = screen.getByRole('button', { name: 'Reservar' });
84
+ await user.type(screen.getByLabelText(/Nombre/), 'Juan Pérez');
85
+ expect(reserve).toBeDisabled();
86
+ await user.type(screen.getByLabelText(/Correo/), 'juan@');
87
+ expect(reserve).toBeDisabled();
88
+ await user.type(screen.getByLabelText(/Correo/), 'correo.com');
89
+ expect(reserve).toBeEnabled();
90
+ await user.click(reserve);
91
+ expect(onChange).toHaveBeenCalledWith({
92
+ type: 'BOOKING_VALUE',
93
+ slotId: 's-1',
94
+ date: format(new Date(), 'yyyy-MM-dd'),
95
+ startTime: '09:00',
96
+ endTime: '09:50',
97
+ personId: 'p-1',
98
+ personName: 'María González',
99
+ contactName: 'Juan Pérez',
100
+ contactEmail: 'juan@correo.com',
101
+ status: 'PENDING',
102
+ });
103
+ });
104
+ it('explains that without an email there is no confirmation', async () => {
105
+ const user = userEvent.setup();
106
+ renderPicker({ askEmail: true, fetchSlots: () => Promise.resolve(SLOTS) });
107
+ await walkToConfirm(user);
108
+ expect(screen.getByText('Sin correo no podremos enviarte la confirmación.')).toBeInTheDocument();
109
+ });
110
+ it('reuses the contact the form already has and asks for neither field', async () => {
111
+ const user = userEvent.setup();
112
+ const holdSlot = vi.fn(() => Promise.resolve({ appointment: 'a', hold: 'h', lockedUntil: 'x' }));
113
+ renderPicker({
114
+ knownContact: { name: 'Ana Gómez', email: 'ana@correo.com' },
115
+ fetchSlots: () => Promise.resolve(SLOTS),
116
+ holdSlot,
117
+ });
118
+ await walkToConfirm(user);
119
+ expect(screen.queryByLabelText(/Nombre/)).not.toBeInTheDocument();
120
+ expect(screen.queryByLabelText(/Correo/)).not.toBeInTheDocument();
121
+ await user.click(screen.getByRole('button', { name: 'Reservar' }));
122
+ expect(holdSlot).toHaveBeenCalledWith({
123
+ slot: SLOTS[0],
124
+ personId: undefined,
125
+ contact: { name: 'Ana Gómez', email: 'ana@correo.com' },
126
+ });
127
+ });
128
+ it('always asks for the name, even when the email comes from another step', async () => {
129
+ const user = userEvent.setup();
130
+ renderPicker({ knownContact: { email: 'ana@correo.com' }, fetchSlots: () => Promise.resolve(SLOTS) });
131
+ await walkToConfirm(user);
132
+ const reserve = screen.getByRole('button', { name: 'Reservar' });
133
+ expect(reserve).toBeDisabled();
134
+ expect(screen.queryByLabelText(/Correo/)).not.toBeInTheDocument();
135
+ await user.type(screen.getByLabelText(/Nombre/), 'Juan');
136
+ expect(reserve).toBeEnabled();
137
+ });
138
+ it('skips the person step when personSelectionMode is NONE', async () => {
139
+ const user = userEvent.setup();
140
+ renderPicker({ durationMinutes: 30, fetchSlots: () => Promise.resolve(SLOTS) });
141
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
142
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
143
+ expect(screen.queryByText('¿Con quién quieres agendar?')).not.toBeInTheDocument();
144
+ expect(screen.queryByText('Persona')).not.toBeInTheDocument();
145
+ });
146
+ it('allows navigating back through the breadcrumb without losing the selection', async () => {
147
+ const user = userEvent.setup();
148
+ renderPicker({
149
+ personSelectionMode: PersonSelectionMode.REQUIRED,
150
+ persons: PERSONS,
151
+ fetchSlots: () => Promise.resolve(SLOTS),
152
+ });
153
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
154
+ await user.click(await screen.findByText('Carlos Ruiz'));
155
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
156
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
157
+ await user.click(screen.getByRole('button', { name: /Persona/ }));
158
+ expect(await screen.findByRole('heading', { name: '¿Con quién quieres agendar?' })).toBeInTheDocument();
159
+ expect(screen.getByRole('button', { name: 'Siguiente' })).toBeEnabled();
160
+ });
161
+ it('offers only the days the host names, and holds the slot before the value is set', async () => {
162
+ const user = userEvent.setup();
163
+ const onChange = vi.fn();
164
+ const today = format(new Date(), 'yyyy-MM-dd');
165
+ const fetchDays = vi.fn(() => Promise.resolve([today]));
166
+ const holdSlot = vi.fn(() => Promise.resolve({
167
+ appointment: 'appt-1',
168
+ hold: 'secret-1',
169
+ lockedUntil: '2026-09-22T14:15:00.000Z',
170
+ startTime: '09:15',
171
+ endTime: '10:05',
172
+ personName: 'Bruno Pérez',
173
+ }));
174
+ renderPicker({
175
+ onChange,
176
+ knownContact: { name: 'Ana', email: 'ana@correo.com' },
177
+ fetchDays,
178
+ fetchSlots: () => Promise.resolve(SLOTS),
179
+ holdSlot,
180
+ });
181
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
182
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
183
+ expect(fetchDays).toHaveBeenCalledWith({ personId: undefined });
184
+ const todayButton = clickableToday();
185
+ const enabledDays = screen
186
+ .getAllByRole('button')
187
+ .filter((b) => /^\d{1,2}$/.test(b.textContent ?? '') && !b.disabled);
188
+ expect(enabledDays).toEqual([todayButton]);
189
+ await user.click(todayButton);
190
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
191
+ await user.click(await hour('09:00'));
192
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
193
+ await user.click(await screen.findByRole('button', { name: 'Reservar' }));
194
+ // What the hold says wins over the picker's own selection: real hours and the assigned person.
195
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({
196
+ slotId: 's-1',
197
+ date: today,
198
+ startTime: '09:15',
199
+ endTime: '10:05',
200
+ personName: 'Bruno Pérez',
201
+ appointment: 'appt-1',
202
+ hold: 'secret-1',
203
+ lockedUntil: '2026-09-22T14:15:00.000Z',
204
+ status: 'PENDING',
205
+ }));
206
+ });
207
+ it('goes back to the hours with the reason when the hold is refused', async () => {
208
+ const user = userEvent.setup();
209
+ const onChange = vi.fn();
210
+ const fetchSlots = vi.fn(() => Promise.resolve(SLOTS));
211
+ const holdSlot = vi.fn(() => Promise.reject(new Error('Ese horario ya fue reservado por alguien más')));
212
+ renderPicker({ onChange, knownContact: { name: 'Ana' }, fetchSlots, holdSlot });
213
+ await walkToConfirm(user);
214
+ await user.click(await screen.findByRole('button', { name: 'Reservar' }));
215
+ expect(await screen.findByRole('alert')).toHaveTextContent('Ese horario ya fue reservado por alguien más');
216
+ expect(screen.getByRole('heading', { name: 'Elige una hora' })).toBeInTheDocument();
217
+ expect(fetchSlots).toHaveBeenCalledTimes(2);
218
+ expect(onChange).not.toHaveBeenCalled();
219
+ });
220
+ it('keeps the hours of the last day picked when an earlier answer arrives late', async () => {
221
+ const user = userEvent.setup();
222
+ const tomorrow = addDays(new Date(), 1);
223
+ const resolvers = {};
224
+ const fetchSlots = vi.fn(({ date }) => new Promise((resolve) => {
225
+ resolvers[date] = resolve;
226
+ }));
227
+ const todayKey = format(new Date(), 'yyyy-MM-dd');
228
+ const tomorrowKey = format(tomorrow, 'yyyy-MM-dd');
229
+ renderPicker({
230
+ fetchDays: () => Promise.resolve([todayKey, tomorrowKey]),
231
+ fetchSlots,
232
+ });
233
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
234
+ await user.click(await screen.findByRole('button', { name: dayName(new Date()) }));
235
+ if (format(tomorrow, 'M') !== format(new Date(), 'M')) {
236
+ await user.click(screen.getByRole('button', { name: 'Mes siguiente' }));
237
+ }
238
+ await user.click(screen.getByRole('button', { name: dayName(tomorrow) }));
239
+ await waitFor(() => expect(resolvers[tomorrowKey]).toBeDefined());
240
+ resolvers[tomorrowKey]([{ id: 't-1', startTime: '15:00', endTime: '15:50', available: true }]);
241
+ resolvers[todayKey]([{ id: 'd-1', startTime: '08:00', endTime: '08:50', available: true }]);
242
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
243
+ expect(await hour('15:00')).toBeInTheDocument();
244
+ expect(screen.queryByRole('button', { name: /^08:00/ })).not.toBeInTheDocument();
245
+ });
246
+ it('tells a failed load of hours apart from an empty day, and retries', async () => {
247
+ const user = userEvent.setup();
248
+ const fetchSlots = vi
249
+ .fn()
250
+ .mockRejectedValueOnce(new Error('Hay muchas consultas en este momento. Intenta de nuevo en unos segundos.'))
251
+ .mockResolvedValueOnce(SLOTS);
252
+ renderPicker({ fetchSlots });
253
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
254
+ await user.click(clickableToday());
255
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
256
+ expect(await screen.findByRole('alert')).toHaveTextContent('Hay muchas consultas en este momento');
257
+ expect(screen.queryByText('No hay horarios disponibles ese día.')).not.toBeInTheDocument();
258
+ await user.click(screen.getByRole('button', { name: 'Reintentar' }));
259
+ expect(await hour('09:00')).toBeInTheDocument();
260
+ });
261
+ it('still says a day is empty when it really has no hours', async () => {
262
+ const user = userEvent.setup();
263
+ renderPicker({ fetchSlots: () => Promise.resolve([]) });
264
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
265
+ await user.click(clickableToday());
266
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
267
+ expect(await screen.findByText('No hay horarios disponibles ese día.')).toBeInTheDocument();
268
+ });
269
+ it('tells a failed load of days apart from a person without days, and retries', async () => {
270
+ const user = userEvent.setup();
271
+ const today = format(new Date(), 'yyyy-MM-dd');
272
+ const fetchDays = vi
273
+ .fn()
274
+ .mockRejectedValueOnce(new Error('Esa persona ya no está disponible'))
275
+ .mockResolvedValueOnce([today]);
276
+ renderPicker({ fetchDays, fetchSlots: () => Promise.resolve(SLOTS) });
277
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
278
+ expect(await screen.findByRole('alert')).toHaveTextContent('Esa persona ya no está disponible');
279
+ expect(screen.queryByText('No hay días con horarios disponibles.')).not.toBeInTheDocument();
280
+ expect(clickableToday()).toBeDisabled();
281
+ await user.click(screen.getByRole('button', { name: 'Reintentar' }));
282
+ await waitFor(() => expect(clickableToday()).toBeEnabled());
283
+ });
284
+ it('names the organization clock and the requester clock when they differ', async () => {
285
+ const user = userEvent.setup();
286
+ const local = Intl.DateTimeFormat().resolvedOptions().timeZone;
287
+ const zone = local === 'Asia/Tokyo' ? 'America/Bogota' : 'Asia/Tokyo';
288
+ const start = new Date();
289
+ start.setUTCHours(0, 0, 0, 0);
290
+ const slot = {
291
+ id: 'z-1',
292
+ startTime: new Intl.DateTimeFormat('en-GB', {
293
+ hour: '2-digit',
294
+ minute: '2-digit',
295
+ hourCycle: 'h23',
296
+ timeZone: zone,
297
+ }).format(start),
298
+ endTime: '23:59',
299
+ available: true,
300
+ start: start.toISOString(),
301
+ };
302
+ const localTime = new Intl.DateTimeFormat('en-GB', {
303
+ hour: '2-digit',
304
+ minute: '2-digit',
305
+ hourCycle: 'h23',
306
+ timeZone: local,
307
+ }).format(start);
308
+ renderPicker({ timeZone: zone, fetchSlots: () => Promise.resolve([slot]) });
309
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
310
+ await user.click(clickableToday());
311
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
312
+ expect(await screen.findByText(/^Horas en hora de Tokyo|^Horas en hora de Bogotá/)).toBeInTheDocument();
313
+ await user.click(await hour(slot.startTime));
314
+ expect(screen.getAllByText(new RegExp(`\\(${localTime} en tu hora\\)`)).length).toBeGreaterThan(0);
315
+ });
316
+ it('does not show the buffer, an internal detail', async () => {
317
+ const user = userEvent.setup();
318
+ renderPicker({ fetchSlots: () => Promise.resolve(SLOTS) });
319
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
320
+ expect(screen.queryByText(/buffer/)).not.toBeInTheDocument();
321
+ });
322
+ it('sends the requester back to the hours when a held slot lapses', async () => {
323
+ const user = userEvent.setup();
324
+ const fetchSlots = vi.fn(() => Promise.resolve(SLOTS));
325
+ function Host({ lapsed }) {
326
+ const [value, setValue] = useState(null);
327
+ return (_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkBookingPicker, { value: lapsed ? null : value, onChange: setValue, label: "Agendar cita", durationMinutes: 50, knownContact: { name: 'Ana' }, lapsed: lapsed, fetchSlots: fetchSlots, holdSlot: () => Promise.resolve({ appointment: 'a', hold: 'h', lockedUntil: 'x' }) }) }));
328
+ }
329
+ const { rerender } = render(_jsx(Host, {}));
330
+ await walkToConfirm(user);
331
+ await user.click(screen.getByRole('button', { name: 'Reservar' }));
332
+ expect(await screen.findByRole('button', { name: 'Cambiar' })).toBeInTheDocument();
333
+ rerender(_jsx(Host, { lapsed: "Tu reserva venci\u00F3; elige la hora de nuevo." }));
334
+ expect(await screen.findByRole('alert')).toHaveTextContent('Tu reserva venció; elige la hora de nuevo.');
335
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
336
+ expect(await screen.findByRole('heading', { name: 'Elige una hora' })).toBeInTheDocument();
337
+ expect(screen.getByRole('alert')).toHaveTextContent('Tu reserva venció; elige la hora de nuevo.');
338
+ // Fresh hours: the lapsed slot may be gone.
339
+ await waitFor(() => expect(fetchSlots).toHaveBeenCalledTimes(2));
340
+ });
341
+ it('renders the compact summary with the assigned person and the pending note', () => {
342
+ renderPicker({
343
+ value: {
344
+ type: 'BOOKING_VALUE',
345
+ slotId: 's-3',
346
+ date: '2026-07-02',
347
+ startTime: '11:00',
348
+ endTime: '11:50',
349
+ personName: 'María González',
350
+ status: 'PENDING',
351
+ lockedUntil: '2026-07-01T15:30:00.000Z',
352
+ },
353
+ timeZone: 'America/Bogota',
354
+ });
355
+ expect(screen.getByText(/11:00–11:50 · con María González/)).toBeInTheDocument();
356
+ expect(screen.getByText(/Reservado hasta las 10:30\. Envía el formulario para confirmar la cita\./)).toBeInTheDocument();
357
+ expect(screen.getByRole('button', { name: 'Cambiar' })).toBeInTheDocument();
358
+ });
359
+ it('asks for the event template first and narrows days, hours and the hold to it', async () => {
360
+ const user = userEvent.setup();
361
+ const onChange = vi.fn();
362
+ const fetchDays = vi.fn(() => Promise.resolve([format(new Date(), 'yyyy-MM-dd')]));
363
+ const fetchSlots = vi.fn(() => Promise.resolve(SLOTS));
364
+ const holdSlot = vi.fn(() => Promise.resolve({ eventTemplate: { name: 'Asesoría de crédito', durationMinutes: 45 } }));
365
+ renderPicker({
366
+ onChange,
367
+ eventTemplates: TEMPLATES,
368
+ knownContact: { name: 'Ana', email: 'ana@correo.com' },
369
+ fetchDays,
370
+ fetchSlots,
371
+ holdSlot,
372
+ });
373
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
374
+ expect(await screen.findByRole('heading', { name: '¿Qué necesitas?' })).toBeInTheDocument();
375
+ expect(screen.getByRole('button', { name: /^1Tipo/ })).toBeInTheDocument();
376
+ // Nothing is asked of the edge before a type is picked: the days depend on it.
377
+ expect(fetchDays).not.toHaveBeenCalled();
378
+ expect(screen.getByRole('button', { name: 'Siguiente' })).toBeDisabled();
379
+ expect(screen.getByText('Primera visita, sin costo.')).toBeInTheDocument();
380
+ await user.click(screen.getByRole('button', { name: /Asesoría de crédito/ }));
381
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
382
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
383
+ expect(fetchDays).toHaveBeenLastCalledWith({ personId: undefined, eventTemplate: 't-2' });
384
+ expect(screen.getByText('45 min')).toBeInTheDocument();
385
+ await user.click(clickableToday());
386
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
387
+ expect(await screen.findByText(/citas de 45 min/)).toBeInTheDocument();
388
+ expect(fetchSlots).toHaveBeenLastCalledWith({
389
+ personId: undefined,
390
+ date: format(new Date(), 'yyyy-MM-dd'),
391
+ eventTemplate: 't-2',
392
+ });
393
+ await user.click(await hour('09:00'));
394
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
395
+ await user.click(await screen.findByRole('button', { name: 'Reservar' }));
396
+ expect(holdSlot).toHaveBeenCalledWith(expect.objectContaining({ eventTemplate: 't-2' }));
397
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ eventTemplate: { name: 'Asesoría de crédito', durationMinutes: 45 } }));
398
+ });
399
+ it('drops the day and hour picked for a type when the requester goes back and picks another', async () => {
400
+ const user = userEvent.setup();
401
+ renderPicker({ eventTemplates: TEMPLATES, fetchSlots: () => Promise.resolve(SLOTS) });
402
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
403
+ await user.click(await screen.findByRole('button', { name: /Valoración/ }));
404
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
405
+ await user.click(clickableToday());
406
+ await user.click(screen.getByRole('button', { name: 'Atrás' }));
407
+ await user.click(screen.getByRole('button', { name: /Asesoría de crédito/ }));
408
+ await user.click(screen.getByRole('button', { name: 'Siguiente' }));
409
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
410
+ expect(screen.getByRole('button', { name: 'Siguiente' })).toBeDisabled();
411
+ });
412
+ it('offers no type step with a single event template', async () => {
413
+ const user = userEvent.setup();
414
+ renderPicker({ eventTemplates: [TEMPLATES[0]], fetchSlots: () => Promise.resolve(SLOTS) });
415
+ await user.click(screen.getByRole('button', { name: 'Agendar cita' }));
416
+ expect(await screen.findByRole('heading', { name: 'Elige un día' })).toBeInTheDocument();
417
+ expect(screen.queryByRole('heading', { name: '¿Qué necesitas?' })).not.toBeInTheDocument();
418
+ });
419
+ it('shows the picked event template and its duration in the compact summary', () => {
420
+ renderPicker({
421
+ value: {
422
+ type: 'BOOKING_VALUE',
423
+ date: '2026-07-02',
424
+ startTime: '11:00',
425
+ endTime: '11:45',
426
+ status: 'PENDING',
427
+ eventTemplate: { name: 'Asesoría de crédito', durationMinutes: 45 },
428
+ },
429
+ });
430
+ expect(screen.getByText('Asesoría de crédito · 45 min')).toBeInTheDocument();
431
+ });
432
+ it('offers to retry when the offer did not load', async () => {
433
+ const user = userEvent.setup();
434
+ const onRetryLoad = vi.fn();
435
+ renderPicker({ disabled: true, loadError: 'No pudimos cargar los horarios.', onRetryLoad });
436
+ expect(screen.getByRole('alert')).toHaveTextContent('No pudimos cargar los horarios.');
437
+ await user.click(screen.getByRole('button', { name: 'Reintentar' }));
438
+ expect(onRetryLoad).toHaveBeenCalledTimes(1);
439
+ });
440
+ });
@@ -2,11 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useMemo } from 'react';
3
3
  import { isValid } from 'date-fns';
4
4
  import { InputAdornment, styled, Dialog as MuiDialog } from '@mui/material';
5
- import { MobileDatePicker as MuiDatePicker, MobileDateTimePicker as MuiDateTimePicker, DatePickerToolbar as MuiDatePickerToolbar, DateTimePickerToolbar as MuiDateTimePickerToolbar, usePickerContext, usePickerAdapter, } from '@mui/x-date-pickers';
6
- import { PickerDay as MuiPickerDay } from '@mui/x-date-pickers/PickerDay';
5
+ import { MobileDatePicker as MuiDatePicker, MobileDateTimePicker as MuiDateTimePicker, DatePickerToolbar as MuiDatePickerToolbar, DateTimePickerToolbar as MuiDateTimePickerToolbar, usePickerContext, usePickerAdapter, PickerDay as MuiPickerDay, renderTimeViewClock, } from '@mui/x-date-pickers';
7
6
  import { StyledTextField } from '../ErkTextField/ErkTextField';
8
7
  import CalendarIcon from '../../Icons/CalendarIcon';
9
- import { renderTimeViewClock } from '@mui/x-date-pickers/timeViewRenderers';
10
8
  const StyledDateTimePicker = styled(MuiDateTimePicker, {
11
9
  shouldForwardProp: (propName) => propName !== 'size',
12
10
  })(({ disabled, readOnly, size }) => ({
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
- import { MobileDateRangePickerProps as MuiDateRangePickerProps } from '@mui/x-date-pickers-pro/MobileDateRangePicker';
3
- import { DateRange } from '@mui/x-date-pickers-pro';
4
- import { PickersShortcutsItem } from '@mui/x-date-pickers/PickersShortcuts';
2
+ import { PickersShortcutsItem } from '@mui/x-date-pickers';
3
+ import { MobileDateRangePickerProps as MuiDateRangePickerProps, DateRange } from '@mui/x-date-pickers-pro';
5
4
  import { ErkTextFieldProps } from '../ErkTextField/ErkTextField';
6
5
  interface ErkDateRangePickerProps extends Omit<MuiDateRangePickerProps, 'ref' | 'onChange' | 'value' | 'slotProps'>, Pick<ErkTextFieldProps, 'error' | 'required' | 'helperText' | 'size'> {
7
6
  onChange?: (dateRange: DateRange<Date>) => void;
@@ -2,8 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useMemo } from 'react';
3
3
  import { InputAdornment, styled, Dialog as MuiDialog } from '@mui/material';
4
4
  import { DatePickerToolbar as MuiDatePickerToolbar, usePickerContext, usePickerAdapter, } from '@mui/x-date-pickers';
5
- import { MobileDateRangePicker as MuiDateRangePicker, } from '@mui/x-date-pickers-pro/MobileDateRangePicker';
6
- import { DateRangePickerDay as MuiDateRangePickerDay } from '@mui/x-date-pickers-pro';
5
+ import { MobileDateRangePicker as MuiDateRangePicker, DateRangePickerDay as MuiDateRangePickerDay, } from '@mui/x-date-pickers-pro';
7
6
  import { isValid } from 'date-fns';
8
7
  import { StyledTextField } from '../ErkTextField/ErkTextField';
9
8
  import CalendarIcon from '../../Icons/CalendarIcon';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export interface ErkIconButtonProps {
3
+ /** Tooltip y nombre accesible del botón. */
4
+ title: string;
5
+ onClick?: () => void;
6
+ disabled?: boolean;
7
+ /** Muestra un spinner en lugar del icono (y deshabilita el botón). */
8
+ loading?: boolean;
9
+ children: React.ReactNode;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
14
+ * que los inputs del form (theming vía variables --eureka-*), con tooltip.
15
+ */
16
+ declare function ErkIconButton({ title, onClick, disabled, loading, children, className, }: ErkIconButtonProps): JSX.Element;
17
+ export default ErkIconButton;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CircularProgress, Tooltip } from '@mui/material';
3
+ import styles from './ErkIconButton.module.css';
4
+ /**
5
+ * Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
6
+ * que los inputs del form (theming vía variables --eureka-*), con tooltip.
7
+ */
8
+ function ErkIconButton({ title, onClick, disabled = false, loading = false, children, className, }) {
9
+ return (_jsx(Tooltip, { title: title, children: _jsx("span", { className: styles.wrap, children: _jsx("button", { type: "button", "aria-label": title, disabled: disabled || loading, className: className ? `${styles.button} ${className}` : styles.button, onClick: onClick, children: loading ? _jsx(CircularProgress, { size: 16 }) : children }) }) }));
10
+ }
11
+ export default ErkIconButton;
@@ -0,0 +1,41 @@
1
+ .wrap {
2
+ display: inline-flex;
3
+ flex-shrink: 0;
4
+ }
5
+
6
+ /* Mismo alto, radio y borde que los inputs del form (ErkTextField small) */
7
+ .button {
8
+ height: 31px;
9
+ width: 31px;
10
+ box-sizing: border-box;
11
+ border: 1px solid var(--eureka-outline, #b8b8b8);
12
+ border-radius: 10px;
13
+ background-color: var(--eureka-bg, #fff);
14
+ color: var(--eureka-text, #293241);
15
+ display: grid;
16
+ place-items: center;
17
+ padding: 0;
18
+ cursor: pointer;
19
+ transition: all 0.2s ease-in-out;
20
+ }
21
+
22
+ .button svg {
23
+ width: 18px;
24
+ height: 18px;
25
+ }
26
+
27
+ .button:hover:not(:disabled) {
28
+ background-color: rgba(0, 0, 0, 0.04);
29
+ border-color: var(--eureka-primary, #3d5a7f);
30
+ color: var(--eureka-primary, #3d5a7f);
31
+ }
32
+
33
+ .button:focus-visible {
34
+ outline: 2px solid var(--eureka-primary, #3d5a7f);
35
+ outline-offset: 1px;
36
+ }
37
+
38
+ .button:disabled {
39
+ opacity: 0.5;
40
+ cursor: default;
41
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { LocationInputMode, LocationStep, LocationStepValue } from '../../@Types/LocationFormStep';
3
+ export interface ErkLocationFieldProps {
4
+ value?: LocationStepValue | null;
5
+ onChange: (value: LocationStepValue | null) => void;
6
+ allowedModes: LocationInputMode[];
7
+ defaultMode?: LocationInputMode;
8
+ addressFields?: LocationStep['addressFields'];
9
+ /** ISO-2 para acotar el buscador (ej. ['co']). */
10
+ regionCodes?: string[];
11
+ error?: boolean;
12
+ helperText?: string | null;
13
+ required?: boolean;
14
+ readOnly?: boolean;
15
+ inputRef?: React.Ref<HTMLInputElement>;
16
+ }
17
+ /**
18
+ * Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
19
+ * Google Places) y botones de acción — GPS y edición manual (en modal). Al
20
+ * seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
21
+ * La config por campo (fijo / omitir) se aplica a todos los modos.
22
+ */
23
+ declare function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
24
+ export default ErkLocationField;