"use client"; import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { getPolicyOcrStatus, listPolicyOcrBatches, uploadPolicyOcrBatch, } from "@/lib/api"; import { useCan } from "@/lib/abilities"; import { formatDate } from "@/lib/labels"; import type { PolicyOcrBatch, PolicyOcrBatchStatus } from "@/lib/types"; /** * Insurance OCR intake — mirror of StatementIntake, scoped to the insurance * side. GMX and A.N.A. today; the parser dispatches on a brand wordmark * (`Grupo Mexicano de Seguros` / `gmx.com.mx`, `A.N.A. Compañía de Seguros` / * `anaseguros.com.mx`) and a new portal only needs a new BRAND entry plus a * parser file. The uploader is never asked which provider a file came from — * a batch may mix them, and the pipeline labels the batch from what the * parsers actually claimed. * * Lives inside the `Pólizas` page rather than a top-level route because it * is one mode of one job (staff uploading whatever PDFs the office has on * hand that day, mixed service vs insurance), and the matching/review queue * already keys on the policyNumber → existing Policy transition that the * rest of /polizas owns. */ const STATUS_LABEL: Record = { UPLOADED: "Recibido", PROCESSING: "Procesando…", READY_FOR_REVIEW: "Listo para revisar", COMPLETED: "Aplicado", FAILED: "Falló", DISCARDED: "Descartado", }; export function PolicyOcrIntake() { const canIngest = useCan("policy:ingest"); const [batches, setBatches] = useState([]); const [ocrAvailable, setOcrAvailable] = useState(null); const [storageAvailable, setStorageAvailable] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(async () => { try { const [list, status] = await Promise.all([ listPolicyOcrBatches(), getPolicyOcrStatus(), ]); setBatches(list.items); setOcrAvailable(status.ocrAvailable); setStorageAvailable(status.storageAvailable); setError(null); } catch (e) { setError((e as Error)?.message ?? "No se pudieron cargar los lotes."); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const working = batches.some( (b) => b.status === "PROCESSING" || b.status === "UPLOADED", ); useEffect(() => { if (!working) return; const t = setInterval(() => void load(), 4000); return () => clearInterval(t); }, [working, load]); const ready = ocrAvailable === true && storageAvailable === true; return (
{ocrAvailable === false && (
Este servidor no tiene OCR instalado, así que no se pueden leer PDFs de pólizas escaneados. La captura manual sigue funcionando.
)} {storageAvailable === false && (
Este servidor no tiene configurado el almacenamiento de documentos, así que no hay dónde guardar los PDFs. Mientras tanto, capture las pólizas a mano.
)} {canIngest && ready && } {error &&
{error}
}

Lotes

{loading ? (
Cargando…
) : batches.length === 0 ? (
Todavía no hay lotes de pólizas. Descargue la póliza del portal de GMX o de A.N.A. y suéltela arriba.
) : (
{batches.map((b) => ( ))}
Fecha Aseguradora Referencia Estado Páginas Subido por
{formatDate(b.createdAt)} {b.provider} {b.label || "—"} {b.error && (
{b.error}
)}
{b._count?.documents ?? 0} {b.uploadedBy?.name ?? "—"} Revisar
)}
); } function StatusTag({ status }: { status: PolicyOcrBatchStatus }) { return {STATUS_LABEL[status] ?? status}; } function UploadCard({ onDone }: { onDone: () => void }) { const [files, setFiles] = useState([]); const [label, setLabel] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); async function submit() { if (!files.length) return; setBusy(true); setError(null); try { await uploadPolicyOcrBatch(files, label.trim() || undefined); setFiles([]); setLabel(""); onDone(); } catch (e) { setError((e as Error)?.message ?? "No se pudo subir el lote."); } finally { setBusy(false); } } return (

Subir PDFs de pólizas (GMX / A.N.A.)

{error && (
{error}
)}

Un lote puede traer varios PDFs. Cada página se procesa por separado; el sistema busca una póliza existente por número y, si no la encuentra, propone crear una nueva bajo el cliente que se elija en la revisión.

); }