<?php
require_once __DIR__.'/bootstrap.php'; require_once __DIR__.'/layout.php'; require_login(); sync_permissions();
$p=$_GET['p']??'';$action=$_GET['action']??'list';$id=(int)($_GET['id']??0);
function rows(string $sql): array{return db()->query($sql)->fetch_all(MYSQLI_ASSOC);}
function opts(string $table): array{return rows("SELECT id,nombre FROM `$table` WHERE activo=1 ORDER BY nombre");}
function select_opts(string $table,int $selected=0):void{foreach(opts($table) as $r)echo '<option value="'.$r['id'].'" '.($selected==(int)$r['id']?'selected':'').'>'.e($r['nombre']).'</option>';}
function poststr(string $k):string{return trim((string)($_POST[$k]??''));}
function postfloat(string $k):float{return (float)str_replace(',','.',poststr($k));}
function sincronizar_cobros_detalle(int $detalleId): void {
    $sql="UPDATE operacion_detalles d SET
      cobrado_gastos_cobranza=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Gastos de Cobranza'),0),
      cobrado_punitorio=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Interés Punitorio'),0),
      cobrado_moratorio=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Interés Moratorio'),0),
      cobrado_gastos_administrativos=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Gastos Administrativos'),0),
      cobrado_gastos_operativos=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Gastos Operativos'),0),
      cobrado_comision=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Comisión'),0),
      cobrado_seguro=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Seguro'),0),
      cobrado_interes=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Interés'),0),
      cobrado_capital=COALESCE((SELECT SUM(ca.monto) FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=d.id AND ca.concepto='Capital'),0),
      ultima_fecha_cobro=(SELECT MAX(c.fecha) FROM cobros c WHERE c.operacion_detalle_id=d.id)
      WHERE d.id=$detalleId";
    db()->query($sql);
}
function total_saldos(string $where='1=1'): float {
    $total=0;foreach(rows("SELECT d.id FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id WHERE $where") as $item){$estado=estado_deuda((int)$item['id']);$total+=(float)($estado['saldo']??0);}return $total;
}
function estado_deuda(int $detalleId): array {
    $base=rows("SELECT d.*,o.cliente_id,o.fecha,o.tasa_interes,o.gastos_operativos,o.total_importe,o.moneda_id,m.codigo moneda_codigo,m.nombre moneda,m.simbolo,m.decimales,DATEDIFF(CURDATE(),d.vencimiento) dias_mora FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id JOIN monedas m ON m.id=o.moneda_id WHERE d.id=$detalleId")[0]??[];
    if(!$base)return [];
    $pagados=[];foreach(rows("SELECT ca.concepto,SUM(ca.monto) monto FROM cobro_aplicaciones ca JOIN cobros c ON c.id=ca.cobro_id WHERE c.operacion_detalle_id=$detalleId GROUP BY ca.concepto") as $r)$pagados[$r['concepto']]=(float)$r['monto'];
    $dias=max(0,(int)$base['dias_mora']);$capitalOriginal=(float)$base['neto'];
    $saldo=max(0,(float)$base['importe']-(float)$base['cobrado_gastos_administrativos']-(float)$base['cobrado_gastos_operativos']-(float)$base['cobrado_comision']-(float)$base['cobrado_seguro']-(float)$base['cobrado_interes']-(float)$base['cobrado_capital']);
    $divisor=loan_divisor();$moratorioTotal=round($saldo*$dias*(float)$base['tasa_interes']/$divisor,2);
    $moratorio=max(0,$moratorioTotal-($pagados['Interés Moratorio']??0));
    $punitorioTotal=round($moratorioTotal*.30,2);$punitorio=max(0,$punitorioTotal-($pagados['Interés Punitorio']??0));
    $tasaCobranza=(float)(rows("SELECT tasa FROM gastos_cobranza WHERE activo=1 AND $dias BETWEEN dias_desde AND dias_hasta ORDER BY dias_desde LIMIT 1")[0]['tasa']??0);
    $cobranzaTotal=round($saldo*$dias*$tasaCobranza/$divisor,2);$cobranza=max(0,$cobranzaTotal-($pagados['Gastos de Cobranza']??0));
    $administrativo=max(0,(float)$base['gasto_administrativo']-($pagados['Gastos Administrativos']??0));
    $operativoOriginal=(float)$base['gasto_operativo'];if($operativoOriginal<=0&&(float)$base['total_importe']>0)$operativoOriginal=round((float)$base['gastos_operativos']*((float)$base['importe']/(float)$base['total_importe']),2);
    $operativo=max(0,$operativoOriginal-($pagados['Gastos Operativos']??0));
    $comision=max(0,(float)$base['comision']-($pagados['Comisión']??0));
    $seguro=max(0,(float)$base['seguro']-($pagados['Seguro']??0));
    $interes=max(0,(float)$base['interes']-($pagados['Interés']??0));
    $total=$saldo+$moratorio+$punitorio+$cobranza;
    return $base+['capital_original'=>$capitalOriginal,'saldo'=>$saldo,'dias_mora'=>$dias,'moratorio'=>$moratorio,'punitorio'=>$punitorio,'gastos_cobranza'=>$cobranza,'gastos_administrativos_pendientes'=>$administrativo,'gastos_operativos_pendientes'=>$operativo,'comision_pendiente'=>$comision,'seguro_pendiente'=>$seguro,'interes_pendiente'=>$interes,'total_adeudado'=>$total,'pagados'=>$pagados];
}

if($p==='buscar'){
 header('Content-Type: application/json');$q='%'.poststr('x').'%';$q='%'.trim($_GET['q']??'').'%';$type=$_GET['type']??'clientes';
 if($type==='vendedores'){$st=db()->prepare("SELECT id,nombre label FROM vendedores WHERE activo=1 AND (nombre LIKE ? OR documento LIKE ?) LIMIT 15");}
 else {$st=db()->prepare("SELECT id,TRIM(CONCAT(COALESCE(razon_social,''),IF(razon_social IS NULL OR razon_social='','', ' - '),nombres,' ',COALESCE(apellidos,''),' (',documento,')')) label FROM clientes WHERE activo=1 AND (nombres LIKE ? OR apellidos LIKE ? OR razon_social LIKE ? OR documento LIKE ?) LIMIT 15");}
 if($type==='vendedores')$st->bind_param('ss',$q,$q);else $st->bind_param('ssss',$q,$q,$q,$q);$st->execute();echo json_encode($st->get_result()->fetch_all(MYSQLI_ASSOC),JSON_UNESCAPED_UNICODE);exit;
}

$catalogs=[
 'canales_venta'=>['Canales de Venta',['nombre'=>'Nombre','descripcion'=>'Descripción']],
 'tipos_documento'=>['Tipos de Documento',['nombre'=>'Nombre','descripcion'=>'Descripción']],
 'garantias'=>['Garantías',['nombre'=>'Nombre','descripcion'=>'Descripción']],
 'bancos'=>['Bancos',['nombre'=>'Nombre','codigo'=>'Código']],
 'clases_prestamo'=>['Clases de Préstamo',['nombre'=>'Nombre','descripcion'=>'Descripción']],
 'vendedores'=>['Vendedores',['nombre'=>'Nombre completo','documento'=>'Documento','telefono'=>'Teléfono','email'=>'Correo','tasa_comision'=>'Tasa comisión %']]
];
if($p==='catalogo'){
 $t=$_GET['t']??'';if(!isset($catalogs[$t]))exit('Catálogo inválido');$perm=$t;require_permission($perm);[$title,$fields]=$catalogs[$t];
 try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();if(isset($_POST['delete'])){$did=(int)$_POST['delete'];$st=db()->prepare("UPDATE `$t` SET activo=0 WHERE id=?");$st->bind_param('i',$did);$st->execute();audit('DESACTIVAR',$t,$did);flash('success','Registro desactivado.');redirect("app.php?p=catalogo&t=$t");}$vals=[];foreach($fields as $f=>$label)$vals[$f]=poststr($f);if($vals['nombre']==='')throw new RuntimeException('El nombre es obligatorio.');$eid=(int)($_POST['id']??0);if($eid){$sets=implode(',',array_map(fn($f)=>"`$f`=?",array_keys($fields)));$st=db()->prepare("UPDATE `$t` SET $sets,activo=1 WHERE id=?");$types=str_repeat('s',count($vals)).'i';$params=[...array_values($vals),$eid];$st->bind_param($types,...$params);$st->execute();audit('EDITAR',$t,$eid);}else{$cols=implode(',',array_map(fn($f)=>"`$f`",array_keys($fields)));$marks=implode(',',array_fill(0,count($fields),'?'));$st=db()->prepare("INSERT INTO `$t`($cols) VALUES($marks)");$types=str_repeat('s',count($vals));$params=array_values($vals);$st->bind_param($types,...$params);$st->execute();audit('CREAR',$t,$st->insert_id);}flash('success','Registro guardado.');redirect("app.php?p=catalogo&t=$t");}}catch(Throwable $e){$error=$e->getMessage();}
 $edit=[];if($id){$st=db()->prepare("SELECT * FROM `$t` WHERE id=?");$st->bind_param('i',$id);$st->execute();$edit=$st->get_result()->fetch_assoc()?:[];}page_start($title);?>
 <div class="d-flex justify-content-between mb-3"><h2><?=e($title)?></h2><button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#form">Nuevo</button></div><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="collapse <?=$edit?'show':''?>" id="form"><div class="card mb-4"><div class="card-body"><form method="post"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$id?>"><div class="row g-3"><?php foreach($fields as $f=>$label):?><div class="col-md-<?=count($fields)>3?'4':'6'?>"><label class="form-label <?=$f==='nombre'?'required':''?>"><?=e($label)?></label><input class="form-control" name="<?=$f?>" value="<?=e($edit[$f]??'')?>" <?=$f==='nombre'?'required':''?>></div><?php endforeach;?></div><button class="btn btn-primary mt-3">Guardar</button></form></div></div></div><div class="card"><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><?php foreach($fields as $label):?><th><?=e($label)?></th><?php endforeach;?><th>Acciones</th></tr></thead><tbody><?php foreach(rows("SELECT * FROM `$t` WHERE activo=1 ORDER BY id DESC") as $r):?><tr><?php foreach($fields as $f=>$label):?><td><?=e($r[$f])?></td><?php endforeach;?><td class="text-nowrap"><a class="btn btn-sm btn-outline-primary" href="?p=catalogo&t=<?=$t?>&id=<?=$r['id']?>">Editar</a><form method="post" class="d-inline" onsubmit="return confirm('¿Desactivar este registro?')"><input type="hidden" name="csrf" value="<?=csrf()?>"><button class="btn btn-sm btn-outline-danger" name="delete" value="<?=$r['id']?>">Desactivar</button></form></td></tr><?php endforeach;?></tbody></table></div></div><?php page_end();exit;
}

