Files
HoruxDespachosNuevo/docs/plans/2026-04-26-admin-global-setup.md

10 KiB

Setup admin global + gestión de clientes (2026-04-26 turno tardío)

Sesión continuación del día. Foco: habilitar al admin global del fork para que pueda operar como plataforma — gestión de clientes, edición de catálogos (planes, add-ons), auto-facturación de pagos, y experiencia de login dedicada.

Relacionado: 2026-04-26-session.md (índice del día), 2026-04-26-sprints-1-2-3.md (sprints anteriores), y los docs específicos de IVA y notificaciones.


1. Bootstrap admin global del fork

El fork Horux_despacho no tenía tenant raíz ni platform_admin. Ejecutado:

HORUX_ADMIN_EMAIL=carlos@horuxfin.com \
HORUX_ADMIN_NOMBRE=Carlos \
HORUX_TI_EMAIL=ivan@horuxfin.com \
HORUX_TI_NOMBRE=Ivan \
pnpm bootstrap:admin-global

Crea:

  • Tenant Horux 360 (RFC HTS240708LJA, plan enterprise, dbMode MANAGED).
  • carlos@horuxfin.com con rol platform_admin (admin global).
  • ivan@horuxfin.com con rol platform_ti (TI superset).

Contraseña fijada manualmente a Admin12345! con bcrypt cost 12 + tokenVersion: increment para invalidar JWTs viejos.

Cuentas finales del fork (todas con Admin12345!):

  • carlos@horuxfin.com — platform_admin
  • ivan@horuxfin.com — platform_ti
  • jd@demo.com — owner Patito
  • jf@demo.com — owner Zorro
  • supervisor@patito.com / auxiliar@patito.com / cliente@patito.com

2. Login y home redirect

apps/web/app/(auth)/login/page.tsx ahora detecta el admin global tras el login y lo manda directo a /clientes (gestión de tenants), no a /dashboard (operativo despacho). dashboard/page.tsx también tiene useEffect defensivo que redirige si llega ahí por URL directa.

const isGlobalAdmin = isGlobalAdminRfc(response.user?.tenantRfc, userRole, platformRoles);
if (isGlobalAdmin) router.push('/clientes');

3. Sidebar — banner "Configuración inicial" oculto

components/layouts/sidebar.tsx: el banner que linkea a /onboarding solo se muestra cuando (!contribuyentes || contribuyentes.length === 0) && role !== 'cliente' && !isGlobalAdmin. El admin global nunca tendrá contribuyentes en su tenant raíz Horux 360 — el banner sería ruido permanente.

(Una versión inicial también limpiaba todos los items operativos del sidebar para el admin global. Revertida — el admin global mantiene acceso completo para impersonar tenants. Solo cambia el banner.)


4. /admin/usuarios — fix roleLabels[role].icon undefined

Bug latente desde que se introdujeron los roles del despacho. roleLabels solo tenía owner/contador/visor (Horux 360 mainline); cuando aparecía un user con rol cfo, supervisor, auxiliar o cliente, el lookup retornaba undefined y roleInfo.icon lanzaba TypeError.

Fix: agregar los 7 roles + defaultRoleInfo como fallback defensivo:

const roleLabels: Record<string, { label: string; icon: typeof Shield; color: string }> = {
  owner: { label: 'Dueño', icon: Shield, color: 'text-primary' },
  cfo: { label: 'CFO', icon: Briefcase, color: 'text-indigo-600' },
  contador: { label: 'Contador', icon: Calculator, color: 'text-green-600' },
  supervisor: { label: 'Supervisor', icon: UserCheck, color: 'text-blue-600' },
  auxiliar: { label: 'Auxiliar', icon: UserCog, color: 'text-cyan-600' },
  cliente: { label: 'Cliente', icon: User, color: 'text-amber-600' },
  visor: { label: 'Visor', icon: Eye, color: 'text-muted-foreground' },
};
const defaultRoleInfo = { label: 'Sin rol', icon: User, color: 'text-muted-foreground' };

5. /configuracion/precios-suscripcion — planes despacho

La página leía plan_prices (BD central, planes Horux 360 legacy: starter/business/business_ia/enterprise). En el fork no aplican.

Reescrita para mostrar los 4 planes despacho desde DESPACHO_PLAN_PRICES:

Plan Mensual Anual 1er año Anual renovación RFCs Timbres/mes
Mi Empresa $580 $5,800 $5,800 1 50
Mi Empresa + $900 $9,000 $9,000 1 50
Business Control No aplica $25,850 $25,850 100 0
Enterprise No aplica $43,000 $43,000 100 0

Modo read-only porque los precios viven en DESPACHO_PLAN_PRICES (catálogo estático en @horux/shared).

Capacidad latente para edición

Migración aplicada 20260426230000_despacho_plan_prices:

CREATE TABLE despacho_plan_prices (
  plan TEXT PRIMARY KEY,
  monthly DECIMAL(10,2),
  first_year DECIMAL(10,2) NOT NULL,
  renewal DECIMAL(10,2) NOT NULL,
  permite_monthly BOOLEAN NOT NULL DEFAULT false,
  updated_at TIMESTAMP NOT NULL
);

Seed inicial con los 4 planes y sus valores. Permite migrar a editable cuando se quiera — service + endpoints + UI editable quedan como follow-up.


6. Gestión de clientes (/clientes)

Página enriquecida con KPIs y operaciones para el admin global.

