Registro y elevación de componentes base (ADN, Scripts, Servicios)

This commit is contained in:
Ricardo Monla
2026-02-15 20:37:53 -03:00
parent 9e9bf62c68
commit c865a7772b
25279 changed files with 3354766 additions and 0 deletions
@@ -0,0 +1,135 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface User {
id: number
dtic_id: string
first_name: string
last_name: string
email: string
role: 'admin' | 'technician' | 'viewer'
department: string
}
interface AuthState {
user: User | null
token: string | null
isAuthenticated: boolean
isLoading: boolean
login: (email: string, password: string) => Promise<void>
logout: () => void
checkAuth: () => Promise<void>
refreshToken: () => Promise<void>
}
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
login: async (email: string, password: string) => {
set({ isLoading: true })
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
})
const data = await response.json()
if (!data.success) {
throw new Error(data.message || 'Error en el inicio de sesión')
}
console.log('AuthStore login: setting user and token', { user: data.data.user, token: !!data.data.token })
set({
user: data.data.user,
token: data.data.token,
isAuthenticated: true,
isLoading: false,
})
} catch (error) {
set({ isLoading: false })
throw error
}
},
logout: () => {
set({
user: null,
token: null,
isAuthenticated: false,
})
},
checkAuth: async () => {
const { token } = get()
console.log('AuthStore checkAuth: token present?', !!token)
if (!token) return
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`,
},
})
if (response.ok) {
const data = await response.json()
console.log('AuthStore checkAuth: user set from /me', { user: data.data.user })
set({
user: data.data.user,
isAuthenticated: true,
})
} else {
console.log('AuthStore checkAuth: token invalid, logging out')
// Token inválido, hacer logout
get().logout()
}
} catch (error) {
console.error('Error checking auth:', error)
get().logout()
}
},
refreshToken: async () => {
const { token } = get()
if (!token) return
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
})
if (response.ok) {
const data = await response.json()
set({ token: data.data.token })
} else {
get().logout()
}
} catch (error) {
console.error('Error refreshing token:', error)
get().logout()
}
},
}),
{
name: 'auth-storage',
partialize: (state) => ({
user: state.user,
token: state.token,
isAuthenticated: state.isAuthenticated,
}),
}
)
)
@@ -0,0 +1,268 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
export interface EntityConfig {
name: string
api: {
endpoint: string
methods: Record<string, string>
}
fields: any[]
table: {
columns: any[]
}
filters: any[]
actions: any[]
stats: any[]
modals: any[]
}
export interface GenericEntityState {
entities: any[]
loading: boolean
error: string | null
pagination: {
page: number
pages: number
total: number
per_page: number
} | null
filters: Record<string, any>
config: EntityConfig | null
}
export interface GenericEntityActions {
setConfig: (config: EntityConfig) => void
fetchEntities: (page?: number) => Promise<void>
createEntity: (data: any) => Promise<void>
updateEntity: (id: number, data: any) => Promise<void>
deleteEntity: (id: number) => Promise<void>
toggleEntityStatus: (id: number, isActive: boolean) => Promise<void>
setFilters: (filters: Record<string, any>) => void
clearFilters: () => void
setEntities: (entities: any[]) => void
setLoading: (loading: boolean) => void
setError: (error: string | null) => void
reset: () => void
}
export type GenericEntityStore = GenericEntityState & GenericEntityActions
const initialState: GenericEntityState = {
entities: [],
loading: false,
error: null,
pagination: null,
filters: {},
config: null
}
export const useGenericEntityStore = create<GenericEntityStore>()(
devtools(
(set, get) => ({
...initialState,
setConfig: (config: EntityConfig) => {
set({ config })
},
fetchEntities: async (page = 1) => {
const { config, filters } = get()
if (!config) return
set({ loading: true, error: null })
try {
const queryParams = new URLSearchParams({
page: page.toString(),
limit: '20', // Optimized default limit
...filters
})
const response = await fetch(`${config.api.endpoint}?${queryParams}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
// Handle different API response formats
let entities = []
let pagination = null
if (data.success && data.data) {
// API response format: { success: true, data: { tecnicos: [...], pagination: {...} } }
if (Array.isArray(data.data)) {
entities = data.data
} else if (data.data.tecnicos) {
entities = data.data.tecnicos
pagination = data.data.pagination
} else if (data.data.recursos) {
entities = data.data.recursos
pagination = data.data.pagination
} else if (data.data.usuarios) {
entities = data.data.usuarios
pagination = data.data.pagination
} else if (data.data.tasks) {
entities = data.data.tasks
pagination = data.data.pagination
} else if (data.data.tareas) {
entities = data.data.tareas
pagination = data.data.pagination
} else {
entities = data.data
}
} else {
// Fallback to direct data
entities = data.data || data
}
set({
entities,
pagination,
loading: false
})
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Error fetching entities',
loading: false
})
}
},
createEntity: async (data: any) => {
const { config } = get()
if (!config) return
try {
const response = await fetch(config.api.endpoint, {
method: config.api.methods.create,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
// Refresh entities after creation
get().fetchEntities()
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Error creating entity'
})
throw error
}
},
updateEntity: async (id: number, data: any) => {
const { config } = get()
if (!config) return
try {
const response = await fetch(`${config.api.endpoint}/${id}`, {
method: config.api.methods.update,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
// Refresh entities after update
get().fetchEntities()
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Error updating entity'
})
throw error
}
},
deleteEntity: async (id: number) => {
const { config } = get()
if (!config) return
try {
const response = await fetch(`${config.api.endpoint}/${id}`, {
method: config.api.methods.delete
})
if (!response.ok) {
// Handle specific error cases with user-friendly messages
if (response.status === 409) {
const errorData = await response.json().catch(() => ({}))
const message = errorData.message || 'No se puede eliminar la entidad'
// Show specific messages based on the error
if (message.includes('tareas activas')) {
throw new Error('No se puede eliminar porque tiene tareas activas asignadas')
} else if (message.includes('desactivar')) {
throw new Error('El técnico será desactivado (no eliminado permanentemente) porque tiene tareas activas')
} else {
throw new Error(message)
}
} else {
throw new Error(`HTTP error! status: ${response.status}`)
}
}
// Refresh entities after successful deletion/deactivation
await get().fetchEntities()
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Error deleting entity'
})
// Still refresh the list even on error to show current state
await get().fetchEntities()
throw error
}
},
setFilters: (filters: Record<string, any>) => {
set({ filters })
},
clearFilters: () => {
set({ filters: {} })
},
setEntities: (entities: any[]) => {
set({ entities })
},
setLoading: (loading: boolean) => {
set({ loading })
},
setError: (error: string | null) => {
set({ error })
},
toggleEntityStatus: async (id: number, isActive: boolean) => {
const { config } = get()
if (!config) return
try {
await get().updateEntity(id, { is_active: isActive })
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Error toggling entity status'
})
throw error
}
},
reset: () => {
set(initialState)
}
}),
{
name: 'generic-entity-store'
}
)
)