if($p==='tasas'){
 $t=($_GET['t']??'')==='tasas_cuotas'?'tasas_cuotas':'tasas_descuento';require_permission($t);$title=$t==='tasas_cuotas'?'Tasas Préstamo Cuotas':'Tasas Descuento';
 try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();if(isset($_POST['delete'])){db()->query("UPDATE `$t` SET activo=0 WHERE id=".(int)$_POST['delete']);flash('success','Rango desactivado.');redirect("app.php?p=tasas&t=$t");}$desde=postfloat('desde');$hasta=postfloat('hasta');$tasa=postfloat('tasa');if($desde<0||$hasta<$desde||$tasa<0)throw new RuntimeException('Revise el rango y la tasa.');$eid=(int)($_POST['id']??0);$over=db()->prepare("SELECT id FROM `$t` WHERE activo=1 AND id<>? AND NOT(monto_hasta<? OR monto_desde>?) LIMIT 1");$over->bind_param('idd',$eid,$desde,$hasta);$over->execute();if($over->get_result()->fetch_row())throw new RuntimeException('El rango se superpone con otro rango activo.');if($eid){$st=db()->prepare("UPDATE `$t` SET monto_desde=?,monto_hasta=?,tasa=?,activo=1 WHERE id=?");$st->bind_param('dddi',$desde,$hasta,$tasa,$eid);}else{$st=db()->prepare("INSERT INTO `$t`(monto_desde,monto_hasta,tasa) VALUES(?,?,?)");$st->bind_param('ddd',$desde,$hasta,$tasa);}$st->execute();flash('success','Rango guardado.');redirect("app.php?p=tasas&t=$t");}}catch(Throwable $e){$error=$e->getMessage();}
 $edit=$id?rows("SELECT * FROM `$t` WHERE id=$id")[0]??[]:[];page_start($title);?>
 <h2 class="mb-3"><?=e($title)?></h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="card mb-4"><div class="card-body"><form method="post"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$id?>"><div class="row g-3"><div class="col-md-4"><label class="form-label required">Monto desde</label><input type="number" step="0.01" min="0" class="form-control" name="desde" value="<?=e($edit['monto_desde']??'')?>" required></div><div class="col-md-4"><label class="form-label required">Monto hasta</label><input type="number" step="0.01" min="0" class="form-control" name="hasta" value="<?=e($edit['monto_hasta']??'')?>" required></div><div class="col-md-4"><label class="form-label required">Tasa anual %</label><input type="number" step="0.0001" min="0" class="form-control" name="tasa" value="<?=e($edit['tasa']??'')?>" required></div></div><button class="btn btn-primary mt-3">Guardar rango</button></form></div></div><div class="card"><table class="table mb-0"><thead><tr><th>Desde</th><th>Hasta</th><th>Tasa %</th><th></th></tr></thead><tbody><?php foreach(rows("SELECT * FROM `$t` WHERE activo=1 ORDER BY monto_desde") as $r):?><tr><td><?=money((float)$r['monto_desde'])?></td><td><?=money((float)$r['monto_hasta'])?></td><td><?=e($r['tasa'])?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=tasas&t=<?=$t?>&id=<?=$r['id']?>">Editar</a> <form method="post" class="d-inline"><input type="hidden" name="csrf" value="<?=csrf()?>"><button class="btn btn-sm btn-outline-danger" name="delete" value="<?=$r['id']?>">Desactivar</button></form></td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='tae'){
 require_permission('tae_limites');try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$d=poststr('fecha_desde');$h=poststr('fecha_hasta');$tae=postfloat('tae');if(!$d||!$h||$h<$d||$tae<0)throw new RuntimeException('Revise la vigencia y la TAE.');$st=db()->prepare("INSERT INTO tae_limites(fecha_desde,fecha_hasta,tae) VALUES(?,?,?)");$st->bind_param('ssd',$d,$h,$tae);$st->execute();flash('success','TAE guardada.');redirect('app.php?p=tae');}}catch(Throwable $e){$error=$e->getMessage();}page_start('TAE Límite');?>
 <h2>TAE Límite</h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="card mb-4"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><div class="col-md-4"><label class="form-label">Desde</label><input type="date" class="form-control" name="fecha_desde" required></div><div class="col-md-4"><label class="form-label">Hasta</label><input type="date" class="form-control" name="fecha_hasta" required></div><div class="col-md-4"><label class="form-label">TAE %</label><input type="number" step="0.0001" class="form-control" name="tae" required></div><div><button class="btn btn-primary">Guardar</button></div></form></div></div><div class="card"><table class="table mb-0"><thead><tr><th>Desde</th><th>Hasta</th><th>TAE %</th></tr></thead><tbody><?php foreach(rows("SELECT * FROM tae_limites WHERE activo=1 ORDER BY fecha_desde DESC") as $r):?><tr><td><?=e($r['fecha_desde'])?></td><td><?=e($r['fecha_hasta'])?></td><td><?=e($r['tae'])?></td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='clientes'){
 require_permission('clientes');
 try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();
   if(isset($_POST['delete'])){db()->query("UPDATE clientes SET activo=0 WHERE id=".(int)$_POST['delete']);flash('success','Cliente desactivado.');redirect('app.php?p=clientes');}
   $fields=['tipo_persona','documento','nombres','apellidos','razon_social','fecha_nacimiento','nacionalidad','estado_civil','telefono','email','direccion','ciudad','actividad','empresa','ingreso_mensual','observaciones'];$v=[];foreach($fields as $f)$v[$f]=poststr($f);if(!$v['documento']||!$v['nombres'])throw new RuntimeException('Documento y nombres son obligatorios.');$eid=(int)($_POST['id']??0);
   if($eid){$st=db()->prepare("UPDATE clientes SET tipo_persona=?,documento=?,nombres=?,apellidos=?,razon_social=?,fecha_nacimiento=NULLIF(?,''),nacionalidad=?,estado_civil=?,telefono=?,email=?,direccion=?,ciudad=?,actividad=?,empresa=?,ingreso_mensual=?,observaciones=?,activo=1 WHERE id=?");$types='ssssssssssssssdsi';$params=[...array_values($v),$eid];$st->bind_param($types,...$params);$st->execute();$cid=$eid;}else{$st=db()->prepare("INSERT INTO clientes(tipo_persona,documento,nombres,apellidos,razon_social,fecha_nacimiento,nacionalidad,estado_civil,telefono,email,direccion,ciudad,actividad,empresa,ingreso_mensual,observaciones) VALUES(?,?,?,?,?,NULLIF(?,''),?,?,?,?,?,?,?,?,?,?)");$types='ssssssssssssssds';$params=array_values($v);$st->bind_param($types,...$params);$st->execute();$cid=$st->insert_id;}
   if(!empty($_POST['ref_nombre'])){$rst=db()->prepare("INSERT INTO cliente_referencias(cliente_id,tipo,nombre,relacion,telefono,email,direccion,observacion) VALUES(?,?,?,?,?,?,?,?)");foreach($_POST['ref_nombre'] as $i=>$name){$name=trim($name);if(!$name)continue;$type=$_POST['ref_tipo'][$i]??'Personal';$rel=$_POST['ref_relacion'][$i]??'';$tel=$_POST['ref_telefono'][$i]??'';$mail=$_POST['ref_email'][$i]??'';$dir=$_POST['ref_direccion'][$i]??'';$obs=$_POST['ref_observacion'][$i]??'';$rst->bind_param('isssssss',$cid,$type,$name,$rel,$tel,$mail,$dir,$obs);$rst->execute();}}
   if(!empty($_FILES['archivos']['name'])){if(!is_dir(__DIR__.'/uploads'))mkdir(__DIR__.'/uploads',0775,true);$ast=db()->prepare("INSERT INTO cliente_archivos(cliente_id,nombre_original,archivo,mime,descripcion,fecha_vencimiento) VALUES(?,?,?,?,?,NULLIF(?,''))");foreach($_FILES['archivos']['name'] as $i=>$orig){if($_FILES['archivos']['error'][$i]!==UPLOAD_ERR_OK)continue;if($_FILES['archivos']['size'][$i]>UPLOAD_MAX_BYTES)throw new RuntimeException('Un archivo supera 10 MB.');$mime=(new finfo(FILEINFO_MIME_TYPE))->file($_FILES['archivos']['tmp_name'][$i]);$allowed=['image/jpeg','image/png','image/webp','application/pdf'];if(!in_array($mime,$allowed,true))throw new RuntimeException('Solo se admiten JPG, PNG, WEBP y PDF.');$ext=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp','application/pdf'=>'pdf'][$mime];$file=bin2hex(random_bytes(16)).'.'.$ext;move_uploaded_file($_FILES['archivos']['tmp_name'][$i],__DIR__.'/uploads/'.$file);$desc=$_POST['archivo_descripcion'][$i]??'';$vence=$_POST['archivo_vencimiento'][$i]??'';$ast->bind_param('isssss',$cid,$orig,$file,$mime,$desc,$vence);$ast->execute();}}
   audit($eid?'EDITAR':'CREAR','clientes',$cid);flash('success','Cliente guardado.');redirect('app.php?p=clientes');
 }}catch(Throwable $e){$error=$e->getMessage();}
 $edit=$id?rows("SELECT * FROM clientes WHERE id=$id")[0]??[]:[];page_start('Clientes');?>
 <div class="d-flex justify-content-between mb-3"><h2>Clientes</h2><a class="btn btn-primary" href="?p=clientes&action=form">Nuevo cliente</a></div><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><?php if($action==='form'||$id):?>
 <form method="post" enctype="multipart/form-data"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$id?>"><div class="card mb-4"><div class="card-header">Datos básicos</div><div class="card-body"><div class="row g-3"><?php $flds=['tipo_persona'=>'Tipo de persona','documento'=>'Documento','nombres'=>'Nombres / Nombre comercial','apellidos'=>'Apellidos','razon_social'=>'Razón social','fecha_nacimiento'=>'Fecha nacimiento/constitución','nacionalidad'=>'Nacionalidad','estado_civil'=>'Estado civil','telefono'=>'Teléfono','email'=>'Correo','direccion'=>'Dirección','ciudad'=>'Ciudad','actividad'=>'Actividad','empresa'=>'Empresa','ingreso_mensual'=>'Ingreso mensual'];foreach($flds as $f=>$label):?><div class="col-md-4"><label class="form-label <?=in_array($f,['documento','nombres'])?'required':''?>"><?=e($label)?></label><?php if($f==='tipo_persona'):?><select class="form-select" name="<?=$f?>"><option>Física</option><option <?=($edit[$f]??'')==='Jurídica'?'selected':''?>>Jurídica</option></select><?php else:?><input class="form-control" name="<?=$f?>" value="<?=e($edit[$f]??'')?>" <?=$f==='fecha_nacimiento'?'type="date"':''?> <?=in_array($f,['documento','nombres'])?'required':''?>><?php endif;?></div><?php endforeach;?><div class="col-12"><label class="form-label">Observaciones</label><textarea class="form-control" name="observaciones"><?=e($edit['observaciones']??'')?></textarea></div></div></div></div>
 <div class="card mb-4"><div class="card-header d-flex">Referencias personales y comerciales <button type="button" class="btn btn-sm btn-outline-primary ms-auto" onclick="addRef()">Agregar</button></div><div class="card-body" id="refs"></div></div><div class="card mb-4"><div class="card-header d-flex">Imágenes y documentos <button type="button" class="btn btn-sm btn-outline-primary ms-auto" onclick="addFile()">Agregar</button></div><div class="card-body" id="files"></div></div><button class="btn btn-primary mb-4">Guardar cliente</button> <a class="btn btn-light mb-4" href="?p=clientes">Cancelar</a></form>
 <script>function addRef(){document.querySelector('#refs').insertAdjacentHTML('beforeend',`<div class="row g-2 mb-2"><div class="col-md-2"><select class="form-select" name="ref_tipo[]"><option>Personal</option><option>Comercial</option></select></div><div class="col-md-2"><input class="form-control" name="ref_nombre[]" placeholder="Nombre"></div><div class="col-md-2"><input class="form-control" name="ref_relacion[]" placeholder="Relación"></div><div class="col-md-2"><input class="form-control" name="ref_telefono[]" placeholder="Teléfono"></div><div class="col-md-2"><input class="form-control" name="ref_email[]" placeholder="Correo"></div><div class="col-md-2"><button type="button" class="btn btn-outline-danger" onclick="this.closest('.row').remove()">Quitar</button></div><input type="hidden" name="ref_direccion[]"><input type="hidden" name="ref_observacion[]"></div>`)}function addFile(){document.querySelector('#files').insertAdjacentHTML('beforeend',`<div class="row g-2 mb-2"><div class="col-md-4"><input type="file" class="form-control" name="archivos[]" accept="image/jpeg,image/png,image/webp,application/pdf"></div><div class="col-md-4"><input class="form-control" name="archivo_descripcion[]" placeholder="Descripción"></div><div class="col-md-3"><input type="date" class="form-control" name="archivo_vencimiento[]"></div><div class="col-md-1"><button type="button" class="btn btn-outline-danger" onclick="this.closest('.row').remove()">×</button></div></div>`)}addRef();addFile();</script>
 <?php else:?><div class="card"><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><th>Documento</th><th>Cliente</th><th>Teléfono</th><th>Ciudad</th><th>Acciones</th></tr></thead><tbody><?php foreach(rows("SELECT * FROM clientes WHERE activo=1 ORDER BY id DESC") as $r):?><tr><td><?=e($r['documento'])?></td><td><?=e($r['razon_social']?:trim($r['nombres'].' '.$r['apellidos']))?></td><td><?=e($r['telefono'])?></td><td><?=e($r['ciudad'])?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=clientes&id=<?=$r['id']?>">Editar</a><form method="post" class="d-inline"><input type="hidden" name="csrf" value="<?=csrf()?>"><button name="delete" value="<?=$r['id']?>" class="btn btn-sm btn-outline-danger">Desactivar</button></form></td></tr><?php endforeach;?></tbody></table></div></div><?php endif;?><?php page_end();exit;
}

