Initial commit - Horux Despachos NL
This commit is contained in:
186
docs/superpowers/specs/2026-04-13-opinion-cumplimiento-design.md
Normal file
186
docs/superpowers/specs/2026-04-13-opinion-cumplimiento-design.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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.ts` — `OpinionCumplimiento`, `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)
|
||||
|
||||
```sql
|
||||
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
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"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`:
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
{ 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)
|
||||
Reference in New Issue
Block a user