Pull Lino

This commit is contained in:
jls846
2025-11-18 16:14:54 -06:00
13 changed files with 1324 additions and 504 deletions
+31 -14
View File
@@ -6,7 +6,6 @@ import axios from "axios";
import Cookies from "js-cookie";
import toast from "react-hot-toast";
import { AREAS } from "@/data/areas";
import { SO_POR_EQUIPO } from "@/data/so_por_equipo";
import { PROCESADORES_POR_EQUIPO } from "@/data/procesadores";
import { useRouter } from "next/navigation";
@@ -133,7 +132,6 @@ export default function Page() {
setAdscripciones(adscripcionesRes.data);
setTiposEquipo(tiposEquipoRes.data);
setSistemasOperativos(sistemasOperativosRes.data);
console.log(adscripciones);
} catch (err) {
if (axios.isAxiosError(err)) {
if (err.response) {
@@ -200,9 +198,15 @@ export default function Page() {
const textValue = value as string;
setAdscripcionLabel(textValue);
const matches = AREAS.filter((a) =>
a.label.toLowerCase().includes(textValue.toLowerCase())
).map((a) => a.label);
const normalize = (str: string) =>
str
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
const matches = adscripciones
.filter((a) => normalize(a.adscripcion).includes(normalize(textValue)))
.map((a) => a.adscripcion);
setSuggestions((prev) => ({
...prev,
@@ -234,7 +238,6 @@ export default function Page() {
};
const handleGuardar = async () => {
if (!formData.inventario) {
toast.error("Inventario no encontrado");
return;
@@ -272,7 +275,11 @@ export default function Page() {
}
}
if(formData.id_tipo_equipo != 8 && formData.id_tipo_equipo != 7 && formData.id_tipo_equipo != 9){
if (
formData.id_tipo_equipo != 8 &&
formData.id_tipo_equipo != 7 &&
formData.id_tipo_equipo != 9
) {
if (!formData.id_sistema_operativo) {
toast.error("Sistema operativo no encontrado ");
return;
@@ -422,13 +429,17 @@ export default function Page() {
<div className="formGroup">
<label>Procesador</label>
<select
disabled={formData.id_tipo_equipo ==0}
disabled={formData.id_tipo_equipo == 0}
value={formData.id_procesador}
onChange={(e) =>
handleInputChange("id_procesador", e.target.value)
}
>
<option value="">{formData.id_tipo_equipo ? "Selecciona procesador":"Selecciona primero el tipo de equipo"}</option>
<option value="">
{formData.id_tipo_equipo
? "Selecciona procesador"
: "Selecciona primero el tipo de equipo"}
</option>
{PROCESADORES_POR_EQUIPO[formData.id_tipo_equipo]?.map(
(p) => (
<option key={p.id_procesador} value={p.id_procesador}>
@@ -442,7 +453,7 @@ export default function Page() {
<div className="formGroup">
<label>Sistema operativo</label>
<select
disabled={formData.id_tipo_equipo ==0}
disabled={formData.id_tipo_equipo == 0}
value={formData.id_sistema_operativo}
onChange={(e) =>
handleInputChange(
@@ -451,7 +462,11 @@ export default function Page() {
)
}
>
<option value="">{formData.id_tipo_equipo ? "Selecciona sistema operativo":"Selecciona primero el tipo de equipo"}</option>
<option value="">
{formData.id_tipo_equipo
? "Selecciona sistema operativo"
: "Selecciona primero el tipo de equipo"}
</option>
{SO_POR_EQUIPO[formData.id_tipo_equipo]?.map((so) => (
<option
key={so.id_sistema_operativo}
@@ -502,13 +517,15 @@ export default function Page() {
<li
key={s}
onClick={() => {
const selected = AREAS.find((a) => a.label === s);
const selected = adscripciones.find(
(a) => a.adscripcion === s
);
if (selected) {
setFormData((prev) => ({
...prev,
id_adscripcion: selected.id,
id_adscripcion: selected.id_adscripcion,
}));
setAdscripcionLabel(selected.label);
setAdscripcionLabel(selected.adscripcion);
setSuggestions((prev) => ({
...prev,
adscripcion: [],
+138 -66
View File
@@ -1,99 +1,171 @@
"use client";
import React, { useEffect, useRef } from "react";
import Quagga from "@ericblade/quagga2";
// Tipos para la configuración (opcional, pero mejora legibilidad)
interface QuaggaConfig {
inputStream: {
name: string;
type: string;
target: HTMLElement;
constraints: MediaTrackConstraints;
};
decoder: {
readers: string[];
};
locate: boolean;
}
import { useEffect, useRef } from "react";
import toast from "react-hot-toast";
import { BrowserMultiFormatReader } from "@zxing/browser";
import { NotFoundException } from "@zxing/library";
interface BarcodeScannerProps {
onScan: (code: string) => void;
}
export default function BarcodeScanner({ onScan }: BarcodeScannerProps) {
const scannerRef = useRef<HTMLDivElement>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const lastScan = useRef<string | null>(null);
useEffect(() => {
if (!scannerRef.current) return;
let stream: MediaStream | null = null;
let animationFrame: number;
let zxingReader: BrowserMultiFormatReader | null = null;
let detector: any = null;
const config: QuaggaConfig = {
inputStream: {
name: "Live",
type: "LiveStream",
target: scannerRef.current,
constraints: {
width: 300,
height: 500,
facingMode: "environment",
},
},
decoder: {
readers: ["code_128_reader"],
},
locate: true,
};
// El callback de error: tipamos `err` como `unknown`
const initCallback = (err: unknown) => {
if (err) {
console.error("Error al iniciar el escáner:", err);
return;
/** 🚀 Intentar usar BarcodeDetector API (moderno) */
const startBarcodeDetector = async () => {
const supported = "BarcodeDetector" in window;
if (!supported) {
console.warn("BarcodeDetector no soportado, usando ZXing...");
return startZXing();
}
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: "environment" } }, // 👈 fuerza cámara trasera
});
if (!videoRef.current) return;
videoRef.current.srcObject = stream;
await new Promise<void>((resolve) => {
videoRef.current!.onloadedmetadata = async () => {
try {
await videoRef.current!.play();
resolve();
} catch {
resolve();
}
};
});
detector = new (window as any).BarcodeDetector({
formats: ["code_128", "ean_13", "qr_code"],
});
const detect = async () => {
if (!videoRef.current) return;
if (videoRef.current.readyState < 2) {
animationFrame = requestAnimationFrame(detect);
return;
}
try {
const barcodes = await detector.detect(videoRef.current);
if (barcodes.length > 0) {
const raw = barcodes[0].rawValue.trim();
if (raw && raw !== lastScan.current) {
lastScan.current = raw;
const processed = raw.startsWith("0") ? raw.slice(1) : raw;
onScan(processed);
}
}
} catch (err: any) {
if (
err.message?.includes("Invalid element") ||
err.name === "InvalidStateError"
) {
// ignorar mientras no haya frame válido
} else {
console.error("Error detectando código:", err);
}
}
animationFrame = requestAnimationFrame(detect);
};
detect();
} catch (err) {
console.error("Error con BarcodeDetector:", err);
toast.error("No se pudo acceder a la cámara");
startZXing();
}
Quagga.start();
};
Quagga.init(config as unknown as Record<string, unknown>, initCallback);
const startZXing = async () => {
try {
zxingReader = new BrowserMultiFormatReader();
const devices = await BrowserMultiFormatReader.listVideoInputDevices();
// Handler de detección: `data` es `unknown`, lo validamos
const handleDetected = (data: unknown) => {
// Validación segura
if (
data != null &&
typeof data === "object" &&
"codeResult" in data &&
data.codeResult != null &&
typeof data.codeResult === "object" &&
"code" in data.codeResult &&
typeof data.codeResult.code === "string"
) {
const code = data.codeResult.code.trim();
if (code !== "") {
onScan(code);
Quagga.stop(); // Opcional
if (devices.length === 0) {
toast.error("No se detectó cámara disponible");
return;
}
// 🔍 Intentar encontrar la cámara trasera
const backCamera =
devices.find((d) =>
d.label.toLowerCase().includes("back")
) || devices.find((d) =>
d.label.toLowerCase().includes("rear")
) || devices[devices.length - 1]; // fallback
const selectedDeviceId = backCamera.deviceId;
console.log("Usando cámara:", backCamera.label || selectedDeviceId);
await zxingReader.decodeFromVideoDevice(
selectedDeviceId,
videoRef.current!,
(result, err) => {
if (result) {
const code = result.getText().trim();
if (code && code !== lastScan.current) {
lastScan.current = code;
const processed = code.startsWith("0") ? code.slice(1) : code;
onScan(processed);
}
} else if (err && !(err instanceof NotFoundException)) {
console.error("Error ZXing:", err);
}
}
);
} catch (error) {
console.error("Error al iniciar ZXing:", error);
toast.error("Error al iniciar escáner");
}
};
Quagga.onDetected(handleDetected);
startBarcodeDetector();
return () => {
Quagga.offDetected(handleDetected);
Quagga.stop();
if (animationFrame) cancelAnimationFrame(animationFrame);
if (stream) stream.getTracks().forEach((t) => t.stop());
if (zxingReader) {
if (typeof (zxingReader as any).stopContinuousDecode === "function") {
(zxingReader as any).stopContinuousDecode();
} else if (typeof (zxingReader as any).reset === "function") {
(zxingReader as any).reset();
}
}
};
}, [onScan]);
return (
<div
ref={scannerRef}
<video
ref={videoRef}
style={{
width: "100%",
maxWidth: "500px",
maxWidth: "480px",
height: "300px",
borderRadius: "12px",
overflow: "hidden",
border: "2px solid #0056b3",
borderRadius: "12px",
objectFit: "cover",
}}
muted
playsInline
/>
);
}
//Chatgpt
//Gemini
+43 -27
View File
@@ -7,7 +7,6 @@ import axios from "axios";
import Cookies from "js-cookie";
import toast from "react-hot-toast";
import { SO_POR_EQUIPO } from "@/data/so_por_equipo";
import { AREAS } from "@/data/areas";
import { PROCESADORES_POR_EQUIPO } from "@/data/procesadores";
import "../app/styles/layout/agregarEquipo.scss";
@@ -34,7 +33,7 @@ export default function Editar() {
observaciones: "",
responsable: "",
id_periferico: 0,
fechaFactura: "",
fechaMovimiento: "",
});
interface TipoUso {
@@ -159,7 +158,7 @@ export default function Editar() {
inventario: string;
serie: string;
lugar: string;
fechaFactura: string;
fechaMovimiento: string;
antiguedad: string;
modelo: string;
estado: { id_estado: number; estado: string };
@@ -213,20 +212,20 @@ export default function Editar() {
setFormData({
inventario: equipo.inventario || "",
serie: equipo.serie || "",
serie: (equipo.serie || "").toUpperCase(),
id_marca: equipo.marca?.id_marca || 0,
modelo: equipo.modelo || "",
id_tipo_equipo: equipo.tipoEquipo?.id_tipo_de_equipo || 0,
id_estado: equipo.estado?.id_estado || 0,
id_sistema_operativo:
equipo.sistemaOperativo?.id_sistema_operativo || 0,
id_procesador:equipo.procesador?.id_procesador || 0,
id_procesador: equipo.procesador?.id_procesador || 0,
id_uso: equipo.tipoUso?.id_uso || 0,
observaciones: equipo.observaciones || "",
id_adscripcion: equipo.adscripcion?.id_adscripcion || 0,
lugar: equipo.lugar || "",
id_periferico: equipo.periferico?.id_periferico || 0,
fechaFactura: equipo.fechaFactura || "",
fechaMovimiento: equipo.fechaMovimiento || "",
responsable,
});
@@ -280,8 +279,6 @@ export default function Editar() {
}, [formData.id_tipo_equipo]);
const handleGuardar = async () => {
if (!formData.inventario) {
toast.error("Inventario no encontrado");
return;
@@ -319,7 +316,11 @@ export default function Editar() {
}
}
if (formData.id_tipo_equipo != 8 && formData.id_tipo_equipo != 7 && formData.id_tipo_equipo != 9) {
if (
formData.id_tipo_equipo != 8 &&
formData.id_tipo_equipo != 7 &&
formData.id_tipo_equipo != 9
) {
if (!formData.id_sistema_operativo) {
toast.error("Sistema operativo no encontrado ");
return;
@@ -350,14 +351,11 @@ export default function Editar() {
};
try {
await axios.patch(
`${api_url}/equipos/update/${id}`,
data,
{
headers,
}
);
await axios.patch(`${api_url}/equipos/update/${id}`, data, {
headers,
});
toast.success("Equipo actualizado correctamente");
router.push("/escaner");
} catch (error) {
console.error("Error al guardar:", error);
toast.error("Hubo un error al guardar el equipo.");
@@ -372,9 +370,15 @@ export default function Editar() {
const textValue = value as string;
setAdscripcionLabel(textValue);
const matches = AREAS.filter((a) =>
a.label.toLowerCase().includes(textValue.toLowerCase())
).map((a) => a.label);
const normalize = (str: string) =>
str
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
const matches = adscripciones
.filter((a) => normalize(a.adscripcion).includes(normalize(textValue)))
.map((a) => a.adscripcion);
setSuggestions((prev) => ({
...prev,
@@ -425,8 +429,9 @@ export default function Editar() {
const first = data[0];
setFormData((prev) => ({
...prev,
responsable: `${first.nombre ?? ""} ${first.apellidos ?? ""
}`.trim(),
responsable: `${first.nombre ?? ""} ${
first.apellidos ?? ""
}`.trim(),
}));
} else {
toast.error(
@@ -460,7 +465,9 @@ export default function Editar() {
<span>Inventario: {formData.inventario}</span>
<span>
Fecha de censo:{" "}
{new Date(formData.fechaFactura).toLocaleDateString("es-MX")}
{formData.fechaMovimiento
? new Date(formData.fechaMovimiento).toLocaleDateString("es-MX")
: "Sin fecha"}
</span>
</h2>
@@ -474,7 +481,10 @@ export default function Editar() {
value={formData.serie}
placeholder="Ingresa serie"
onChange={(e) =>
setFormData({ ...formData, serie: e.target.value })
setFormData({
...formData,
serie: e.target.value.toUpperCase(),
})
}
/>
</div>
@@ -578,7 +588,11 @@ export default function Editar() {
handleInputChange("id_procesador", e.target.value)
}
>
<option value="">{formData.id_tipo_equipo ? "Selecciona procesador" : "Selecciona primero el tipo de equipo"}</option>
<option value="">
{formData.id_tipo_equipo
? "Selecciona procesador"
: "Selecciona primero el tipo de equipo"}
</option>
{PROCESADORES_POR_EQUIPO[formData.id_tipo_equipo]?.map(
(p) => (
<option key={p.id_procesador} value={p.id_procesador}>
@@ -648,13 +662,15 @@ export default function Editar() {
<li
key={s}
onClick={() => {
const selected = AREAS.find((a) => a.label === s);
const selected = adscripciones.find(
(a) => a.adscripcion === s
);
if (selected) {
setFormData((prev) => ({
...prev,
id_adscripcion: selected.id,
id_adscripcion: selected.id_adscripcion,
}));
setAdscripcionLabel(selected.label);
setAdscripcionLabel(selected.adscripcion);
setSuggestions((prev) => ({
...prev,
adscripcion: [],