if($p==='operaciones'){
 require_permission('operaciones');
 try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$fecha=poststr('fecha');$cliente=(int)($_POST['cliente_id']??0);$vendedor=(int)($_POST['vendedor_id']??0);$canal=(int)($_POST['canal_id']??0);$garantia=(int)($_POST['garantia_id']??0);$codeudor=(int)($_POST['codeudor_id']??0)?:null;$clase=(int)($_POST['clase_prestamo_id']??0)?:null;$rate=postfloat('tasa_interes');$moneda=(int)($_POST['moneda_id']??1);$origen=(int)($_POST['origen_capital_id']??0);if(!$fecha||!$cliente||!$vendedor||!$canal||!$garantia||!$moneda||!$origen)throw new RuntimeException('Complete todos los datos obligatorios de la operación.');$docs=$_POST['doc_tipo']??[];if(!$docs)throw new RuntimeException('Agregue al menos un documento.');$seller=rows("SELECT tasa_comision FROM vendedores WHERE id=$vendedor")[0]??['tasa_comision'=>0];$details=[];$total=$disc=$net=$totSeguro=$totComision=$totGastos=$totAdmin=0;$today=new DateTime('today');$divisor=loan_divisor();foreach($docs as $i=>$tipo){$tipo=(int)$tipo;$amount=(float)($_POST['doc_importe'][$i]??0);$due=$_POST['doc_vencimiento'][$i]??'';if(!$tipo||$amount<=0||$amount>9999999999||!$due)throw new RuntimeException('Cada detalle requiere tipo, vencimiento e importe de hasta 10 dígitos.');$days=max(0,(int)$today->diff(new DateTime($due))->format('%r%a'));$find=function($table,$field,$a,$b,$value){$rs=rows("SELECT * FROM `$table` WHERE activo=1 AND $value BETWEEN `$a` AND `$b` ORDER BY `$a` LIMIT 1");return $rs[0][$field]??0;};$applied=(float)$find('tasas_descuento','tasa','monto_desde','monto_hasta',$amount)?:$rate;$insuranceRate=(float)$find('tasa_seguro','tasa','monto_desde','monto_hasta',$amount);$adminRate=(float)$find('gastos_administrativos','tasa','monto_desde','monto_hasta',$amount);$fixed=0;$interest=round($amount*$days*$applied/$divisor,2);$insurance=round($amount*$days*$insuranceRate/$divisor,2);$commission=round($amount*$days*(float)$seller['tasa_comision']/$divisor,2);$admin=round($amount*$days*$adminRate/$divisor,2);$totalDiscount=$interest+$insurance+$commission+$admin;$n=round($amount-$totalDiscount,2);$total+=$amount;$disc+=$totalDiscount;$net+=$n;$totSeguro+=$insurance;$totComision+=$commission;$totGastos+=$fixed;$totAdmin+=$admin;$details[]=[$tipo,$amount,$due,(int)($_POST['doc_banco'][$i]??0)?:null,trim($_POST['doc_cheque'][$i]??''),trim($_POST['doc_cuenta'][$i]??''),(int)($_POST['doc_firmante'][$i]??0)?:null,$days,$applied,$interest,$insurance,$commission,$fixed,$admin,$totalDiscount,$n];}$hr=db()->prepare("SELECT tasa FROM tasas_descuento WHERE activo=1 AND ? BETWEEN monto_desde AND monto_hasta ORDER BY monto_desde LIMIT 1");$hr->bind_param('d',$total);$hr->execute();if($headerRate=$hr->get_result()->fetch_assoc())$rate=(float)$headerRate['tasa'];$limit=rows("SELECT * FROM limites_operativos WHERE activo=1 AND '$fecha' BETWEEN fecha_desde AND fecha_hasta ORDER BY fecha_desde DESC LIMIT 1")[0]??null;$operating=0;$gp=general_params();if(!empty($gp['calcula_gastos_bcp'])&&$limit&&$total<=(float)$gp['salario_minimo']*$limit['max_salarios_credito'])$operating=min($limit['jornal_minimo']*$limit['max_jornales'],max(0,$net)*$limit['porcentaje_neto']/100);$net-=$operating;
   $tr=db();$tr->begin_transaction();$st=$tr->prepare("INSERT INTO operaciones(fecha,cliente_id,vendedor_id,canal_id,garantia_id,codeudor_id,clase_prestamo_id,moneda_id,origen_capital_id,tasa_interes,total_importe,total_descuento,total_seguro,total_comision,total_gastos,total_gastos_administrativos,gastos_operativos,total_neto,observaciones,creado_por) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");$obs=poststr('observaciones');$uid=(int)user()['id'];$st->bind_param('siiiiiiiidddddddddsi',$fecha,$cliente,$vendedor,$canal,$garantia,$codeudor,$clase,$moneda,$origen,$rate,$total,$disc,$totSeguro,$totComision,$totGastos,$totAdmin,$operating,$net,$obs,$uid);$st->execute();$oid=$st->insert_id;$dst=$tr->prepare("INSERT INTO operacion_detalles(operacion_id,tipo_documento_id,importe,vencimiento,banco_id,nro_cheque,cuenta_corriente,firmante_id,plazo_dias,tasa_aplicada,interes,seguro,comision,gasto,gasto_administrativo,descuento,neto) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");foreach($details as $d){[$tipo,$amount,$due,$bank,$check,$account,$signer,$days,$applied,$interest,$insurance,$commission,$fixed,$admin,$discount,$n]=$d;$dst->bind_param('iidsissiidddddddd',$oid,$tipo,$amount,$due,$bank,$check,$account,$signer,$days,$applied,$interest,$insurance,$commission,$fixed,$admin,$discount,$n);$dst->execute();$did=$dst->insert_id;$tr->query("INSERT INTO detalle_saldos(operacion_detalle_id,seguro,gastos_administrativos,gastos_operativos,interes,capital) VALUES($did,$insurance,$admin,0,$interest,$n)");}$tr->commit();audit('CREAR','operaciones',$oid);flash('success','Descuento calculado y guardado correctamente.');redirect('app.php?p=operaciones&view='.$oid);
 }}catch(Throwable $e){if(isset($tr))$tr->rollback();$error=$e->getMessage();}
 $view=(int)($_GET['view']??0);page_start('Operaciones');if($view){$o=rows("SELECT o.*,CONCAT(c.nombres,' ',c.apellidos) cliente,v.nombre vendedor,cv.nombre canal,g.nombre garantia,m.codigo,m.nombre moneda,m.simbolo,m.decimales,oc.nombre origen_capital FROM operaciones o JOIN monedas m ON m.id=o.moneda_id LEFT JOIN origenes_capital oc ON oc.id=o.origen_capital_id JOIN clientes c ON c.id=o.cliente_id JOIN vendedores v ON v.id=o.vendedor_id JOIN canales_venta cv ON cv.id=o.canal_id JOIN garantias g ON g.id=o.garantia_id WHERE o.id=$view")[0]??null;?>
 <?php $fm=fn($v)=>money_currency((float)$v,$o);?><div class="d-flex justify-content-between mb-3"><h2>Descuento #<?=$view?></h2><div><a target="_blank" class="btn btn-primary" href="?p=imprimir&view=<?=$view?>"><i class="fa fa-print"></i> Imprimir resumen</a> <a class="btn btn-light" href="?p=operaciones">Volver</a></div></div><?php if($o):?><div class="card mb-4"><div class="card-body row"><div class="col-md-3"><b>Cliente</b><br><?=e($o['cliente'])?></div><div class="col-md-3"><b>Vendedor</b><br><?=e($o['vendedor'])?></div><div class="col-md-3"><b>Canal</b><br><?=e($o['canal'])?></div><div class="col-md-3"><b>Garantía</b><br><?=e($o['garantia'])?></div><div class="col-md-3 mt-3"><b>Moneda</b><br><?=e($o['moneda'])?></div><div class="col-md-3 mt-3"><b>Origen del Capital</b><br><?=e($o['origen_capital'])?></div></div></div><div class="card"><div class="table-responsive"><table class="table mb-0"><thead><tr><th>Tipo</th><th>Documento</th><th>Plazo</th><th>Interés</th><th>Comisión</th><th>Gto.Adm.</th><th>Seguro</th><th>Total descuento</th><th>Neto</th></tr></thead><tbody><?php foreach(rows("SELECT d.*,td.nombre tipo FROM operacion_detalles d JOIN tipos_documento td ON td.id=d.tipo_documento_id WHERE operacion_id=$view") as $d):$ed=estado_deuda((int)$d['id']);?><tr><td><?=e($d['tipo'])?><br><small><?=e($d['vencimiento'])?></small></td><td><?=$fm((float)$d['importe'])?></td><td><?=$d['plazo_dias']?> días</td><td><?=$fm((float)$d['interes'])?></td><td><?=$fm((float)$d['comision'])?><br><small class="text-muted">Cobrado: <?=$fm((float)$d['cobrado_comision'])?> · Pendiente: <?=$fm((float)$ed['comision_pendiente'])?></small></td><td><?=$fm((float)$d['gasto_administrativo'])?></td><td><?=$fm((float)$d['seguro'])?></td><td><?=$fm((float)$d['descuento'])?></td><td><?=$fm((float)$d['neto'])?></td></tr><?php endforeach;?><tr class="fw-bold"><td>Totales</td><td><?=$fm((float)$o['total_importe'])?></td><td></td><td><?=$fm((float)$o['total_descuento']-$o['total_comision']-$o['total_gastos_administrativos']-$o['total_seguro'])?></td><td><?=$fm((float)$o['total_comision'])?></td><td><?=$fm((float)$o['total_gastos_administrativos'])?></td><td><?=$fm((float)$o['total_seguro'])?></td><td><?=$fm((float)$o['total_descuento'])?></td><td><?=$fm((float)$o['total_neto']+$o['gastos_operativos'])?></td></tr></tbody></table></div></div><div class="card mt-4"><div class="card-header">Detalle de cobros aplicados</div><div class="table-responsive"><table class="table table-sm table-hover mb-0"><thead><tr><th>Fecha / Recibo</th><th>Documento</th><th>G. Cobranza</th><th>Punitorio</th><th>Moratorio</th><th>G. Adm.</th><th>G. Oper.</th><th>Comisión</th><th>Seguro</th><th>Interés</th><th>Capital</th><th>Total</th></tr></thead><tbody><?php foreach(rows("SELECT co.*,COALESCE(SUM(CASE WHEN ca.concepto='Gastos de Cobranza' THEN ca.monto END),0) cobranza,COALESCE(SUM(CASE WHEN ca.concepto='Interés Punitorio' THEN ca.monto END),0) punitorio,COALESCE(SUM(CASE WHEN ca.concepto='Interés Moratorio' THEN ca.monto END),0) moratorio,COALESCE(SUM(CASE WHEN ca.concepto='Gastos Administrativos' THEN ca.monto END),0) administrativo,COALESCE(SUM(CASE WHEN ca.concepto='Gastos Operativos' THEN ca.monto END),0) operativo,COALESCE(SUM(CASE WHEN ca.concepto='Comisión' THEN ca.monto END),0) comision,COALESCE(SUM(CASE WHEN ca.concepto='Seguro' THEN ca.monto END),0) seguro,COALESCE(SUM(CASE WHEN ca.concepto='Interés' THEN ca.monto END),0) interes,COALESCE(SUM(CASE WHEN ca.concepto='Capital' THEN ca.monto END),0) capital FROM cobros co JOIN operacion_detalles od ON od.id=co.operacion_detalle_id LEFT JOIN cobro_aplicaciones ca ON ca.cobro_id=co.id WHERE od.operacion_id=$view GROUP BY co.id ORDER BY co.fecha DESC,co.id DESC") as $co):?><tr><td><?=e($co['fecha'])?><br><small><?=e($co['recibo'])?></small></td><td>#<?=$co['operacion_detalle_id']?></td><td><?=$fm($co['cobranza'])?></td><td><?=$fm($co['punitorio'])?></td><td><?=$fm($co['moratorio'])?></td><td><?=$fm($co['administrativo'])?></td><td><?=$fm($co['operativo'])?></td><td><?=$fm($co['comision'])?></td><td><?=$fm($co['seguro'])?></td><td><?=$fm($co['interes'])?></td><td><?=$fm($co['capital'])?></td><td class="fw-bold"><?=$fm($co['monto'])?></td></tr><?php endforeach;?></tbody></table></div></div><?php endif;
 }elseif($action==='form'){ $initial=(float)(rows("SELECT tasa FROM tasas_descuento WHERE activo=1 ORDER BY monto_desde,id LIMIT 1")[0]['tasa']??0);$defaultClass=(int)(rows("SELECT id FROM clases_prestamo WHERE activo=1 AND nombre='Nuevos' ORDER BY id LIMIT 1")[0]['id']??0);$defaultChannel=(int)(rows("SELECT id FROM canales_venta WHERE activo=1 AND nombre='Venta Directa' ORDER BY id LIMIT 1")[0]['id']??0);$defaultGuarantee=(int)(rows("SELECT id FROM garantias WHERE activo=1 AND nombre='A Sola Firma' ORDER BY id LIMIT 1")[0]['id']??0);?>
 <h2 class="mb-3">Nuevo Descuento de Documento</h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><form method="post" id="operacionForm"><input type="hidden" name="csrf" value="<?=csrf()?>"><div class="card mb-4"><div class="card-header">Cabecera</div><div class="card-body"><div class="row g-3"><div class="col-md-3"><label class="form-label required">Fecha</label><input type="date" class="form-control" name="fecha" value="<?=date('Y-m-d')?>" required></div><?php foreach([['cliente_id','Cliente','clientes'],['vendedor_id','Vendedor','vendedores'],['codeudor_id','Codeudor (opcional)','clientes']] as $a):?><div class="col-md-3 autocomplete"><label class="form-label <?=$a[0]!=='codeudor_id'?'required':''?>"><?=$a[1]?></label><input class="form-control lookup" placeholder="Escriba para buscar"><input type="hidden" name="<?=$a[0]?>"><div class="autocomplete-results"></div></div><?php endforeach;?><div class="col-md-3"><label class="form-label required">Canal</label><select class="form-select" name="canal_id" required><option value="">Seleccione</option><?php select_opts('canales_venta',$defaultChannel);?></select></div><div class="col-md-3"><label class="form-label required">Garantía</label><select class="form-select" name="garantia_id" required><option value="">Seleccione</option><?php select_opts('garantias',$defaultGuarantee);?></select></div><div class="col-md-3"><label class="form-label">Clase de préstamo</label><select class="form-select" name="clase_prestamo_id"><option value="">Seleccione</option><?php select_opts('clases_prestamo',$defaultClass);?></select></div><div class="col-md-3"><label class="form-label required">Moneda</label><select class="form-select" name="moneda_id" id="operacionMoneda" required><?php foreach(rows("SELECT * FROM monedas WHERE activo=1 ORDER BY orden") as $m):?><option value="<?=$m['id']?>" data-codigo="<?=e($m['codigo'])?>" data-decimales="<?=$m['decimales']?>"><?=e($m['nombre'])?> (<?=e($m['simbolo'])?>)</option><?php endforeach;?></select></div><div class="col-md-3"><label class="form-label required">Origen del Capital</label><select class="form-select" name="origen_capital_id" required><?php select_opts('origenes_capital');?></select></div><div class="col-md-3"><label class="form-label required">Tasa interés %</label><input type="number" step="0.0001" class="form-control" name="tasa_interes" value="<?=$initial?>" required><small class="text-muted">Inicial automática; editable.</small></div><div class="col-12"><label class="form-label">Observaciones</label><textarea class="form-control" name="observaciones"></textarea></div></div></div></div><div class="card mb-4"><div class="card-header d-flex"><span>Detalle de documentos</span> <button type="button" class="btn btn-sm btn-outline-primary ms-auto" onclick="addDoc()">Agregar línea</button></div><div class="card-body"><div id="docs"></div></div></div><div id="resumenCalculo" class="card mx-auto mb-4 d-none" style="max-width:680px"><div class="card-header text-center">Totales</div><div class="card-body p-0"><table class="table table-sm mb-0 align-middle"><thead><tr><th>Concepto</th><th class="text-end">Documentos</th><th class="text-end">Descuentos</th></tr></thead><tbody><tr><td>Documentos</td><td class="text-end" id="rDocumentos">0</td><td></td></tr><tr><td>Interés</td><td></td><td class="text-end" id="rInteres">0</td></tr><tr><td>Comisión</td><td></td><td class="text-end" id="rComision">0</td></tr><tr><td>Gasto Operativo</td><td></td><td class="text-end" id="rOperativo">0</td></tr><tr><td>Gastos Admin.</td><td></td><td class="text-end" id="rAdmin">0</td></tr><tr><td>Seguro</td><td></td><td class="text-end" id="rSeguro">0</td></tr></tbody><tfoot><tr class="table-primary fw-bold"><td>Neto a retirar</td><td colspan="2" class="text-end fs-5" id="rNeto">0</td></tr></tfoot></table></div></div><div class="d-flex gap-2"><button type="button" class="btn btn-outline-primary" onclick="calcularOperacion()"><i class="fa fa-calculator me-1"></i>Calcular</button><button type="submit" id="guardarOperacion" class="btn btn-primary" disabled><i class="fa fa-floppy-disk me-1"></i>Guardar</button><a class="btn btn-light" href="?p=operaciones">Cancelar</a></div></form>
 <script>window.BASE_URL='<?=BASE_URL?>';document.querySelectorAll('.lookup').forEach((x,i)=>autocomplete(x,x.nextElementSibling,i===1?'vendedores':'clientes'));const tipos=<?=json_encode(opts('tipos_documento'))?>,bancos=<?=json_encode(opts('bancos'))?>,rangosDescuento=<?=json_encode(rows("SELECT * FROM tasas_descuento WHERE activo=1"))?>,rangosSeguro=<?=json_encode(rows("SELECT * FROM tasa_seguro WHERE activo=1"))?>,rangosAdmin=<?=json_encode(rows("SELECT * FROM gastos_administrativos WHERE activo=1"))?>,tasasVendedor=<?=json_encode(array_column(rows("SELECT id,tasa_comision FROM vendedores WHERE activo=1"),'tasa_comision','id'))?>,limiteOperativo=<?=json_encode(rows("SELECT * FROM limites_operativos WHERE activo=1 AND CURDATE() BETWEEN fecha_desde AND fecha_hasta ORDER BY fecha_desde DESC LIMIT 1")[0]??null)?>,parametrosGenerales=<?=json_encode(general_params())?>,divisorCalculo=<?=json_encode(loan_divisor())?>;function options(a){return '<option value="">Seleccione</option>'+a.map(x=>`<option value="${x.id}">${x.nombre}</option>`).join('')}function addDoc(){document.querySelector('#docs').insertAdjacentHTML('beforeend',`<div class="doc-row border rounded p-2 mb-3"><div class="row g-2"><div class="col-md-2"><label class="small">Tipo</label><select class="form-select form-select-sm" name="doc_tipo[]" required>${options(tipos)}</select></div><div class="col-md-3"><label class="small">Documento</label><input type="text" inputmode="decimal" maxlength="16" class="form-control form-control-sm money-display" placeholder="Hasta 9.999.999.999,99" required><input type="hidden" class="doc-importe" name="doc_importe[]"></div><div class="col-md-2"><label class="small">Vencimiento</label><input type="date" class="form-control form-control-sm doc-vencimiento" name="doc_vencimiento[]" required></div><div class="col-md-2"><label class="small">Banco</label><select class="form-select form-select-sm" name="doc_banco[]">${options(bancos)}</select></div><div class="col-md-2"><label class="small">Nro. cheque</label><input class="form-control form-control-sm" name="doc_cheque[]"></div><div class="col-md-2"><label class="small">Cuenta corriente</label><input class="form-control form-control-sm" name="doc_cuenta[]"></div></div><div class="row g-2 mt-1 text-center"><div class="col"><small>Plazo</small><div class="fw-semibold calc-plazo">0</div></div><div class="col"><small>Interés</small><div class="fw-semibold calc-interes">0</div></div><div class="col"><small>Comisión</small><div class="fw-semibold calc-comision">0</div></div><div class="col"><small>Gto.Adm.</small><div class="fw-semibold calc-admin">0</div></div><div class="col"><small>Seguro</small><div class="fw-semibold calc-seguro">0</div></div><div class="col"><small>Total Descuento</small><div class="fw-semibold calc-descuento">0</div></div><div class="col"><small>Neto</small><div class="fw-semibold calc-neto">0</div></div></div><div class="row mt-2"><div class="col-md-4 autocomplete"><label class="small">Firmante (opcional)</label><input class="form-control form-control-sm signer" placeholder="Buscar cliente"><input type="hidden" name="doc_firmante[]"><div class="autocomplete-results"></div></div><div class="col align-self-end text-end"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.doc-row').remove();invalidarCalculo()">Quitar línea</button></div></div></div>`);let row=document.querySelector('#docs .doc-row:last-child'),x=row.querySelector('.signer');autocomplete(x,x.nextElementSibling,'clientes');let money=row.querySelector('.money-display'),raw=row.querySelector('.doc-importe');money.addEventListener('input',()=>{let digits=money.value.replace(/\D/g,'').slice(0,12),usd=document.querySelector('#operacionMoneda').selectedOptions[0]?.dataset.codigo==='USD',value=usd?Number(digits)/100:Number(digits);raw.value=value.toFixed(usd?2:0);money.value=digits?new Intl.NumberFormat('es-PY',{minimumFractionDigits:usd?2:0,maximumFractionDigits:usd?2:0}).format(value):'';invalidarCalculo()});invalidarCalculo()}function rango(lista,monto){return lista.find(r=>monto>=Number(r.monto_desde)&&monto<=Number(r.monto_hasta))?.tasa||0}function fmt(n){let usd=document.querySelector('#operacionMoneda').selectedOptions[0]?.dataset.codigo==='USD';return new Intl.NumberFormat('es-PY',{minimumFractionDigits:usd?2:0,maximumFractionDigits:usd?2:0}).format(n)}function invalidarCalculo(){document.querySelector('#guardarOperacion').disabled=true;document.querySelector('#resumenCalculo').classList.add('d-none')}function calcularOperacion(){let form=document.querySelector('#operacionForm');if(!form.reportValidity())return;let docs=0,interes=0,comision=0,admin=0,seguro=0;let vend=Number(form.vendedor_id.value),tasaCom=Number(tasasVendedor[vend]||0),hoy=new Date();hoy.setHours(0,0,0,0);document.querySelectorAll('.doc-row').forEach(row=>{let monto=Number(row.querySelector('.doc-importe').value),vence=new Date(row.querySelector('.doc-vencimiento').value+'T00:00:00'),dias=Math.max(0,Math.round((vence-hoy)/86400000)),ti=Number(rango(rangosDescuento,monto)||form.tasa_interes.value),ts=Number(rango(rangosSeguro,monto)),ta=Number(rango(rangosAdmin,monto)),i=monto*dias*ti/divisorCalculo,c=monto*dias*tasaCom/divisorCalculo,a=monto*dias*ta/divisorCalculo,s=monto*dias*ts/divisorCalculo,des=i+c+a+s,net=monto-des;docs+=monto;interes+=i;comision+=c;admin+=a;seguro+=s;row.querySelector('.calc-plazo').textContent=dias+' días';row.querySelector('.calc-interes').textContent=fmt(i);row.querySelector('.calc-comision').textContent=fmt(c);row.querySelector('.calc-admin').textContent=fmt(a);row.querySelector('.calc-seguro').textContent=fmt(s);row.querySelector('.calc-descuento').textContent=fmt(des);row.querySelector('.calc-neto').textContent=fmt(net)});let capital=docs-interes-comision-admin-seguro,operativo=0;if(Number(parametrosGenerales.calcula_gastos_bcp)===1&&limiteOperativo&&docs<=Number(parametrosGenerales.salario_minimo)*Number(limiteOperativo.max_salarios_credito))operativo=Math.min(Number(limiteOperativo.jornal_minimo)*Number(limiteOperativo.max_jornales),Math.max(0,capital)*Number(limiteOperativo.porcentaje_neto)/100);let neto=docs-interes-comision-admin-seguro-operativo;[['rDocumentos',docs],['rInteres',interes],['rComision',comision],['rOperativo',operativo],['rAdmin',admin],['rSeguro',seguro],['rNeto',neto]].forEach(x=>document.getElementById(x[0]).textContent=fmt(x[1]));document.querySelector('#resumenCalculo').classList.remove('d-none');document.querySelector('#guardarOperacion').disabled=false;document.querySelector('#resumenCalculo').scrollIntoView({behavior:'smooth',block:'center'})}document.querySelector('#operacionForm').addEventListener('input',invalidarCalculo);document.querySelector('#operacionMoneda').addEventListener('change',()=>{document.querySelector('#docs').innerHTML='';addDoc()});addDoc();</script>
 <?php }else{?><div class="d-flex justify-content-between mb-3"><h2>Operaciones</h2><a class="btn btn-primary" href="?p=operaciones&action=form">Nueva operación</a></div><div class="card"><table class="table table-hover mb-0"><thead><tr><th>Nro.</th><th>Fecha</th><th>Cliente</th><th>Moneda</th><th>Importe</th><th>Descuento</th><th>Neto</th><th>Estado</th></tr></thead><tbody><?php foreach(rows("SELECT o.*,CONCAT(c.nombres,' ',c.apellidos) cliente,m.codigo,m.nombre moneda,m.simbolo,m.decimales FROM operaciones o JOIN clientes c ON c.id=o.cliente_id JOIN monedas m ON m.id=o.moneda_id ORDER BY m.orden,o.id DESC") as $o):?><tr><td><a href="?p=operaciones&view=<?=$o['id']?>">#<?=$o['id']?></a></td><td><?=e($o['fecha'])?></td><td><?=e($o['cliente'])?></td><td><?=e($o['moneda'])?></td><td><?=money_currency((float)$o['total_importe'],$o)?></td><td><?=money_currency((float)$o['total_descuento'],$o)?></td><td><?=money_currency((float)$o['total_neto'],$o)?></td><td><span class="badge bg-success"><?=e($o['estado'])?></span></td></tr><?php endforeach;?></tbody></table></div><?php }page_end();exit;
}

