// ROCA GOLF — v2.0 · Importar OCs históricas (de otra plataforma)
// Sube el .xlsx (2 hojas = 2 empresas), previsualiza con mapeo difuso de PROYECTO,
// corrige y confirma. Lista con CRUD. Folio guardado con sufijo 'D'.
function moneyI(n) { try { return '$' + fmtMoney(Number(n) || 0); } catch { return '$' + (Number(n)||0).toFixed(2); } }
function ScoreBadge({ score }) {
if (score === null || score === undefined) return null;
const c = score >= 85 ? 'var(--rg-flag-ok)' : (score >= 60 ? 'var(--rg-flag-warn)' : 'var(--rg-flag-over)');
return {score}% ;
}
// ── Modal de edición de una OC importada ──────────────────────────────────────
function EditOCModal({ open, onClose, row, proyectos, onSaved }) {
const [f, setF] = useState(null);
useEffect(() => { if (open && row) setF({ ...row }); }, [open, row]);
if (!open || !f) return null;
const set = (k, v) => setF(s => ({ ...s, [k]: v }));
async function save() {
try {
await apiFetch('oc_import.php?id=' + f.id, 'PUT', {
empresa: f.empresa, folio: f.folio, fecha: f.fecha || null,
proyectoid: f.proyectoid, proveedor: f.proveedor, clave_insumo: f.clave_insumo,
descripcion: f.descripcion, total: parseFloat(f.total)||0,
pagado: parseFloat(f.pagado)||0, pendiente: parseFloat(f.pendiente)||0,
moneda: f.moneda || 'MXN', tc: parseFloat(f.tc)||1,
});
toast.success('OC actualizada'); onSaved(); onClose();
} catch (e) { toast.error(e.message); }
}
return (
Cancelar
Guardar >}>
Folio set('folio',e.target.value)}/>
Empresa set('empresa',e.target.value)}/>
Proyecto asignado
set('proyectoid', e.target.value)}>
— sin asignar —
{(proyectos||[]).map(p => {p.clave} · {p.nombre} )}
Texto original: {f.proyecto_texto}
Fecha set('fecha',e.target.value)}/>
Clave insumo set('clave_insumo',e.target.value)}/>
Proveedor set('proveedor',e.target.value)}/>
Descripción
Moneda
set('moneda', e.target.value)}>
MXN
USD
Tipo de cambio (1 USD = MXN)
set('tc',e.target.value)} disabled={f.moneda !== 'USD'}/>
Total {f.moneda==='USD'?'(USD)':'(MXN)'} set('total',e.target.value)}/>
Pagado {f.moneda==='USD'?'(USD)':'(MXN)'} set('pagado',e.target.value)}/>
Pendiente {f.moneda==='USD'?'(USD)':'(MXN)'} set('pendiente',e.target.value)}/>
{f.moneda==='USD' &&
Equivale a {moneyI((parseFloat(f.total)||0)*(parseFloat(f.tc)||1))} MXN (Total × TC).
}
);
}
function ImportOCView({ onNav, user, onLogout }) {
const { data: proyectos } = useApiData('proyectos.php');
const fileRef = useRef(null);
const [preview, setPreview] = useState(null); // { rows, proyectos, count }
const [uploading, setUploading] = useState(false);
const [saving, setSaving] = useState(false);
const [tcUsd, setTcUsd] = useState(17); // 1 USD = ? MXN (tipo de cambio)
const [empresa, setEmpresa] = useState('');
const [q, setQ] = useState('');
const [page, setPage] = useState(1);
const dq = useDebounce(q);
const { data: listData, loading, reload } = useApiData('oc_import.php', { empresa, q: dq, page, limit: 100 });
const rows = listData?.rows || [];
const resumen = listData?.resumen || [];
const pages = listData?.pages || 1;
const [editRow, setEditRow] = useState(null);
useEffect(() => setPage(1), [empresa, dq]);
async function handleFile(e) {
const file = e.target.files && e.target.files[0];
e.target.value = '';
if (!file) return;
setUploading(true);
try {
const token = sessionStorage.getItem('rg_token');
const fd = new FormData(); fd.append('file', file);
const res = await fetch('api/oc_import.php', { method:'POST', headers: token ? { 'Authorization':'Bearer '+token } : {}, body: fd });
const json = await res.json();
if (!json.ok) throw new Error(json.error || 'Error al leer el archivo');
setPreview(json.data);
toast.success(`${json.data.count} OCs leídas. Revisa el mapeo y confirma.`);
} catch (err) { toast.error(err.message); }
finally { setUploading(false); }
}
const setPrevCell = (i, field, val) => setPreview(p => ({ ...p, rows: p.rows.map((r, idx) => idx === i ? { ...r, [field]: val } : r) }));
// Al reasignar el proyecto, la empresa y el nombre mapeado se toman de Contraloría.
function changePrevProyecto(i, val) {
const pid = val ? Number(val) : null;
const proj = pid ? (preview.proyectos || []).find(p => p.id === pid) : null;
setPreview(p => ({ ...p, rows: p.rows.map((r, idx) => idx === i
? { ...r, proyectoid: pid, proyecto_match: proj ? proj.nombre : '', empresa: proj ? proj.empresa : r.empresa }
: r) }));
}
async function commit() {
if (!preview || !preview.rows.length) return;
setSaving(true);
const tc = Number(tcUsd) || 1;
const rowsToSave = preview.rows.map(r => ({ ...r, tc: r.moneda === 'USD' ? tc : 1 }));
try {
const res = await apiFetch('oc_import.php', 'POST', { accion:'commit', rows: rowsToSave });
toast.success(`${res.guardadas} OCs guardadas`);
setPreview(null);
reload();
} catch (err) { toast.error(err.message); }
finally { setSaving(false); }
}
async function del(id) {
if (!window.confirm('¿Eliminar esta OC importada?')) return;
try { await apiFetch('oc_import.php?id=' + id, 'DELETE'); toast.success('Eliminada'); reload(); }
catch (e) { toast.error(e.message); }
}
async function downloadReporteEmpresa() {
try {
const token = sessionStorage.getItem('rg_token');
const res = await fetch('api/reporte_empresa.php', { headers: token ? { 'Authorization':'Bearer '+token } : {} });
if (!res.ok) { toast.error('No se pudo generar el reporte'); return; }
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = `Reporte_por_Empresa_${new Date().toISOString().slice(0,10)}.xlsx`; a.click();
URL.revokeObjectURL(url);
} catch (e) { toast.error(e.message); }
}
const lowCount = preview ? preview.rows.filter(r => (r.match_score ?? 0) < 60).length : 0;
return (
{Icon.excel()} Reporte por empresa
fileRef.current && fileRef.current.click()} disabled={uploading}>
{Icon.excel()} {uploading ? 'Leyendo…' : 'Subir Excel (.xlsx)'}
}/>
{/* ── PREVISUALIZACIÓN ── */}
{preview && (
Empresa
Folio
Fecha
Proyecto (texto → asignado)
Proveedor
Clave
Moneda
Total (MXN)
Pagado (MXN)
{preview.rows.map((r, i) => (
{r.empresa}
{r.folio}
{r.fecha || '—'}
{r.proyecto_texto}
changePrevProyecto(i, e.target.value)}>
— sin asignar —
{(preview.proyectos||[]).map(p => {p.clave} · {p.nombre} )}
{r.empresa && Empresa: {r.empresa}
}
{r.proveedor}
{r.clave_insumo || '—'}
{r.moneda === 'USD'
? USD
: 'MXN'}
{moneyI(r.moneda === 'USD' ? r.total * (Number(tcUsd)||1) : r.total)}
{r.moneda === 'USD' && USD {fmtMoney(r.total)}
}
{moneyI(r.moneda === 'USD' ? r.pagado * (Number(tcUsd)||1) : r.pagado)}
))}
)}
{/* ── RESUMEN POR EMPRESA ── */}
{resumen.length > 0 && (
{resumen.map(rr => (
{rr.empresa}
{moneyI(rr.total)}
{rr.ocs} OCs · pagado {moneyI(rr.pagado)}
))}
)}
{/* ── LISTA (CRUD) ── */}
Folio
Empresa
Fecha
Proyecto
Proveedor
Clave
Moneda
Total (MXN)
Pagado (MXN)
Acciones
{loading && Cargando… }
{!loading && rows.length === 0 && Sin OCs importadas. Sube un Excel para comenzar. }
{rows.map(r => {
const proy = (proyectos||[]).find(p => p.id === r.proyectoid);
return (
{r.folio}
{r.empresa}
{r.fecha || '—'}
{proy ? {proy.clave} · {proy.nombre} : {r.proyecto_texto} (sin asignar) }
{r.proveedor}
{r.clave_insumo || '—'}
{r.moneda === 'USD' ? USD : 'MXN'}
{moneyI(r.moneda === 'USD' ? r.total * (r.tc||1) : r.total)}
{r.moneda === 'USD' && USD {fmtMoney(r.total)}
}
{moneyI(r.moneda === 'USD' ? r.pagado * (r.tc||1) : r.pagado)}
setEditRow(r)}>{Icon.edit()}
del(r.id)}>{Icon.x(12)}
);
})}
{pages > 1 && (
setPage(p => p-1)}>Anterior
Página {page} de {pages}
=pages} onClick={() => setPage(p => p+1)}>Siguiente
)}
setEditRow(null)} onSaved={reload}/>
);
}
Object.assign(window, { ImportOCView });