6.8 KiB
Opinión de Cumplimiento — Integration Design
Date: 2026-04-13 Status: Approved
Problem
Horux360 has no way to check or track a tenant's SAT compliance status (Opinión de Cumplimiento). This is a critical fiscal document that indicates whether a company is current on all tax obligations. Currently, users must manually download it from the SAT portal.
Solution
Integrate the existing standalone Playwright-based prototype into Horux360 as a weekly automated process. Display results in a new "Documentos" page accessible to all roles (business+ plans). Store last 6 months in DB, show last 5 in UI. Alert if status is not Positiva.
Source Prototype
Located at C:\Users\chtr1\Downloads\sat-opinion-prototype. Key files to adapt:
src/sat-login.ts— Playwright navigation: public page → FIEL login → reportsrc/opinion-scraper.ts— 4 strategies to extract PDF base64 from DOMsrc/pdf-parser.ts— Regex extraction of RFC, razón social, estatus, folio, cadena originalsrc/types.ts—OpinionCumplimiento,Obligacioninterfaces
Architecture
New Files
| File | Purpose |
|---|---|
src/services/opinion-cumplimiento.service.ts |
Orchestration: decrypt FIEL → temp files → Playwright → parse → save to DB → cleanup |
src/services/sat/sat-opinion-login.ts |
Adapted sat-login.ts: works with temp file paths from decrypted FIEL Buffers |
src/services/sat/sat-opinion-scraper.ts |
Adapted opinion-scraper.ts: extracts PDF from SAT Angular SPA |
src/services/sat/sat-opinion-parser.ts |
Adapted pdf-parser.ts: regex extraction from PDF text |
src/controllers/documentos.controller.ts |
Endpoints: list opinions, download PDF, manual trigger |
src/routes/documentos.routes.ts |
Routes with tenantMiddleware + feature gate |
src/migrations/tenant/002_create_opiniones_cumplimiento.sql |
Tenant DB migration |
apps/web/app/(dashboard)/documentos/page.tsx |
Frontend: Documentos page with Opinión tab |
apps/web/lib/api/documentos.ts |
API client functions |
apps/web/lib/hooks/use-documentos.ts |
React Query hooks |
Modified Files
| File | Change |
|---|---|
src/jobs/sat-sync.job.ts |
Add weekly cron for opinion download |
src/services/alertas-auto.service.ts |
Add alert for non-Positiva status |
apps/web/components/layouts/sidebar.tsx |
Add Documentos nav item |
apps/api/package.json |
Add playwright, pdf-parse dependencies |
packages/shared/src/types/ |
Add OpinionCumplimiento types |
Database
Table: opiniones_cumplimiento (per-tenant DB)
CREATE TABLE IF NOT EXISTS opiniones_cumplimiento (
id SERIAL PRIMARY KEY,
rfc VARCHAR(14) NOT NULL,
razon_social VARCHAR(255),
estatus VARCHAR(50) NOT NULL,
folio VARCHAR(50),
cadena_original TEXT,
fecha_consulta TIMESTAMP NOT NULL,
pdf BYTEA NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_opiniones_fecha ON opiniones_cumplimiento(fecha_consulta DESC);
Migration file: 002_create_opiniones_cumplimiento.sql
Retention: Records older than 6 months are deleted during the weekly cron run.
UI display: Only the last 5 records are shown via ORDER BY fecha_consulta DESC LIMIT 5.
FIEL Security
The FIEL is stored encrypted (AES-256-GCM) in the central DB. For Playwright, which requires file paths:
getDecryptedFiel(tenantId)returns Buffers in memory- Write .cer and .key to
os.tmpdir()with permissions0o600 - Pass paths to Playwright
page.setInputFiles() - Delete temp files in
finallyblock (guaranteed cleanup even on error) - Password is only passed via
page.fill()— never written to disk
Additional:
- Playwright runs headless in production (no
slowMo) - 3-minute timeout per tenant to prevent hanging processes
- Temp file names use
crypto.randomUUID()to avoid collisions
Cron Schedule
'0 4 * * 0' — Sundays 4:00 AM (America/Mexico_City)
Runs after the daily SAT sync (3:00 AM) to avoid overlap. Processes tenants sequentially (Playwright is heavy — no parallelism).
Cron Flow
For each active tenant with FIEL configured:
- Decrypt FIEL → write temp files
- Launch Playwright headless → login → navigate to report
- Extract PDF base64 from DOM → parse text
- INSERT into
opiniones_cumplimiento - DELETE records older than 6 months
- Cleanup temp files
- Close browser
Error handling: if one tenant fails, log error and continue to next. Don't stop the batch.
API Endpoints
| Method | Route | Auth | Description |
|---|---|---|---|
| GET | /api/documentos/opiniones |
All roles | Last 5 opinions (metadata only, no PDF binary) |
| GET | /api/documentos/opiniones/:id/pdf |
All roles | Download PDF as binary (Content-Type: application/pdf) |
| POST | /api/documentos/opiniones/consultar |
Admin only | Trigger manual download for current tenant |
All routes use tenantMiddleware + requireFeature('documentos').
GET /api/documentos/opiniones response
[
{
"id": 1,
"rfc": "HTS240708LJA",
"razonSocial": "HORUX 360 SA DE CV",
"estatus": "Positiva",
"folio": "26NC4144337",
"cadenaOriginal": "||HTS240708LJA|26NC4144337|...",
"fechaConsulta": "2026-04-13T20:59:00.000Z",
"createdAt": "2026-04-13T22:00:00.000Z"
}
]
Auto-Alert
New alert in alertas-auto.service.ts:
async function alertaOpinionCumplimiento(pool: Pool): Promise<AlertaAuto | null>
- Queries latest record from
opiniones_cumplimiento - If
estatus !== 'Positiva'→ returns alert with priority 'alta' - Message: "Tu Opinión de Cumplimiento es {estatus}. Última consulta: {fecha}."
- No drill-down (Documentos page shows details)
- If no records exist → no alert
Frontend
Sidebar
{ name: 'Documentos', href: '/documentos', icon: FileCheck, feature: 'documentos' }
Between Facturación and Usuarios in the navigation array. Visible to all roles.
Page: /documentos
- Tab structure (future-proof for other document types): first tab "Opinión de Cumplimiento"
- Card/row per opinion showing:
- Fecha de consulta (formatted)
- Estatus badge (green=Positiva, red=Negativa, yellow=others)
- Folio
- Button to download PDF
- "Consultar ahora" button (admin only) triggers POST
- Empty state: "No hay opiniones registradas. La consulta automática se ejecuta cada semana."
- Loading/error states with React Query
Dependencies
Add to apps/api/package.json:
playwright(for Chromium automation)pdf-parsev2 (for PDF text extraction)
Post-install: npx playwright install chromium (needed on deploy)
Scope Exclusions
- Parser for "Negativa" opinion obligations list (refine when sample PDF available)
- Email notifications on status change (only auto-alert for now)
- Multiple document types in the Documentos page (only Opinión de Cumplimiento in v1)
- PDF viewer in browser (download only)