if($p==='usuarios'){
 require_permission('usuarios');try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$name=poststr('nombre');$login=poststr('usuario');$email=poststr('email');$role=(int)($_POST['rol_id']??0);$pass=$_POST['password']??'';$eid=(int)($_POST['id']??0);if(!$name||!$login||!$role)throw new RuntimeException('Nombre, usuario y rol son obligatorios.');if(!$eid&&strlen($pass)<8)throw new RuntimeException('La contraseña debe tener al menos 8 caracteres.');if($eid){if($pass){$hash=password_hash($pass,PASSWORD_DEFAULT);$st=db()->prepare("UPDATE usuarios SET nombre=?,usuario=?,email=?,rol_id=?,password=?,activo=1 WHERE id=?");$st->bind_param('sssisi',$name,$login,$email,$role,$hash,$eid);}else{$st=db()->prepare("UPDATE usuarios SET nombre=?,usuario=?,email=?,rol_id=?,activo=1 WHERE id=?");$st->bind_param('sssii',$name,$login,$email,$role,$eid);}}else{$hash=password_hash($pass,PASSWORD_DEFAULT);$st=db()->prepare("INSERT INTO usuarios(nombre,usuario,email,rol_id,password) VALUES(?,?,?,?,?)");$st->bind_param('sssis',$name,$login,$email,$role,$hash);}$st->execute();flash('success','Usuario guardado.');redirect('app.php?p=usuarios');}}catch(Throwable $e){$error=$e->getMessage();}$edit=$id?rows("SELECT * FROM usuarios WHERE id=$id")[0]??[]:[];page_start('Usuarios');?>
 <h2>Usuarios</h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="card mb-4"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$id?>"><div class="col-md-3"><label class="form-label">Nombre</label><input class="form-control" name="nombre" value="<?=e($edit['nombre']??'')?>" required></div><div class="col-md-3"><label class="form-label">Usuario</label><input class="form-control" name="usuario" value="<?=e($edit['usuario']??'')?>" required></div><div class="col-md-3"><label class="form-label">Correo</label><input type="email" class="form-control" name="email" value="<?=e($edit['email']??'')?>"></div><div class="col-md-3"><label class="form-label">Rol</label><select class="form-select" name="rol_id" required><?php foreach(opts('roles') as $r):?><option value="<?=$r['id']?>" <?=($edit['rol_id']??0)==$r['id']?'selected':''?>><?=e($r['nombre'])?></option><?php endforeach;?></select></div><div class="col-md-4"><label class="form-label">Contraseña <?=$id?'(vacío conserva la actual)':''?></label><input type="password" class="form-control" name="password" <?=$id?'':'required minlength="8"'?>></div><div class="col-12"><button class="btn btn-primary">Guardar</button></div></form></div></div><div class="card"><table class="table mb-0"><thead><tr><th>Nombre</th><th>Usuario</th><th>Rol</th><th></th></tr></thead><tbody><?php foreach(rows("SELECT u.*,r.nombre rol FROM usuarios u JOIN roles r ON r.id=u.rol_id WHERE u.activo=1") as $u):?><tr><td><?=e($u['nombre'])?></td><td><?=e($u['usuario'])?></td><td><?=e($u['rol'])?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=usuarios&id=<?=$u['id']?>">Editar</a></td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='roles'){
 require_permission('roles');try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$name=poststr('nombre');$desc=poststr('descripcion');$rid=(int)($_POST['id']??0);if(!$name)throw new RuntimeException('El nombre es obligatorio.');$cn=db();$cn->begin_transaction();if($rid){$st=$cn->prepare("UPDATE roles SET nombre=?,descripcion=? WHERE id=? AND es_admin=0");$st->bind_param('ssi',$name,$desc,$rid);$st->execute();}else{$st=$cn->prepare("INSERT INTO roles(nombre,descripcion) VALUES(?,?)");$st->bind_param('ss',$name,$desc);$st->execute();$rid=$st->insert_id;}$isAdmin=(int)($cn->query("SELECT es_admin FROM roles WHERE id=$rid")->fetch_assoc()['es_admin']??0);if(!$isAdmin){$cn->query("DELETE FROM rol_permisos WHERE rol_id=$rid");$ins=$cn->prepare("INSERT INTO rol_permisos(rol_id,permiso_id) VALUES(?,?)");foreach($_POST['permisos']??[] as $pid){$pid=(int)$pid;$ins->bind_param('ii',$rid,$pid);$ins->execute();}}$cn->commit();audit('GUARDAR','roles',$rid);flash('success','Rol y permisos guardados.');redirect('app.php?p=roles');}}catch(Throwable $e){if(isset($cn))$cn->rollback();$error=$e->getMessage();}$edit=$id?rows("SELECT * FROM roles WHERE id=$id")[0]??[]:[];$selected=$id?array_column(rows("SELECT permiso_id FROM rol_permisos WHERE rol_id=$id"),'permiso_id'):[];page_start('Roles y permisos');?>
 <h2>Roles y permisos</h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="card mb-4"><div class="card-body"><form method="post"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$id?>"><div class="row g-3"><div class="col-md-4"><label class="form-label">Rol</label><input class="form-control" name="nombre" value="<?=e($edit['nombre']??'')?>" required></div><div class="col-md-8"><label class="form-label">Descripción</label><input class="form-control" name="descripcion" value="<?=e($edit['descripcion']??'')?>"></div></div><h6 class="mt-4">Opciones habilitadas</h6><div class="row"><?php foreach(rows("SELECT * FROM permisos WHERE activo=1 ORDER BY grupo_menu,nombre") as $pr):?><div class="col-md-4"><label class="form-check border rounded p-2 ps-5 mb-2"><input type="checkbox" class="form-check-input" name="permisos[]" value="<?=$pr['id']?>" <?=in_array($pr['id'],$selected)?'checked':''?> <?=($edit['es_admin']??0)?'disabled':''?>><span class="fw-semibold"><?=e($pr['nombre'])?></span><small class="d-block text-muted"><?=e($pr['grupo_menu'])?></small></label></div><?php endforeach;?></div><button class="btn btn-primary mt-2">Guardar rol</button></form></div></div><div class="card"><table class="table mb-0"><thead><tr><th>Rol</th><th>Descripción</th><th>Tipo</th><th></th></tr></thead><tbody><?php foreach(rows("SELECT * FROM roles WHERE activo=1") as $r):?><tr><td><?=e($r['nombre'])?></td><td><?=e($r['descripcion'])?></td><td><?=$r['es_admin']?'Administrador':'Personalizado'?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=roles&id=<?=$r['id']?>">Editar</a></td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='imprimir'){
 require_permission('operaciones');$oid=(int)($_GET['view']??0);$o=rows("SELECT o.*,CONCAT(c.nombres,' ',c.apellidos) cliente,c.documento,v.nombre vendedor,cv.nombre canal,g.nombre garantia,m.codigo,m.nombre moneda,m.simbolo,m.decimales,oc.nombre origen_capital FROM operaciones o JOIN monedas m ON m.id=o.moneda_id LEFT JOIN origenes_capital oc ON oc.id=o.origen_capital_id JOIN clientes c ON c.id=o.cliente_id JOIN vendedores v ON v.id=o.vendedor_id JOIN canales_venta cv ON cv.id=o.canal_id JOIN garantias g ON g.id=o.garantia_id WHERE o.id=$oid")[0]??null;if(!$o)exit('Operación no encontrada');$totalInteres=(float)$o['total_descuento']-(float)$o['total_comision']-(float)$o['total_gastos_administrativos']-(float)$o['total_seguro'];$fm=fn($v)=>money_currency((float)$v,$o);?><!doctype html><html lang="es"><head><meta charset="utf-8"><title>Resumen #<?=$oid?></title><style>body{font-family:Montserrat,Poppins,Arial,Helvetica,sans-serif;margin:30px;color:#071D2B;font-size:13px}h1{color:#071D2B;margin:0;font-size:22px}.head{display:flex;justify-content:space-between;align-items:center;border-bottom:3px solid #2AA9A4;padding-bottom:12px}.print-brand{display:flex;align-items:center;gap:14px}.print-logo{width:76px;height:76px;object-fit:contain}.tagline{color:#6B7280}.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:15px;margin:22px 0}.table{width:100%;border-collapse:collapse}.table th,.table td{padding:7px;border:1px solid #ccd5df;text-align:right}.table th{background:#173247;color:#FFFFFF}.table th:first-child,.table td:first-child{text-align:left}.totals{width:620px;max-width:100%;margin:28px auto;border:1px solid #ccd5df;border-radius:8px;overflow:hidden}.totals h3{text-align:center;background:#173247;color:#FFFFFF;margin:0;padding:10px}.totals table{width:100%;border-collapse:collapse}.totals th,.totals td{padding:7px 12px;border-bottom:1px solid #e1e6ec}.totals th:first-child,.totals td:first-child{text-align:left}.totals th:not(:first-child),.totals td:not(:first-child){text-align:right}.neto{background:#d9f3f1;color:#071D2B;font-size:16px;font-weight:bold}button{padding:8px 16px}@media print{button{display:none}body{margin:10px}}</style></head><body><div style="text-align:right;margin-bottom:10px"><button onclick="print()">Imprimir</button></div><div class="head"><div class="print-brand"><img class="print-logo" src="<?=url('assets/img/logo_principal.png')?>" alt="Logo"><div><h1><?=e(APP_NAME)?></h1><div class="tagline"><?=e(APP_TAGLINE)?></div></div></div><div><b>Resumen de descuento #<?=$oid?></b><br><?=e($o['fecha'])?></div></div><div class="grid"><div><b>Cliente</b><br><?=e($o['cliente'])?></div><div><b>Documento</b><br><?=e($o['documento'])?></div><div><b>Vendedor</b><br><?=e($o['vendedor'])?></div><div><b>Canal / Garantía</b><br><?=e($o['canal'])?> / <?=e($o['garantia'])?></div></div><table class="table"><thead><tr><th>Tipo</th><th>Documento</th><th>Plazo</th><th>Interés</th><th>Comisión</th><th>Gto.Adm.</th><th>Seguro</th><th>Total Descuento</th><th>Neto</th></tr></thead><tbody><?php foreach(rows("SELECT d.*,td.nombre tipo FROM operacion_detalles d JOIN tipos_documento td ON td.id=d.tipo_documento_id WHERE operacion_id=$oid") as $d):?><tr><td><?=e($d['tipo'])?><br><small><?=e($d['vencimiento'])?></small></td><td><?=$fm((float)$d['importe'])?></td><td><?=$d['plazo_dias']?> días</td><td><?=$fm((float)$d['interes'])?></td><td><?=$fm((float)$d['comision'])?></td><td><?=$fm((float)$d['gasto_administrativo'])?></td><td><?=$fm((float)$d['seguro'])?></td><td><?=$fm((float)$d['descuento'])?></td><td><?=$fm((float)$d['neto'])?></td></tr><?php endforeach;?></tbody></table><div class="totals"><h3>Totales</h3><table><thead><tr><th>Concepto</th><th>Documentos</th><th>Descuentos</th></tr></thead><tbody><tr><td>Documentos</td><td><?=$fm((float)$o['total_importe'])?></td><td></td></tr><tr><td>Interés</td><td></td><td><?=$fm($totalInteres)?></td></tr><tr><td>Comisión</td><td></td><td><?=$fm((float)$o['total_comision'])?></td></tr><tr><td>Gasto Operativo</td><td></td><td><?=$fm((float)$o['gastos_operativos'])?></td></tr><tr><td>Gastos Admin.</td><td></td><td><?=$fm((float)$o['total_gastos_administrativos'])?></td></tr><tr><td>Seguro</td><td></td><td><?=$fm((float)$o['total_seguro'])?></td></tr><tr class="neto"><td>Neto a retirar</td><td colspan="2"><?=$fm((float)$o['total_neto'])?></td></tr></tbody></table></div></body></html><?php exit;
}

