diff --git a/.changeset/warm-doors-shave.md b/.changeset/warm-doors-shave.md new file mode 100644 index 0000000..124be0e --- /dev/null +++ b/.changeset/warm-doors-shave.md @@ -0,0 +1,25 @@ +--- +"@freeticket/cli": minor +--- + +Contract 1.5.0 + admin 1.1.0, and two output/exit-code fixes. + +- `ft settlements list` — settlements paid to the organizer (`--event`, `--status`). + The comprobante PDF is still panel-only; the contract exposes `hasDocument` + and the file names, not a download URL. +- `ft reports financials` — per-function P&L (gross, platform fee, facial, + payment fee, 4x1000, net to settle) plus the linked settlement status. Same + numbers as the Liquidaciones dashboard, so a finance integration no longer + has to recompute them from `/sales` + Mercado Pago. +- `ft admin tokens list|create|revoke` — platform service tokens (PAT), the + headless credential for `ft admin` in CI. +- `ft events list --q ` — server-side search. +- Fix: `reports export reconciliation` answers `text/csv`, so its payload is a + string. It used to go through `JSON.stringify`, which quoted the whole file + and escaped the newlines as a literal `\n`, breaking Excel/pandas/Sheets. + CSV payloads are now written verbatim. +- Fix: a refused or non-confirmable destructive command (`delete`, `revoke`, + `admin ... suspend`) exits 1 instead of 0, so `ft … delete && next-step` no + longer runs `next-step`. Without a TTY it fails immediately pointing at + `--yes` instead of silently doing nothing. +- `--csv` on every `reports export`, `--json` on `ft login` / `ft config`. diff --git a/admin-openapi.json b/admin-openapi.json index 264b278..27f177d 100644 --- a/admin-openapi.json +++ b/admin-openapi.json @@ -1 +1,2952 @@ -{"openapi":"3.1.0","info":{"title":"FreeTicket Admin API","version":"1.0.0","description":"API REST de superadmin de FreeTicket (cross-tenant). Autenticación por sesión de plataforma (staff con rol SUPER_ADMIN) — NO usa API key de tenant. Aislada del contrato B2B /api/v1."},"servers":[{"url":"https://admin.appfreeticket.com/api/admin"}],"security":[{"session":[]}],"paths":{"/me":{"get":{"operationId":"getMe","tags":["me"],"summary":"Identidad del superadmin autenticado","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminMe"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces":{"get":{"operationId":"getWorkspaces","tags":["workspaces"],"summary":"Listar tenants (cross-tenant)","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"active(default)|suspended|deleted|all","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Búsqueda por nombre o slug","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWorkspace"}},"page":{"$ref":"#/components/schemas/Page"}},"required":["data","page"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postWorkspaces","tags":["workspaces"],"summary":"Crear tenant","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWorkspaceCreate"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminWorkspace"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{id}":{"get":{"operationId":"getWorkspacesId","tags":["workspaces"],"summary":"Detalle de un tenant","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminWorkspace"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"patchWorkspacesId","tags":["workspaces"],"summary":"Actualizar tenant","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWorkspaceUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminWorkspace"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{id}/suspend":{"post":{"operationId":"postWorkspacesIdSuspend","tags":["workspaces"],"summary":"Suspender tenant (corta acceso B2B)","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminWorkspace"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{id}/restore":{"post":{"operationId":"postWorkspacesIdRestore","tags":["workspaces"],"summary":"Reactivar tenant suspendido","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminWorkspace"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users":{"get":{"operationId":"getUsers","tags":["users"],"summary":"Listar usuarios cross-tenant","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Búsqueda por nombre o email","schema":{"type":"string"}},{"name":"role","in":"query","required":false,"description":"SUPER_ADMIN|ADMIN|STAFF|VIEWER|MINCULTURA","schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"Filtra por membresía a un workspace","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminUser"}},"page":{"$ref":"#/components/schemas/Page"}},"required":["data","page"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}":{"get":{"operationId":"getUsersId","tags":["users"],"summary":"Detalle de usuario (incluye workspaces)","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminUser"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"patchUsersId","tags":["users"],"summary":"Actualizar rol global / baneo","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminUser"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/impersonate":{"post":{"operationId":"postImpersonate","tags":["impersonation"],"summary":"Emitir token de impersonation acotado","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImpersonate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminImpersonation"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/impersonate/stop":{"post":{"operationId":"postImpersonateStop","tags":["impersonation"],"summary":"Terminar impersonation","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/platform-plans":{"get":{"operationId":"getPlatformPlans","tags":["platform-plans"],"summary":"Listar planes de plataforma","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminPlatformPlan"}}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postPlatformPlans","tags":["platform-plans"],"summary":"Crear plan de plataforma","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPlatformPlanCreate"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminPlatformPlan"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/platform-plans/{id}":{"get":{"operationId":"getPlatformPlansId","tags":["platform-plans"],"summary":"Detalle de plan (límites, pricing, features)","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminPlatformPlan"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"patchPlatformPlansId","tags":["platform-plans"],"summary":"Editar límites/pricing/features de un plan","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPlatformPlanUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminPlatformPlan"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feature-flags":{"get":{"operationId":"getFeatureFlags","tags":["feature-flags"],"summary":"Listar feature flags","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"key","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"description":"global|plan|workspace","schema":{"type":"string"}},{"name":"scopeId","in":"query","required":false,"description":"plan.slug u organization.id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminFeatureFlag"}}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feature-flags/{key}":{"put":{"operationId":"putFeatureFlagsKey","tags":["feature-flags"],"summary":"Setear un feature flag por scope","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminFeatureFlagSet"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AdminFeatureFlag"}},"required":["data"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/audit-log":{"get":{"operationId":"getAuditLog","tags":["audit-log"],"summary":"Registro append-only de acciones de superadmin","description":"Requiere sesión de plataforma con rol `SUPER_ADMIN`.","security":[{"session":[]}],"parameters":[{"name":"actorId","in":"query","required":false,"schema":{"type":"string"}},{"name":"action","in":"query","required":false,"schema":{"type":"string"}},{"name":"targetType","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"ISO 8601","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"ISO 8601","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAuditEntry"}},"page":{"$ref":"#/components/schemas/Page"}},"required":["data","page"]}}}},"401":{"description":"Credencial inválida o ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rol insuficiente o recurso no accesible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente o fuera de alcance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validación del cuerpo/parámetros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"AdminMe":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string","enum":["SUPER_ADMIN","ADMIN","STAFF","VIEWER","MINCULTURA"]}},"required":["userId","name","email","role"],"additionalProperties":false},"FeatureFlagScope":{"type":"string","enum":["global","plan","workspace"]},"Page":{"type":"object","properties":{"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}]},"hasMore":{"type":"boolean"}},"required":["nextCursor","hasMore"],"additionalProperties":false},"AdminWorkspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"type":{"type":"string","enum":["ARTIST","VENUE","ORGANIZER"]},"country":{"type":"string"},"isPublished":{"type":"boolean"},"suspended":{"type":"boolean"},"suspendedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"deletedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","name","slug","type","country","isPublished","suspended","suspendedAt","deletedAt","createdAt"],"additionalProperties":false},"AdminWorkspaceCreate":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1,"pattern":"^[a-z0-9-]+$"},"type":{"default":"ORGANIZER","type":"string","enum":["ARTIST","VENUE","ORGANIZER"]},"country":{"default":"CO","type":"string"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["name","slug","type","country"],"additionalProperties":false},"AdminWorkspaceUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1,"pattern":"^[a-z0-9-]+$"},"type":{"type":"string","enum":["ARTIST","VENUE","ORGANIZER"]},"isPublished":{"type":"boolean"}},"additionalProperties":false},"AdminUserWorkspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"isOwner":{"type":"boolean"}},"required":["id","name","slug","isOwner"],"additionalProperties":false},"AdminUser":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string","enum":["SUPER_ADMIN","ADMIN","STAFF","VIEWER","MINCULTURA"]},"banned":{"type":"boolean"},"bannedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"homeOrganizationId":{"anyOf":[{"type":"string"},{"type":"null"}]},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserWorkspace"}},"createdAt":{"type":"string"}},"required":["id","name","email","role","banned","bannedAt","homeOrganizationId","workspaces","createdAt"],"additionalProperties":false},"AdminUserUpdate":{"type":"object","properties":{"role":{"type":"string","enum":["SUPER_ADMIN","ADMIN","STAFF","VIEWER","MINCULTURA"]},"banned":{"type":"boolean"}},"additionalProperties":false},"AdminImpersonate":{"type":"object","properties":{"targetUserId":{"type":"string","minLength":1},"workspaceId":{"type":"string","minLength":1}},"additionalProperties":false},"AdminImpersonation":{"type":"object","properties":{"token":{"type":"string"},"expiresAt":{"type":"string"},"actorUserId":{"type":"string"},"targetUserId":{"type":"string"},"targetOrgId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["token","expiresAt","actorUserId","targetUserId","targetOrgId"],"additionalProperties":false},"AdminPlatformPlan":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"priceMonthly":{"type":"number"},"priceYearly":{"type":"number"},"priceBiannual":{"anyOf":[{"type":"number"},{"type":"null"}]},"maxEvents":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"isActive":{"type":"boolean"},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"features":{"type":"object","properties":{"memberships":{"type":"boolean"},"content":{"type":"boolean"},"fanDatabase":{"type":"boolean"},"directMessages":{"type":"boolean"},"streaming":{"type":"boolean"},"customLanding":{"type":"boolean"},"dataExport":{"type":"boolean"},"seatMaps":{"type":"boolean"},"customDomain":{"type":"boolean"},"analytics":{"type":"boolean"}},"required":["memberships","content","fanDatabase","directMessages","streaming","customLanding","dataExport","seatMaps","customDomain","analytics"],"additionalProperties":false},"createdAt":{"type":"string"}},"required":["id","name","slug","priceMonthly","priceYearly","priceBiannual","maxEvents","isActive","sortOrder","features","createdAt"],"additionalProperties":false},"AdminPlatformPlanCreate":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1,"pattern":"^[a-z0-9-]+$"},"priceMonthly":{"default":0,"type":"number","minimum":0},"priceYearly":{"default":0,"type":"number","minimum":0},"priceBiannual":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}]},"isActive":{"default":true,"type":"boolean"},"memberships":{"type":"boolean"},"content":{"type":"boolean"},"fanDatabase":{"type":"boolean"},"directMessages":{"type":"boolean"},"streaming":{"type":"boolean"},"customLanding":{"type":"boolean"},"dataExport":{"type":"boolean"},"seatMaps":{"type":"boolean"},"customDomain":{"type":"boolean"},"analytics":{"type":"boolean"},"maxEvents":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["name","slug","priceMonthly","priceYearly","isActive"],"additionalProperties":false},"AdminPlatformPlanUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1},"priceMonthly":{"type":"number","minimum":0},"priceYearly":{"type":"number","minimum":0},"priceBiannual":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}]},"isActive":{"type":"boolean"},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"memberships":{"type":"boolean"},"content":{"type":"boolean"},"fanDatabase":{"type":"boolean"},"directMessages":{"type":"boolean"},"streaming":{"type":"boolean"},"customLanding":{"type":"boolean"},"dataExport":{"type":"boolean"},"seatMaps":{"type":"boolean"},"customDomain":{"type":"boolean"},"analytics":{"type":"boolean"},"maxEvents":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"additionalProperties":false},"AdminFeatureFlag":{"type":"object","properties":{"key":{"type":"string"},"scope":{"$ref":"#/components/schemas/FeatureFlagScope"},"scopeId":{"anyOf":[{"type":"string"},{"type":"null"}]},"enabled":{"type":"boolean"},"updatedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"updatedAt":{"type":"string"}},"required":["key","scope","scopeId","enabled","updatedBy","updatedAt"],"additionalProperties":false},"AdminFeatureFlagSet":{"type":"object","properties":{"scope":{"$ref":"#/components/schemas/FeatureFlagScope"},"scopeId":{"type":"string","minLength":1},"enabled":{"type":"boolean"}},"required":["scope","enabled"],"additionalProperties":false},"AdminAuditEntry":{"type":"object","properties":{"id":{"type":"string"},"actorUserId":{"type":"string"},"action":{"type":"string"},"targetType":{"type":"string"},"targetId":{"type":"string"},"before":{"anyOf":[{},{"type":"null"}]},"after":{"anyOf":[{},{"type":"null"}]},"ipAddress":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","actorUserId","action","targetType","targetId","before","after","ipAddress","createdAt"],"additionalProperties":false}},"securitySchemes":{"session":{"type":"apiKey","in":"cookie","name":"better-auth.session_token","description":"Cookie de sesión de plataforma (better-auth staff). El portador debe tener rol SUPER_ADMIN. No se emite API key para este contrato."}}}} \ No newline at end of file +{ + "openapi": "3.1.0", + "info": { + "title": "FreeTicket Admin API", + "version": "1.1.0", + "description": "API REST de superadmin de FreeTicket (cross-tenant). Autenticación por sesión de plataforma (staff con rol SUPER_ADMIN) o por service token (PAT) vía `Authorization: Bearer ` — NO usa API key de tenant. Aislada del contrato B2B /api/v1." + }, + "servers": [ + { + "url": "https://admin.appfreeticket.com/api/admin" + } + ], + "security": [ + { + "session": [] + } + ], + "paths": { + "/me": { + "get": { + "operationId": "getMe", + "tags": [ + "me" + ], + "summary": "Identidad del superadmin autenticado", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminMe" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/workspaces": { + "get": { + "operationId": "getWorkspaces", + "tags": [ + "workspaces" + ], + "summary": "Listar tenants (cross-tenant)", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "required": false, + "description": "active(default)|suspended|deleted|all", + "schema": { + "type": "string" + } + }, + { + "name": "q", + "in": "query", + "required": false, + "description": "Búsqueda por nombre o slug", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "postWorkspaces", + "tags": [ + "workspaces" + ], + "summary": "Crear tenant", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminWorkspaceCreate" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/workspaces/{id}": { + "get": { + "operationId": "getWorkspacesId", + "tags": [ + "workspaces" + ], + "summary": "Detalle de un tenant", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "patchWorkspacesId", + "tags": [ + "workspaces" + ], + "summary": "Actualizar tenant", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminWorkspaceUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/workspaces/{id}/suspend": { + "post": { + "operationId": "postWorkspacesIdSuspend", + "tags": [ + "workspaces" + ], + "summary": "Suspender tenant (corta acceso B2B)", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/workspaces/{id}/restore": { + "post": { + "operationId": "postWorkspacesIdRestore", + "tags": [ + "workspaces" + ], + "summary": "Reactivar tenant suspendido", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminWorkspace" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/users": { + "get": { + "operationId": "getUsers", + "tags": [ + "users" + ], + "summary": "Listar usuarios cross-tenant", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "q", + "in": "query", + "required": false, + "description": "Búsqueda por nombre o email", + "schema": { + "type": "string" + } + }, + { + "name": "role", + "in": "query", + "required": false, + "description": "SUPER_ADMIN|ADMIN|STAFF|VIEWER|MINCULTURA", + "schema": { + "type": "string" + } + }, + { + "name": "workspaceId", + "in": "query", + "required": false, + "description": "Filtra por membresía a un workspace", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminUser" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/users/{id}": { + "get": { + "operationId": "getUsersId", + "tags": [ + "users" + ], + "summary": "Detalle de usuario (incluye workspaces)", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminUser" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "patchUsersId", + "tags": [ + "users" + ], + "summary": "Actualizar rol global / baneo", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminUserUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminUser" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/impersonate": { + "post": { + "operationId": "postImpersonate", + "tags": [ + "impersonation" + ], + "summary": "Emitir token de impersonation acotado", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminImpersonate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminImpersonation" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/impersonate/stop": { + "post": { + "operationId": "postImpersonateStop", + "tags": [ + "impersonation" + ], + "summary": "Terminar impersonation", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/platform-plans": { + "get": { + "operationId": "getPlatformPlans", + "tags": [ + "platform-plans" + ], + "summary": "Listar planes de plataforma", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminPlatformPlan" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "postPlatformPlans", + "tags": [ + "platform-plans" + ], + "summary": "Crear plan de plataforma", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminPlatformPlanCreate" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminPlatformPlan" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/platform-plans/{id}": { + "get": { + "operationId": "getPlatformPlansId", + "tags": [ + "platform-plans" + ], + "summary": "Detalle de plan (límites, pricing, features)", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminPlatformPlan" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "patchPlatformPlansId", + "tags": [ + "platform-plans" + ], + "summary": "Editar límites/pricing/features de un plan", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminPlatformPlanUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminPlatformPlan" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/feature-flags": { + "get": { + "operationId": "getFeatureFlags", + "tags": [ + "feature-flags" + ], + "summary": "Listar feature flags", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "key", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "scope", + "in": "query", + "required": false, + "description": "global|plan|workspace", + "schema": { + "type": "string" + } + }, + { + "name": "scopeId", + "in": "query", + "required": false, + "description": "plan.slug u organization.id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminFeatureFlag" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/feature-flags/{key}": { + "put": { + "operationId": "putFeatureFlagsKey", + "tags": [ + "feature-flags" + ], + "summary": "Setear un feature flag por scope", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminFeatureFlagSet" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminFeatureFlag" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/audit-log": { + "get": { + "operationId": "getAuditLog", + "tags": [ + "audit-log" + ], + "summary": "Registro append-only de acciones de superadmin", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "actorId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "targetType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "ISO 8601", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "ISO 8601", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminAuditEntry" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/tokens": { + "get": { + "operationId": "getTokens", + "tags": [ + "tokens" + ], + "summary": "Listar service tokens (PAT) de plataforma", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminToken" + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "postTokens", + "tags": [ + "tokens" + ], + "summary": "Emitir un service token (PAT) — devuelve el secreto una sola vez", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminTokenCreate" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminTokenCreated" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/tokens/{id}": { + "delete": { + "operationId": "deleteTokensId", + "tags": [ + "tokens" + ], + "summary": "Revocar un service token (PAT)", + "description": "Requiere sesión de plataforma con rol `SUPER_ADMIN`.", + "security": [ + { + "session": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminToken" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "details": {} + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "AdminMe": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "SUPER_ADMIN", + "ADMIN", + "STAFF", + "VIEWER", + "MINCULTURA" + ] + } + }, + "required": [ + "userId", + "name", + "email", + "role" + ], + "additionalProperties": false + }, + "FeatureFlagScope": { + "type": "string", + "enum": [ + "global", + "plan", + "workspace" + ] + }, + "Page": { + "type": "object", + "properties": { + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "nextCursor", + "hasMore" + ], + "additionalProperties": false + }, + "AdminWorkspace": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ARTIST", + "VENUE", + "ORGANIZER" + ] + }, + "country": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "suspended": { + "type": "boolean" + }, + "suspendedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "deletedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug", + "type", + "country", + "isPublished", + "suspended", + "suspendedAt", + "deletedAt", + "createdAt" + ], + "additionalProperties": false + }, + "AdminWorkspaceCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9-]+$" + }, + "type": { + "default": "ORGANIZER", + "type": "string", + "enum": [ + "ARTIST", + "VENUE", + "ORGANIZER" + ] + }, + "country": { + "default": "CO", + "type": "string" + }, + "email": { + "type": "string", + "format": "email", + "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" + } + }, + "required": [ + "name", + "slug", + "type", + "country" + ], + "additionalProperties": false + }, + "AdminWorkspaceUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9-]+$" + }, + "type": { + "type": "string", + "enum": [ + "ARTIST", + "VENUE", + "ORGANIZER" + ] + }, + "isPublished": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "AdminUserWorkspace": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "isOwner": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "slug", + "isOwner" + ], + "additionalProperties": false + }, + "AdminUser": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "SUPER_ADMIN", + "ADMIN", + "STAFF", + "VIEWER", + "MINCULTURA" + ] + }, + "banned": { + "type": "boolean" + }, + "bannedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "homeOrganizationId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminUserWorkspace" + } + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "email", + "role", + "banned", + "bannedAt", + "homeOrganizationId", + "workspaces", + "createdAt" + ], + "additionalProperties": false + }, + "AdminUserUpdate": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "SUPER_ADMIN", + "ADMIN", + "STAFF", + "VIEWER", + "MINCULTURA" + ] + }, + "banned": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "AdminImpersonate": { + "type": "object", + "properties": { + "targetUserId": { + "type": "string", + "minLength": 1 + }, + "workspaceId": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "AdminImpersonation": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "expiresAt": { + "type": "string" + }, + "actorUserId": { + "type": "string" + }, + "targetUserId": { + "type": "string" + }, + "targetOrgId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "token", + "expiresAt", + "actorUserId", + "targetUserId", + "targetOrgId" + ], + "additionalProperties": false + }, + "AdminPlatformPlan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "priceMonthly": { + "type": "number" + }, + "priceYearly": { + "type": "number" + }, + "priceBiannual": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "maxEvents": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "isActive": { + "type": "boolean" + }, + "sortOrder": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "features": { + "type": "object", + "properties": { + "memberships": { + "type": "boolean" + }, + "content": { + "type": "boolean" + }, + "fanDatabase": { + "type": "boolean" + }, + "directMessages": { + "type": "boolean" + }, + "streaming": { + "type": "boolean" + }, + "customLanding": { + "type": "boolean" + }, + "dataExport": { + "type": "boolean" + }, + "seatMaps": { + "type": "boolean" + }, + "customDomain": { + "type": "boolean" + }, + "analytics": { + "type": "boolean" + } + }, + "required": [ + "memberships", + "content", + "fanDatabase", + "directMessages", + "streaming", + "customLanding", + "dataExport", + "seatMaps", + "customDomain", + "analytics" + ], + "additionalProperties": false + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug", + "priceMonthly", + "priceYearly", + "priceBiannual", + "maxEvents", + "isActive", + "sortOrder", + "features", + "createdAt" + ], + "additionalProperties": false + }, + "AdminPlatformPlanCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9-]+$" + }, + "priceMonthly": { + "default": 0, + "type": "number", + "minimum": 0 + }, + "priceYearly": { + "default": 0, + "type": "number", + "minimum": 0 + }, + "priceBiannual": { + "anyOf": [ + { + "type": "number", + "minimum": 0 + }, + { + "type": "null" + } + ] + }, + "isActive": { + "default": true, + "type": "boolean" + }, + "memberships": { + "type": "boolean" + }, + "content": { + "type": "boolean" + }, + "fanDatabase": { + "type": "boolean" + }, + "directMessages": { + "type": "boolean" + }, + "streaming": { + "type": "boolean" + }, + "customLanding": { + "type": "boolean" + }, + "dataExport": { + "type": "boolean" + }, + "seatMaps": { + "type": "boolean" + }, + "customDomain": { + "type": "boolean" + }, + "analytics": { + "type": "boolean" + }, + "maxEvents": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name", + "slug", + "priceMonthly", + "priceYearly", + "isActive" + ], + "additionalProperties": false + }, + "AdminPlatformPlanUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "priceMonthly": { + "type": "number", + "minimum": 0 + }, + "priceYearly": { + "type": "number", + "minimum": 0 + }, + "priceBiannual": { + "anyOf": [ + { + "type": "number", + "minimum": 0 + }, + { + "type": "null" + } + ] + }, + "isActive": { + "type": "boolean" + }, + "sortOrder": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "memberships": { + "type": "boolean" + }, + "content": { + "type": "boolean" + }, + "fanDatabase": { + "type": "boolean" + }, + "directMessages": { + "type": "boolean" + }, + "streaming": { + "type": "boolean" + }, + "customLanding": { + "type": "boolean" + }, + "dataExport": { + "type": "boolean" + }, + "seatMaps": { + "type": "boolean" + }, + "customDomain": { + "type": "boolean" + }, + "analytics": { + "type": "boolean" + }, + "maxEvents": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "AdminFeatureFlag": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "scope": { + "$ref": "#/components/schemas/FeatureFlagScope" + }, + "scopeId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "enabled": { + "type": "boolean" + }, + "updatedBy": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "key", + "scope", + "scopeId", + "enabled", + "updatedBy", + "updatedAt" + ], + "additionalProperties": false + }, + "AdminFeatureFlagSet": { + "type": "object", + "properties": { + "scope": { + "$ref": "#/components/schemas/FeatureFlagScope" + }, + "scopeId": { + "type": "string", + "minLength": 1 + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "scope", + "enabled" + ], + "additionalProperties": false + }, + "AdminAuditEntry": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "actorUserId": { + "type": "string" + }, + "action": { + "type": "string" + }, + "targetType": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "before": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + }, + "after": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + }, + "ipAddress": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "actorUserId", + "action", + "targetType", + "targetId", + "before", + "after", + "ipAddress", + "createdAt" + ], + "additionalProperties": false + }, + "AdminToken": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tokenPrefix": { + "type": "string" + }, + "lastUsedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "revokedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "tokenPrefix", + "lastUsedAt", + "expiresAt", + "revokedAt", + "createdAt" + ], + "additionalProperties": false + }, + "AdminTokenCreated": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "token": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "lastUsedAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "token", + "createdAt", + "expiresAt", + "lastUsedAt" + ], + "additionalProperties": false + }, + "AdminTokenCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "expiresAt": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "securitySchemes": { + "session": { + "type": "apiKey", + "in": "cookie", + "name": "better-auth.session_token", + "description": "Autenticación de plataforma. Dos formas aceptadas: (1) cookie de sesión de plataforma `better-auth.session_token` (staff con rol SUPER_ADMIN), o (2) service token (PAT) con el header `Authorization: Bearer ` emitido por POST /tokens. En ambos casos el portador debe ser SUPER_ADMIN. No se emite API key de tenant para este contrato." + } + } + } +} diff --git a/openapi.json b/openapi.json index dac7649..df2af28 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ }, "servers": [ { - "url": "http://localhost:3000/api/v1" + "url": "https://admin.appfreeticket.com/api/v1" } ], "security": [ @@ -16,95 +16,29 @@ } ], "paths": { - "/auth/device/code": { - "post": { - "operationId": "postAuthDeviceCode", - "tags": [ - "auth" - ], - "summary": "Inicia el Device Authorization Grant (RFC 8628)", - "description": "Endpoint público (sin auth): bootstrap de credenciales del Device Authorization Grant (RFC 8628).", - "security": [], - "responses": { - "200": { - "description": "Flujo iniciado: device_code (lo poll-ea el CLI) + user_code (lo aprueba el usuario en el browser).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceCodeResponse" - } - } - } - }, - "429": { - "description": "Demasiadas solicitudes desde la misma IP (anti-flood).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/auth/device/token": { - "post": { - "operationId": "postAuthDeviceToken", - "tags": [ - "auth" - ], - "summary": "Poll del CLI: canjea el device_code por una API key al aprobar", - "description": "Endpoint público (sin auth): bootstrap de credenciales del Device Authorization Grant (RFC 8628).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceTokenRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Aprobado: entrega la API key ft_live_… una sola vez y consume la fila.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceTokenResponse" - } - } - } - }, - "400": { - "description": "Estado RFC 8628: authorization_pending | slow_down | expired_token | access_denied | invalid_request.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceTokenError" - } - } - } - } - } - } - }, - "/me": { + "/events/{id}/dates": { "get": { - "operationId": "getMe", + "operationId": "getEventsIdDates", "tags": [ - "me" + "events" ], - "summary": "Usuario autenticado, rol y workspaces accesibles", + "summary": "Listar fechas de un evento", "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", @@ -114,7 +48,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Me" + "type": "array", + "items": { + "$ref": "#/components/schemas/EventDate" + } } }, "required": [ @@ -165,16 +102,14 @@ } } } - } - }, - "/events": { - "get": { - "operationId": "getEvents", + }, + "post": { + "operationId": "postEventsIdDates", "tags": [ "events" ], - "summary": "Listar eventos del workspace", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Agregar fecha a un evento", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -182,26 +117,26 @@ ], "parameters": [ { - "name": "limit", - "in": "query", - "required": false, - "description": "1-100, default 20", - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "description": "id del último resultado de la página previa", + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventDateCreate" + } + } + } + }, "responses": { - "200": { + "201": { "description": "OK", "content": { "application/json": { @@ -209,18 +144,11 @@ "type": "object", "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Event" - } - }, - "page": { - "$ref": "#/components/schemas/Page" + "$ref": "#/components/schemas/EventDate" } }, "required": [ - "data", - "page" + "data" ] } } @@ -267,44 +195,28 @@ } } } - }, - "post": { - "operationId": "postEvents", + } + }, + "/reports/exports/subscribers": { + "get": { + "operationId": "getReportsExportsSubscribers", "tags": [ - "events" + "reports" ], - "summary": "Crear evento", + "summary": "Export de suscriptores (máx 5000)", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventCreate" - } - } - } - }, "responses": { - "201": { + "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/Event" - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -352,14 +264,14 @@ } } }, - "/events/{id}": { + "/reports/exports/reconciliation": { "get": { - "operationId": "getEventsId", + "operationId": "getReportsExportsReconciliation", "tags": [ - "events" + "reports" ], - "summary": "Obtener un evento", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Export CSV de la conciliación financiera CFO", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -367,9 +279,37 @@ ], "parameters": [ { - "name": "id", - "in": "path", + "name": "date_from", + "in": "query", + "required": true, + "description": "ISO 8601 (inicio, sobre Sale.created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "date_to", + "in": "query", "required": true, + "description": "ISO 8601 (fin)", + "schema": { + "type": "string" + } + }, + { + "name": "match_status", + "in": "query", + "required": false, + "description": "Filtra por flag: OK|MISSING_INVOICE|MISSING_CUFE|AMOUNT_MISMATCH|MISSING_PAYMENT", + "schema": { + "type": "string" + } + }, + { + "name": "provider", + "in": "query", + "required": false, + "description": "Filtra por payment_provider (ej: mercadopago)", "schema": { "type": "string" } @@ -381,15 +321,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/Event" - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -435,14 +367,16 @@ } } } - }, - "patch": { - "operationId": "patchEventsId", + } + }, + "/settlements": { + "get": { + "operationId": "getSettlements", "tags": [ - "events" + "settlements" ], - "summary": "Actualizar evento", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Liquidaciones del workspace (pagos al organizador)", + "description": "Lista las liquidaciones visibles para la organización (SENT, AWAITING_PAYMENT, PAID) con su id estándar, monto, evento/función y comprobantes. Los archivos (PDF y comprobantes) se descargan desde el panel, no por esta API.", "security": [ { "apiKey": [] @@ -450,24 +384,42 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "event", + "in": "query", + "required": false, + "description": "Filtra por id de evento", "schema": { "type": "string" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventUpdate" - } + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "SENT|AWAITING_PAYMENT|PAID", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "1..100 (default 20)", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Id de la última fila de la página previa", + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "OK", @@ -477,11 +429,18 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Event" + "type": "array", + "items": { + "$ref": "#/components/schemas/Settlement" + } + }, + "page": { + "$ref": "#/components/schemas/Page" } }, "required": [ - "data" + "data", + "page" ] } } @@ -528,14 +487,16 @@ } } } - }, - "delete": { - "operationId": "deleteEventsId", + } + }, + "/reports/financials": { + "get": { + "operationId": "getReportsFinancials", "tags": [ - "events" + "reports" ], - "summary": "Eliminar (soft) evento", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Estado financiero por función: bruto, costos y neto a liquidar", + "description": "Una fila por función (event_date) con el desglose financiero de sus ventas confirmadas — bruto cobrado, cargo de plataforma + IVA, valor facial, comisión real de la pasarela, 4x1000 y neto a liquidar — más el estado de la liquidación asociada. Mismos números que /dashboard/liquidaciones.", "security": [ { "apiKey": [] @@ -543,9 +504,19 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "event", + "in": "query", + "required": false, + "description": "Filtra por id de evento", + "schema": { + "type": "string" + } + }, + { + "name": "past", + "in": "query", + "required": false, + "description": "true = solo funciones ya ocurridas (liquidables); false = futuras", "schema": { "type": "string" } @@ -557,7 +528,18 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportFinancialsRow" + } + } + }, + "required": [ + "data" + ] } } } @@ -605,28 +587,94 @@ } } }, - "/events/{id}/publish": { + "/auth/device/code": { "post": { - "operationId": "postEventsIdPublish", + "operationId": "postAuthDeviceCode", "tags": [ - "events" + "auth" ], - "summary": "Publicar evento", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", - "security": [ - { - "apiKey": [] + "summary": "Inicia el Device Authorization Grant (RFC 8628)", + "description": "Endpoint público (sin auth): bootstrap de credenciales del Device Authorization Grant (RFC 8628).", + "security": [], + "responses": { + "200": { + "description": "Flujo iniciado: device_code (lo poll-ea el CLI) + user_code (lo aprueba el usuario en el browser).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCodeResponse" + } + } + } + }, + "429": { + "description": "Demasiadas solicitudes desde la misma IP (anti-flood).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } + } + } + }, + "/auth/device/token": { + "post": { + "operationId": "postAuthDeviceToken", + "tags": [ + "auth" ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" + "summary": "Poll del CLI: canjea el device_code por una API key al aprobar", + "description": "Endpoint público (sin auth): bootstrap de credenciales del Device Authorization Grant (RFC 8628).", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceTokenRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Aprobado: entrega la API key ft_live_… una sola vez y consume la fila.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceTokenResponse" + } + } + } + }, + "400": { + "description": "Estado RFC 8628: authorization_pending | slow_down | expired_token | access_denied | invalid_request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceTokenError" + } + } } } + } + } + }, + "/me": { + "get": { + "operationId": "getMe", + "tags": [ + "me" + ], + "summary": "Usuario autenticado, rol y workspaces accesibles", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "security": [ + { + "apiKey": [] + } ], "responses": { "200": { @@ -637,7 +685,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Event" + "$ref": "#/components/schemas/Me" } }, "required": [ @@ -690,13 +738,13 @@ } } }, - "/events/{id}/dates": { + "/events": { "get": { - "operationId": "getEventsIdDates", + "operationId": "getEvents", "tags": [ "events" ], - "summary": "Listar fechas de un evento", + "summary": "Listar eventos del workspace", "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -705,9 +753,28 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "limit", + "in": "query", + "required": false, + "description": "1-100, default 20", + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "description": "id del último resultado de la página previa", + "schema": { + "type": "string" + } + }, + { + "name": "q", + "in": "query", + "required": false, + "description": "Búsqueda por nombre/descripción, insensible a mayúsculas y acentos", "schema": { "type": "string" } @@ -724,12 +791,16 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/EventDate" + "$ref": "#/components/schemas/Event" } + }, + "page": { + "$ref": "#/components/schemas/Page" } }, "required": [ - "data" + "data", + "page" ] } } @@ -778,33 +849,23 @@ } }, "post": { - "operationId": "postEventsIdDates", + "operationId": "postEvents", "tags": [ "events" ], - "summary": "Agregar fecha a un evento", + "summary": "Crear evento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EventDateCreate" + "$ref": "#/components/schemas/EventCreate" } } } @@ -818,7 +879,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/EventDate" + "$ref": "#/components/schemas/Event" } }, "required": [ @@ -871,14 +932,14 @@ } } }, - "/events/{id}/dates/{dateId}": { - "patch": { - "operationId": "patchEventsIdDatesDateId", + "/events/{id}": { + "get": { + "operationId": "getEventsId", "tags": [ "events" ], - "summary": "Actualizar fecha", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Obtener un evento", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -892,26 +953,8 @@ "schema": { "type": "string" } - }, - { - "name": "dateId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventDateUpdate" - } - } - } - }, "responses": { "200": { "description": "OK", @@ -921,7 +964,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/EventDate" + "$ref": "#/components/schemas/Event" } }, "required": [ @@ -973,12 +1016,12 @@ } } }, - "delete": { - "operationId": "deleteEventsIdDatesDateId", + "patch": { + "operationId": "patchEventsId", "tags": [ "events" ], - "summary": "Eliminar fecha", + "summary": "Actualizar evento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -993,23 +1036,33 @@ "schema": { "type": "string" } - }, - { - "name": "dateId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Event" + } + }, + "required": [ + "data" + ] } } } @@ -1055,16 +1108,14 @@ } } } - } - }, - "/ticket-types": { - "get": { - "operationId": "getTicketTypes", + }, + "delete": { + "operationId": "deleteEventsId", "tags": [ - "ticket-types" + "events" ], - "summary": "Listar tipos de ticket del workspace", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Eliminar (soft) evento", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1072,26 +1123,9 @@ ], "parameters": [ { - "name": "eventDateId", - "in": "query", - "required": false, - "description": "filtra por fecha de evento", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -1103,22 +1137,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TicketType" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "data", - "page" - ] + "type": "object" } } } @@ -1164,31 +1183,33 @@ } } } - }, + } + }, + "/events/{id}/publish": { "post": { - "operationId": "postTicketTypes", + "operationId": "postEventsIdPublish", "tags": [ - "ticket-types" + "events" ], - "summary": "Crear tipo de ticket", + "summary": "Publicar evento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TicketTypeCreate" - } + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { - "201": { + "200": { "description": "OK", "content": { "application/json": { @@ -1196,7 +1217,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/TicketType" + "$ref": "#/components/schemas/Event" } }, "required": [ @@ -1249,14 +1270,14 @@ } } }, - "/ticket-types/{id}": { - "get": { - "operationId": "getTicketTypesId", + "/events/{id}/dates/{dateId}": { + "patch": { + "operationId": "patchEventsIdDatesDateId", "tags": [ - "ticket-types" + "events" ], - "summary": "Obtener un tipo de ticket", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Actualizar fecha", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1270,8 +1291,26 @@ "schema": { "type": "string" } + }, + { + "name": "dateId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventDateUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", @@ -1281,7 +1320,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/TicketType" + "$ref": "#/components/schemas/EventDate" } }, "required": [ @@ -1333,12 +1372,12 @@ } } }, - "patch": { - "operationId": "patchTicketTypesId", + "delete": { + "operationId": "deleteEventsIdDatesDateId", "tags": [ - "ticket-types" + "events" ], - "summary": "Actualizar tipo de ticket", + "summary": "Eliminar fecha", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -1353,33 +1392,23 @@ "schema": { "type": "string" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TicketTypeUpdate" - } + }, + { + "name": "dateId", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/TicketType" - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -1425,14 +1454,16 @@ } } } - }, - "delete": { - "operationId": "deleteTicketTypesId", + } + }, + "/ticket-types": { + "get": { + "operationId": "getTicketTypes", "tags": [ "ticket-types" ], - "summary": "Eliminar (soft) tipo de ticket", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Listar tipos de ticket del workspace", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1440,9 +1471,26 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "eventDateId", + "in": "query", + "required": false, + "description": "filtra por fecha de evento", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, "schema": { "type": "string" } @@ -1454,7 +1502,22 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TicketType" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] } } } @@ -1500,186 +1563,14 @@ } } } - } - }, - "/sales": { - "get": { - "operationId": "getSales", + }, + "post": { + "operationId": "postTicketTypes", "tags": [ - "sales" + "ticket-types" ], - "summary": "Listar ventas del workspace", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", - "security": [ - { - "apiKey": [] - } - ], - "parameters": [ - { - "name": "status", - "in": "query", - "required": false, - "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED", - "schema": { - "type": "string" - } - }, - { - "name": "channel", - "in": "query", - "required": false, - "description": "WEB|MOBILE|POS|ADMIN", - "schema": { - "type": "string" - } - }, - { - "name": "event", - "in": "query", - "required": false, - "description": "id del evento", - "schema": { - "type": "string" - } - }, - { - "name": "eventDate", - "in": "query", - "required": false, - "description": "id de la fecha del evento", - "schema": { - "type": "string" - } - }, - { - "name": "reference", - "in": "query", - "required": false, - "description": "referencia/código de la venta (parcial)", - "schema": { - "type": "string" - } - }, - { - "name": "buyer", - "in": "query", - "required": false, - "description": "nombre o email del comprador (parcial)", - "schema": { - "type": "string" - } - }, - { - "name": "from", - "in": "query", - "required": false, - "description": "fecha desde (ISO 8601, sobre created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "to", - "in": "query", - "required": false, - "description": "fecha hasta (ISO 8601, sobre created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Sale" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "data", - "page" - ] - } - } - } - }, - "401": { - "description": "Credencial inválida o ausente.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Rol insuficiente o recurso no accesible.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Recurso inexistente o fuera de alcance.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Validación del cuerpo/parámetros.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "operationId": "postSales", - "tags": [ - "sales" - ], - "summary": "Crear una venta/orden (cortesías y ventas programáticas)", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Crear tipo de ticket", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1690,7 +1581,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SaleCreate" + "$ref": "#/components/schemas/TicketTypeCreate" } } } @@ -1704,7 +1595,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SaleDetail" + "$ref": "#/components/schemas/TicketType" } }, "required": [ @@ -1757,14 +1648,14 @@ } } }, - "/sales/{id}": { + "/ticket-types/{id}": { "get": { - "operationId": "getSalesId", + "operationId": "getTicketTypesId", "tags": [ - "sales" + "ticket-types" ], - "summary": "Detalle de una venta con sus ítems", - "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Obtener un tipo de ticket", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1789,7 +1680,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SaleDetail" + "$ref": "#/components/schemas/TicketType" } }, "required": [ @@ -1840,15 +1731,13 @@ } } } - } - }, - "/sales/{id}/cancel": { - "post": { - "operationId": "postSalesIdCancel", + }, + "patch": { + "operationId": "patchTicketTypesId", "tags": [ - "sales" + "ticket-types" ], - "summary": "Cancelar una venta", + "summary": "Actualizar tipo de ticket", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -1865,6 +1754,16 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TicketTypeUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", @@ -1874,7 +1773,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Sale" + "$ref": "#/components/schemas/TicketType" } }, "required": [ @@ -1925,16 +1824,14 @@ } } } - } - }, - "/sales/{id}/refund": { - "post": { - "operationId": "postSalesIdRefund", + }, + "delete": { + "operationId": "deleteTicketTypesId", "tags": [ - "sales" + "ticket-types" ], - "summary": "Marcar una venta como REEMBOLSADA (NO ejecuta el reembolso en MercadoPago)", - "description": "Marca la venta como REFUNDED y libera las sillas numeradas asociadas. IMPORTANTE: este endpoint NO dispara el reembolso real en el proveedor de pago (MercadoPago); el dinero NO se devuelve al comprador automáticamente. Un 200 aquí confirma únicamente el cambio de estado contable, no el movimiento de fondos. El reembolso en MercadoPago debe gestionarse por separado (panel de MercadoPago o su API).", + "summary": "Eliminar (soft) tipo de ticket", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -1956,15 +1853,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/Sale" - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -2012,13 +1901,13 @@ } } }, - "/membership-plans": { + "/sales": { "get": { - "operationId": "getMembershipPlans", + "operationId": "getSales", "tags": [ - "membership-plans" + "sales" ], - "summary": "Listar planes de membresía del workspace", + "summary": "Listar ventas del workspace", "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2027,34 +1916,106 @@ ], "parameters": [ { - "name": "limit", + "name": "status", "in": "query", "required": false, + "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED", "schema": { "type": "string" } }, { - "name": "cursor", + "name": "channel", "in": "query", "required": false, + "description": "WEB|MOBILE|POS|ADMIN", "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { + }, + { + "name": "event", + "in": "query", + "required": false, + "description": "id del evento", + "schema": { + "type": "string" + } + }, + { + "name": "eventDate", + "in": "query", + "required": false, + "description": "id de la fecha del evento", + "schema": { + "type": "string" + } + }, + { + "name": "reference", + "in": "query", + "required": false, + "description": "referencia/código de la venta (parcial)", + "schema": { + "type": "string" + } + }, + { + "name": "buyer", + "in": "query", + "required": false, + "description": "nombre o email del comprador (parcial)", + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "fecha desde (ISO 8601, sobre created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "fecha hasta (ISO 8601, sobre created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { "type": "array", "items": { - "$ref": "#/components/schemas/MembershipPlan" + "$ref": "#/components/schemas/Sale" } }, "page": { @@ -2112,11 +2073,11 @@ } }, "post": { - "operationId": "postMembershipPlans", + "operationId": "postSales", "tags": [ - "membership-plans" + "sales" ], - "summary": "Crear plan de membresía", + "summary": "Crear una venta/orden (cortesías y ventas programáticas)", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2128,7 +2089,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MembershipPlanCreate" + "$ref": "#/components/schemas/SaleCreate" } } } @@ -2142,7 +2103,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/MembershipPlan" + "$ref": "#/components/schemas/SaleDetail" } }, "required": [ @@ -2195,14 +2156,14 @@ } } }, - "/membership-plans/{id}": { + "/sales/{id}": { "get": { - "operationId": "getMembershipPlansId", + "operationId": "getSalesId", "tags": [ - "membership-plans" + "sales" ], - "summary": "Obtener un plan de membresía", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Detalle de una venta con sus ítems", + "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -2227,7 +2188,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/MembershipPlan" + "$ref": "#/components/schemas/SaleDetail" } }, "required": [ @@ -2278,13 +2239,15 @@ } } } - }, - "patch": { - "operationId": "patchMembershipPlansId", + } + }, + "/sales/{id}/cancel": { + "post": { + "operationId": "postSalesIdCancel", "tags": [ - "membership-plans" + "sales" ], - "summary": "Actualizar plan", + "summary": "Cancelar una venta", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2301,16 +2264,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MembershipPlanUpdate" - } - } - } - }, "responses": { "200": { "description": "OK", @@ -2320,7 +2273,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/MembershipPlan" + "$ref": "#/components/schemas/Sale" } }, "required": [ @@ -2371,14 +2324,16 @@ } } } - }, - "delete": { - "operationId": "deleteMembershipPlansId", + } + }, + "/sales/{id}/refund": { + "post": { + "operationId": "postSalesIdRefund", "tags": [ - "membership-plans" + "sales" ], - "summary": "Eliminar (soft) plan", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Marcar una venta como REEMBOLSADA (NO ejecuta el reembolso en MercadoPago)", + "description": "Marca la venta como REFUNDED y libera las sillas numeradas asociadas. IMPORTANTE: este endpoint NO dispara el reembolso real en el proveedor de pago (MercadoPago); el dinero NO se devuelve al comprador automáticamente. Un 200 aquí confirma únicamente el cambio de estado contable, no el movimiento de fondos. El reembolso en MercadoPago debe gestionarse por separado (panel de MercadoPago o su API).", "security": [ { "apiKey": [] @@ -2400,7 +2355,15 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Sale" + } + }, + "required": [ + "data" + ] } } } @@ -2448,13 +2411,13 @@ } } }, - "/venues": { + "/membership-plans": { "get": { - "operationId": "getVenues", + "operationId": "getMembershipPlans", "tags": [ - "venues" + "membership-plans" ], - "summary": "Listar venues del workspace", + "summary": "Listar planes de membresía del workspace", "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2490,7 +2453,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/Venue" + "$ref": "#/components/schemas/MembershipPlan" } }, "page": { @@ -2548,11 +2511,11 @@ } }, "post": { - "operationId": "postVenues", + "operationId": "postMembershipPlans", "tags": [ - "venues" + "membership-plans" ], - "summary": "Crear venue", + "summary": "Crear plan de membresía", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2564,7 +2527,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VenueCreate" + "$ref": "#/components/schemas/MembershipPlanCreate" } } } @@ -2578,7 +2541,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Venue" + "$ref": "#/components/schemas/MembershipPlan" } }, "required": [ @@ -2631,13 +2594,13 @@ } } }, - "/venues/{id}": { + "/membership-plans/{id}": { "get": { - "operationId": "getVenuesId", + "operationId": "getMembershipPlansId", "tags": [ - "venues" + "membership-plans" ], - "summary": "Obtener un venue", + "summary": "Obtener un plan de membresía", "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2663,7 +2626,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Venue" + "$ref": "#/components/schemas/MembershipPlan" } }, "required": [ @@ -2716,11 +2679,11 @@ } }, "patch": { - "operationId": "patchVenuesId", + "operationId": "patchMembershipPlansId", "tags": [ - "venues" + "membership-plans" ], - "summary": "Actualizar venue", + "summary": "Actualizar plan", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2742,7 +2705,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VenueUpdate" + "$ref": "#/components/schemas/MembershipPlanUpdate" } } } @@ -2756,7 +2719,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Venue" + "$ref": "#/components/schemas/MembershipPlan" } }, "required": [ @@ -2809,11 +2772,11 @@ } }, "delete": { - "operationId": "deleteVenuesId", + "operationId": "deleteMembershipPlansId", "tags": [ - "venues" + "membership-plans" ], - "summary": "Eliminar (soft) venue", + "summary": "Eliminar (soft) plan", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -2884,14 +2847,14 @@ } } }, - "/staff": { + "/venues": { "get": { - "operationId": "getStaff", + "operationId": "getVenues", "tags": [ - "staff" + "venues" ], - "summary": "Listar staff con acceso al workspace", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Listar venues del workspace", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -2926,7 +2889,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/StaffUser" + "$ref": "#/components/schemas/Venue" } }, "page": { @@ -2984,11 +2947,11 @@ } }, "post": { - "operationId": "postStaff", + "operationId": "postVenues", "tags": [ - "staff" + "venues" ], - "summary": "Crear usuario staff en el workspace", + "summary": "Crear venue", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -3000,7 +2963,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StaffCreate" + "$ref": "#/components/schemas/VenueCreate" } } } @@ -3014,7 +2977,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffUser" + "$ref": "#/components/schemas/Venue" } }, "required": [ @@ -3067,14 +3030,14 @@ } } }, - "/staff/{id}/role": { - "patch": { - "operationId": "patchStaffIdRole", + "/venues/{id}": { + "get": { + "operationId": "getVenuesId", "tags": [ - "staff" + "venues" ], - "summary": "Cambiar rol de un usuario staff", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Obtener un venue", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -3090,16 +3053,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RoleUpdate" - } - } - } - }, "responses": { "200": { "description": "OK", @@ -3109,7 +3062,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/StaffUser" + "$ref": "#/components/schemas/Venue" } }, "required": [ @@ -3160,16 +3113,14 @@ } } } - } - }, - "/reports/summary": { - "get": { - "operationId": "getReportsSummary", + }, + "patch": { + "operationId": "patchVenuesId", "tags": [ - "reports" + "venues" ], - "summary": "KPIs del workspace (revenue, tickets, ventas, membresías)", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Actualizar venue", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -3177,15 +3128,24 @@ ], "parameters": [ { - "name": "period", - "in": "query", - "required": false, - "description": "7d | 30d (default) | 90d | 1y", + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VenueUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", @@ -3195,7 +3155,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/ReportSummary" + "$ref": "#/components/schemas/Venue" } }, "required": [ @@ -3246,16 +3206,14 @@ } } } - } - }, - "/reports/inventory": { - "get": { - "operationId": "getReportsInventory", + }, + "delete": { + "operationId": "deleteVenuesId", "tags": [ - "reports" + "venues" ], - "summary": "Inventario agregado por evento/fecha/tipo (capacidad, vendidos, disponibles)", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Eliminar (soft) venue", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -3263,55 +3221,9 @@ ], "parameters": [ { - "name": "eventId", - "in": "query", - "required": false, - "description": "Filtra por evento", - "schema": { - "type": "string" - } - }, - { - "name": "eventDateId", - "in": "query", - "required": false, - "description": "Filtra por fecha del evento", - "schema": { - "type": "string" - } - }, - { - "name": "from", - "in": "query", - "required": false, - "description": "Inicio de función ≥ (ISO 8601)", - "schema": { - "type": "string" - } - }, - { - "name": "to", - "in": "query", - "required": false, - "description": "Inicio de función ≤ (ISO 8601)", - "schema": { - "type": "string" - } - }, - { - "name": "includeDrafts", - "in": "query", - "required": false, - "description": "Incluir eventos/fechas en DRAFT (default false)", - "schema": { - "type": "string" - } - }, - { - "name": "groupBy", - "in": "query", - "required": false, - "description": "event | date | ticketType (default)", + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -3323,18 +3235,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/InventoryRow" - } - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -3382,13 +3283,13 @@ } } }, - "/reports/reconciliation": { + "/staff": { "get": { - "operationId": "getReportsReconciliation", + "operationId": "getStaff", "tags": [ - "reports" + "staff" ], - "summary": "Conciliación financiera CFO (MP ↔ venta ↔ factura Siigo)", + "summary": "Listar staff con acceso al workspace", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -3397,55 +3298,17 @@ ], "parameters": [ { - "name": "date_from", - "in": "query", - "required": true, - "description": "ISO 8601 (inicio, sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "date_to", - "in": "query", - "required": true, - "description": "ISO 8601 (fin)", - "schema": { - "type": "string" - } - }, - { - "name": "match_status", - "in": "query", - "required": false, - "description": "Filtra por flag: OK|MISSING_INVOICE|MISSING_CUFE|AMOUNT_MISMATCH|MISSING_PAYMENT", - "schema": { - "type": "string" - } - }, - { - "name": "provider", - "in": "query", - "required": false, - "description": "Filtra por payment_provider (ej: mercadopago)", - "schema": { - "type": "string" - } - }, - { - "name": "page", + "name": "limit", "in": "query", "required": false, - "description": "Página 1-based (default 1)", "schema": { "type": "string" } }, { - "name": "page_size", + "name": "cursor", "in": "query", "required": false, - "description": "Tamaño de página 1..500 (default 100)", "schema": { "type": "string" } @@ -3462,12 +3325,16 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ReconciliationRow" + "$ref": "#/components/schemas/StaffUser" } + }, + "page": { + "$ref": "#/components/schemas/Page" } }, "required": [ - "data" + "data", + "page" ] } } @@ -3514,66 +3381,44 @@ } } } - } - }, - "/reports/exports/reconciliation": { - "get": { - "operationId": "getReportsExportsReconciliation", + }, + "post": { + "operationId": "postStaff", "tags": [ - "reports" + "staff" ], - "summary": "Export CSV de la conciliación financiera CFO", + "summary": "Crear usuario staff en el workspace", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "parameters": [ - { - "name": "date_from", - "in": "query", - "required": true, - "description": "ISO 8601 (inicio, sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "date_to", - "in": "query", - "required": true, - "description": "ISO 8601 (fin)", - "schema": { - "type": "string" - } - }, - { - "name": "match_status", - "in": "query", - "required": false, - "description": "Filtra por flag: OK|MISSING_INVOICE|MISSING_CUFE|AMOUNT_MISMATCH|MISSING_PAYMENT", - "schema": { - "type": "string" - } - }, - { - "name": "provider", - "in": "query", - "required": false, - "description": "Filtra por payment_provider (ej: mercadopago)", - "schema": { - "type": "string" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StaffCreate" + } } } - ], + }, "responses": { - "200": { + "201": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/StaffUser" + } + }, + "required": [ + "data" + ] } } } @@ -3621,13 +3466,13 @@ } } }, - "/reports/exports/attendees": { - "get": { - "operationId": "getReportsExportsAttendees", + "/staff/{id}/role": { + "patch": { + "operationId": "patchStaffIdRole", "tags": [ - "reports" + "staff" ], - "summary": "Export de asistentes por ticket con filtros (máx 5000)", + "summary": "Cambiar rol de un usuario staff", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -3636,58 +3481,39 @@ ], "parameters": [ { - "name": "event", - "in": "query", - "required": false, - "description": "Filtra por Event.id", - "schema": { - "type": "string" - } - }, - { - "name": "eventDate", - "in": "query", - "required": false, - "description": "Filtra por EventDate.id", - "schema": { - "type": "string" - } - }, - { - "name": "from", - "in": "query", - "required": false, - "description": "ISO 8601 — desde (sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "to", - "in": "query", - "required": false, - "description": "ISO 8601 — hasta (sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "required": false, - "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED (default CONFIRMED)", + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/StaffUser" + } + }, + "required": [ + "data" + ] } } } @@ -3735,14 +3561,14 @@ } } }, - "/reports/exports/buyers": { + "/reports/summary": { "get": { - "operationId": "getReportsExportsBuyers", + "operationId": "getReportsSummary", "tags": [ "reports" ], - "summary": "Export de compradores con filtros (máx 5000)", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "KPIs del workspace (revenue, tickets, ventas, membresías)", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -3750,46 +3576,10 @@ ], "parameters": [ { - "name": "event", - "in": "query", - "required": false, - "description": "Filtra por Event.id", - "schema": { - "type": "string" - } - }, - { - "name": "eventDate", - "in": "query", - "required": false, - "description": "Filtra por EventDate.id", - "schema": { - "type": "string" - } - }, - { - "name": "from", - "in": "query", - "required": false, - "description": "ISO 8601 — desde (sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "to", - "in": "query", - "required": false, - "description": "ISO 8601 — hasta (sobre Sale.created_at)", - "schema": { - "type": "string" - } - }, - { - "name": "status", + "name": "period", "in": "query", "required": false, - "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED (default CONFIRMED)", + "description": "7d | 30d (default) | 90d | 1y", "schema": { "type": "string" } @@ -3801,7 +3591,15 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ReportSummary" + } + }, + "required": [ + "data" + ] } } } @@ -3849,92 +3647,43 @@ } } }, - "/reports/exports/subscribers": { + "/reports/inventory": { "get": { - "operationId": "getReportsExportsSubscribers", + "operationId": "getReportsInventory", "tags": [ "reports" ], - "summary": "Export de suscriptores (máx 5000)", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Inventario agregado por evento/fecha/tipo (capacidad, vendidos, disponibles)", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "401": { - "description": "Credencial inválida o ausente.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Rol insuficiente o recurso no accesible.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } + "parameters": [ + { + "name": "eventId", + "in": "query", + "required": false, + "description": "Filtra por evento", + "schema": { + "type": "string" } }, - "404": { - "description": "Recurso inexistente o fuera de alcance.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } + { + "name": "eventDateId", + "in": "query", + "required": false, + "description": "Filtra por fecha del evento", + "schema": { + "type": "string" } }, - "422": { - "description": "Validación del cuerpo/parámetros.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/reports/by-event": { - "get": { - "operationId": "getReportsByEvent", - "tags": [ - "reports" - ], - "summary": "KPIs de venta por evento del workspace", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", - "security": [ - { - "apiKey": [] - } - ], - "parameters": [ { "name": "from", "in": "query", "required": false, - "description": "ISO 8601, filtra Sale.created_at (gte)", + "description": "Inicio de función ≥ (ISO 8601)", "schema": { "type": "string" } @@ -3943,16 +3692,25 @@ "name": "to", "in": "query", "required": false, - "description": "ISO 8601, filtra Sale.created_at (lte)", + "description": "Inicio de función ≤ (ISO 8601)", "schema": { "type": "string" } }, { - "name": "status", + "name": "includeDrafts", "in": "query", "required": false, - "description": "PENDING|CONFIRMED (default)|ABANDONED|CANCELLED|REFUNDED", + "description": "Incluir eventos/fechas en DRAFT (default false)", + "schema": { + "type": "string" + } + }, + { + "name": "groupBy", + "in": "query", + "required": false, + "description": "event | date | ticketType (default)", "schema": { "type": "string" } @@ -3969,7 +3727,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ReportByEventRow" + "$ref": "#/components/schemas/InventoryRow" } } }, @@ -4023,14 +3781,14 @@ } } }, - "/reports/timeseries": { + "/reports/reconciliation": { "get": { - "operationId": "getReportsTimeseries", + "operationId": "getReportsReconciliation", "tags": [ "reports" ], - "summary": "Serie temporal de ventas CONFIRMED por bucket (day|week|month)", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Conciliación financiera CFO (MP ↔ venta ↔ factura Siigo)", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -4038,37 +3796,55 @@ ], "parameters": [ { - "name": "interval", + "name": "date_from", "in": "query", "required": true, - "description": "day | week | month", + "description": "ISO 8601 (inicio, sobre Sale.created_at)", "schema": { "type": "string" } }, { - "name": "from", + "name": "date_to", + "in": "query", + "required": true, + "description": "ISO 8601 (fin)", + "schema": { + "type": "string" + } + }, + { + "name": "match_status", "in": "query", "required": false, - "description": "ISO 8601, filtra Sale.created_at (gte)", + "description": "Filtra por flag: OK|MISSING_INVOICE|MISSING_CUFE|AMOUNT_MISMATCH|MISSING_PAYMENT", "schema": { "type": "string" } }, { - "name": "to", + "name": "provider", "in": "query", "required": false, - "description": "ISO 8601, filtra Sale.created_at (lte)", + "description": "Filtra por payment_provider (ej: mercadopago)", "schema": { "type": "string" } }, { - "name": "event", + "name": "page", "in": "query", "required": false, - "description": "ID de evento del workspace (opcional)", + "description": "Página 1-based (default 1)", + "schema": { + "type": "string" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "description": "Tamaño de página 1..500 (default 100)", "schema": { "type": "string" } @@ -4085,7 +3861,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ReportTimeseriesRow" + "$ref": "#/components/schemas/ReconciliationRow" } } }, @@ -4139,13 +3915,13 @@ } } }, - "/discounts": { + "/reports/exports/attendees": { "get": { - "operationId": "getDiscounts", + "operationId": "getReportsExportsAttendees", "tags": [ - "discounts" + "reports" ], - "summary": "Listar cupones de descuento del workspace", + "summary": "Export de asistentes por ticket con filtros (máx 5000)", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -4157,32 +3933,43 @@ "name": "event", "in": "query", "required": false, - "description": "id de evento para filtrar", + "description": "Filtra por Event.id", "schema": { "type": "string" } }, { - "name": "active", + "name": "eventDate", "in": "query", "required": false, - "description": "true|false", + "description": "Filtra por EventDate.id", "schema": { "type": "string" } }, { - "name": "limit", + "name": "from", "in": "query", "required": false, + "description": "ISO 8601 — desde (sobre Sale.created_at)", "schema": { "type": "string" } }, { - "name": "cursor", + "name": "to", + "in": "query", + "required": false, + "description": "ISO 8601 — hasta (sobre Sale.created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "status", "in": "query", "required": false, + "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED (default CONFIRMED)", "schema": { "type": "string" } @@ -4194,22 +3981,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Discount" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "data", - "page" - ] + "type": "object" } } } @@ -4255,44 +4027,75 @@ } } } - }, - "post": { - "operationId": "postDiscounts", + } + }, + "/reports/exports/buyers": { + "get": { + "operationId": "getReportsExportsBuyers", "tags": [ - "discounts" + "reports" ], - "summary": "Crear cupón de descuento", + "summary": "Export de compradores con filtros (máx 5000)", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DiscountCreate" - } + "parameters": [ + { + "name": "event", + "in": "query", + "required": false, + "description": "Filtra por Event.id", + "schema": { + "type": "string" + } + }, + { + "name": "eventDate", + "in": "query", + "required": false, + "description": "Filtra por EventDate.id", + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "ISO 8601 — desde (sobre Sale.created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "ISO 8601 — hasta (sobre Sale.created_at)", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "PENDING|CONFIRMED|ABANDONED|CANCELLED|REFUNDED (default CONFIRMED)", + "schema": { + "type": "string" } } - }, + ], "responses": { - "201": { + "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/Discount" - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -4340,14 +4143,14 @@ } } }, - "/discounts/{id}": { - "patch": { - "operationId": "patchDiscountsId", + "/reports/by-event": { + "get": { + "operationId": "getReportsByEvent", "tags": [ - "discounts" + "reports" ], - "summary": "Actualizar cupón de descuento", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "KPIs de venta por evento del workspace", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -4355,24 +4158,33 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "from", + "in": "query", + "required": false, + "description": "ISO 8601, filtra Sale.created_at (gte)", "schema": { "type": "string" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DiscountUpdate" - } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "ISO 8601, filtra Sale.created_at (lte)", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "PENDING|CONFIRMED (default)|ABANDONED|CANCELLED|REFUNDED", + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "OK", @@ -4382,7 +4194,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/Discount" + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportByEventRow" + } } }, "required": [ @@ -4433,14 +4248,16 @@ } } } - }, - "delete": { - "operationId": "deleteDiscountsId", + } + }, + "/reports/timeseries": { + "get": { + "operationId": "getReportsTimeseries", "tags": [ - "discounts" + "reports" ], - "summary": "Eliminar (soft) cupón de descuento", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Serie temporal de ventas CONFIRMED por bucket (day|week|month)", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -4448,9 +4265,37 @@ ], "parameters": [ { - "name": "id", - "in": "path", + "name": "interval", + "in": "query", "required": true, + "description": "day | week | month", + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "ISO 8601, filtra Sale.created_at (gte)", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "ISO 8601, filtra Sale.created_at (lte)", + "schema": { + "type": "string" + } + }, + { + "name": "event", + "in": "query", + "required": false, + "description": "ID de evento del workspace (opcional)", "schema": { "type": "string" } @@ -4462,7 +4307,18 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportTimeseriesRow" + } + } + }, + "required": [ + "data" + ] } } } @@ -4510,13 +4366,13 @@ } } }, - "/webhooks": { + "/discounts": { "get": { - "operationId": "getWebhooks", + "operationId": "getDiscounts", "tags": [ - "webhooks" + "discounts" ], - "summary": "Listar webhooks del workspace", + "summary": "Listar cupones de descuento del workspace", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -4524,6 +4380,24 @@ } ], "parameters": [ + { + "name": "event", + "in": "query", + "required": false, + "description": "id de evento para filtrar", + "schema": { + "type": "string" + } + }, + { + "name": "active", + "in": "query", + "required": false, + "description": "true|false", + "schema": { + "type": "string" + } + }, { "name": "limit", "in": "query", @@ -4552,7 +4426,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/Webhook" + "$ref": "#/components/schemas/Discount" } }, "page": { @@ -4610,11 +4484,11 @@ } }, "post": { - "operationId": "postWebhooks", + "operationId": "postDiscounts", "tags": [ - "webhooks" + "discounts" ], - "summary": "Crear webhook (devuelve el signingSecret una única vez)", + "summary": "Crear cupón de descuento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -4626,7 +4500,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WebhookCreate" + "$ref": "#/components/schemas/DiscountCreate" } } } @@ -4640,7 +4514,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/WebhookCreated" + "$ref": "#/components/schemas/Discount" } }, "required": [ @@ -4693,13 +4567,13 @@ } } }, - "/webhooks/{id}": { - "delete": { - "operationId": "deleteWebhooksId", + "/discounts/{id}": { + "patch": { + "operationId": "patchDiscountsId", "tags": [ - "webhooks" + "discounts" ], - "summary": "Eliminar un webhook del workspace", + "summary": "Actualizar cupón de descuento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -4716,13 +4590,31 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DiscountUpdate" + } + } + } + }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Discount" + } + }, + "required": [ + "data" + ] } } } @@ -4768,16 +4660,13 @@ } } } - } - }, - "/membership-plans/{id}/subscribers": { - "get": { - "operationId": "getMembershipPlansIdSubscribers", + }, + "delete": { + "operationId": "deleteDiscountsId", "tags": [ - "membership-plans", - "subscriptions" + "discounts" ], - "summary": "Listar suscriptores de un plan de membresía", + "summary": "Eliminar (soft) cupón de descuento", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { @@ -4792,16 +4681,76 @@ "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } }, - { - "name": "status", - "in": "query", - "required": false, - "description": "PENDING|ACTIVE|EXPIRED|CANCELLED|PAUSED", - "schema": { - "type": "string" + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/webhooks": { + "get": { + "operationId": "getWebhooks", + "tags": [ + "webhooks" + ], + "summary": "Listar webhooks del workspace", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "security": [ + { + "apiKey": [] + } + ], + "parameters": [ { "name": "limit", "in": "query", @@ -4830,7 +4779,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/Subscriber" + "$ref": "#/components/schemas/Webhook" } }, "page": { @@ -4886,34 +4835,31 @@ } } } - } - }, - "/subscriptions/{id}/cancel": { + }, "post": { - "operationId": "postSubscriptionsIdCancel", + "operationId": "postWebhooks", "tags": [ - "subscriptions", - "membership-plans" + "webhooks" ], - "summary": "Cancelar una suscripción de membresía", + "summary": "Crear webhook (devuelve el signingSecret una única vez)", "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCreate" + } } } - ], + }, "responses": { - "200": { + "201": { "description": "OK", "content": { "application/json": { @@ -4921,7 +4867,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SubscriptionCancelResult" + "$ref": "#/components/schemas/WebhookCreated" } }, "required": [ @@ -4974,14 +4920,14 @@ } } }, - "/sales/{id}/tickets": { - "get": { - "operationId": "getSalesIdTickets", + "/webhooks/{id}": { + "delete": { + "operationId": "deleteWebhooksId", "tags": [ - "sales" + "webhooks" ], - "summary": "Tickets individuales de una venta", - "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Eliminar un webhook del workspace", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5003,18 +4949,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SaleTicket" - } - } - }, - "required": [ - "data" - ] + "type": "object" } } } @@ -5062,14 +4997,15 @@ } } }, - "/tickets/{ticketCode}/checkin": { - "post": { - "operationId": "postTicketsTicketCodeCheckin", + "/membership-plans/{id}/subscribers": { + "get": { + "operationId": "getMembershipPlansIdSubscribers", "tags": [ - "tickets" + "membership-plans", + "subscriptions" ], - "summary": "Registrar el ingreso (check-in) de un ticket en puerta", - "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Listar suscriptores de un plan de membresía", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5077,12 +5013,37 @@ ], "parameters": [ { - "name": "ticketCode", + "name": "id", "in": "path", "required": true, "schema": { "type": "string" } + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "PENDING|ACTIVE|EXPIRED|CANCELLED|PAUSED", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -5094,11 +5055,18 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/CheckinResult" + "type": "array", + "items": { + "$ref": "#/components/schemas/Subscriber" + } + }, + "page": { + "$ref": "#/components/schemas/Page" } }, "required": [ - "data" + "data", + "page" ] } } @@ -5147,14 +5115,15 @@ } } }, - "/tickets/{ticketCode}/access": { - "get": { - "operationId": "getTicketsTicketCodeAccess", + "/subscriptions/{id}/cancel": { + "post": { + "operationId": "postSubscriptionsIdCancel", "tags": [ - "tickets" + "subscriptions", + "membership-plans" ], - "summary": "Consultar la validez de un ticket sin consumirlo", - "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Cancelar una suscripción de membresía", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5162,7 +5131,7 @@ ], "parameters": [ { - "name": "ticketCode", + "name": "id", "in": "path", "required": true, "schema": { @@ -5179,7 +5148,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/TicketAccess" + "$ref": "#/components/schemas/SubscriptionCancelResult" } }, "required": [ @@ -5232,14 +5201,14 @@ } } }, - "/api-keys": { + "/sales/{id}/tickets": { "get": { - "operationId": "getApiKeys", + "operationId": "getSalesIdTickets", "tags": [ - "api-keys" + "sales" ], - "summary": "Listar mis API keys", - "description": "Lista las API keys del usuario autenticado (nunca el hash ni el secreto en claro).", + "summary": "Tickets individuales de una venta", + "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5247,17 +5216,9 @@ ], "parameters": [ { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "cursor", - "in": "query", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -5274,16 +5235,12 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/ApiKey" + "$ref": "#/components/schemas/SaleTicket" } - }, - "page": { - "$ref": "#/components/schemas/Page" } }, "required": [ - "data", - "page" + "data" ] } } @@ -5330,31 +5287,33 @@ } } } - }, + } + }, + "/tickets/{ticketCode}/resend": { "post": { - "operationId": "postApiKeys", + "operationId": "postTicketsTicketCodeResend", "tags": [ - "api-keys" + "tickets" ], - "summary": "Mintear una API key (devuelve el plano una única vez)", - "description": "Crea una API key de servicio para el usuario autenticado. scope=\"read\" (default) solo permite GET/HEAD; scope=\"write\" habilita mutaciones según el rol del usuario. El secreto en claro solo aparece en esta respuesta.", + "summary": "Reenviar el correo de confirmación con el ticket", + "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiKeyCreate" - } + "parameters": [ + { + "name": "ticketCode", + "in": "path", + "required": true, + "schema": { + "type": "string" } } - }, + ], "responses": { - "201": { + "200": { "description": "OK", "content": { "application/json": { @@ -5362,7 +5321,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/ApiKeyCreated" + "$ref": "#/components/schemas/ResendTicket" } }, "required": [ @@ -5415,14 +5374,14 @@ } } }, - "/api-keys/{id}": { - "delete": { - "operationId": "deleteApiKeysId", + "/tickets/{ticketCode}/checkin": { + "post": { + "operationId": "postTicketsTicketCodeCheckin", "tags": [ - "api-keys" + "tickets" ], - "summary": "Revocar una de mis API keys", - "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Registrar el ingreso (check-in) de un ticket en puerta", + "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5430,7 +5389,7 @@ ], "parameters": [ { - "name": "id", + "name": "ticketCode", "in": "path", "required": true, "schema": { @@ -5444,7 +5403,15 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckinResult" + } + }, + "required": [ + "data" + ] } } } @@ -5492,14 +5459,14 @@ } } }, - "/tickets/{ticketCode}/resend": { - "post": { - "operationId": "postTicketsTicketCodeResend", + "/tickets/{ticketCode}/access": { + "get": { + "operationId": "getTicketsTicketCodeAccess", "tags": [ "tickets" ], - "summary": "Reenviar el correo de confirmación con el ticket", - "description": "Rol mínimo: `ADMIN`. Workspace activo vía header `X-Workspace-Id`.", + "summary": "Consultar la validez de un ticket sin consumirlo", + "description": "Rol mínimo: `STAFF`. Workspace activo vía header `X-Workspace-Id`.", "security": [ { "apiKey": [] @@ -5524,7 +5491,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/ResendTicket" + "$ref": "#/components/schemas/TicketAccess" } }, "required": [ @@ -5576,258 +5543,1031 @@ } } } - } - }, - "components": { - "schemas": { - "Error": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - }, - "details": {} - }, - "required": [ - "code", - "message" - ], - "additionalProperties": false - } - }, - "required": [ - "error" + }, + "/api-keys": { + "get": { + "operationId": "getApiKeys", + "tags": [ + "api-keys" ], - "additionalProperties": false - }, - "Page": { - "type": "object", - "properties": { - "nextCursor": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "hasMore": { - "type": "boolean" + "summary": "Listar mis API keys", + "description": "Lista las API keys del usuario autenticado (nunca el hash ni el secreto en claro).", + "security": [ + { + "apiKey": [] } - }, - "required": [ - "nextCursor", - "hasMore" ], - "additionalProperties": false - }, - "Workspace": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } }, - "slug": { - "type": "string" + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } - }, - "required": [ - "id", - "name", - "slug" ], - "additionalProperties": false - }, - "Me": { - "type": "object", - "properties": { - "userId": { - "type": "string" - }, - "name": { - "type": "string" + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApiKey" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] + } + } + } }, - "email": { - "type": "string" + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "role": { - "type": "string", - "enum": [ - "SUPER_ADMIN", - "ADMIN", - "STAFF", - "VIEWER", - "MINCULTURA" - ] + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "activeWorkspaceId": { - "type": "string" + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "postApiKeys", + "tags": [ + "api-keys" + ], + "summary": "Mintear una API key (devuelve el plano una única vez)", + "description": "Crea una API key de servicio para el usuario autenticado. scope=\"read\" (default) solo permite GET/HEAD; scope=\"write\" habilita mutaciones según el rol del usuario. El secreto en claro solo aparece en esta respuesta.", + "security": [ + { + "apiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiKeyCreate" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiKeyCreated" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api-keys/{id}": { + "delete": { + "operationId": "deleteApiKeysId", + "tags": [ + "api-keys" + ], + "summary": "Revocar una de mis API keys", + "description": "Rol mínimo: `VIEWER`. Workspace activo vía header `X-Workspace-Id`.", + "security": [ + { + "apiKey": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/customer/me": { + "get": { + "operationId": "getCustomerMe", + "tags": [ + "customer" + ], + "summary": "Identidad del comprador autenticado (SSO headless)", + "description": "Requiere API key de servicio enterprise Y el header `X-Customer-Session` con el session_token obtenido en el canje del SSO headless.", + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CustomerMe" + } + }, + "required": [ + "data" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/customer/tickets": { + "get": { + "operationId": "getCustomerTickets", + "tags": [ + "customer" + ], + "summary": "Entradas del comprador en el alcance de la key (SSO headless)", + "description": "Requiere API key de servicio enterprise Y el header `X-Customer-Session`. Devuelve solo ventas CONFIRMED, matcheadas por cuenta del comprador o correo de compra, de eventos que el workspace pineado a la key puede leer: los propios Y aquellos donde figura como artista vinculado.", + "security": [ + { + "apiKey": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerSale" + } + }, + "page": { + "$ref": "#/components/schemas/Page" + } + }, + "required": [ + "data", + "page" + ] + } + } + } + }, + "401": { + "description": "Credencial inválida o ausente.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Rol insuficiente o recurso no accesible.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Recurso inexistente o fuera de alcance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Validación del cuerpo/parámetros.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/customer-auth/enterprise-exchange": { + "post": { + "operationId": "postApiCustomerAuthEnterpriseExchange", + "tags": [ + "customer" + ], + "summary": "Canjear el one-time token del SSO headless por un session token", + "description": "Server-to-server: exige una API key de servicio enterprise (Bearer o x-api-key). Canjea el one-time token entregado por /api/customer-auth/enterprise-return (single-use, TTL 60s) por el session token del comprador. Body plano, sin envoltura { data }.", + "security": [ + { + "apiKey": [] + } + ], + "servers": [ + { + "url": "/", + "description": "Host canónico (fuera de /api/v1)" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnterpriseExchangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Sesión del comprador canjeada.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnterpriseExchangeResponse" + } + } + } + }, + "401": { + "description": "API key inválida/ausente, o token inválido/expirado." + }, + "403": { + "description": "La API key no pertenece a un user de servicio enterprise, o el token fue minteado para otro workspace." + }, + "422": { + "description": "Cuerpo inválido." + } + } + } + } + }, + "components": { + "schemas": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "details": {} + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "Page": { + "type": "object", + "properties": { + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "nextCursor", + "hasMore" + ], + "additionalProperties": false + }, + "Workspace": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug" + ], + "additionalProperties": false + }, + "Me": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "SUPER_ADMIN", + "ADMIN", + "STAFF", + "VIEWER", + "MINCULTURA" + ] + }, + "activeWorkspaceId": { + "type": "string" + }, + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Workspace" + } + } + }, + "required": [ + "userId", + "name", + "email", + "role", + "activeWorkspaceId", + "workspaces" + ], + "additionalProperties": false + }, + "DeviceCodeResponse": { + "type": "object", + "properties": { + "device_code": { + "type": "string" + }, + "user_code": { + "type": "string" + }, + "verification_uri": { + "type": "string" + }, + "verification_uri_complete": { + "type": "string" + }, + "expires_in": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "interval": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "device_code", + "user_code", + "verification_uri", + "verification_uri_complete", + "expires_in", + "interval" + ], + "additionalProperties": false + }, + "DeviceTokenRequest": { + "type": "object", + "properties": { + "device_code": { + "type": "string", + "minLength": 1 + }, + "grant_type": { + "type": "string", + "const": "urn:ietf:params:oauth:grant-type:device_code" + } + }, + "required": [ + "device_code" + ], + "additionalProperties": false + }, + "DeviceTokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string", + "const": "Bearer" + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "SUPER_ADMIN", + "ADMIN", + "STAFF", + "VIEWER", + "MINCULTURA" + ] + } + }, + "required": [ + "id", + "email", + "role" + ], + "additionalProperties": false + }, + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Workspace" + } + } + }, + "required": [ + "access_token", + "token_type", + "user", + "workspaces" + ], + "additionalProperties": false + }, + "DeviceTokenError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "authorization_pending", + "slow_down", + "expired_token", + "access_denied", + "invalid_request" + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "Venue": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "country": { + "type": "string" + }, + "latitude": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "longitude": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "capacity": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "googlePlaceId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "portalVisible": { + "type": "boolean" + }, + "organizationId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "seatsioChartKey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, - "workspaces": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Workspace" - } + "createdAt": { + "type": "string" } }, "required": [ - "userId", + "id", "name", - "email", - "role", - "activeWorkspaceId", - "workspaces" + "slug", + "address", + "city", + "country", + "latitude", + "longitude", + "capacity", + "googlePlaceId", + "portalVisible", + "organizationId", + "seatsioChartKey", + "createdAt" ], "additionalProperties": false }, - "DeviceCodeResponse": { + "Event": { "type": "object", "properties": { - "device_code": { - "type": "string" - }, - "user_code": { + "id": { "type": "string" }, - "verification_uri": { + "name": { "type": "string" }, - "verification_uri_complete": { + "slug": { "type": "string" }, - "expires_in": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, - "interval": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - }, - "required": [ - "device_code", - "user_code", - "verification_uri", - "verification_uri_complete", - "expires_in", - "interval" - ], - "additionalProperties": false - }, - "DeviceTokenRequest": { - "type": "object", - "properties": { - "device_code": { + "status": { "type": "string", - "minLength": 1 + "enum": [ + "DRAFT", + "PUBLISHED", + "SOLD_OUT", + "CANCELLED", + "COMPLETED" + ] }, - "grant_type": { - "type": "string", - "const": "urn:ietf:params:oauth:grant-type:device_code" - } - }, - "required": [ - "device_code" - ], - "additionalProperties": false - }, - "DeviceTokenResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" + "coverImageUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, - "token_type": { - "type": "string", - "const": "Bearer" + "bannerImageUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, - "user": { - "type": "object", - "properties": { - "id": { + "squareImageUrl": { + "anyOf": [ + { "type": "string" }, - "email": { + { + "type": "null" + } + ] + }, + "storyImageUrl": { + "anyOf": [ + { "type": "string" }, - "role": { - "type": "string", - "enum": [ - "SUPER_ADMIN", - "ADMIN", - "STAFF", - "VIEWER", - "MINCULTURA" - ] + { + "type": "null" } - }, - "required": [ - "id", - "email", - "role" - ], - "additionalProperties": false + ] }, - "workspaces": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Workspace" - } - } - }, - "required": [ - "access_token", - "token_type", - "user", - "workspaces" - ], - "additionalProperties": false - }, - "DeviceTokenError": { - "type": "object", - "properties": { - "error": { + "organizationId": { + "type": "string" + }, + "venueId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "venue": { + "anyOf": [ + { + "$ref": "#/components/schemas/Venue" + }, + { + "type": "null" + } + ] + }, + "externalTicketUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "access": { "type": "string", "enum": [ - "authorization_pending", - "slow_down", - "expired_token", - "access_denied", - "invalid_request" + "owner", + "artist" ] + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" } }, "required": [ - "error" + "id", + "name", + "slug", + "description", + "status", + "coverImageUrl", + "bannerImageUrl", + "squareImageUrl", + "storyImageUrl", + "organizationId", + "venueId", + "venue", + "externalTicketUrl", + "access", + "createdAt", + "updatedAt" ], "additionalProperties": false }, - "Event": { + "TicketType": { "type": "object", "properties": { "id": { "type": "string" }, + "eventDateId": { + "type": "string" + }, "name": { "type": "string" }, - "slug": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "price": { + "type": "number" + }, + "currency": { "type": "string" }, - "description": { + "capacity": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "sold": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "available": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "soldOut": { + "type": "boolean" + }, + "maxPerOrder": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "saleStartsAt": { "anyOf": [ { "type": "string" @@ -5837,17 +6577,7 @@ } ] }, - "status": { - "type": "string", - "enum": [ - "DRAFT", - "PUBLISHED", - "SOLD_OUT", - "CANCELLED", - "COMPLETED" - ] - }, - "coverImageUrl": { + "saleEndsAt": { "anyOf": [ { "type": "string" @@ -5857,10 +6587,22 @@ } ] }, - "organizationId": { - "type": "string" + "isVisible": { + "type": "boolean" }, - "venueId": { + "organizerAbsorbsFee": { + "type": "boolean" + }, + "buyerServiceFee": { + "type": "number" + }, + "iva": { + "type": "number" + }, + "buyerTotal": { + "type": "number" + }, + "seatsioCategoryKey": { "anyOf": [ { "type": "string" @@ -5872,22 +6614,29 @@ }, "createdAt": { "type": "string" - }, - "updatedAt": { - "type": "string" } }, "required": [ "id", + "eventDateId", "name", - "slug", "description", - "status", - "coverImageUrl", - "organizationId", - "venueId", - "createdAt", - "updatedAt" + "price", + "currency", + "capacity", + "sold", + "available", + "soldOut", + "maxPerOrder", + "saleStartsAt", + "saleEndsAt", + "isVisible", + "organizerAbsorbsFee", + "buyerServiceFee", + "iva", + "buyerTotal", + "seatsioCategoryKey", + "createdAt" ], "additionalProperties": false }, @@ -5946,6 +6695,33 @@ } ] }, + "venue": { + "anyOf": [ + { + "$ref": "#/components/schemas/Venue" + }, + { + "type": "null" + } + ] + }, + "ticketTypes": { + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/TicketType" + } + }, + "seatsioEventKey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, "status": { "type": "string", "enum": [ @@ -5972,6 +6748,9 @@ "doorsOpenAt", "timezone", "venueId", + "venue", + "ticketTypes", + "seatsioEventKey", "status", "streamType", "createdAt" @@ -6210,91 +6989,6 @@ }, "additionalProperties": false }, - "TicketType": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "eventDateId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "price": { - "type": "number" - }, - "currency": { - "type": "string" - }, - "capacity": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "maxPerOrder": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "saleStartsAt": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "saleEndsAt": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "isVisible": { - "type": "boolean" - }, - "organizerAbsorbsFee": { - "type": "boolean" - }, - "createdAt": { - "type": "string" - } - }, - "required": [ - "id", - "eventDateId", - "name", - "description", - "price", - "currency", - "capacity", - "maxPerOrder", - "saleStartsAt", - "saleEndsAt", - "isVisible", - "organizerAbsorbsFee", - "createdAt" - ], - "additionalProperties": false - }, "TicketTypeCreate": { "type": "object", "properties": { @@ -6863,157 +7557,60 @@ "MembershipPlanUpdate": { "type": "object", "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "price": { - "type": "number", - "minimum": 0 - }, - "currency": { - "type": "string" - }, - "billingCycle": { - "type": "string", - "enum": [ - "MONTHLY", - "QUARTERLY", - "ANNUAL", - "LIFETIME" - ] - }, - "benefitPresale": { - "type": "boolean" - }, - "benefitFreeTicket": { - "type": "boolean" - }, - "benefitDiscount": { - "type": "boolean" - }, - "benefitExclusiveContent": { - "type": "boolean" - }, - "benefitMerch": { - "type": "boolean" - }, - "isActive": { - "type": "boolean" - }, - "sortOrder": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - } - }, - "additionalProperties": false - }, - "Venue": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "address": { - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "latitude": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "longitude": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] + "name": { + "type": "string", + "minLength": 1 }, - "capacity": { + "description": { "anyOf": [ { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 + "type": "string" }, { "type": "null" } ] }, - "googlePlaceId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } + "price": { + "type": "number", + "minimum": 0 + }, + "currency": { + "type": "string" + }, + "billingCycle": { + "type": "string", + "enum": [ + "MONTHLY", + "QUARTERLY", + "ANNUAL", + "LIFETIME" ] }, - "portalVisible": { + "benefitPresale": { "type": "boolean" }, - "organizationId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] + "benefitFreeTicket": { + "type": "boolean" }, - "createdAt": { - "type": "string" + "benefitDiscount": { + "type": "boolean" + }, + "benefitExclusiveContent": { + "type": "boolean" + }, + "benefitMerch": { + "type": "boolean" + }, + "isActive": { + "type": "boolean" + }, + "sortOrder": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 } }, - "required": [ - "id", - "name", - "slug", - "address", - "city", - "country", - "latitude", - "longitude", - "capacity", - "googlePlaceId", - "portalVisible", - "organizationId", - "createdAt" - ], "additionalProperties": false }, "VenueCreate": { @@ -7367,7 +7964,238 @@ "sale_amount": { "type": "number" }, - "mp_payment_ref": { + "mp_payment_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "mp_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "mp_amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "siigo_invoice_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "siigo_invoice_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "siigo_cufe": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "match_status": { + "$ref": "#/components/schemas/MatchStatus" + } + }, + "required": [ + "sale_reference", + "sale_status", + "sale_amount", + "mp_payment_ref", + "mp_provider", + "mp_amount", + "siigo_invoice_name", + "siigo_invoice_status", + "siigo_cufe", + "match_status" + ], + "additionalProperties": false + }, + "Settlement": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "label": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "AWAITING_PAYMENT", + "PAID" + ] + }, + "amount": { + "type": "number" + }, + "grossAmount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "unitsSold": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "currency": { + "type": "string" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "rejectionReason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "event": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "eventDate": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "startsAt": { + "type": "string" + } + }, + "required": [ + "id", + "startsAt" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "hasDocument": { + "type": "boolean" + }, + "paymentProofs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fileName": { + "type": "string" + } + }, + "required": [ + "fileName" + ], + "additionalProperties": false + } + }, + "allocations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bankAccountId": { + "type": "string" + }, + "percentage": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "bankAccountId", + "percentage" + ], + "additionalProperties": false + } + }, + "requestedAt": { "anyOf": [ { "type": "string" @@ -7377,7 +8205,7 @@ } ] }, - "mp_provider": { + "paidAt": { "anyOf": [ { "type": "string" @@ -7387,27 +8215,92 @@ } ] }, - "mp_amount": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "reference", + "label", + "status", + "amount", + "grossAmount", + "unitsSold", + "currency", + "notes", + "rejectionReason", + "event", + "eventDate", + "hasDocument", + "paymentProofs", + "allocations", + "requestedAt", + "paidAt", + "createdAt" + ], + "additionalProperties": false + }, + "ReportFinancialsRow": { + "type": "object", + "properties": { + "eventId": { + "type": "string" }, - "siigo_invoice_name": { + "eventName": { + "type": "string" + }, + "eventDateId": { + "type": "string" + }, + "startsAt": { + "type": "string" + }, + "past": { + "type": "boolean" + }, + "unitsSold": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "gross": { + "type": "number" + }, + "platformFee": { + "type": "number" + }, + "facial": { + "type": "number" + }, + "paymentFee": { + "type": "number" + }, + "gmf": { + "type": "number" + }, + "net": { + "type": "number" + }, + "currency": { + "type": "string" + }, + "settlementStatus": { "anyOf": [ { - "type": "string" + "type": "string", + "enum": [ + "SENT", + "AWAITING_PAYMENT", + "PAID" + ] }, { "type": "null" } ] }, - "siigo_invoice_status": { + "settlementId": { "anyOf": [ { "type": "string" @@ -7417,31 +8310,34 @@ } ] }, - "siigo_cufe": { + "settlementAmount": { "anyOf": [ { - "type": "string" + "type": "number" }, { "type": "null" } ] - }, - "match_status": { - "$ref": "#/components/schemas/MatchStatus" } }, "required": [ - "sale_reference", - "sale_status", - "sale_amount", - "mp_payment_ref", - "mp_provider", - "mp_amount", - "siigo_invoice_name", - "siigo_invoice_status", - "siigo_cufe", - "match_status" + "eventId", + "eventName", + "eventDateId", + "startsAt", + "past", + "unitsSold", + "gross", + "platformFee", + "facial", + "paymentFee", + "gmf", + "net", + "currency", + "settlementStatus", + "settlementId", + "settlementAmount" ], "additionalProperties": false }, @@ -8041,6 +8937,26 @@ ], "additionalProperties": false }, + "ResendTicket": { + "type": "object", + "properties": { + "ticketCode": { + "type": "string" + }, + "sentTo": { + "type": "string" + }, + "sentAt": { + "type": "string" + } + }, + "required": [ + "ticketCode", + "sentTo", + "sentAt" + ], + "additionalProperties": false + }, "CheckinResult": { "type": "object", "properties": { @@ -8319,23 +9235,191 @@ ], "additionalProperties": false }, - "ResendTicket": { + "CustomerMe": { "type": "object", "properties": { - "ticketCode": { + "id": { "type": "string" }, - "sentTo": { + "email": { "type": "string" }, - "sentAt": { + "name": { + "type": "string" + }, + "emailVerified": { + "type": "boolean" + } + }, + "required": [ + "id", + "email", + "name", + "emailVerified" + ], + "additionalProperties": false + }, + "CustomerTicketItem": { + "type": "object", + "properties": { + "ticketTypeName": { "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "ticketCode": { + "type": "string", + "description": "Código opaco del ticket. El QR debe codificar la URL del validador (`https://admin.appfreeticket.com/dashboard/tickets/validar?code=`)." + }, + "seatLabel": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Silla exacta en eventos numerados (seats.io), o null." } }, "required": [ + "ticketTypeName", + "quantity", "ticketCode", - "sentTo", - "sentAt" + "seatLabel" + ], + "additionalProperties": false + }, + "CustomerSale": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "status": { + "type": "string" + }, + "event": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "name", + "slug" + ], + "additionalProperties": false + }, + "eventDate": { + "type": "object", + "properties": { + "startsAt": { + "type": "string", + "description": "Instante del evento en ISO 8601 UTC." + }, + "timezone": { + "type": "string", + "description": "Zona horaria IANA del evento." + } + }, + "required": [ + "startsAt", + "timezone" + ], + "additionalProperties": false + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerTicketItem" + } + }, + "total": { + "type": "number" + }, + "currency": { + "type": "string" + } + }, + "required": [ + "id", + "reference", + "status", + "event", + "eventDate", + "items", + "total", + "currency" + ], + "additionalProperties": false + }, + "EnterpriseExchangeRequest": { + "type": "object", + "properties": { + "token": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "One-time token recibido en el callback de la landing (formato `{slug}.{token}`, se reenvía tal cual)." + } + }, + "required": [ + "token" + ], + "additionalProperties": false + }, + "EnterpriseExchangeResponse": { + "type": "object", + "properties": { + "session_token": { + "type": "string", + "description": "Token de sesión del comprador. Usable como header `X-Customer-Session` en /api/v1/customer/*." + }, + "expires_at": { + "type": "string", + "description": "Expiración de la sesión (ISO 8601, sliding de 7 días)." + }, + "customer": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email_verified": { + "type": "boolean" + } + }, + "required": [ + "id", + "email", + "name", + "email_verified" + ], + "additionalProperties": false + } + }, + "required": [ + "session_token", + "expires_at", + "customer" ], "additionalProperties": false } diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 4d782a3..ac7a6b8 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -2,11 +2,13 @@ import chalk from "chalk"; import type { Command } from "commander"; import { + deleteTokensId, getAuditLog, getFeatureFlags, getMe, getPlatformPlans, getPlatformPlansId, + getTokens, getUsers, getUsersId, getWorkspaces, @@ -17,6 +19,7 @@ import { postImpersonate, postImpersonateStop, postPlatformPlans, + postTokens, postWorkspaces, postWorkspacesIdRestore, postWorkspacesIdSuspend, @@ -24,7 +27,7 @@ import { } from "../admin-client/sdk.gen"; import { configureAdminClient, unwrap } from "../lib/api"; import { CONFIG_PATH, loadConfig, saveConfig } from "../lib/config"; -import { confirm, parseData } from "../lib/input"; +import { confirmOrExit, parseData } from "../lib/input"; import { print, printNextCursor, toCsv } from "../lib/output"; type SdkFn = ( @@ -223,6 +226,25 @@ export function registerAdmin(program: Command): void { columns: ["key", "enabled", "description"], noPaging: true, }, + { + // Platform service tokens (PAT): headless credential for `ft admin` in + // CI, minted from an interactive SUPER_ADMIN session. The plaintext is + // only ever returned by `create` — store it right away. + name: "tokens", + describe: "Platform service tokens (PAT)", + list: getTokens, + create: postTokens, + actions: [ + { + name: "revoke", + describe: "Revoke a service token", + fn: deleteTokensId, + confirm: true, + }, + ], + columns: ["id", "name", "lastUsedAt", "expiresAt", "createdAt"], + noPaging: true, + }, { name: "audit-log", describe: "Superadmin audit log", @@ -330,13 +352,8 @@ function registerAdminResource(parent: Command, spec: AdminResource): void { cmd.option("--data ", "JSON body (inline or @file.json)"); } cmd.action(async (value, opts) => { - if ( - action.confirm && - !opts.yes && - !(await confirm(`${action.name} ${singular} ${value}?`)) - ) { - console.error("Aborted."); - return; + if (action.confirm) { + await confirmOrExit(`${action.name} ${singular} ${value}?`, opts.yes); } configureAdminClient(); const payload: Record = { path: { [param]: value } }; diff --git a/src/commands/api-keys.ts b/src/commands/api-keys.ts index a5d644f..75df7d1 100644 --- a/src/commands/api-keys.ts +++ b/src/commands/api-keys.ts @@ -3,7 +3,7 @@ import type { Command } from "commander"; import { deleteApiKeysId, getApiKeys, postApiKeys } from "../client/sdk.gen"; import type { ApiKeyScope } from "../client/types.gen"; import { configureClient, fail, unwrap } from "../lib/api"; -import { confirm } from "../lib/input"; +import { confirmOrExit } from "../lib/input"; import { print, printNextCursor, resolveColumns, toCsv } from "../lib/output"; /** Columns shown in `api-keys list` — never the secret (the API never returns it). */ @@ -132,10 +132,7 @@ export function registerApiKeys(program: Command): void { .option("--workspace ", "workspace override") .option("--json", "raw JSON output") .action(async (id, opts) => { - if (!opts.yes && !(await confirm(`Revoke API key ${id}?`))) { - console.error("Aborted."); - return; - } + await confirmOrExit(`Revoke API key ${id}?`, opts.yes); configureClient(opts.workspace); const body = unwrap(await deleteApiKeysId({ path: { id } })); print(body?.data ?? { revoked: id }, { json: opts.json }); diff --git a/src/commands/auth.ts b/src/commands/auth.ts index f6f02e1..2864faa 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -26,6 +26,7 @@ export function registerAuth(program: Command): void { "API base URL (default: https://admin.appfreeticket.com)", ) .option("--workspace ", "default active workspace") + .option("--json", "raw JSON output (confirmation goes to stderr)") .action(async (opts) => { if (opts.url) saveConfig({ apiUrl: opts.url }); @@ -42,10 +43,11 @@ export function registerAuth(program: Command): void { // Verify the credential against /me before reporting success. configureClient(workspaceId); const me = unwrap(await getMe({})).data; - console.log( + // stderr so `ft login --json | jq` stays parseable. + console.error( `${chalk.green("✓")} Session saved in ${chalk.dim(CONFIG_PATH)}`, ); - print(me, {}); + print(me, { json: opts.json }); }); program @@ -71,7 +73,8 @@ export function registerAuth(program: Command): void { program .command("config") .description("Show active configuration (the credential is masked)") - .action(() => { + .option("--json", "raw JSON output") + .action((opts) => { const cfg = loadConfig(); print( { @@ -80,7 +83,7 @@ export function registerAuth(program: Command): void { session: cfg.apiKey ? `${cfg.apiKey.slice(0, 12)}…` : null, workspaceId: cfg.workspaceId ?? null, }, - {}, + { json: opts.json }, ); }); } diff --git a/src/commands/event-dates.ts b/src/commands/event-dates.ts index 2e4832d..8eb545c 100644 --- a/src/commands/event-dates.ts +++ b/src/commands/event-dates.ts @@ -1,7 +1,7 @@ // biome-ignore-all lint/suspicious/noExplicitAny: generated SDK boundary — signatures vary by resource. import type { Command } from "commander"; import { configureClient, unwrap } from "../lib/api"; -import { confirm, parseData } from "../lib/input"; +import { confirmOrExit, parseData } from "../lib/input"; import { print } from "../lib/output"; type SdkFn = ( @@ -86,10 +86,7 @@ export function registerEventDates( .option("--workspace ", "workspace override") .option("--json", "raw JSON output") .action(async (eventId, dateId, opts) => { - if (!opts.yes && !(await confirm(`Delete date ${dateId}?`))) { - console.error("Aborted."); - return; - } + await confirmOrExit(`Delete date ${dateId}?`, opts.yes); configureClient(opts.workspace); const body = unwrap(await spec.del({ path: { id: eventId, dateId } })); print(body?.data ?? { deleted: dateId }, { json: opts.json }); diff --git a/src/commands/reports.test.ts b/src/commands/reports.test.ts new file mode 100644 index 0000000..613ed5f --- /dev/null +++ b/src/commands/reports.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { printExport } from "./reports"; + +/** Captures whatever the command writes to stdout. */ +function capture(fn: () => void): string { + let out = ""; + const spy = vi + .spyOn(process.stdout, "write") + .mockImplementation((chunk: string | Uint8Array) => { + out += String(chunk); + return true; + }); + fn(); + spy.mockRestore(); + return out; +} + +afterEach(() => vi.restoreAllMocks()); + +describe("printExport", () => { + // /reports/exports/reconciliation answers text/csv, so the payload reaches + // us as a string. Issue #22: it used to go through JSON.stringify, which + // quoted the whole file and turned the newlines into a literal backslash-n. + const csv = "a,b\n1,2\n3,4"; + + it("writes a text/csv payload verbatim", () => { + const out = capture(() => printExport(csv, {})); + expect(out).toBe("a,b\n1,2\n3,4\n"); + expect(out).not.toContain("\\n"); + expect(out.startsWith('"')).toBe(false); + }); + + it("does not double the trailing newline", () => { + expect(capture(() => printExport(`${csv}\n`, {}))).toBe(`${csv}\n`); + }); + + it("serializes JSON rows to CSV with --csv", () => { + const out = capture(() => printExport([{ a: 1, b: 2 }], { csv: true })); + expect(out).toBe("a,b\n1,2\n"); + }); + + it("falls back to JSON when the payload is rows and --csv is absent", () => { + const out = capture(() => printExport([{ a: 1 }], { json: true })); + expect(JSON.parse(out)).toEqual([{ a: 1 }]); + }); +}); diff --git a/src/commands/reports.ts b/src/commands/reports.ts index 95f2d7e..c78b0a2 100644 --- a/src/commands/reports.ts +++ b/src/commands/reports.ts @@ -5,13 +5,14 @@ import { getReportsExportsBuyers, getReportsExportsReconciliation, getReportsExportsSubscribers, + getReportsFinancials, getReportsInventory, getReportsReconciliation, getReportsSummary, getReportsTimeseries, } from "../client/sdk.gen"; import { configureClient, unwrap } from "../lib/api"; -import { print } from "../lib/output"; +import { print, toCsv } from "../lib/output"; export function registerReports(program: Command): void { const root = program.command("reports").description("KPIs and exports"); @@ -61,6 +62,29 @@ export function registerReports(program: Command): void { print(body.data, { json: opts.json }); }); + root + .command("financials") + .description( + "Per-function P&L: gross, platform fee, facial, payment fee, 4x1000, net to settle", + ) + .option("--event ", "filter by event") + .option("--past", "only functions that already happened (settleable)") + .option("--workspace ", "workspace override") + .option("--json", "raw JSON output") + .option("--csv", "CSV output (for spreadsheets/accounting)") + .action(async (opts) => { + configureClient(opts.workspace); + const body = unwrap( + await getReportsFinancials({ + query: { + event: opts.event, + past: opts.past === undefined ? undefined : String(opts.past), + }, + }), + ); + printExport(body.data, opts); + }); + root .command("by-event") .description("Revenue / tickets sold / availability grouped by event") @@ -146,6 +170,7 @@ export function registerReports(program: Command): void { .option("--provider

", "filter by payment provider") .option("--workspace ", "workspace override") .option("--json", "raw JSON output") + .option("--csv", "CSV output (for spreadsheets/accounting)") .action(async (opts) => { configureClient(opts.workspace); const body = unwrap( @@ -158,7 +183,7 @@ export function registerReports(program: Command): void { }, }), ); - print(body.data ?? body, { json: opts.json }); + printExport(body.data ?? body, opts); }); // buyers = one row per sale, attendees = one row per ticket; both filterable. @@ -176,6 +201,7 @@ export function registerReports(program: Command): void { .option("--status ", "filter by sale status") .option("--workspace ", "workspace override") .option("--json", "raw JSON output") + .option("--csv", "CSV output (for spreadsheets/accounting)") .action(async (opts) => { configureClient(opts.workspace); const body = unwrap( @@ -189,7 +215,7 @@ export function registerReports(program: Command): void { }, }), ); - print(body.data ?? body, { json: opts.json }); + printExport(body.data ?? body, opts); }); } @@ -198,9 +224,32 @@ export function registerReports(program: Command): void { .description("Export subscribers") .option("--workspace ", "workspace override") .option("--json", "raw JSON output") + .option("--csv", "CSV output (for spreadsheets/accounting)") .action(async (opts) => { configureClient(opts.workspace); const body = unwrap(await getReportsExportsSubscribers({})); - print(body.data ?? body, { json: opts.json }); + printExport(body.data ?? body, opts); }); } + +/** + * Exports promise "(CSV)". Two server shapes reach here: + * - `text/csv` → the payload is already a CSV string. Write it verbatim; + * running it through print()/JSON.stringify would quote the whole file and + * escape the newlines as literal `\n` (issue #22). + * - `application/json` → an array of rows: honor --csv, else JSON/table. + */ +export function printExport( + rows: unknown, + opts: { json?: boolean; csv?: boolean }, +) { + if (typeof rows === "string") { + process.stdout.write(rows.endsWith("\n") ? rows : `${rows}\n`); + return; + } + if (opts.csv && Array.isArray(rows)) { + process.stdout.write(`${toCsv(rows)}\n`); + return; + } + print(rows, { json: opts.json }); +} diff --git a/src/commands/resource.ts b/src/commands/resource.ts index 83803d5..3837201 100644 --- a/src/commands/resource.ts +++ b/src/commands/resource.ts @@ -1,7 +1,7 @@ // biome-ignore-all lint/suspicious/noExplicitAny: generated SDK boundary — signatures vary by resource. import type { Command } from "commander"; import { configureClient, unwrap } from "../lib/api"; -import { confirm, parseData } from "../lib/input"; +import { confirmOrExit, parseData } from "../lib/input"; import { print, printNextCursor, resolveColumns, toCsv } from "../lib/output"; type SdkFn = ( @@ -170,10 +170,7 @@ export function registerResource(program: Command, spec: ResourceSpec): void { .option("--workspace ", "workspace override") .option("--json", "raw JSON output") .action(async (id, opts) => { - if (!opts.yes && !(await confirm(`Delete ${singular} ${id}?`))) { - console.error("Aborted."); - return; - } + await confirmOrExit(`Delete ${singular} ${id}?`, opts.yes); configureClient(opts.workspace); const body = unwrap(await del({ path: { id } })); print(body?.data ?? { deleted: id }, { json: opts.json }); diff --git a/src/index.ts b/src/index.ts index 6732e3a..bb36a08 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ import { getSales, getSalesId, getSalesIdTickets, + getSettlements, getStaff, getTicketTypes, getTicketTypesId, @@ -81,6 +82,25 @@ registerResource(program, { ], // startsAt lives on EventDate, not Event — use createdAt for a temporal column. columns: ["id", "name", "status", "createdAt"], + listFlags: [{ flag: "--q ", describe: "search by name", query: "q" }], +}); + +// Read-only: settlements are created by FreeTicket, never by the organizer. +// The PDF/comprobante is still panel-only — the contract exposes hasDocument +// and the file names, not a download URL (see CONTRACT-GAPS.md). +registerResource(program, { + name: "settlements", + describe: "Settlements paid to the organizer", + list: getSettlements, + columns: ["id", "reference", "status", "amount", "currency", "createdAt"], + listFlags: [ + { flag: "--event ", describe: "filter by event", query: "event" }, + { + flag: "--status ", + describe: "SENT | AWAITING_PAYMENT | PAID", + query: "status", + }, + ], }); registerEventDates(program, { diff --git a/src/lib/input.test.ts b/src/lib/input.test.ts new file mode 100644 index 0000000..469a40c --- /dev/null +++ b/src/lib/input.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { confirmOrExit } from "./input"; + +/** Issue #24: aborting a destructive command must not exit 0. */ +describe("confirmOrExit", () => { + const tty = process.stdin.isTTY; + afterEach(() => { + Object.defineProperty(process.stdin, "isTTY", { + value: tty, + configurable: true, + }); + vi.restoreAllMocks(); + }); + + function noTty(): void { + Object.defineProperty(process.stdin, "isTTY", { + value: false, + configurable: true, + }); + } + + it("exits non-zero when there is no TTY to confirm on", async () => { + noTty(); + vi.spyOn(console, "error").mockImplementation(() => {}); + const exit = vi.spyOn(process, "exit").mockImplementation((() => { + throw new Error("exited"); + }) as never); + await expect(confirmOrExit("Delete event x?", undefined)).rejects.toThrow( + "exited", + ); + expect(exit).toHaveBeenCalledWith(1); + }); + + it("passes straight through with --yes, TTY or not", async () => { + noTty(); + const exit = vi.spyOn(process, "exit").mockImplementation((() => { + throw new Error("exited"); + }) as never); + await expect( + confirmOrExit("Delete event x?", true), + ).resolves.toBeUndefined(); + expect(exit).not.toHaveBeenCalled(); + }); +}); diff --git a/src/lib/input.ts b/src/lib/input.ts index 9b934ab..3077805 100644 --- a/src/lib/input.ts +++ b/src/lib/input.ts @@ -28,6 +28,26 @@ export function parseData(input?: string): unknown { } } +/** + * Confirmation gate for destructive commands. Aborting exits non-zero so a + * script chaining `ft … delete && next-step` never mistakes a refusal for a + * success (issue #24). Without a TTY it fails immediately pointing at --yes, + * instead of hanging or silently doing nothing. + */ +export async function confirmOrExit( + question: string, + yes: boolean | undefined, +): Promise { + if (yes) return; + if (!process.stdin.isTTY) { + fail( + `Refusing: ${question} — no TTY to confirm.`, + "Pass --yes in scripts / CI.", + ); + } + if (!(await confirm(question))) fail("Aborted."); +} + /** * Y/N confirmation prompt on stderr (keeps stdout clean for `--json`/pipes). * Returns true only on an explicit yes. Non-interactive stdin → false.