392 lines
18 KiB
Markdown
392 lines
18 KiB
Markdown
# Sesión 2026-04-22 — Cierre de pendientes y add-ons por contribuyente
|
||
|
||
Sesión continuación del trabajo de Tanda A / B documentado en
|
||
`docs/plans/2026-04-21-session-2-mp-setup-and-bugfixes.md`. Esta sesión cubrió:
|
||
|
||
1. **Tanda B.2-B.5** — Extensión del cache read-through, alineación
|
||
dashboard ≡ impuestos, lock SAT por contribuyente, watchdog CLI
|
||
(ver doc 2026-04-21 § "Tanda B.2" en adelante).
|
||
2. **Pendientes derivados de hoy** — A, B, C, D + mejora de logging SAT.
|
||
3. **Feature: Add-ons por contribuyente** — infraestructura para cobro
|
||
recurrente mensual por RFC, con preapproval MP independiente de la
|
||
licencia anual del despacho. Primer add-on: Lolita IA ($250/mes).
|
||
|
||
---
|
||
|
||
## Pendientes derivados cerrados
|
||
|
||
### A. Watchdog CLI de SAT stale jobs
|
||
|
||
**Problema:** jobs `pending` con `nextRetryAt` vencido o `running`
|
||
huérfanos (proceso crasheó a mitad del sync) quedaban invisibles y
|
||
bloqueaban futuros syncs por el lock.
|
||
|
||
**Solución:** `apps/api/scripts/sweep-stale-sat-jobs.ts`. Dos categorías
|
||
con thresholds sobreescribibles por env:
|
||
|
||
- `pending` con `nextRetryAt` < now − `STALE_PENDING_HOURS` (default 12)
|
||
- `running` con `startedAt` < now − `STALE_RUNNING_HOURS` (default 4)
|
||
|
||
Dry-run por default; `--apply` ejecuta. Verificado con un dry-run
|
||
limpio (0 stale) mientras Manuel corría.
|
||
|
||
Pendiente: wiring como cron cada 2h en `sat-sync.job.ts`.
|
||
|
||
### B. Crons en dev con flag
|
||
|
||
**Problema:** todos los crons estaban gateados con
|
||
`env.NODE_ENV === 'production'`. En dev ningún cron arrancaba — por eso
|
||
el job de Alexa (status pending con `nextRetryAt = +5min`) quedó
|
||
colgado: el cron horario `retryTimedOutJobs` nunca corrió.
|
||
|
||
**Solución:** en `apps/api/src/index.ts`, partir el gate en dos:
|
||
|
||
```ts
|
||
const cronsEnabled = env.NODE_ENV === 'production' || process.env.ENABLE_CRONS_IN_DEV === '1';
|
||
const sendRealEmails = env.NODE_ENV === 'production';
|
||
if (cronsEnabled) {
|
||
startSatSyncJob();
|
||
startMetricasInvalidationsJob();
|
||
if (sendRealEmails) startWeeklyUpdateJob();
|
||
}
|
||
```
|
||
|
||
`weekly-update` sigue prod-only para no mandar emails a owners reales
|
||
desde dev. Confirmado al restart: `[Cron] Jobs omitidos en dev (usar
|
||
ENABLE_CRONS_IN_DEV=1 para activar)`.
|
||
|
||
### C. Cache en `getIvaMensual` + refactor a fórmula canónica
|
||
|
||
**Problema doble:** la fórmula de `getIvaMensual` divergía de la del
|
||
dashboard/impuestos (no filtraba PUE, no manejaba NC, retenido gross).
|
||
Además no leía de `metricas_mensuales` para años cerrados.
|
||
|
||
**Solución:**
|
||
- Reescribir con los 6 buckets canónicos (ver `getResumenIva` en 2026-04-21 § Tanda B.3).
|
||
- Cache read-through desde `metricas_mensuales` cuando año < actual,
|
||
sin conciliación, con contribuyente seleccionado. Helper nuevo
|
||
`readIvaMensualFromCache` agrega T/A/R por mes.
|
||
- On-the-fly: 2 queries (una por lado causado/acreditable) grouped por mes.
|
||
|
||
`getIsrMensual` y `getResumenIsr` siguen on-the-fly — requieren tarifas
|
||
progresivas y no están en `metricas_mensuales`. Fuera de alcance.
|
||
|
||
### D. Cache en `calcularFlujoPorMes` — **fuera de alcance**
|
||
|
||
**Problema:** `calcularFlujoPorMes` usa `total_mxn`/`monto_pago_mxn`
|
||
(IVA incluido) pero los campos stored `flujo_entradas/salidas/neto` en
|
||
`metricas_mensuales` se poblan desde ingresos/egresos NETOS (sin IVA).
|
||
|
||
**Decisión:** no cachear hasta tener columnas `flujo_bruto_*`
|
||
separadas o reescribir el concepto. El cómputo on-the-fly ya es
|
||
eficiente (6 queries agregadas por año). Costo/beneficio no lo justifica
|
||
ahora. Documentado como pendiente.
|
||
|
||
### Extra. Logging informativo de rejections SAT
|
||
|
||
**Problema:** durante el sync de Manuel, 9 bloques consecutivos de
|
||
emitidos cayeron en `rejected`. El mensaje `verifyResult.message` era
|
||
el genérico `"Solicitud Aceptada"` del wrapper HTTP. La razón real
|
||
(códigos 5001, 5002, 5003, 5005, etc.) quedaba enterrada.
|
||
|
||
**Solución:** en `sat-client.service.ts:verifySatRequest`, cuando
|
||
`status` es `rejected`/`failed`, construir el message con
|
||
`SAT code=N request=EntryId(value) msg="..."` que incluye:
|
||
- `statusCode` (código numérico SAT)
|
||
- `entryId` (etiqueta del `StatusRequest`)
|
||
- `value` (valor numérico del `StatusRequest`)
|
||
- `msg` (mensaje del wrapper, ya existente)
|
||
|
||
---
|
||
|
||
## Feature: Add-ons por contribuyente
|
||
|
||
### Modelo de negocio
|
||
- **Lolita IA** — $250/mes por cada contribuyente que lo active.
|
||
Cualquier plan puede contratarlo.
|
||
- **Contribuyente adicional Business Cloud** — $45/mes por RFC extra
|
||
(el plan incluye 3; del 4º en adelante). Automático por count,
|
||
no opt-in. Modelado como add-on para que el preapproval MP lo cubra.
|
||
|
||
Ambos add-ons son **mensuales**; la licencia del despacho es **anual**.
|
||
→ Requieren preapproval MP **independiente** por add-on — cancelación
|
||
granular sin tocar la suscripción base.
|
||
|
||
### Ruta descartada
|
||
Primer intento fue una tabla tenant `contribuyente_addons
|
||
(contribuyente_id, addon_key, enabled, config)` con feature-toggles
|
||
(facturación/conciliación/documentos/calendario/reportes). Modelo
|
||
incorrecto: los add-ons reales son servicios de cobro recurrente, no
|
||
switches de features. Revertido completo antes de iterar.
|
||
|
||
### Ruta correcta — extender `SubscriptionAddon` existente
|
||
|
||
Ya existía infraestructura a nivel tenant (`plan_addon_catalogo` +
|
||
`subscription_addons` + `addon.service.ts` con preapproval MP por
|
||
add-on). Extensión:
|
||
|
||
**Schema (Prisma):**
|
||
```prisma
|
||
model SubscriptionAddon {
|
||
contribuyenteId String? @map("contribuyente_id") // NULL = tenant-level
|
||
// ... resto igual
|
||
@@index([subscriptionId, contribuyenteId])
|
||
// @@unique([subscriptionId, planAddonCatalogoId]) ← REMOVIDO
|
||
}
|
||
```
|
||
|
||
Sin `@@unique` compuesto porque Postgres trata `NULL != NULL` y no hay
|
||
forma trivial de enforcar "un solo addon activo por (sub, codename,
|
||
contribuyente?)" con Prisma. Validación queda a nivel app en
|
||
`subscribeAddon.findFirst`.
|
||
|
||
**Migration SQL:**
|
||
- Agrega `contribuyente_id TEXT NULL`
|
||
- Elimina UNIQUE(subscription_id, plan_addon_catalogo_id)
|
||
- Agrega índice (subscription_id, contribuyente_id)
|
||
|
||
**Catálogo (seed.ts):** 2 nuevos add-ons:
|
||
- `lolita_ia_contribuyente` — $250/mes, `verticalProfile=CONTABLE`
|
||
- `contribuyente_extra_business_cloud` — $45/mes, `verticalProfile=CONTABLE`
|
||
|
||
**Service (`addon.service.ts`):**
|
||
- `subscribeAddon` acepta `contribuyenteId: string | null`. El reason
|
||
del preapproval incluye prefix del RFC cuando aplica
|
||
(`"Horux Despachos - Lolita IA (RFC abcd1234) x1 - Zorro Despacho"`).
|
||
- `listActiveAddons(tenantId, contribuyenteId?)` filtra por RFC cuando
|
||
se pasa el param. Sin param → retorna todos los add-ons del tenant
|
||
(incluye tenant-level y per-contribuyente).
|
||
- La validación "ya tienes activo" ahora considera `contribuyenteId`:
|
||
mismo addon en 2 contribuyentes distintos es OK; 2 veces para el
|
||
mismo contribuyente rechaza.
|
||
|
||
**Controller (`subscription.controller.ts`):**
|
||
- `GET /subscriptions/me/addons?contribuyenteId=...` — filtra por RFC.
|
||
- `POST /subscriptions/me/addons` acepta `{ addonCodename, quantity,
|
||
contribuyenteId }` en body.
|
||
|
||
**Frontend:**
|
||
- `apps/web/lib/api/addons.ts` + `use-addons.ts` hooks.
|
||
- `apps/web/app/(dashboard)/contribuyentes/addons-dialog.tsx`:
|
||
catálogo `ADDONS_POR_CONTRIBUYENTE` (hoy solo Lolita IA). Muestra
|
||
precio, descripción, estado (activo/pending/sin contratar), fecha
|
||
del próximo cobro. Botón "Contratar" abre MP init_point en nueva
|
||
pestaña; "Cancelar" pide confirmación y revoca el preapproval.
|
||
- `contribuyentes/page.tsx`: botón `Sparkles` por contribuyente abre
|
||
el dialog.
|
||
|
||
### Cableado automático del overage Business Cloud
|
||
|
||
El add-on `contribuyente_extra_business_cloud` ($45/mes) ahora se ajusta
|
||
automáticamente al crear o desactivar un contribuyente.
|
||
|
||
**Modelo:** un único `SubscriptionAddon` a nivel tenant
|
||
(`contribuyenteId = null`, `codename = contribuyente_extra_business_cloud`)
|
||
con `quantity = max(0, activeCount − 3)`. El monto del preapproval MP
|
||
refleja `precio × quantity`. Cuando `quantity` cambia, se actualiza vía
|
||
`updatePreapprovalAmount` (sin re-autorización del usuario).
|
||
|
||
**Función:** `adjustBusinessCloudOverage(tenantId, activeContribuyenteCount)`
|
||
en `addon.service.ts`. Idempotente. Maneja los 5 casos:
|
||
|
||
- Plan ≠ `business_cloud` → `'skipped'`
|
||
- `overage = 0` sin addon → `'none'`
|
||
- `overage = 0` con addon → `'cancelled'` (revoca preapproval)
|
||
- `overage > 0` sin addon → `'created'` (crea addon + preapproval, retorna `paymentUrl`)
|
||
- `overage > 0` con addon, quantity ya coincide → `'none'` (idempotente)
|
||
- `overage > 0` con addon, quantity distinto → `'updated'` (updatePreapprovalAmount)
|
||
|
||
**Integración:**
|
||
- `contribuyente.controller.ts:create` y `:deactivate` llaman
|
||
`countActiveContribuyentes(pool)` + `adjustBusinessCloudOverage(tenantId, count)`
|
||
tras la operación. Fail-soft: si el ajuste falla, el contribuyente queda
|
||
creado/desactivado y el error se loguea (no bloquea la respuesta).
|
||
- Frontend (`contribuyentes/page.tsx`): si `result.overage.action === 'created'`
|
||
+ `paymentUrl`, muestra alerta y abre MP en nueva pestaña. Para `'updated'`
|
||
o `'cancelled'` muestra toast informativo.
|
||
|
||
**Transparencia de cobro:**
|
||
- Plan `business_cloud` = $15K/año (licencia, anual).
|
||
- Addon overage = $45/mes × quantity (mensual).
|
||
- MercadoPago cobra ambos independientemente. Cancelar la licencia
|
||
cancela su preapproval; cancelar RFCs baja el quantity del addon
|
||
automáticamente.
|
||
|
||
### Casos de uso validados
|
||
|
||
| Escenario | Estado |
|
||
|---|---|
|
||
| Tenant nuevo, crea 3 RFCs | Sin addon (no excede) |
|
||
| Crea 4º RFC → overage=1 | Addon `created`, paymentUrl devuelto, $45/mes pending |
|
||
| Crea 5º RFC → overage=2 | Addon `updated`, `updatePreapprovalAmount($90)` |
|
||
| Desactiva 1 (quedan 4) → overage=1 | Addon `updated`, `updatePreapprovalAmount($45)` |
|
||
| Desactiva otro (quedan 3) → overage=0 | Addon `cancelled`, preapproval MP revocado |
|
||
| Tenant en `business_control` crea 10º RFC | `skipped` (plan no aplica) |
|
||
| Tenant sin suscripción activa | `skipped` (catch-all) |
|
||
|
||
---
|
||
|
||
## Puesta en marcha de datos para testing
|
||
|
||
### Backfill de suscripciones de despacho
|
||
|
||
Los tenants Zorro (`DESPACHO_MO7JE8BZ_VDOPR`) y Patito (`DESPACHO_MO3NI6U8_B9VGG`)
|
||
fueron provisionados directamente como admin (sin pasar por el flujo
|
||
self-serve de MP), por lo que no tenían `Subscription` en BD central. Esto
|
||
bloqueaba el testing de add-ons (gate en `subscribeAddon`).
|
||
|
||
Se insertaron manualmente suscripciones `authorized` con `mpPreapprovalId=null`
|
||
(licencia por arreglo directo, cobro de add-ons va por separado):
|
||
|
||
| Tenant | Plan | Amount | Frequency | Period |
|
||
|---|---|---|---|---|
|
||
| Zorro | `business_cloud` | $15,000 | annual | 2026-04-23 → 2027-04-23 |
|
||
| Patito | `business_control` | $21,000 | annual | 2026-04-23 → 2027-04-23 |
|
||
|
||
`Tenant.plan` también se actualizó al valor correcto (antes Zorro estaba
|
||
en `enterprise` y Patito en `business`).
|
||
|
||
### Configuración MercadoPago sandbox
|
||
|
||
Agregado a `.env`:
|
||
```
|
||
MP_ACCESS_TOKEN=TEST-...
|
||
```
|
||
|
||
**Gotcha descubierto:** MP rechaza `http://localhost:3000` como `back_url`
|
||
del preapproval (requiere HTTPS público). Durante el testing se cambió
|
||
`FRONTEND_URL` a `https://horuxfin.com` temporalmente y se revirtió al
|
||
terminar. Solución durable pendiente (doc más abajo).
|
||
|
||
### Add-ons Lolita IA activos
|
||
|
||
| Contribuyente | Despacho | addonId | preapprovalId | status |
|
||
|---|---|---|---|---|
|
||
| Alexa G. Torres Romero (TORA0007099R6) | Zorro | `0cfb5c0b-…` | `b0dd70c3…` | `authorized` |
|
||
| Carlos H. Torres Romero (TORC9611214CA) | Patito | `17ed5185-…` | `48e20f17…` | `authorized` |
|
||
|
||
Preapprovals reales en MP sandbox. Status movido manualmente a `authorized`
|
||
via `handleAddonPayment(addonId, 'manual-sim', 'authorized')` porque no hay
|
||
webhook configurado. En prod esto lo hace automáticamente
|
||
`POST /api/webhooks/mercadopago`.
|
||
|
||
Period mensual: 2026-04-23 → 2026-05-23. El próximo ciclo se renovaría con
|
||
MP webhook real (pendiente Cloudflare Tunnel).
|
||
|
||
---
|
||
|
||
## Archivos tocados esta sesión
|
||
|
||
### Backend
|
||
- `apps/api/src/index.ts` — gate de crons con `ENABLE_CRONS_IN_DEV`
|
||
- `apps/api/src/services/sat/sat-client.service.ts` — rejection logging informativo
|
||
- `apps/api/src/services/impuestos.service.ts` — `getIvaMensual` refactor + cache (helper `readIvaMensualFromCache`); constantes SQL elevadas a file-level en Tanda B.3 (§ sesión 2026-04-21)
|
||
- `apps/api/src/services/dashboard.service.ts` — (ver Tanda B.3 en sesión 2026-04-21)
|
||
- `apps/api/src/services/sat/sat.service.ts` — (ver Tanda B.4 en sesión 2026-04-21)
|
||
- `apps/api/src/services/metricas.service.ts` — (ver Tanda A bugfix en sesión 2026-04-21)
|
||
- `apps/api/src/services/payment/addon.service.ts` — `contribuyenteId` en `subscribeAddon` + `listActiveAddons`; **nueva función `adjustBusinessCloudOverage`** para cableado automático del overage
|
||
- `apps/api/src/controllers/subscription.controller.ts` — `getMyAddons` + `addMyAddon` aceptan contribuyenteId
|
||
- `apps/api/src/controllers/contribuyente.controller.ts` — `create` y `deactivate` llaman `adjustBusinessCloudOverage` tras la operación; helper `countActiveContribuyentes`
|
||
- `apps/api/prisma/schema.prisma` — `SubscriptionAddon.contribuyenteId` opcional
|
||
- `apps/api/prisma/migrations/20260422172323_subscription_addons_contribuyente_id/migration.sql` — **nuevo**
|
||
- `apps/api/prisma/seed.ts` — 2 addons nuevos
|
||
- `apps/api/scripts/sweep-stale-sat-jobs.ts` — **nuevo** (watchdog CLI)
|
||
- `apps/api/scripts/validate-dashboard-impuestos.ts` — (ver Tanda B.3 en sesión 2026-04-21)
|
||
|
||
### Frontend
|
||
- `apps/web/lib/api/addons.ts` — **nuevo** (cliente API)
|
||
- `apps/web/lib/hooks/use-addons.ts` — **nuevo** (hooks React Query)
|
||
- `apps/web/app/(dashboard)/contribuyentes/addons-dialog.tsx` — **nuevo**
|
||
- `apps/web/app/(dashboard)/contribuyentes/page.tsx` — botón Sparkles + wiring del dialog
|
||
|
||
### Data directa
|
||
- `horux_despachos` (central):
|
||
- `planAddonCatalogo` upsert con 2 filas nuevas (`lolita_ia_contribuyente`
|
||
$250/mes, `contribuyente_extra_business_cloud` $45/mes). Aplicado vía
|
||
script temporal ya borrado.
|
||
- `subscriptions` INSERT manual para Zorro (`business_cloud`, $15K/año) y
|
||
Patito (`business_control`, $21K/año). Status `authorized`,
|
||
`mpPreapprovalId=null`. Script temporal borrado.
|
||
- `Tenant.plan` UPDATE en Zorro (de `enterprise`) y Patito (de `business`)
|
||
al plan real.
|
||
- `subscription_addons` INSERT para Alexa (Zorro) y Carlos (Patito) con
|
||
codename `lolita_ia_contribuyente`, preapproval MP real (sandbox).
|
||
Posteriormente se marcaron `authorized` simulando el webhook (script
|
||
temporal que llama `handleAddonPayment(id, 'manual-sim', 'authorized')`,
|
||
ya borrado).
|
||
- `.env`:
|
||
- Agregado `MP_ACCESS_TOKEN` (sandbox).
|
||
- `FRONTEND_URL` cambiado temporalmente a HTTPS y revertido a localhost
|
||
al cerrar. **Próxima vez que se teste MP en dev:** cambiarlo a una URL
|
||
HTTPS pública (Cloudflare Tunnel, ngrok) o a `https://horuxfin.com`.
|
||
|
||
### Documentación
|
||
- `docs/plans/2026-04-21-session-2-mp-setup-and-bugfixes.md` — extendido
|
||
con Tanda B.2-B.5 y referencia al add-on model.
|
||
- `docs/Horux_despachos-vs-Horux360.md` — extendido con §9 crons dev,
|
||
§10 rejection logging, §11 getIvaMensual refactor. Add-ons
|
||
NO incluidos (exclusivos del fork multi-contribuyente).
|
||
|
||
---
|
||
|
||
## Pendientes vigentes al cierre
|
||
|
||
### Derivados de hoy
|
||
- ✅ Wiring del watchdog (`sweep-stale-sat-jobs.ts`) como cron cada 2h en
|
||
`sat-sync.job.ts` — completado 2026-04-23 (refactorizado a función
|
||
exportable `sweepStaleSatJobs` en `services/sat/sweep-stale-jobs.service.ts`;
|
||
cron `WATCHDOG_CRON_SCHEDULE = '0 */2 * * *'` en `startSatSyncJob`).
|
||
- ✅ Cableado automático del add-on `contribuyente_extra_business_cloud`
|
||
completado en esta sesión.
|
||
- **Cloudflare Tunnel en prod** para `MP_NOTIFICATION_URL` — endpoint
|
||
`POST /api/webhooks/mercadopago`. Sin esto, addons pagados en MP se
|
||
quedan `pending` en BD hasta que manualmente se llame `handleAddonPayment`.
|
||
- `FRONTEND_URL` en dev vs MP sandbox — MP rechaza `http://localhost`.
|
||
Solución durable: setear una URL HTTPS de dev (Cloudflare Tunnel,
|
||
ngrok) o un dominio propio permanente.
|
||
- **Investigación SAT rejections** — completado 2026-04-23:
|
||
- `sat-client.service.ts:verifySatRequest` ahora expone `codeRequest`
|
||
(método `getCodeRequest()` de la lib) con su valor numérico + entryId
|
||
+ message descriptivo en el debug log y en el error message. Los 5
|
||
códigos SAT posibles son: `5000 Accepted`, `5002 Exhausted`, `5003
|
||
MaximumLimit`, `5004 EmptyResult`, `5005 Duplicated`.
|
||
- Patrón observado en Manuel: 9 rejections de emitidos (bloques 3-9
|
||
y 12-13), pero bloques 10-11 sí funcionaron — NO es rate limit
|
||
constante. Hipótesis más probable: **5005 Duplicated** (solicitudes
|
||
previas stale para rangos similares que quedaron huérfanas y nuevo
|
||
re-sync es considerado duplicado por el SAT). Requiere capturar
|
||
un caso nuevo con el código mejorado para confirmar.
|
||
- Si se confirma 5005: solución es limpiar solicitudes previas en el
|
||
SAT antes de reintentar (no trivial — SAT no ofrece endpoint de
|
||
cancelación), o esperar ~72h entre intentos. Si es 5003 (MaximumLimit):
|
||
reducir tamaño de rango. Si es 5002 (Exhausted): cambiar FIEL /
|
||
esperar 24h.
|
||
- Re-sync custom de los rangos de emitidos faltantes de Manuel (bloques
|
||
3-9 del XML initial) — pendiente, depende del diagnóstico del punto
|
||
anterior (capturar el `codeRequest` real cuando vuelva a ocurrir).
|
||
- ✅ Validación preventiva CSD↔RFC en `uploadCsdContribuyente` —
|
||
completado 2026-04-23. Ahora valida: (1) cert no es FIEL, (2) RFC del
|
||
cert coincide con contribuyente, (3) no vencido. Mensajes de error
|
||
específicos. Usa `@nodecfdi/credentials`.
|
||
- Recomputar overage al cambiar de plan (ej. downgrade business_cloud →
|
||
business_control debería cancelar el addon overage si existe). Hoy
|
||
solo se dispara desde create/deactivate contribuyente.
|
||
|
||
### De sesiones anteriores (abiertos)
|
||
- Recrear org Facturapi de Carlos (TORC9611214CA)
|
||
- Validación preventiva CSD↔RFC en `uploadCsdContribuyente`
|
||
- Prueba cross-contribuyente end-to-end
|
||
- Typecheck web cleanup (~12 errores preexistentes en sidebar/cfdi/usuarios)
|
||
|
||
### Features pending
|
||
Ver `docs/plans/2026-04-19-pending-features.md`. De esa lista:
|
||
- ✅ #8 Extras en Documentos — completado en sesión anterior
|
||
- ✅ #5 Add-ons por contribuyente — **Lolita IA completado hoy**; falta
|
||
overage business_cloud automático
|
||
- #1 Editar contribuyentes asignados a cliente
|
||
- #2 Convertir Pendientes → Despacho con métricas
|
||
- #6 Enlazar obligaciones ↔ declaraciones
|
||
- #7 Colores obligaciones en calendario
|
||
- #9 Avisos por correo al subir declaración / doc extra
|
||
- #10 Alertas de obligaciones — bug de filtros per-contribuyente
|