Cobranza: prepararCobranza() restringe primero la carpeta y luego el libro

El libro hereda de la carpeta el acceso del dominio y Drive no deja quitar un permiso
heredado en el hijo (403 cannotDeletePermission). El simulacro de la API de Drive
ahora reproduce la herencia; la raíz de Mi unidad simulada ya no está compartida.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Efrain (Helgen) 2026-10-20 17:57:22 -06:00
parent bfe4661968
commit c75e754137
2 changed files with 7 additions and 3 deletions

View File

@ -1054,6 +1054,7 @@ function exigirCobranza_() {
* las personas agregadas una por una (tania@, leo@). Workspace comparte con todo helgen.tech lo que se crea (§6.11).
* Usa la API de Drive con el token del script: DriveApp.setSharing da "Access denied: DriveApp." en helgen.tech
* (20/10/2026). No pide permisos nuevos (el de Drive ya lo da DriveApp). Si no puede, lanza error: nada queda abierto.
* Restringir siempre de arriba abajo: un permiso heredado de la carpeta no se puede quitar en el hijo (403).
*/
function privado_(item) {
const url = 'https://www.googleapis.com/drive/v3/files/' + encodeURIComponent(item.getId()) + '/permissions';
@ -1537,8 +1538,8 @@ function cancelarPago(folioPago, motivo) {
*/
function prepararCobranza() {
const libro = libroCobranza_();
const carpeta = carpetaCobranza_(); // primero la carpeta: el libro hereda su acceso y Drive no deja quitarlo en el hijo
const archivoLibro = privado_(DriveApp.getFileById(libro.getId()));
const carpeta = carpetaCobranza_();
Logger.log('Carpeta de cobranza: ' + carpeta.getUrl() + ' (restringida)');
Logger.log('Libro de cobranza: ' + archivoLibro.getUrl() + ' (restringido)');
Logger.log('ROL_COBRANZA: ' + (PropertiesService.getScriptProperties().getProperty('ROL_COBRANZA') || '(vacío: nadie tiene acceso)'));

View File

@ -42,8 +42,10 @@ function apiDrive(url,op){const m=url.match(/drive\/v3\/files\/([^/?]+)\/permiss
assert(op&&op.headers&&op.headers.Authorization==='Bearer TOKEN-SCRIPT','API de Drive con el token del script');
const resp=(c,b)=>({getResponseCode:()=>c,getContentText:()=>JSON.stringify(b||{})});
if(!it)return resp(404);
if(op.method==='delete'){assert.equal(m[2],'dom');it.acceso='PRIVATE';return resp(204)}
return resp(200,{permissions:[{id:'own',type:'user'}].concat(it.acceso==='DOMAIN'?[{id:'dom',type:'domain'}]:[])})}
// Como Drive: el acceso del dominio se hereda de las carpetas y no se puede quitar en el hijo mientras el padre lo tenga
let heredado=false;for(let f=it.padre;f;f=f.padre)if(f.acceso==='DOMAIN')heredado=true;
if(op.method==='delete'){assert.equal(m[2],'dom');if(heredado)return resp(403,{error:{reason:'cannotDeletePermission'}});it.acceso='PRIVATE';return resp(204)}
return resp(200,{permissions:[{id:'own',type:'user'}].concat(it.acceso==='DOMAIN'||heredado?[{id:'dom',type:'domain'}]:[])})}
function carpeta(nombre,padre){const f=compartible({id:'F'+(++nId),nombre,padre,hijos:[],archivos:[],trashed:false}); Object.assign(f,{
getId:()=>f.id,getName:()=>f.nombre,getUrl:()=>'https://drive/'+f.id,isTrashed:()=>f.trashed,
getFoldersByName:n=>{const l=f.hijos.filter(h=>h.nombre===n);let i=0;return{hasNext:()=>i<l.length,next:()=>l[i++]}},
@ -51,6 +53,7 @@ function carpeta(nombre,padre){const f=compartible({id:'F'+(++nId),nombre,padre,
createFile:b=>{const id='A'+(++nId);const a=compartible({id,blob:b,padre:f});archivos[id]=a;f.archivos.push(a);return Object.assign(a,{getId:()=>id,getUrl:()=>'https://drive/file/'+id})}});
carpetas[f.id]=f;return f}
const miUnidad=carpeta('Mi unidad',null), maestra=miUnidad.createFolder('Cotizador Helgen'), pdfRaiz=maestra.createFolder('Cotizaciones Helgen (PDF)');
miUnidad.acceso='PRIVATE'; // la raíz de Mi unidad no se comparte; la carpeta maestra sí (todo el dominio, §6.11)
const ruta=f=>f.padre?ruta(f.padre)+'/'+f.nombre:f.nombre;
archivos.DB={id:'DB',padre:maestra};