# Documentos: Declaraciones Provisionales + Constancia de Situación Fiscal Fecha: 2026-04-14 Autores: Carlos e Ivan (Horux360) ## Contexto `/documentos` solo tenía Opinión de Cumplimiento. Pedimos: 1. Panel donde el contador sube el PDF de declaraciones mensuales del cliente (normal + complementarias) y se desactivan los recordatorios automáticamente. 2. Descarga automática mensual de la Constancia de Situación Fiscal (CSF) del portal SAT, con auto-fill del domicilio fiscal y regímenes activos del tenant. ## Alcance `/documentos` se reorganizó en 3 pestañas: - **Opinión de Cumplimiento** (pre-existente) - **Constancia de Situación Fiscal** (nueva) - **Declaraciones Provisionales** (nueva) ## Declaraciones Provisionales ### Schema - Migración `003_create_declaraciones_provisionales.sql`: tabla con `pdf_declaracion BYTEA`, `pdf_pago BYTEA`, `impuestos TEXT[]` (IVA/ISR/IEPS/SUELDOS/DIOT/OTRO), `tipo` (normal/complementaria). UNIQUE INDEX parcial para 1 normal por (año, mes). - Migración `004_declaraciones_liga_pago_pdf.sql`: reemplaza `link_pago TEXT` por `pdf_liga_pago BYTEA` + `pdf_liga_pago_filename` (la liga de pago es PDF de línea de captura, no URL). ### Reglas de auto-resolución de alertas - Tipo `normal`: resuelve alertas `decl--YYYY-MM-*` de los impuestos seleccionados. - Tipo `complementaria`: resuelve `decl-*` y `pago-*` (la complementaria sustituye a la normal en pago — ya no hay que pagar la normal). - Mapping `Impuesto → prefijo de alerta`: - `IVA` → `decl-iva` + `pago-iva` - `ISR` → `decl-isr` + `pago-isr` - `IEPS` → `decl-ieps` + `pago-ieps` - `SUELDOS` → `decl-sueldos` (no tiene pago) - `DIOT` → `diot` (solo declaración) - `OTRO` → sin mapping ### Subida de comprobante de pago Endpoint separado `POST /documentos/declaraciones/:id/comprobante-pago` que solo acepta el PDF. Al subir, se resuelven las alertas `pago-*` del mes. ### Retención 5 años (CFF Art. 30). Purge en cron lifecycle 2:30 AM. ## Constancia de Situación Fiscal ### Schema - Migración `005_create_constancias_situacion_fiscal.sql`: `pdf BYTEA`, `datos JSONB` (shape completo), `rfc`, `id_cif`, `razon_social`, `estatus_padron`, `fecha_emision`. Index DESC por `fecha_consulta`. ### Scraper portado del prototipo Del proyecto standalone `sat-csf-prototype/` (en Downloads): - `sat-csf-login.ts`: navegación portal SAT → popup SERVICIO → login FIEL. Fix crítico: el click sintético a "e.firma" a veces no dispara el handler del SAT, por eso si tras 10s no aparece el `input[type=file]` se reintenta con `dispatchEvent('click')`. - `sat-csf-scraper.ts`: el botón "Generar Constancia" vive en un iframe JSF legacy (`rfcampc.siat.sat.gob.mx/PTSC/...`). Iteramos `appPage.frames()`. 3 rutas de extracción del PDF: download event, popup viewer (blob:/data:/http), response interception. - `sat-csf-parser.ts`: parser PF+PM. Labels `key:value` con lookahead a la siguiente label o sección. Tablas (actividades/regímenes/obligaciones) agrupadas por "chunk termina en dd/mm/yyyy" + filtro de page-break noise. Obligaciones PM requieren regex extendido (`Dentro de`, `Mensualmente`, etc. además de `A más tardar`). ### Auto-fill de domicilio y regímenes `sincronizarDatosFiscales(tenantId, csf)` en `constancia.service.ts`: - **Domicilio fiscal** — actualiza campos `tenants`: `codigo_postal`, `calle` (compuesta por `tipoVialidad + nombreVialidad`), `num_exterior`, `num_interior` (ignora "SIN NUMERO"), `colonia`, `ciudad` (← localidad), `municipio`, `estado` (← entidadFederativa). Solo actualiza campos cuando el CSF trae valor no-vacío — nunca pisa con null. - **Regímenes activos** — matchea `csf.regimenes` (nombre libre) contra catálogo `regimenes` (clave SAT + descripcion). Normalización: strip "Régimen", "de las/los", NFD+accents, lowercase; matching por `===` o `includes`. Reemplaza toda la lista `tenant_regimenes_activos` con lo que diga la CSF (solo regímenes sin `fechaFin`). El usuario puede sobreescribir manualmente después desde `/configuracion` — cada consulta de CSF vuelve a pisar. ### Disparadores 1. **Cron mensual** día 1 04:00 AM (`0 4 1 * *`) — en `sat-sync.job.ts`, junto con los demás. 2. **Primer upload de FIEL** — en `fiel.service.ts` se detecta `!existingFiel || !existingFiel.isActive` y se disparan Opinión + CSF con `import()` fire-and-forget. No bloquea la respuesta. 3. **Manual** — `POST /documentos/constancias/consultar` (owner/cfo, rate limit 2/día). ### Retención 5 años. Purge en cron lifecycle 2:30 AM junto con declaraciones. ### Headless `chromium.launch({ headless: true })` por default (no se ve ventana al usuario). Flag `SAT_HEADLESS=false` en `.env` para debug visual temporal. El fix del `dispatchEvent` hace que headless sea confiable. ## UI - `apps/web/app/(dashboard)/documentos/page.tsx` reescrito con `Tabs` de 3 pestañas. - `OpinionTab`: preserva funcionalidad existente. - `ConstanciaTab`: detalle de último CSF (identificación / domicilio / regímenes / obligaciones) + historial de 12 con detalle expandible por row + descarga PDF + "Consultar ahora". - `DeclaracionesTab`: selector de año, tabla mensual, dialog para subir declaración (multi-select de impuestos, PDF declaración obligatorio, liga de pago y notas opcionales), dialog para subir comprobante de pago, descarga PDFs. ## Archivos creados Backend: - `apps/api/src/migrations/tenant/003_create_declaraciones_provisionales.sql` - `apps/api/src/migrations/tenant/004_declaraciones_liga_pago_pdf.sql` - `apps/api/src/migrations/tenant/005_create_constancias_situacion_fiscal.sql` - `apps/api/src/services/declaraciones.service.ts` - `apps/api/src/services/constancia.service.ts` - `apps/api/src/services/sat/sat-csf-login.ts` - `apps/api/src/services/sat/sat-csf-scraper.ts` - `apps/api/src/services/sat/sat-csf-parser.ts` Frontend: - `apps/web/lib/api/declaraciones.ts` - `apps/web/lib/api/constancias.ts` - `apps/web/lib/hooks/use-declaraciones.ts` - `apps/web/lib/hooks/use-constancias.ts` ## Archivos modificados - `apps/api/src/controllers/documentos.controller.ts` — endpoints declaraciones + constancias - `apps/api/src/routes/documentos.routes.ts` — rutas nuevas - `apps/api/src/jobs/sat-sync.job.ts` — cron CSF mensual + purge 5 años - `apps/api/src/services/fiel.service.ts` — trigger on first upload - `apps/web/app/(dashboard)/documentos/page.tsx` — tabs + UI completa ## Pendientes futuros 1. Al detectar cambios en domicilio/régimen/obligaciones entre consultas de CSF, levantar alerta al contador y owner (notificación in-app + email opcional). 2. Si el `estatusPadron` != ACTIVO, alerta de alta prioridad (análoga a la de Opinión Negativa). 3. Eventualmente exponer `actividadesEconomicas` en la UI de configuración para que el contador las revise.