- Extiende KpiData con ncsEmitidas, ncsEmitidasPorRegimen, ncsRecibidas y ncsRecibidasPorRegimen.
- En getKpis se reutilizan calcularNcsEmitidasPorRegimen y calcularNcsRecibidasPorRegimen en paralelo.
- En el dashboard se agregan dos KpiCard y su desglose por régimen.
This reverts commit d3b326e.
The deployment caused reports of blank screens and 400 errors. Reverting to restore stable state while investigating root cause.
- Add Sheet primitive component for mobile drawers
- Add MobileNav with hamburger menu for dashboard layout
- Hide desktop sidebars on mobile; show mobile header
- Make dashboard header responsive with stacked layout on small screens
- Hide selector text on mobile, show icons only
- Convert fixed-width filters to responsive widths (CFDI, Clientes, Admin, Documentos, Alertas)
- Cap dialog widths to 95vw on mobile (CFDI viewer, Documentos, Reportes, Contribuyentes, Facturación)
- Make calendar grid smaller and use single-letter weekdays on mobile
- Update viewport to include viewport-fit=cover for Samsung safe areas
- Backend inviteUsuario: permite owner, cfo y supervisor
- Backend valida que supervisor solo pueda invitar rol cliente
- Backend addClienteAcceso: supervisor solo puede asignar contribuyentes
que tenga visibles (getEntidadesVisibles)
- Frontend: supervisor ve botón Invitar Usuario y solo puede seleccionar
rol Cliente en el dropdown
- Backend: getSupervisor devuelve supervisorNombre desde Prisma
- Frontend: usa SelectTrigger con renderizado manual del label seleccionado
en lugar de depender de SelectValue, que no siempre encontraba el texto
del SelectItem cuando el supervisor no estaba en la lista de carteras
- Backend getSupervisor ahora devuelve supervisorNombre buscando en Prisma
- Frontend usa supervisorNombre para mostrar en Select cuando el supervisor
no está en la lista de carteras/supervisores
- Agrega migración 050 con columnas de aprobación de cliente
(requiere_aprobacion_cliente, estado_cliente, aprobado_por_cliente, etc.)
- Backend: endpoints /aprobar-cliente y /rechazar-cliente con validación de permisos
- Backend: list/download permiten acceso a clientes filtrando por entidades visibles
- Backend: notificación por email a clientes cuando se les solicita aprobación
- Frontend: checkbox independiente para solicitar aprobación del cliente
- Frontend: badge de estado combinado (owner + cliente)
- Frontend: botones de aprobar/rechazar para clientes en su propio flujo
- Agrega helper withJitOff en impuestos.service.ts
- Ejecuta getResumenIva, getIvaMensual y readResumenIvaFromCache con SET LOCAL jit = off
- Evita compilación JIT de ~17s en queries con costo estimado alto
feat(contribuyentes): auto-asignar a cartera del supervisor
- Al crear contribuyente con supervisorUserId, se agrega automáticamente
a todas las carteras top-level del supervisor
feat(permisos): restricciones de UI por rol en contribuyentes
- Oculta botón Add-ons para roles distintos de owner/cfo
- Oculta botón Eliminar contribuyente para no-owner
- Oculta botón Agregar RFC para auxiliar/visor/cliente/contador
feat(cfdi): ver CFDI desde conceptos y forma de pago en Excel
- Agrega botón Ver CFDI en cada fila de la tabla de Conceptos
- Agrega columna Forma de Pago en export Excel de CFDIs
- Agrega columna Forma de Pago en export individual de CFDI
chore(migraciones): índices GIN para relaciones de activos
- 048: índices btree parciales para activos
- 049: índices GIN para cfdis_relacionados y uuid_relacionado
- Backend: agrega 'supervisor' a ROLES_UPLOAD en documentos.controller.ts
- Frontend: agrega 'supervisor' a ROLES_UPLOAD y ROLES_UPLOAD_EXTRA en
documentos/page.tsx para habilitar botones de subir declaración,
comprobante de pago, eliminar y subir PDFs extra
- Sidebars/topnav: agrega 'auxiliar' y 'cliente' a la opción Configuracion
- /configuracion/page.tsx: auxiliar y cliente solo ven Información de Usuario,
Información de Empresa y Seguridad (cambio de contraseña). Todo lo demás
(FIEL, Obligaciones, Notificaciones, Facturación, CSD) queda restringido
a owner/cfo/supervisor
- Backend: agrega 'supervisor' a authorize() de rutas:
- POST/DELETE /contribuyentes/:id/fiel
- POST /contribuyentes/:id/facturapi/csd
- POST/DELETE /contribuyentes/:id/obligaciones/*
- Frontend: muestra tarjeta 'Obligaciones Fiscales' en /configuracion
para rol supervisor
- Agrega contador 'X de Y RFCs' debajo del título de la página
- Usa DESPACHO_PLANS desde @horux/shared para obtener maxRfcs del plan actual
- Durante trial muestra 'X de 5 RFCs'
- Planes ilimitados muestran solo 'X RFCs'
- Cambia la opción 'SUELDOS' por 'ISN' (Impuesto Sobre Nómina)
- Agrega nueva opción 'ISH' (Impuesto Sobre Hospedaje)
- ISH no cierra alertas ni obligaciones (aún no hay flujo definido)
- ISN mantiene keywords de sueldos/salarios/nómina + agrega 'isn'
- Migración 047: actualiza declaraciones históricas SUELDOS→ISN en BD
- Backend: POST /cfdi/download-xmls acepta CfdiFilters, usa getXmlsByFilters con LIMIT 1000
- Frontend: eliminados checkboxes y estado selectedIds; botón Descargar XMLs usa filtros activos
- Si >1000 resultados, muestra confirm() de advertencia pero permite proceder
- Agregada documentación técnica y changelog
- Agregar prop suggestions a FilterHeader con dropdown de opciones
- Calcular valores unicos de rfc/nombre emisor/receptor desde los
CFDIs cargados en memoria
- Filtrar sugerencias segun texto escrito (max 8 resultados)
- Al seleccionar una sugerencia se aplica el filtro y cierra el popover
- Mover FilterHeader fuera de ConciliacionPage para evitar
desmonte/remonte en cada render (causaba perdida de foco)
- Agregar debounce de 300ms al input de filtro para reducir
re-renders mientras el usuario escribe
- Quitado Invitaciones Trial del sidebar (4 layouts)
- Agregado tab Invitaciones Trial dentro de /admin/usuarios
- Componente reutilizable invitaciones-trial-tab.tsx
- Agregada nueva opcion Tareas en el sidebar principal
- Nueva pagina /tareas para ver y marcar tareas operativas
- Endpoint GET /tareas/mis-tareas con periodo actual
- Quitado boton de marcar completada de obligaciones fiscales en /pendientes
- Componente seguimiento-auxiliares.tsx con tabs Asignadas/Sin asignar
- Tabs internos Obligaciones/Tareas en cada vista
- API client y hooks para asignaciones
- Fix: invalidar query sin-asignar al asignar/desasignar
- Frontend: muestra input 'No. Cuenta Predial' en sección 'Datos del Inmueble'
cuando el régimen del emisor es 606 (Arrendamiento), antes de Conceptos
- Frontend: incluye cuentaPredial en payload; se resetea al cambiar contribuyente
- Backend: pasa property_tax_account a nivel de cada item en Facturapi
para facturapi.service.ts y contribuyente-facturapi.service.ts
- Build y deploy exitosos
- Cambia label de 'Precio Unitario (IVA incluido)' a 'Precio Unitario (sin IVA)'
- Elimina división interna price/(1+iva) en calcConcepto; ahora price es la base
- Cambia taxIncluded: true → false en payload enviado a backend
- Backend: tax_included default false en facturapi.service.ts y
contribuyente-facturapi.service.ts
- Build y deploy exitosos
- Frontend: input datetime-local visible solo para tipos I, E, T
(no P). Default al día actual a las 12:00. Se resetea al cambiar tipo.
- Frontend: validación en handleSubmit: fecha ≤ ahora y ≥ ahora-72h
- Backend controller: validación idéntica antes de consumir timbre
- Backend servicios: pasa campo 'date' al payload de Facturapi
cuando viene 'fechaEmision' en el body
- Build y deploy exitosos
Factura Global & fecha_efectiva:
- Migracion 045_factura_global.sql: periodicidad, meses_global, año_global, fecha_efectiva
- sat-parser.service.ts: extrae InformacionGlobal del XML
- sat.service.ts: calcFechaEfectiva con soporte bimestral (periodicidad 05)
- metricas-compute, dashboard, impuestos, cfdi, export, conciliacion, alertas:
reemplaza fecha_emision-1h por COALESCE(fecha_efectiva, fecha_emision-1h)
- Script recalc-metricas.ts para recalculo manual
Fallback datos fiscales tenant → contribuyente:
- contribuyente.service.ts: fetchTenantFiscalData + mergeContribuyenteWithTenant
rellena regimenFiscal, codigoPostal y domicilio cuando el contribuyente
tiene el mismo RFC que el tenant y sus campos estan vacios
- contribuyente.controller.ts y contribuyente-config.controller.ts:
pasan req.user!.tenantId al servicio
Fix critico SAT sync:
- sat.service.ts: anio_global → año_global en INSERT/UPDATE de CFDIs
(la migracion creo 'año_global' con tilde; el codigo usaba 'anio_global',
causando fallo en 100% de inserciones de CFDI)
- determineChunkMonths: salta sondeo si existe job previo con requestIds
- MAX_POLL_ATTEMPTS: 45 → 500 (~8h) para syncs iniciales grandes
Docs:
- docs/sessions/2026-05-22-factura-global-contribuyente-fallback.md
- Inicializar saldo_pendiente_mxn al emitir facturas I/PPD vía Facturapi
(antes quedaba NULL y no aparecían en complemento de pago)
- Validar ownership en cancelación: backend rechaza 403 si el caller
intenta cancelar una factura de otro contribuyente
- Frontend: ocultar botón cancelar si no se es el emisor de la factura
- Frontend: enviar contribuyenteId en la petición de cancelación
El admin global ahora puede crear usuarios directamente desde
/admin/usuarios sin depender de que un owner los invite.
Backend:
- Nuevo endpoint POST /usuarios/global (controller + service)
- Valida límite de usuarios del plan del tenant destino
- Si el email ya existe, agrega membership al tenant destino
- Si no existe, crea user con temp password + membership
- Schema Zod: email, nombre, role, tenantId, supervisorUserId?
Frontend:
- Botón 'Agregar Usuario' en /admin/usuarios
- Formulario con: nombre, email, rol, empresa
- Hook useCreateUsuarioGlobal con invalidación de queries
Antes, createTenant() solo seteaba nombre, rfc, plan y databaseName.
Ahora registra tenants completos como despachos:
- dbMode: 'MANAGED'
- verticalProfile: CONTABLE | JURIDICO | ARQUITECTURA
- trialEndsAt: +30 días para plan trial
- codigoPostal: opcional (se llena automáticamente de la CSF al subir FIEL)
Frontend:
- Selector de Tipo de Despacho en /clientes
- C.P. omitido del formulario (viene de CSF -> sincronizarDatosFiscales)
- Tipos Tenant y CreateTenantData actualizados
Backend:
- getAllTenants y getTenantById retornan verticalProfile y codigoPostal
Refs: docs/sessions/2026-05-04-fix-clientes-crea-despacho.md
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.
- Backend (cfdi.service.ts): getConceptosList ahora soporta filtro noIdentificacion
via cc.no_identificacion ILIKE
- Frontend API (cfdi.ts): ConceptosFilters incluye noIdentificacion; se envía
como query param
- Frontend página (cfdi/page.tsx):
* Nuevo estado noIdentificacion en conceptosFilters
* Nueva columna 'No. Identificación' en header de tabla con Popover filtro
* Celda no_identificacion renderizada en cada fila
* Export a Excel respeta el nuevo filtro
- Nuevo endpoint GET /reportes/estado-resultados-detallado con cálculo contable:
* Ventas, Devoluciones, Ventas netas, Costo de ventas, Utilidad bruta,
Gastos operativos, Utilidad de la operación
* Fórmula: subtotal_mxn - descuento_mxn (sin impuestos), nómina usa total_mxn
* Excluye anticipos (uso_cfdi=P01 o clave_prod_serv=84111506)
* Filtro por régimen fiscal opcional
* Año anterior calculado automáticamente
- Nuevo endpoint GET /reportes/estado-resultados/drill-down:
* Nivel 1: resumen agrupado por RFC
* Nivel 2: CFDIs individuales filtrados por categoría
* Categorías: ventas, devoluciones, costo-ventas, gastos-operativos
- Nuevo endpoint GET /reportes/estado-resultados/export:
* Genera Excel con formato condicional (verde/rojo, negritas)
- Frontend:
* Tabla vertical con % vertical, año anterior y variación %
* Filas clickeables para drill-down modal de 2 niveles
* Top 10 Clientes/Proveedores mantenidos debajo
* Selector de régimen conectado al reporte
- Fix: NaN en total de drill-down nivel 2 por numeric como string en pg
* Agregado ::float en queries SQL de CFDIs individuales
Backend:
- client-invitations.service.ts: funcion resendInvitation() que
genera nuevo token, actualiza expiresAt y reenvia el email.
- Controller + routes: POST /invitations/client/:id/resend
Frontend:
- API client + hook useResendInvitation con invalidacion de cache.
- Pagina /admin/invitar-cliente: boton 'Reenviar' por cada
invitacion pendiente en la tabla.
Refs: docs/CAMBIOS-2026-05-09.md
El SelectValue del proyecto solo muestra el value raw (el ID).
Reemplazado por un span dentro de SelectTrigger que busca
el banco seleccionado por su ID y muestra el nombre + terminacion.
Creada funcion local formatCurrencyConciliacion con
minimumFractionDigits/maximumFractionDigits = 2.
El resto del sitio mantiene formatCurrency original sin decimales.
Backend:
- Nuevo modelo Prisma ClientInvitation con token unico, expiracion
y estados (pending/accepted/expired).
- Migracion: 20260511213955_add_client_invitations
- Service client-invitations.service.ts: crear invitacion,
validar token, registrar desde invitacion (reutiliza logica
de creacion de tenant + usuario de despacho.service).
- Controller + routes: POST /invitations/client (admin),
GET /invitations/client/validate/:token (publico),
POST /invitations/client/register/:token (publico),
GET /invitations/client (admin).
- Email template client-invitation.ts con link a
/invitacion/registro/{token}.
- Agregado sendClientInvitation a email.service.
Frontend:
- Pagina /invitacion/registro/[token] para que el invitado
complete registro (nombre, password, despacho, RFC, perfil).
- Pagina /admin/invitar-cliente para que admin global envie
invitaciones y vea el historial.
- Hooks useCreateInvitation, useValidateInvitationToken,
useRegisterFromInvitation, useClientInvitations.
- API client lib/api/client-invitations.ts.
Infra:
- PM2 ecosystem.config.js: usa node --import tsx con
kill_timeout aumentado a 15s para evitar EADDRINUSE.
- React Query retry=2 con delay exponencial para resiliencia.
Refs: docs/CAMBIOS-2026-05-09.md
- conciliacion.service.ts: filtros y ordenamiento ahora usan
COALESCE(fecha_pago_p, fecha_emision). Los CFDIs tipo P
(complementos de pago) aparecen en el periodo del pago real,
no de la emision del CFDI.
- conciliacion.service.ts: agrega fechaPagoP al SELECT y a la
interfaz ConciliacionCfdi.
- conciliacion/page.tsx: tablas y export Excel usan
fechaPagoP || fechaEmision para mostrar la fecha.
- cfdi-invoice.tsx: para tipo P con fechaPagoP, muestra
'Pago: {fecha}' en el encabezado.
- conciliacion.ts: actualiza interfaz ConciliacionCfdi con
todos los campos que ya devuelve el backend.
Refs: docs/CAMBIOS-2026-05-09.md secciones 7 y 8