if($p==='grupo'){
 $group=trim($_GET['g']??'');$valid=['Clientes','Mantenimiento','Parámetros','Operaciones','Caja','Informes','Seguridad'];if(!in_array($group,$valid,true))exit('Grupo inválido');page_start($group);$items=array_filter(menu_registry(),fn($m)=>$m[3]===$group);?>
 <div class="mb-4"><h2><?=e($group)?></h2><p class="text-muted">Seleccione una opción</p></div><div class="row g-3"><?php foreach($items as $key=>$m):if(!allowed($key))continue;?><div class="col-md-6 col-xl-4"><a class="text-decoration-none" href="<?=url($m[2])?>"><div class="card h-100"><div class="card-body py-4"><i class="fa-solid <?=e($m[1])?> text-primary fs-2 mb-3"></i><h5 class="text-dark"><?=e($m[0])?></h5><span class="text-muted">Abrir opción <i class="fa fa-arrow-right"></i></span></div></div></a></div><?php endforeach;?></div><?php page_end();exit;
}

if($p==='rangos'){
 $t=$_GET['t']??'';$defs=['tasa_seguro'=>['Tasa Seguro','Monto desde','Monto hasta','Tasa anual %','monto_desde','monto_hasta','tasa'], 'gastos_administrativos'=>['Tasa Gastos Administrativos','Monto desde','Monto hasta','Porcentaje %','monto_desde','monto_hasta','tasa'], 'gastos_cobranza'=>['Tasa Gastos de Cobranza','Días mora desde','Días mora hasta','Tasa anual %','dias_desde','dias_hasta','tasa']];if(!isset($defs[$t]))exit('Configuración inválida');require_permission($t);$d=$defs[$t];
 try{if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$a=postfloat('a');$b=postfloat('b');$c=postfloat('c');if($a<0||$b<$a||$c<0)throw new RuntimeException('Revise el rango y el valor.');$st=db()->prepare("INSERT INTO `$t`(`{$d[4]}`,`{$d[5]}`,`{$d[6]}`) VALUES(?,?,?)");$st->bind_param('ddd',$a,$b,$c);$st->execute();flash('success','Rango guardado.');redirect("app.php?p=rangos&t=$t");}}catch(Throwable $e){$error=$e->getMessage();}page_start($d[0]);?>
 <h2><?=e($d[0])?></h2><?php if(isset($error)):?><div class="alert alert-danger"><?=e($error)?></div><?php endif;?><div class="card mb-4"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><?php foreach([['a',$d[1]],['b',$d[2]],['c',$d[3]]] as $f):?><div class="col-md-4"><label class="form-label required"><?=e($f[1])?></label><input type="number" min="0" step="0.0001" class="form-control" name="<?=$f[0]?>" required></div><?php endforeach;?><div><button class="btn btn-primary">Guardar rango</button></div></form></div></div><div class="card"><table class="table mb-0"><thead><tr><th><?=e($d[1])?></th><th><?=e($d[2])?></th><th><?=e($d[3])?></th></tr></thead><tbody><?php foreach(rows("SELECT * FROM `$t` WHERE activo=1 ORDER BY `{$d[4]}`") as $r):?><tr><td><?=e($r[$d[4]])?></td><td><?=e($r[$d[5]])?></td><td><?=e($r[$d[6]])?></td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='limite_operativo'){
 require_permission('limite_operativo');if($_SERVER['REQUEST_METHOD']==='POST'){check_csrf();$st=db()->prepare("INSERT INTO limites_operativos(fecha_desde,fecha_hasta,jornal_minimo,salario_minimo,max_jornales,porcentaje_neto,max_salarios_credito) VALUES(?,?,?,?,?,?,?)");$a=poststr('desde');$b=poststr('hasta');$j=postfloat('jornal');$s=(float)(general_params()['salario_minimo']??0);$mj=postfloat('jornales');$pn=postfloat('porcentaje');$ms=postfloat('salarios');$st->bind_param('ssddddd',$a,$b,$j,$s,$mj,$pn,$ms);$st->execute();flash('success','Límite guardado.');redirect('app.php?p=limite_operativo');}page_start('Límite Gastos Operativos BCP');?>
 <h2>Límite Gastos Operativos BCP</h2><div class="alert alert-info">Para créditos de hasta el máximo de salarios configurado, se aplica el menor entre jornales × jornal mínimo y el porcentaje del neto a desembolsar.</div><div class="card mb-4"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><?php foreach([['desde','Desde','date'],['hasta','Hasta','date'],['jornal','Jornal mínimo','number'],['jornales','Máx. jornales','number'],['porcentaje','% del neto','number'],['salarios','Máx. salarios del crédito','number']] as $f):?><div class="col-md-3"><label class="form-label"><?=$f[1]?></label><input type="<?=$f[2]?>" step="0.01" class="form-control" name="<?=$f[0]?>" required></div><?php endforeach;?><div><button class="btn btn-primary">Guardar vigencia</button></div></form></div></div><div class="card"><table class="table"><thead><tr><th>Vigencia</th><th>Jornal</th><th>Salario</th><th>Límite</th></tr></thead><tbody><?php foreach(rows("SELECT * FROM limites_operativos WHERE activo=1 ORDER BY fecha_desde DESC") as $r):?><tr><td><?=e($r['fecha_desde'])?> a <?=e($r['fecha_hasta'])?></td><td><?=money((float)$r['jornal_minimo'])?></td><td><?=money((float)$r['salario_minimo'])?></td><td>Menor entre <?=$r['max_jornales']?> jornales y <?=$r['porcentaje_neto']?>% del neto</td></tr><?php endforeach;?></tbody></table></div><?php page_end();exit;
}

