{
  "name": "flow-quote",
  "title": {
    "es": "Preparar una cotización",
    "en": "Preparar una cotización"
  },
  "summary": {
    "es": "Cliente → editar partidas y totales → exportar cotización JSON.",
    "en": "Cliente → editar partidas y totales → exportar cotización JSON."
  },
  "category": "flows",
  "status": "beta",
  "nivel": "flujos",
  "updated": "2026-10-04",
  "license": "MIT",
  "page": "https://componentes-lovat.vercel.app/c/flow-quote/",
  "requires": [],
  "files": [
    {
      "type": "html",
      "url": "https://componentes-lovat.vercel.app/raw/flow-quote/flow-quote.html",
      "content": "<section class=\"work-card\" data-flow=\"quote\"><h2>Preparar cotización</h2><p class=\"steps\"></p><div data-step><label>Cliente<input data-client required maxlength=\"120\" placeholder=\"Empresa o persona\"></label><button data-next>Editar partidas</button></div><div data-step hidden><section class=\"work-card\" aria-label=\"Costeo\"><h2>Costeo de materiales</h2><p class=\"muted\">Edita cantidades y precios. Importes de ejemplo en USD.</p><label>Filtrar partidas<input data-filter type=\"search\" placeholder=\"Material o concepto\"></label><div class=\"table-scroll\"><table><thead><tr><th>Seleccionar</th><th>Concepto</th><th>Cantidad</th><th>Precio USD</th><th>Subtotal</th></tr></thead><tbody data-rows></tbody></table></div><div class=\"actions\"><button data-add>Añadir partida</button><button data-remove>Quitar seleccionadas</button></div><p>Total USD: <output data-total></output></p><p role=\"status\" data-status></p></section><button data-back>Volver</button><button data-next>Revisar cotización</button></div><div data-step hidden><p data-result role=\"status\"></p><button data-back>Corregir partidas</button><button data-download>Exportar JSON</button><button data-restart>Volver al cliente</button></div></section>\n"
    },
    {
      "type": "css",
      "url": "https://componentes-lovat.vercel.app/raw/flow-quote/flow-quote.css",
      "content": ".work-card{width:min(720px,100%);padding:24px;box-sizing:border-box;border:1px solid var(--border);border-radius:12px;background:var(--background);color:var(--foreground);font:14px/1.5 var(--font-sans)}.work-card h2{font-size:24px;margin:0 0 8px}.work-card label{display:grid;gap:5px;margin:12px 0}.work-card input,.work-card textarea,.work-card button{font:inherit;border:1px solid var(--border);border-radius:6px;padding:8px;background:var(--background);color:var(--foreground)}.work-card button{cursor:pointer}.work-card button:disabled{opacity:.45}.work-card :focus-visible{outline:2px solid var(--foreground);outline-offset:2px}.work-card table{width:100%;border-collapse:collapse}.work-card td,.work-card th{padding:8px 4px;text-align:left;border-bottom:1px solid var(--border)}.work-card td input{width:100%;box-sizing:border-box;min-width:65px}.work-card .table-scroll{overflow:auto}.work-card .table-scroll table{min-width:460px}.work-card .actions{display:flex;gap:8px;flex-wrap:wrap;margin:16px 0}.work-card [hidden]{display:none!important}.work-card pre{white-space:pre-wrap;max-height:240px;overflow:auto}.work-card .steps{color:var(--muted-foreground);font-size:12px}.work-card img{max-width:100%;max-height:200px;object-fit:contain}.work-card output{font-weight:600}.work-card .muted{color:var(--muted-foreground)}\n"
    },
    {
      "type": "js",
      "url": "https://componentes-lovat.vercel.app/raw/flow-quote/flow-quote.js",
      "content": "(()=>{document.querySelectorAll('[aria-label=\"Costeo\"]').forEach(root=>{let rows=[{name:'Material',qty:2,price:45},{name:'Transporte',qty:1,price:20}];const body=root.querySelector('[data-rows]'), money=n=>n.toFixed(2);function totals(){let total=0;body.querySelectorAll('tr').forEach((tr,i)=>{const vals=tr.querySelectorAll('input');rows[i]={name:vals[1].value,qty:Number(vals[2].value),price:Number(vals[3].value)};const sub=rows[i].qty*rows[i].price;tr.querySelector('output').textContent=money(sub);total+=sub;});root.querySelector('[data-total]').textContent=money(total);}function render(){body.replaceChildren();rows.forEach(r=>{const tr=document.createElement('tr');tr.innerHTML='<td><input type=\"checkbox\" aria-label=\"Seleccionar partida\"></td><td><input aria-label=\"Concepto\"></td><td><input type=\"number\" min=\"0\" step=\"0.01\" aria-label=\"Cantidad\"></td><td><input type=\"number\" min=\"0\" step=\"0.01\" aria-label=\"Precio\"></td><td><output></output></td>';const inputs=tr.querySelectorAll('input');inputs[1].value=r.name;inputs[2].value=r.qty;inputs[3].value=r.price;body.append(tr);});totals();}body.addEventListener('input',()=>{if([...body.querySelectorAll('input[type=number]')].some(i=>!i.validity.valid)){root.querySelector('[data-status]').textContent='Revisa cantidades y precios: usa valores no negativos.';return;}root.querySelector('[data-status]').textContent='';totals();});root.querySelector('[data-add]').onclick=()=>{rows.push({name:'Nueva partida',qty:1,price:0});render();};root.querySelector('[data-remove]').onclick=()=>{rows=rows.filter((r,i)=>!body.rows[i].querySelector('input').checked);render();};root.querySelector('[data-filter]').oninput=e=>{[...body.rows].forEach((tr,i)=>tr.hidden=!rows[i].name.toLowerCase().includes(e.target.value.toLowerCase()));};root.exportRows=()=>rows.map(r=>({...r}));render();});})();\n(()=>{document.querySelectorAll('[data-flow]').forEach(root=>{const panels=[...root.querySelectorAll(':scope > [data-step]')];let step=0;const show=n=>{step=n;panels.forEach((p,i)=>p.hidden=i!==n);root.querySelector('.steps').textContent=['1 · Preparar','2 · Revisar','3 · Resultado'][n];};root.querySelectorAll('[data-next]').forEach(b=>b.onclick=()=>{const input=panels[step].querySelector('input[required]');if(input&&!input.reportValidity())return;if(root.dataset.flow==='import'&&step===0&&!root.file){root.querySelector('[role=status]').textContent='Elige un archivo de texto o CSV para continuar.';return;}if(root.dataset.flow==='quote'&&step===1&&[...root.querySelectorAll('input[type=number]')].some(i=>!i.reportValidity()))return;show(Math.min(2,step+1));if(step===2){const summary=root.querySelector('[data-result]');summary.textContent=root.dataset.flow==='quote'?'Cotización revisada · USD '+root.querySelector('[data-total]').textContent:root.dataset.flow==='import'?root.file.name+' revisado. Listo para guardar una copia.':'Referencia guardada en esta demostración. Elige una variación.';}});root.querySelectorAll('[data-back]').forEach(b=>b.onclick=()=>show(Math.max(0,step-1)));root.querySelector('[data-restart]').onclick=()=>show(0);const file=root.querySelector('[data-import]');if(file)file.onchange=async e=>{const f=e.target.files[0];if(!f)return;if(f.size>1000000||!(/\\.(txt|md|csv)$/i.test(f.name))){root.querySelector('[role=status]').textContent='Admite TXT, Markdown o CSV de hasta 1 MB.';root.file=null;return;}root.file=f;root.querySelector('[data-review]').textContent=await f.text();root.querySelector('[role=status]').textContent='Archivo preparado.';};const save=root.querySelector('[data-download]');if(save)save.onclick=()=>{const quote=root.querySelector('[aria-label=\"Costeo\"]');const blob=root.dataset.flow==='import'?root.file:new Blob([JSON.stringify({cliente:root.querySelector('[data-client]').value,moneda:'USD',partidas:quote.exportRows()},null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=root.dataset.flow==='import'?root.file.name:'cotizacion.json';a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000);};root.querySelectorAll('[data-variant]').forEach(b=>b.onclick=()=>{root.querySelector('[data-preview]').style.filter=b.dataset.variant;});show(0);});})();\n"
    },
    {
      "type": "standalone",
      "url": "https://componentes-lovat.vercel.app/r/flow-quote.html"
    }
  ],
  "tokensFile": "https://componentes-lovat.vercel.app/tokens.css",
  "darkTheme": "add class 'dark' to <html>",
  "contexto": "herramienta",
  "densidad": "media",
  "tono": "sobria",
  "states": [
    "empty",
    "editing",
    "error",
    "complete"
  ],
  "use": {
    "es": [
      "Cliente → editar partidas y totales → exportar cotización JSON."
    ],
    "en": [
      "Cliente → editar partidas y totales → exportar cotización JSON."
    ]
  },
  "avoid": {
    "es": [
      "Los ejemplos no se conectan a cuentas ni guardan archivos en un servidor."
    ],
    "en": [
      "Examples do not connect accounts or save files to a server."
    ]
  },
  "a11y": {
    "roles": [
      "region"
    ],
    "keyboard": [
      {
        "keys": "Tab / Enter",
        "es": "Recorre y activa los controles",
        "en": "Navigate and activate controls"
      }
    ],
    "notes": {
      "es": [
        "Errores y resultados se anuncian con role=status."
      ],
      "en": [
        "Errors and results use role=status."
      ]
    }
  },
  "issues": null,
  "credits": [],
  "changes": [
    {
      "date": "2026-10-04",
      "es": "Ejemplo funcional local para el taller",
      "en": "Functional local workshop example"
    }
  ],
  "partes": [],
  "pieza": "https://componentes-lovat.vercel.app/r/flow-quote.html"
}
