const { useEffect, useMemo, useRef, useState, useCallback } = React; const SUPABASE_URL = "https://api.jdos.online"; const SUPABASE_ANON = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY2MDg0ODYzLCJleHAiOjIwODE0NDQ4NjN9.6SeLS42HHXWmDcdWBgDKfdchHILauzJ2jJ3zcM-taFc"; const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON); const PRIORIDADES = [ { value: "URGENTE", label: "Urgente" }, { value: "7_DIAS", label: "7 dias" }, { value: "7_DIAS_O_MAS", label: "7 dias o mas" }, { value: "MENOS_30_DIAS", label: "Menos de 30 dias" }, { value: "MAS_30_DIAS", label: "Mas de 30 dias" }, ]; const PROCESS_CARDS = [ { key: "produccion", label: "Producción", icon: "https://api.jdos.online/storage/v1/object/public/DATA/planta.png", description: "Reportes de producción" }, { key: "distribucion", label: "Distribución", icon: "https://api.jdos.online/storage/v1/object/public/DATA/camion.png", description: "Gestión de distribución" }, { key: "entrega", label: "Entrega", icon: "https://api.jdos.online/storage/v1/object/public/DATA/entrega.png", description: "Reportes de entrega" }, ]; const AREA_ICONS = { CARGADORES: "https://api.jdos.online/storage/v1/object/public/DATA/CARGADORES.png", "SUBESTACION ELECTRICA": "https://api.jdos.online/storage/v1/object/public/DATA/subestacion.jpg", GENERADORES: "https://api.jdos.online/storage/v1/object/public/DATA/Generador.png", "ESTACION DE BOMBEO": "https://api.jdos.online/storage/v1/object/public/DATA/sistema%20bombeo.png", "BASCULA CAMIONERA": "https://api.jdos.online/storage/v1/object/public/DATA/BASCULA%20CAMIONERA%20.png", }; const INITIAL_SELECTION = { procesoId: null, plantaId: null, areaId: null, lineaId: null, equipoId: null, conjuntoCodigo: null, parteCodigo: null, sintomaFamiliaId: null, sintomaId: null, causaFamiliaId: null, causaId: null, }; const defaultStatus = () => ({ type: null, message: "" }); const formatDate = (date) => date ? new Intl.DateTimeFormat("es-CO", { hour12: false, hour: "2-digit", minute: "2-digit", day: "2-digit", month: "short", year: "numeric", }).format(date) : ""; const formatPriorityLabel = (value) => { if (!value) return null; const map = { CEDC: "EJECUTADO", PLAN: "PLANEAR", PROG: "PROGRAMAR", URGENTE: "URGENTE", "7_DIAS": "7 DIAS", "7_DIAS_O_MAS": "7 DIAS O MAS", "MENOS_30_DIAS": "MENOS DE 30 DIAS", "MAS_30_DIAS": "MAS DE 30 DIAS" }; return map[value] || value.replace(/_/g, " ").toUpperCase(); }; const getPriorityClass = (value) => { const key = (value || "").toUpperCase(); // Estados if (key === "CEDC") return "low"; // Green/Done if (key === "PLAN") return "medium"; // Yellow/Warning if (key === "PROG") return "high"; // Orange/Info // Legacy Prioridades if (key.includes("URGENTE")) return "urgent"; if (key.includes("7_DIAS")) return "high"; if (key.includes("MENOS_30")) return "medium"; if (key.includes("MAS_30")) return "low"; return "muted"; }; const HERO_FEATURES = [ { icon: "⚡", title: "Tiempo real", description: "Sincronizacion instantanea de datos", }, { icon: "📊", title: "Analisis avanzado", description: "Dashboards interactivos y reportes", }, { icon: "🔐", title: "Seguridad garantizada", description: "Proteccion de la informacion corporativa", }, ]; // Admin Modal Component (Portal version) function AdminModal({ show, onClose, supabase, profile }) { const [activeTab, setActiveTab] = useState("equipos"); const EQUIPOS_CONFIG = { produccion: { label: "Produccion", table: "equipos", plantField: "planta_linea_id", plantLabel: "Linea", }, distribucion: { label: "Distribucion", table: "equipos_distribucion", plantField: "planta_id", plantLabel: "Planta", }, entrega: { label: "Entrega", table: "equipos_entrega", plantField: "planta_id", plantLabel: "Planta", }, }; const TAREAS_CONFIG = { produccion: { label: "Produccion", table: "tareas_estandar_produccion", }, distribucion: { label: "Distribucion", table: "tareas_estandar_distribucion", }, entrega: { label: "Entrega", table: "tareas_estandar_entrega", }, }; const ROLE_OPTIONS = ["usuario", "admin", "super_admin"]; const isAdmin = ["admin", "super_admin"].includes(profile?.rol); const [plantasCatalog, setPlantasCatalog] = useState([]); const [lineasCatalog, setLineasCatalog] = useState([]); const [areasCatalog, setAreasCatalog] = useState([]); const [catalogosLoading, setCatalogosLoading] = useState(false); const [catalogosError, setCatalogosError] = useState(""); const [equiposScope, setEquiposScope] = useState("produccion"); const [equiposRows, setEquiposRows] = useState([]); const [equiposSearch, setEquiposSearch] = useState(""); const [equiposPlantaFilter, setEquiposPlantaFilter] = useState(""); const [equiposLoading, setEquiposLoading] = useState(false); const [equiposError, setEquiposError] = useState(""); const [equiposFormOpen, setEquiposFormOpen] = useState(false); const [equiposEditing, setEquiposEditing] = useState(null); const equiposFormRef = useRef(null); const [equiposForm, setEquiposForm] = useState({ nombre: "", codigo: "", plantValue: "", }); const [tareasScope, setTareasScope] = useState("distribucion"); const [tareasRows, setTareasRows] = useState([]); const [tareasSearch, setTareasSearch] = useState(""); const [tareasLoading, setTareasLoading] = useState(false); const [tareasError, setTareasError] = useState(""); const [tareasFormOpen, setTareasFormOpen] = useState(false); const [tareasEditing, setTareasEditing] = useState(null); const [tareasForm, setTareasForm] = useState({ texto_breve: "", sub_equipo: "", conjunto: "", parte: "", }); const [perfilesRows, setPerfilesRows] = useState([]); const [perfilesSearch, setPerfilesSearch] = useState(""); const [perfilesLoading, setPerfilesLoading] = useState(false); const [perfilesError, setPerfilesError] = useState(""); const [perfilesFormOpen, setPerfilesFormOpen] = useState(false); const [perfilesEditing, setPerfilesEditing] = useState(null); const [perfilesForm, setPerfilesForm] = useState({ id: "", nombre: "", rol: "usuario", plantas_ids: [], }); const [ejecutoresRows, setEjecutoresRows] = useState([]); const [ejecutoresSearch, setEjecutoresSearch] = useState(""); const [ejecutoresLoading, setEjecutoresLoading] = useState(false); const [ejecutoresError, setEjecutoresError] = useState(""); const [ejecutoresFormOpen, setEjecutoresFormOpen] = useState(false); const [ejecutoresEditing, setEjecutoresEditing] = useState(null); const [ejecutoresForm, setEjecutoresForm] = useState({ codigo: "", nombre: "", activo: true, }); const parseNumberOrText = (value) => { const text = String(value || "").trim(); if (!text) return null; const num = Number(text); return Number.isFinite(num) ? num : text; }; const parsePlantasIds = (value) => { if (Array.isArray(value)) { return value .map((item) => Number(item)) .filter((item) => Number.isFinite(item)); } const raw = String(value || "").trim(); if (!raw) return []; return raw .split(",") .map((item) => Number(item.trim())) .filter((item) => Number.isFinite(item)); }; const loadEquipos = useCallback(async () => { const cfg = EQUIPOS_CONFIG[equiposScope]; if (!cfg) return; setEquiposLoading(true); setEquiposError(""); const columns = ["id", "nombre", "codigo", cfg.plantField]; const { data, error } = await supabase .from(cfg.table) .select(columns.join(",")) .order("nombre", { ascending: true }); if (error) { setEquiposError(error.message || "Error cargando equipos."); setEquiposRows([]); } else { setEquiposRows(data || []); } setEquiposLoading(false); }, [equiposScope, supabase]); const loadTareas = useCallback(async () => { const cfg = TAREAS_CONFIG[tareasScope]; if (!cfg) return; setTareasLoading(true); setTareasError(""); const { data, error } = await supabase .from(cfg.table) .select("id,texto_breve,sub_equipo,conjunto,parte") .order("texto_breve", { ascending: true }); if (error) { setTareasError(error.message || "Error cargando tareas."); setTareasRows([]); } else { setTareasRows(data || []); } setTareasLoading(false); }, [tareasScope, supabase]); const loadPerfiles = useCallback(async () => { setPerfilesLoading(true); setPerfilesError(""); const { data, error } = await supabase .from("perfiles") .select("id,nombre,rol,plantas_ids") .order("nombre", { ascending: true }); if (error) { setPerfilesError(error.message || "Error cargando perfiles."); setPerfilesRows([]); } else { setPerfilesRows(data || []); } setPerfilesLoading(false); }, [supabase]); const loadEjecutores = useCallback(async () => { setEjecutoresLoading(true); setEjecutoresError(""); const { data, error } = await supabase .from("ejecutores") .select("id,codigo,nombre,activo") .order("nombre", { ascending: true }); if (error) { setEjecutoresError(error.message || "Error cargando ejecutores."); setEjecutoresRows([]); } else { setEjecutoresRows(data || []); } setEjecutoresLoading(false); }, [supabase]); const loadCatalogos = useCallback(async () => { setCatalogosLoading(true); setCatalogosError(""); const [plantasResp, lineasResp, areasResp] = await Promise.all([ supabase.from("plantas").select("id,nombre").order("nombre"), supabase .from("planta_lineas") .select("id,nombre,planta_area_id") .order("nombre"), supabase.from("planta_areas").select("id,planta_id").order("id"), ]); const lineasData = Array.isArray(lineasResp.data) ? lineasResp.data : []; const areasData = Array.isArray(areasResp.data) ? areasResp.data : []; if (plantasResp.error || lineasResp.error || areasResp.error) { setCatalogosError( plantasResp.error?.message || lineasResp.error?.message || areasResp.error?.message || "Error cargando catalogos." ); } setPlantasCatalog(plantasResp.data || []); setLineasCatalog(lineasData); setAreasCatalog(areasData); setCatalogosLoading(false); }, [supabase]); useEffect(() => { if (!show) return; loadCatalogos(); }, [show, loadCatalogos]); useEffect(() => { if (!show) return; if (activeTab === "equipos") loadEquipos(); }, [show, activeTab, equiposScope, loadEquipos]); useEffect(() => { if (!show) return; if (activeTab === "tareas") loadTareas(); }, [show, activeTab, tareasScope, loadTareas]); useEffect(() => { if (!show) return; if (activeTab === "perfiles") loadPerfiles(); }, [show, activeTab, loadPerfiles]); useEffect(() => { if (!show) return; if (activeTab === "ejecutores") loadEjecutores(); }, [show, activeTab, loadEjecutores]); useEffect(() => { if (!show) return; if (activeTab === "ejecutores") loadEjecutores(); }, [show, activeTab, loadEjecutores]); const equiposConfig = EQUIPOS_CONFIG[equiposScope]; const tareasConfig = TAREAS_CONFIG[tareasScope]; const isProduccionEquipos = equiposScope === "produccion"; const plantasById = useMemo(() => { const entries = plantasCatalog.map((item) => [Number(item.id), item.nombre]); return new Map(entries); }, [plantasCatalog]); const lineasById = useMemo(() => { const entries = lineasCatalog.map((item) => [Number(item.id), item.nombre]); return new Map(entries); }, [lineasCatalog]); const areasToPlantId = useMemo(() => { const entries = areasCatalog.map((item) => [Number(item.id), Number(item.planta_id)]); return new Map(entries); }, [areasCatalog]); const lineaToPlantId = useMemo(() => { const entries = lineasCatalog.map((item) => [ Number(item.id), areasToPlantId.get(Number(item.planta_area_id)), ]); return new Map(entries); }, [lineasCatalog, areasToPlantId]); const getLookupValue = (lookup, value) => { if (!lookup || lookup.size === 0) return ""; if (value === null || value === undefined || value === "") return ""; const numeric = Number(value); const key = Number.isFinite(numeric) ? numeric : value; return lookup.get(key) || "Sin nombre"; }; const getEquipoLocationLabel = (value) => { if (equiposScope === "produccion") { return getLookupValue(lineasById, value); } return getLookupValue(plantasById, value); }; const getEquipoPlantId = (value) => { if (equiposScope === "produccion") { return lineaToPlantId.get(Number(value)); } return Number(value); }; const getEquipoPlantaLabel = (value) => { const plantId = getEquipoPlantId(value); return getLookupValue(plantasById, plantId); }; const formatPlantas = (ids) => { if (!plantasById || plantasById.size === 0) return ""; const list = parsePlantasIds(ids); if (!list.length) return ""; return list.map((id) => plantasById.get(id) || "Sin nombre").join(", "); }; const equiposFiltered = useMemo(() => { const term = equiposSearch.trim().toLowerCase(); const plantFilter = equiposPlantaFilter ? Number(equiposPlantaFilter) : null; return equiposRows.filter((row) => { const plantValue = row[equiposConfig?.plantField] ?? ""; if (plantFilter !== null) { const plantId = getEquipoPlantId(plantValue); if (Number(plantId) !== plantFilter) return false; } if (!term) return true; return ( String(row.id || "").toLowerCase().includes(term) || String(row.nombre || "").toLowerCase().includes(term) || String(row.codigo || "").toLowerCase().includes(term) || String(getEquipoLocationLabel(plantValue)).toLowerCase().includes(term) || String(getEquipoPlantaLabel(plantValue)).toLowerCase().includes(term) ); }); }, [ equiposRows, equiposSearch, equiposConfig, equiposScope, plantasById, lineasById, equiposPlantaFilter, lineaToPlantId, ]); const tareasFiltered = useMemo(() => { const term = tareasSearch.trim().toLowerCase(); if (!term) return tareasRows; return tareasRows.filter((row) => [row.id, row.texto_breve, row.sub_equipo, row.conjunto, row.parte] .map((value) => String(value || "").toLowerCase()) .some((value) => value.includes(term)) ); }, [tareasRows, tareasSearch]); const perfilesFiltered = useMemo(() => { const term = perfilesSearch.trim().toLowerCase(); if (!term) return perfilesRows; return perfilesRows.filter((row) => [ row.id, row.nombre, row.rol, formatPlantas(row.plantas_ids), ] .map((value) => String(value || "").toLowerCase()) .some((value) => value.includes(term)) ); }, [perfilesRows, perfilesSearch, plantasById]); const openEquiposForm = (row) => { const config = equiposConfig || EQUIPOS_CONFIG.produccion; setEquiposEditing(row || null); setEquiposForm({ nombre: row?.nombre || "", codigo: row?.codigo || "", plantValue: row ? row[config.plantField] ?? "" : "", }); setEquiposFormOpen(true); }; useEffect(() => { if (!equiposFormOpen) return; if (equiposFormRef.current) { equiposFormRef.current.scrollIntoView({ behavior: "smooth", block: "start" }); } }, [equiposFormOpen]); const saveEquipos = async () => { if (!equiposConfig) return; const payload = { nombre: String(equiposForm.nombre || "").trim(), codigo: String(equiposForm.codigo || "").trim(), }; payload[equiposConfig.plantField] = parseNumberOrText(equiposForm.plantValue); if (!payload.nombre || !payload.codigo) { setEquiposError("Nombre y codigo son obligatorios."); return; } setEquiposError(""); if (equiposEditing) { const { error } = await supabase .from(equiposConfig.table) .update(payload) .eq("id", equiposEditing.id); if (error) { setEquiposError(error.message || "Error actualizando equipo."); return; } } else { const { error } = await supabase .from(equiposConfig.table) .insert(payload); if (error) { setEquiposError(error.message || "Error creando equipo."); return; } } setEquiposFormOpen(false); setEquiposEditing(null); loadEquipos(); }; const deleteEquipos = async (row) => { if (!equiposConfig || !row?.id) return; if (!window.confirm("Eliminar este equipo?")) return; const { error } = await supabase .from(equiposConfig.table) .delete() .eq("id", row.id); if (error) { setEquiposError(error.message || "Error eliminando equipo."); return; } loadEquipos(); }; const openTareasForm = (row) => { setTareasEditing(row || null); setTareasForm({ texto_breve: row?.texto_breve || "", sub_equipo: row?.sub_equipo || "", conjunto: row?.conjunto || "", parte: row?.parte || "", }); setTareasFormOpen(true); }; const saveTareas = async () => { if (!tareasConfig) return; const payload = { texto_breve: String(tareasForm.texto_breve || "").trim(), sub_equipo: String(tareasForm.sub_equipo || "").trim(), conjunto: String(tareasForm.conjunto || "").trim(), parte: String(tareasForm.parte || "").trim(), }; if (!payload.texto_breve) { setTareasError("Texto breve es obligatorio."); return; } setTareasError(""); if (tareasEditing) { const { error } = await supabase .from(tareasConfig.table) .update(payload) .eq("id", tareasEditing.id); if (error) { setTareasError(error.message || "Error actualizando tarea."); return; } } else { const { error } = await supabase.from(tareasConfig.table).insert(payload); if (error) { setTareasError(error.message || "Error creando tarea."); return; } } setTareasFormOpen(false); setTareasEditing(null); loadTareas(); }; const deleteTareas = async (row) => { if (!tareasConfig || !row?.id) return; if (!window.confirm("Eliminar esta tarea?")) return; const { error } = await supabase .from(tareasConfig.table) .delete() .eq("id", row.id); if (error) { setTareasError(error.message || "Error eliminando tarea."); return; } loadTareas(); }; const openPerfilesForm = (row) => { setPerfilesEditing(row || null); setPerfilesForm({ id: row?.id || "", nombre: row?.nombre || "", rol: row?.rol || "usuario", plantas_ids: parsePlantasIds(row?.plantas_ids), }); setPerfilesFormOpen(true); }; const savePerfiles = async () => { const payload = { nombre: String(perfilesForm.nombre || "").trim(), rol: String(perfilesForm.rol || "usuario").trim(), plantas_ids: parsePlantasIds(perfilesForm.plantas_ids), }; if (!payload.nombre) { setPerfilesError("Nombre es obligatorio."); return; } if (perfilesEditing) { const { error } = await supabase .from("perfiles") .update(payload) .eq("id", perfilesEditing.id); if (error) { setPerfilesError(error.message || "Error actualizando perfil."); return; } } else { const idValue = String(perfilesForm.id || "").trim(); if (!idValue) { setPerfilesError("ID es obligatorio para crear perfil."); return; } const { error } = await supabase .from("perfiles") .insert({ id: idValue, ...payload }); if (error) { setPerfilesError(error.message || "Error creando perfil."); return; } } setPerfilesFormOpen(false); setPerfilesEditing(null); loadPerfiles(); }; const deletePerfiles = async (row) => { if (!row?.id) return; if (!window.confirm("Eliminar este perfil?")) return; const { error } = await supabase.from("perfiles").delete().eq("id", row.id); if (error) { setPerfilesError(error.message || "Error eliminando perfil."); return; } loadPerfiles(); }; const openEjecutoresForm = (row) => { setEjecutoresEditing(row || null); setEjecutoresForm({ codigo: row?.codigo || "", nombre: row?.nombre || "", activo: row?.activo ?? true, }); setEjecutoresFormOpen(true); }; const saveEjecutores = async () => { const payload = { codigo: String(ejecutoresForm.codigo || "").trim(), nombre: String(ejecutoresForm.nombre || "").trim(), activo: ejecutoresForm.activo, }; if (!payload.codigo || !payload.nombre) { setEjecutoresError("Codigo y Nombre son obligatorios."); return; } if (ejecutoresEditing) { const { error } = await supabase .from("ejecutores") .update(payload) .eq("id", ejecutoresEditing.id); if (error) { setEjecutoresError(error.message || "Error actualizando ejecutor."); return; } } else { const { error } = await supabase.from("ejecutores").insert(payload); if (error) { setEjecutoresError(error.message || "Error creando ejecutor."); return; } } setEjecutoresFormOpen(false); setEjecutoresEditing(null); loadEjecutores(); }; const deleteEjecutores = async (row) => { if (!row?.id) return; if (!window.confirm("Eliminar este ejecutor?")) return; const { error } = await supabase.from("ejecutores").delete().eq("id", row.id); if (error) { setEjecutoresError(error.message || "Error eliminando ejecutor."); return; } loadEjecutores(); }; if (!show) return null; if (!isAdmin) { return ReactDOM.createPortal(
event.stopPropagation()}>

