@livex/contracts 0.1.0 → 0.1.1

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/dist/index.js CHANGED
@@ -10,150 +10,4525 @@ import {
10
10
  var livex_v1_default = {
11
11
  openapi: "3.1.0",
12
12
  info: {
13
- title: "LIVEX API",
14
- version: "1.0.0",
15
- description: "Contratos v1 del MVP: cat\xE1logo, disponibilidad, bookings y webhooks de pago.",
16
- license: { name: "MIT", url: "https://opensource.org/licenses/MIT" }
13
+ title: "LIVEX API - Documentaci\xF3n Completa",
14
+ version: "1.0.1",
15
+ description: "API completa de LIVEX con todos los m\xF3dulos: Auth, Users, Experiences, Categories, Resorts, Availability, Bookings, Payments, Agents, Admin"
17
16
  },
18
- servers: [{ url: "https://api.livex.app/v1" }],
17
+ servers: [
18
+ {
19
+ url: "https://api.livex.app",
20
+ description: "Producci\xF3n"
21
+ },
22
+ {
23
+ url: "http://localhost:3000",
24
+ description: "Desarrollo"
25
+ }
26
+ ],
19
27
  security: [
20
- { BearerAuth: [] }
28
+ {
29
+ BearerAuth: []
30
+ }
21
31
  ],
22
32
  paths: {
23
- "/experiences": {
33
+ "/api/v1/auth/register": {
34
+ post: {
35
+ tags: [
36
+ "Auth"
37
+ ],
38
+ summary: "Registrar usuario",
39
+ security: [],
40
+ requestBody: {
41
+ required: true,
42
+ content: {
43
+ "application/json": {
44
+ schema: {
45
+ type: "object",
46
+ required: [
47
+ "email",
48
+ "password"
49
+ ],
50
+ properties: {
51
+ email: {
52
+ type: "string",
53
+ format: "email"
54
+ },
55
+ password: {
56
+ type: "string",
57
+ minLength: 8,
58
+ pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)"
59
+ },
60
+ fullName: {
61
+ type: "string",
62
+ maxLength: 120
63
+ },
64
+ role: {
65
+ type: "string",
66
+ enum: [
67
+ "tourist",
68
+ "resort",
69
+ "admin"
70
+ ]
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ },
77
+ responses: {
78
+ "201": {
79
+ description: "Usuario registrado"
80
+ }
81
+ }
82
+ }
83
+ },
84
+ "/api/v1/auth/login": {
85
+ post: {
86
+ tags: [
87
+ "Auth"
88
+ ],
89
+ summary: "Iniciar sesi\xF3n",
90
+ security: [],
91
+ requestBody: {
92
+ required: true,
93
+ content: {
94
+ "application/json": {
95
+ schema: {
96
+ type: "object",
97
+ required: [
98
+ "email",
99
+ "password"
100
+ ],
101
+ properties: {
102
+ email: {
103
+ type: "string"
104
+ },
105
+ password: {
106
+ type: "string"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ responses: {
114
+ "200": {
115
+ description: "Login exitoso con tokens JWT"
116
+ }
117
+ }
118
+ }
119
+ },
120
+ "/api/v1/auth/refresh": {
121
+ post: {
122
+ tags: [
123
+ "Auth"
124
+ ],
125
+ summary: "Refrescar token",
126
+ security: [],
127
+ requestBody: {
128
+ required: true,
129
+ content: {
130
+ "application/json": {
131
+ schema: {
132
+ type: "object",
133
+ required: [
134
+ "refreshToken"
135
+ ],
136
+ properties: {
137
+ refreshToken: {
138
+ type: "string"
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ },
145
+ responses: {
146
+ "200": {
147
+ description: "Token renovado"
148
+ }
149
+ }
150
+ }
151
+ },
152
+ "/api/v1/auth/logout": {
153
+ post: {
154
+ tags: [
155
+ "Auth"
156
+ ],
157
+ summary: "Cerrar sesi\xF3n",
158
+ requestBody: {
159
+ content: {
160
+ "application/json": {
161
+ schema: {
162
+ type: "object",
163
+ properties: {
164
+ refreshToken: {
165
+ type: "string"
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ },
172
+ responses: {
173
+ "200": {
174
+ description: "Sesi\xF3n cerrada"
175
+ }
176
+ }
177
+ }
178
+ },
179
+ "/api/v1/auth/password/request-reset": {
180
+ post: {
181
+ tags: [
182
+ "Auth"
183
+ ],
184
+ summary: "Solicitar reseteo de contrase\xF1a",
185
+ security: [],
186
+ requestBody: {
187
+ required: true,
188
+ content: {
189
+ "application/json": {
190
+ schema: {
191
+ type: "object",
192
+ required: [
193
+ "email"
194
+ ],
195
+ properties: {
196
+ email: {
197
+ type: "string",
198
+ format: "email"
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ },
205
+ responses: {
206
+ "200": {
207
+ description: "Email enviado"
208
+ }
209
+ }
210
+ }
211
+ },
212
+ "/api/v1/auth/password/reset": {
213
+ post: {
214
+ tags: [
215
+ "Auth"
216
+ ],
217
+ summary: "Resetear contrase\xF1a",
218
+ security: [],
219
+ requestBody: {
220
+ required: true,
221
+ content: {
222
+ "application/json": {
223
+ schema: {
224
+ type: "object",
225
+ required: [
226
+ "token",
227
+ "newPassword"
228
+ ],
229
+ properties: {
230
+ token: {
231
+ type: "string"
232
+ },
233
+ newPassword: {
234
+ type: "string",
235
+ minLength: 8
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+ },
242
+ responses: {
243
+ "200": {
244
+ description: "Contrase\xF1a actualizada"
245
+ }
246
+ }
247
+ }
248
+ },
249
+ "/api/v1/user": {
250
+ get: {
251
+ tags: [
252
+ "Users"
253
+ ],
254
+ summary: "Obtener perfil del usuario autenticado",
255
+ responses: {
256
+ "200": {
257
+ description: "Perfil del usuario"
258
+ }
259
+ }
260
+ },
261
+ put: {
262
+ tags: [
263
+ "Users"
264
+ ],
265
+ summary: "Actualizar perfil",
266
+ requestBody: {
267
+ content: {
268
+ "application/json": {
269
+ schema: {
270
+ type: "object",
271
+ properties: {
272
+ fullName: {
273
+ type: "string"
274
+ },
275
+ email: {
276
+ type: "string",
277
+ format: "email"
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+ },
284
+ responses: {
285
+ "200": {
286
+ description: "Perfil actualizado"
287
+ }
288
+ }
289
+ }
290
+ },
291
+ "/api/v1/experiences": {
292
+ get: {
293
+ tags: [
294
+ "Experiences"
295
+ ],
296
+ summary: "Listar experiencias",
297
+ parameters: [
298
+ {
299
+ name: "resort_id",
300
+ in: "query",
301
+ schema: {
302
+ type: "string",
303
+ format: "uuid"
304
+ }
305
+ },
306
+ {
307
+ name: "category",
308
+ in: "query",
309
+ schema: {
310
+ type: "string",
311
+ enum: [
312
+ "islands",
313
+ "nautical",
314
+ "city_tour"
315
+ ]
316
+ }
317
+ },
318
+ {
319
+ name: "status",
320
+ in: "query",
321
+ schema: {
322
+ type: "string",
323
+ enum: [
324
+ "draft",
325
+ "under_review",
326
+ "active",
327
+ "rejected"
328
+ ]
329
+ }
330
+ },
331
+ {
332
+ name: "min_price",
333
+ in: "query",
334
+ schema: {
335
+ type: "integer"
336
+ }
337
+ },
338
+ {
339
+ name: "max_price",
340
+ in: "query",
341
+ schema: {
342
+ type: "integer"
343
+ }
344
+ },
345
+ {
346
+ name: "min_rating",
347
+ in: "query",
348
+ schema: {
349
+ type: "number",
350
+ minimum: 0,
351
+ maximum: 5
352
+ }
353
+ },
354
+ {
355
+ name: "has_images",
356
+ in: "query",
357
+ schema: {
358
+ type: "boolean"
359
+ }
360
+ },
361
+ {
362
+ name: "include_images",
363
+ in: "query",
364
+ schema: {
365
+ type: "boolean"
366
+ }
367
+ },
368
+ {
369
+ name: "limit",
370
+ in: "query",
371
+ schema: {
372
+ type: "integer",
373
+ default: 20,
374
+ maximum: 100
375
+ }
376
+ },
377
+ {
378
+ name: "offset",
379
+ in: "query",
380
+ schema: {
381
+ type: "integer",
382
+ default: 0
383
+ }
384
+ }
385
+ ],
386
+ responses: {
387
+ "200": {
388
+ description: "Lista paginada de experiencias"
389
+ }
390
+ }
391
+ },
392
+ post: {
393
+ tags: [
394
+ "Experiences"
395
+ ],
396
+ summary: "Crear experiencia",
397
+ requestBody: {
398
+ required: true,
399
+ content: {
400
+ "application/json": {
401
+ schema: {
402
+ type: "object",
403
+ required: [
404
+ "resort_id",
405
+ "title",
406
+ "category",
407
+ "price_cents"
408
+ ],
409
+ properties: {
410
+ resort_id: {
411
+ type: "string",
412
+ format: "uuid"
413
+ },
414
+ title: {
415
+ type: "string",
416
+ minLength: 5,
417
+ maxLength: 200
418
+ },
419
+ description: {
420
+ type: "string",
421
+ maxLength: 2e3
422
+ },
423
+ category: {
424
+ type: "string",
425
+ enum: [
426
+ "islands",
427
+ "nautical",
428
+ "city_tour"
429
+ ]
430
+ },
431
+ price_cents: {
432
+ type: "integer",
433
+ minimum: 1
434
+ },
435
+ currency: {
436
+ type: "string",
437
+ default: "COP",
438
+ maxLength: 3
439
+ },
440
+ includes: {
441
+ type: "string",
442
+ maxLength: 1e3
443
+ },
444
+ excludes: {
445
+ type: "string",
446
+ maxLength: 1e3
447
+ },
448
+ main_image_url: {
449
+ type: "string",
450
+ format: "uri"
451
+ },
452
+ status: {
453
+ type: "string",
454
+ enum: [
455
+ "draft",
456
+ "under_review",
457
+ "active",
458
+ "rejected"
459
+ ],
460
+ default: "under_review"
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ },
467
+ responses: {
468
+ "201": {
469
+ description: "Experiencia creada"
470
+ }
471
+ }
472
+ }
473
+ },
474
+ "/api/v1/experiences/{id}": {
475
+ get: {
476
+ tags: [
477
+ "Experiences"
478
+ ],
479
+ summary: "Obtener experiencia por ID",
480
+ parameters: [
481
+ {
482
+ name: "id",
483
+ in: "path",
484
+ required: true,
485
+ schema: {
486
+ type: "string",
487
+ format: "uuid"
488
+ }
489
+ },
490
+ {
491
+ name: "include_images",
492
+ in: "query",
493
+ schema: {
494
+ type: "boolean"
495
+ }
496
+ }
497
+ ],
498
+ responses: {
499
+ "200": {
500
+ description: "Detalle de experiencia"
501
+ }
502
+ }
503
+ },
504
+ patch: {
505
+ tags: [
506
+ "Experiences"
507
+ ],
508
+ summary: "Actualizar experiencia",
509
+ parameters: [
510
+ {
511
+ name: "id",
512
+ in: "path",
513
+ required: true,
514
+ schema: {
515
+ type: "string",
516
+ format: "uuid"
517
+ }
518
+ }
519
+ ],
520
+ requestBody: {
521
+ content: {
522
+ "application/json": {
523
+ schema: {
524
+ type: "object"
525
+ }
526
+ }
527
+ }
528
+ },
529
+ responses: {
530
+ "200": {
531
+ description: "Experiencia actualizada"
532
+ }
533
+ }
534
+ },
535
+ delete: {
536
+ tags: [
537
+ "Experiences"
538
+ ],
539
+ summary: "Eliminar experiencia",
540
+ parameters: [
541
+ {
542
+ name: "id",
543
+ in: "path",
544
+ required: true,
545
+ schema: {
546
+ type: "string",
547
+ format: "uuid"
548
+ }
549
+ }
550
+ ],
551
+ responses: {
552
+ "204": {
553
+ description: "Eliminada"
554
+ }
555
+ }
556
+ }
557
+ },
558
+ "/api/v1/experiences/resort/{resortId}/slug/{slug}": {
559
+ get: {
560
+ tags: [
561
+ "Experiences"
562
+ ],
563
+ summary: "Buscar experiencia por slug y resort",
564
+ parameters: [
565
+ {
566
+ name: "resortId",
567
+ in: "path",
568
+ required: true,
569
+ schema: {
570
+ type: "string",
571
+ format: "uuid"
572
+ }
573
+ },
574
+ {
575
+ name: "slug",
576
+ in: "path",
577
+ required: true,
578
+ schema: {
579
+ type: "string"
580
+ }
581
+ },
582
+ {
583
+ name: "include_images",
584
+ in: "query",
585
+ schema: {
586
+ type: "boolean"
587
+ }
588
+ }
589
+ ],
590
+ responses: {
591
+ "200": {
592
+ description: "Experiencia encontrada"
593
+ }
594
+ }
595
+ }
596
+ },
597
+ "/api/v1/experiences/{id}/images/presign": {
598
+ post: {
599
+ tags: [
600
+ "Experiences"
601
+ ],
602
+ summary: "Obtener URL pre-firmada para subir imagen",
603
+ parameters: [
604
+ {
605
+ name: "id",
606
+ in: "path",
607
+ required: true,
608
+ schema: {
609
+ type: "string",
610
+ format: "uuid"
611
+ }
612
+ }
613
+ ],
614
+ requestBody: {
615
+ required: true,
616
+ content: {
617
+ "application/json": {
618
+ schema: {
619
+ type: "object",
620
+ required: [
621
+ "filename",
622
+ "contentType"
623
+ ],
624
+ properties: {
625
+ filename: {
626
+ type: "string"
627
+ },
628
+ contentType: {
629
+ type: "string"
630
+ }
631
+ }
632
+ }
633
+ }
634
+ }
635
+ },
636
+ responses: {
637
+ "201": {
638
+ description: "URL pre-firmada generada"
639
+ }
640
+ }
641
+ }
642
+ },
643
+ "/api/v1/experiences/{id}/images/upload": {
644
+ post: {
645
+ tags: [
646
+ "Experiences"
647
+ ],
648
+ summary: "Subir imagen de experiencia",
649
+ parameters: [
650
+ {
651
+ name: "id",
652
+ in: "path",
653
+ required: true,
654
+ schema: {
655
+ type: "string",
656
+ format: "uuid"
657
+ }
658
+ }
659
+ ],
660
+ requestBody: {
661
+ required: true,
662
+ content: {
663
+ "multipart/form-data": {
664
+ schema: {
665
+ type: "object"
666
+ }
667
+ }
668
+ }
669
+ },
670
+ responses: {
671
+ "201": {
672
+ description: "Imagen subida"
673
+ }
674
+ }
675
+ }
676
+ },
677
+ "/api/v1/experiences/{id}/images/{imageId}": {
678
+ delete: {
679
+ tags: [
680
+ "Experiences"
681
+ ],
682
+ summary: "Eliminar imagen de experiencia",
683
+ parameters: [
684
+ {
685
+ name: "id",
686
+ in: "path",
687
+ required: true,
688
+ schema: {
689
+ type: "string",
690
+ format: "uuid"
691
+ }
692
+ },
693
+ {
694
+ name: "imageId",
695
+ in: "path",
696
+ required: true,
697
+ schema: {
698
+ type: "string",
699
+ format: "uuid"
700
+ }
701
+ }
702
+ ],
703
+ responses: {
704
+ "204": {
705
+ description: "Imagen eliminada"
706
+ }
707
+ }
708
+ }
709
+ },
710
+ "/api/v1/experiences/{id}/submit": {
711
+ post: {
712
+ tags: [
713
+ "Experiences"
714
+ ],
715
+ summary: "Enviar experiencia a revisi\xF3n",
716
+ parameters: [
717
+ {
718
+ name: "id",
719
+ in: "path",
720
+ required: true,
721
+ schema: {
722
+ type: "string",
723
+ format: "uuid"
724
+ }
725
+ }
726
+ ],
727
+ requestBody: {
728
+ content: {
729
+ "application/json": {
730
+ schema: {
731
+ type: "object",
732
+ properties: {
733
+ notes: {
734
+ type: "string"
735
+ }
736
+ }
737
+ }
738
+ }
739
+ }
740
+ },
741
+ responses: {
742
+ "200": {
743
+ description: "Enviada a revisi\xF3n"
744
+ }
745
+ }
746
+ }
747
+ },
748
+ "/api/v1/categories": {
749
+ get: {
750
+ tags: [
751
+ "Categories"
752
+ ],
753
+ summary: "Listar categor\xEDas con paginaci\xF3n y filtros",
754
+ parameters: [
755
+ {
756
+ name: "name",
757
+ in: "query",
758
+ schema: {
759
+ type: "string"
760
+ }
761
+ },
762
+ {
763
+ name: "slug",
764
+ in: "query",
765
+ schema: {
766
+ type: "string"
767
+ }
768
+ },
769
+ {
770
+ name: "limit",
771
+ in: "query",
772
+ schema: {
773
+ type: "integer",
774
+ default: 20,
775
+ minimum: 1,
776
+ maximum: 100
777
+ }
778
+ },
779
+ {
780
+ name: "offset",
781
+ in: "query",
782
+ schema: {
783
+ type: "integer",
784
+ default: 0,
785
+ minimum: 0
786
+ }
787
+ }
788
+ ],
789
+ responses: {
790
+ "200": {
791
+ description: "Lista paginada de categor\xEDas",
792
+ content: {
793
+ "application/json": {
794
+ schema: {
795
+ type: "object",
796
+ properties: {
797
+ items: {
798
+ type: "array",
799
+ items: {
800
+ type: "object",
801
+ properties: {
802
+ id: {
803
+ type: "string",
804
+ format: "uuid"
805
+ },
806
+ name: {
807
+ type: "string"
808
+ },
809
+ slug: {
810
+ type: "string"
811
+ },
812
+ created_at: {
813
+ type: "string",
814
+ format: "date-time"
815
+ },
816
+ updated_at: {
817
+ type: "string",
818
+ format: "date-time"
819
+ }
820
+ }
821
+ }
822
+ },
823
+ total: {
824
+ type: "integer"
825
+ },
826
+ limit: {
827
+ type: "integer"
828
+ },
829
+ offset: {
830
+ type: "integer"
831
+ }
832
+ }
833
+ }
834
+ }
835
+ }
836
+ }
837
+ }
838
+ },
839
+ post: {
840
+ tags: [
841
+ "Categories"
842
+ ],
843
+ summary: "Crear nueva categor\xEDa",
844
+ requestBody: {
845
+ required: true,
846
+ content: {
847
+ "application/json": {
848
+ schema: {
849
+ type: "object",
850
+ required: [
851
+ "name"
852
+ ],
853
+ properties: {
854
+ name: {
855
+ type: "string",
856
+ minLength: 2,
857
+ maxLength: 100
858
+ },
859
+ slug: {
860
+ type: "string",
861
+ minLength: 2,
862
+ maxLength: 100,
863
+ pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
864
+ }
865
+ }
866
+ }
867
+ }
868
+ }
869
+ },
870
+ responses: {
871
+ "201": {
872
+ description: "Categor\xEDa creada exitosamente",
873
+ content: {
874
+ "application/json": {
875
+ schema: {
876
+ type: "object",
877
+ properties: {
878
+ id: {
879
+ type: "string",
880
+ format: "uuid"
881
+ },
882
+ name: {
883
+ type: "string"
884
+ },
885
+ slug: {
886
+ type: "string"
887
+ },
888
+ created_at: {
889
+ type: "string",
890
+ format: "date-time"
891
+ },
892
+ updated_at: {
893
+ type: "string",
894
+ format: "date-time"
895
+ }
896
+ }
897
+ }
898
+ }
899
+ }
900
+ }
901
+ }
902
+ }
903
+ },
904
+ "/api/v1/categories/{id}": {
905
+ get: {
906
+ tags: [
907
+ "Categories"
908
+ ],
909
+ summary: "Obtener categor\xEDa por ID",
910
+ parameters: [
911
+ {
912
+ name: "id",
913
+ in: "path",
914
+ required: true,
915
+ schema: {
916
+ type: "string",
917
+ format: "uuid"
918
+ }
919
+ }
920
+ ],
921
+ responses: {
922
+ "200": {
923
+ description: "Categor\xEDa encontrada",
924
+ content: {
925
+ "application/json": {
926
+ schema: {
927
+ type: "object",
928
+ properties: {
929
+ id: {
930
+ type: "string",
931
+ format: "uuid"
932
+ },
933
+ name: {
934
+ type: "string"
935
+ },
936
+ slug: {
937
+ type: "string"
938
+ },
939
+ created_at: {
940
+ type: "string",
941
+ format: "date-time"
942
+ },
943
+ updated_at: {
944
+ type: "string",
945
+ format: "date-time"
946
+ }
947
+ }
948
+ }
949
+ }
950
+ }
951
+ }
952
+ }
953
+ },
954
+ patch: {
955
+ tags: [
956
+ "Categories"
957
+ ],
958
+ summary: "Actualizar categor\xEDa",
959
+ parameters: [
960
+ {
961
+ name: "id",
962
+ in: "path",
963
+ required: true,
964
+ schema: {
965
+ type: "string",
966
+ format: "uuid"
967
+ }
968
+ }
969
+ ],
970
+ requestBody: {
971
+ required: true,
972
+ content: {
973
+ "application/json": {
974
+ schema: {
975
+ type: "object",
976
+ properties: {
977
+ name: {
978
+ type: "string",
979
+ minLength: 2,
980
+ maxLength: 100
981
+ },
982
+ slug: {
983
+ type: "string",
984
+ minLength: 2,
985
+ maxLength: 100,
986
+ pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
987
+ }
988
+ }
989
+ }
990
+ }
991
+ }
992
+ },
993
+ responses: {
994
+ "200": {
995
+ description: "Categor\xEDa actualizada",
996
+ content: {
997
+ "application/json": {
998
+ schema: {
999
+ type: "object",
1000
+ properties: {
1001
+ id: {
1002
+ type: "string",
1003
+ format: "uuid"
1004
+ },
1005
+ name: {
1006
+ type: "string"
1007
+ },
1008
+ slug: {
1009
+ type: "string"
1010
+ },
1011
+ created_at: {
1012
+ type: "string",
1013
+ format: "date-time"
1014
+ },
1015
+ updated_at: {
1016
+ type: "string",
1017
+ format: "date-time"
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ },
1026
+ delete: {
1027
+ tags: [
1028
+ "Categories"
1029
+ ],
1030
+ summary: "Eliminar categor\xEDa",
1031
+ parameters: [
1032
+ {
1033
+ name: "id",
1034
+ in: "path",
1035
+ required: true,
1036
+ schema: {
1037
+ type: "string",
1038
+ format: "uuid"
1039
+ }
1040
+ }
1041
+ ],
1042
+ responses: {
1043
+ "204": {
1044
+ description: "Categor\xEDa eliminada exitosamente"
1045
+ }
1046
+ }
1047
+ }
1048
+ },
1049
+ "/api/v1/categories/slug/{slug}": {
1050
+ get: {
1051
+ tags: [
1052
+ "Categories"
1053
+ ],
1054
+ summary: "Buscar categor\xEDa por slug",
1055
+ parameters: [
1056
+ {
1057
+ name: "slug",
1058
+ in: "path",
1059
+ required: true,
1060
+ schema: {
1061
+ type: "string",
1062
+ pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
1063
+ }
1064
+ }
1065
+ ],
1066
+ responses: {
1067
+ "200": {
1068
+ description: "Categor\xEDa encontrada",
1069
+ content: {
1070
+ "application/json": {
1071
+ schema: {
1072
+ type: "object",
1073
+ properties: {
1074
+ id: {
1075
+ type: "string",
1076
+ format: "uuid"
1077
+ },
1078
+ name: {
1079
+ type: "string"
1080
+ },
1081
+ slug: {
1082
+ type: "string"
1083
+ },
1084
+ created_at: {
1085
+ type: "string",
1086
+ format: "date-time"
1087
+ },
1088
+ updated_at: {
1089
+ type: "string",
1090
+ format: "date-time"
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ },
1100
+ "/api/v1/resorts": {
1101
+ get: {
1102
+ tags: [
1103
+ "Resorts"
1104
+ ],
1105
+ summary: "Listar todos los resorts (Admin)",
1106
+ parameters: [
1107
+ {
1108
+ name: "limit",
1109
+ in: "query",
1110
+ schema: {
1111
+ type: "integer",
1112
+ default: 20,
1113
+ minimum: 1,
1114
+ maximum: 100
1115
+ }
1116
+ },
1117
+ {
1118
+ name: "offset",
1119
+ in: "query",
1120
+ schema: {
1121
+ type: "integer",
1122
+ default: 0,
1123
+ minimum: 0
1124
+ }
1125
+ }
1126
+ ],
1127
+ responses: {
1128
+ "200": {
1129
+ description: "Lista paginada de resorts",
1130
+ content: {
1131
+ "application/json": {
1132
+ schema: {
1133
+ type: "object",
1134
+ properties: {
1135
+ items: {
1136
+ type: "array",
1137
+ items: {
1138
+ type: "object",
1139
+ properties: {
1140
+ id: {
1141
+ type: "string",
1142
+ format: "uuid"
1143
+ },
1144
+ owner_id: {
1145
+ type: "string",
1146
+ format: "uuid"
1147
+ },
1148
+ name: {
1149
+ type: "string"
1150
+ },
1151
+ description: {
1152
+ type: "string"
1153
+ },
1154
+ contact_email: {
1155
+ type: "string",
1156
+ format: "email"
1157
+ },
1158
+ contact_phone: {
1159
+ type: "string"
1160
+ },
1161
+ address_line: {
1162
+ type: "string"
1163
+ },
1164
+ city: {
1165
+ type: "string"
1166
+ },
1167
+ country: {
1168
+ type: "string"
1169
+ },
1170
+ latitude: {
1171
+ type: "number"
1172
+ },
1173
+ longitude: {
1174
+ type: "number"
1175
+ },
1176
+ is_active: {
1177
+ type: "boolean"
1178
+ },
1179
+ status: {
1180
+ type: "string",
1181
+ enum: [
1182
+ "draft",
1183
+ "under_review",
1184
+ "active",
1185
+ "rejected"
1186
+ ]
1187
+ },
1188
+ created_at: {
1189
+ type: "string",
1190
+ format: "date-time"
1191
+ },
1192
+ updated_at: {
1193
+ type: "string",
1194
+ format: "date-time"
1195
+ }
1196
+ }
1197
+ }
1198
+ },
1199
+ total: {
1200
+ type: "integer"
1201
+ },
1202
+ limit: {
1203
+ type: "integer"
1204
+ },
1205
+ offset: {
1206
+ type: "integer"
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+ },
1215
+ post: {
1216
+ tags: [
1217
+ "Resorts"
1218
+ ],
1219
+ summary: "Crear resort",
1220
+ requestBody: {
1221
+ required: true,
1222
+ content: {
1223
+ "application/json": {
1224
+ schema: {
1225
+ type: "object",
1226
+ required: [
1227
+ "name"
1228
+ ],
1229
+ properties: {
1230
+ name: {
1231
+ type: "string",
1232
+ minLength: 2,
1233
+ maxLength: 100
1234
+ },
1235
+ description: {
1236
+ type: "string",
1237
+ maxLength: 1e3
1238
+ },
1239
+ contact_email: {
1240
+ type: "string",
1241
+ format: "email"
1242
+ },
1243
+ contact_phone: {
1244
+ type: "string",
1245
+ maxLength: 20
1246
+ },
1247
+ address_line: {
1248
+ type: "string",
1249
+ maxLength: 200
1250
+ },
1251
+ city: {
1252
+ type: "string",
1253
+ maxLength: 100
1254
+ },
1255
+ country: {
1256
+ type: "string",
1257
+ maxLength: 100
1258
+ },
1259
+ latitude: {
1260
+ type: "number"
1261
+ },
1262
+ longitude: {
1263
+ type: "number"
1264
+ },
1265
+ is_active: {
1266
+ type: "boolean"
1267
+ }
1268
+ }
1269
+ }
1270
+ }
1271
+ }
1272
+ },
1273
+ responses: {
1274
+ "201": {
1275
+ description: "Resort creado",
1276
+ content: {
1277
+ "application/json": {
1278
+ schema: {
1279
+ type: "object",
1280
+ properties: {
1281
+ id: {
1282
+ type: "string",
1283
+ format: "uuid"
1284
+ },
1285
+ owner_id: {
1286
+ type: "string",
1287
+ format: "uuid"
1288
+ },
1289
+ name: {
1290
+ type: "string"
1291
+ },
1292
+ description: {
1293
+ type: "string"
1294
+ },
1295
+ contact_email: {
1296
+ type: "string"
1297
+ },
1298
+ contact_phone: {
1299
+ type: "string"
1300
+ },
1301
+ address_line: {
1302
+ type: "string"
1303
+ },
1304
+ city: {
1305
+ type: "string"
1306
+ },
1307
+ country: {
1308
+ type: "string"
1309
+ },
1310
+ latitude: {
1311
+ type: "number"
1312
+ },
1313
+ longitude: {
1314
+ type: "number"
1315
+ },
1316
+ is_active: {
1317
+ type: "boolean"
1318
+ },
1319
+ status: {
1320
+ type: "string"
1321
+ },
1322
+ created_at: {
1323
+ type: "string",
1324
+ format: "date-time"
1325
+ },
1326
+ updated_at: {
1327
+ type: "string",
1328
+ format: "date-time"
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+ }
1337
+ },
1338
+ "/api/v1/resorts/my-resorts": {
1339
+ get: {
1340
+ tags: [
1341
+ "Resorts"
1342
+ ],
1343
+ summary: "Obtener mis resorts",
1344
+ parameters: [
1345
+ {
1346
+ name: "limit",
1347
+ in: "query",
1348
+ schema: {
1349
+ type: "integer",
1350
+ default: 20
1351
+ }
1352
+ },
1353
+ {
1354
+ name: "offset",
1355
+ in: "query",
1356
+ schema: {
1357
+ type: "integer",
1358
+ default: 0
1359
+ }
1360
+ }
1361
+ ],
1362
+ responses: {
1363
+ "200": {
1364
+ description: "Lista de mis resorts",
1365
+ content: {
1366
+ "application/json": {
1367
+ schema: {
1368
+ type: "object",
1369
+ properties: {
1370
+ items: {
1371
+ type: "array",
1372
+ items: {
1373
+ type: "object",
1374
+ properties: {
1375
+ id: {
1376
+ type: "string",
1377
+ format: "uuid"
1378
+ },
1379
+ name: {
1380
+ type: "string"
1381
+ },
1382
+ description: {
1383
+ type: "string"
1384
+ },
1385
+ city: {
1386
+ type: "string"
1387
+ },
1388
+ country: {
1389
+ type: "string"
1390
+ },
1391
+ status: {
1392
+ type: "string"
1393
+ },
1394
+ created_at: {
1395
+ type: "string",
1396
+ format: "date-time"
1397
+ }
1398
+ }
1399
+ }
1400
+ },
1401
+ total: {
1402
+ type: "integer"
1403
+ }
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+ },
1412
+ "/api/v1/resorts/{id}": {
1413
+ get: {
1414
+ tags: [
1415
+ "Resorts"
1416
+ ],
1417
+ summary: "Obtener resort por ID",
1418
+ parameters: [
1419
+ {
1420
+ name: "id",
1421
+ in: "path",
1422
+ required: true,
1423
+ schema: {
1424
+ type: "string",
1425
+ format: "uuid"
1426
+ }
1427
+ }
1428
+ ],
1429
+ responses: {
1430
+ "200": {
1431
+ description: "Resort encontrado",
1432
+ content: {
1433
+ "application/json": {
1434
+ schema: {
1435
+ type: "object",
1436
+ properties: {
1437
+ id: {
1438
+ type: "string",
1439
+ format: "uuid"
1440
+ },
1441
+ owner_id: {
1442
+ type: "string",
1443
+ format: "uuid"
1444
+ },
1445
+ name: {
1446
+ type: "string"
1447
+ },
1448
+ description: {
1449
+ type: "string"
1450
+ },
1451
+ contact_email: {
1452
+ type: "string"
1453
+ },
1454
+ contact_phone: {
1455
+ type: "string"
1456
+ },
1457
+ address_line: {
1458
+ type: "string"
1459
+ },
1460
+ city: {
1461
+ type: "string"
1462
+ },
1463
+ country: {
1464
+ type: "string"
1465
+ },
1466
+ latitude: {
1467
+ type: "number"
1468
+ },
1469
+ longitude: {
1470
+ type: "number"
1471
+ },
1472
+ is_active: {
1473
+ type: "boolean"
1474
+ },
1475
+ status: {
1476
+ type: "string"
1477
+ },
1478
+ created_at: {
1479
+ type: "string",
1480
+ format: "date-time"
1481
+ },
1482
+ updated_at: {
1483
+ type: "string",
1484
+ format: "date-time"
1485
+ }
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1491
+ }
1492
+ },
1493
+ patch: {
1494
+ tags: [
1495
+ "Resorts"
1496
+ ],
1497
+ summary: "Actualizar resort",
1498
+ parameters: [
1499
+ {
1500
+ name: "id",
1501
+ in: "path",
1502
+ required: true,
1503
+ schema: {
1504
+ type: "string",
1505
+ format: "uuid"
1506
+ }
1507
+ }
1508
+ ],
1509
+ requestBody: {
1510
+ required: true,
1511
+ content: {
1512
+ "application/json": {
1513
+ schema: {
1514
+ type: "object",
1515
+ properties: {
1516
+ name: {
1517
+ type: "string",
1518
+ minLength: 2,
1519
+ maxLength: 100
1520
+ },
1521
+ description: {
1522
+ type: "string",
1523
+ maxLength: 1e3
1524
+ },
1525
+ contact_email: {
1526
+ type: "string",
1527
+ format: "email"
1528
+ },
1529
+ contact_phone: {
1530
+ type: "string",
1531
+ maxLength: 20
1532
+ },
1533
+ address_line: {
1534
+ type: "string",
1535
+ maxLength: 200
1536
+ },
1537
+ city: {
1538
+ type: "string",
1539
+ maxLength: 100
1540
+ },
1541
+ country: {
1542
+ type: "string",
1543
+ maxLength: 100
1544
+ },
1545
+ latitude: {
1546
+ type: "number"
1547
+ },
1548
+ longitude: {
1549
+ type: "number"
1550
+ },
1551
+ is_active: {
1552
+ type: "boolean"
1553
+ }
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ },
1559
+ responses: {
1560
+ "200": {
1561
+ description: "Resort actualizado",
1562
+ content: {
1563
+ "application/json": {
1564
+ schema: {
1565
+ type: "object",
1566
+ properties: {
1567
+ id: {
1568
+ type: "string",
1569
+ format: "uuid"
1570
+ },
1571
+ name: {
1572
+ type: "string"
1573
+ },
1574
+ description: {
1575
+ type: "string"
1576
+ },
1577
+ status: {
1578
+ type: "string"
1579
+ },
1580
+ updated_at: {
1581
+ type: "string",
1582
+ format: "date-time"
1583
+ }
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+ },
1591
+ delete: {
1592
+ tags: [
1593
+ "Resorts"
1594
+ ],
1595
+ summary: "Eliminar resort",
1596
+ parameters: [
1597
+ {
1598
+ name: "id",
1599
+ in: "path",
1600
+ required: true,
1601
+ schema: {
1602
+ type: "string",
1603
+ format: "uuid"
1604
+ }
1605
+ }
1606
+ ],
1607
+ responses: {
1608
+ "204": {
1609
+ description: "Resort eliminado exitosamente"
1610
+ }
1611
+ }
1612
+ }
1613
+ },
1614
+ "/api/v1/resorts/{id}/submit": {
1615
+ post: {
1616
+ tags: [
1617
+ "Resorts"
1618
+ ],
1619
+ summary: "Enviar resort a revisi\xF3n",
1620
+ parameters: [
1621
+ {
1622
+ name: "id",
1623
+ in: "path",
1624
+ required: true,
1625
+ schema: {
1626
+ type: "string",
1627
+ format: "uuid"
1628
+ }
1629
+ }
1630
+ ],
1631
+ responses: {
1632
+ "200": {
1633
+ description: "Resort enviado a revisi\xF3n",
1634
+ content: {
1635
+ "application/json": {
1636
+ schema: {
1637
+ type: "object",
1638
+ properties: {
1639
+ id: {
1640
+ type: "string",
1641
+ format: "uuid"
1642
+ },
1643
+ name: {
1644
+ type: "string"
1645
+ },
1646
+ status: {
1647
+ type: "string",
1648
+ enum: [
1649
+ "under_review"
1650
+ ]
1651
+ },
1652
+ updated_at: {
1653
+ type: "string",
1654
+ format: "date-time"
1655
+ }
1656
+ }
1657
+ }
1658
+ }
1659
+ }
1660
+ }
1661
+ }
1662
+ }
1663
+ },
1664
+ "/api/v1/resorts/{id}/approve": {
1665
+ post: {
1666
+ tags: [
1667
+ "Resorts"
1668
+ ],
1669
+ summary: "Aprobar resort (Admin)",
1670
+ parameters: [
1671
+ {
1672
+ name: "id",
1673
+ in: "path",
1674
+ required: true,
1675
+ schema: {
1676
+ type: "string",
1677
+ format: "uuid"
1678
+ }
1679
+ }
1680
+ ],
1681
+ requestBody: {
1682
+ content: {
1683
+ "application/json": {
1684
+ schema: {
1685
+ type: "object",
1686
+ properties: {
1687
+ notes: {
1688
+ type: "string",
1689
+ maxLength: 500
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ },
1696
+ responses: {
1697
+ "200": {
1698
+ description: "Resort aprobado",
1699
+ content: {
1700
+ "application/json": {
1701
+ schema: {
1702
+ type: "object",
1703
+ properties: {
1704
+ id: {
1705
+ type: "string",
1706
+ format: "uuid"
1707
+ },
1708
+ name: {
1709
+ type: "string"
1710
+ },
1711
+ status: {
1712
+ type: "string",
1713
+ enum: [
1714
+ "active"
1715
+ ]
1716
+ },
1717
+ updated_at: {
1718
+ type: "string",
1719
+ format: "date-time"
1720
+ }
1721
+ }
1722
+ }
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ }
1728
+ },
1729
+ "/api/v1/resorts/{id}/reject": {
1730
+ post: {
1731
+ tags: [
1732
+ "Resorts"
1733
+ ],
1734
+ summary: "Rechazar resort (Admin)",
1735
+ parameters: [
1736
+ {
1737
+ name: "id",
1738
+ in: "path",
1739
+ required: true,
1740
+ schema: {
1741
+ type: "string",
1742
+ format: "uuid"
1743
+ }
1744
+ }
1745
+ ],
1746
+ requestBody: {
1747
+ required: true,
1748
+ content: {
1749
+ "application/json": {
1750
+ schema: {
1751
+ type: "object",
1752
+ required: [
1753
+ "rejection_reason"
1754
+ ],
1755
+ properties: {
1756
+ rejection_reason: {
1757
+ type: "string",
1758
+ maxLength: 500
1759
+ }
1760
+ }
1761
+ }
1762
+ }
1763
+ }
1764
+ },
1765
+ responses: {
1766
+ "200": {
1767
+ description: "Resort rechazado",
1768
+ content: {
1769
+ "application/json": {
1770
+ schema: {
1771
+ type: "object",
1772
+ properties: {
1773
+ id: {
1774
+ type: "string",
1775
+ format: "uuid"
1776
+ },
1777
+ name: {
1778
+ type: "string"
1779
+ },
1780
+ status: {
1781
+ type: "string",
1782
+ enum: [
1783
+ "rejected"
1784
+ ]
1785
+ },
1786
+ rejection_reason: {
1787
+ type: "string"
1788
+ },
1789
+ updated_at: {
1790
+ type: "string",
1791
+ format: "date-time"
1792
+ }
1793
+ }
1794
+ }
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+ }
1800
+ },
1801
+ "/api/v1/experiences/{experienceId}/availability": {
1802
+ get: {
1803
+ tags: [
1804
+ "Availability"
1805
+ ],
1806
+ summary: "Consultar disponibilidad de experiencia",
1807
+ description: "Retorna slots de disponibilidad agrupados por fecha con informaci\xF3n de capacidad y precio",
1808
+ parameters: [
1809
+ {
1810
+ name: "experienceId",
1811
+ in: "path",
1812
+ required: true,
1813
+ schema: {
1814
+ type: "string",
1815
+ format: "uuid"
1816
+ }
1817
+ },
1818
+ {
1819
+ name: "from",
1820
+ in: "query",
1821
+ schema: {
1822
+ type: "string",
1823
+ format: "date",
1824
+ description: "YYYY-MM-DD"
1825
+ }
1826
+ },
1827
+ {
1828
+ name: "to",
1829
+ in: "query",
1830
+ schema: {
1831
+ type: "string",
1832
+ format: "date",
1833
+ description: "YYYY-MM-DD"
1834
+ }
1835
+ },
1836
+ {
1837
+ name: "limit",
1838
+ in: "query",
1839
+ schema: {
1840
+ type: "integer",
1841
+ default: 30,
1842
+ minimum: 1,
1843
+ maximum: 100
1844
+ }
1845
+ },
1846
+ {
1847
+ name: "offset",
1848
+ in: "query",
1849
+ schema: {
1850
+ type: "integer",
1851
+ default: 0
1852
+ }
1853
+ },
1854
+ {
1855
+ name: "include_full_slots",
1856
+ in: "query",
1857
+ schema: {
1858
+ type: "boolean",
1859
+ default: false
1860
+ }
1861
+ }
1862
+ ],
1863
+ responses: {
1864
+ "200": {
1865
+ description: "Disponibilidad encontrada",
1866
+ content: {
1867
+ "application/json": {
1868
+ schema: {
1869
+ type: "object",
1870
+ properties: {
1871
+ experience_id: {
1872
+ type: "string",
1873
+ format: "uuid"
1874
+ },
1875
+ availability: {
1876
+ type: "array"
1877
+ },
1878
+ query_params: {
1879
+ type: "object"
1880
+ },
1881
+ total_days: {
1882
+ type: "integer"
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1891
+ },
1892
+ "/api/v1/experiences/{experienceId}/availability/slot": {
1893
+ post: {
1894
+ tags: [
1895
+ "Availability"
1896
+ ],
1897
+ summary: "Crear slot individual de disponibilidad",
1898
+ parameters: [
1899
+ {
1900
+ name: "experienceId",
1901
+ in: "path",
1902
+ required: true,
1903
+ schema: {
1904
+ type: "string",
1905
+ format: "uuid"
1906
+ }
1907
+ }
1908
+ ],
1909
+ requestBody: {
1910
+ required: true,
1911
+ content: {
1912
+ "application/json": {
1913
+ schema: {
1914
+ type: "object",
1915
+ required: [
1916
+ "start_time",
1917
+ "end_time",
1918
+ "capacity"
1919
+ ],
1920
+ properties: {
1921
+ start_time: {
1922
+ type: "string",
1923
+ format: "date-time",
1924
+ description: "ISO 8601"
1925
+ },
1926
+ end_time: {
1927
+ type: "string",
1928
+ format: "date-time",
1929
+ description: "ISO 8601"
1930
+ },
1931
+ capacity: {
1932
+ type: "integer",
1933
+ minimum: 0
1934
+ }
1935
+ }
1936
+ }
1937
+ }
1938
+ }
1939
+ },
1940
+ responses: {
1941
+ "201": {
1942
+ description: "Slot creado",
1943
+ content: {
1944
+ "application/json": {
1945
+ schema: {
1946
+ type: "object",
1947
+ properties: {
1948
+ message: {
1949
+ type: "string"
1950
+ },
1951
+ slot: {
1952
+ type: "object"
1953
+ }
1954
+ }
1955
+ }
1956
+ }
1957
+ }
1958
+ }
1959
+ }
1960
+ }
1961
+ },
1962
+ "/api/v1/experiences/{experienceId}/availability/bulk": {
1963
+ post: {
1964
+ tags: [
1965
+ "Availability"
1966
+ ],
1967
+ summary: "Crear m\xFAltiples slots (bulk)",
1968
+ description: "Crea m\xFAltiples slots de disponibilidad para un rango de fechas con configuraci\xF3n de horarios",
1969
+ parameters: [
1970
+ {
1971
+ name: "experienceId",
1972
+ in: "path",
1973
+ required: true,
1974
+ schema: {
1975
+ type: "string",
1976
+ format: "uuid"
1977
+ }
1978
+ }
1979
+ ],
1980
+ requestBody: {
1981
+ required: true,
1982
+ content: {
1983
+ "application/json": {
1984
+ schema: {
1985
+ type: "object",
1986
+ required: [
1987
+ "start_date",
1988
+ "end_date",
1989
+ "slots"
1990
+ ],
1991
+ properties: {
1992
+ start_date: {
1993
+ type: "string",
1994
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
1995
+ description: "YYYY-MM-DD"
1996
+ },
1997
+ end_date: {
1998
+ type: "string",
1999
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
2000
+ description: "YYYY-MM-DD"
2001
+ },
2002
+ capacity: {
2003
+ type: "integer",
2004
+ minimum: 0,
2005
+ default: 10
2006
+ },
2007
+ slots: {
2008
+ type: "array",
2009
+ items: {
2010
+ type: "object",
2011
+ required: [
2012
+ "start_hour",
2013
+ "start_minute",
2014
+ "end_hour",
2015
+ "end_minute"
2016
+ ],
2017
+ properties: {
2018
+ start_hour: {
2019
+ type: "integer",
2020
+ minimum: 0,
2021
+ maximum: 23
2022
+ },
2023
+ start_minute: {
2024
+ type: "integer",
2025
+ minimum: 0,
2026
+ maximum: 59
2027
+ },
2028
+ end_hour: {
2029
+ type: "integer",
2030
+ minimum: 0,
2031
+ maximum: 23
2032
+ },
2033
+ end_minute: {
2034
+ type: "integer",
2035
+ minimum: 0,
2036
+ maximum: 59
2037
+ },
2038
+ capacity: {
2039
+ type: "integer",
2040
+ minimum: 0
2041
+ },
2042
+ days_of_week: {
2043
+ type: "array",
2044
+ items: {
2045
+ type: "integer",
2046
+ minimum: 0,
2047
+ maximum: 6
2048
+ },
2049
+ description: "0=Domingo, 1=Lunes, ..., 6=S\xE1bado"
2050
+ }
2051
+ }
2052
+ }
2053
+ }
2054
+ }
2055
+ }
2056
+ }
2057
+ }
2058
+ },
2059
+ responses: {
2060
+ "201": {
2061
+ description: "Slots creados en bulk",
2062
+ content: {
2063
+ "application/json": {
2064
+ schema: {
2065
+ type: "object",
2066
+ properties: {
2067
+ message: {
2068
+ type: "string"
2069
+ },
2070
+ results: {
2071
+ type: "object",
2072
+ properties: {
2073
+ created_slots: {
2074
+ type: "integer"
2075
+ },
2076
+ skipped_slots: {
2077
+ type: "integer"
2078
+ },
2079
+ errors: {
2080
+ type: "array",
2081
+ items: {
2082
+ type: "string"
2083
+ }
2084
+ }
2085
+ }
2086
+ },
2087
+ experience_id: {
2088
+ type: "string",
2089
+ format: "uuid"
2090
+ },
2091
+ date_range: {
2092
+ type: "object",
2093
+ properties: {
2094
+ start_date: {
2095
+ type: "string"
2096
+ },
2097
+ end_date: {
2098
+ type: "string"
2099
+ }
2100
+ }
2101
+ }
2102
+ }
2103
+ }
2104
+ }
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ },
2110
+ "/api/v1/experiences/{experienceId}/availability/slots/{slotId}": {
2111
+ delete: {
2112
+ tags: [
2113
+ "Availability"
2114
+ ],
2115
+ summary: "Eliminar slot de disponibilidad",
2116
+ parameters: [
2117
+ {
2118
+ name: "experienceId",
2119
+ in: "path",
2120
+ required: true,
2121
+ schema: {
2122
+ type: "string",
2123
+ format: "uuid"
2124
+ }
2125
+ },
2126
+ {
2127
+ name: "slotId",
2128
+ in: "path",
2129
+ required: true,
2130
+ schema: {
2131
+ type: "string",
2132
+ format: "uuid"
2133
+ }
2134
+ }
2135
+ ],
2136
+ responses: {
2137
+ "204": {
2138
+ description: "Slot eliminado"
2139
+ }
2140
+ }
2141
+ }
2142
+ },
2143
+ "/api/v1/experiences/{experienceId}/availability/slots/{slotId}/remaining": {
2144
+ get: {
2145
+ tags: [
2146
+ "Availability"
2147
+ ],
2148
+ summary: "Obtener capacidad restante de un slot",
2149
+ parameters: [
2150
+ {
2151
+ name: "experienceId",
2152
+ in: "path",
2153
+ required: true,
2154
+ schema: {
2155
+ type: "string",
2156
+ format: "uuid"
2157
+ }
2158
+ },
2159
+ {
2160
+ name: "slotId",
2161
+ in: "path",
2162
+ required: true,
2163
+ schema: {
2164
+ type: "string",
2165
+ format: "uuid"
2166
+ }
2167
+ }
2168
+ ],
2169
+ responses: {
2170
+ "200": {
2171
+ description: "Capacidad restante",
2172
+ content: {
2173
+ "application/json": {
2174
+ schema: {
2175
+ type: "object",
2176
+ properties: {
2177
+ experience_id: {
2178
+ type: "string",
2179
+ format: "uuid"
2180
+ },
2181
+ slot_id: {
2182
+ type: "string",
2183
+ format: "uuid"
2184
+ },
2185
+ remaining: {
2186
+ type: "integer"
2187
+ }
2188
+ }
2189
+ }
2190
+ }
2191
+ }
2192
+ }
2193
+ }
2194
+ }
2195
+ },
2196
+ "/api/v1/experiences/{experienceId}/availability/debug": {
2197
+ get: {
2198
+ tags: [
2199
+ "Availability"
2200
+ ],
2201
+ summary: "DEBUG: Ver todos los slots de una experiencia",
2202
+ description: "Endpoint temporal para debugging - muestra todos los slots",
2203
+ parameters: [
2204
+ {
2205
+ name: "experienceId",
2206
+ in: "path",
2207
+ required: true,
2208
+ schema: {
2209
+ type: "string",
2210
+ format: "uuid"
2211
+ }
2212
+ }
2213
+ ],
2214
+ responses: {
2215
+ "200": {
2216
+ description: "Todos los slots",
2217
+ content: {
2218
+ "application/json": {
2219
+ schema: {
2220
+ type: "object",
2221
+ properties: {
2222
+ experience_id: {
2223
+ type: "string",
2224
+ format: "uuid"
2225
+ },
2226
+ slots: {
2227
+ type: "array"
2228
+ }
2229
+ }
2230
+ }
2231
+ }
2232
+ }
2233
+ }
2234
+ }
2235
+ }
2236
+ },
2237
+ "/api/v1/bookings": {
2238
+ post: {
2239
+ tags: [
2240
+ "Bookings"
2241
+ ],
2242
+ summary: "Crear reserva pendiente (con lock de inventario)",
2243
+ parameters: [
2244
+ {
2245
+ name: "Idempotency-Key",
2246
+ in: "header",
2247
+ schema: {
2248
+ type: "string",
2249
+ maxLength: 128
2250
+ }
2251
+ }
2252
+ ],
2253
+ requestBody: {
2254
+ required: true,
2255
+ content: {
2256
+ "application/json": {
2257
+ schema: {
2258
+ type: "object",
2259
+ required: [
2260
+ "slotId",
2261
+ "experienceId",
2262
+ "adults",
2263
+ "subtotalCents",
2264
+ "taxCents"
2265
+ ],
2266
+ properties: {
2267
+ slotId: {
2268
+ type: "string",
2269
+ format: "uuid"
2270
+ },
2271
+ experienceId: {
2272
+ type: "string",
2273
+ format: "uuid"
2274
+ },
2275
+ adults: {
2276
+ type: "integer",
2277
+ minimum: 1
2278
+ },
2279
+ children: {
2280
+ type: "integer",
2281
+ minimum: 0,
2282
+ default: 0
2283
+ },
2284
+ subtotalCents: {
2285
+ type: "integer",
2286
+ minimum: 0
2287
+ },
2288
+ taxCents: {
2289
+ type: "integer",
2290
+ minimum: 0
2291
+ },
2292
+ currency: {
2293
+ type: "string",
2294
+ pattern: "^[A-Z]{3}$",
2295
+ default: "COP"
2296
+ },
2297
+ agentId: {
2298
+ type: "string",
2299
+ format: "uuid"
2300
+ },
2301
+ referralCode: {
2302
+ type: "string"
2303
+ }
2304
+ }
2305
+ }
2306
+ }
2307
+ }
2308
+ },
2309
+ responses: {
2310
+ "201": {
2311
+ description: "Booking creado en estado pending con lock de inventario",
2312
+ content: {
2313
+ "application/json": {
2314
+ schema: {
2315
+ type: "object",
2316
+ properties: {
2317
+ bookingId: {
2318
+ type: "string",
2319
+ format: "uuid"
2320
+ },
2321
+ lockId: {
2322
+ type: "string",
2323
+ format: "uuid"
2324
+ },
2325
+ status: {
2326
+ type: "string",
2327
+ enum: [
2328
+ "pending"
2329
+ ]
2330
+ },
2331
+ expiresAt: {
2332
+ type: "string",
2333
+ format: "date-time"
2334
+ }
2335
+ }
2336
+ }
2337
+ }
2338
+ }
2339
+ }
2340
+ }
2341
+ }
2342
+ },
2343
+ "/api/v1/bookings/{bookingId}/confirm": {
2344
+ patch: {
2345
+ tags: [
2346
+ "Bookings"
2347
+ ],
2348
+ summary: "Confirmar booking pendiente",
2349
+ parameters: [
2350
+ {
2351
+ name: "bookingId",
2352
+ in: "path",
2353
+ required: true,
2354
+ schema: {
2355
+ type: "string",
2356
+ format: "uuid"
2357
+ }
2358
+ }
2359
+ ],
2360
+ responses: {
2361
+ "204": {
2362
+ description: "Booking confirmado"
2363
+ }
2364
+ }
2365
+ }
2366
+ },
2367
+ "/api/v1/bookings/{bookingId}/cancel": {
2368
+ patch: {
2369
+ tags: [
2370
+ "Bookings"
2371
+ ],
2372
+ summary: "Cancelar booking pendiente",
2373
+ parameters: [
2374
+ {
2375
+ name: "bookingId",
2376
+ in: "path",
2377
+ required: true,
2378
+ schema: {
2379
+ type: "string",
2380
+ format: "uuid"
2381
+ }
2382
+ }
2383
+ ],
2384
+ requestBody: {
2385
+ content: {
2386
+ "application/json": {
2387
+ schema: {
2388
+ type: "object",
2389
+ properties: {
2390
+ reason: {
2391
+ type: "string"
2392
+ }
2393
+ }
2394
+ }
2395
+ }
2396
+ }
2397
+ },
2398
+ responses: {
2399
+ "204": {
2400
+ description: "Booking cancelado y lock liberado"
2401
+ }
2402
+ }
2403
+ }
2404
+ },
2405
+ "/api/v1/bookings/{bookingId}/cancel-confirmed": {
2406
+ post: {
2407
+ tags: [
2408
+ "Bookings"
2409
+ ],
2410
+ summary: "Cancelar booking confirmado (con reembolso autom\xE1tico)",
2411
+ description: "Cancela la reserva confirmada, libera inventario y procesa reembolso autom\xE1ticamente",
2412
+ parameters: [
2413
+ {
2414
+ name: "bookingId",
2415
+ in: "path",
2416
+ required: true,
2417
+ schema: {
2418
+ type: "string",
2419
+ format: "uuid"
2420
+ }
2421
+ }
2422
+ ],
2423
+ requestBody: {
2424
+ content: {
2425
+ "application/json": {
2426
+ schema: {
2427
+ type: "object",
2428
+ properties: {
2429
+ reason: {
2430
+ type: "string"
2431
+ }
2432
+ }
2433
+ }
2434
+ }
2435
+ }
2436
+ },
2437
+ responses: {
2438
+ "200": {
2439
+ description: "Booking cancelado y reembolso procesado",
2440
+ content: {
2441
+ "application/json": {
2442
+ schema: {
2443
+ type: "object",
2444
+ properties: {
2445
+ refund: {
2446
+ type: "object",
2447
+ properties: {
2448
+ id: {
2449
+ type: "string",
2450
+ format: "uuid"
2451
+ },
2452
+ amount: {
2453
+ type: "integer"
2454
+ },
2455
+ status: {
2456
+ type: "string"
2457
+ }
2458
+ }
2459
+ }
2460
+ }
2461
+ }
2462
+ }
2463
+ }
2464
+ }
2465
+ }
2466
+ }
2467
+ },
2468
+ "/v1/payments": {
2469
+ post: {
2470
+ tags: [
2471
+ "Payments"
2472
+ ],
2473
+ summary: "Crear pago para un booking",
2474
+ description: "NOTA: Esta ruta usa /v1/payments (sin api/ prefix)",
2475
+ parameters: [
2476
+ {
2477
+ name: "idempotency-key",
2478
+ in: "header",
2479
+ schema: {
2480
+ type: "string"
2481
+ }
2482
+ }
2483
+ ],
2484
+ requestBody: {
2485
+ required: true,
2486
+ content: {
2487
+ "application/json": {
2488
+ schema: {
2489
+ type: "object",
2490
+ required: [
2491
+ "bookingId",
2492
+ "provider"
2493
+ ],
2494
+ properties: {
2495
+ bookingId: {
2496
+ type: "string",
2497
+ format: "uuid"
2498
+ },
2499
+ provider: {
2500
+ type: "string",
2501
+ enum: [
2502
+ "wompi",
2503
+ "epayco",
2504
+ "stripe",
2505
+ "paypal"
2506
+ ]
2507
+ },
2508
+ paymentMethod: {
2509
+ type: "string"
2510
+ },
2511
+ redirectUrl: {
2512
+ type: "string",
2513
+ format: "uri"
2514
+ },
2515
+ customerEmail: {
2516
+ type: "string",
2517
+ format: "email"
2518
+ }
2519
+ }
2520
+ }
2521
+ }
2522
+ }
2523
+ },
2524
+ responses: {
2525
+ "201": {
2526
+ description: "Pago creado",
2527
+ content: {
2528
+ "application/json": {
2529
+ schema: {
2530
+ type: "object",
2531
+ properties: {
2532
+ id: {
2533
+ type: "string",
2534
+ format: "uuid"
2535
+ },
2536
+ bookingId: {
2537
+ type: "string",
2538
+ format: "uuid"
2539
+ },
2540
+ provider: {
2541
+ type: "string"
2542
+ },
2543
+ amount: {
2544
+ type: "integer"
2545
+ },
2546
+ currency: {
2547
+ type: "string"
2548
+ },
2549
+ status: {
2550
+ type: "string",
2551
+ enum: [
2552
+ "pending",
2553
+ "authorized",
2554
+ "paid",
2555
+ "failed",
2556
+ "cancelled",
2557
+ "refunded"
2558
+ ]
2559
+ },
2560
+ checkoutUrl: {
2561
+ type: "string",
2562
+ format: "uri"
2563
+ },
2564
+ expiresAt: {
2565
+ type: "string",
2566
+ format: "date-time"
2567
+ },
2568
+ createdAt: {
2569
+ type: "string",
2570
+ format: "date-time"
2571
+ }
2572
+ }
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ }
2578
+ }
2579
+ },
2580
+ "/v1/payments/{id}": {
2581
+ get: {
2582
+ tags: [
2583
+ "Payments"
2584
+ ],
2585
+ summary: "Obtener pago por ID",
2586
+ description: "NOTA: Esta ruta usa /v1/payments (sin api/ prefix)",
2587
+ parameters: [
2588
+ {
2589
+ name: "id",
2590
+ in: "path",
2591
+ required: true,
2592
+ schema: {
2593
+ type: "string",
2594
+ format: "uuid"
2595
+ }
2596
+ }
2597
+ ],
2598
+ responses: {
2599
+ "200": {
2600
+ description: "Detalle del pago",
2601
+ content: {
2602
+ "application/json": {
2603
+ schema: {
2604
+ type: "object",
2605
+ properties: {
2606
+ id: {
2607
+ type: "string",
2608
+ format: "uuid"
2609
+ },
2610
+ bookingId: {
2611
+ type: "string",
2612
+ format: "uuid"
2613
+ },
2614
+ provider: {
2615
+ type: "string"
2616
+ },
2617
+ providerPaymentId: {
2618
+ type: "string"
2619
+ },
2620
+ amount: {
2621
+ type: "integer"
2622
+ },
2623
+ currency: {
2624
+ type: "string"
2625
+ },
2626
+ status: {
2627
+ type: "string"
2628
+ },
2629
+ paymentMethod: {
2630
+ type: "string"
2631
+ },
2632
+ checkoutUrl: {
2633
+ type: "string"
2634
+ },
2635
+ expiresAt: {
2636
+ type: "string",
2637
+ format: "date-time"
2638
+ },
2639
+ authorizedAt: {
2640
+ type: "string",
2641
+ format: "date-time"
2642
+ },
2643
+ paidAt: {
2644
+ type: "string",
2645
+ format: "date-time"
2646
+ },
2647
+ failedAt: {
2648
+ type: "string",
2649
+ format: "date-time"
2650
+ },
2651
+ failureReason: {
2652
+ type: "string"
2653
+ },
2654
+ createdAt: {
2655
+ type: "string",
2656
+ format: "date-time"
2657
+ },
2658
+ updatedAt: {
2659
+ type: "string",
2660
+ format: "date-time"
2661
+ }
2662
+ }
2663
+ }
2664
+ }
2665
+ }
2666
+ }
2667
+ }
2668
+ }
2669
+ },
2670
+ "/v1/payments/booking/{bookingId}": {
2671
+ get: {
2672
+ tags: [
2673
+ "Payments"
2674
+ ],
2675
+ summary: "Obtener pagos de un booking",
2676
+ description: "NOTA: Esta ruta usa /v1/payments (sin api/ prefix)",
2677
+ parameters: [
2678
+ {
2679
+ name: "bookingId",
2680
+ in: "path",
2681
+ required: true,
2682
+ schema: {
2683
+ type: "string",
2684
+ format: "uuid"
2685
+ }
2686
+ }
2687
+ ],
2688
+ responses: {
2689
+ "200": {
2690
+ description: "Lista de pagos del booking"
2691
+ }
2692
+ }
2693
+ }
2694
+ },
2695
+ "/v1/payments/refunds": {
2696
+ post: {
2697
+ tags: [
2698
+ "Payments"
2699
+ ],
2700
+ summary: "Crear reembolso manual",
2701
+ description: "NOTA: Esta ruta usa /v1/payments (sin api/ prefix)",
2702
+ requestBody: {
2703
+ required: true,
2704
+ content: {
2705
+ "application/json": {
2706
+ schema: {
2707
+ type: "object",
2708
+ required: [
2709
+ "paymentId",
2710
+ "amountCents"
2711
+ ],
2712
+ properties: {
2713
+ paymentId: {
2714
+ type: "string",
2715
+ format: "uuid"
2716
+ },
2717
+ amountCents: {
2718
+ type: "integer",
2719
+ minimum: 1
2720
+ },
2721
+ reason: {
2722
+ type: "string"
2723
+ }
2724
+ }
2725
+ }
2726
+ }
2727
+ }
2728
+ },
2729
+ responses: {
2730
+ "201": {
2731
+ description: "Reembolso creado",
2732
+ content: {
2733
+ "application/json": {
2734
+ schema: {
2735
+ type: "object",
2736
+ properties: {
2737
+ id: {
2738
+ type: "string",
2739
+ format: "uuid"
2740
+ },
2741
+ paymentId: {
2742
+ type: "string",
2743
+ format: "uuid"
2744
+ },
2745
+ amount: {
2746
+ type: "integer"
2747
+ },
2748
+ currency: {
2749
+ type: "string"
2750
+ },
2751
+ status: {
2752
+ type: "string"
2753
+ },
2754
+ reason: {
2755
+ type: "string"
2756
+ },
2757
+ requestedAt: {
2758
+ type: "string",
2759
+ format: "date-time"
2760
+ },
2761
+ createdAt: {
2762
+ type: "string",
2763
+ format: "date-time"
2764
+ }
2765
+ }
2766
+ }
2767
+ }
2768
+ }
2769
+ }
2770
+ }
2771
+ }
2772
+ },
2773
+ "/v1/payments/webhooks/{provider}": {
2774
+ post: {
2775
+ tags: [
2776
+ "Payments"
2777
+ ],
2778
+ summary: "Webhook de proveedor de pagos",
2779
+ description: "NOTA: Esta ruta usa /v1/payments (sin api/ prefix). Recibe webhooks de PayPal, Wompi, Epayco y Stripe",
2780
+ security: [],
2781
+ parameters: [
2782
+ {
2783
+ name: "provider",
2784
+ in: "path",
2785
+ required: true,
2786
+ schema: {
2787
+ type: "string",
2788
+ enum: [
2789
+ "paypal",
2790
+ "wompi",
2791
+ "epayco",
2792
+ "stripe"
2793
+ ]
2794
+ }
2795
+ },
2796
+ {
2797
+ name: "paypal-transmission-id",
2798
+ in: "header",
2799
+ schema: {
2800
+ type: "string"
2801
+ },
2802
+ description: "Requerido para PayPal"
2803
+ },
2804
+ {
2805
+ name: "paypal-transmission-sig",
2806
+ in: "header",
2807
+ schema: {
2808
+ type: "string"
2809
+ },
2810
+ description: "Requerido para PayPal"
2811
+ },
2812
+ {
2813
+ name: "x-signature",
2814
+ in: "header",
2815
+ schema: {
2816
+ type: "string"
2817
+ },
2818
+ description: "Requerido para Wompi/Epayco/Stripe"
2819
+ }
2820
+ ],
2821
+ requestBody: {
2822
+ required: true,
2823
+ content: {
2824
+ "application/json": {
2825
+ schema: {
2826
+ type: "object"
2827
+ }
2828
+ }
2829
+ }
2830
+ },
2831
+ responses: {
2832
+ "200": {
2833
+ description: "Webhook procesado",
2834
+ content: {
2835
+ "application/json": {
2836
+ schema: {
2837
+ type: "object",
2838
+ properties: {
2839
+ success: {
2840
+ type: "boolean"
2841
+ },
2842
+ error: {
2843
+ type: "string"
2844
+ }
2845
+ }
2846
+ }
2847
+ }
2848
+ }
2849
+ }
2850
+ }
2851
+ }
2852
+ },
2853
+ "/agents/resorts/{resortId}": {
2854
+ post: {
2855
+ tags: [
2856
+ "Agents"
2857
+ ],
2858
+ summary: "Crear acuerdo de agente con resort",
2859
+ parameters: [
2860
+ {
2861
+ name: "resortId",
2862
+ in: "path",
2863
+ required: true,
2864
+ schema: {
2865
+ type: "string",
2866
+ format: "uuid"
2867
+ }
2868
+ }
2869
+ ],
2870
+ requestBody: {
2871
+ required: true,
2872
+ content: {
2873
+ "application/json": {
2874
+ schema: {
2875
+ type: "object",
2876
+ required: [
2877
+ "userId",
2878
+ "commissionBps"
2879
+ ],
2880
+ properties: {
2881
+ userId: {
2882
+ type: "string",
2883
+ format: "uuid"
2884
+ },
2885
+ commissionBps: {
2886
+ type: "integer",
2887
+ minimum: 0,
2888
+ maximum: 1e4
2889
+ }
2890
+ }
2891
+ }
2892
+ }
2893
+ }
2894
+ },
2895
+ responses: {
2896
+ "201": {
2897
+ description: "Acuerdo creado"
2898
+ }
2899
+ }
2900
+ },
2901
+ get: {
2902
+ tags: [
2903
+ "Agents"
2904
+ ],
2905
+ summary: "Obtener agentes de un resort",
2906
+ parameters: [
2907
+ {
2908
+ name: "resortId",
2909
+ in: "path",
2910
+ required: true,
2911
+ schema: {
2912
+ type: "string",
2913
+ format: "uuid"
2914
+ }
2915
+ }
2916
+ ],
2917
+ responses: {
2918
+ "200": {
2919
+ description: "Lista de agentes"
2920
+ }
2921
+ }
2922
+ }
2923
+ },
2924
+ "/agents/resorts/{resortId}/users/{userId}": {
2925
+ patch: {
2926
+ tags: [
2927
+ "Agents"
2928
+ ],
2929
+ summary: "Actualizar comisi\xF3n de agente",
2930
+ parameters: [
2931
+ {
2932
+ name: "resortId",
2933
+ in: "path",
2934
+ required: true,
2935
+ schema: {
2936
+ type: "string",
2937
+ format: "uuid"
2938
+ }
2939
+ },
2940
+ {
2941
+ name: "userId",
2942
+ in: "path",
2943
+ required: true,
2944
+ schema: {
2945
+ type: "string",
2946
+ format: "uuid"
2947
+ }
2948
+ }
2949
+ ],
2950
+ requestBody: {
2951
+ required: true,
2952
+ content: {
2953
+ "application/json": {
2954
+ schema: {
2955
+ type: "object",
2956
+ required: [
2957
+ "commissionBps"
2958
+ ],
2959
+ properties: {
2960
+ commissionBps: {
2961
+ type: "integer",
2962
+ minimum: 0,
2963
+ maximum: 1e4
2964
+ }
2965
+ }
2966
+ }
2967
+ }
2968
+ }
2969
+ },
2970
+ responses: {
2971
+ "200": {
2972
+ description: "Comisi\xF3n actualizada"
2973
+ }
2974
+ }
2975
+ }
2976
+ },
2977
+ "/agents/commissions": {
2978
+ get: {
2979
+ tags: [
2980
+ "Agents"
2981
+ ],
2982
+ summary: "Obtener mis comisiones",
2983
+ responses: {
2984
+ "200": {
2985
+ description: "Lista de comisiones"
2986
+ }
2987
+ }
2988
+ }
2989
+ },
2990
+ "/agents/stats": {
2991
+ get: {
2992
+ tags: [
2993
+ "Agents"
2994
+ ],
2995
+ summary: "Obtener mis estad\xEDsticas",
2996
+ responses: {
2997
+ "200": {
2998
+ description: "Estad\xEDsticas"
2999
+ }
3000
+ }
3001
+ }
3002
+ },
3003
+ "/agents/profile": {
3004
+ get: {
3005
+ tags: [
3006
+ "Agents"
3007
+ ],
3008
+ summary: "Obtener mi perfil de agente",
3009
+ responses: {
3010
+ "200": {
3011
+ description: "Perfil"
3012
+ }
3013
+ }
3014
+ },
3015
+ post: {
3016
+ tags: [
3017
+ "Agents"
3018
+ ],
3019
+ summary: "Actualizar mi perfil de agente",
3020
+ requestBody: {
3021
+ content: {
3022
+ "application/json": {
3023
+ schema: {
3024
+ type: "object",
3025
+ properties: {
3026
+ bankName: {
3027
+ type: "string"
3028
+ },
3029
+ accountNumber: {
3030
+ type: "string"
3031
+ },
3032
+ accountType: {
3033
+ type: "string"
3034
+ },
3035
+ accountHolderName: {
3036
+ type: "string"
3037
+ },
3038
+ taxId: {
3039
+ type: "string"
3040
+ }
3041
+ }
3042
+ }
3043
+ }
3044
+ }
3045
+ },
3046
+ responses: {
3047
+ "200": {
3048
+ description: "Perfil actualizado"
3049
+ }
3050
+ }
3051
+ }
3052
+ },
3053
+ "/agents/referral-codes": {
3054
+ post: {
3055
+ tags: [
3056
+ "Agents"
3057
+ ],
3058
+ summary: "Crear c\xF3digo de referido",
3059
+ requestBody: {
3060
+ required: true,
3061
+ content: {
3062
+ "application/json": {
3063
+ schema: {
3064
+ type: "object",
3065
+ required: [
3066
+ "code"
3067
+ ],
3068
+ properties: {
3069
+ code: {
3070
+ type: "string"
3071
+ },
3072
+ codeType: {
3073
+ type: "string",
3074
+ enum: [
3075
+ "commission",
3076
+ "discount",
3077
+ "both"
3078
+ ]
3079
+ },
3080
+ discountType: {
3081
+ type: "string",
3082
+ enum: [
3083
+ "percentage",
3084
+ "fixed",
3085
+ "none"
3086
+ ]
3087
+ },
3088
+ discountValue: {
3089
+ type: "integer",
3090
+ minimum: 0
3091
+ },
3092
+ commissionOverrideBps: {
3093
+ type: "integer",
3094
+ minimum: 0,
3095
+ maximum: 1e4
3096
+ },
3097
+ usageLimit: {
3098
+ type: "integer",
3099
+ minimum: 1
3100
+ },
3101
+ expiresAt: {
3102
+ type: "string",
3103
+ format: "date-time"
3104
+ },
3105
+ description: {
3106
+ type: "string"
3107
+ },
3108
+ allowStacking: {
3109
+ type: "boolean"
3110
+ },
3111
+ minPurchaseCents: {
3112
+ type: "integer",
3113
+ minimum: 0
3114
+ },
3115
+ maxDiscountCents: {
3116
+ type: "integer",
3117
+ minimum: 0
3118
+ }
3119
+ }
3120
+ }
3121
+ }
3122
+ }
3123
+ },
3124
+ responses: {
3125
+ "201": {
3126
+ description: "C\xF3digo creado"
3127
+ }
3128
+ }
3129
+ },
3130
+ get: {
3131
+ tags: [
3132
+ "Agents"
3133
+ ],
3134
+ summary: "Obtener mis c\xF3digos de referido",
3135
+ responses: {
3136
+ "200": {
3137
+ description: "Lista de c\xF3digos"
3138
+ }
3139
+ }
3140
+ }
3141
+ },
3142
+ "/agents/referral-codes/{codeId}/toggle": {
3143
+ post: {
3144
+ tags: [
3145
+ "Agents"
3146
+ ],
3147
+ summary: "Activar/desactivar c\xF3digo de referido",
3148
+ parameters: [
3149
+ {
3150
+ name: "codeId",
3151
+ in: "path",
3152
+ required: true,
3153
+ schema: {
3154
+ type: "string",
3155
+ format: "uuid"
3156
+ }
3157
+ }
3158
+ ],
3159
+ requestBody: {
3160
+ required: true,
3161
+ content: {
3162
+ "application/json": {
3163
+ schema: {
3164
+ type: "object",
3165
+ required: [
3166
+ "isActive"
3167
+ ],
3168
+ properties: {
3169
+ isActive: {
3170
+ type: "boolean"
3171
+ }
3172
+ }
3173
+ }
3174
+ }
3175
+ }
3176
+ },
3177
+ responses: {
3178
+ "200": {
3179
+ description: "Estado actualizado"
3180
+ }
3181
+ }
3182
+ }
3183
+ },
3184
+ "/agents/referral-codes/{codeId}/restrictions": {
3185
+ post: {
3186
+ tags: [
3187
+ "Agents"
3188
+ ],
3189
+ summary: "Agregar restricci\xF3n a c\xF3digo",
3190
+ parameters: [
3191
+ {
3192
+ name: "codeId",
3193
+ in: "path",
3194
+ required: true,
3195
+ schema: {
3196
+ type: "string",
3197
+ format: "uuid"
3198
+ }
3199
+ }
3200
+ ],
3201
+ requestBody: {
3202
+ required: true,
3203
+ content: {
3204
+ "application/json": {
3205
+ schema: {
3206
+ type: "object",
3207
+ required: [
3208
+ "restrictionType"
3209
+ ],
3210
+ properties: {
3211
+ restrictionType: {
3212
+ type: "string",
3213
+ enum: [
3214
+ "experience",
3215
+ "category",
3216
+ "resort"
3217
+ ]
3218
+ },
3219
+ experienceId: {
3220
+ type: "string",
3221
+ format: "uuid"
3222
+ },
3223
+ categorySlug: {
3224
+ type: "string"
3225
+ },
3226
+ resortId: {
3227
+ type: "string",
3228
+ format: "uuid"
3229
+ }
3230
+ }
3231
+ }
3232
+ }
3233
+ }
3234
+ },
3235
+ responses: {
3236
+ "201": {
3237
+ description: "Restricci\xF3n agregada"
3238
+ }
3239
+ }
3240
+ },
3241
+ get: {
3242
+ tags: [
3243
+ "Agents"
3244
+ ],
3245
+ summary: "Obtener restricciones de c\xF3digo",
3246
+ parameters: [
3247
+ {
3248
+ name: "codeId",
3249
+ in: "path",
3250
+ required: true,
3251
+ schema: {
3252
+ type: "string",
3253
+ format: "uuid"
3254
+ }
3255
+ }
3256
+ ],
3257
+ responses: {
3258
+ "200": {
3259
+ description: "Restricciones"
3260
+ }
3261
+ }
3262
+ }
3263
+ },
3264
+ "/agents/restrictions/{restrictionId}": {
3265
+ delete: {
3266
+ tags: [
3267
+ "Agents"
3268
+ ],
3269
+ summary: "Eliminar restricci\xF3n",
3270
+ parameters: [
3271
+ {
3272
+ name: "restrictionId",
3273
+ in: "path",
3274
+ required: true,
3275
+ schema: {
3276
+ type: "string",
3277
+ format: "uuid"
3278
+ }
3279
+ }
3280
+ ],
3281
+ responses: {
3282
+ "204": {
3283
+ description: "Restricci\xF3n eliminada"
3284
+ }
3285
+ }
3286
+ }
3287
+ },
3288
+ "/agents/referral-codes/{codeId}/variants": {
3289
+ post: {
3290
+ tags: [
3291
+ "Agents"
3292
+ ],
3293
+ summary: "Crear variante de c\xF3digo (A/B testing)",
3294
+ parameters: [
3295
+ {
3296
+ name: "codeId",
3297
+ in: "path",
3298
+ required: true,
3299
+ schema: {
3300
+ type: "string",
3301
+ format: "uuid"
3302
+ }
3303
+ }
3304
+ ],
3305
+ requestBody: {
3306
+ required: true,
3307
+ content: {
3308
+ "application/json": {
3309
+ schema: {
3310
+ type: "object",
3311
+ required: [
3312
+ "variantName",
3313
+ "code"
3314
+ ],
3315
+ properties: {
3316
+ variantName: {
3317
+ type: "string"
3318
+ },
3319
+ code: {
3320
+ type: "string"
3321
+ },
3322
+ discountValue: {
3323
+ type: "integer",
3324
+ minimum: 0
3325
+ },
3326
+ commissionOverrideBps: {
3327
+ type: "integer",
3328
+ minimum: 0,
3329
+ maximum: 1e4
3330
+ }
3331
+ }
3332
+ }
3333
+ }
3334
+ }
3335
+ },
3336
+ responses: {
3337
+ "201": {
3338
+ description: "Variante creada"
3339
+ }
3340
+ }
3341
+ },
3342
+ get: {
3343
+ tags: [
3344
+ "Agents"
3345
+ ],
3346
+ summary: "Obtener variantes de c\xF3digo",
3347
+ parameters: [
3348
+ {
3349
+ name: "codeId",
3350
+ in: "path",
3351
+ required: true,
3352
+ schema: {
3353
+ type: "string",
3354
+ format: "uuid"
3355
+ }
3356
+ }
3357
+ ],
3358
+ responses: {
3359
+ "200": {
3360
+ description: "Variantes"
3361
+ }
3362
+ }
3363
+ }
3364
+ },
3365
+ "/agents/variants/{variantId}/toggle": {
3366
+ post: {
3367
+ tags: [
3368
+ "Agents"
3369
+ ],
3370
+ summary: "Activar/desactivar variante",
3371
+ parameters: [
3372
+ {
3373
+ name: "variantId",
3374
+ in: "path",
3375
+ required: true,
3376
+ schema: {
3377
+ type: "string",
3378
+ format: "uuid"
3379
+ }
3380
+ }
3381
+ ],
3382
+ requestBody: {
3383
+ required: true,
3384
+ content: {
3385
+ "application/json": {
3386
+ schema: {
3387
+ type: "object",
3388
+ required: [
3389
+ "isActive"
3390
+ ],
3391
+ properties: {
3392
+ isActive: {
3393
+ type: "boolean"
3394
+ }
3395
+ }
3396
+ }
3397
+ }
3398
+ }
3399
+ },
3400
+ responses: {
3401
+ "200": {
3402
+ description: "Estado actualizado"
3403
+ }
3404
+ }
3405
+ }
3406
+ },
3407
+ "/agents/analytics": {
24
3408
  get: {
25
- summary: "Listar experiencias",
26
- operationId: "listExperiences",
27
- security: [],
3409
+ tags: [
3410
+ "Agents"
3411
+ ],
3412
+ summary: "Obtener analytics de mis c\xF3digos",
3413
+ parameters: [
3414
+ {
3415
+ name: "codeId",
3416
+ in: "query",
3417
+ schema: {
3418
+ type: "string",
3419
+ format: "uuid"
3420
+ }
3421
+ }
3422
+ ],
3423
+ responses: {
3424
+ "200": {
3425
+ description: "Analytics"
3426
+ }
3427
+ }
3428
+ }
3429
+ },
3430
+ "/agents/referral-codes/{codeId}/variant-analytics": {
3431
+ get: {
3432
+ tags: [
3433
+ "Agents"
3434
+ ],
3435
+ summary: "Obtener analytics de variantes",
28
3436
  parameters: [
29
- { name: "q", in: "query", schema: { type: "string" } },
30
- { name: "categoryId", in: "query", schema: { type: "string", format: "uuid" } },
31
- { name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 100, default: 20 } },
32
- { name: "offset", in: "query", schema: { type: "integer", minimum: 0, default: 0 } }
3437
+ {
3438
+ name: "codeId",
3439
+ in: "path",
3440
+ required: true,
3441
+ schema: {
3442
+ type: "string",
3443
+ format: "uuid"
3444
+ }
3445
+ }
3446
+ ],
3447
+ responses: {
3448
+ "200": {
3449
+ description: "Analytics de variantes"
3450
+ }
3451
+ }
3452
+ }
3453
+ },
3454
+ "/api/v1/admin/dashboard": {
3455
+ get: {
3456
+ tags: [
3457
+ "Admin"
33
3458
  ],
3459
+ summary: "Obtener m\xE9tricas del dashboard",
34
3460
  responses: {
35
3461
  "200": {
36
- description: "OK",
3462
+ description: "M\xE9tricas del dashboard",
37
3463
  content: {
38
3464
  "application/json": {
39
3465
  schema: {
40
3466
  type: "object",
41
3467
  properties: {
42
- items: { type: "array", items: { $ref: "#/components/schemas/ExperienceSummary" } },
43
- total: { type: "integer" }
44
- },
45
- required: ["items", "total"]
3468
+ resorts: {
3469
+ type: "object",
3470
+ properties: {
3471
+ draft: {
3472
+ type: "integer"
3473
+ },
3474
+ under_review: {
3475
+ type: "integer"
3476
+ },
3477
+ approved: {
3478
+ type: "integer"
3479
+ },
3480
+ rejected: {
3481
+ type: "integer"
3482
+ },
3483
+ total: {
3484
+ type: "integer"
3485
+ }
3486
+ }
3487
+ },
3488
+ experiences: {
3489
+ type: "object",
3490
+ properties: {
3491
+ draft: {
3492
+ type: "integer"
3493
+ },
3494
+ under_review: {
3495
+ type: "integer"
3496
+ },
3497
+ active: {
3498
+ type: "integer"
3499
+ },
3500
+ rejected: {
3501
+ type: "integer"
3502
+ },
3503
+ total: {
3504
+ type: "integer"
3505
+ }
3506
+ }
3507
+ },
3508
+ recentActivity: {
3509
+ type: "array",
3510
+ items: {
3511
+ type: "object",
3512
+ properties: {
3513
+ action: {
3514
+ type: "string"
3515
+ },
3516
+ entity_type: {
3517
+ type: "string"
3518
+ },
3519
+ entity_id: {
3520
+ type: "string",
3521
+ format: "uuid"
3522
+ },
3523
+ after: {
3524
+ type: "object"
3525
+ },
3526
+ created_at: {
3527
+ type: "string",
3528
+ format: "date-time"
3529
+ },
3530
+ admin_email: {
3531
+ type: "string",
3532
+ format: "email"
3533
+ }
3534
+ }
3535
+ }
3536
+ },
3537
+ timestamp: {
3538
+ type: "string",
3539
+ format: "date-time"
3540
+ }
3541
+ }
46
3542
  }
47
3543
  }
48
3544
  }
49
3545
  },
50
- "400": { $ref: "#/components/responses/BadRequest" }
3546
+ "401": {
3547
+ description: "No autorizado"
3548
+ },
3549
+ "403": {
3550
+ description: "Acceso denegado - Se requiere rol de administrador"
3551
+ }
51
3552
  }
52
3553
  }
53
3554
  },
54
- "/experiences/{experienceId}": {
3555
+ "/api/v1/admin/resorts/review": {
55
3556
  get: {
56
- summary: "Detalle de experiencia",
57
- operationId: "getExperience",
58
- security: [],
3557
+ tags: [
3558
+ "Admin"
3559
+ ],
3560
+ summary: "Obtener resorts pendientes de revisi\xF3n",
59
3561
  parameters: [
60
- { name: "experienceId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
3562
+ {
3563
+ name: "page",
3564
+ in: "query",
3565
+ required: false,
3566
+ schema: {
3567
+ type: "integer",
3568
+ minimum: 1,
3569
+ default: 1
3570
+ }
3571
+ },
3572
+ {
3573
+ name: "limit",
3574
+ in: "query",
3575
+ required: false,
3576
+ schema: {
3577
+ type: "integer",
3578
+ minimum: 1,
3579
+ maximum: 100,
3580
+ default: 10
3581
+ }
3582
+ },
3583
+ {
3584
+ name: "search",
3585
+ in: "query",
3586
+ required: false,
3587
+ schema: {
3588
+ type: "string"
3589
+ }
3590
+ }
61
3591
  ],
62
3592
  responses: {
63
3593
  "200": {
64
- description: "OK",
65
- content: { "application/json": { schema: { $ref: "#/components/schemas/Experience" } } }
3594
+ description: "Resorts para revisar",
3595
+ content: {
3596
+ "application/json": {
3597
+ schema: {
3598
+ type: "object",
3599
+ properties: {
3600
+ data: {
3601
+ type: "array",
3602
+ items: {
3603
+ type: "object",
3604
+ properties: {
3605
+ id: {
3606
+ type: "string",
3607
+ format: "uuid"
3608
+ },
3609
+ name: {
3610
+ type: "string"
3611
+ },
3612
+ description: {
3613
+ type: ["string", "null"]
3614
+ },
3615
+ contact_email: {
3616
+ type: ["string", "null"],
3617
+ format: "email"
3618
+ },
3619
+ contact_phone: {
3620
+ type: ["string", "null"]
3621
+ },
3622
+ address_line: {
3623
+ type: ["string", "null"]
3624
+ },
3625
+ city: {
3626
+ type: ["string", "null"]
3627
+ },
3628
+ country: {
3629
+ type: ["string", "null"]
3630
+ },
3631
+ latitude: {
3632
+ type: ["number", "null"]
3633
+ },
3634
+ longitude: {
3635
+ type: ["number", "null"]
3636
+ },
3637
+ owner_user_id: {
3638
+ type: ["string", "null"],
3639
+ format: "uuid"
3640
+ },
3641
+ is_active: {
3642
+ type: "boolean"
3643
+ },
3644
+ status: {
3645
+ type: "string",
3646
+ enum: [
3647
+ "draft",
3648
+ "under_review",
3649
+ "approved",
3650
+ "rejected"
3651
+ ]
3652
+ },
3653
+ approved_by: {
3654
+ type: ["string", "null"],
3655
+ format: "uuid"
3656
+ },
3657
+ approved_at: {
3658
+ type: ["string", "null"],
3659
+ format: "date-time"
3660
+ },
3661
+ rejection_reason: {
3662
+ type: ["string", "null"]
3663
+ },
3664
+ created_at: {
3665
+ type: "string",
3666
+ format: "date-time"
3667
+ },
3668
+ updated_at: {
3669
+ type: "string",
3670
+ format: "date-time"
3671
+ },
3672
+ owner_email: {
3673
+ type: ["string", "null"],
3674
+ format: "email"
3675
+ },
3676
+ owner_name: {
3677
+ type: ["string", "null"]
3678
+ }
3679
+ }
3680
+ }
3681
+ },
3682
+ meta: {
3683
+ type: "object",
3684
+ properties: {
3685
+ total: {
3686
+ type: "integer"
3687
+ },
3688
+ page: {
3689
+ type: "integer"
3690
+ },
3691
+ limit: {
3692
+ type: "integer"
3693
+ },
3694
+ totalPages: {
3695
+ type: "integer"
3696
+ },
3697
+ hasNextPage: {
3698
+ type: "boolean"
3699
+ },
3700
+ hasPreviousPage: {
3701
+ type: "boolean"
3702
+ }
3703
+ }
3704
+ }
3705
+ }
3706
+ }
3707
+ }
3708
+ }
66
3709
  },
67
- "400": { $ref: "#/components/responses/BadRequest" },
68
- "404": { $ref: "#/components/responses/NotFound" }
3710
+ "401": {
3711
+ description: "No autorizado"
3712
+ },
3713
+ "403": {
3714
+ description: "Acceso denegado - Se requiere rol de administrador"
3715
+ }
69
3716
  }
70
3717
  }
71
3718
  },
72
- "/availability": {
73
- get: {
74
- summary: "Consultar disponibilidad por experiencia y fecha",
75
- operationId: "getAvailability",
76
- security: [],
3719
+ "/api/v1/admin/resorts/{id}/approve": {
3720
+ post: {
3721
+ tags: [
3722
+ "Admin"
3723
+ ],
3724
+ summary: "Aprobar resort",
77
3725
  parameters: [
78
- { name: "experienceId", in: "query", required: true, schema: { type: "string", format: "uuid" } },
79
- { name: "date", in: "query", required: true, schema: { type: "string", format: "date" } }
3726
+ {
3727
+ name: "id",
3728
+ in: "path",
3729
+ required: true,
3730
+ schema: {
3731
+ type: "string",
3732
+ format: "uuid"
3733
+ }
3734
+ }
80
3735
  ],
3736
+ requestBody: {
3737
+ content: {
3738
+ "application/json": {
3739
+ schema: {
3740
+ type: "object",
3741
+ properties: {
3742
+ notes: {
3743
+ type: "string",
3744
+ maxLength: 500
3745
+ }
3746
+ }
3747
+ }
3748
+ }
3749
+ }
3750
+ },
81
3751
  responses: {
82
3752
  "200": {
83
- description: "OK",
3753
+ description: "Resort aprobado",
84
3754
  content: {
85
3755
  "application/json": {
86
- schema: { type: "array", items: { $ref: "#/components/schemas/AvailabilitySlot" } }
3756
+ schema: {
3757
+ type: "object",
3758
+ properties: {
3759
+ id: {
3760
+ type: "string",
3761
+ format: "uuid"
3762
+ },
3763
+ name: {
3764
+ type: "string"
3765
+ },
3766
+ description: {
3767
+ type: ["string", "null"]
3768
+ },
3769
+ contact_email: {
3770
+ type: ["string", "null"],
3771
+ format: "email"
3772
+ },
3773
+ contact_phone: {
3774
+ type: ["string", "null"]
3775
+ },
3776
+ address_line: {
3777
+ type: ["string", "null"]
3778
+ },
3779
+ city: {
3780
+ type: ["string", "null"]
3781
+ },
3782
+ country: {
3783
+ type: ["string", "null"]
3784
+ },
3785
+ latitude: {
3786
+ type: ["number", "null"]
3787
+ },
3788
+ longitude: {
3789
+ type: ["number", "null"]
3790
+ },
3791
+ owner_user_id: {
3792
+ type: ["string", "null"],
3793
+ format: "uuid"
3794
+ },
3795
+ is_active: {
3796
+ type: "boolean"
3797
+ },
3798
+ status: {
3799
+ type: "string",
3800
+ enum: [
3801
+ "approved"
3802
+ ]
3803
+ },
3804
+ approved_by: {
3805
+ type: "string",
3806
+ format: "uuid"
3807
+ },
3808
+ approved_at: {
3809
+ type: "string",
3810
+ format: "date-time"
3811
+ },
3812
+ rejection_reason: {
3813
+ type: ["string", "null"]
3814
+ },
3815
+ created_at: {
3816
+ type: "string",
3817
+ format: "date-time"
3818
+ },
3819
+ updated_at: {
3820
+ type: "string",
3821
+ format: "date-time"
3822
+ }
3823
+ }
3824
+ }
87
3825
  }
88
3826
  }
89
3827
  },
90
- "400": { $ref: "#/components/responses/BadRequest" }
3828
+ "400": {
3829
+ description: "Solo resorts en revisi\xF3n pueden ser aprobados"
3830
+ },
3831
+ "401": {
3832
+ description: "No autorizado"
3833
+ },
3834
+ "403": {
3835
+ description: "Acceso denegado - Se requiere rol de administrador"
3836
+ },
3837
+ "404": {
3838
+ description: "Resort no encontrado"
3839
+ }
91
3840
  }
92
3841
  }
93
3842
  },
94
- "/bookings": {
3843
+ "/api/v1/admin/resorts/{id}/reject": {
95
3844
  post: {
96
- summary: "Crear booking (pending + lock de inventario)",
97
- operationId: "createBooking",
3845
+ tags: [
3846
+ "Admin"
3847
+ ],
3848
+ summary: "Rechazar resort",
98
3849
  parameters: [
99
3850
  {
100
- name: "Idempotency-Key",
101
- in: "header",
3851
+ name: "id",
3852
+ in: "path",
102
3853
  required: true,
103
- description: "Requerido para evitar duplicados en creaci\xF3n.",
104
- schema: { type: "string", maxLength: 128 }
3854
+ schema: {
3855
+ type: "string",
3856
+ format: "uuid"
3857
+ }
105
3858
  }
106
3859
  ],
107
3860
  requestBody: {
108
3861
  required: true,
109
- content: { "application/json": { schema: { $ref: "#/components/schemas/CreateBookingInput" } } }
3862
+ content: {
3863
+ "application/json": {
3864
+ schema: {
3865
+ type: "object",
3866
+ required: [
3867
+ "rejection_reason"
3868
+ ],
3869
+ properties: {
3870
+ rejection_reason: {
3871
+ type: "string",
3872
+ maxLength: 500
3873
+ }
3874
+ }
3875
+ }
3876
+ }
3877
+ }
110
3878
  },
111
3879
  responses: {
112
- "201": {
113
- description: "Creado",
114
- headers: {
115
- Location: { schema: { type: "string" }, description: "URL del booking creado." }
116
- },
117
- content: { "application/json": { schema: { $ref: "#/components/schemas/Booking" } } }
3880
+ "200": {
3881
+ description: "Resort rechazado",
3882
+ content: {
3883
+ "application/json": {
3884
+ schema: {
3885
+ type: "object",
3886
+ properties: {
3887
+ id: {
3888
+ type: "string",
3889
+ format: "uuid"
3890
+ },
3891
+ name: {
3892
+ type: "string"
3893
+ },
3894
+ description: {
3895
+ type: ["string", "null"]
3896
+ },
3897
+ contact_email: {
3898
+ type: ["string", "null"],
3899
+ format: "email"
3900
+ },
3901
+ contact_phone: {
3902
+ type: ["string", "null"]
3903
+ },
3904
+ address_line: {
3905
+ type: ["string", "null"]
3906
+ },
3907
+ city: {
3908
+ type: ["string", "null"]
3909
+ },
3910
+ country: {
3911
+ type: ["string", "null"]
3912
+ },
3913
+ latitude: {
3914
+ type: ["number", "null"]
3915
+ },
3916
+ longitude: {
3917
+ type: ["number", "null"]
3918
+ },
3919
+ owner_user_id: {
3920
+ type: ["string", "null"],
3921
+ format: "uuid"
3922
+ },
3923
+ is_active: {
3924
+ type: "boolean"
3925
+ },
3926
+ status: {
3927
+ type: "string",
3928
+ enum: [
3929
+ "rejected"
3930
+ ]
3931
+ },
3932
+ approved_by: {
3933
+ type: "string",
3934
+ format: "uuid"
3935
+ },
3936
+ approved_at: {
3937
+ type: "string",
3938
+ format: "date-time"
3939
+ },
3940
+ rejection_reason: {
3941
+ type: "string"
3942
+ },
3943
+ created_at: {
3944
+ type: "string",
3945
+ format: "date-time"
3946
+ },
3947
+ updated_at: {
3948
+ type: "string",
3949
+ format: "date-time"
3950
+ }
3951
+ }
3952
+ }
3953
+ }
3954
+ }
3955
+ },
3956
+ "400": {
3957
+ description: "Solo resorts en revisi\xF3n pueden ser rechazados"
118
3958
  },
119
- "400": { $ref: "#/components/responses/BadRequest" },
120
- "409": { $ref: "#/components/responses/Conflict" },
121
- "422": { $ref: "#/components/responses/UnprocessableEntity" }
3959
+ "401": {
3960
+ description: "No autorizado"
3961
+ },
3962
+ "403": {
3963
+ description: "Acceso denegado - Se requiere rol de administrador"
3964
+ },
3965
+ "404": {
3966
+ description: "Resort no encontrado"
3967
+ }
122
3968
  }
123
3969
  }
124
3970
  },
125
- "/bookings/{bookingId}": {
3971
+ "/api/v1/admin/experiences/review": {
126
3972
  get: {
127
- summary: "Obtener booking por ID",
128
- operationId: "getBooking",
3973
+ tags: [
3974
+ "Admin"
3975
+ ],
3976
+ summary: "Obtener experiencias pendientes de revisi\xF3n",
3977
+ parameters: [
3978
+ {
3979
+ name: "page",
3980
+ in: "query",
3981
+ required: false,
3982
+ schema: {
3983
+ type: "integer",
3984
+ minimum: 1,
3985
+ default: 1
3986
+ }
3987
+ },
3988
+ {
3989
+ name: "limit",
3990
+ in: "query",
3991
+ required: false,
3992
+ schema: {
3993
+ type: "integer",
3994
+ minimum: 1,
3995
+ maximum: 100,
3996
+ default: 10
3997
+ }
3998
+ },
3999
+ {
4000
+ name: "search",
4001
+ in: "query",
4002
+ required: false,
4003
+ schema: {
4004
+ type: "string"
4005
+ }
4006
+ }
4007
+ ],
4008
+ responses: {
4009
+ "200": {
4010
+ description: "Experiencias para revisar",
4011
+ content: {
4012
+ "application/json": {
4013
+ schema: {
4014
+ type: "object",
4015
+ properties: {
4016
+ data: {
4017
+ type: "array",
4018
+ items: {
4019
+ type: "object",
4020
+ properties: {
4021
+ id: {
4022
+ type: "string",
4023
+ format: "uuid"
4024
+ },
4025
+ resort_id: {
4026
+ type: "string",
4027
+ format: "uuid"
4028
+ },
4029
+ title: {
4030
+ type: "string"
4031
+ },
4032
+ slug: {
4033
+ type: "string"
4034
+ },
4035
+ description: {
4036
+ type: ["string", "null"]
4037
+ },
4038
+ category: {
4039
+ type: "string",
4040
+ enum: [
4041
+ "islands",
4042
+ "nautical",
4043
+ "city_tour"
4044
+ ]
4045
+ },
4046
+ price_cents: {
4047
+ type: "integer"
4048
+ },
4049
+ currency: {
4050
+ type: "string"
4051
+ },
4052
+ includes: {
4053
+ type: ["string", "null"]
4054
+ },
4055
+ excludes: {
4056
+ type: ["string", "null"]
4057
+ },
4058
+ main_image_url: {
4059
+ type: ["string", "null"]
4060
+ },
4061
+ status: {
4062
+ type: "string",
4063
+ enum: [
4064
+ "draft",
4065
+ "under_review",
4066
+ "active",
4067
+ "rejected"
4068
+ ]
4069
+ },
4070
+ rating_avg: {
4071
+ type: "number"
4072
+ },
4073
+ rating_count: {
4074
+ type: "integer"
4075
+ },
4076
+ approved_by: {
4077
+ type: ["string", "null"],
4078
+ format: "uuid"
4079
+ },
4080
+ approved_at: {
4081
+ type: ["string", "null"],
4082
+ format: "date-time"
4083
+ },
4084
+ rejection_reason: {
4085
+ type: ["string", "null"]
4086
+ },
4087
+ created_at: {
4088
+ type: "string",
4089
+ format: "date-time"
4090
+ },
4091
+ updated_at: {
4092
+ type: "string",
4093
+ format: "date-time"
4094
+ },
4095
+ resort_name: {
4096
+ type: ["string", "null"]
4097
+ },
4098
+ resort_city: {
4099
+ type: ["string", "null"]
4100
+ }
4101
+ }
4102
+ }
4103
+ },
4104
+ meta: {
4105
+ type: "object",
4106
+ properties: {
4107
+ total: {
4108
+ type: "integer"
4109
+ },
4110
+ page: {
4111
+ type: "integer"
4112
+ },
4113
+ limit: {
4114
+ type: "integer"
4115
+ },
4116
+ totalPages: {
4117
+ type: "integer"
4118
+ },
4119
+ hasNextPage: {
4120
+ type: "boolean"
4121
+ },
4122
+ hasPreviousPage: {
4123
+ type: "boolean"
4124
+ }
4125
+ }
4126
+ }
4127
+ }
4128
+ }
4129
+ }
4130
+ }
4131
+ },
4132
+ "401": {
4133
+ description: "No autorizado"
4134
+ },
4135
+ "403": {
4136
+ description: "Acceso denegado - Se requiere rol de administrador"
4137
+ }
4138
+ }
4139
+ }
4140
+ },
4141
+ "/api/v1/admin/experiences/{id}/approve": {
4142
+ post: {
4143
+ tags: [
4144
+ "Admin"
4145
+ ],
4146
+ summary: "Aprobar experiencia",
129
4147
  parameters: [
130
- { name: "bookingId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
4148
+ {
4149
+ name: "id",
4150
+ in: "path",
4151
+ required: true,
4152
+ schema: {
4153
+ type: "string",
4154
+ format: "uuid"
4155
+ }
4156
+ }
131
4157
  ],
4158
+ requestBody: {
4159
+ content: {
4160
+ "application/json": {
4161
+ schema: {
4162
+ type: "object",
4163
+ properties: {
4164
+ notes: {
4165
+ type: "string",
4166
+ maxLength: 500
4167
+ }
4168
+ }
4169
+ }
4170
+ }
4171
+ }
4172
+ },
132
4173
  responses: {
133
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/Booking" } } } },
134
- "400": { $ref: "#/components/responses/BadRequest" },
135
- "404": { $ref: "#/components/responses/NotFound" }
4174
+ "200": {
4175
+ description: "Experiencia aprobada",
4176
+ content: {
4177
+ "application/json": {
4178
+ schema: {
4179
+ type: "object",
4180
+ properties: {
4181
+ id: {
4182
+ type: "string",
4183
+ format: "uuid"
4184
+ },
4185
+ resort_id: {
4186
+ type: "string",
4187
+ format: "uuid"
4188
+ },
4189
+ title: {
4190
+ type: "string"
4191
+ },
4192
+ slug: {
4193
+ type: "string"
4194
+ },
4195
+ description: {
4196
+ type: ["string", "null"]
4197
+ },
4198
+ category: {
4199
+ type: "string",
4200
+ enum: [
4201
+ "islands",
4202
+ "nautical",
4203
+ "city_tour"
4204
+ ]
4205
+ },
4206
+ price_cents: {
4207
+ type: "integer"
4208
+ },
4209
+ currency: {
4210
+ type: "string"
4211
+ },
4212
+ includes: {
4213
+ type: ["string", "null"]
4214
+ },
4215
+ excludes: {
4216
+ type: ["string", "null"]
4217
+ },
4218
+ main_image_url: {
4219
+ type: ["string", "null"]
4220
+ },
4221
+ status: {
4222
+ type: "string",
4223
+ enum: [
4224
+ "active"
4225
+ ]
4226
+ },
4227
+ rating_avg: {
4228
+ type: "number"
4229
+ },
4230
+ rating_count: {
4231
+ type: "integer"
4232
+ },
4233
+ approved_by: {
4234
+ type: "string",
4235
+ format: "uuid"
4236
+ },
4237
+ approved_at: {
4238
+ type: "string",
4239
+ format: "date-time"
4240
+ },
4241
+ rejection_reason: {
4242
+ type: ["string", "null"]
4243
+ },
4244
+ created_at: {
4245
+ type: "string",
4246
+ format: "date-time"
4247
+ },
4248
+ updated_at: {
4249
+ type: "string",
4250
+ format: "date-time"
4251
+ }
4252
+ }
4253
+ }
4254
+ }
4255
+ }
4256
+ },
4257
+ "400": {
4258
+ description: "Solo experiencias en revisi\xF3n pueden ser aprobadas"
4259
+ },
4260
+ "401": {
4261
+ description: "No autorizado"
4262
+ },
4263
+ "403": {
4264
+ description: "Acceso denegado - Se requiere rol de administrador"
4265
+ },
4266
+ "404": {
4267
+ description: "Experiencia no encontrada"
4268
+ }
136
4269
  }
137
4270
  }
138
4271
  },
139
- "/webhooks/payments/{provider}": {
4272
+ "/api/v1/admin/experiences/{id}/reject": {
140
4273
  post: {
141
- summary: "Webhook de pagos (firma validada por API)",
142
- operationId: "paymentsWebhook",
143
- security: [],
4274
+ tags: [
4275
+ "Admin"
4276
+ ],
4277
+ summary: "Rechazar experiencia",
144
4278
  parameters: [
145
- { name: "provider", in: "path", required: true, schema: { type: "string", enum: ["wompi", "epayco", "other"] } }
4279
+ {
4280
+ name: "id",
4281
+ in: "path",
4282
+ required: true,
4283
+ schema: {
4284
+ type: "string",
4285
+ format: "uuid"
4286
+ }
4287
+ }
146
4288
  ],
147
4289
  requestBody: {
148
4290
  required: true,
149
4291
  content: {
150
- "application/json": { schema: { $ref: "#/components/schemas/PaymentWebhookPayload" } }
4292
+ "application/json": {
4293
+ schema: {
4294
+ type: "object",
4295
+ required: [
4296
+ "rejection_reason"
4297
+ ],
4298
+ properties: {
4299
+ rejection_reason: {
4300
+ type: "string",
4301
+ maxLength: 500
4302
+ }
4303
+ }
4304
+ }
4305
+ }
151
4306
  }
152
4307
  },
153
4308
  responses: {
154
- "202": { description: "Aceptado para procesamiento as\xEDncrono" },
155
- "400": { $ref: "#/components/responses/BadRequest" },
156
- "401": { $ref: "#/components/responses/Unauthorized" }
4309
+ "200": {
4310
+ description: "Experiencia rechazada",
4311
+ content: {
4312
+ "application/json": {
4313
+ schema: {
4314
+ type: "object",
4315
+ properties: {
4316
+ id: {
4317
+ type: "string",
4318
+ format: "uuid"
4319
+ },
4320
+ resort_id: {
4321
+ type: "string",
4322
+ format: "uuid"
4323
+ },
4324
+ title: {
4325
+ type: "string"
4326
+ },
4327
+ slug: {
4328
+ type: "string"
4329
+ },
4330
+ description: {
4331
+ type: ["string", "null"]
4332
+ },
4333
+ category: {
4334
+ type: "string",
4335
+ enum: [
4336
+ "islands",
4337
+ "nautical",
4338
+ "city_tour"
4339
+ ]
4340
+ },
4341
+ price_cents: {
4342
+ type: "integer"
4343
+ },
4344
+ currency: {
4345
+ type: "string"
4346
+ },
4347
+ includes: {
4348
+ type: ["string", "null"]
4349
+ },
4350
+ excludes: {
4351
+ type: ["string", "null"]
4352
+ },
4353
+ main_image_url: {
4354
+ type: ["string", "null"]
4355
+ },
4356
+ status: {
4357
+ type: "string",
4358
+ enum: [
4359
+ "rejected"
4360
+ ]
4361
+ },
4362
+ rating_avg: {
4363
+ type: "number"
4364
+ },
4365
+ rating_count: {
4366
+ type: "integer"
4367
+ },
4368
+ approved_by: {
4369
+ type: "string",
4370
+ format: "uuid"
4371
+ },
4372
+ approved_at: {
4373
+ type: "string",
4374
+ format: "date-time"
4375
+ },
4376
+ rejection_reason: {
4377
+ type: "string"
4378
+ },
4379
+ created_at: {
4380
+ type: "string",
4381
+ format: "date-time"
4382
+ },
4383
+ updated_at: {
4384
+ type: "string",
4385
+ format: "date-time"
4386
+ }
4387
+ }
4388
+ }
4389
+ }
4390
+ }
4391
+ },
4392
+ "400": {
4393
+ description: "Solo experiencias en revisi\xF3n pueden ser rechazadas"
4394
+ },
4395
+ "401": {
4396
+ description: "No autorizado"
4397
+ },
4398
+ "403": {
4399
+ description: "Acceso denegado - Se requiere rol de administrador"
4400
+ },
4401
+ "404": {
4402
+ description: "Experiencia no encontrada"
4403
+ }
4404
+ }
4405
+ }
4406
+ },
4407
+ "/api/v1/admin/audit-logs": {
4408
+ get: {
4409
+ tags: [
4410
+ "Admin"
4411
+ ],
4412
+ summary: "Obtener logs de auditor\xEDa",
4413
+ parameters: [
4414
+ {
4415
+ name: "page",
4416
+ in: "query",
4417
+ required: false,
4418
+ schema: {
4419
+ type: "integer",
4420
+ minimum: 1,
4421
+ default: 1
4422
+ }
4423
+ },
4424
+ {
4425
+ name: "limit",
4426
+ in: "query",
4427
+ required: false,
4428
+ schema: {
4429
+ type: "integer",
4430
+ minimum: 1,
4431
+ maximum: 100,
4432
+ default: 20
4433
+ }
4434
+ },
4435
+ {
4436
+ name: "search",
4437
+ in: "query",
4438
+ required: false,
4439
+ schema: {
4440
+ type: "string"
4441
+ }
4442
+ }
4443
+ ],
4444
+ responses: {
4445
+ "200": {
4446
+ description: "Logs de auditor\xEDa",
4447
+ content: {
4448
+ "application/json": {
4449
+ schema: {
4450
+ type: "object",
4451
+ properties: {
4452
+ data: {
4453
+ type: "array",
4454
+ items: {
4455
+ type: "object",
4456
+ properties: {
4457
+ id: {
4458
+ type: "string",
4459
+ format: "uuid"
4460
+ },
4461
+ actor_user_id: {
4462
+ type: "string",
4463
+ format: "uuid"
4464
+ },
4465
+ actor_role: {
4466
+ type: "string"
4467
+ },
4468
+ action: {
4469
+ type: "string"
4470
+ },
4471
+ entity_type: {
4472
+ type: "string"
4473
+ },
4474
+ entity_id: {
4475
+ type: "string",
4476
+ format: "uuid"
4477
+ },
4478
+ before: {
4479
+ type: "object"
4480
+ },
4481
+ after: {
4482
+ type: "object"
4483
+ },
4484
+ created_at: {
4485
+ type: "string",
4486
+ format: "date-time"
4487
+ },
4488
+ actor_email: {
4489
+ type: ["string", "null"],
4490
+ format: "email"
4491
+ },
4492
+ actor_name: {
4493
+ type: ["string", "null"]
4494
+ }
4495
+ }
4496
+ }
4497
+ },
4498
+ meta: {
4499
+ type: "object",
4500
+ properties: {
4501
+ total: {
4502
+ type: "integer"
4503
+ },
4504
+ page: {
4505
+ type: "integer"
4506
+ },
4507
+ limit: {
4508
+ type: "integer"
4509
+ },
4510
+ totalPages: {
4511
+ type: "integer"
4512
+ },
4513
+ hasNextPage: {
4514
+ type: "boolean"
4515
+ },
4516
+ hasPreviousPage: {
4517
+ type: "boolean"
4518
+ }
4519
+ }
4520
+ }
4521
+ }
4522
+ }
4523
+ }
4524
+ }
4525
+ },
4526
+ "401": {
4527
+ description: "No autorizado"
4528
+ },
4529
+ "403": {
4530
+ description: "Acceso denegado - Se requiere rol de administrador"
4531
+ }
157
4532
  }
158
4533
  }
159
4534
  }
@@ -165,147 +4540,6 @@ var livex_v1_default = {
165
4540
  scheme: "bearer",
166
4541
  bearerFormat: "JWT"
167
4542
  }
168
- },
169
- schemas: {
170
- UUID: { type: "string", format: "uuid" },
171
- Money: {
172
- type: "object",
173
- properties: {
174
- amount: { type: "integer", minimum: 0, description: "Centavos" },
175
- currency: { type: "string", minLength: 3, maxLength: 3, example: "COP" }
176
- },
177
- required: ["amount", "currency"],
178
- additionalProperties: false
179
- },
180
- ExperienceSummary: {
181
- type: "object",
182
- properties: {
183
- id: { $ref: "#/components/schemas/UUID" },
184
- title: { type: "string" },
185
- coverImageUrl: { type: "string", format: "uri" },
186
- city: { type: "string" },
187
- country: { type: "string" },
188
- ratingAvg: { type: "number", minimum: 0, maximum: 5 },
189
- ratingCount: { type: "integer", minimum: 0 },
190
- fromPrice: { $ref: "#/components/schemas/Money" }
191
- },
192
- required: ["id", "title", "fromPrice"]
193
- },
194
- Experience: {
195
- allOf: [
196
- { $ref: "#/components/schemas/ExperienceSummary" },
197
- {
198
- type: "object",
199
- properties: {
200
- description: { type: "string" },
201
- includes: { type: "array", items: { type: "string" } },
202
- notIncludes: { type: "array", items: { type: "string" } },
203
- images: { type: "array", items: { type: "string", format: "uri" } },
204
- status: { type: "string", enum: ["draft", "under_review", "active", "rejected"] }
205
- },
206
- required: ["status"]
207
- }
208
- ]
209
- },
210
- AvailabilitySlot: {
211
- type: "object",
212
- properties: {
213
- slotId: { $ref: "#/components/schemas/UUID" },
214
- start: { type: "string", format: "date-time" },
215
- end: { type: "string", format: "date-time" },
216
- capacity: { type: "integer", minimum: 1 },
217
- remaining: { type: "integer", minimum: 0 },
218
- price: { $ref: "#/components/schemas/Money" }
219
- },
220
- required: ["slotId", "start", "end", "capacity", "remaining", "price"]
221
- },
222
- CreateBookingInput: {
223
- type: "object",
224
- properties: {
225
- experienceId: { $ref: "#/components/schemas/UUID" },
226
- slotId: { $ref: "#/components/schemas/UUID" },
227
- quantity: { type: "integer", minimum: 1 },
228
- buyer: {
229
- type: "object",
230
- properties: {
231
- fullName: { type: "string" },
232
- email: { type: "string", format: "email" },
233
- phone: { type: "string" }
234
- },
235
- required: ["fullName", "email"]
236
- },
237
- payment: {
238
- type: "object",
239
- properties: {
240
- method: { type: "string", enum: ["card", "pse", "cash", "other"] },
241
- provider: { type: "string", enum: ["wompi", "epayco", "other"] }
242
- },
243
- required: ["method", "provider"]
244
- }
245
- },
246
- required: ["experienceId", "slotId", "quantity", "buyer", "payment"],
247
- additionalProperties: false
248
- },
249
- Booking: {
250
- type: "object",
251
- properties: {
252
- id: { $ref: "#/components/schemas/UUID" },
253
- status: { type: "string", enum: ["pending", "confirmed", "completed", "cancelled", "refunded", "expired"] },
254
- experienceId: { $ref: "#/components/schemas/UUID" },
255
- slotId: { $ref: "#/components/schemas/UUID" },
256
- quantity: { type: "integer", minimum: 1 },
257
- total: { $ref: "#/components/schemas/Money" },
258
- createdAt: { type: "string", format: "date-time" }
259
- },
260
- required: ["id", "status", "experienceId", "slotId", "quantity", "total", "createdAt"]
261
- },
262
- PaymentWebhookPayload: {
263
- type: "object",
264
- properties: {
265
- event: { type: "string", example: "transaction.updated" },
266
- data: {
267
- type: "object",
268
- properties: {
269
- providerRef: { type: "string" },
270
- status: { type: "string", enum: ["APPROVED", "DECLINED", "VOIDED", "ERROR", "PENDING"] },
271
- amountInCents: { type: "integer", minimum: 0 },
272
- currency: { type: "string", minLength: 3, maxLength: 3 },
273
- bookingId: { type: "string", format: "uuid", description: "Opcional si el flujo incluye referencia directa al booking." }
274
- },
275
- required: ["providerRef", "status", "amountInCents", "currency"]
276
- },
277
- sentAt: { type: "string", format: "date-time" }
278
- },
279
- required: ["event", "data"]
280
- },
281
- Error: {
282
- type: "object",
283
- properties: {
284
- error_code: { type: "string" },
285
- message: { type: "string" },
286
- details: { type: ["object", "null"] }
287
- },
288
- required: ["error_code", "message"]
289
- }
290
- },
291
- responses: {
292
- BadRequest: {
293
- description: "Solicitud inv\xE1lida",
294
- content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
295
- },
296
- Unauthorized: { description: "No autorizado" },
297
- NotFound: {
298
- description: "No encontrado",
299
- content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
300
- },
301
- Conflict: {
302
- description: "Conflicto (idempotencia, duplicado, etc.)",
303
- content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
304
- },
305
- UnprocessableEntity: {
306
- description: "Validaci\xF3n fallida",
307
- content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
308
- }
309
4543
  }
310
4544
  }
311
4545
  };