fix(fiel/csd): usa contribuyente seleccionado sin depender de isDespachoTenant

Problema: isDespachoTenant(user?.tenantRfc) compara contra prefijo
'DESPACHO_' que ningun tenant real usa. Esto hacia que sat/page.tsx
siempre usara el endpoint legacy a nivel tenant, ignorando el contribuyente
seleccionado y mostrando datos del tenant en lugar del contribuyente.

Cambios:
- sat/page.tsx: elimina isDespachoTenant, usa selectedContribuyenteId
  directamente para determinar contribId. Muestra banner cuando no hay
  contribuyente seleccionado.
- csd/page.tsx: agrega banner de contribuyente seleccionado y oculta
  la UI de CSD cuando no hay contribuyente seleccionado.
- tenant-selector.tsx: limpia selectedContribuyenteId al cambiar de
  tenant para evitar stale state.
This commit is contained in:
Horux Dev
2026-05-16 15:53:17 +00:00
parent 414e862a44
commit a8503fd574
3 changed files with 44 additions and 20 deletions

View File

@@ -7,7 +7,7 @@ import { useTimbres } from '@/lib/hooks/use-facturacion';
import { apiClient } from '@/lib/api/client'; import { apiClient } from '@/lib/api/client';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useContribuyenteStore } from '@/stores/contribuyente-store'; import { useContribuyenteStore } from '@/stores/contribuyente-store';
import { Shield, Upload, Check, AlertCircle, Receipt, Palette, Image } from 'lucide-react'; import { Shield, Upload, Check, AlertCircle, Receipt, Palette, Image, Building2 } from 'lucide-react';
function CustomizationSection() { function CustomizationSection() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -147,7 +147,7 @@ function CustomizationSection() {
} }
export default function CsdConfigPage() { export default function CsdConfigPage() {
const { selectedContribuyenteId } = useContribuyenteStore(); const { selectedContribuyenteId, selectedContribuyenteRfc, selectedContribuyenteNombre } = useContribuyenteStore();
const { data: orgStatus, isLoading } = useQuery({ const { data: orgStatus, isLoading } = useQuery({
queryKey: ['facturapi-org-contrib', selectedContribuyenteId], queryKey: ['facturapi-org-contrib', selectedContribuyenteId],
queryFn: () => selectedContribuyenteId queryFn: () => selectedContribuyenteId
@@ -228,8 +228,28 @@ export default function CsdConfigPage() {
<> <>
<Header title="Configuración CSD" /> <Header title="Configuración CSD" />
<main className="p-6 space-y-6"> <main className="p-6 space-y-6">
{/* Show which contribuyente or prompt to select */}
{!selectedContribuyenteId && (
<Card className="border-amber-200 bg-amber-50 dark:bg-amber-950/20">
<CardContent className="py-4 flex items-center gap-3">
<Building2 className="h-5 w-5 text-amber-600" />
<p className="text-sm text-amber-800 dark:text-amber-300">Selecciona un contribuyente en el header para ver y configurar su CSD.</p>
</CardContent>
</Card>
)}
{selectedContribuyenteId && (
<Card className="bg-primary/5 border-primary/20">
<CardContent className="py-3 px-5 flex items-center gap-2">
<Building2 className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">CSD de: {selectedContribuyenteNombre}</span>
<span className="text-xs text-muted-foreground font-mono">{selectedContribuyenteRfc}</span>
</CardContent>
</Card>
)}
{/* Estado de la organización */} {/* Estado de la organización */}
<Card> {selectedContribuyenteId && <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2 text-base"> <CardTitle className="flex items-center gap-2 text-base">
<Shield className="h-4 w-4" /> <Shield className="h-4 w-4" />
@@ -262,10 +282,10 @@ export default function CsdConfigPage() {
</div> </div>
)} )}
</CardContent> </CardContent>
</Card> </Card>}
{/* Subir CSD */} {/* Subir CSD */}
{orgStatus?.configured && !orgStatus.hasCsd && ( {selectedContribuyenteId && orgStatus?.configured && !orgStatus.hasCsd && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2 text-base"> <CardTitle className="flex items-center gap-2 text-base">
@@ -299,7 +319,7 @@ export default function CsdConfigPage() {
)} )}
{/* CSD ya configurado */} {/* CSD ya configurado */}
{orgStatus?.configured && orgStatus.hasCsd && ( {selectedContribuyenteId && orgStatus?.configured && orgStatus.hasCsd && (
<Card> <Card>
<CardContent className="pt-6 text-center space-y-2"> <CardContent className="pt-6 text-center space-y-2">
<div className="h-12 w-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mx-auto"> <div className="h-12 w-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mx-auto">
@@ -314,10 +334,10 @@ export default function CsdConfigPage() {
)} )}
{/* Personalización de factura */} {/* Personalización de factura */}
{orgStatus?.configured && <CustomizationSection />} {selectedContribuyenteId && orgStatus?.configured && <CustomizationSection />}
{/* Timbres */} {/* Timbres */}
<Card> {selectedContribuyenteId && <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2 text-base"> <CardTitle className="flex items-center gap-2 text-base">
<Receipt className="h-4 w-4" /> <Receipt className="h-4 w-4" />
@@ -356,10 +376,10 @@ export default function CsdConfigPage() {
</p> </p>
)} )}
</CardContent> </CardContent>
</Card> </Card>}
{/* Mensajes */} {/* Mensajes */}
{message && ( {selectedContribuyenteId && message && (
<div className={`p-3 rounded-lg text-sm flex items-center gap-2 ${ <div className={`p-3 rounded-lg text-sm flex items-center gap-2 ${
message.type === 'success' ? 'bg-green-50 text-green-800 dark:bg-green-950 dark:text-green-200' message.type === 'success' ? 'bg-green-50 text-green-800 dark:bg-green-950 dark:text-green-200'
: 'bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-200' : 'bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-200'

View File

@@ -10,7 +10,7 @@ import { getFielStatus, deleteFiel } from '@/lib/api/fiel';
import { useTenantViewStore } from '@/stores/tenant-view-store'; import { useTenantViewStore } from '@/stores/tenant-view-store';
import { useContribuyenteStore } from '@/stores/contribuyente-store'; import { useContribuyenteStore } from '@/stores/contribuyente-store';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
import { isDespachoTenant } from '@horux/shared';
import { Building2 } from 'lucide-react'; import { Building2 } from 'lucide-react';
import type { FielStatus } from '@horux/shared'; import type { FielStatus } from '@horux/shared';
@@ -21,11 +21,9 @@ export default function SatConfigPage() {
const [deleting, setDeleting] = useState(false); const [deleting, setDeleting] = useState(false);
const { viewingTenantId } = useTenantViewStore(); const { viewingTenantId } = useTenantViewStore();
const { selectedContribuyenteId, selectedContribuyenteRfc, selectedContribuyenteNombre } = useContribuyenteStore(); const { selectedContribuyenteId, selectedContribuyenteRfc, selectedContribuyenteNombre } = useContribuyenteStore();
const user = useAuthStore(s => s.user);
const isDespacho = isDespachoTenant(user?.tenantRfc);
// For despachos, use per-contribuyente FIEL; for Horux360, use tenant-level // Per-contribuyente FIEL when a contribuyente is selected; otherwise tenant-level legacy
const contribId = isDespacho ? selectedContribuyenteId : null; const contribId = selectedContribuyenteId || null;
const fetchFielStatus = async () => { const fetchFielStatus = async () => {
setLoading(true); setLoading(true);
@@ -81,8 +79,8 @@ export default function SatConfigPage() {
<Header title="Configuración SAT" /> <Header title="Configuración SAT" />
<main className="p-6 space-y-6"> <main className="p-6 space-y-6">
{/* Despacho: show which contribuyente or prompt to select */} {/* Show which contribuyente or prompt to select */}
{isDespacho && !selectedContribuyenteId && ( {!selectedContribuyenteId && (
<Card className="border-amber-200 bg-amber-50 dark:bg-amber-950/20"> <Card className="border-amber-200 bg-amber-50 dark:bg-amber-950/20">
<CardContent className="py-4 flex items-center gap-3"> <CardContent className="py-4 flex items-center gap-3">
<Building2 className="h-5 w-5 text-amber-600" /> <Building2 className="h-5 w-5 text-amber-600" />
@@ -91,7 +89,7 @@ export default function SatConfigPage() {
</Card> </Card>
)} )}
{isDespacho && selectedContribuyenteId && ( {selectedContribuyenteId && (
<Card className="bg-primary/5 border-primary/20"> <Card className="bg-primary/5 border-primary/20">
<CardContent className="py-3 px-5 flex items-center gap-2"> <CardContent className="py-3 px-5 flex items-center gap-2">
<Building2 className="h-4 w-4 text-primary" /> <Building2 className="h-4 w-4 text-primary" />
@@ -101,8 +99,8 @@ export default function SatConfigPage() {
</Card> </Card>
)} )}
{/* For despachos without RFC selected, hide everything below the banner */} {/* Hide FIEL UI when no contribuyente is selected */}
{isDespacho && !selectedContribuyenteId ? null : ( {!selectedContribuyenteId ? null : (
<> <>
{/* Estado de la FIEL */} {/* Estado de la FIEL */}
<Card> <Card>

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { getTenants, type Tenant } from '@/lib/api/tenants'; import { getTenants, type Tenant } from '@/lib/api/tenants';
import { useTenantViewStore } from '@/stores/tenant-view-store'; import { useTenantViewStore } from '@/stores/tenant-view-store';
import { useContribuyenteStore } from '@/stores/contribuyente-store';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
import { Building, ChevronDown, Check, X } from 'lucide-react'; import { Building, ChevronDown, Check, X } from 'lucide-react';
import { cn } from '@horux/shared-ui'; import { cn } from '@horux/shared-ui';
@@ -14,6 +15,7 @@ export function TenantSelector() {
const { user } = useAuthStore(); const { user } = useAuthStore();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { viewingTenantId, viewingTenantName, setViewingTenant, clearViewingTenant } = useTenantViewStore(); const { viewingTenantId, viewingTenantName, setViewingTenant, clearViewingTenant } = useTenantViewStore();
const { clearSelectedContribuyente } = useContribuyenteStore();
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles);
const { data: tenants, isLoading } = useQuery({ const { data: tenants, isLoading } = useQuery({
@@ -66,12 +68,14 @@ export function TenantSelector() {
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
clearViewingTenant(); clearViewingTenant();
clearSelectedContribuyente();
queryClient.invalidateQueries(); queryClient.invalidateQueries();
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation(); e.stopPropagation();
clearViewingTenant(); clearViewingTenant();
clearSelectedContribuyente();
queryClient.invalidateQueries(); queryClient.invalidateQueries();
} }
}} }}
@@ -98,6 +102,7 @@ export function TenantSelector() {
<button <button
onClick={() => { onClick={() => {
clearViewingTenant(); clearViewingTenant();
clearSelectedContribuyente();
setOpen(false); setOpen(false);
queryClient.invalidateQueries(); queryClient.invalidateQueries();
}} }}
@@ -126,6 +131,7 @@ export function TenantSelector() {
key={tenant.id} key={tenant.id}
onClick={() => { onClick={() => {
setViewingTenant(tenant.id, tenant.nombre, tenant.rfc); setViewingTenant(tenant.id, tenant.nombre, tenant.rfc);
clearSelectedContribuyente();
setOpen(false); setOpen(false);
queryClient.invalidateQueries(); queryClient.invalidateQueries();
}} }}