Panel de Administracion

Acceso restringido. Contacta al administrador.
, document.body ); } return ReactDOM.createPortal(
event.stopPropagation()}>

Panel de Administracion

{activeTab === "equipos" && (
setEquiposSearch(event.target.value)} />
{catalogosLoading &&
Cargando catalogos...
} {catalogosError &&
{catalogosError}
} {equiposFormOpen && (

{equiposEditing ? "Editar equipo" : "Nuevo equipo"}

setEquiposForm((prev) => ({ ...prev, nombre: event.target.value })) } /> setEquiposForm((prev) => ({ ...prev, codigo: event.target.value })) } />
)} {equiposLoading &&
Cargando...
} {equiposError &&
{equiposError}
} {!equiposLoading && !equiposError && (
{isProduccionEquipos && } {equiposFiltered.map((row) => ( {isProduccionEquipos && ( )} ))} {!equiposFiltered.length && ( )}
Nombre Codigo {equiposConfig.plantLabel}PlantaAcciones
{row.nombre} {row.codigo} {getEquipoLocationLabel(row[equiposConfig.plantField])}{getEquipoPlantaLabel(row[equiposConfig.plantField])}
Sin registros
)}
)} {activeTab === "tareas" && (
setTareasSearch(event.target.value)} />
{tareasFormOpen && (

{tareasEditing ? "Editar tarea" : "Nueva tarea"}

setTareasForm((prev) => ({ ...prev, texto_breve: event.target.value })) } /> setTareasForm((prev) => ({ ...prev, sub_equipo: event.target.value })) } /> setTareasForm((prev) => ({ ...prev, conjunto: event.target.value })) } /> setTareasForm((prev) => ({ ...prev, parte: event.target.value })) } />
)} {tareasLoading &&
Cargando...
} {tareasError &&
{tareasError}
} {!tareasLoading && !tareasError && (
{tareasFiltered.map((row) => ( ))} {!tareasFiltered.length && ( )}
Texto breve Sub equipo Conjunto Parte Acciones
{row.texto_breve} {row.sub_equipo} {row.conjunto} {row.parte}
Sin registros
)}
)} {activeTab === "perfiles" && (
setPerfilesSearch(event.target.value)} />
{catalogosLoading &&
Cargando catalogos...
} {catalogosError &&
{catalogosError}
} {perfilesFormOpen && (

{perfilesEditing ? "Editar perfil" : "Nuevo perfil"}

{!perfilesEditing && ( setPerfilesForm((prev) => ({ ...prev, id: event.target.value })) } /> )} setPerfilesForm((prev) => ({ ...prev, nombre: event.target.value })) } />
)} {perfilesLoading &&
Cargando...
} {perfilesError &&
{perfilesError}
} {!perfilesLoading && !perfilesError && (
{perfilesFiltered.map((row) => ( ))} {!perfilesFiltered.length && ( )}
Nombre Rol Plantas Acciones
{row.nombre} {row.rol} {formatPlantas(row.plantas_ids)}
Sin registros
)}
)} {activeTab === "ejecutores" && (
setEjecutoresSearch(event.target.value)} />
{ejecutoresFormOpen && (

{ejecutoresEditing ? "Editar ejecutor" : "Nuevo ejecutor"}

setEjecutoresForm((prev) => ({ ...prev, codigo: event.target.value })) } /> setEjecutoresForm((prev) => ({ ...prev, nombre: event.target.value })) } />
setEjecutoresForm((prev) => ({ ...prev, activo: event.target.checked })) } style={{ width: 'auto', margin: 0 }} />
)} {ejecutoresLoading &&
Cargando...
} {ejecutoresError &&
{ejecutoresError}
} {!ejecutoresLoading && !ejecutoresError && (
{ejecutoresRows .filter((row) => { const term = ejecutoresSearch.toLowerCase(); return ( String(row.nombre || "").toLowerCase().includes(term) || String(row.codigo || "").toLowerCase().includes(term) ); }) .map((row) => ( ))} {ejecutoresRows.length === 0 && ( )}
Codigo Nombre Estado Acciones
{row.codigo} {row.nombre} {row.activo ? "Activo" : "Inactivo"}
Sin registros
)}
)}
, document.body ); } function App() { const [session, setSession] = useState(null); const [profile, setProfile] = useState(null); const [loadingProfile, setLoadingProfile] = useState(false); const [status, setStatus] = useState(defaultStatus()); const [sugerenciasTexto, setSugerenciasTexto] = useState([]); const [hydrating, setHydrating] = useState(true); const [saving, setSaving] = useState(false); const [screen, setScreen] = useState("auth"); const [authForm, setAuthForm] = useState({ email: "", password: "" }); const [authLoading, setAuthLoading] = useState(false); const [selection, setSelection] = useState({ ...INITIAL_SELECTION }); const [formData, setFormData] = useState({ textoBreve: "", textoBreve: "", textoExt: "", danoOperativo: "NO", estadoUsuario: "", ejecutorId: null, fechaInicio: "", fechaFin: "", }); const [fotoFiles, setFotoFiles] = useState([]); const MAX_PHOTOS = 4; const cameraInputRef = useRef(null); const galleryInputRef = useRef(null); const [catalogos, setCatalogos] = useState({ procesos: [], plantas: [], sintomaFamilias: [], sintomas: [], causaFamilias: [], causas: [], ejecutores: [], }); const [areas, setAreas] = useState([]); const [lineas, setLineas] = useState([]); const [equipos, setEquipos] = useState([]); const [conjuntos, setConjuntos] = useState([]); const [partes, setPartes] = useState([]); const [partesCache, setPartesCache] = useState({}); const [areaRequiresLinea, setAreaRequiresLinea] = useState(true); const [reportes, setReportes] = useState([]); const [loadingReportes, setLoadingReportes] = useState(false); const [tableActionId, setTableActionId] = useState(null); const [editingReporte, setEditingReporte] = useState(null); const [plantasConEquipos, setPlantasConEquipos] = useState([]); // Admin Panel State const [showAdminPanel, setShowAdminPanel] = useState(false); useEffect(() => { supabase.auth.getSession().then(({ data }) => setSession(data.session)); const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => setSession(session)); return () => subscription.unsubscribe(); }, []); useEffect(() => { if (!session) { setProfile(null); setScreen("auth"); return; } setLoadingProfile(true); supabase .from("perfiles") .select("*") .eq("id", session.user.id) .maybeSingle() .then(({ data, error }) => { if (error) throw error; if (!data) throw new Error("No existe un perfil asociado al usuario."); const plantas_ids = Array.isArray(data.plantas_ids) ? data.plantas_ids.map((x) => Number(x)) : []; setProfile({ ...data, plantas_ids }); // Expose role for Desktop App localStorage.setItem("ARGOS_USER_ROLE", data.rol); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: error.message || "No fue posible cargar el perfil.", }); }) .finally(() => setLoadingProfile(false)); }, [session]); useEffect(() => { if (!session) return; setHydrating(true); Promise.all([ supabase.from("procesos").select("id,nombre").order("nombre"), supabase.from("plantas").select("id,nombre,proceso_id").order("nombre"), supabase.from("sintoma_familias").select("id,nombre,codigo").order("nombre"), supabase.from("sintomas").select("id,nombre,codigo,familia_id").order("nombre"), supabase.from("causa_familias").select("id,nombre,codigo").order("nombre"), supabase.from("causas").select("id,nombre,codigo,familia_id").order("nombre"), supabase.from("ejecutores").select("id,nombre").eq("activo", true).order("nombre"), ]) .then( ([ procesos, plantas, sintomaFamilias, sintomas, causaFamilias, causas, ejecutores, ]) => { setCatalogos({ procesos: procesos.data || [], plantas: plantas.data || [], sintomaFamilias: sintomaFamilias.data || [], sintomas: sintomas.data || [], causaFamilias: causaFamilias.data || [], causas: causas.data || [], ejecutores: ejecutores.data || [], }); } ) .catch((error) => { console.error(error); setStatus({ type: "error", message: "No fue posible sincronizar los catalogos base.", }); }) .finally(() => setHydrating(false)); }, [session]); /* Hook definitions moved up to ensure availability */ const procesoActual = useMemo( () => catalogos.procesos.find((p) => p.id === selection.procesoId), [catalogos.procesos, selection.procesoId] ); const isDistribucion = useMemo( () => procesoActual?.nombre?.toLowerCase().includes("distribucion"), [procesoActual] ); const isEntrega = useMemo( () => procesoActual?.nombre?.toLowerCase().includes("entrega"), [procesoActual] ); useEffect(() => { if (!session || !profile) return; // Use the memoized values const skipAreas = isDistribucion || isEntrega; if (!selection.procesoId) { setScreen("process"); } else if (!selection.areaId && screen === "form" && !skipAreas) { setScreen("selection"); } }, [session, profile, selection.procesoId, selection.areaId, screen, procesoActual]); // Cargar plantas que tienen equipos para Distribucion/Entrega useEffect(() => { const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); console.log("[DEBUG] Plant filter useEffect:", { isDistribucion, isEntrega, procesoActual: procesoActual?.nombre }); if (!isDistribucion && !isEntrega) { setPlantasConEquipos([]); return; } const tablaEquipos = isEntrega ? "equipos_entrega" : "equipos_distribucion"; console.log("[DEBUG] Querying table:", tablaEquipos); supabase .from(tablaEquipos) .select("planta_id") .then(({ data, error }) => { if (error) { console.error("Error cargando plantas con equipos", error); return; } // Obtener IDs únicos de plantas const plantaIds = [...new Set(data.map(eq => eq.planta_id))]; console.log("[DEBUG] Plant IDs with equipment:", plantaIds); setPlantasConEquipos(plantaIds); }) .catch(err => console.error(err)); }, [procesoActual]); useEffect(() => { if (screen === "selection") { const skipAreas = isDistribucion || isEntrega; if (selection.areaId) { setScreen("form"); } else if (skipAreas && selection.plantaId) { // Flujo directo para distribución/entrega: Planta -> Formulario setScreen("form"); } } }, [screen, selection.areaId, selection.plantaId, procesoActual, isDistribucion, isEntrega]); useEffect(() => { if (!selection.plantaId) { setAreas([]); setLineas([]); setEquipos([]); setAreaRequiresLinea(true); setSelection((prev) => ({ ...prev, areaId: null, lineaId: null, equipoId: null })); return; } // Si es distribución o entrega, no cargamos áreas const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); if (isDistribucion || isEntrega) return; supabase .from("planta_areas") .select("id,nombre") .eq("planta_id", selection.plantaId) .order("orden", { ascending: true }) .then(({ data, error }) => { if (error) throw error; setAreas(data || []); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: "No se pudieron cargar las areas." }); }); }, [selection.plantaId, procesoActual]); // Añadido procesoActual a dependencias useEffect(() => { if (!selection.areaId) { setLineas([]); setEquipos([]); setAreaRequiresLinea(true); setSelection((prev) => ({ ...prev, lineaId: null, equipoId: null })); return; } supabase .from("planta_lineas") .select("id,nombre") .eq("planta_area_id", selection.areaId) .order("nombre", { ascending: true }) .then(({ data, error }) => { if (error) throw error; const list = data || []; setLineas(list); if (!list.length) { setAreaRequiresLinea(false); setSelection((prev) => prev.lineaId === null && prev.equipoId === null ? prev : { ...prev, lineaId: null, equipoId: null } ); return; } if (list.length === 1) { setAreaRequiresLinea(false); setSelection((prev) => prev.lineaId === list[0].id ? prev : { ...prev, lineaId: list[0].id } ); return; } setAreaRequiresLinea(true); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: "No se pudieron cargar las lineas." }); }); }, [selection.areaId]); useEffect(() => { // Si NO es distribucion ni entrega, dependemos de la linea const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); if (!isDistribucion && !isEntrega) { if (!selection.lineaId) { setEquipos([]); setSelection((prev) => ({ ...prev, equipoId: null })); return; } supabase .from("equipos") .select("id,nombre,codigo") .eq("planta_linea_id", selection.lineaId) .order("nombre", { ascending: true }) .then(({ data, error }) => { if (error) throw error; setEquipos(data || []); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: "No se pudieron cargar los equipos." }); }); } else { // En distribucion y entrega, cargamos equipos por planta if (!selection.plantaId) return; // Determinar qué tabla usar según el proceso const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); const tablaEquipos = isEntrega ? "equipos_entrega" : "equipos_distribucion"; supabase .from(tablaEquipos) .select("id,nombre,codigo") .eq("planta_id", selection.plantaId) // Ordenamos por CODIGO descendente para que salga de Mayor a Menor (ej. 1080 antes que 900) .order("codigo", { ascending: false }) .then(({ data, error }) => { if (error) { console.error(`Error cargando equipos ${tablaEquipos}`, error); setEquipos([]); return; } setEquipos(data || []); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: `Error al cargar equipos de ${isEntrega ? 'entrega' : 'distribución'}.` }); }); } }, [selection.lineaId, selection.plantaId, procesoActual]); useEffect(() => { if (!selection.equipoId) { setConjuntos([]); setPartes([]); setSelection((prev) => ({ ...prev, conjuntoCodigo: null, parteCodigo: null })); return; } const equipo = equipos.find((item) => item.id === selection.equipoId); if (!equipo || !equipo.codigo) return; const prefix = equipo.codigo.slice(0, 5); const cached = partesCache[prefix]; if (cached) { setConjuntos(buildConjuntosFromPartes(cached)); return; } supabase .from("partes") .select( "equipo_codigo_prefix,conjunto_codigo,conjunto_nombre,codigo_parte,nombre_parte" ) .eq("equipo_codigo_prefix", prefix) .then(({ data, error }) => { if (error) throw error; const dataset = data || []; setPartesCache((prev) => ({ ...prev, [prefix]: dataset })); setConjuntos(buildConjuntosFromPartes(dataset)); }) .catch((error) => { console.error(error); setStatus({ type: "error", message: "No se pudieron cargar los conjuntos del equipo.", }); }); }, [selection.equipoId, equipos, partesCache]); useEffect(() => { if (!selection.conjuntoCodigo) { setPartes([]); setSelection((prev) => ({ ...prev, parteCodigo: null })); return; } const equipo = equipos.find((item) => item.id === selection.equipoId); if (!equipo) return; const prefix = equipo.codigo.slice(0, 5); const dataset = partesCache[prefix] || []; const filtered = dataset.filter( (row) => row.conjunto_codigo === selection.conjuntoCodigo ); setPartes(filtered); }, [selection.conjuntoCodigo, selection.equipoId, equipos, partesCache]); useEffect(() => { // 1. GLOBAL SEARCH (Prioridad: Si hay texto > 2 chars, busca globalmente) const term = typeof formData.textoBreve === 'string' ? formData.textoBreve.trim() : ""; // Lógica Unificada: // Determinar qué tabla de tareas estándar usar según el proceso const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); const tablaTareas = isEntrega ? "tareas_estandar_entrega" : isDistribucion ? "tareas_estandar_distribucion" : "tareas_estandar_produccion"; let query = supabase.from(tablaTareas).select("texto_breve, sub_equipo, conjunto, parte").limit(50); if (term.length >= 3) { // Búsqueda global (Reverse Lookup) - Prioridad 1: Si escribe, busca en todo query = query.ilike("texto_breve", `%${term}%`); } else if (selection.conjuntoCodigo && selection.parteCodigo) { // Filtro preciso - Prioridad 2: Si no escribe (o es corto), pero tiene contexto, muestra items del contexto const conjunto = conjuntos.find((c) => c.codigo === selection.conjuntoCodigo); const parte = partes.find((p) => p.codigo_parte === selection.parteCodigo); if (conjunto && parte) { query = query.ilike("conjunto", conjunto.nombre).ilike("parte", parte.nombre_parte); } } else { // Nada seleccionado ni escrito suficiente setSugerenciasTexto([]); return; } query .then(({ data, error }) => { if (error) { console.error("Error fetching suggestions:", error); setSugerenciasTexto([]); return; } // Map para unicos const uniqueMap = new Map(); data.forEach((item) => { if (!uniqueMap.has(item.texto_breve)) { uniqueMap.set(item.texto_breve, { texto: item.texto_breve, sub: item.sub_equipo, conjuntoName: item.conjunto, parteName: item.parte }); } }); setSugerenciasTexto(Array.from(uniqueMap.values())); }) .catch((err) => console.error(err)); }, [ selection.conjuntoCodigo, selection.parteCodigo, conjuntos, partes, formData.textoBreve, procesoActual, ]); const allowedPlantas = useMemo(() => { if (!profile) return []; const all = catalogos.plantas; if (["admin", "super_admin"].includes(profile.rol)) return all; if (!profile.plantas_ids || profile.plantas_ids.length === 0) return all; const allowedIds = profile.plantas_ids.map(Number); return all.filter((planta) => allowedIds.includes(planta.id)); }, [catalogos.plantas, profile]); const procesosVisibles = useMemo(() => { if (!allowedPlantas.length) return catalogos.procesos; const procesosDisponibles = new Set( allowedPlantas.map((planta) => planta.proceso_id) ); return catalogos.procesos.filter((p) => procesosDisponibles.has(p.id)); }, [allowedPlantas, catalogos.procesos]); const plantasPorProceso = useMemo(() => { const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); // Para Entrega: hardcode de plantas permitidas if (isEntrega) { const plantasEntrega = ["NORTE", "ZONA FRANCA", "SANTA MARTA"]; const filtered = allowedPlantas.filter(planta => { const nombreUpper = planta.nombre?.toUpperCase().trim() || ""; const match = plantasEntrega.some(p => nombreUpper === p || nombreUpper.includes(p)); return match; }); return filtered; } // Para Distribucion: usar filtro dinámico if (isDistribucion) { if (plantasConEquipos.length === 0) { return []; } const filtered = allowedPlantas.filter(planta => plantasConEquipos.includes(planta.id)); return filtered; } return allowedPlantas; }, [allowedPlantas, procesoActual, plantasConEquipos]); const sintomasDisponibles = useMemo(() => { if (!selection.sintomaFamiliaId) return []; return catalogos.sintomas.filter( (item) => item.familia_id === selection.sintomaFamiliaId ); }, [catalogos.sintomas, selection.sintomaFamiliaId]); const causasDisponibles = useMemo(() => { if (!selection.causaFamiliaId) return []; return catalogos.causas.filter( (item) => item.familia_id === selection.causaFamiliaId ); }, [catalogos.causas, selection.causaFamiliaId]); const equipoSeleccionado = useMemo( () => equipos.find((item) => item.id === selection.equipoId), [equipos, selection.equipoId] ); const conjuntoSeleccionado = useMemo( () => conjuntos.find((item) => item.codigo === selection.conjuntoCodigo), [conjuntos, selection.conjuntoCodigo] ); const parteSeleccionada = useMemo( () => partes.find((item) => item.codigo_parte === selection.parteCodigo), [partes, selection.parteCodigo] ); const sintomaFamiliaSeleccionada = useMemo( () => catalogos.sintomaFamilias.find( (item) => item.id === selection.sintomaFamiliaId ), [catalogos.sintomaFamilias, selection.sintomaFamiliaId] ); const sintomaSeleccionado = useMemo( () => catalogos.sintomas.find((item) => item.id === selection.sintomaId), [catalogos.sintomas, selection.sintomaId] ); const causaFamiliaSeleccionada = useMemo( () => catalogos.causaFamilias.find((item) => item.id === selection.causaFamiliaId), [catalogos.causaFamilias, selection.causaFamiliaId] ); const causaSeleccionada = useMemo( () => catalogos.causas.find((item) => item.id === selection.causaId), [catalogos.causas, selection.causaId] ); const handleAuth = async (event) => { event.preventDefault(); setAuthLoading(true); setStatus(defaultStatus()); try { const { error } = await supabase.auth.signInWithPassword(authForm); if (error) throw error; } catch (error) { console.error(error); setStatus({ type: "error", message: error.message || "Credenciales invalidas.", }); } finally { setAuthLoading(false); } }; const handleLogout = async () => { await supabase.auth.signOut(); localStorage.removeItem("ARGOS_USER_ROLE"); setSelection({ ...INITIAL_SELECTION }); setFormData({ textoBreve: "", textoExt: "", danoOperativo: "NO", estadoUsuario: "" }); setFotoFile(null); setAreas([]); setLineas([]); setEquipos([]); setConjuntos([]); setPartes([]); setStatus(defaultStatus()); }; const handleSelectionChange = (field, value) => { setSelection((prev) => { const next = { ...prev, [field]: value }; if (field === "procesoId") { next.plantaId = null; next.areaId = null; next.lineaId = null; next.equipoId = null; next.conjuntoCodigo = null; next.parteCodigo = null; } else if (field === "plantaId") { next.areaId = null; next.lineaId = null; next.equipoId = null; next.conjuntoCodigo = null; next.parteCodigo = null; } else if (field === "areaId") { next.lineaId = null; next.equipoId = null; next.conjuntoCodigo = null; next.parteCodigo = null; } else if (field === "lineaId") { next.equipoId = null; next.conjuntoCodigo = null; next.parteCodigo = null; } else if (field === "equipoId") { next.conjuntoCodigo = null; next.parteCodigo = null; } else if (field === "conjuntoCodigo") { next.parteCodigo = null; } else if (field === "sintomaFamiliaId") { next.sintomaId = null; } else if (field === "causaFamiliaId") { next.causaId = null; } return next; }); }; const handleProcessSelect = (key) => { const match = catalogos.procesos.find((item) => item.nombre.toLowerCase().includes(key) ); if (!match) { setStatus({ type: "error", message: "Proceso no disponible." }); return; } setSelection({ ...INITIAL_SELECTION, procesoId: match.id, }); setAreas([]); setLineas([]); setEquipos([]); setAreaRequiresLinea(true); setScreen("selection"); }; // Clear reportes when switching process to avoid ghost data useEffect(() => { setReportes([]); }, [selection.procesoId]); const resetEditingState = () => { setEditingReporte(null); setFormData({ textoBreve: "", textoExt: "", danoOperativo: "NO", estadoUsuario: "" }); setFotoFiles([]); if (cameraInputRef.current) cameraInputRef.current.value = ""; if (galleryInputRef.current) galleryInputRef.current.value = ""; setSelection((prev) => ({ ...prev, conjuntoCodigo: null, parteCodigo: null, sintomaFamiliaId: null, sintomaId: null, causaFamiliaId: null, causaId: null, })); setConjuntos([]); setPartes([]); }; const fetchReportes = useCallback(async () => { if (!session || !selection.procesoId) return; setLoadingReportes(true); const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); let tableName = "reporte_produccion"; if (isDistribucion) tableName = "reporte_distribucion"; if (isEntrega) tableName = "reporte_entrega"; try { let query = supabase .from(tableName) .select( (isDistribucion || isEntrega) ? [ "id", "proceso_id", "planta_id", "equipo_id", "conjunto_codigo", "conjunto_nombre", "parte_codigo", "parte_nombre", "sintoma_familia_id", "sintoma_id", "causa_familia_id", "causa_id", "estado_usuario", "dano_operativo", "texto_breve", "texto_extendido", "foto_url", "created_at", "usuario_id" ].join(",") : [ "id", "proceso_id", "planta_id", "planta_area_id", "planta_linea_id", "equipo_id", "conjunto_codigo", "conjunto_nombre", "parte_codigo", "parte_nombre", "sintoma_familia_id", "sintoma_id", "causa_familia_id", "causa_id", "estado_usuario", "texto_breve", "texto_extendido", "foto_url", "created_at", "usuario_id" ].join(",") ) .eq("proceso_id", selection.procesoId) .order("created_at", { ascending: false }) .limit(50); const { data, error } = await query; if (error) throw error; const base = data || []; // 1. Obtener nombres de usuarios creadores const userIds = [...new Set(base.map(r => r.usuario_id).filter(Boolean))]; let userMap = {}; if (userIds.length > 0) { const { data: usersData } = await supabase .from("perfiles") .select("id,nombre") .in("id", userIds); if (usersData) { userMap = usersData.reduce((acc, u) => ({ ...acc, [u.id]: u.nombre }), {}); } } if (isDistribucion || isEntrega) { const equiposIds = [...new Set(base.map(r => r.equipo_id).filter(Boolean))]; let equiposMap = {}; if (equiposIds.length > 0) { const tablaEquipos = isEntrega ? 'equipos_entrega' : 'equipos_distribucion'; const { data: equiposData, error: equiposError } = await supabase .from(tablaEquipos) .select('id,nombre') .in('id', equiposIds); if (!equiposError && equiposData) { equiposMap = equiposData.reduce((acc, item) => { acc[item.id] = item.nombre; return acc; }, {}); } } const enriched = base.map((item) => { // Para Dist/Entrega: Linea = nombre de planta const plantaObj = catalogos.plantas.find(p => p.id === item.planta_id); return { ...item, equipoNombre: equiposMap[item.equipo_id] || "Sin equipo", lineaNombre: "N/A", displayLinea: plantaObj ? plantaObj.nombre : "General", creatorName: userMap[item.usuario_id] || "Desconocido" }; }); setReportes(enriched); } else { const uniqueIds = (key) => [...new Set(base.map((item) => item[key]).filter(Boolean))]; const fetchNameMap = async (table, ids) => { if (!ids.length) return {}; const { data, error } = await supabase .from(table) .select("id,nombre") .in("id", ids); if (error) throw error; return (data || []).reduce((acc, item) => { acc[item.id] = item.nombre; return acc; }, {}); }; const [lineasMap, equiposMapResult] = await Promise.all([ fetchNameMap("planta_lineas", uniqueIds("planta_linea_id")), fetchNameMap("equipos", uniqueIds("equipo_id")), ]); const enriched = base.map((item) => ({ ...item, lineaNombre: lineasMap[item.planta_linea_id] || "Sin linea", displayLinea: lineasMap[item.planta_linea_id] || "Sin linea", equipoNombre: equiposMapResult[item.equipo_id] || "Sin equipo", creatorName: userMap[item.usuario_id] || "Desconocido" })); setReportes(enriched); } } catch (error) { console.error(error); setStatus({ type: "error", message: `Error carga: ${error?.message || "Desconocido"}`, }); } finally { setLoadingReportes(false); } }, [session, supabase, setStatus, procesoActual, catalogos.plantas]); useEffect(() => { if (!session) return; fetchReportes(); }, [session, fetchReportes]); // Función para redimensionar imágenes (versión robusta con log detallado) const resizeImage = (file) => { return new Promise((resolve, reject) => { console.log("🔄 Iniciando resizeImage para:", file.name, "Tipo:", file.type, "Tamaño:", file.size); const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { try { const canvas = document.createElement("canvas"); let width = img.width; let height = img.height; // Reducir maximo a 1024px para asegurar bajo peso const MAX_WIDTH = 1024; const MAX_HEIGHT = 1024; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); // Forzar salida a JPEG calidad 0.6 (60%) // Esto garantiza que el tamaño sea muy bajo canvas.toBlob((blob) => { if (!blob) { reject(new Error("Falló la conversión a Blob en canvas")); return; } // Crear archivo forzando extension jpg const newName = file.name.replace(/\.[^/.]+$/, "") + ".jpg"; const resizedFile = new File([blob], newName, { type: "image/jpeg", lastModified: Date.now(), }); console.log("✅ Resize exitoso:", resizedFile.name, "Tamaño final:", resizedFile.size); resolve(resizedFile); }, "image/jpeg", 0.6); } catch (err) { reject(err); } }; img.onerror = (err) => { console.error("❌ Error img.onerror (posible imagen corrupta):", err); reject(new Error("La imagen no se pudo decodificar.")); }; img.src = e.target.result; }; reader.onerror = (err) => { console.error("❌ Error reader.onerror:", err); reject(new Error("No se pudo leer el archivo local.")); }; reader.readAsDataURL(file); }); }; const uploadPhotoIfNeeded = async () => { console.log("📸 uploadPhotoIfNeeded - fotoFiles.length:", fotoFiles.length); if (fotoFiles.length === 0) return null; const bucket = "reportes_fotos"; const uploadedUrls = []; let successCount = 0; // Recolector de errores para informar al usuario const criticalErrors = []; for (let i = 0; i < fotoFiles.length; i++) { const file = fotoFiles[i]; setStatus({ type: "info", message: `Comprimiendo y subiendo foto ${i + 1} de ${fotoFiles.length}...` }); let fileToUpload = null; try { // Redimensionar es OBLIGATORIO para evitar error de tamaño. // Si esto falla, es mejor fallar la foto que subir algo impagable. fileToUpload = await resizeImage(file); console.log(`📉 Compresión: ${(file.size / 1024).toFixed(1)}KB -> ${(fileToUpload.size / 1024).toFixed(1)}KB`); } catch (err) { console.error("❌ Error crítico redimensionando foto:", i + 1, err); criticalErrors.push(`Foto ${i + 1}: Error al comprimir (${err.message})`); continue; // Saltar esta foto } // Siempre jpg ahora const extension = "jpg"; const path = `${session.user.id}/${Date.now()}_${Math.random().toString(36).substring(7)}.${extension}`; const { error } = await supabase.storage .from(bucket) .upload(path, fileToUpload, { upsert: true }); if (error) { console.error("❌ Error uploading photo a Supabase:", error); // Analizar error para dar mejor mensaje if (error.message && error.message.includes("size")) { criticalErrors.push(`Foto ${i + 1}: Excede el tamaño máximo permitido por el servidor.`); } else { criticalErrors.push(`Foto ${i + 1}: Error de subida (${error.message || "Desconocido"})`); } continue; } const { data } = supabase.storage.from(bucket).getPublicUrl(path); if (data?.publicUrl) { uploadedUrls.push(data.publicUrl); successCount++; console.log("✅ Subida completada:", data.publicUrl); } } // Manejo de errores globales if (criticalErrors.length > 0) { console.warn("⚠️ Hubo errores subiendo fotos:", criticalErrors); // Si fallaron todas, devolvemos un error especial que bloquea el guardado if (successCount === 0) { return { error: "ALL_FAILED", details: criticalErrors.join("\n") }; } // Si solo fallaron algunas, avisamos pero permitimos guardar las que pasaron setStatus({ type: "warning", message: `Atención: ${criticalErrors.length} foto(s) no se pudieron subir. Se guardarán solo las exitosas.` }); // Breve pausa para lectura await new Promise(r => setTimeout(r, 2000)); } // Retorna string JSON solo si hay urls validas return uploadedUrls.length > 0 ? JSON.stringify(uploadedUrls) : null; }; const handlePhotoSelect = (event) => { const files = Array.from(event.target.files || []); const remainingSlots = MAX_PHOTOS - fotoFiles.length; const filesToAdd = files.slice(0, remainingSlots); if (filesToAdd.length > 0) { setFotoFiles(prev => [...prev, ...filesToAdd]); } if (files.length > remainingSlots) { setStatus({ type: "warning", message: `Solo puedes agregar ${remainingSlots} foto(s) más. Máximo ${MAX_PHOTOS} fotos por reporte.` }); } // Reset input event.target.value = ''; }; const removePhoto = (index) => { setFotoFiles(prev => prev.filter((_, i) => i !== index)); }; const handleVerFoto = (fotoUrlJson) => { if (!fotoUrlJson) { setStatus({ type: "warning", message: "Este reporte no tiene fotos adjuntas.", }); return; } try { // Intentar parsear como JSON array const urls = JSON.parse(fotoUrlJson); if (Array.isArray(urls)) { // Abrir cada foto en una nueva pestaña urls.forEach(url => { if (url) window.open(url, "_blank", "noopener,noreferrer"); }); } else { // Si no es array, abrir como URL única (backward compatibility) window.open(fotoUrlJson, "_blank", "noopener,noreferrer"); } } catch (e) { // Si falla el parse, asumir que es una URL única window.open(fotoUrlJson, "_blank", "noopener,noreferrer"); } }; const handleEditarReporte = (reporte) => { if (!reporte) return; setEditingReporte(reporte); setSelection({ procesoId: reporte.proceso_id || null, plantaId: reporte.planta_id || null, areaId: reporte.planta_area_id || null, lineaId: reporte.planta_linea_id || null, equipoId: reporte.equipo_id || null, conjuntoCodigo: reporte.conjunto_codigo || null, parteCodigo: reporte.parte_codigo || null, sintomaFamiliaId: reporte.sintoma_familia_id || null, sintomaId: reporte.sintoma_id || null, causaFamiliaId: reporte.causa_familia_id || null, causaId: reporte.causa_id || null, }); setFormData({ textoBreve: reporte.texto_breve || "", textoExt: reporte.texto_extendido || "", estadoUsuario: reporte.estado_usuario || "CEDC", ejecutorId: reporte.ejecutor_id || "", fechaInicio: reporte.fecha_inicio_programada || "", fechaFin: reporte.fecha_fin_programada || "", danoOperativo: reporte.dano_operativo || "NO", // Si tiene JSON array, no podemos cargar las fotos al fotoFiles porque son URLs remotas // El usuario tendrá que borrarlas todas para subir nuevas, o agregamos logica compleja // Por ahora, fotoFiles empieza vacío. }); // Resetear fotos nuevas setFotoFiles([]); setStatus({ type: "warning", message: "Editando reporte existente. Usa Actualizar o cancela cambios.", }); window.scrollTo({ top: 0, behavior: "smooth" }); }; const handleBorrarReporte = async (id) => { if (!id) return; const confirmed = window.confirm( "¿Quieres eliminar este reporte? Esta accion no se puede deshacer." ); if (!confirmed) return; setTableActionId(id); try { let tableName = "reporte_produccion"; // Intentamos deducir de donde borrar basandonos en el reporte actual o el proceso seleccionado // Si estamos en la vista de lista, 'reportes' contiene objetos mixtos si hubieramos unificado, // pero ahora 'reportes' depende del modo actual. // Si 'procesoActual' es distribucion, borramos de tabla distribucion. const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); if (isDistribucion) tableName = "reporte_distribucion"; const { error } = await supabase.from(tableName).delete().eq("id", id); if (error) throw error; setReportes((prev) => prev.filter((item) => item.id !== id)); if (editingReporte?.id === id) { setEditingReporte(null); } setStatus({ type: "success", message: "Reporte eliminado." }); } catch (error) { console.error(error); setStatus({ type: "error", message: "No se pudo eliminar el reporte.", }); } finally { setTableActionId(null); } }; const handleGuardar = async () => { const isDistribucion = procesoActual?.nombre?.toLowerCase().includes("distribucion"); const isEntrega = procesoActual?.nombre?.toLowerCase().includes("entrega"); // 1. Validar Planta/Proceso (Base) if (!selection.procesoId || !selection.plantaId) { setStatus({ type: "warning", message: "Faltan datos de planta/proceso." }); return; } // 2. Validar Jerarquia Especifica if (!isDistribucion && !isEntrega) { // Produccion if (!selection.areaId || !selection.lineaId) { setStatus({ type: "warning", message: "Selecciona Area y Linea." }); return; } } // 3. Validar Equipo (Comun a todos) if (!selection.equipoId) { setStatus({ type: "warning", message: "Debes seleccionar un Equipo." }); return; } // 4. Validar Componentes y Taxonomia (TODOS OBLIGATORIOS) if ( !selection.conjuntoCodigo || !selection.parteCodigo || !selection.sintomaFamiliaId || !selection.sintomaId || !selection.causaFamiliaId || !selection.causaId ) { setStatus({ type: "warning", message: "Completa Conjunto, Parte, Sintomas y Causas." }); return; } // 5. Validar Textos y Estado if (!formData.estadoUsuario) { setStatus({ type: "warning", message: "Debes seleccionar un Estado / Acción." }); return; } if ((formData.estadoUsuario === "CEDC" || formData.estadoUsuario === "PROG") && !formData.ejecutorId) { setStatus({ type: "warning", message: "Selecciona el Ejecutor." }); return; } if (!formData.textoBreve || !formData.textoBreve.trim()) { setStatus({ type: "warning", message: "El Texto Breve es obligatorio." }); return; } if (!formData.textoExt || !formData.textoExt.trim()) { setStatus({ type: "warning", message: "El Texto Extendido es obligatorio." }); return; } // Dano Operativo tiene default "NO", asi que siempre tiene valor. const editingId = editingReporte?.id || null; setSaving(true); setStatus(defaultStatus()); try { const fotoUrlResult = await uploadPhotoIfNeeded(); // VALIDACION CRITICA: Si seleccionó fotos pero falló la subida de todas if (fotoUrlResult && typeof fotoUrlResult === 'object' && fotoUrlResult.error === "ALL_FAILED") { throw new Error("No se pudieron subir las fotos:\n" + fotoUrlResult.details); } if (fotoFiles.length > 0 && fotoUrlResult === "ERROR_ALL_FAILED") { throw new Error("No se pudieron subir las fotos."); } // Si es exitoso, fotoUrlResult es un string JSON const fotoUrl = (typeof fotoUrlResult === 'string') ? fotoUrlResult : null; console.log("💾 handleGuardar - fotoUrl:", fotoUrl); console.log("💾 handleGuardar - editingId:", editingId); console.log("💾 handleGuardar - editingReporte?.foto_url:", editingReporte?.foto_url); const payload = { usuario_id: session.user.id, proceso_id: selection.procesoId, planta_id: selection.plantaId, sintoma_familia_id: selection.sintomaFamiliaId, sintoma_id: selection.sintomaId, causa_familia_id: selection.causaFamiliaId, causa_id: selection.causaId, // Codigos (para referencia rapida en VBS) codigo_sintoma_familia: sintomaFamiliaSeleccionada?.codigo || null, codigo_sintoma: sintomaSeleccionado?.codigo || null, codigo_causa_familia: causaFamiliaSeleccionada?.codigo || null, codigo_causa: causaSeleccionada?.codigo || null, texto_breve: formData.textoBreve, texto_extendido: formData.textoExt, estado_usuario: formData.estadoUsuario, // Nuevos Campos ejecutor_id: (formData.estadoUsuario === "CEDC" || formData.estadoUsuario === "PROG") ? formData.ejecutorId : null, fecha_inicio_programada: formData.fechaInicio || null, fecha_fin_programada: formData.fechaFin || null, // Foto: usar nueva si existe, sino mantener antigua al editar foto_url: fotoUrl || (editingId ? editingReporte?.foto_url : null) || null, }; // LOGICA SUFIJO SUB_EQUIPO (CAM/MEZ) // Buscamos si el textoBreve actual coincide con alguna sugerencia para obtener su sub_equipo let sufijoSubEquipo = ""; if (formData.textoBreve) { const found = sugerenciasTexto.find((s) => s.texto === formData.textoBreve); if (found && found.sub) { sufijoSubEquipo = found.sub; // "CAM" o "MEZ" } } let tableName = "reporte_produccion"; if (isDistribucion) { tableName = "reporte_distribucion"; // Campos de equipo/conjunto/parte tambien van en distribucion si existen columnas // Asumimos que la tabla reporte_distribucion tiene estas columnas ya que el usuario lo pidio. payload.equipo_id = selection.equipoId; payload.conjunto_codigo = conjuntoSeleccionado?.codigo || null; payload.conjunto_nombre = conjuntoSeleccionado?.nombre || null; payload.parte_codigo = parteSeleccionada?.codigo_parte || null; payload.parte_nombre = parteSeleccionada?.nombre_parte || null; payload.dano_operativo = formData.danoOperativo || "NO"; payload.codigo_equipo = (equipoSeleccionado?.codigo || "") + sufijoSubEquipo; } else if (isEntrega) { tableName = "reporte_entrega"; payload.equipo_id = selection.equipoId; payload.conjunto_codigo = conjuntoSeleccionado?.codigo || null; payload.conjunto_nombre = conjuntoSeleccionado?.nombre || null; payload.parte_codigo = parteSeleccionada?.codigo_parte || null; payload.parte_nombre = parteSeleccionada?.nombre_parte || null; payload.dano_operativo = formData.danoOperativo || "NO"; payload.codigo_equipo = (equipoSeleccionado?.codigo || "") + sufijoSubEquipo; } else { // Campos extra solo para produccion (jerarquia completa) payload.planta_area_id = selection.areaId; payload.planta_linea_id = selection.lineaId; payload.equipo_id = selection.equipoId; payload.conjunto_codigo = conjuntoSeleccionado?.codigo || null; payload.conjunto_nombre = conjuntoSeleccionado?.nombre || null; payload.parte_codigo = parteSeleccionada?.codigo_parte || null; payload.parte_nombre = parteSeleccionada?.nombre_parte || null; payload.codigo_equipo = (equipoSeleccionado?.codigo || "") + sufijoSubEquipo; } let error; if (editingId) { ({ error } = await supabase .from(tableName) .update(payload) .eq("id", editingId)); } else { ({ error } = await supabase.from(tableName).insert(payload)); } if (error) throw error; setStatus({ type: "success", message: editingId ? "Reporte actualizado correctamente." : "Reporte guardado correctamente.", }); resetEditingState(); await fetchReportes(); // Actualizar lista y esperar } catch (error) { console.error(error); setStatus({ type: "error", message: error.message || "No se pudo guardar el reporte.", }); } finally { setSaving(false); } }; if (!session) { return (
ARGOS PRODUCCION

Centro maestro de reportes

Plataforma inteligente para la gestion y visualizacion de inspecciones digitales en tiempo real.

{HERO_FEATURES.map((feature) => (

{feature.title}

{feature.description}

))}

Bienvenido

Ingresa tus credenciales para continuar

setAuthForm((prev) => ({ ...prev, email: event.target.value })) } required />
setAuthForm((prev) => ({ ...prev, password: event.target.value })) } required />

