[ST-P2-002,ST-P2-003] feat: Add compound indices and JSONB schema documentation

- 11-compound-indices.sql: 14 compound indices for common queries
- docs/JSONB-SCHEMAS.md: 50+ TypeScript interfaces documenting JSONB fields
- DDL-EXECUTION-ORDER.md: Updated to include compound indices step

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 07:39:14 -06:00
parent 6b02d8ab08
commit dcdc15e162
3 changed files with 890 additions and 2 deletions

View File

@ -70,8 +70,9 @@ Cuando se crea una BD limpia SIN erp-core instalado previamente:
| 9 | `schemas/08-finance-schema-ddl.sql` | Schema finance (11 tablas, 20 ENUMs) | | 9 | `schemas/08-finance-schema-ddl.sql` | Schema finance (11 tablas, 20 ENUMs) |
| 10 | `schemas/09-assets-schema-ddl.sql` | Schema assets (11 tablas, 9 ENUMs) | | 10 | `schemas/09-assets-schema-ddl.sql` | Schema assets (11 tablas, 9 ENUMs) |
| 11 | `schemas/10-documents-schema-ddl.sql` | Schema documents (11 tablas, 8 ENUMs) | | 11 | `schemas/10-documents-schema-ddl.sql` | Schema documents (11 tablas, 8 ENUMs) |
| 12 | `schemas/99-rls-policies.sql` | Politicas RLS para todos los schemas | | 12 | `schemas/11-compound-indices.sql` | Indices compound para queries frecuentes |
| 13 | `init-scripts/02-payment-terminals.sql` | Terminales de pago (opcional) | | 13 | `schemas/99-rls-policies.sql` | Politicas RLS para todos los schemas |
| 14 | `init-scripts/02-payment-terminals.sql` | Terminales de pago (opcional) |
### Escenario B: Base de Datos con erp-core (Recomendado) ### Escenario B: Base de Datos con erp-core (Recomendado)

781
docs/JSONB-SCHEMAS.md Normal file
View File

