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