Files
HoruxDespachos/docs/superpowers/specs/2026-04-13-opinion-cumplimiento-design.md
2026-04-27 22:09:36 -06:00

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 → report
  • src/opinion-scraper.ts — 4 strategies to extract PDF base64 from DOM
  • src/pdf-parser.ts — Regex extraction of RFC, razón social, estatus, folio, cadena original
  • src/types.tsOpinionCumplimiento, Obligacion interfaces

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:

  1. getDecryptedFiel(tenantId) returns Buffers in memory
  2. Write .cer and .key to os.tmpdir() with permissions 0o600
  3. Pass paths to Playwright page.setInputFiles()
  4. Delete temp files in finally block (guaranteed cleanup even on error)
  5. 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:

  1. Decrypt FIEL → write temp files
  2. Launch Playwright headless → login → navigate to report
  3. Extract PDF base64 from DOM → parse text
  4. INSERT into opiniones_cumplimiento
  5. DELETE records older than 6 months
  6. Cleanup temp files
  7. 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-parse v2 (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)