@ -0,0 +1,781 @@
# JSONB Schemas Documentation - ERP Construccion
**Version:** 1.0.0
**Fecha:** 2026-02-03
**Proyecto:** erp-construccion
---
## Descripcion
Este documento define los schemas esperados para los campos JSONB en la base de datos de ERP Construccion. Los campos JSONB almacenan datos flexibles/extensibles sin estructura rigida de tablas relacionales.
---
## Convenciones
### Notacion
- `{}` - Objeto JSON
- `[]` - Array JSON
- `?` - Campo opcional
- `type` - Tipo esperado (string, number, boolean, date)
### Validacion
Los schemas deben validarse en el backend antes de insertar datos. Se recomienda usar JSON Schema o Zod para validacion.
---
## Schema: construction
### fraccionamientos.metadata
```typescript
interface FraccionamientoMetadata {
// Informacion legal
escritura_publica?: string;
notario?: string;
numero_registro_publico?: string;
fecha_registro?: string; // YYYY-MM-DD
// Permisos
permiso_uso_suelo?: string;
fecha_permiso_uso_suelo?: string;
permiso_construccion?: string;
fecha_permiso_construccion?: string;
// Urbanizacion
porcentaje_urbanizacion?: number; // 0-100
servicios_instalados?: string[]; // ['agua', 'luz', 'drenaje', 'gas']
// Contacto
contacto_nombre?: string;
contacto_telefono?: string;
contacto_email?: string;
// Financiamiento
credito_puente?: boolean;
banco_financiador?: string;
monto_credito?: number;
// Seguros
poliza_rc?: string;
vigencia_poliza_rc?: string;
// Custom fields
[key: string]: any;
}
```
### prototipos.metadata
```typescript
interface PrototipoMetadata {
// Especificaciones tecnicas
estructura?: string; // 'concreto', 'acero', 'mixta'
acabados?: {
pisos?: string;
muros?: string;
plafones?: string;
cocina?: string;
banos?: string;
};
// Equipamiento incluido
equipamiento?: string[]; // ['calentador', 'tinaco', 'hidroneumatico']
// Renders y materiales de venta
fotos_galeria?: string[]; // URLs
video_recorrido?: string;
brochure_pdf?: string;
// Precios por concepto
desglose_precio?: {
terreno?: number;
construccion?: number;
equipamiento?: number;
gastos_escrituracion?: number;
};
// Custom fields
[key: string]: any;
}
```
---
## Schema: finance
### bank_accounts.metadata
```typescript
interface BankAccountMetadata {
// Acceso banca electronica
banca_electronica?: {
url?: string;
usuario_consulta?: string;
tiene_transferencias?: boolean;
limite_diario?: number;
limite_mensual?: number;
};
// Documentos
contrato_url?: string;
estado_cuenta_url?: string;
// Autorizadores
firmas_autorizadas?: Array<{
nombre: string;
cargo: string;
limite_firma?: number;
}>;
// Integraciones
api_config?: {
provider?: string;
api_key_ref?: string; // Referencia a secret manager
webhook_url?: string;
};
// Custom fields
[key: string]: any;
}
```
### bank_movements.raw_data
```typescript
interface BankMovementRawData {
// Datos originales del banco
referencia_banco?: string;
concepto_banco?: string;
tipo_movimiento_banco?: string;
codigo_movimiento?: string;
// Datos de importacion
imported_from?: 'api' | 'csv' | 'pdf' | 'manual';
import_date?: string;
original_row?: Record<string, any>;
// Matching info
auto_matched?: boolean;
match_score?: number;
match_candidates?: string[]; // IDs de posibles matches
}
```
### bank_reconciliation.reconciliation_items
```typescript
interface ReconciliationItems {
matched: Array<{
bank_movement_id: string;
erp_movement_id: string;
match_date: string;
match_type: 'auto' | 'manual';
amount: number;
}>;
unmatched_bank: Array<{
bank_movement_id: string;
amount: number;
date: string;
description: string;
}>;
unmatched_erp: Array<{
erp_movement_id: string;
amount: number;
date: string;
description: string;
}>;
adjustments: Array<{
type: 'charge' | 'commission' | 'interest' | 'other';
amount: number;
description: string;
date: string;
}>;
}
```
### cash_flow_projection.income_breakdown / expense_breakdown
```typescript
interface CashFlowBreakdown {
categories: Array<{
category: string;
subcategories: Array<{
name: string;
projected: number;
actual?: number;
variance?: number;
notes?: string;
}>;
total_projected: number;
total_actual?: number;
}>;
total: number;
currency: string;
}
```
---
## Schema: hse
### programa_seguridad.metas
```typescript
interface ProgramaSeguridad_Metas {
indicadores: Array<{
indicador_id: string;
meta_anual: number;
metas_mensuales?: number[]; // 12 valores
tolerancia_inferior?: number;
tolerancia_superior?: number;
}>;
objetivos: Array<{
descripcion: string;
fecha_limite: string;
responsable_id?: string;
estado?: 'pendiente' | 'en_progreso' | 'completado';
evidencia_url?: string;
}>;
}
```
### documento_stps.datos_documento
```typescript
interface DocumentoSTPS_DatosDocumento {
// Para DC-3 (Constancias)
dc3?: {
folio_constancia: string;
nombre_curso: string;
duracion_horas: number;
fecha_capacitacion: string;
nombre_instructor: string;
registro_stps_instructor: string;
agente_capacitador: string;
registro_agente: string;
};
// Para ST-7 (Dictamen)
st7?: {
numero_dictamen: string;
unidad_verificacion: string;
fecha_verificacion: string;
resultado: 'favorable' | 'condicionado' | 'negativo';
vigencia_hasta: string;
observaciones?: string[];
};
// Campos genericos
[key: string]: any;
}
```
### tipo_permiso_trabajo.documentos_requeridos
```typescript
interface PermisoTrabajo_DocumentosRequeridos {
documentos: Array<{
nombre: string;
descripcion?: string;
obligatorio: boolean;
formato_aceptado?: string[]; // ['pdf', 'jpg', 'png']
plantilla_url?: string;
}>;
}
```
### tipo_permiso_trabajo.requisitos_personal
```typescript
interface PermisoTrabajo_RequisitosPersonal {
roles: Array<{
rol: 'ejecutor' | 'supervisor' | 'vigia' | 'operador';
cantidad_minima: number;
capacitaciones_requeridas?: string[]; // IDs de capacitaciones
certificaciones_requeridas?: string[];
}>;
epp_obligatorio?: string[]; // IDs de EPP del catalogo
examen_medico_requerido?: boolean;
}
```
### tipo_permiso_trabajo.equipos_requeridos
```typescript
interface PermisoTrabajo_EquiposRequeridos {
equipos: Array<{
descripcion: string;
cantidad: number;
especificaciones?: string;
certificacion_requerida?: boolean;
}>;
herramientas: Array<{
descripcion: string;
cantidad: number;
}>;
}
```
---
## Schema: assets
### equipment.specifications
```typescript
interface EquipmentSpecifications {
// Motor
motor?: {
tipo?: string;
marca?: string;
modelo?: string;
potencia_hp?: number;
combustible?: string;
capacidad_tanque_lt?: number;
};
// Dimensiones
dimensiones?: {
largo_m?: number;
ancho_m?: number;
alto_m?: number;
peso_kg?: number;
};
// Capacidades
capacidades?: {
carga_max_kg?: number;
volumen_m3?: number;
alcance_m?: number;
};
// Llantas/Rodamiento
rodamiento?: {
tipo?: string; // 'neumatico', 'orugas', 'mixto'
cantidad?: number;
medida?: string;
};
// Custom fields
[key: string]: any;
}
```
### maintenance_plan.activities
```typescript
interface MaintenancePlanActivities {
activities: Array<{
sequence: number;
name: string;
description?: string;
duration_minutes?: number;
skill_required?: string;
checklist: Array<{
item: string;
type: 'check' | 'measurement' | 'photo';
required: boolean;
expected_value?: string | number;
tolerance?: number;
}>;
safety_notes?: string[];
}>;
}
```
### maintenance_plan.required_parts / required_tools
```typescript
interface MaintenancePlanParts {
parts: Array<{
part_number?: string;
description: string;
quantity: number;
unit: string;
estimated_cost?: number;
supplier_id?: string;
}>;
}
interface MaintenancePlanTools {
tools: Array<{
name: string;
specification?: string;
quantity: number;
}>;
}
```
### maintenance_order.activities_checklist
```typescript
interface MaintenanceOrderChecklist {
completed_activities: Array<{
activity_index: number;
completed_at: string;
completed_by: string;
checklist_results: Array<{
item_index: number;
passed: boolean;
value?: string | number;
photo_url?: string;
notes?: string;
}>;
}>;
overall_result: 'pass' | 'fail' | 'partial';
technician_notes?: string;
}
```
### maintenance_order.photos_before / photos_after / documents
```typescript
interface MaintenanceOrderPhotos {
photos: Array<{
url: string;
description?: string;
taken_at?: string;
taken_by?: string;
location?: string;
}>;
}
interface MaintenanceOrderDocuments {
documents: Array<{
url: string;
name: string;
type: 'report' | 'invoice' | 'warranty' | 'other';
uploaded_at?: string;
uploaded_by?: string;
}>;
}
```
### equipment_telemetry.raw_data
```typescript
interface EquipmentTelemetryRawData {
// Datos crudos del dispositivo
device_id: string;
timestamp: string;
signal_strength?: number;
battery_level?: number;
// Payload del sensor
payload: Record<string, any>;
// Procesamiento
processed?: boolean;
processing_errors?: string[];
}
```
---
## Schema: documents
### document_types.metadata
```typescript
interface DocumentTypeMetadata {
// Reglas de nomenclatura
naming_convention?: {
prefix?: string;
separator?: string;
include_date?: boolean;
date_format?: string;
sequence_length?: number;
};
// Restricciones
max_file_size_mb?: number;
allowed_extensions?: string[];
max_versions?: number;
// Workflow por defecto
default_workflow_id?: string;
auto_start_workflow?: boolean;
// Retencion
retention_years?: number;
auto_archive?: boolean;
}
```
### documents.custom_fields
```typescript
interface DocumentCustomFields {
// Campos dinamicos segun tipo de documento
[field_name: string]: {
value: any;
label?: string;
type?: 'text' | 'number' | 'date' | 'select' | 'boolean';
};
}
```
### document_versions.extracted_metadata
```typescript
interface DocumentExtractedMetadata {
// Metadatos extraidos del archivo
author?: string;
title?: string;
subject?: string;
keywords?: string[];
creation_date?: string;
modification_date?: string;
// Para PDFs
pdf_version?: string;
page_count?: number;
is_encrypted?: boolean;
has_forms?: boolean;
// Para imagenes
width?: number;
height?: number;
color_space?: string;
dpi?: number;
// GPS si disponible
gps_coordinates?: {
latitude: number;
longitude: number;
};
// OCR
ocr_text?: string;
ocr_confidence?: number;
}
```
### approval_workflows.steps
```typescript
interface ApprovalWorkflowSteps {
steps: Array<{
step_number: number;
name: string;
type: 'single' | 'any' | 'all' | 'percentage';
// Aprobadores
approvers: Array<{
type: 'user' | 'role' | 'department';
id: string;
}>;
// Para type='percentage'
required_percentage?: number;
// Para type='any' o 'all'
required_count?: number;
// Tiempo limite
deadline_hours?: number;
reminder_hours?: number;
escalation_user_id?: string;
// Acciones permitidas
allow_comments?: boolean;
allow_attachments?: boolean;
require_signature?: boolean;
}>;
}
```
### annotations.style
```typescript
interface AnnotationStyle {
// Para todas las anotaciones
color?: string; // Hex color
opacity?: number; // 0-1
// Para lineas
stroke_width?: number;
stroke_style?: 'solid' | 'dashed' | 'dotted';
arrow_start?: boolean;
arrow_end?: boolean;
// Para formas
fill_color?: string;
fill_opacity?: number;
border_radius?: number;
// Para texto
font_family?: string;
font_size?: number;
font_weight?: 'normal' | 'bold';
text_align?: 'left' | 'center' | 'right';
text_decoration?: 'none' | 'underline' | 'strikethrough';
// Para stamps
stamp_text?: string;
stamp_rotation?: number;
}
```
### document_audit.action_details
```typescript
interface DocumentAuditActionDetails {
// Contexto de la accion
ip_address?: string;
user_agent?: string;
session_id?: string;
// Datos especificos por tipo de accion
view?: {
page_viewed?: number;
duration_seconds?: number;
};
download?: {
format?: string;
};
edit?: {
fields_changed?: string[];
previous_values?: Record<string, any>;
new_values?: Record<string, any>;
};
share?: {
shared_with?: string[];
permissions?: string[];
expires_at?: string;
};
workflow?: {
action?: 'approve' | 'reject' | 'request_changes';
step_number?: number;
comments?: string;
};
}
```
---
## Schema: infonavit
### derechohabiente.metadata
```typescript
interface DerechohabienteMetadata {
// Datos adicionales INFONAVIT
puntos_infonavit?: number;
monto_credito_aprobado?: number;
subsidio_aprobado?: number;
// Documentos
identificacion_url?: string;
constancia_situacion_fiscal_url?: string;
comprobante_domicilio_url?: string;
acta_nacimiento_url?: string;
// Historial crediticio
score_buro?: number;
fecha_consulta_buro?: string;
// Referencias
referencias_personales?: Array<{
nombre: string;
telefono: string;
parentesco: string;
}>;
// Custom fields
[key: string]: any;
}
```
### lote_infonavit.metadata
```typescript
interface LoteInfonavitMetadata {
// Especificaciones vivienda
tipo_vivienda?: string;
valor_vivienda?: number;
ecotecnologias?: string[];
// Avaluo
numero_avaluo?: string;
fecha_avaluo?: string;
valor_avaluo?: number;
valuador?: string;
// ROC
roc_asignado?: string;
fecha_asignacion_roc?: string;
// Custom fields
[key: string]: any;
}
```
---
## Validacion con Zod (Ejemplo)
```typescript
import { z } from 'zod';
// Schema para bank_accounts.metadata
export const BankAccountMetadataSchema = z.object({
banca_electronica: z.object({
url: z.string().url().optional(),
usuario_consulta: z.string().optional(),
tiene_transferencias: z.boolean().optional(),
limite_diario: z.number().positive().optional(),
limite_mensual: z.number().positive().optional(),
}).optional(),
contrato_url: z.string().url().optional(),
estado_cuenta_url: z.string().url().optional(),
firmas_autorizadas: z.array(z.object({
nombre: z.string(),
cargo: z.string(),
limite_firma: z.number().optional(),
})).optional(),
api_config: z.object({
provider: z.string().optional(),
api_key_ref: z.string().optional(),
webhook_url: z.string().url().optional(),
}).optional(),
}).passthrough(); // Allow additional fields
// Uso
const validateMetadata = (data: unknown) => {
return BankAccountMetadataSchema.parse(data);
};
```
---
## Referencias
- `DDL-EXECUTION-ORDER.md` - Orden de ejecucion DDL
- `@ESTANDAR-DATABASE` - Estandares de base de datos
- TypeScript interfaces en backend
---
*Documentacion creada: 2026-02-03 - ST-P2-003*

