349 lines
8.5 KiB
Plaintext
349 lines
8.5 KiB
Plaintext
|
|
classDiagram
|
|
|
|
|
|
class App{
|
|
|
|
|
|
}
|
|
class AuthUser {
|
|
<<type>>
|
|
+id: string | number
|
|
+username: string
|
|
+fullName: string
|
|
+role: string
|
|
|
|
}
|
|
class AuthSession {
|
|
<<type>>
|
|
+user: AuthUser
|
|
+accessToken: string
|
|
+expiresAt: string
|
|
|
|
}
|
|
class ApiEnvelope~T~ {
|
|
<<type>>
|
|
+ok: boolean
|
|
+data: T
|
|
|
|
}
|
|
class InvimaDataset {
|
|
<<type>>
|
|
+key: string
|
|
+id: string
|
|
+name: string
|
|
+defaultCategory: string
|
|
|
|
}
|
|
class InvimaCategory {
|
|
<<type>>
|
|
+categoria: string
|
|
+slug: string
|
|
+total: number
|
|
|
|
}
|
|
class InvimaFilterOption {
|
|
<<type>>
|
|
+value: string
|
|
+total: number
|
|
|
|
}
|
|
class InvimaCatalogItem {
|
|
<<type>>
|
|
+source_dataset_key: string
|
|
+source_dataset_id: string
|
|
+source_dataset_name: string
|
|
+categoria_linea: string
|
|
+categoria_canonica: string
|
|
+categoria_slug: string
|
|
+expediente?: string
|
|
+registro_sanitario?: string
|
|
+producto?: string
|
|
+titular?: string
|
|
+estado_registro?: string
|
|
+fecha_expedicion?: string
|
|
+fecha_vencimiento?: string
|
|
+modalidad?: string
|
|
+grupo?: string
|
|
+marca?: string
|
|
+principio_activo?: string
|
|
+forma_farmaceutica?: string
|
|
+presentacion_comercial?: string
|
|
+atc?: string
|
|
+rol_nombre?: string
|
|
+rol_tipo?: string
|
|
+ciudad_titular?: string
|
|
+pais_titular?: string
|
|
+fabricante?: string
|
|
+importador?: string
|
|
+vigencia?: string
|
|
+score?: number
|
|
|
|
}
|
|
class InvimaCatalogResponse {
|
|
<<type>>
|
|
+items: InvimaCatalogItem[]
|
|
+total: number
|
|
+limit: number
|
|
+offset: number
|
|
|
|
}
|
|
class InvimaFiltersResponse {
|
|
<<type>>
|
|
+estados: InvimaFilterOption[]
|
|
+modalidades: InvimaFilterOption[]
|
|
+grupos: InvimaFilterOption[]
|
|
|
|
}
|
|
class InvimaSyncSummary {
|
|
<<type>>
|
|
+datasets: number
|
|
+fetched: number
|
|
+persisted: number
|
|
+pages: number
|
|
+failed?: number
|
|
+requested?: number
|
|
|
|
}
|
|
class InvimaSyncResponse {
|
|
<<type>>
|
|
+ok: boolean
|
|
+alreadyRunning?: boolean
|
|
+job?: InvimaSyncJob | null
|
|
+summary?: InvimaSyncSummary
|
|
+failed?: #123; datasetKey: string; error: string; #125;[]
|
|
|
|
}
|
|
class InvimaSyncJobDataset {
|
|
<<type>>
|
|
+key: string
|
|
+datasetKey: string
|
|
+datasetId: string | null
|
|
+name: string
|
|
+status: "pending" | "running" | "completed" | "failed"
|
|
+fetched: number
|
|
+persisted: number
|
|
+pages: number
|
|
+warnings: string[]
|
|
+error: string | null
|
|
+startedAt: string | null
|
|
+updatedAt: string
|
|
+finishedAt: string | null
|
|
|
|
}
|
|
class InvimaSyncJob {
|
|
<<type>>
|
|
+id: string
|
|
+status: "running" | "completed" | "failed"
|
|
+requested: number
|
|
+completed: number
|
|
+failed: number
|
|
+fetched: number
|
|
+persisted: number
|
|
+pages: number
|
|
+concurrency?: number
|
|
+pageSize?: number
|
|
+datasets: InvimaSyncJobDataset[]
|
|
+summary?: InvimaSyncSummary | null
|
|
+error?: string | null
|
|
+startedAt: string
|
|
+updatedAt: string
|
|
+finishedAt: string | null
|
|
|
|
}
|
|
class InvimaSyncDatasetStats {
|
|
<<type>>
|
|
+key: string
|
|
+id: string
|
|
+name: string
|
|
+localRows: number
|
|
+selected: boolean
|
|
|
|
}
|
|
class InvimaSyncStats {
|
|
<<type>>
|
|
+at: string
|
|
+totals: #123; catalogRows: number; rawRows: number; categories: number; datasetsWithData: number; vigentes: number; vencidos: number; maxUpdatedAt: string | null; #125;
|
|
+datasets: InvimaSyncDatasetStats[]
|
|
|
|
}
|
|
AuthSession -- AuthUser
|
|
InvimaCatalogResponse -- "0..*" InvimaCatalogItem
|
|
InvimaFiltersResponse -- "0..*" InvimaFilterOption
|
|
InvimaFiltersResponse -- "0..*" InvimaFilterOption
|
|
InvimaFiltersResponse -- "0..*" InvimaFilterOption
|
|
InvimaSyncResponse -- InvimaSyncJob
|
|
InvimaSyncResponse -- InvimaSyncSummary
|
|
InvimaSyncJob -- "0..*" InvimaSyncJobDataset
|
|
InvimaSyncJob -- InvimaSyncSummary
|
|
InvimaSyncStats -- "0..*" InvimaSyncDatasetStats
|
|
class AuthService{
|
|
-apiBase: "/api/auth"
|
|
-_token: WritableSignal~string | null~
|
|
-_user: WritableSignal~AuthUser | null~
|
|
+user: Signal~AuthUser | null~
|
|
+isAuthenticated: Signal~boolean~
|
|
-http: HttpClient
|
|
-hydrate() void
|
|
-setSession() void
|
|
+clearSession() void
|
|
+getAccessToken() string | null
|
|
+login() Observable~AuthSession~
|
|
+me() Observable~AuthUser~
|
|
+logout() Observable~boolean~
|
|
}
|
|
class InvimaService{
|
|
-apiBase: "/api/invima"
|
|
-http: HttpClient
|
|
-buildExportParams() HttpParams
|
|
-buildFallbackFilename() string
|
|
-extractFilename() string
|
|
+getDatasets() Observable~#123; items: InvimaDataset[]; #125;~
|
|
+getCategories() Observable~#123; items: InvimaCategory[]; #125;~
|
|
+getFilterOptions() Observable~InvimaFiltersResponse~
|
|
+searchCatalog() Observable~InvimaCatalogResponse~
|
|
+sync() Observable~InvimaSyncResponse~
|
|
+getSyncStats() Observable~InvimaSyncStats~
|
|
+getSyncJob() Observable~#123; ok: boolean; job: InvimaSyncJob | null; #125;~
|
|
+exportCatalog() Observable~InvimaExportFile~
|
|
+buildExportUrl() string
|
|
}
|
|
class CatalogQuery {
|
|
<<type>>
|
|
+q?: string
|
|
+categorias?: string[]
|
|
+datasets?: string[]
|
|
+estados?: string[]
|
|
+modalidades?: string[]
|
|
+grupos?: string[]
|
|
+fechaVencDesde?: string
|
|
+fechaVencHasta?: string
|
|
+fechaExpDesde?: string
|
|
+fechaExpHasta?: string
|
|
+onlyVigentes?: boolean
|
|
+limit?: number
|
|
+offset?: number
|
|
|
|
}
|
|
class InvimaExportFile {
|
|
<<type>>
|
|
+blob: Blob
|
|
+filename: string
|
|
+contentType: string
|
|
|
|
}
|
|
class LoginPageComponent{
|
|
-auth: AuthService
|
|
-router: Router
|
|
-route: ActivatedRoute
|
|
+loading: WritableSignal~boolean~
|
|
+error: WritableSignal~string~
|
|
+identifier: string
|
|
+password: string
|
|
+submit() void
|
|
}
|
|
LoginPageComponent -- AuthService
|
|
class DashboardPageComponent{
|
|
-auth: AuthService
|
|
+fullName: Signal~string~
|
|
|
|
}
|
|
DashboardPageComponent -- AuthService
|
|
class CatalogPageComponent{
|
|
-invima: InvimaService
|
|
+loading: WritableSignal~boolean~
|
|
+error: WritableSignal~string~
|
|
+exporting: WritableSignal~"csv" | "xlsx" | null~
|
|
+exportMessage: WritableSignal~string~
|
|
+exportError: WritableSignal~string~
|
|
+items: WritableSignal~InvimaCatalogItem[]~
|
|
+total: WritableSignal~number~
|
|
+categories: WritableSignal~InvimaCategory[]~
|
|
+datasets: WritableSignal~InvimaDataset[]~
|
|
+estados: WritableSignal~InvimaFilterOption[]~
|
|
+modalidades: WritableSignal~InvimaFilterOption[]~
|
|
+grupos: WritableSignal~InvimaFilterOption[]~
|
|
+selectedCategories: WritableSignal~string[]~
|
|
+selectedDatasets: WritableSignal~string[]~
|
|
+limit: WritableSignal~number~
|
|
+offset: WritableSignal~number~
|
|
+page: Signal~number~
|
|
+hasMore: Signal~boolean~
|
|
+qText: string
|
|
+estado: string
|
|
+modalidad: string
|
|
+grupo: string
|
|
+fechaVencDesde: string
|
|
+fechaVencHasta: string
|
|
+fechaExpDesde: string
|
|
+fechaExpHasta: string
|
|
+onlyVigentes: boolean
|
|
-buildQuery() CatalogQuery
|
|
-loadMetadata() void
|
|
-loadData() void
|
|
+applyFilters() void
|
|
+clearFilters() void
|
|
+toggleCategory() void
|
|
+isCategorySelected() boolean
|
|
+toggleDataset() void
|
|
+isDatasetSelected() boolean
|
|
+changeLimit() void
|
|
+nextPage() void
|
|
+prevPage() void
|
|
+download() void
|
|
+formatDate() string
|
|
+isVigente() boolean
|
|
+isVencido() boolean
|
|
}
|
|
CatalogPageComponent -- InvimaService
|
|
class SyncPageComponent{
|
|
-invima: InvimaService
|
|
-refreshMs: 10000
|
|
-refreshHandle: number | null
|
|
-announcedFinalJobId: string | null
|
|
+loading: WritableSignal~boolean~
|
|
+message: WritableSignal~string~
|
|
+statsError: WritableSignal~string~
|
|
+jobError: WritableSignal~string~
|
|
+datasets: WritableSignal~InvimaDataset[]~
|
|
+selected: WritableSignal~string[]~
|
|
+stats: WritableSignal~InvimaSyncStats | null~
|
|
+job: WritableSignal~InvimaSyncJob | null~
|
|
+baselineTotal: WritableSignal~number | null~
|
|
+baselineByDataset: WritableSignal~Record~string, number~~
|
|
+liveAdded: Signal~number~
|
|
+pageSize: number
|
|
+concurrency: number
|
|
+ngOnDestroy() void
|
|
-loadStats() void
|
|
-loadJob() void
|
|
-ensureBaseline() void
|
|
-refreshLiveMessage() void
|
|
-buildFinalMessage() string
|
|
-captureBaselineFromCurrentStats() void
|
|
+datasetDelta() number
|
|
+formatNumber() string
|
|
+formatDateTime() string
|
|
+toggle() void
|
|
+isSelected() boolean
|
|
+selectAll() void
|
|
+sync() void
|
|
}
|
|
OnDestroy<|..SyncPageComponent
|
|
SyncPageComponent -- InvimaService
|
|
class MainLayoutComponent{
|
|
+auth: AuthService
|
|
-router: Router
|
|
+menu: MenuItem[]
|
|
+initials: Signal~string~
|
|
+logout() void
|
|
}
|
|
class MenuItem {
|
|
<<type>>
|
|
+label: string
|
|
+path: string
|
|
+hint: string
|
|
|
|
}
|
|
MainLayoutComponent -- AuthService
|
|
MainLayoutComponent -- "0..*" MenuItem |