Backend

  • Service: apps/api/src/services/admin-clientes.service.ts
    • getClientesStats({ from, to }) — devuelve:
      • suscripcionesPorPlan: groupBy plan con count (status=authorized).
      • ingresos: sum + count de Payment.amount con status=approved en rango.
      • noRenovaciones: subs con currentPeriodEnd en rango y status terminal (cancelled / trial_expired / paused) + datos del tenant.
      • usuariosPorCliente: count de memberships activos por tenant.
    • getTenantUsuarios(tenantId) — drill-down: lista users + rol + isOwner.
  • Controller: controllers/admin-clientes.controller.ts con requireStaff(req) (platform_admin/ti).
  • Routes: routes/admin-clientes.routes.ts montadas en /api/admin/clientes (GET /stats, GET /:tenantId/usuarios).

UI

  • Selector de rango de fechas (default mes en curso).
  • 4 KPI cards: clientes registrados / suscripciones activas (con breakdown por plan en pie) / ingresos del periodo / no renovaciones.
  • Tabla "Clientes que no renovaron" expandible con detalle (cliente, RFC, plan, vencimiento, status).
  • En cada item de la lista de clientes, click sobre el contador de usuarios abre un modal con la lista (email, nombre, rol, badge de owner).

7. Auto-facturación de pagos al cliente

CLAUDE.md ya documentaba invoicing.service.ts:emitInvoiceIfApplicable. Cada webhook payment.approved lo dispara. Idempotente por Payment.facturapiInvoiceId, fail-soft si Facturapi falla.

Ajustes en este turno

  • PLAN_LABELS extendido con los 4 planes despacho. Antes la factura mostraba el codename (mi_empresa, business_cloud) en lugar de "Mi Empresa" / "Enterprise".
  • Texto "Horux 360" → "Horux Despachos" en la descripción del CFDI y en el concepto de timbres.

Prerequisito operativo (manual del admin)

El emisor de las facturas es el tenant Horux 360 (RFC HTS240708LJA). Requiere:

  1. CSD válido subido vía /configuracion/csd por Carlos (admin global).
  2. tenants.facturapi_org_id poblado tras crear la organización en Facturapi.

Hoy la columna está en null para el tenant recién bootstrapeado. Mientras falte, emitInvoiceIfApplicable registra error en consola y Payment.facturapiInvoiceId queda null. Pendiente operativo: que Carlos suba el CSD.


8. Gestión de add-ons (/configuracion/addons)

Página nueva para que el admin global edite el catálogo de add-ons.

Backend

  • Controller: controllers/admin-addons.controller.ts
    • listCatalogo — incluye count de suscripciones activas por add-on.
    • updateCatalogoItem(id, { nombre?, precio?, active? }) con audit log.
  • Routes: /api/admin/addons/catalogo (GET, PUT /:id). requireStaff.

UI

  • Tabla con columnas: codename, nombre (editable), precio MXN (editable), frecuencia, suscripciones activas (read-only), estado (badge), acciones.
  • Edición inline con dos botones (guardar / cancelar) por fila.
  • Toggle de activación con confirmación condicional: si el add-on tiene ≥1 suscripción activa, pide confirmar antes de desactivar.
  • Card link agregada al /configuracion raíz solo visible para admin global.

Add-ons del catálogo actual

  • lolita_ia_contribuyente — Lolita IA (por contribuyente) — $250/mes
  • modulo_ia — Módulo IA Fiscal — $390/mes
  • rfcs_extra_10 — +10 RFCs adicionales — $190/mes
  • rfcs_extra_50 — +50 RFCs adicionales — $690/mes
  • timbres_extra_500 — +500 timbres mensuales — $490/mes
  • contribuyente_extra_business_cloud — Contribuyente adicional — $45/mes (overage automático Business Control / Enterprise)

9. Sync al repo OneDrive

Tras cada bloque de cambios, ejecutado:

robocopy "C:\Users\chtr1\Downloads\Horux_despacho" "C:\Users\chtr1\OneDrive\Documentos\GitHub\Horux_despachos" /E `
  /XD node_modules .next .turbo dist .pnpm data email-previews .git xmls `
  /XF .env .env.local "*.log" tsconfig.tsbuildinfo /NFL /NDL /NJH

Excluye node_modules, builds, caches, secretos, datos de tenants, y el .git/ del destino para preservar el repo origen.


10. Pendientes derivados

  • Cargar CSD del Horux 360 vía /configuracion/csd para activar auto-facturación real. Sin esto, emitInvoiceIfApplicable falla silencioso.
  • Hacer editable DespachoPlanPrice desde UI: la migración + seed están aplicados pero falta service + endpoint + UI editable. Mientras tanto la página de precios queda read-only.
  • Notificación al desactivar add-on con suscripciones activas: hoy se bloquean las contrataciones nuevas pero las activas siguen vigentes. Quizá enviar email al owner avisando que el add-on quedará "deprecated".
  • Visual cue del prerequisito faltante de Facturapi org: agregar alerta en /clientes o /configuracion cuando el tenant Horux 360 no tenga facturapi_org_id para que Carlos lo configure.

11. Pendientes históricos (sin cambio)

  • SMTP .env prod (cron de notificaciones loguea a consola sin SMTP).
  • Cloudflare Tunnel + FRONTEND_URL HTTPS dev.
  • Typecheck web cleanup (~18 errores preexistentes).
  • Nómina (tipo N) y Carta Porte sin implementar.
  • SAT rejections — esperando captura real.
  • Re-notificación bug 2.4 visibilidad auxiliares (sin reproducción específica).