'use client'; import { useState, useEffect } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Header } from '@/components/layouts/header'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Label, Input } from '@horux/shared-ui'; import { useThemeStore } from '@/stores/theme-store'; import { useAuthStore } from '@/stores/auth-store'; import { themes, type ThemeName } from '@/themes'; import { Check, Palette, User, Building, Sidebar, PanelTop, Minimize2, Sparkles, RefreshCw, Scale, Trash2, MapPin, KeyRound, Tags, Receipt, Bell, Package } from 'lucide-react'; import { isGlobalAdminRfc, isDespachoTenant } from '@horux/shared'; import Link from 'next/link'; import { apiClient } from '@/lib/api/client'; import { useBancos, useCreateBanco, useDeleteBanco } from '@/lib/hooks/use-bancos'; import { useTenantViewStore } from '@/stores/tenant-view-store'; import { useContribuyenteStore } from '@/stores/contribuyente-store'; const themeOptions: { name: ThemeName; label: string; description: string; layoutDesc: string; layoutIcon: typeof Sidebar }[] = [ { name: 'light', label: 'Light', description: 'Tema claro profesional', layoutDesc: 'Sidebar estándar fijo', layoutIcon: Sidebar, }, { name: 'dark', label: 'Dark', description: 'Modo oscuro con acentos neón', layoutDesc: 'Sidebar flotante con efecto glass', layoutIcon: Sparkles, }, ]; function RegimenesActivosSection() { const queryClient = useQueryClient(); const [saving, setSaving] = useState(false); const { viewingTenantId } = useTenantViewStore(); const { selectedContribuyenteId } = useContribuyenteStore(); const user = useAuthStore(s => s.user); const isDespacho = isDespachoTenant(user?.tenantRfc); const tenantKey = viewingTenantId || 'own'; const { data: catalogo } = useQuery({ queryKey: ['regimenes-catalogo'], queryFn: async () => { const res = await apiClient.get<{ id: number; clave: string; descripcion: string; tipoPersona: string }[]>('/regimenes'); return res.data; }, }); // Despacho: read régimen from contribuyente; Horux360: from tenant activos const { data: activos } = useQuery({ queryKey: ['regimenes-activos', tenantKey, selectedContribuyenteId], queryFn: async () => { if (isDespacho && selectedContribuyenteId) { const res = await apiClient.get(`/contribuyentes/${selectedContribuyenteId}`); const c = res.data; // Build activos array from regimen_fiscal field (may be comma-separated: "623,606,612") if (c.regimenFiscal && catalogo) { const claves = c.regimenFiscal.split(',').map((s: string) => s.trim()); return claves .map((clave: string) => catalogo.find((cat: any) => cat.clave === clave)) .filter(Boolean) .map((cat: any) => ({ id: cat.id, clave: cat.clave, descripcion: cat.descripcion })); } return []; } const res = await apiClient.get<{ id: number; clave: string; descripcion: string }[]>('/regimenes/activos'); return res.data; }, enabled: !!catalogo, }); const [selected, setSelected] = useState>(new Set()); useEffect(() => { if (activos && catalogo) { const ids = new Set(activos.map((a: { clave: string }) => catalogo.find((c: { clave: string; id: number }) => c.clave === a.clave)?.id).filter(Boolean) as number[]); setSelected(ids); } }, [activos, catalogo]); const toggle = (id: number) => { setSelected(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const handleSave = async () => { setSaving(true); try { await apiClient.put('/regimenes/activos', { regimenIds: Array.from(selected) }); queryClient.invalidateQueries({ queryKey: ['regimenes-activos', tenantKey, selectedContribuyenteId] }); queryClient.invalidateQueries({ queryKey: ['calendario'] }); queryClient.invalidateQueries({ queryKey: ['regimenes-periodo'] }); } catch { alert('Error al guardar'); } finally { setSaving(false); } }; if (!catalogo) return null; return ( Regimenes Fiscales Activos Selecciona los regimenes fiscales bajo los que opera tu empresa. Esto afecta el calendario de obligaciones y los filtros disponibles.
{catalogo.map(r => ( ))}

{selected.size} regimenes seleccionados

); } function DomicilioFiscalSection() { const queryClient = useQueryClient(); const { viewingTenantId } = useTenantViewStore(); const { selectedContribuyenteId } = useContribuyenteStore(); const user = useAuthStore(s => s.user); const isDespacho = isDespachoTenant(user?.tenantRfc); const tenantKey = viewingTenantId || 'own'; const [saving, setSaving] = useState(false); const [msg, setMsg] = useState(null); // Despacho: read domicilio from contribuyente; Horux360: read from tenant datos-fiscales const { data, isLoading } = useQuery({ queryKey: ['datos-fiscales', tenantKey, selectedContribuyenteId], queryFn: async () => { if (isDespacho && selectedContribuyenteId) { const res = await apiClient.get(`/contribuyentes/${selectedContribuyenteId}`); const c = res.data; const dom = c.domicilio || {}; return { codigoPostal: c.codigoPostal || dom.codigoPostal || '', calle: dom.calle || '', numExterior: dom.numExterior || '', numInterior: dom.numInterior || '', colonia: dom.colonia || '', ciudad: dom.ciudad || '', municipio: dom.municipio || '', estado: dom.estado || '', telefono: dom.telefono || '', }; } const res = await apiClient.get('/facturacion/datos-fiscales'); return res.data; }, }); const [form, setForm] = useState({ codigoPostal: '', calle: '', numExterior: '', numInterior: '', colonia: '', ciudad: '', municipio: '', estado: '', telefono: '', }); useEffect(() => { if (data) { setForm({ codigoPostal: data.codigoPostal || '', calle: data.calle || '', numExterior: data.numExterior || '', numInterior: data.numInterior || '', colonia: data.colonia || '', ciudad: data.ciudad || '', municipio: data.municipio || '', estado: data.estado || '', telefono: data.telefono || '', }); } }, [data]); const handleSave = async () => { setSaving(true); setMsg(null); try { if (isDespacho && selectedContribuyenteId) { // Save domicilio to contribuyente await apiClient.put(`/contribuyentes/${selectedContribuyenteId}`, { domicilio: form, codigoPostal: form.codigoPostal, }); } else { await apiClient.put('/facturacion/datos-fiscales', form); } queryClient.invalidateQueries({ queryKey: ['datos-fiscales', tenantKey, selectedContribuyenteId] }); setMsg('Datos guardados'); setTimeout(() => setMsg(null), 3000); } catch { setMsg('Error al guardar'); } finally { setSaving(false); } }; if (isLoading) return null; return ( Domicilio Fiscal Dirección y teléfono de la empresa. Se usa para facturas al público en general.
setForm({ ...form, codigoPostal: e.target.value.replace(/\D/g, '').slice(0, 5) })} placeholder="06600" maxLength={5} />
setForm({ ...form, calle: e.target.value })} placeholder="Av. Reforma" />
setForm({ ...form, numExterior: e.target.value })} placeholder="123" />
setForm({ ...form, numInterior: e.target.value })} placeholder="4A" />
setForm({ ...form, colonia: e.target.value })} placeholder="Juárez" />
setForm({ ...form, ciudad: e.target.value })} placeholder="Ciudad de México" />
setForm({ ...form, municipio: e.target.value })} placeholder="Cuauhtémoc" />
setForm({ ...form, estado: e.target.value })} placeholder="CDMX" />
setForm({ ...form, telefono: e.target.value.replace(/[^\d+\-() ]/g, '').slice(0, 20) })} placeholder="+52 55 1234 5678" />
{msg &&

{msg}

} {!msg &&
}
); } function BancosSection() { const { data: bancos, isLoading } = useBancos(); const createBanco = useCreateBanco(); const deleteBancoMut = useDeleteBanco(); const [nombre, setNombre] = useState(''); const [terminacion, setTerminacion] = useState(''); const handleAdd = async (e: React.FormEvent) => { e.preventDefault(); if (!nombre || !terminacion) return; try { await createBanco.mutateAsync({ banco: nombre, terminacionCuenta: terminacion }); setNombre(''); setTerminacion(''); } catch (err: any) { alert(err.response?.data?.message || 'Error al crear banco'); } }; const handleDelete = async (id: number) => { if (!confirm('Eliminar este banco?')) return; try { await deleteBancoMut.mutateAsync(id); } catch (err: any) { alert(err.response?.data?.message || 'Error al eliminar'); } }; return ( Bancos Cuentas bancarias para conciliacion {isLoading ? (

Cargando...

) : bancos && bancos.length > 0 ? (
{bancos.map((b) => (
{b.banco} ****{b.terminacionCuenta}
))}
) : (

No hay bancos registrados

)}
setNombre(e.target.value)} placeholder="BBVA" required />
setTerminacion(e.target.value.replace(/\D/g, '').slice(0, 4))} placeholder="1234" maxLength={4} required />
); } export default function ConfiguracionPage() { const { theme, setTheme } = useThemeStore(); const { user } = useAuthStore(); const { viewingTenantName } = useTenantViewStore(); const { selectedContribuyenteId, selectedContribuyenteRfc, selectedContribuyenteNombre } = useContribuyenteStore(); const empresaNombre = viewingTenantName || user?.tenantName; const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); const isDespacho = isDespachoTenant(user?.tenantRfc); return ( <>
{/* User Info */} Información del Usuario

Nombre

{user?.nombre}

Email

{user?.email}

Rol

{user?.role}

{/* Company Info */} Información de la Empresa

Empresa

{empresaNombre}

{/* Contribuyente header — shown when despacho has one selected */} {isDespacho && selectedContribuyenteId && ( Configuración de: {selectedContribuyenteNombre} {selectedContribuyenteRfc} )} {/* Regímenes Fiscales, Domicilio Fiscal, Bancos */} {(user?.role === 'owner' || user?.role === 'cfo') && ( isDespacho && !selectedContribuyenteId ? (

Selecciona un contribuyente en el header para ver su configuración fiscal.

) : ( <> ) )} {/* SAT Configuration */} Sincronizacion SAT Configura tu FIEL y la sincronizacion automatica de CFDIs con el SAT

Descarga automaticamente tus facturas emitidas y recibidas directamente del portal del SAT.

{/* Obligaciones Fiscales */} {(user?.role === 'owner' || user?.role === 'cfo' || user?.role === 'supervisor') && ( Obligaciones Fiscales Gestiona las obligaciones fiscales de tus contribuyentes

Recibe recomendaciones basadas en el régimen fiscal, agrega o elimina obligaciones según las necesidades de cada RFC.

)} {/* Notificaciones */} Notificaciones Activa o desactiva los correos informativos por contribuyente

Controla qué correos quieres recibir por cada cliente: documentos subidos, reporte semanal, recordatorios fiscales, vencimiento de suscripción.

{/* Preferencias de Facturación (auto-emisión de pagos de suscripción) */} Preferencias de Facturación Define cómo facturamos los pagos de tu suscripción a Horux 360

Elige si tus facturas salen con tus datos fiscales o como Público en General. Configura el uso CFDI (G03 Gastos en general / S01 Sin obligaciones) y el régimen a usar si tienes varios activos.

{/* Seguridad */} Seguridad Cambia tu contraseña y gestiona las sesiones activas de tu cuenta

Actualiza tu contraseña o cierra todas las sesiones activas si sospechas un acceso no autorizado.

{/* CSD / Facturapi */} Certificado de Sello Digital (CSD) Configura tu CSD para emitir facturas electrónicas desde Horux360

Sube tu certificado y llave privada para timbrar CFDIs directamente desde la plataforma.

{/* Admin global: edición de precios */} {isGlobalAdmin && ( <> Precios de suscripciones Modifica los precios de los planes Starter, Business, Business + IA y Enterprise.

Los cambios aplican a suscripciones nuevas y renovaciones. Las vigentes conservan el precio contratado.

Precios de timbres adicionales Modifica los precios de los paquetes de timbres adicionales (100, 1000, 10000).

Los cambios aplican a compras nuevas. Los paquetes ya vendidos conservan el precio que pagó el cliente.

Add-ons del catálogo Gestiona los complementos disponibles (Lolita IA, RFCs extra, módulo IA, etc.).

Modifica nombre, precio y disponibilidad de cada add-on. Los add-ons ya contratados conservan el precio al que se cobraron; los cambios aplican a contrataciones nuevas.

)}
); }