feat: metabase auto-registration + ui fixes + migration scripts

- Add metabase.service.ts for automatic DB registration on tenant creation
- Hook createTenant, addTenantToOwner and deleteTenant to sync with Metabase
- Add environment variables for Metabase integration
- Fix dashboard routing for global admin users
- Fix CFDI status casing (Vigente vs vigente)
- Fix sidebar empty nav crash
- Fix KPI null regimen_fiscal values
- Fix CFDI type mapping (EMITIDO/RECIBIDO)
- Update branding from Horux360 to Horux Despachos
- Add legacy migration scripts for central and tenant DBs
This commit is contained in:
Horux Dev
2026-04-28 00:34:41 +00:00
parent 56a05ba767
commit e8dc3aed67
18 changed files with 846 additions and 45 deletions

View File

@@ -177,8 +177,8 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
<div className="bg-gray-50 rounded-lg p-3 text-center">
<p className="text-xs text-gray-500 uppercase tracking-wide">Moneda</p>
<p className="text-sm font-semibold text-gray-800 mt-1">{cfdi.moneda || 'MXN'}</p>
{cfdi.typeCambio && cfdi.typeCambio !== 1 && (
<p className="text-xs text-gray-500">TC: {cfdi.typeCambio}</p>
{cfdi.tipoCambio && cfdi.tipoCambio !== 1 && (
<p className="text-xs text-gray-500">TC: {cfdi.tipoCambio}</p>
)}
</div>
<div className="bg-gray-50 rounded-lg p-3 text-center">

View File

@@ -121,7 +121,7 @@ export function CfdiViewerModal({ cfdi, open, onClose }: CfdiViewerModalProps) {
let xml = xmlContent;
if (!xml) {
xml = await getCfdiXml(cfdi.id);
xml = await getCfdiXml(cfdi.id.toString());
}
if (!xml) {

View File

@@ -51,13 +51,13 @@ export function SidebarCompact() {
const role = user?.role || 'visor';
const filteredNav = navigation.filter((item) => {
if ('feature' in item && item.feature && !hasFeature(plan, item.feature)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role as any)) return false;
return true;
});
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, role, user?.platformRoles);
const allNavigation = isGlobalAdmin
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]]
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]].filter(Boolean)
: filteredNav;
const handleLogout = async () => {
@@ -86,7 +86,7 @@ export function SidebarCompact() {
<Link href="/dashboard" className="flex items-center gap-2">
<Image
src="/logo.jpg"
alt="Horux360"
alt="Horux Despachos"
width={32}
height={32}
className="rounded-full flex-shrink-0"
@@ -95,7 +95,7 @@ export function SidebarCompact() {
'font-bold text-lg whitespace-nowrap transition-opacity duration-300',
expanded ? 'opacity-100' : 'opacity-0 w-0 overflow-hidden'
)}>
Horux360
Horux Despachos
</span>
</Link>
</div>

View File

@@ -49,13 +49,13 @@ export function SidebarFloating() {
const role = user?.role || 'visor';
const filteredNav = navigation.filter((item) => {
if ('feature' in item && item.feature && !hasFeature(plan, item.feature)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role as any)) return false;
return true;
});
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, role, user?.platformRoles);
const allNavigation = isGlobalAdmin
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]]
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]].filter(Boolean)
: filteredNav;
const handleLogout = async () => {
@@ -76,13 +76,13 @@ export function SidebarFloating() {
<div className="flex items-center gap-3 mb-6 px-2">
<Image
src="/logo.jpg"
alt="Horux360"
alt="Horux Despachos"
width={40}
height={40}
className="rounded-full shadow-lg shadow-primary/25"
/>
<div>
<span className="font-bold text-lg block">Horux360</span>
<span className="font-bold text-lg block">Horux Despachos</span>
<span className="text-xs text-muted-foreground">Análisis Fiscal</span>
</div>
</div>

View File

@@ -108,7 +108,7 @@ export function Sidebar() {
// (Horux 360) no tiene contribuyentes propios y nunca los tendrá.
const showOnboarding = (!contribuyentes || contribuyentes.length === 0) && role !== 'cliente' && !isGlobalAdmin;
const allNavigation = isGlobalAdmin
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]]
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]].filter(Boolean)
: filteredNav;
return (

View File

@@ -51,13 +51,13 @@ export function TopNav() {
const role = user?.role || 'visor';
const filteredNav = navigation.filter((item) => {
if ('feature' in item && item.feature && !hasFeature(plan, item.feature)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role)) return false;
if ('roles' in item && item.roles && !item.roles.includes(role as any)) return false;
return true;
});
const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, role, user?.platformRoles);
const allNavigation = isGlobalAdmin
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]]
? [...filteredNav.slice(0, -1), ...adminNavigation, filteredNav[filteredNav.length - 1]].filter(Boolean)
: filteredNav;
const handleLogout = async () => {
@@ -79,7 +79,7 @@ export function TopNav() {
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
<span className="text-primary-foreground font-bold text-lg">H</span>
</div>
<span className="font-bold text-xl">Horux360</span>
<span className="font-bold text-xl">Horux Despachos</span>
</Link>
{/* Navigation */}