27 KiB
Sesión 2026-04-27 — Resumen del día
Sesión enfocada en el módulo de Impuestos y la gestión de planes: refactor del cálculo de ISR acumulado al estilo formato 14 del SAT, dos toggles nuevos para excluir activos fijos y NCs, extensión del filtro de activos para cubrir las cadenas P → I y E → {I, P → I}, límite de 5 RFCs durante el trial gratuito, fix de un bug crítico de scope SQL, y un plan "Custom" gratis sin fecha fin asignable solo por Admin Global.
Ocho releases shippeadas en OneDrive durante la sesión: V.1.0.6, V.1.0.7, V.1.0.8, V.1.0.9, V.1.0.11, V.1.0.12, V.1.0.14 (V.1.0.10 y V.1.0.13 fueron solo docs).
Índice
- V.1.0.6 — ISR base gravable acumulada y desglose del periodo
- V.1.0.7 — Filtros "Considerar activos" y "Considerar NCs" (Fase 1)
- V.1.0.8 — Defaults de los toggles a ON (cache-friendly)
- V.1.0.9 — Filtro de activos extendido a P y E relacionadas
- Spec en pipeline (no shipped) — Sort por nombre en drill-down
- V.1.0.11 — Límite de 5 RFCs durante trial gratuito
- V.1.0.12 — Fix bug de scope SQL en filtro de activos
- V.1.0.14 — Plan Custom (gratis, sin fecha fin, solo admin)
- Pendientes derivados
Documentos relacionados creados hoy:
docs/superpowers/specs/2026-04-27-isr-base-gravable-acumulada-design.mddocs/superpowers/plans/2026-04-27-isr-base-gravable-acumulada.mddocs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.mddocs/superpowers/plans/2026-04-27-filtros-activos-ncs-impuestos-fase1.mddocs/superpowers/specs/2026-04-27-drill-down-sort-by-name-design.md(no implementado)docs/superpowers/specs/2026-04-27-trial-rfc-limit-design.mddocs/superpowers/specs/2026-04-27-custom-plan-design.md
1. V.1.0.6 — ISR base gravable acumulada y desglose del periodo
Problema
La pestaña ISR de /impuestos calculaba la base gravable mes a mes con
Math.max(0, ing − ded). Esto perdía déficits acumulados: un mes con
pérdida no reducía el acumulado de meses siguientes. La lógica fiscal
correcta es acumular ingresos y deducciones desde enero, restar al final,
y solo aplicar max(0, …) al pasar a ISR causado.
Cambios — Tabla "Histórico ISR"
Antes: 4 columnas (Mes, Ingresos, Deducciones, Base Gravable). Base
Gravable era el max(0, ing_mes − ded_mes) mensual independiente.
Después: 6 columnas — Mes, Ingresos, Ingresos Acum., Deducciones,
Deducciones Acum., Base Gravable Acum. La columna BG mensual desaparece.
La BG Acum. se calcula como ingAcum − dedAcum sin clamp: si el
acumulado es negativo, se renderiza en rojo (text-destructive). Fila
"Total" eliminada (la última fila con datos ya es el YTD).
Cambios — Sección "Cálculo de ISR Acumulado" → "Cálculo de ISR del Periodo"
Rename del título y reescritura del card al estilo del formato 14 SAT:
Ingresos del periodo (Mar 2026) $X
(+) Ingresos acumulados anteriores (Ene-Feb) $A
(−) Deducciones del periodo (Mar 2026) $Y
(−) Deducciones acumuladas anteriores $B
─────────────────────────────────────────────
(=) Base gravable acumulada $X+A−Y−B ← rojo si negativa
ISR causado (acumulado) tarifa(max(0, BG))
(−) ISR retenido (acumulado) $R
─────────────────────────────────────────────
ISR a pagar max(0, causado − retenido)
"Del periodo" = único el mes final del filtro (no el rango entero).
"Anteriores" = enero hasta el mes previo al mes final, mismo año.
Etiquetas de mes derivadas dinámicamente: mesFinal=1 muestra "(sin meses
anteriores)", mesFinal=2 muestra "(Ene)", etc.
Backend — endpoint nuevo
GET /api/impuestos/isr/resumen-desglosado?fechaFin=...&conciliacion=...&contribuyenteId=...
Internamente llama 3 veces a getResumenIsr con rangos distintos:
delPeriodo: solo el mes final (1 mes)anteriores: Ene-1 a (mesFinal-1)-último-día (vacío si mesFinal=1)total: Ene-1 a último-día-del-mes-final
Promise.all para los 2 que son independientes (delPeriodo + total).
Cuando mesFinal === 1, evita query inútil retornando emptyResumenIsr()
para anteriores.
Archivos modificados (V.1.0.6)
packages/shared/src/types/impuestos.ts
apps/api/src/services/impuestos.service.ts
apps/api/src/controllers/impuestos.controller.ts
apps/api/src/routes/impuestos.routes.ts
apps/web/lib/api/impuestos.ts
apps/web/lib/hooks/use-impuestos.ts
apps/web/app/(dashboard)/impuestos/page.tsx
IsrMensual extendido con ingresosAcum, deduccionesAcum,
baseGravableAcum (running totals desde enero). BaseGravableRegimen
ganó isrCausado para alimentar la sección por régimen.
2. V.1.0.7 — Filtros "Considerar activos" y "Considerar NCs" (Fase 1)
Problema
La pestaña Impuestos no permitía excluir compras de activos fijos (que se deprecian, no se deducen mensualmente) ni notas de crédito (NCs tipoRel=01 que ajustan facturas previas). El contador necesita poder ver/ocultar estas categorías para análisis.
UI
Dos toggles nuevos junto a "Conciliación":
[Régimen ▾] [☐ Conciliación] [☐ Considerar activos] [☐ Considerar NCs]
Mismo styling que Conciliación, tooltips descriptivos via title.
En esta versión los defaults eran OFF (excluir por default). El default
se invirtió a ON en V.1.0.8 — ver §3.
Toggle ON = considerar/incluir. Toggle OFF = no considerar/excluir.
Backend
Helper neutral en módulo nuevo apps/api/src/services/_shared/cfdi-filters.ts:
buildExtraFilters(considerarActivos, considerarNCs)→ fragmento WHERE para queries conFROM cfdisdirecto.buildExtraFiltersAlias(alias, considerarActivos, considerarNCs)→ versión alias-aware para subqueries (FROM cfdis e).
Cuando ambos flags son true retorna string vacío (no afecta el WHERE).
Se extendieron 7 funciones de servicio con 2 nuevos parámetros opcionales,
default true para preservar el comportamiento de los callers que no los
pasan (dashboard, reportes, alertas):
| Función | Archivo |
|---|---|
calcularIngresosPorRegimen |
dashboard.service.ts |
calcularEgresosPorRegimen |
dashboard.service.ts |
getResumenIva |
impuestos.service.ts |
getIvaMensual |
impuestos.service.ts |
getResumenIsr |
impuestos.service.ts |
getIsrMensual |
impuestos.service.ts |
getResumenIsrDesglosado |
impuestos.service.ts |
Templates de subqueries de la rama I PPD/07 (SUM_E_REFERENCING_TRAS,
SUM_E_REFERENCING_RET, HAS_E_REFERENCING_MISMO_MES) y sus helpers
intermedios (bucketCausadoAny, bucketAcreditableAny, signed* exprs,
readResumenIvaFromCache) propagaron los flags a través de 3 niveles.
Cache gate de IVA extendido: metricas_mensuales solo se consulta cuando
!conciliacion && considerarActivos && considerarNCs. Cualquier toggle
distinto del default backend → live query.
Frontend
- API client: 5 funciones HTTP serializan los flags como query params,
incluyendo cuando son
false(if (flag !== undefined) params.set(..., String(flag))). - Hooks: 5 hooks incluyen los flags en
queryKeypara refetch al togglear. - UI: state + 2 toggle buttons + propagación a las 5 llamadas.
Decisión de diseño — Default backend true, default UI inicial false
El backend default true (= include todo) preserva dashboard, reportes,
etc. La UI inicialmente arrancó con default false (= excluir por
default) por lógica fiscal. La asimetría se resolvió en V.1.0.8.
Pruebas
pnpm typecheckshared + api: PASS.- Web typecheck para los archivos del plan: clean (otros errores web son pre-existentes, fuera de scope).
- Smoke deferido a verificación manual del owner.
Fase 2 (futura)
Extender metricas_mensuales con columnas base + 2 deltas (*_activos,
*_ncs_01) por métrica IVA. Hace los toggles instantáneos vía
suma/resta sin live query.
3. V.1.0.8 — Defaults de los toggles a ON (cache-friendly)
Motivación
Tras shippear V.1.0.7, el final code review levantó una observación
importante: con UI default OFF (ambos toggles excluyendo), el cache
metricas_mensuales queda siempre bypass-eado en /impuestos.
Cada carga inicial era live query (~1-3s). El cache solo servía cuando
el contador activaba manualmente ambos toggles.
Decisión
Invertir defaults UI de false a true. Trade-off:
- Antes (V.1.0.7): default OFF → carga inicial siempre lenta. Default fiscalmente "más correcto" (excluir por automático).
- Después (V.1.0.8): default ON → carga inicial rápida (cache hit, comportamiento idéntico al de versiones previas). El contador activa el filtro cuando lo necesita.
La consciencia del filtro queda como acción del contador, no como default silencioso. Fase 2 elimina el dilema: con cache base+deltas, ambos defaults serán igual de rápidos.
Cambios
// Antes:
const [considerarActivos, setConsiderarActivos] = useState(false);
const [considerarNCs, setConsiderarNCs] = useState(false);
// Después:
const [considerarActivos, setConsiderarActivos] = useState(true);
const [considerarNCs, setConsiderarNCs] = useState(true);
Plus actualización del spec doc para reflejar la decisión.
Archivos modificados (V.1.0.8)
apps/web/app/(dashboard)/impuestos/page.tsx
docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md
4. V.1.0.9 — Filtro de activos extendido a P y E relacionadas
Problema
El filtro de activos en V.1.0.7 solo excluía facturas tipo I con uso I01-I08. Pero en realidad un activo fijo se materializa en varias facturas relacionadas:
- La I de la compra (uso I01-I08).
- La P (complemento de pago) que paga esa compra.
- Eventualmente una E (NC) que cancela la compra o el pago.
Si el contador desactiva "Considerar activos", esperaría ver excluidas todas las facturas asociadas a la operación de activo fijo, no solo la I original. De lo contrario los pagos y NCs quedan visibles sin la factura que los origina, generando inconsistencia.
Solución
Extender el predicado del filtro de activos para cubrir 3 capas:
| Capa | Predicado SQL |
|---|---|
| 1. I directa | tipo_comprobante = 'I' AND uso_cfdi IN (I01-I08) |
| 2. P → I-activo | tipo='P' AND EXISTS(SELECT 1 FROM cfdis i_act WHERE i_act.uuid=uuid_relacionado AND i_act.tipo='I' AND i_act.uso_cfdi IN (I01-I08)) |
| 3. E → {I-activo, P-de-activo} | tipo='E' AND EXISTS(SELECT 1 FROM cfdis r_act WHERE r_act.uuid IN cfdis_relacionados pipe-split AND (r_act es I-activo OR r_act es P-de-activo)) |
La capa 3 cubre los dos casos del owner:
- E tipoRel=01 que cancela una P que pagó un activo (caso 1 del prompt).
- E tipoRel=03 que devuelve directamente una I-activo (caso 3 del prompt).
- Cualquier otro tipoRel — el predicado es genérico, no filtra por
cfdi_tipo_relacionen la rama de activos.
Independencia con el filtro de NCs
Los dos filtros operan en AND:
- "Considerar NCs" OFF: excluye todas las E tipoRel=01, sin importar a qué se relacionen.
- "Considerar activos" OFF: excluye E (cualquier tipoRel) que se relacione con activos.
Una E tipoRel=01 sobre I regular: solo el filtro de NCs la afecta. Una E tipoRel=03 sobre I-activo: solo el filtro de activos la afecta. Una E tipoRel=01 sobre I-activo: ambos filtros la excluirían.
Comportamiento por tipo de CFDI
| CFDI | Excluido si activos OFF? |
|---|---|
| I uso I01-I08 | ✅ predicado 1 |
| I uso G03 (gasto regular) | ❌ |
| P pagando I-activo | ✅ predicado 2 |
| P pagando I regular | ❌ |
| E tipoRel=01 → I-activo | ✅ predicado 3 |
| E tipoRel=03 → I-activo | ✅ predicado 3 |
| E tipoRel=01 → P-de-activo | ✅ predicado 3 |
| E tipoRel=07 → I PPD/07 (anticipo) | ❌ no es activo |
| E tipoRel=01 → I regular | ❌ (lo cubre el filtro NCs si está OFF) |
Implementación
Cambio único en apps/api/src/services/_shared/cfdi-filters.ts (~70
líneas netas, el archivo creció de ~50 a ~110 líneas). Helpers internos
activosExclusionNoAlias() y activosExclusionAlias(alias) encapsulan
los 3 predicados. buildExtraFilters y buildExtraFiltersAlias los
invocan cuando !considerarActivos.
Cero cambios downstream — todos los callsites del helper (16+ en service/dashboard) heredan automáticamente el comportamiento extendido.
Performance
- Default UI ON (V.1.0.8): el helper retorna empty string. Cero impacto, cache hit normal.
- Filtro activos OFF: cada query con
FROM cfdisejecuta los 3 predicados con EXISTS anidados. Sin índice enuuid_relacionadonicfdis_relacionados, las queries grandes pueden ser ~10-20% más lentas. Aceptable para Fase 1. - Si el perf hit se vuelve notorio, evaluar índice B-tree en
uuid_relacionadoy GIN sobre array paracfdis_relacionadosen Fase 2 (parte del cache extension).
Archivos modificados (V.1.0.9)
apps/api/src/services/_shared/cfdi-filters.ts
5. Spec en pipeline (no shipped) — Sort por nombre en drill-down
Se diseñó y especificó el cambio para agregar sort por nombre emisor /
receptor en la página /drill-down genérica (los KPIs del dashboard
abren ahí). Spec en
docs/superpowers/specs/2026-04-27-drill-down-sort-by-name-design.md,
shippeada en V.1.0.5.
Estado: spec aprobada, implementación pendiente. Cambio trivial (~6 líneas en un archivo). Las páginas de alertas con tablas similares quedaron fuera de scope para evitar plan grande — se evaluarán en otra sesión.
6. V.1.0.11 — Límite de 5 RFCs durante trial gratuito
Problema
Despachos en periodo de prueba (30 días) podían agregar RFCs sin restricción. El owner pidió un límite duro de 5 RFCs para forzar al contador a contratar un plan si necesita gestionar más.
Reglas
| Estado | Límite RFCs |
|---|---|
Trial activo (tenant.trialEndsAt > now) |
5 contribuyentes activos (5 OK, 6 bloqueado) |
| Trial expirado | Aplica el límite del plan vigente; este spec no agrega nada nuevo |
| Plan pagado (sin trial activo) | Sin nuevo límite |
Backend (apps/api/src/controllers/contribuyente.controller.ts)
Constante local TRIAL_MAX_CONTRIBUYENTES = 5. En el handler create,
antes del createContribuyente:
const tenant = await prisma.tenant.findUnique({
where: { id: req.user!.tenantId },
select: { trialEndsAt: true },
});
const isTrialActive = tenant?.trialEndsAt ? tenant.trialEndsAt > new Date() : false;
if (isTrialActive) {
const activeCount = await countActiveContribuyentes(req.tenantPool!);
if (activeCount >= TRIAL_MAX_CONTRIBUYENTES) {
return next(new AppError(
403,
`Durante el periodo de prueba puedes gestionar hasta ${TRIAL_MAX_CONTRIBUYENTES} contribuyentes. Contrata un plan para agregar más.`,
));
}
}
Frontend (apps/web/app/(dashboard)/contribuyentes/page.tsx)
-
useQuery(['my-plan-info'], ...)para fetch/despachos/me/plan(endpoint existente). -
Cómputo
trialAtLimit = isTrialActive && activeCount >= 5. -
Los 2 botones "Agregar RFC" / "Agregar primer RFC" reciben
disabled={trialAtLimit}contitlemostrando el tooltip exacto literal del owner:"Límite de contribuyentes para la prueba gratuita, para continuar agregando contribuyentes, selecciona un plan."
No-cambios
- No nueva tabla, no migration.
- Mi Empresa hard limit a 1 RFC sigue siendo billing-only (out of scope).
tenant.cfdiLimit,tenant.usersLimitno se tocan.
Archivos modificados (V.1.0.11)
apps/api/src/controllers/contribuyente.controller.ts
apps/web/app/(dashboard)/contribuyentes/page.tsx
docs/superpowers/specs/2026-04-27-trial-rfc-limit-design.md
7. V.1.0.12 — Fix bug de scope SQL en filtro de activos
Problema reportado por owner
Tras shippear V.1.0.9 (filtro de activos extendido a P y E relacionadas),
el owner reportó que la factura 8ec2eaf3-7879-11f0-81a8-8daae9822b10
(tipo P, monto $295,100) seguía apareciendo en cálculos cuando
desactivaba el toggle "Considerar activos". La P pagaba la I
5C874749-748F-11F0-96B1-2B9310891836, que tenía uso_cfdi = I03
(Equipo de transporte) — un activo fijo per la regla.
Causa raíz — scope ambiguity SQL
En activosExclusionNoAlias() (helper en _shared/cfdi-filters.ts),
el subquery del predicado P referenciaba LOWER(uuid_relacionado) sin
qualifying. Como el subquery usa FROM cfdis i_act y i_act también
tiene la columna uuid_relacionado, PostgreSQL resolvía la referencia
no-qualificada al scope interno (i_act.uuid_relacionado) en vez
del outer (cfdis.uuid_relacionado).
Resultado: el predicado evaluaba "¿existe un i_act donde
i_act.uuid = i_act.uuid_relacionado AND uso I01-I08?" — eso es
prácticamente siempre false (un CFDI no se referencia a sí mismo).
El AND NOT (FALSE) = AND TRUE, así que la P nunca se excluía.
Mismo bug en el predicado E (cfdis_relacionados no qualificado dentro
del subquery con r_act que también tiene esa columna).
La versión Alias (activosExclusionAlias) NO tenía el bug porque ahí
escribí ${alias}.uuid_relacionado qualificado explícitamente.
Fix
Qualifying las 2 referencias outer dentro de los subqueries con cfdis.:
-- Antes (bug):
WHERE LOWER(i_act.uuid) = LOWER(uuid_relacionado)
-- Después (fix):
WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado)
Y para el predicado E:
-- Antes (bug):
WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis_relacionados), '|'))
-- Después (fix):
WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|'))
Comentario JSDoc agregado a la función explicando por qué necesita qualifying explícito (evitar futuras regresiones).
Validación
Test directo con SQL antes/después del fix:
BUGGY version (sin qualifying outer):
bug_excludes = false ← P nunca se excluía
FIXED version (cfdis.uuid_relacionado):
fixed_excludes = true ← P correctamente excluida
Referenced I: { uuid: '5c874749...', uso_cfdi: 'I03', tipo_comprobante: 'I' }
Lecciones
- Qualifying explícito es defensivo siempre que un subquery comparta nombre de tabla con el outer. Postgres prioriza scope interno sin warning, así que el bug es silencioso.
- El test del owner en producción es indispensable: el typecheck y los tests de tipo no detectan errores semánticos de SQL. El fix solo apareció porque el owner reportó un caso específico que esperaba excluir.
Archivos modificados (V.1.0.12)
apps/api/src/services/_shared/cfdi-filters.ts
8. V.1.0.14 — Plan Custom (gratis, sin fecha fin, solo admin)
Problema
El owner pidió un plan especial para casos de cortesía / beta tester / caso especial: acceso al sistema sin cobro, sin fecha de finalización, asignable solo por el Admin Global y oculto del catálogo user-facing.
Decisión clave — Reusar enum custom
El Plan enum de Prisma ya tenía custom (legacy: "precio variable por
tenant") con 0 tenants en dev. La lógica antigua en
subscription.service.ts ya rechazaba custom del flujo self-serve —
patrón que coincide con la nueva semántica. Reusar el enum evita migration
y mantiene compatibilidad con código existente.
Comportamiento
- Limits: idéntico a Mi Empresa (1 RFC, 50 timbres/mes, MANAGED, sin API ni Lolita).
- Costo: $0. NO se incluye en
DESPACHO_PLAN_PRICES→ no genera Subscription, no usa MercadoPago. - Vigencia: indefinida.
tenant.trialEndsAt = null. SincurrentPeriodEnd. Ningún cron lo expira (expireTrials,applyPendingChangesno le afectan). - Visibilidad: oculto del catálogo en
/configuracion/planes-despacho(página user). Solo aparece como opción en/clientes(admin global).
Catálogo (packages/shared/src/constants/despacho-plans.ts)
Nueva entrada custom en DESPACHO_PLANS con limits idénticos a Mi Empresa
y mismo array de features. NO se agrega a DESPACHO_PLAN_PRICES —
helpers permiteOverage('custom') y isDespachoPaidPlan('custom') ya
retornan false por exclusión.
Admin /clientes — extensión del dropdown
La página admin tenía un dropdown limitado a starter | business | enterprise. Cambios:
PlanTypeextendido astarter | business | business_ia | enterprise | custom.- Tipo
CreateTenantData.planyUpdateTenantData.planenapps/web/lib/api/tenants.tsextendidos al mismo union. - Dropdown ahora lista los 4 legacy con etiqueta "(legacy)" + Custom con descripción "Sin cobro, sin fecha fin (despacho)".
- Cuando se selecciona Custom, el input "Monto Mensual" se oculta y aparece nota: "Plan Custom no genera cobro ni suscripción. Vigencia indefinida."
- Lista de tenants ahora usa el
PLAN_LABELSglobal (cubre todos los planes incluyendo despacho) en vez delplanLabelslocal que solo cubría legacy.planColorsextendido con entradas para todos los planes despacho + custom.
Out of scope: asignar planes despacho pagables (mi_empresa,
mi_empresa_plus, business_control, business_cloud) desde
/clientes. Esos van por self-serve del owner para evitar el escenario
de un tenant en plan paid sin Subscription. Si se necesita en el futuro,
requiere manejar la creación/cancelación de preapproval MP en el
endpoint admin.
User /configuracion/planes-despacho — banner Custom
Despachoplantype extendido concustom.- Si
currentPlan === 'custom': banner rosa al top con "Plan Custom — sin cobro, vigencia indefinida" + descripción "Tu cuenta está bajo un plan especial asignado por tu administrador. Contacta a soporte si necesitas cambiar de plan." Las cards de planes pagables se ocultan (no hay opción de auto-cambio). - Otros planes: comportamiento idéntico al de antes.
Backend — sin cambios
PUT /api/tenants/:id ya acepta cualquier valor del enum Prisma (no
hay Zod gate restrictivo en el endpoint admin). Solo el endpoint
self-serve addMyTenant tiene Zod limitado a legacy — no se toca, sigue
siendo correcto que self-serve no permita Custom.
Limitaciones aceptadas
- Transición paid → custom: si el admin cambia un tenant que tenía
suscripción MP activa a Custom, el preapproval MP sigue cobrando
hasta que se cancele manualmente. Mitigación: el admin debe cancelar
la suscripción primero desde
/configuracion/suscripciondel tenant impersonado. - Hard limit 1 RFC en Custom: igual que Mi Empresa, el límite de
1 RFC para Custom es solo billing-only hoy (no enforced en
contribuyente.controller.ts:create). Si se quiere enforce duro, replicar el patrón del trial limit V.1.0.11.
Archivos modificados (V.1.0.14)
packages/shared/src/constants/despacho-plans.ts
apps/web/lib/api/tenants.ts
apps/web/app/(dashboard)/clientes/page.tsx
apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx
docs/superpowers/specs/2026-04-27-custom-plan-design.md
9. Pendientes derivados
Verificación manual del owner (smoke)
Aún no se ha ejecutado smoke en navegador para los cambios de la sesión. Lista mínima:
- Dashboard regression: KPIs (
Ingresos,Gastos,Utilidad) deben ser idénticos a los de V.1.0.5 (= sin filtros aplicados). - Histórico ISR (V.1.0.6): 6 columnas, BG_acum negativa en rojo, running totals correctos, sin fila Total.
- Cálculo de ISR del Periodo (V.1.0.6): 8 renglones, etiquetas
dinámicas según
mesFinal, BG en rojo si negativa. - Toggle "Considerar activos" OFF (V.1.0.7+V.1.0.9):
- Excluye I uso I01-I08.
- Excluye P pagando esos I.
- Excluye E (cualquier tipoRel) referenciando esos I o P.
- Toggle "Considerar NCs" OFF (V.1.0.7): excluye E tipoRel=01.
- Combinaciones de los 3 toggles (Conciliación + Activos + NCs): 8 combinaciones, números deben ser consistentes.
- Activos Fijos tab: la tabla sigue mostrando todos los I con uso I01-I08, no afectada por los toggles de impuestos.
- Filtro de régimen: sigue distribuyendo correctamente cuando se selecciona un régimen y se togglea cualquier filtro.
Performance follow-ups
- Si toggle activos OFF en
/impuestossiente lento (>3-4s), considerar:- Índice B-tree en
uuid_relacionado(cheap). - Migración para indexar
cfdis_relacionados(caro: requiere GIN sobre array, o normalizar a tabla M:N).
- Índice B-tree en
- Fase 2 del feature de filtros: extender
metricas_mensualescon columnas base + deltas. Toggles instantáneos sin importar el estado.
Push manual
Las versiones están commiteadas en OneDrive pero no pusheadas a
origin/main. Owner las push cuando quiera.
72ffdca V.1.0.14 ← Custom plan (gratis, sin fecha fin, solo admin)
6686e70 V.1.0.13 ← session doc updated (V.1.0.11 + V.1.0.12)
59d71ae V.1.0.12 ← fix scope SQL helper (bug crítico V.1.0.9)
f4e0d6f V.1.0.11 ← trial RFC limit (5 max)
b8c9df1 V.1.0.10 ← session summary doc original
297ffdb V.1.0.9 ← filtro activos extendido a P y E (con bug que V.1.0.12 arregla)
4b7566e V.1.0.8 ← defaults flipped a ON
2970ccf V.1.0.7 ← filtros activos/NCs Fase 1
cc34c39 V.1.0.6 ← ISR base gravable acumulada
Fuera de scope (a evaluar después)
- Implementar drill-down sort por nombre (spec ya aprobada).
- Replicar los toggles de impuestos a
/dashboard(si se pide): ya está habilitado por las signatures decalcular*PorRegimen— solo falta UI + propagación. - Persistencia de los toggles (hoy son
useState, se pierden al recargar): considerarlocalStorageotenant-view-store. - Hard limit Mi Empresa (1 RFC): hoy es solo billing-only. Replicar el patrón del trial limit V.1.0.11 (Mi Empresa también haría check duro al crear). Aplica también para Custom (1 RFC). Considerar al implementar Fase 2 del feature de filtros.
- Bug class similar al V.1.0.12 en otras subqueries: revisar otros helpers SQL del repo que tengan subqueries con tablas del mismo nombre (cfdis, conciliaciones, etc.) y qualifying débil. Sería un audit pasivo, no urgente.
- Asignar planes despacho pagados desde
/clientesadmin: hoy Custom es el único plan despacho asignable desde admin. Si se quiere agregar tambiénmi_empresa,business_control, etc., requiere manejar creación/cancelación de preapproval MP en el endpoint admin. Out of scope para V.1.0.14, pendiente futuro.