if($p==='informe_clientes'){
 require_permission('informe_clientes');$cid=(int)($_GET['cliente_id']??0);page_start('Informe de Clientes');?>
 <h2>Informe de saldos por cliente</h2><form class="card card-body mb-4"><input type="hidden" name="p" value="informe_clientes"><div class="row"><div class="col-md-8 autocomplete"><label class="form-label">Cliente</label><input class="form-control lookup" placeholder="Buscar cliente"><input type="hidden" name="cliente_id"><div class="autocomplete-results"></div></div><div class="col-md-2 align-self-end"><button class="btn btn-primary">Consultar</button></div></div></form><?php if($cid):?><div class="card"><table class="table mb-0"><thead><tr><th>Nro. Operación</th><th>Monto documento</th><th>Capital</th><th>Saldo</th><th>Moratorio</th><th>Punitorio</th><th>G. cobranza</th><th>Total</th><th></th></tr></thead><tbody><?php foreach(rows("SELECT d.id,d.operacion_id FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id WHERE o.cliente_id=$cid ORDER BY o.moneda_id,d.id DESC") as $item):$r=estado_deuda((int)$item['id']);?><tr><td>#<?=$r['operacion_id']?></td><td><?=money((float)$r['importe'])?></td><td><?=money((float)$r['capital_original'])?></td><td><?=money((float)$r['saldo'])?></td><td><?=money((float)$r['moratorio'])?></td><td><?=money((float)$r['punitorio'])?></td><td><?=money((float)$r['gastos_cobranza'])?></td><td class="fw-bold"><?=money((float)$r['total_adeudado'])?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=operaciones&view=<?=$r['operacion_id']?>">Ver detalle</a></td></tr><?php endforeach;?></tbody></table></div><?php endif;?><script>window.BASE_URL='<?=BASE_URL?>';let x=document.querySelector('.lookup');autocomplete(x,x.nextElementSibling,'clientes');</script><?php page_end();exit;
}