o

¿Necesitas ayuda? Contacta soporte tecnico

setStatus(defaultStatus())} />
); } if (loadingProfile || hydrating || !profile) { return (
); } if (screen === "process") { return (

ARGOS Sistema de gestion

PROCESO

Hola {profile.nombre || session.user.email}, elige el proceso que deseas gestionar.

{['admin', 'super_admin'].includes(profile.rol) && ( )}
{PROCESS_CARDS.map((card) => ( ))}
{showAdminPanel && ( setShowAdminPanel(false)} supabase={supabase} profile={profile} /> )}
); } if (screen === "selection") { return (

{procesoActual?.nombre ? procesoActual.nombre.toUpperCase() : "PLANTAS"}

handleSelectionChange("areaId", id)} disabled={!selection.plantaId} iconMap={AREA_ICONS} />
); } return (

ARGOS Produccion

{screen === "form" && procesoActual ? procesoActual.nombre.toUpperCase() : "Reportes"}

{!(procesoActual?.nombre?.toLowerCase().includes("distribucion") || procesoActual?.nombre?.toLowerCase().includes("entrega")) ? ( <> {areaRequiresLinea && ( handleSelectionChange("lineaId", value)} options={lineas} disabled={!selection.areaId} placeholder="Selecciona la linea" /> )} handleSelectionChange("equipoId", value)} options={equipos} disabled={areaRequiresLinea ? !selection.lineaId : !selection.areaId} placeholder="Selecciona el equipo" /> handleSelectionChange("conjuntoCodigo", value)} options={conjuntos.map((item) => ({ id: item.codigo, nombre: item.nombre, value: item.codigo, }))} disabled={!selection.equipoId} placeholder="Selecciona el conjunto" customValue /> handleSelectionChange("parteCodigo", value)} options={partes.map((item) => ({ id: item.codigo_parte, nombre: item.nombre_parte, value: item.codigo_parte, }))} disabled={!selection.conjuntoCodigo} placeholder="Selecciona la parte" customValue /> ) : ( <> {/* Campos para Distribucion/Entrega (sin Area/Linea) */} handleSelectionChange("equipoId", value)} options={equipos} disabled={!selection.plantaId} placeholder="Selecciona el equipo" /> handleSelectionChange("conjuntoCodigo", value)} options={conjuntos.map((item) => ({ id: item.codigo, nombre: item.nombre, value: item.codigo, }))} disabled={!selection.equipoId} placeholder="Selecciona el conjunto" customValue /> handleSelectionChange("parteCodigo", value)} options={partes.map((item) => ({ id: item.codigo_parte, nombre: item.nombre_parte, value: item.codigo_parte, }))} disabled={!selection.conjuntoCodigo} placeholder="Selecciona la parte" customValue /> )}
handleSelectionChange("sintomaFamiliaId", value)} options={catalogos.sintomaFamilias} placeholder="Elige la familia" /> handleSelectionChange("sintomaId", value)} options={sintomasDisponibles} disabled={!selection.sintomaFamiliaId} placeholder="Selecciona el sintoma" />
handleSelectionChange("causaFamiliaId", value)} options={catalogos.causaFamilias} placeholder="Elige la familia" /> handleSelectionChange("causaId", value)} options={causasDisponibles} disabled={!selection.causaFamiliaId} placeholder="Selecciona la causa" />
{/* Distribucion: 3 columnas (Estado, Daño, Prioridad REMOVIDA -> Solo 2 + Espacio o reajuste) Usuario dijo: "elimina este campo [Priority] ya que lo remplaza el campo Estado / Acción" Y "tambien realizalo con el proceso produccion" Nueva Estructura: Distribucion: [Estado] [Daño Operativo] Produccion: [Estado] */} {isDistribucion ? (
setFormData((prev) => ({ ...prev, estadoUsuario: value }))} options={[ { id: "CEDC", nombre: "EJECUTADO", value: "CEDC" }, { id: "PLAN", nombre: "PLANEAR", value: "PLAN" }, { id: "PROG", nombre: "PROGRAMADO", value: "PROG" } ]} customValue placeholder="Estado" /> {(formData.estadoUsuario === "CEDC" || formData.estadoUsuario === "PROG") && ( setFormData((prev) => ({ ...prev, ejecutorId: value }))} options={catalogos.ejecutores} placeholder={formData.estadoUsuario === "PROG" ? "Quien ejecutará?" : "Quien ejecutó?"} /> )} setFormData((prev) => ({ ...prev, danoOperativo: value }))} options={[ { id: "SI", nombre: "SI", value: "SI" }, { id: "NO", nombre: "NO", value: "NO" } ]} customValue placeholder="¿Daño?" />
) : (
setFormData((prev) => ({ ...prev, estadoUsuario: value }))} options={[ { id: "CEDC", nombre: "EJECUTADO", value: "CEDC" }, { id: "PLAN", nombre: "PLANEAR", value: "PLAN" }, { id: "PROG", nombre: "PROGRAMADO", value: "PROG" } ]} customValue placeholder="Selecciona acción" /> {(formData.estadoUsuario === "CEDC" || formData.estadoUsuario === "PROG") && ( setFormData((prev) => ({ ...prev, ejecutorId: value }))} options={catalogos.ejecutores} placeholder={formData.estadoUsuario === "PROG" ? "Quien ejecutará?" : "Quien ejecutó?"} /> )}
)}
{/* Layout change: Full width for Textfield */} { setFormData((prev) => ({ ...prev, textoBreve: value.slice(0, 40) })); // --- Reverse Lookup Auto-fill --- // Verifica si el texto ingresado coincide exactamente con una sugerencia const match = sugerenciasTexto.find(s => s.texto === value); if (match && selection.equipoId) { // Si encuentra coincidencia y tenemos equipo seleccionado, intentamos resolver Conjunto/Parte // Necesitamos buscar en el cache de partes del equipo actual const equipo = equipos.find(e => e.id === selection.equipoId); if (equipo) { const prefix = equipo.codigo.slice(0, 5); const partsList = partesCache[prefix] || []; // Buscamos la fila que coincida con conjuntoName y parteName que vienen de la tarea estandar // Normalización ROBUSTA (sin tildes, minusculas, trim) const targetPart = partsList.find(p => normalizeStr(p.conjunto_nombre) === normalizeStr(match.conjuntoName) && normalizeStr(p.nombre_parte) === normalizeStr(match.parteName) ); if (targetPart) { // Actualizamos seleccion. Esto disparará los useEffect en cadena para actualizar conjuntos/partes setSelection(prev => ({ ...prev, conjuntoCodigo: targetPart.conjunto_codigo, parteCodigo: targetPart.codigo_parte })); } } } }} placeholder="Descripcion corta para SAP" list="sugerencias-texto-breve" /> {sugerenciasTexto.map((item, index) => (
{/* Layout change: Full width for TextArea */} setFormData((prev) => ({ ...prev, textoExt: value }))} placeholder="Detalle completo de la novedad" className="wide-field" />
Foto
= MAX_PHOTOS} /> = MAX_PHOTOS} /> {fotoFiles.length > 0 && (
{fotoFiles.length} foto(s) seleccionada(s) (máx. {MAX_PHOTOS})
{fotoFiles.map((file, index) => (
{`Foto {file.name}
))}
)}

Reportes creados

{loadingReportes && Actualizando...}
{reportes.length === 0 ? (

Aun no se han registrado reportes o no tienes permisos para verlos.

) : (
{reportes.map((reporte) => { const cardTitle = (reporte.texto_breve || "Reporte").toUpperCase(); const formattedDate = reporte.created_at ? formatDate(new Date(reporte.created_at)) : null; const badgeLabel = formatPriorityLabel(reporte.estado_usuario) || "SIN ESTADO"; // USAR NOMBRE DEL CREADOR (o fallback a desconocido) const profileName = reporte.creatorName || "Desconocido"; const priorityClass = getPriorityClass(reporte.estado_usuario); // LOGICA VISUAL CAMPO LINEA let lineaText = reporte.lineaNombre; if (!lineaText || lineaText === "N/A" || lineaText === "Sin linea") { // Si es N/A, mostrar displayLinea (que tiene la Planta) lineaText = reporte.displayLinea || "General"; } return (

{formattedDate || "Fecha pendiente"}

{cardTitle}

Equipo: {reporte.equipoNombre || "Sin equipo"}

Linea: {lineaText}

{badgeLabel}
Taller: {profileName} Conjunto: {reporte.conjunto_nombre || "Sin conjunto"} Parte: {reporte.parte_nombre || "Sin parte"}
{reporte.foto_url ? (() => { console.log("🖼️ Reporte foto_url:", reporte.foto_url); try { const urls = JSON.parse(reporte.foto_url); console.log("✅ Parsed URLs:", urls); if (Array.isArray(urls) && urls.length > 0) { return (
{urls.map((url, idx) => ( {`Foto ))}
); } } catch (e) { console.error("❌ Error parsing foto_url:", e); console.log("📝 foto_url value:", reporte.foto_url); // Si falla el parse, mostrar como URL única return ( {`Foto ); } return null; })() : (
Sin foto registrada
)}
); })}
)}
setStatus(defaultStatus())} /> {/* Admin Modal */} {showAdminPanel && ( setShowAdminPanel(false)} supabase={supabase} profile={profile} /> )}
); } function buildConjuntosFromPartes(dataset) { const seen = new Set(); const unique = []; dataset.forEach((row) => { if (!row.conjunto_codigo || seen.has(row.conjunto_codigo)) return; seen.add(row.conjunto_codigo); unique.push({ codigo: row.conjunto_codigo, nombre: row.conjunto_nombre }); }); return unique; } function normalizeStr(str) { if (!str) return ""; return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim(); } function CircleOptions({ title, options, value, onSelect, disabled, iconMap = {} }) { const fallbackIcon = iconMap.DEFAULT || "https://api.jdos.online/storage/v1/object/public/DATA/planta.png"; const normalize = (text) => text?.toUpperCase() || ""; return (

{title}

{options.length === 0 ? (

{disabled ? "Selecciona una planta para ver las areas." : "Sin areas disponibles."}

) : ( options.map((option) => { const key = normalize(option.nombre); const icon = iconMap[key] || iconMap[option.nombre] || fallbackIcon; const isActive = value === option.id; return ( ); }) )}
); } function SelectField({ label, value, onChange, options, disabled, placeholder = "Selecciona una opcion", className = "", customValue = false, }) { return (
); } function TextField({ label, value, onChange, placeholder, type = "text", list }) { return (
onChange(event.target.value.toUpperCase())} placeholder={placeholder} list={list} autoComplete="off" />
); } function TextAreaField({ label, value, onChange, placeholder, className = "" }) { return (