// Prisma schema for Construction Management System generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============== ENUMS ============== enum Role { ADMIN GERENTE SUPERVISOR CONTADOR EMPLEADO } enum EstadoObra { PLANIFICACION EN_PROGRESO PAUSADA COMPLETADA CANCELADA } enum EstadoTarea { PENDIENTE EN_PROGRESO COMPLETADA BLOQUEADA } enum EstadoGasto { PENDIENTE APROBADO RECHAZADO PAGADO } enum CategoriaGasto { MATERIALES MANO_DE_OBRA EQUIPOS SUBCONTRATISTAS PERMISOS TRANSPORTE SERVICIOS OTROS } enum TipoFactura { EMITIDA RECIBIDA } enum EstadoFactura { PENDIENTE PAGADA VENCIDA CANCELADA } enum TipoMovimiento { ENTRADA SALIDA AJUSTE } enum UnidadMedida { UNIDAD METRO METRO_CUADRADO METRO_CUBICO KILOGRAMO TONELADA LITRO BOLSA PIEZA ROLLO CAJA HORA JORNADA VIAJE LOTE GLOBAL } enum CategoriaManoObra { PEON AYUDANTE OFICIAL_ALBANIL OFICIAL_FIERRERO OFICIAL_CARPINTERO OFICIAL_PLOMERO OFICIAL_ELECTRICISTA CABO MAESTRO_OBRA OPERADOR_EQUIPO } enum TipoEquipo { MAQUINARIA_PESADA MAQUINARIA_LIGERA HERRAMIENTA_ELECTRICA TRANSPORTE } enum TipoInsumoAPU { MATERIAL MANO_OBRA EQUIPO HERRAMIENTA_MENOR } // ============== MODELS ============== model User { id String @id @default(cuid()) email String @unique password String nombre String apellido String role Role @default(EMPLEADO) telefono String? activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations gastosCreados Gasto[] @relation("GastoCreador") gastosAprobados Gasto[] @relation("GastoAprobador") tareasAsignadas TareaObra[] obrasSupervision Obra[] @relation("ObraSupervisor") registrosAvance RegistroAvance[] fotosSubidas FotoAvance[] bitacorasRegistradas BitacoraObra[] asistenciasRegistradas Asistencia[] ordenesCreadas OrdenCompra[] @relation("OrdenCreador") ordenesAprobadas OrdenCompra[] @relation("OrdenAprobador") pushSubscriptions PushSubscription[] notificaciones Notificacion[] actividades ActividadLog[] avancesRegistrados AvancePartida[] @relation("AvancesRegistrados") avancesAprobados AvancePartida[] @relation("AvancesAprobados") importaciones ImportacionCatalogo[] @@index([empresaId]) @@index([email]) } model Empresa { id String @id @default(cuid()) nombre String rfc String? @unique direccion String? telefono String? email String? logo String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations usuarios User[] obras Obra[] materiales Material[] empleados Empleado[] subcontratistas Subcontratista[] clientes Cliente[] // APU Relations categoriasTrabajo CategoriaTrabajoAPU[] equiposMaquinaria EquipoMaquinaria[] apus AnalisisPrecioUnitario[] configuracionAPU ConfiguracionAPU? importaciones ImportacionCatalogo[] } model Cliente { id String @id @default(cuid()) nombre String rfc String? direccion String? telefono String? email String? empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations obras Obra[] accesos ClienteAcceso[] @@index([empresaId]) } // ============== PORTAL DE CLIENTES ============== model ClienteAcceso { id String @id @default(cuid()) email String @unique password String? // Hash de contraseña (opcional si usa token) token String? @unique // Token de acceso único tokenExpira DateTime? // Expiración del token activo Boolean @default(true) ultimoAcceso DateTime? // Permisos verFotos Boolean @default(true) verAvances Boolean @default(true) verGastos Boolean @default(false) verDocumentos Boolean @default(true) descargarPDF Boolean @default(true) // Relaciones clienteId String cliente Cliente @relation(fields: [clienteId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([clienteId]) @@index([token]) } model Obra { id String @id @default(cuid()) nombre String descripcion String? direccion String estado EstadoObra @default(PLANIFICACION) fechaInicio DateTime? fechaFinPrevista DateTime? fechaFinReal DateTime? porcentajeAvance Float @default(0) presupuestoTotal Float @default(0) gastoTotal Float @default(0) imagenPortada String? empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) clienteId String? cliente Cliente? @relation(fields: [clienteId], references: [id]) supervisorId String? supervisor User? @relation("ObraSupervisor", fields: [supervisorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations fases FaseObra[] presupuestos Presupuesto[] gastos Gasto[] facturas Factura[] movimientosInv MovimientoInventario[] asignaciones AsignacionEmpleado[] contratos ContratoSubcontratista[] registrosAvance RegistroAvance[] fotos FotoAvance[] bitacoras BitacoraObra[] asistencias Asistencia[] ordenesCompra OrdenCompra[] actividadesLog ActividadLog[] @@index([empresaId]) @@index([estado]) @@index([clienteId]) } model FaseObra { id String @id @default(cuid()) nombre String descripcion String? orden Int fechaInicio DateTime? fechaFin DateTime? porcentajeAvance Float @default(0) obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations tareas TareaObra[] fotos FotoAvance[] @@index([obraId]) } model TareaObra { id String @id @default(cuid()) nombre String descripcion String? estado EstadoTarea @default(PENDIENTE) prioridad Int @default(1) fechaInicio DateTime? fechaFin DateTime? porcentajeAvance Float @default(0) faseId String fase FaseObra @relation(fields: [faseId], references: [id], onDelete: Cascade) asignadoId String? asignado User? @relation(fields: [asignadoId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([faseId]) @@index([estado]) @@index([asignadoId]) @@index([faseId, estado]) } model RegistroAvance { id String @id @default(cuid()) descripcion String porcentaje Float fotos String[] obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) registradoPorId String registradoPor User @relation(fields: [registradoPorId], references: [id]) createdAt DateTime @default(now()) @@index([obraId]) @@index([registradoPorId]) @@index([obraId, createdAt]) } model Presupuesto { id String @id @default(cuid()) nombre String descripcion String? version Int @default(1) total Float @default(0) aprobado Boolean @default(false) obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations partidas PartidaPresupuesto[] estimaciones Estimacion[] @@index([obraId]) } model PartidaPresupuesto { id String @id @default(cuid()) codigo String descripcion String unidad UnidadMedida cantidad Float precioUnitario Float total Float categoria CategoriaGasto presupuestoId String presupuesto Presupuesto @relation(fields: [presupuestoId], references: [id], onDelete: Cascade) apuId String? apu AnalisisPrecioUnitario? @relation(fields: [apuId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations gastos Gasto[] avances AvancePartida[] estimacionPartidas EstimacionPartida[] @@index([presupuestoId]) @@index([apuId]) } model AvancePartida { id String @id @default(cuid()) fecha DateTime @default(now()) cantidadEjecutada Float // Cantidad ejecutada en este registro cantidadAcumulada Float // Cantidad acumulada hasta este registro porcentajeAvance Float // Porcentaje de avance (0-100) montoEjecutado Float // Monto correspondiente a este avance montoAcumulado Float // Monto acumulado hasta este registro notas String? fotos String[] partidaId String partida PartidaPresupuesto @relation(fields: [partidaId], references: [id], onDelete: Cascade) registradoPorId String registradoPor User @relation("AvancesRegistrados", fields: [registradoPorId], references: [id]) aprobadoPorId String? aprobadoPor User? @relation("AvancesAprobados", fields: [aprobadoPorId], references: [id]) aprobado Boolean @default(false) fechaAprobacion DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([partidaId]) @@index([registradoPorId]) @@index([fecha]) } model Gasto { id String @id @default(cuid()) concepto String descripcion String? monto Float fecha DateTime categoria CategoriaGasto estado EstadoGasto @default(PENDIENTE) comprobante String? notas String? obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) partidaId String? partida PartidaPresupuesto? @relation(fields: [partidaId], references: [id]) creadoPorId String creadoPor User @relation("GastoCreador", fields: [creadoPorId], references: [id]) aprobadoPorId String? aprobadoPor User? @relation("GastoAprobador", fields: [aprobadoPorId], references: [id]) fechaAprobacion DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([obraId]) @@index([estado]) @@index([categoria]) @@index([fecha]) } model Factura { id String @id @default(cuid()) numero String tipo TipoFactura concepto String monto Float iva Float @default(0) total Float fechaEmision DateTime fechaVencimiento DateTime? fechaPago DateTime? estado EstadoFactura @default(PENDIENTE) archivo String? obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) proveedorNombre String? proveedorRfc String? clienteNombre String? clienteRfc String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([obraId]) @@index([tipo]) @@index([estado]) } model Material { id String @id @default(cuid()) codigo String nombre String descripcion String? unidad UnidadMedida precioUnitario Float stockMinimo Float @default(0) stockActual Float @default(0) ubicacion String? activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations movimientos MovimientoInventario[] itemsOrden ItemOrdenCompra[] insumosAPU InsumoAPU[] @@unique([codigo, empresaId]) @@index([empresaId]) @@index([nombre]) @@index([activo, empresaId]) } model MovimientoInventario { id String @id @default(cuid()) tipo TipoMovimiento cantidad Float motivo String? materialId String material Material @relation(fields: [materialId], references: [id]) obraId String? obra Obra? @relation(fields: [obraId], references: [id]) createdAt DateTime @default(now()) @@index([materialId]) @@index([obraId]) @@index([tipo]) } model Empleado { id String @id @default(cuid()) nombre String apellido String documento String? telefono String? email String? puesto String salarioBase Float? fechaIngreso DateTime fechaBaja DateTime? activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations asignaciones AsignacionEmpleado[] jornadas JornadaTrabajo[] asistencias Asistencia[] @@index([empresaId]) } model AsignacionEmpleado { id String @id @default(cuid()) fechaInicio DateTime fechaFin DateTime? activo Boolean @default(true) empleadoId String empleado Empleado @relation(fields: [empleadoId], references: [id]) obraId String obra Obra @relation(fields: [obraId], references: [id]) createdAt DateTime @default(now()) @@index([empleadoId]) @@index([obraId]) } model JornadaTrabajo { id String @id @default(cuid()) fecha DateTime horasRegular Float @default(0) horasExtra Float @default(0) notas String? empleadoId String empleado Empleado @relation(fields: [empleadoId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([empleadoId]) @@index([fecha]) @@index([empleadoId, fecha]) } model Subcontratista { id String @id @default(cuid()) nombre String rfc String? especialidad String telefono String? email String? direccion String? activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations contratos ContratoSubcontratista[] @@index([empresaId]) } model ContratoSubcontratista { id String @id @default(cuid()) descripcion String montoContratado Float montoPagado Float @default(0) fechaInicio DateTime fechaFin DateTime? estado String @default("ACTIVO") subcontratistaId String subcontratista Subcontratista @relation(fields: [subcontratistaId], references: [id]) obraId String obra Obra @relation(fields: [obraId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([subcontratistaId]) @@index([obraId]) } // ============== ÓRDENES DE COMPRA ============== enum EstadoOrdenCompra { BORRADOR PENDIENTE APROBADA ENVIADA RECIBIDA_PARCIAL RECIBIDA CANCELADA } enum PrioridadOrden { BAJA NORMAL ALTA URGENTE } model OrdenCompra { id String @id @default(cuid()) numero String // Número de orden (OC-001, etc.) // Estado y prioridad estado EstadoOrdenCompra @default(BORRADOR) prioridad PrioridadOrden @default(NORMAL) // Fechas fechaEmision DateTime @default(now()) fechaRequerida DateTime? // Fecha en que se necesitan los materiales fechaAprobacion DateTime? fechaEnvio DateTime? fechaRecepcion DateTime? // Proveedor proveedorNombre String proveedorRfc String? proveedorContacto String? proveedorTelefono String? proveedorEmail String? proveedorDireccion String? // Totales subtotal Float @default(0) descuento Float @default(0) iva Float @default(0) total Float @default(0) // Condiciones condicionesPago String? // Ej: "Contado", "Crédito 30 días" tiempoEntrega String? // Ej: "3-5 días hábiles" lugarEntrega String? // Dirección de entrega // Notas notas String? @db.Text notasInternas String? @db.Text // Relaciones obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) creadoPorId String creadoPor User @relation("OrdenCreador", fields: [creadoPorId], references: [id]) aprobadoPorId String? aprobadoPor User? @relation("OrdenAprobador", fields: [aprobadoPorId], references: [id]) // Items items ItemOrdenCompra[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([numero, obraId]) @@index([obraId]) @@index([estado]) @@index([fechaEmision]) } model ItemOrdenCompra { id String @id @default(cuid()) // Descripción del item codigo String? // Código del material descripcion String unidad UnidadMedida // Cantidades cantidad Float cantidadRecibida Float @default(0) // Precios precioUnitario Float descuento Float @default(0) subtotal Float // Relaciones ordenId String orden OrdenCompra @relation(fields: [ordenId], references: [id], onDelete: Cascade) materialId String? // Opcional: vincular con catálogo de materiales material Material? @relation(fields: [materialId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([ordenId]) @@index([materialId]) } // ============== CONTROL DE ASISTENCIA ============== enum TipoAsistencia { PRESENTE AUSENTE RETARDO PERMISO INCAPACIDAD VACACIONES } model Asistencia { id String @id @default(cuid()) fecha DateTime @db.Date // Estado de asistencia tipo TipoAsistencia @default(PRESENTE) // Registro de entrada horaEntrada DateTime? latitudEntrada Float? longitudEntrada Float? // Registro de salida horaSalida DateTime? latitudSalida Float? longitudSalida Float? // Horas trabajadas (calculadas) horasTrabajadas Float? horasExtra Float @default(0) // Notas y observaciones notas String? motivoAusencia String? // Relaciones empleadoId String empleado Empleado @relation(fields: [empleadoId], references: [id], onDelete: Cascade) obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) registradoPorId String registradoPor User @relation(fields: [registradoPorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([empleadoId, obraId, fecha]) @@index([empleadoId]) @@index([obraId]) @@index([fecha]) @@index([tipo]) } // ============== BITÁCORA DE OBRA ============== enum CondicionClima { SOLEADO NUBLADO PARCIALMENTE_NUBLADO LLUVIA_LIGERA LLUVIA_FUERTE TORMENTA VIENTO_FUERTE FRIO_EXTREMO CALOR_EXTREMO } model BitacoraObra { id String @id @default(cuid()) fecha DateTime @db.Date // Condiciones climáticas clima CondicionClima temperaturaMin Float? temperaturaMax Float? condicionesExtra String? // Notas adicionales del clima // Personal en obra personalPropio Int @default(0) personalSubcontrato Int @default(0) personalDetalle String? // Descripción del personal // Actividades del día actividadesRealizadas String @db.Text actividadesPendientes String? @db.Text // Materiales materialesUtilizados String? @db.Text materialesRecibidos String? @db.Text // Equipo y maquinaria equipoUtilizado String? @db.Text // Incidentes y observaciones incidentes String? @db.Text observaciones String? @db.Text // Seguridad incidentesSeguridad String? @db.Text platicaSeguridad Boolean @default(false) temaSeguridad String? // Visitas visitasInspeccion String? @db.Text // Relaciones obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) registradoPorId String registradoPor User @relation(fields: [registradoPorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([obraId, fecha]) @@index([obraId]) @@index([fecha]) } // ============== FOTOS DE AVANCE ============== model FotoAvance { id String @id @default(cuid()) url String // Ruta del archivo thumbnail String? // Ruta de la miniatura titulo String? descripcion String? fechaCaptura DateTime @default(now()) // Geolocalización latitud Float? longitud Float? direccionGeo String? // Dirección obtenida por geocoding // Metadatos tamanio Int? // Tamaño en bytes tipo String? // MIME type (image/jpeg, etc.) ancho Int? // Width en pixels alto Int? // Height en pixels // Relaciones obraId String obra Obra @relation(fields: [obraId], references: [id], onDelete: Cascade) faseId String? fase FaseObra? @relation(fields: [faseId], references: [id]) subidoPorId String subidoPor User @relation(fields: [subidoPorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([obraId]) @@index([faseId]) @@index([fechaCaptura]) @@index([subidoPorId]) @@index([obraId, fechaCaptura]) } // ============== NOTIFICACIONES PUSH ============== enum TipoNotificacion { TAREA_ASIGNADA TAREA_COMPLETADA GASTO_PENDIENTE GASTO_APROBADO ORDEN_APROBADA AVANCE_REGISTRADO RECORDATORIO ALERTA_INVENTARIO GENERAL } model PushSubscription { id String @id @default(cuid()) endpoint String @unique p256dh String auth String activo Boolean @default(true) // Preferencias de notificación notifyTareas Boolean @default(true) notifyGastos Boolean @default(true) notifyOrdenes Boolean @default(true) notifyAvances Boolean @default(true) notifyAlertas Boolean @default(true) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) } model Notificacion { id String @id @default(cuid()) tipo TipoNotificacion titulo String mensaje String url String? // URL para navegar al hacer clic leida Boolean @default(false) enviada Boolean @default(false) // Datos adicionales en JSON metadata String? @db.Text userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@index([userId]) @@index([leida]) @@index([createdAt]) } // ============== LOG DE ACTIVIDADES ============== enum TipoActividad { OBRA_CREADA OBRA_ACTUALIZADA OBRA_ESTADO_CAMBIADO FASE_CREADA TAREA_CREADA TAREA_ASIGNADA TAREA_COMPLETADA TAREA_ESTADO_CAMBIADO GASTO_CREADO GASTO_APROBADO GASTO_RECHAZADO ORDEN_CREADA ORDEN_APROBADA ORDEN_ENVIADA ORDEN_RECIBIDA AVANCE_REGISTRADO FOTO_SUBIDA BITACORA_REGISTRADA MATERIAL_MOVIMIENTO USUARIO_ASIGNADO COMENTARIO_AGREGADO DOCUMENTO_SUBIDO } model ActividadLog { id String @id @default(cuid()) tipo TipoActividad descripcion String detalles String? @db.Text // JSON con datos adicionales // Entidad afectada entidadTipo String? // "obra", "tarea", "gasto", etc. entidadId String? entidadNombre String? // Contexto obraId String? obra Obra? @relation(fields: [obraId], references: [id], onDelete: SetNull) // Usuario que realizó la acción userId String? user User? @relation(fields: [userId], references: [id], onDelete: SetNull) // Empresa para filtrar empresaId String // Metadatos de IP/dispositivo (opcional) ipAddress String? userAgent String? createdAt DateTime @default(now()) @@index([obraId]) @@index([userId]) @@index([empresaId]) @@index([tipo]) @@index([createdAt]) @@index([obraId, createdAt]) @@index([empresaId, createdAt]) } // ============== ANÁLISIS DE PRECIOS UNITARIOS (APU) ============== model CategoriaTrabajoAPU { id String @id @default(cuid()) codigo String nombre String categoria CategoriaManoObra salarioDiario Float factorIMSS Float @default(0.2675) factorINFONAVIT Float @default(0.05) factorRetiro Float @default(0.02) factorVacaciones Float @default(0.0411) factorPrimaVac Float @default(0.0103) factorAguinaldo Float @default(0.0411) factorSalarioReal Float // Calculado: 1 + suma de factores salarioReal Float // salarioDiario * FSR activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations insumosAPU InsumoAPU[] @@unique([codigo, empresaId]) @@index([empresaId]) @@index([categoria]) } model EquipoMaquinaria { id String @id @default(cuid()) codigo String nombre String tipo TipoEquipo valorAdquisicion Float vidaUtilHoras Float valorRescate Float @default(0) consumoCombustible Float? // Litros por hora precioCombustible Float? // Precio por litro factorMantenimiento Float @default(0.60) costoOperador Float? // Costo por hora del operador costoHorario Float // Calculado activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations insumosAPU InsumoAPU[] @@unique([codigo, empresaId]) @@index([empresaId]) @@index([tipo]) } model AnalisisPrecioUnitario { id String @id @default(cuid()) codigo String descripcion String unidad UnidadMedida rendimientoDiario Float? // Cantidad de unidades por jornada costoMateriales Float @default(0) costoManoObra Float @default(0) costoEquipo Float @default(0) costoHerramienta Float @default(0) costoDirecto Float @default(0) porcentajeIndirectos Float @default(0) costoIndirectos Float @default(0) porcentajeUtilidad Float @default(0) costoUtilidad Float @default(0) precioUnitario Float @default(0) activo Boolean @default(true) empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations insumos InsumoAPU[] partidas PartidaPresupuesto[] @@unique([codigo, empresaId]) @@index([empresaId]) @@index([unidad]) } model InsumoAPU { id String @id @default(cuid()) tipo TipoInsumoAPU descripcion String unidad UnidadMedida cantidad Float desperdicio Float @default(0) // Porcentaje cantidadConDesperdicio Float rendimiento Float? // Para mano de obra: unidades por jornada precioUnitario Float importe Float apuId String apu AnalisisPrecioUnitario @relation(fields: [apuId], references: [id], onDelete: Cascade) materialId String? material Material? @relation(fields: [materialId], references: [id]) categoriaManoObraId String? categoriaManoObra CategoriaTrabajoAPU? @relation(fields: [categoriaManoObraId], references: [id]) equipoId String? equipo EquipoMaquinaria? @relation(fields: [equipoId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([apuId]) @@index([tipo]) @@index([materialId]) @@index([categoriaManoObraId]) @@index([equipoId]) } model ConfiguracionAPU { id String @id @default(cuid()) porcentajeHerramientaMenor Float @default(3) porcentajeIndirectos Float @default(8) porcentajeUtilidad Float @default(10) empresaId String @unique empresa Empresa @relation(fields: [empresaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ============== ESTIMACIONES ============== enum EstadoEstimacion { BORRADOR ENVIADA EN_REVISION APROBADA RECHAZADA PAGADA } model Estimacion { id String @id @default(cuid()) numero Int // Número de estimación (1, 2, 3...) periodo String // Descripción del periodo (ej: "15-31 Enero 2024") fechaInicio DateTime // Fecha inicio del periodo fechaFin DateTime // Fecha fin del periodo fechaEmision DateTime @default(now()) fechaEnvio DateTime? fechaAprobacion DateTime? estado EstadoEstimacion @default(BORRADOR) // Totales importeEjecutado Float @default(0) // Monto ejecutado este periodo importeAcumulado Float @default(0) // Monto acumulado hasta esta estimación importeAnterior Float @default(0) // Monto de estimaciones anteriores amortizacion Float @default(0) // Amortización de anticipo retencion Float @default(0) // Retención (% del contrato) porcentajeRetencion Float @default(5) // % de retención deduccionesVarias Float @default(0) // Otras deducciones importeNeto Float @default(0) // Importe a pagar // IVA subtotal Float @default(0) iva Float @default(0) porcentajeIVA Float @default(16) total Float @default(0) // Notas y observaciones observaciones String? @db.Text motivoRechazo String? // Relaciones presupuestoId String presupuesto Presupuesto @relation(fields: [presupuestoId], references: [id], onDelete: Cascade) partidas EstimacionPartida[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([presupuestoId, numero]) @@index([presupuestoId]) @@index([estado]) @@index([fechaEmision]) } model EstimacionPartida { id String @id @default(cuid()) // Cantidades cantidadContrato Float // Cantidad original del presupuesto cantidadAnterior Float @default(0) // Ejecutado en estimaciones previas cantidadEstimacion Float // Ejecutado en esta estimación cantidadAcumulada Float // Anterior + Esta estimación cantidadPendiente Float // Contrato - Acumulada // Importes precioUnitario Float importeAnterior Float @default(0) importeEstimacion Float // cantidadEstimacion * precioUnitario importeAcumulado Float // Porcentajes porcentajeAnterior Float @default(0) porcentajeEstimacion Float porcentajeAcumulado Float // Notas para esta partida notas String? // Relaciones estimacionId String estimacion Estimacion @relation(fields: [estimacionId], references: [id], onDelete: Cascade) partidaId String partida PartidaPresupuesto @relation(fields: [partidaId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([estimacionId, partidaId]) @@index([estimacionId]) @@index([partidaId]) } // ============== IMPORTACIÓN DE CATÁLOGOS ============== enum TipoImportacion { MATERIALES MANO_OBRA EQUIPOS } enum EstadoImportacion { PENDIENTE PROCESANDO COMPLETADA ERROR } model ImportacionCatalogo { id String @id @default(cuid()) tipo TipoImportacion nombreArchivo String estado EstadoImportacion @default(PENDIENTE) registrosTotal Int @default(0) registrosCreados Int @default(0) registrosActualizados Int @default(0) registrosError Int @default(0) errores String? // JSON con detalles de errores fuenteDatos String? // BIMSA, Proveedor, Manual, etc. empresaId String empresa Empresa @relation(fields: [empresaId], references: [id]) creadoPorId String creadoPor User @relation(fields: [creadoPorId], references: [id]) createdAt DateTime @default(now()) completadoAt DateTime? @@index([empresaId]) @@index([tipo]) @@index([estado]) }