diff --git a/DDL-EXECUTION-ORDER.md b/DDL-EXECUTION-ORDER.md index 9d441ed..d9fcb21 100644 --- a/DDL-EXECUTION-ORDER.md +++ b/DDL-EXECUTION-ORDER.md @@ -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) | | 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) | -| 12 | `schemas/99-rls-policies.sql` | Politicas RLS para todos los schemas | -| 13 | `init-scripts/02-payment-terminals.sql` | Terminales de pago (opcional) | +| 12 | `schemas/11-compound-indices.sql` | Indices compound para queries frecuentes | +| 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) diff --git a/docs/JSONB-SCHEMAS.md b/docs/JSONB-SCHEMAS.md new file mode 100644 index 0000000..a1e3821 --- /dev/null +++ b/docs/JSONB-SCHEMAS.md @@ -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; + + // 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; + + // 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; + new_values?: Record; + }; + + 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* diff --git a/schemas/11-compound-indices.sql b/schemas/11-compound-indices.sql new file mode 100644 index 0000000..54c88be --- /dev/null +++ b/schemas/11-compound-indices.sql @@ -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 +-- ============================================================================