/** * @file publications.ts * @description Strict TypeScript interfaces and types for the Publications module. * All entities, props, and enums are defined here to enforce type safety across * the entire publications feature. */ // ───────────────────────────────────────────── // ENUMS // ───────────────────────────────────────────── /** Roles de usuario para el sistema RBAC */ export type UserRole = 'admin' | 'editor' | 'viewer' | 'guest'; /** Estados posibles de una publicación */ export type PostStatus = 'borrador' | 'pendiente' | 'publicado' | 'archivado'; // ───────────────────────────────────────────── // CORE ENTITIES // ───────────────────────────────────────────── /** Entidad del perfil de usuario autenticado */ export interface AuthUser { $id: string; name: string; email: string; labels?: string[]; prefs: { role?: UserRole; [key: string]: unknown; }; } /** Entidad de tipo de publicación */ export interface PublicationType { $id: string; codigo: string; descripcion: string; } /** Entidad de ítem de media */ export interface MediaItem { $id: string; file_id: string; bucket_id: string; titulo: string; alt_text?: string; descripcion_seo?: string; tipo: 'imagen' | 'video'; } /** Entidad de cliente/patrocinador */ export interface Client { $id: string; nombre_entidad: string; nit?: string; slug: string; contacto_nombre: string; numero_whatsapp: string; estado: boolean; } /** Entidad principal de publicación (Post) */ export interface Post { $id: string; $createdAt: string; $updatedAt: string; titulo: string; slug: string; contenido: string; resumen_redes?: string; estado: PostStatus; fecha_publicacion: string; fecha_finalizacion?: string; og_image_url?: string; url_destino?: string; video_url?: string; seo_titulo?: string; seo_descripcion?: string; // Relaciones (pueden llegar como string ID o como objeto resuelto) tipo_publicacion_id: string | PublicationType; imagen_destacada_id?: string | MediaItem; cliente_id?: string | Client; resolved_media?: MediaItem; } // ───────────────────────────────────────────── // COMPONENT PROPS // ───────────────────────────────────────────── /** Props del PostCard (componente de dashboard) */ export interface PostCardProps { post: Post; userRole: UserRole; onEdit?: (post: Post) => void; onDelete?: (postId: string) => void; /** URL base del endpoint de Appwrite para construir URLs de imagen */ appwriteEndpoint: string; appwriteProjectId: string; globalSettings?: Record; } /** Props del PermissionGuard HOC */ export interface PermissionGuardProps { /** Roles que tienen acceso al contenido protegido */ allowedRoles: UserRole[]; /** Rol actual del usuario */ userRole: UserRole; /** Contenido a renderizar si el usuario tiene permiso */ children: React.ReactNode; /** Contenido alternativo si no tiene permiso (opcional) */ fallback?: React.ReactNode; } /** Props del FormSection */ export interface FormSectionProps { title: string; icon?: React.ReactNode; /** Número de columnas del grid interno (1–12) */ columns?: 1 | 2 | 3 | 4 | 6 | 12; children: React.ReactNode; /** Clase CSS adicional */ className?: string; } /** Props del RelationSelectField */ export interface RelationSelectFieldProps { /** Nombre del campo en el formulario */ name: string; /** Label visible */ label: string; /** Opciones actuales disponibles */ options: Array<{ $id: string; [key: string]: unknown }>; /** Propiedad del objeto a mostrar como texto */ labelKey: string; /** Valor actualmente seleccionado */ value?: string; onChange?: (value: string) => void; /** Colección de Appwrite para creación rápida */ relationCollection: string; /** Si el campo es obligatorio */ required?: boolean; /** Desactiva el campo */ disabled?: boolean; } /** Props del PublicationLayout */ export interface PublicationLayoutProps { /** Post existente para edición, undefined para creación */ initialData?: Partial; /** Datos de relaciones pre-cargados desde el servidor */ relationData: Record>; /** Acción de servidor para guardar */ /** Acción de servidor para guardar. Puede retornar un objeto de éxito o void (si redirige) */ submitAction: (formData: FormData) => Promise<{ success: boolean; error?: string; data?: unknown } | void>; /** Usuario autenticado */ user: AuthUser; /** Config de Appwrite para URLs de media */ appwriteConfig: { endpoint: string; projectId: string; databaseId: string; }; } // ───────────────────────────────────────────── // STORE TYPES (Zustand) // ───────────────────────────────────────────── /** Estado del store de publicaciones */ export interface PublicationStoreState { /** Cache de opciones para campos de relación — evita re-fetches */ relationCache: Record>; /** Campo actualmente abierto en QuickCreateModal */ pendingRelationField: string | null; /** Colección destino del modal de creación rápida */ pendingRelationCollection: string | null; /** Estado de la UI optimista durante el guardado */ optimisticState: 'idle' | 'saving' | 'saved' | 'error'; /** Mensaje de error si aplica */ errorMessage: string | null; } /** Acciones del store de publicaciones */ export interface PublicationStoreActions { /** Abre el QuickCreateModal para un campo de relación específico */ openQuickCreate: (fieldName: string, collection: string) => void; /** Cierra el QuickCreateModal */ closeQuickCreate: () => void; /** Inyecta un nuevo documento en el cache y en el campo padre */ injectRelationValue: ( fieldName: string, collection: string, newDoc: { $id: string; [key: string]: unknown } ) => void; /** Actualiza el cache de opciones de una relación */ setRelationCache: ( collection: string, data: Array<{ $id: string; [key: string]: unknown }> ) => void; /** Establece el estado optimista */ setOptimisticState: (state: PublicationStoreState['optimisticState'], error?: string) => void; /** Reinicia el store */ reset: () => void; } export type PublicationStore = PublicationStoreState & PublicationStoreActions; // ───────────────────────────────────────────── // API TYPES // ───────────────────────────────────────────── /** Respuesta estándar de la API de publicaciones */ export interface ApiResponse { success: boolean; data?: T; error?: string; validationErrors?: Record; }