View File

@ -0,0 +1,106 @@
-- ============================================================================
-- COMPOUND INDICES - Indices compuestos para queries frecuentes
-- Version: 1.0.0
-- Fecha: 2026-02-03
-- ============================================================================
-- Proposito: Optimizar queries que filtran por tenant_id + status + created_at
-- Patron comun en listados paginados con filtros de estado
-- ============================================================================
-- ============================================================================
-- SCHEMA: estimates
-- ============================================================================
-- Indice compound para estimaciones (listado por estado y fecha)
CREATE INDEX IF NOT EXISTS idx_estimaciones_tenant_status_created
ON estimates.estimaciones(tenant_id, status, created_at DESC);
-- Indice compound para generadores (listado por estado y fecha)
CREATE INDEX IF NOT EXISTS idx_generadores_tenant_status_created
ON estimates.generadores(tenant_id, status, created_at DESC);
-- Indice compound para anticipos (busqueda por tipo y fecha)
CREATE INDEX IF NOT EXISTS idx_anticipos_tenant_type_created
ON estimates.anticipos(tenant_id, advance_type, created_at DESC);
-- ============================================================================
-- SCHEMA: hse
-- ============================================================================
-- Indice compound para incidentes (listado por estado y fecha)
CREATE INDEX IF NOT EXISTS idx_incidentes_tenant_estado_created
ON hse.incidentes(tenant_id, estado, created_at DESC);
-- Indice compound para incidentes por tipo y gravedad
CREATE INDEX IF NOT EXISTS idx_incidentes_tenant_tipo_gravedad
ON hse.incidentes(tenant_id, tipo, gravedad);
-- Indice compound para incidentes por fraccionamiento
CREATE INDEX IF NOT EXISTS idx_incidentes_tenant_fracc_created
ON hse.incidentes(tenant_id, fraccionamiento_id, created_at DESC);
-- Indice compound para capacitaciones por tipo
CREATE INDEX IF NOT EXISTS idx_capacitaciones_tenant_tipo
ON hse.capacitaciones(tenant_id, tipo);
-- ============================================================================
-- SCHEMA: construction
-- ============================================================================
-- Indice compound para fraccionamientos (listado por estado y fecha)
CREATE INDEX IF NOT EXISTS idx_fracc_tenant_status_created
ON construction.fraccionamientos(tenant_id, status, created_at DESC);
-- Indice compound para lotes (busqueda por estado y fraccionamiento)
-- Nota: Necesitamos el fraccionamiento via manzana->etapa
CREATE INDEX IF NOT EXISTS idx_lotes_tenant_status_created
ON construction.lotes(tenant_id, status, created_at DESC);
-- Indice compound para etapas (listado por estado)
CREATE INDEX IF NOT EXISTS idx_etapas_tenant_status_created
ON construction.etapas(tenant_id, status, created_at DESC);
-- ============================================================================
-- SCHEMA: finance
-- ============================================================================
-- Indice compound para cuentas bancarias (busqueda por estado)
CREATE INDEX IF NOT EXISTS idx_bank_accounts_tenant_status_created
ON finance.bank_accounts(tenant_id, status, created_at DESC);
-- Indice compound para polizas contables (busqueda por tipo y fecha)
CREATE INDEX IF NOT EXISTS idx_accounting_entries_tenant_type_date
ON finance.accounting_entries(tenant_id, entry_type, entry_date DESC);
-- Indice compound para CxC (busqueda por estado)
CREATE INDEX IF NOT EXISTS idx_accounts_receivable_tenant_status_created
ON finance.accounts_receivable(tenant_id, status, created_at DESC);
-- Indice compound para CxP (busqueda por estado)
CREATE INDEX IF NOT EXISTS idx_accounts_payable_tenant_status_created
ON finance.accounts_payable(tenant_id, status, created_at DESC);
-- ============================================================================
-- SCHEMA: hr
-- ============================================================================
-- Indice compound para empleados (busqueda por estado)
CREATE INDEX IF NOT EXISTS idx_employees_tenant_status_created
ON hr.employees(tenant_id, status, created_at DESC);
-- ============================================================================
-- COMENTARIOS
-- ============================================================================
COMMENT ON INDEX estimates.idx_estimaciones_tenant_status_created IS
'Indice compound para listados de estimaciones filtrados por estado, ordenados por fecha';
COMMENT ON INDEX hse.idx_incidentes_tenant_estado_created IS
'Indice compound para listados de incidentes filtrados por estado, ordenados por fecha';
COMMENT ON INDEX construction.idx_fracc_tenant_status_created IS
'Indice compound para listados de fraccionamientos filtrados por estado, ordenados por fecha';
-- ============================================================================
-- FIN - Compound Indices
-- ============================================================================