if($p==='inventario_cartera'){
 require_permission('inventario_cartera');$clienteId=(int)($_GET['cliente_id']??0);$operacionId=(int)($_GET['operacion_id']??0);page_start('Inventario de Cartera');?>
 <div class="d-flex justify-content-between align-items-center mb-3"><div><h2>Inventario de Cartera</h2><p class="text-muted mb-0">Saldos vigentes de documentos</p></div><?php if($operacionId):?><a class="btn btn-outline-secondary" href="?p=inventario_cartera&cliente_id=<?=$clienteId?>"><i class="fa fa-arrow-left me-1"></i>Volver a operaciones</a><?php elseif($clienteId):?><a class="btn btn-outline-secondary" href="?p=inventario_cartera&tipo=descuento"><i class="fa fa-arrow-left me-1"></i>Volver a clientes</a><?php endif;?></div>
 <?php if($operacionId):$op=rows("SELECT o.*,CONCAT(c.nombres,' ',c.apellidos) cliente FROM operaciones o JOIN clientes c ON c.id=o.cliente_id WHERE o.id=$operacionId AND o.cliente_id=$clienteId")[0]??null;if($op):?>
 <div class="card mb-3"><div class="card-body"><h5 class="mb-1">Operación #<?=$operacionId?></h5><div class="text-muted"><?=e($op['cliente'])?> · <?=e($op['fecha'])?></div></div></div><div class="card"><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><th>Documento</th><th>Tipo</th><th>Vencimiento</th><th class="text-end">Importe</th><th class="text-end">Cobrado aplicable</th><th class="text-end">Saldo documento</th></tr></thead><tbody><?php $totalDetalle=0;foreach(rows("SELECT d.id,td.nombre tipo FROM operacion_detalles d JOIN tipos_documento td ON td.id=d.tipo_documento_id WHERE d.operacion_id=$operacionId ORDER BY d.id") as $item):$ed=estado_deuda((int)$item['id']);$cobrado=(float)$ed['importe']-(float)$ed['saldo'];$totalDetalle+=(float)$ed['saldo'];?><tr><td>#<?=$ed['id']?></td><td><?=e($item['tipo'])?></td><td><?=e($ed['vencimiento'])?></td><td class="text-end"><?=money((float)$ed['importe'])?></td><td class="text-end"><?=money($cobrado)?></td><td class="text-end fw-bold"><?=money((float)$ed['saldo'])?></td></tr><?php endforeach;?><tr class="table-primary fw-bold"><td colspan="5">Total operación</td><td class="text-end"><?=money($totalDetalle)?></td></tr></tbody></table></div></div><?php endif;
 elseif($clienteId):$cl=rows("SELECT * FROM clientes WHERE id=$clienteId")[0]??null;if($cl):?>
 <div class="card mb-3"><div class="card-body"><h5 class="mb-1"><?=e($cl['razon_social']?:trim($cl['nombres'].' '.$cl['apellidos']))?></h5><div class="text-muted">Documento <?=e($cl['documento'])?></div></div></div><div class="card"><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><th>Operación</th><th>Fecha</th><th>Documentos</th><th class="text-end">Saldo de documentos</th><th></th></tr></thead><tbody><?php $totalCliente=0;foreach(rows("SELECT o.id,o.fecha,COUNT(d.id) documentos FROM operaciones o JOIN operacion_detalles d ON d.operacion_id=o.id WHERE o.cliente_id=$clienteId GROUP BY o.id ORDER BY o.id DESC") as $op):$saldoOp=total_saldos('o.id='.(int)$op['id']);$totalCliente+=$saldoOp;?><tr><td>#<?=$op['id']?></td><td><?=e($op['fecha'])?></td><td><?=$op['documentos']?></td><td class="text-end fw-bold"><?=money($saldoOp)?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=inventario_cartera&cliente_id=<?=$clienteId?>&operacion_id=<?=$op['id']?>">Ver documentos</a></td></tr><?php endforeach;?><tr class="table-primary fw-bold"><td colspan="3">Total cliente</td><td class="text-end"><?=money($totalCliente)?></td><td></td></tr></tbody></table></div></div><?php endif;
 elseif(isset($_GET['tipo'])):?>
 <div class="card"><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><th>Cliente</th><th>Documento</th><th>Operaciones</th><th class="text-end">Saldo de documentos</th><th></th></tr></thead><tbody><?php $granTotal=0;foreach(rows("SELECT c.id,c.documento,c.nombres,c.apellidos,c.razon_social,COUNT(DISTINCT o.id) operaciones FROM clientes c JOIN operaciones o ON o.cliente_id=c.id JOIN operacion_detalles d ON d.operacion_id=o.id GROUP BY c.id ORDER BY c.nombres,c.apellidos") as $cl):$saldoCliente=total_saldos('o.cliente_id='.(int)$cl['id']);$granTotal+=$saldoCliente;?><tr><td><?=e($cl['razon_social']?:trim($cl['nombres'].' '.$cl['apellidos']))?></td><td><?=e($cl['documento'])?></td><td><?=$cl['operaciones']?></td><td class="text-end fw-bold"><?=money($saldoCliente)?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=inventario_cartera&cliente_id=<?=$cl['id']?>">Ver operaciones</a></td></tr><?php endforeach;?><tr class="table-primary fw-bold"><td colspan="3">Total cartera</td><td class="text-end"><?=money($granTotal)?></td><td></td></tr></tbody></table></div></div>
 <?php else:$totalCartera=total_saldos();?>
 <div class="row"><div class="col-md-8 col-xl-5"><a class="text-decoration-none" href="?p=inventario_cartera&tipo=descuento"><div class="card h-100"><div class="card-body py-4"><div class="d-flex align-items-center"><div class="me-3 text-primary fs-2"><i class="fa-solid fa-file-invoice-dollar"></i></div><div><h5 class="text-dark mb-1">Descuento de Documentos</h5><div class="display-6 fw-bold text-primary"><?=money($totalCartera)?></div><small class="text-muted">Saldo total de documentos · Ver por cliente</small></div></div></div></div></a></div></div>
 <?php endif;page_end();exit;
}

require __DIR__.'/modules_importar.php';
require __DIR__.'/modules_capitales.php';
require __DIR__.'/modules_parametros.php';

