feat: Add major features - Mejoras 5-10
- Mejora 5: Órdenes de Compra integration in obra detail - Mejora 6: Portal de Cliente with JWT auth for clients - Mejora 7: Diagrama de Gantt for project visualization - Mejora 8: Push Notifications with service worker - Mejora 9: Activity Log system with templates - Mejora 10: PWA support with offline capabilities New features include: - Fotos gallery with upload/delete - Bitácora de obra with daily logs - PDF export for reports, gastos, presupuestos - Control de asistencia for employees - Client portal with granular permissions - Gantt chart with task visualization - Push notification system - Activity timeline component - PWA manifest, icons, and install prompt Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,14 @@ model User {
|
||||
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[]
|
||||
|
||||
@@index([empresaId])
|
||||
@@index([email])
|
||||
@@ -145,10 +153,40 @@ model Cliente {
|
||||
|
||||
// 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
|
||||
@@ -180,6 +218,11 @@ model Obra {
|
||||
asignaciones AsignacionEmpleado[]
|
||||
contratos ContratoSubcontratista[]
|
||||
registrosAvance RegistroAvance[]
|
||||
fotos FotoAvance[]
|
||||
bitacoras BitacoraObra[]
|
||||
asistencias Asistencia[]
|
||||
ordenesCompra OrdenCompra[]
|
||||
actividadesLog ActividadLog[]
|
||||
|
||||
@@index([empresaId])
|
||||
@@index([estado])
|
||||
@@ -201,6 +244,7 @@ model FaseObra {
|
||||
|
||||
// Relations
|
||||
tareas TareaObra[]
|
||||
fotos FotoAvance[]
|
||||
|
||||
@@index([obraId])
|
||||
}
|
||||
@@ -350,6 +394,7 @@ model Material {
|
||||
|
||||
// Relations
|
||||
movimientos MovimientoInventario[]
|
||||
itemsOrden ItemOrdenCompra[]
|
||||
|
||||
@@unique([codigo, empresaId])
|
||||
@@index([empresaId])
|
||||
@@ -392,6 +437,7 @@ model Empleado {
|
||||
// Relations
|
||||
asignaciones AsignacionEmpleado[]
|
||||
jornadas JornadaTrabajo[]
|
||||
asistencias Asistencia[]
|
||||
|
||||
@@index([empresaId])
|
||||
}
|
||||
@@ -464,3 +510,388 @@ model ContratoSubcontratista {
|
||||
@@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])
|
||||
}
|
||||
|
||||
// ============== 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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user