'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Header } from '@/components/layouts/header'; import { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@horux/shared-ui'; import { useAuthStore } from '@/stores/auth-store'; import { isGlobalAdminRfc } from '@horux/shared'; import { apiClient } from '@/lib/api/client'; import { Package, ShieldAlert, Pencil, Loader2, Check as CheckIcon, X as XIcon, AlertTriangle, Power, PowerOff } from 'lucide-react'; interface AddonItem { id: string; codename: string; nombre: string; verticalProfile: string | null; precio: number; frecuencia: string; active: boolean; delta: unknown; createdAt: string; suscripcionesActivas: number; } async function listAddons(): Promise { const res = await apiClient.get<{ data: AddonItem[] }>('/admin/addons/catalogo'); return res.data.data; } async function updateAddon(id: string, data: { nombre?: string; precio?: number; active?: boolean }): Promise { await apiClient.put(`/admin/addons/catalogo/${id}`, data); } export default function AddonsPage() { const { user } = useAuthStore(); const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); const queryClient = useQueryClient(); const { data: addons = [], isLoading } = useQuery({ queryKey: ['admin-addons-catalogo'], queryFn: listAddons, enabled: isGlobalAdmin, }); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: { nombre?: string; precio?: number; active?: boolean } }) => updateAddon(id, data), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['admin-addons-catalogo'] }), }); const [editing, setEditing] = useState<{ id: string; nombre: string; precio: string } | null>(null); if (!isGlobalAdmin) { return ( <>

Acceso restringido

Solo admin global puede modificar el catálogo de add-ons.

); } const startEdit = (item: AddonItem) => { setEditing({ id: item.id, nombre: item.nombre, precio: String(item.precio) }); }; const cancelEdit = () => setEditing(null); const saveEdit = async () => { if (!editing) return; const precio = Number(editing.precio); if (!Number.isFinite(precio) || precio < 0) { alert('El precio debe ser un número no negativo'); return; } if (!editing.nombre.trim()) { alert('El nombre no puede estar vacío'); return; } try { await updateMutation.mutateAsync({ id: editing.id, data: { nombre: editing.nombre.trim(), precio }, }); setEditing(null); } catch (err: any) { alert(err?.response?.data?.message || err?.message || 'Error al guardar'); } }; const toggleActive = async (item: AddonItem) => { if (item.active && item.suscripcionesActivas > 0) { const confirmar = confirm( `Hay ${item.suscripcionesActivas} suscripción(es) activa(s) usando este add-on. ` + `Desactivarlo evitará nuevas contrataciones, pero las existentes siguen vigentes hasta su cancelación. ¿Continuar?`, ); if (!confirmar) return; } try { await updateMutation.mutateAsync({ id: item.id, data: { active: !item.active } }); } catch (err: any) { alert(err?.response?.data?.message || err?.message || 'Error al cambiar estado'); } }; return ( <>

Los cambios de precio aplican a contrataciones nuevas. Las suscripciones de add-on vigentes conservan el precio al que se cobraron.

Desactivar un add-on lo oculta del catálogo público, pero las suscripciones activas siguen funcionando hasta su cancelación.

Catálogo de add-ons {isLoading ? (
Cargando catálogo...
) : addons.length === 0 ? (
Sin add-ons configurados.
) : (
{addons.map(item => { const isEditing = editing?.id === item.id; return ( ); })}
Codename Nombre Precio (MXN) Frecuencia Suscripciones activas Estado
{item.codename} {isEditing ? ( setEditing({ ...editing, nombre: e.target.value })} className="h-8" autoFocus /> ) : ( {item.nombre} )} {isEditing ? ( setEditing({ ...editing, precio: e.target.value })} className="h-8 w-32 text-right" /> ) : ( ${item.precio.toLocaleString('es-MX')} )} {item.frecuencia} 0 ? 'font-medium' : 'text-muted-foreground'}> {item.suscripcionesActivas} {item.active ? 'Activo' : 'Inactivo'}
{isEditing ? ( <> ) : ( <> )}
)}
); }