if($p==='caja'){
 require_permission('caja');$did=(int)($_GET['detalle_id']??$_POST['detalle_id']??0);if($_SERVER['REQUEST_METHOD']==='POST'&&$did){check_csrf();$amount=postfloat('monto');$d=estado_deuda($did);if(!$d)throw new RuntimeException('Documento no encontrado.');if($amount<=0||$amount>$d['total_adeudado']+.01)throw new RuntimeException('El monto debe ser mayor a cero y no superar el total adeudado.');$concepts=['Gastos de Cobranza'=>$d['gastos_cobranza'],'Interés Punitorio'=>$d['punitorio'],'Interés Moratorio'=>$d['moratorio'],'Gastos Administrativos'=>$d['gastos_administrativos_pendientes'],'Gastos Operativos'=>$d['gastos_operativos_pendientes'],'Comisión'=>$d['comision_pendiente'],'Seguro'=>$d['seguro_pendiente'],'Interés'=>$d['interes_pendiente'],'Capital'=>$d['saldo']];$cn=db();$cn->begin_transaction();$rec='REC-'.date('YmdHis');$uid=(int)user()['id'];$st=$cn->prepare("INSERT INTO cobros(cliente_id,operacion_detalle_id,monto,recibo,usuario_id) VALUES(?,?,?,?,?)");$st->bind_param('iidsi',$d['cliente_id'],$did,$amount,$rec,$uid);$st->execute();$cob=$st->insert_id;$left=$amount;$order=1;$ap=$cn->prepare("INSERT INTO cobro_aplicaciones(cobro_id,concepto,monto,orden_aplicacion) VALUES(?,?,?,?)");foreach($concepts as $name=>$due){$pay=min($left,$due);if($pay>0){$ap->bind_param('isdi',$cob,$name,$pay,$order);$ap->execute();$left-=$pay;}$order++;if($left<=0)break;}$cn->query("INSERT INTO detalle_saldos(operacion_detalle_id,monto_abonado,capital) VALUES($did,$amount,{$d['capital_original']}) ON DUPLICATE KEY UPDATE monto_abonado=monto_abonado+$amount,capital=VALUES(capital)");sincronizar_cobros_detalle($did);$cn->commit();flash('success',"Cobro registrado. Recibo $rec");redirect("app.php?p=caja&cliente_id={$d['cliente_id']}");}page_start('Caja / Cobros');$cid=(int)($_GET['cliente_id']??0);if($did&&!$cid){$found=rows("SELECT o.cliente_id FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id WHERE d.id=$did");$cid=(int)($found[0]['cliente_id']??0);}?>
<h2 class="mb-3">Caja / Cobros</h2>
<?php if(!$did):?><form method="get" class="card card-body mb-4"><input type="hidden" name="p" value="caja"><div class="row g-3"><div class="col-md-8 autocomplete"><label class="form-label required">Cliente</label><input class="form-control lookup" placeholder="Buscar cliente por nombre o documento"><input type="hidden" name="cliente_id" value="<?=$cid?>"><div class="autocomplete-results"></div></div><div class="col-md-2 align-self-end"><button class="btn btn-primary">Buscar operaciones</button></div></div></form>
<?php if($cid):?><div class="card mb-4"><div class="card-header">Operaciones y documentos del cliente</div><div class="table-responsive"><table class="table table-hover mb-0"><thead><tr><th>Nro. Operación</th><th>Monto documento</th><th>Saldo</th><th>Moratorio</th><th>Punitorio</th><th>G. cobranza</th><th>Total</th><th></th></tr></thead><tbody><?php foreach(rows("SELECT d.id FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id WHERE o.cliente_id=$cid ORDER BY o.moneda_id,d.vencimiento,d.id") as $item):$deuda=estado_deuda((int)$item['id']);?><tr><td>#<?=$deuda['operacion_id']?></td><td><?=money((float)$deuda['importe'])?></td><td><?=money((float)$deuda['saldo'])?></td><td><?=money((float)$deuda['moratorio'])?></td><td><?=money((float)$deuda['punitorio'])?></td><td><?=money((float)$deuda['gastos_cobranza'])?></td><td class="fw-bold"><?=money((float)$deuda['total_adeudado'])?></td><td><a class="btn btn-sm btn-outline-primary" href="?p=caja&cliente_id=<?=$cid?>&detalle_id=<?=$deuda['id']?>">Seleccionar</a></td></tr><?php endforeach;?></tbody></table></div></div><?php endif;?><?php endif;?>
<?php if($did):$d=estado_deuda($did);if($d):$cl=rows("SELECT CONCAT(nombres,' ',apellidos) nombre FROM clientes WHERE id=".(int)$d['cliente_id'])[0]??[];?><div class="card mb-4 border-primary"><div class="card-header">Registrar cobro</div><div class="card-body"><h5><?=e($cl['nombre']??'')?> — Operación #<?=$d['operacion_id']?> / Documento #<?=$did?></h5><div class="row mb-3"><div class="col"><small>Saldo</small><div class="fw-bold"><?=money($d['saldo'])?></div></div><div class="col"><small>Moratorio</small><div class="fw-bold"><?=money($d['moratorio'])?></div></div><div class="col"><small>Punitorio</small><div class="fw-bold"><?=money($d['punitorio'])?></div></div><div class="col"><small>G. cobranza</small><div class="fw-bold"><?=money($d['gastos_cobranza'])?></div></div><div class="col"><small>Total</small><div class="fw-bold text-primary"><?=money($d['total_adeudado'])?></div></div></div><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="detalle_id" value="<?=$did?>"><div class="col-md-4"><label class="form-label required">Monto a cobrar</label><input type="text" inputmode="decimal" maxlength="16" id="montoCobroVisible" class="form-control" placeholder="Hasta 9.999.999.999,99" required><input type="hidden" id="montoCobro" name="monto"></div><div class="col-md-3 align-self-end"><button class="btn btn-primary">Registrar cobro</button> <a class="btn btn-outline-secondary" href="?p=caja&cliente_id=<?=$cid?>">Volver</a></div></form></div></div><?php endif;endif;?>
<?php if(!$did):?><div class="card"><div class="card-header">Últimos cobros e imputación</div><div class="table-responsive"><table class="table table-hover table-sm mb-0"><thead><tr><th>Fecha / Recibo</th><th>Cliente / Operación</th><th>G. cobranza</th><th>Punitorio</th><th>Moratorio</th><th>G. Adm.</th><th>G. Oper.</th><th>Comisión</th><th>Seguro</th><th>Interés</th><th>Capital</th><th>Total</th></tr></thead><tbody><?php foreach(rows("SELECT co.*,CONCAT(cl.nombres,' ',cl.apellidos) cliente,d.operacion_id,COALESCE(SUM(CASE WHEN ca.concepto='Gastos de Cobranza' THEN ca.monto END),0) cobranza,COALESCE(SUM(CASE WHEN ca.concepto='Interés Punitorio' THEN ca.monto END),0) punitorio,COALESCE(SUM(CASE WHEN ca.concepto='Interés Moratorio' THEN ca.monto END),0) moratorio,COALESCE(SUM(CASE WHEN ca.concepto='Gastos Administrativos' THEN ca.monto END),0) administrativo,COALESCE(SUM(CASE WHEN ca.concepto='Gastos Operativos' THEN ca.monto END),0) operativo,COALESCE(SUM(CASE WHEN ca.concepto='Comisión' THEN ca.monto END),0) comision,COALESCE(SUM(CASE WHEN ca.concepto='Seguro' THEN ca.monto END),0) seguro,COALESCE(SUM(CASE WHEN ca.concepto='Interés' THEN ca.monto END),0) interes,COALESCE(SUM(CASE WHEN ca.concepto='Capital' THEN ca.monto END),0) capital FROM cobros co JOIN clientes cl ON cl.id=co.cliente_id JOIN operacion_detalles d ON d.id=co.operacion_detalle_id LEFT JOIN cobro_aplicaciones ca ON ca.cobro_id=co.id GROUP BY co.id ORDER BY co.id DESC LIMIT 25") as $co):?><tr><td><?=e($co['fecha'])?><br><small><?=e($co['recibo'])?></small></td><td><?=e($co['cliente'])?><br><small>#<?=$co['operacion_id']?> / Doc. #<?=$co['operacion_detalle_id']?></small></td><td><?=money((float)$co['cobranza'])?></td><td><?=money((float)$co['punitorio'])?></td><td><?=money((float)$co['moratorio'])?></td><td><?=money((float)$co['administrativo'])?></td><td><?=money((float)$co['operativo'])?></td><td><?=money((float)$co['comision'])?></td><td><?=money((float)$co['seguro'])?></td><td><?=money((float)$co['interes'])?></td><td><?=money((float)$co['capital'])?></td><td class="fw-bold"><?=money((float)$co['monto'])?></td></tr><?php endforeach;?></tbody></table></div></div>
<?php endif;?><script>window.BASE_URL='<?=BASE_URL?>';let cajaCliente=document.querySelector('.lookup');if(cajaCliente)autocomplete(cajaCliente,cajaCliente.nextElementSibling,'clientes');let montoVisible=document.querySelector('#montoCobroVisible'),montoReal=document.querySelector('#montoCobro');if(montoVisible){const cobroUsd=<?=json_encode(($d['moneda_codigo']??'PYG')==='USD')?>;montoVisible.addEventListener('input',()=>{let digits=montoVisible.value.replace(/\D/g,'').slice(0,12),value=cobroUsd?Number(digits)/100:Number(digits);montoReal.value=value.toFixed(cobroUsd?2:0);montoVisible.value=digits?new Intl.NumberFormat('es-PY',{minimumFractionDigits:cobroUsd?2:0,maximumFractionDigits:cobroUsd?2:0}).format(value):''})}</script><?php page_end();exit;?>
 <h2>Caja / Cobros</h2><div class="alert alert-secondary">Orden de imputación: Gastos de Cobranza, Interés Moratorio, Interés Punitorio, Seguro, Gastos Administrativos, Gastos Operativos, Interés y Capital.</div><?php if($did):$d=rows("SELECT d.*,o.cliente_id,CONCAT(c.nombres,' ',c.apellidos) cliente FROM operacion_detalles d JOIN operaciones o ON o.id=d.operacion_id JOIN clientes c ON c.id=o.cliente_id WHERE d.id=$did")[0];?><div class="card"><div class="card-body"><h5><?=e($d['cliente'])?> — Documento #<?=$did?></h5><p>Capital: <b><?=money((float)$d['importe'])?></b> · Vencimiento: <?=e($d['vencimiento'])?></p><form method="post" class="row g-3"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="detalle_id" value="<?=$did?>"><div class="col-md-4"><label class="form-label">Monto a cobrar</label><input type="number" step="0.01" min="1" class="form-control" name="monto" required></div><div class="col-md-3 align-self-end"><button class="btn btn-primary">Registrar cobro</button> <a class="btn btn-outline-secondary" href="?p=caja&cliente_id=<?=$cid?>">Volver</a></div></form></div></div><?php else:?><p class="text-muted">Seleccione una deuda desde el Informe de Clientes.</p><?php endif;?><?php page_end();exit;
}

http_response_code(404);echo 'Página no encontrada';














