Manual Técnico — LegalDoc VE
Arquitectura, esquema de datos, servicios, seguridad, IA y despliegue del sistema de gestión legal y compliance.
1. Stack Tecnológico
Frontend
| React | 19.2.0 |
| Vite | 7.2.4 |
| TypeScript | 5.9.3 |
| Tailwind CSS | CDN + tailwind-merge |
| Recharts | 3.6.0 |
| Framer Motion | 12.26.2 |
| Lucide React | 0.562.0 |
| jsPDF | 4.2.1 + AutoTable 5.0.7 |
| date-fns | 4.1.0 |
Backend / IA
| Supabase JS | 2.93.3 |
| PostgreSQL | 15+ (Supabase Cloud) |
| pgvector | 1536 dimensiones |
| pgcrypto | SHA-256 forense |
| OpenAI GPT-4o | Análisis + Chat legal |
| text-embedding-3-small | Vectores RAG |
| Edge Functions | Deno runtime |
| Deploy | Netlify |
server/ con Express + Prisma + SQLite
(legacy). Está deprecado — toda la producción usa Supabase.
2. Arquitectura del Sistema
┌─────────────────────────────────────────────────────────────────┐
│ NETLIFY CDN (SPA) │
│ React 19 + Vite 7 + TypeScript + Tailwind + Framer Motion │
│ │
│ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Documents │ │Contracts │ │Compliance│ │ Expedientes │ │
│ │ Module │ │ E-Sign │ │ LOTTT │ │ Judicial │ │
│ └─────┬─────┘ └─────┬────┘ └─────┬────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ ┌─────┴──────────────┴──────────────┴──────────────┴──────┐ │
│ │ SERVICE LAYER (TypeScript) │ │
│ │ auth · document · contract · signature · compliance │ │
│ │ expediente · honorarios · calendar · flow · audit │ │
│ │ ai · parameters · notification · report · pdf │ │
│ └─────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ supabase-js 2.93 │ │
│ │ + Web Crypto API │ │
│ └──────────┬──────────┘ │
└─────────────────────────┼───────────────────────────────────────┘
│ HTTPS
┌─────────────────────────┼───────────────────────────────────────┐
│ SUPABASE CLOUD │
│ │ │
│ ┌──────────┐ ┌───────┴──────┐ ┌────────────────────┐ │
│ │ Auth │ │ PostgREST │ │ Edge Functions │ │
│ │ (JWT) │ │ (REST API) │ │ legal-ai-processor│ │
│ └──────────┘ └──────────────┘ │ exchange-rates │ │
│ │ daily-alert │ │
│ ┌────────────────────────────┐ └────────────────────┘ │
│ │ PostgreSQL 15+ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ pgvector│ │ pgcrypto │ │ RLS │ │Triggers │ │ │
│ │ │ (1536d) │ │ (SHA256) │ │org_isol. │ │ audit │ │ │
│ │ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │ │
│ │ 30+ tablas multi-tenant · Storage (archivos) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
organization_id.
RLS garantiza aislamiento a nivel de kernel PostgreSQL.
audit_logs vía trigger
PostgreSQL + pgcrypto. Verificación de integridad por hash encadenado.
3. Estructura del Proyecto
src/
├── App.tsx # Router principal (state-based switch)
├── main.tsx # Entry point
├── core/
│ ├── supabase.ts # Cliente Supabase
│ ├── auth.service.ts # Login, logout, sync, permisos
│ ├── user.types.ts # UserRole, User, Permission, ROLE_PERMISSIONS
│ ├── api.ts # Axios (legacy — deprecado)
│ └── rgpd.service.ts # Consentimiento RGPD
├── ui/layouts/
│ └── MainLayout.tsx # Shell con sidebar de navegación
├── modules/
│ ├── dashboard/ # DashboardView.tsx
│ ├── documents/ # CRUD + análisis IA + workflow aprobación
│ │ ├── types.ts # DocumentType, Status, RiskLevel
│ │ ├── documents.service.ts # CRUD + file upload + signed URLs
│ │ ├── ai.service.ts # Interfaz IAIService
│ │ ├── openai.service.ts # Chat vía Edge Function
│ │ └── workflow.service.ts # Flujo de aprobación por pasos
│ ├── contracts/ # Contratos + firma electrónica
│ │ ├── types.ts # ContractType, Status
│ │ ├── contract.service.ts # CRUD
│ │ ├── signature.service.ts # SHA-256, firma, verificación, revocación
│ │ ├── SignaturePanel.tsx # UI de firma con biometría
│ │ └── ExternalSignView.tsx # Portal público para firma externa
│ ├── compliance/ # Cumplimiento regulatorio
│ │ ├── compliance.service.ts # CRUD + alertas + RGPD audit
│ │ └── RiskMatrixView.tsx # Matriz de riesgos NIST/ISO
│ ├── expedientes/ # Expedientes judiciales
│ │ ├── expediente.service.ts # CRUD + actuaciones + audiencias
│ │ └── ActuacionesTimeline.tsx # Línea de tiempo procesal
│ ├── honorarios/ # Facturación legal
│ │ ├── honorarios.service.ts # 6 sub-servicios (Client, Matter, etc.)
│ │ └── types.ts # FeeType, Currency, InvoiceType
│ ├── calendar/ # Calendario judicial
│ │ ├── calendar.service.ts # Feriados, días hábiles, lapsos
│ │ └── LapsosWidget.tsx # Widget de vencimientos
│ ├── flows/ # Flujos BPM
│ │ └── flow.service.ts # Templates, stages, instances, tasks
│ ├── legal-team/ # Equipo de abogados
│ ├── iam/ # Gestión de usuarios
│ ├── parameters/ # Parámetros del sistema (42 seeds)
│ └── shared/ # Servicios transversales
│ ├── audit.service.ts # Auditoría forense SHA-256
│ ├── bcv-rate.service.ts # Tasa BCV USD/VES
│ ├── legal-knowledge.service.ts # Seed corpus legal venezolano
│ ├── notification.service.ts # Notificaciones multi-canal
│ ├── predictive-ai.service.ts # Predicción judicial GPT-4o
│ ├── report.service.ts # Reportes HTML con pie forense
│ ├── pdf-report.service.ts # PDFs certificados (jsPDF)
│ ├── terms.service.ts # Cálculo de plazos judiciales
│ └── i18n.service.ts # Monedas, conversiones, formatos
4. Base de Datos (30+ tablas)
4.1 Tablas Principales
organizations —
Tenant raíz multi-tenant
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK, gen_random_uuid() |
name |
TEXT | NOT NULL |
rif |
TEXT | RIF venezolano |
legal_name |
TEXT | — |
currency |
TEXT | DEFAULT 'VES' |
legal_region |
TEXT | DEFAULT 'VE' |
subscription |
TEXT | 'basic' | 'pro' | 'enterprise' |
is_active |
BOOLEAN | DEFAULT true |
profiles —
Usuarios (FK → auth.users)
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK, FK → auth.users ON DELETE CASCADE |
email |
TEXT | NOT NULL |
name |
TEXT | NOT NULL |
avatar_url |
TEXT | — |
role |
TEXT | DEFAULT 'aprendiz' |
is_active |
BOOLEAN | DEFAULT true |
organization_id |
UUID | FK → organizations |
documents —
Documentos legales
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK |
title |
TEXT | NOT NULL |
type |
TEXT | contract | policy | regulatory | evidence | legal_opinion | other |
status |
TEXT | draft | in_review | approved | published | archived | expired |
risk_level |
TEXT | low | medium | high | critical |
version |
TEXT | DEFAULT '1.0' |
region |
TEXT | DEFAULT 'nacional' |
tags |
TEXT[] | Array de etiquetas |
assigned_to |
UUID | FK → profiles |
organization_id |
UUID | NOT NULL, FK → organizations |
file_url |
TEXT | Ruta en Storage |
contracts —
Contratos con firma electrónica
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK |
title |
TEXT | NOT NULL |
type |
TEXT | SERVICE | EMPLOYMENT | NDA | LEASE | PARTNERSHIP | ... |
status |
TEXT | DRAFT | REVIEW | ACTIVE | EXPIRED | TERMINATED | CANCELLED |
parties |
TEXT[] | Partes involucradas |
value |
NUMERIC(15,2) | Monto del contrato |
currency |
TEXT | DEFAULT 'VES' |
content_draft |
TEXT | HTML del contrato |
signature_status |
TEXT | unsigned | signed |
signature_hash |
TEXT | SHA-256 |
signature_token |
TEXT | Token de verificación |
signed_at |
TIMESTAMPTZ | — |
signed_by_name |
TEXT | — |
signed_by_email |
TEXT | — |
metadata |
JSONB | {urgent, autoRenewal, confidential, has_biometric, biometric_photo} |
organization_id |
UUID | NOT NULL, FK → organizations |
compliance_items — Elementos de cumplimiento regulatorio
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK |
title |
TEXT | NOT NULL |
area |
TEXT | LEGAL | TAX | LABOR | REGULATORY | ENVIRONMENTAL | OPERATIONAL |
status |
TEXT | COMPLIANT | NON_COMPLIANT | PARTIAL | PENDING | EXPIRED |
risk_level |
TEXT | LOW | MEDIUM | HIGH | CRITICAL |
next_review |
TIMESTAMPTZ | Programa alertas automáticas |
legal_citation |
TEXT | Base legal aplicable |
organization_id |
UUID | NOT NULL, FK → organizations |
audit_logs —
Cadena forense (Append-Only)
| Columna | Tipo | Restricciones |
|---|---|---|
id |
UUID | PK |
action |
TEXT | INSERT | UPDATE | DELETE | LOGIN | EXPORT |
entity_type |
TEXT | NOT NULL |
entity_id |
UUID | NOT NULL |
old_data |
JSONB | Estado anterior (trigger) |
new_data |
JSONB | Estado nuevo (trigger) |
previous_hash |
TEXT | SHA-256 del registro anterior |
checksum |
TEXT | NOT NULL, SHA-256 de este registro |
user_id |
UUID | FK → profiles |
organization_id |
UUID | FK → organizations |
system_parameters — 42 parámetros configurables
| Columna | Tipo | Restricciones |
|---|---|---|
category |
TEXT | LAPSOS | ARANCELES | DIVISAS | NOTIFICACIONES | IA_CUOTAS | COMPLIANCE | HONORARIOS | SISTEMA | CALENDARIO |
code |
TEXT | NOT NULL |
value |
TEXT | NOT NULL |
value_type |
TEXT | text | number | boolean | json | currency | percentage |
jurisdiction |
TEXT | ALL | VE | US | EU | LATAM | MERCOSUR | CAN | CARICOM |
is_system |
BOOLEAN | true = no editable por usuario |
organization_id |
UUID | NULL = global |
UNIQUE(code, organization_id, jurisdiction)
4.2 Tablas Complementarias
Expedientes Judiciales
expedientes— Casos judiciales con cuantía y riesgoactuaciones— Actos procesales (9 tipos)audiencias— Audiencias con alertas automáticasexpediente_lapsos— Plazos calculados por expediente
Honorarios y Facturación
clients— Clientes con riesgo crediticiomatters— Asuntos legales multi-monedatime_entries— Registro de horasmatter_expenses— Gastos por categoríainvoices— Facturas con cálculo ISLRpayments— Pagos (transferencia, Zelle, crypto...)
Flujos y Calendario
legal_flow_templates— Plantillas BPM por procesolegal_flow_stages— Etapas con límites de tiempoexpediente_flow_instances— Instancias activasexpediente_flow_tasks— Tareas individualesjudicial_holidays— Feriados judiciales por jurisdicción
IA, Vectores y Otros
document_analysis— Resultados de análisis GPT-4odocument_vectors— Embeddings pgvector(1536)document_approvals— Pasos de aprobaciónlawyers— Equipo legal (interno/externo)compliance_alerts— Alertas programadasrgpd_consents— Consentimiento RGPDai_usage_logs— Consumo de tokens IAexchange_rates— Historial de tasas BCV
5. Seguridad RLS Multi-Tenant
Patrón org_isolation
Todas las tablas de negocio aplican aislamiento por
organization_id a nivel de kernel PostgreSQL. Ninguna consulta puede acceder a datos de
otra organización, incluso con acceso directo a PostgREST.
-- Aplicado a CADA tabla de negocio
CREATE POLICY "tabla_org_isolation" ON tabla
USING (
organization_id = (
SELECT organization_id
FROM profiles
WHERE id = auth.uid()
)
);
Defensa en profundidad
.eq('organization_id', orgId) como doble validación.
organization_id/.
URLs firmadas con expiración de 1 hora.
organization_id
se extrae del token, no del request body.
Jerarquía RBAC
| Rol | Nivel | Permisos clave |
|---|---|---|
| consultor_general | Máximo | Todo: usuarios, contratos, IA, auditoría, parámetros |
| abogado_senior | Alto | CRUD completo, aprobar contratos, IA, reportes |
| consultor_principal | Medio | CRUD documentos, compliance, expedientes |
| abogado_junior | Básico | Crear/editar documentos asignados, registrar horas |
| aprendiz | Mínimo | Solo lectura, dashboard |
Definido en ROLE_PERMISSIONS en
src/core/user.types.ts. 11 permisos atómicos (view_dashboard, manage_users,
approve_contracts, etc.)
6. Autenticación
Flujo de Login
1. Usuario envía email + password
2. authService.login() → supabase.auth.signInWithPassword()
3. Éxito → query profiles WHERE id = auth.user.id
4. Validar: profile existe, is_active = true, organization_id asignado
5. Mapear a User: { id, email, name, role, organizationId }
6. Guardar en localStorage key 'legal_user'
7. App.tsx: setUser() → renderizar MainLayout
8. En reload: syncSession() → supabase.auth.getSession() → re-fetch profile
9. Check RGPD: rgpdService.hasConsent(userId) → mostrar banner si falta
10. SecurityReminderOverlay mostrado en cada login
Registro
supabase.auth.signUp() con metadata
{name, role}, luego upsert en tabla profiles.
Cambio de Clave
Intenta auth.admin.updateUserById() (service_role),
fallback a auth.updateUser(), luego resetPasswordForEmail().
RGPD
rgpdService: consentimiento, exportación de datos
personales, derecho al olvido. Tabla rgpd_consents.
7. Capa de Servicios (20+ servicios)
authService
core/auth.service.ts
login(email, pass)register(email, pass, name, role)logout()getCurrentUser()syncSession()hasPermission(user, perm)
documentService
modules/documents/
getAll(filter?)getById(id)save(doc)delete(id)uploadFile(file, path)getDownloadUrl(path)— signed 1h
signatureService
modules/contracts/
signBasic(req)→ hash + tokenverify(contractId, content)revoke(contractId, userId, orgId)generateHash(content)— SHA-256
complianceService
modules/compliance/
getAll()/save(item)scheduleAlert(id, date, msg)getSummary(items)getConsentAuditLogs()getAiUsageLogs()
expedienteService
modules/expedientes/
getAll()/getById(id)getStats()— resumen globalgetActuaciones(expId)getAudiencias(expId)getProximasAudiencias(days)
honorariosService
modules/honorarios/ — 6 sub-servicios
clientService— CRUD clientesmatterService— Asuntos + resumen financierotimeEntryService— Registro de horasexpenseService— Gastos por categoríainvoiceService— Facturas + ISLRpaymentService— Pagos multi-método
aiService
modules/documents/ai.service.ts
analyze(docId, text, type)analyzeComplianceRisk(desc)chatQuery(query, context)searchLegalKnowledge(q)generateEmbedding(text)predictSuccessOutcome(case)indexLegalKnowledge(entity)
auditService
modules/shared/audit.service.ts
generateHash(content)— SHA-256log(auditLog)— evento virtualgetByEntity(type?, id?)getDiff(old, new)verifyChain(orgId?, limit?)
calendarService
modules/calendar/
getHolidays(jurisd, year)calcularFechaVencimiento()contarDiasHabiles()calcularLapsosExpediente()getProximasAlertas(dias)
flowService
modules/flows/
getTemplates(processType?)startFlow(expId, templateId)updateTaskStatus(id, status)calcularProgreso(tasks)checkOverdueTasks(instanceId)activateNextTask(instanceId)
parametersService
modules/parameters/ — caché 5 min
getAll(force?)/getByCategory()getValue(code, jurisd?, process?)getLapsoApelacion(processType)getTasaBCV()getModeloIA()calcularHonorario(cuantia, type)
Servicios compartidos
modules/shared/
bcvRateService— Tasa BCV USD/VESnotificationService— Email / SMSreportService— Reportes HTML forensespdfReportService— PDFs con jsPDFpredictiveAiService— Predicción judiciali18nService— Monedas y formatoslegalKnowledgeService— Seed corpus legal
8. Sistema de Tipos TypeScript
Core — Roles y Permisos
type UserRole = 'consultor_general' | 'abogado_senior' | 'abogado_junior'
| 'consultor_principal' | 'aprendiz'
interface User {
id: string; email: string; name: string; role: UserRole;
avatar?: string; isActive: boolean; organizationId?: string;
}
type Permission = 'view_dashboard' | 'manage_users' | 'approve_contracts'
| 'manage_documents' | 'manage_compliance' | ... // 11 total
const ROLE_PERMISSIONS: Record<UserRole, Permission[]>
Documentos
type DocumentType = 'contract' | 'policy' | 'regulatory' | 'evidence'
| 'legal_opinion' | 'permit_license' | 'circular_memo'
| 'corporate_governance' | 'tax_fiscal' | 'labor'
| 'insurance' | 'other' // 12 tipos
type DocumentStatus = 'draft' | 'in_review' | 'approved' | 'published'
| 'archived' | 'expired'
type RiskLevel = 'low' | 'medium' | 'high' | 'critical'
Contratos
type ContractType = 'SERVICE' | 'EMPLOYMENT' | 'NDA' | 'LEASE'
| 'PARTNERSHIP' | 'SUPPLY' | 'CONSULTING'
| 'FRANCHISE' | 'LOAN' | 'OTHER' // 10 tipos
type ContractStatus = 'DRAFT' | 'REVIEW' | 'ACTIVE' | 'EXPIRED'
| 'TERMINATED' | 'CANCELLED'
Expedientes Judiciales
type TipoProceso = 'CIVIL' | 'LABORAL' | 'MERCANTIL' | 'PENAL'
| 'ADMINISTRATIVO' | 'CONSTITUCIONAL' | 'ARBITRAJE'
type ExpedienteStatus = 'ACTIVO' | 'SUSPENDIDO' | 'CERRADO'
| 'GANADO' | 'PERDIDO' | 'CONCILIADO'
type TipoActuacion = 'ESCRITO' | 'AUDIENCIA' | 'SENTENCIA' | ... // 9 tipos
type TipoAudiencia = 'PRELIMINAR' | 'JUICIO' | 'CONCILIACION' | ... // 7 tipos
Honorarios
type FeeType = 'HOURLY' | 'FIXED' | 'CONTINGENCY' | 'RETAINER'
type Currency = 'USD' | 'EUR' | 'VES'
type InvoiceStatus = 'DRAFT' | 'SENT' | 'PAID' | 'OVERDUE' | 'CANCELLED'
type PaymentMethod = 'TRANSFER' | 'CASH' | 'CHECK' | 'ZELLE' | 'CRYPTO'
type ExpenseCategory = 'COURT_FEE' | 'NOTARY' | 'EXPERT' | 'TRAVEL' | ...
type TimeCategory = 'CONSULTATION' | 'DRAFTING' | 'REVIEW' | 'COURT' | ...
Parámetros del Sistema
type ParamCategory = 'LAPSOS' | 'ARANCELES' | 'DIVISAS' | 'NOTIFICACIONES'
| 'IA_CUOTAS' | 'COMPLIANCE' | 'HONORARIOS'
| 'SISTEMA' | 'CALENDARIO' // 9 categorías
type Jurisdiction = 'ALL' | 'VE' | 'US' | 'EU' | 'LATAM'
| 'MERCOSUR' | 'CAN' | 'CARICOM' // 7 opciones
9. Firma Electrónica SHA-256
Generación del Hash (Web Crypto API)
async generateHash(content: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(content);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
Contenido Canónico (V2 — LDFE)
El hash se calcula sobre una representación canónica determinista del contrato, no sobre el HTML original:
LEGALDOC-VE-V2
ID:<contract_id>
TITLE:<TITLE_UPPERCASE>
SIGNER:<name> <<email>>
DATE:<ISO_sin_ms>Z
BIO:<longitud_biometrico | NONE>
BODY:
<html_stripped_body>
HTML se limpia con:
replace(/<[^>]+>/g, ' '), replace(/ /g, ' '),
replace(/\s+/g, ' ')
Token de Verificación
Formato: LDV-<timestamp_base36_UPPER>-<uuid_12chars_UPPER>
Ejemplo: LDV-M5X7K2-A1B2C3D4E5F6
Proceso de Verificación
Verificación interna
- Obtener contrato de DB por ID
- Reconstruir contenido canónico con campos almacenados (
signed_at,signed_by_name, etc.) - Recalcular hash SHA-256
- Comparar con
signature_hashalmacenado - Resultado:
valid: true/falsecon mensaje descriptivo
Firma externa (sin login)
- Compartir URL con token:
/sign/LDV-M5X7K2-... - ExternalSignView carga contrato por token
- Firmante ingresa nombre + email
- Opcionalmente: captura biométrica (foto Base64)
- Se genera hash canónico y se almacena
Revocación
Resetea todos los campos de firma a null/'unsigned'. Se
registra en audit_logs como evento de revocación con los datos anteriores en
old_data.
10. Cadena de Auditoría Forense
Trigger PostgreSQL (pgcrypto)
-- Función trigger: audit_trigger_func
-- Extensión: pgcrypto (SHA-256)
CREATE OR REPLACE FUNCTION audit_trigger_func()
RETURNS TRIGGER AS $$
DECLARE
v_previous_hash TEXT;
v_checksum TEXT;
v_data JSONB;
BEGIN
-- Obtener hash del registro anterior (cadena)
SELECT checksum INTO v_previous_hash
FROM audit_logs
ORDER BY created_at DESC LIMIT 1;
-- Construir datos para hash
v_data := jsonb_build_object(
'action', TG_OP,
'table', TG_TABLE_NAME,
'old', CASE WHEN TG_OP IN ('UPDATE','DELETE')
THEN row_to_json(OLD)::jsonb ELSE NULL END,
'new', CASE WHEN TG_OP IN ('INSERT','UPDATE')
THEN row_to_json(NEW)::jsonb ELSE NULL END,
'previous_hash', COALESCE(v_previous_hash, 'GENESIS')
);
-- Calcular checksum SHA-256
v_checksum := encode(
digest(v_data::text, 'sha256'), 'hex'
);
INSERT INTO audit_logs (
action, entity_type, entity_id,
old_data, new_data,
previous_hash, checksum
) VALUES (
TG_OP, TG_TABLE_NAME,
COALESCE(NEW.id, OLD.id),
CASE WHEN TG_OP IN ('UPDATE','DELETE')
THEN row_to_json(OLD)::jsonb END,
CASE WHEN TG_OP IN ('INSERT','UPDATE')
THEN row_to_json(NEW)::jsonb END,
COALESCE(v_previous_hash, 'GENESIS'),
v_checksum
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Verificación de la Cadena
checksum y previous_hash válidos, formando una
cadena criptográfica.
auditService.log(). Usan checksum: 'VIRTUAL_EVENT'. La verificación
los omite — no rompen la cadena.
verifyChain(orgId?, limit = 100):
1. Fetch últimos N logs ORDER BY created_at DESC
2. Filtrar solo registros forenses (checksum ≠ 'VIRTUAL_EVENT')
3. Para cada registro: verificar que previous_hash
coincida con el checksum de un registro padre
4. Tolerar forks naturales (inserciones concurrentes
con el mismo padre)
5. Retornar { healthy: boolean, details: string }
11. Edge Functions (Supabase Deno)
Las Edge Functions se ejecutan en el runtime Deno de Supabase. Se
invocan desde el frontend vía supabase.functions.invoke('nombre', { body }).
legal-ai-processor
Procesador central de IA legal. Enruta por action
a GPT-4o o text-embedding-3-small.
| Acción | Propósito | Input | Output |
|---|---|---|---|
analyze |
Análisis de documento | { text, type } | { summary, risks[], suggestions[], confidence } |
compliance_risk |
Evaluación de riesgo | { description } | { suggestedLevel, reasoning, legalCitation } |
chat |
Asistente legal Q&A | { context, messages[] } | { content: string } |
embedding |
Generar vector 1536-d | { text } | { data: [{ embedding: number[] }] } |
predict_outcome |
Predicción judicial | { materia, jurisdiccion, descripcion } | { probability, strategy, citingSentences[], riskFactor } |
Todos reciben:
{ action, body, userId, organizationId }. Modelos: gpt-4o +
text-embedding-3-small.
update-exchange-rates
Obtiene la tasa de cambio actual del BCV (Banco Central de
Venezuela). Actualiza el parámetro TASA_USD_VES_BCV en
system_parameters. Invocado desde bcvRateService.syncCurrentRate().
daily-alert-engine
Motor de alertas diarias. Escanea compliance_alerts
con status 'PENDING' y alert_date <= today. Dispara notificaciones
email/SMS y marca alertas como 'TRIGGERED'. Procesamiento principal ejecutado
client-side vía notificationService.processDailyAlerts().
12. Pipeline RAG (Retrieval-Augmented Generation)
Infraestructura
document_vectors con columna
embedding vector(1536)
text-embedding-3-small (OpenAI) — 1536 dimensiones
Función SQL — match_legal_knowledge
-- Búsqueda por similitud coseno en pgvector
CREATE FUNCTION match_legal_knowledge(
query_embedding vector(1536),
match_threshold float DEFAULT 0.65,
match_count int DEFAULT 5,
p_organization_id uuid DEFAULT NULL
) RETURNS TABLE (
id uuid, content text, metadata jsonb, similarity float
) AS $$
SELECT id, content, metadata,
1 - (embedding <=> query_embedding) AS similarity
FROM document_vectors
WHERE 1 - (embedding <=> query_embedding) > match_threshold
AND (organization_id = p_organization_id
OR organization_id = '00000000-0000-0000-0000-000000000000')
ORDER BY embedding <=> query_embedding
LIMIT match_count;
$$ LANGUAGE sql;
Corpus Legal Venezolano (Seed)
legalKnowledgeService.seedBaseKnowledge() indexa
automáticamente el siguiente corpus con org_id público (00000000-...0000):
- LDFE — Ley de Firmas Electrónicas
- LOPCYMAT — Ley de Salud Laboral
- Código Civil — Título de Contratos
- Jurisprudencia TSJ — Sentencias vinculantes
Flujo de Indexación
1. aiService.indexLegalKnowledge(entityId, type, content, orgId, metadata)
2. → aiService.generateEmbedding(content)
→ Edge Function 'legal-ai-processor' { action: 'embedding', body: { text } }
→ OpenAI text-embedding-3-small → vector float[1536]
3. → INSERT INTO document_vectors (entity_id, entity_type, content,
embedding, metadata, organization_id)
4. Disponible para búsqueda semántica vía match_legal_knowledge()
13. Funciones SQL y RPC
| Función | Parámetros | Retorno | Descripción |
|---|---|---|---|
calculate_business_days |
p_start_date, p_working_days, p_jurisdiction | DATE | Calcula fecha de vencimiento sumando días hábiles, excluyendo fines de
semana y judicial_holidays |
count_business_days |
p_start_date, p_end_date, p_jurisdiction | INTEGER | Cuenta días hábiles entre dos fechas |
generate_expediente_id |
— | TEXT | Genera ID secuencial: EXP-001, EXP-002... |
match_legal_knowledge |
query_embedding, match_threshold, match_count, p_org_id | TABLE (id, content, metadata, similarity) | Búsqueda semántica por similitud coseno en pgvector |
audit_trigger_func |
— (trigger) | TRIGGER | Auto-inserta en audit_logs con cadena SHA-256 vía
pgcrypto |
Invocadas desde el frontend con
supabase.rpc('function_name', { params }).
14. Despliegue
Netlify
[build]
command = "npm run build"
publish = "dist"
base = "/"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Security Headers
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(),
microphone=(), geolocation=()
# Assets: cache 1 año inmutable
Cache-Control: public, max-age=31536000,
immutable
Vite — Code Splitting
// vite.config.ts
export default defineConfig({
plugins: [react()],
build: {
chunkSizeWarningLimit: 600,
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom'],
'vendor-supabase': ['@supabase/supabase-js'],
'vendor-pdf': ['jspdf', 'jspdf-autotable'],
'vendor-charts': ['recharts'],
'vendor-icons': ['lucide-react'],
}
}
}
}
})
Comandos de Desarrollo
| Comando | Descripción |
|---|---|
npm run dev |
Servidor local con HMR (Vite 7) |
npm run build |
Build de producción con chunks optimizados |
npm run preview |
Preview del build de producción |
npm run lint |
ESLint con reglas React + TypeScript |
15. Variables de Entorno
Frontend (.env)
| Variable | Requerida | Descripción |
|---|---|---|
VITE_SUPABASE_URL |
Sí | URL del proyecto Supabase |
VITE_SUPABASE_ANON_KEY |
Sí | Clave pública anon de Supabase |
VITE_OPENAI_API_KEY |
Opcional | API key de OpenAI (para llamadas client-side) |
Edge Functions (Supabase Dashboard)
| Variable | Requerida | Descripción |
|---|---|---|
OPENAI_API_KEY |
Sí | Clave de OpenAI para GPT-4o y embeddings |
SUPABASE_SERVICE_ROLE_KEY |
Sí | Service role key para operaciones admin |
SUPABASE_SERVICE_ROLE_KEY ni
OPENAI_API_KEY del servidor en el frontend. Las variables VITE_* son
públicas por diseño de Vite. Las Edge Functions usan sus propias keys configuradas en el dashboard
de Supabase.