(function injectScreensCSS() { if (document.getElementById('tecto-screens-css')) return; const css = ` /* App chrome */ .tk-app { height: 100%; display: flex; flex-direction: column; background: var(--bg); color: var(--ink); overflow: hidden; } .tk-body { flex: 1; min-height: 0; display: flex; overflow: hidden; } .tk-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow: hidden; } .tk-toasts { position: fixed; right: 18px; bottom: 18px; z-index: 300; display: flex; flex-direction: column; gap: 10px; } /* Auth screens */ .tk-auth { height: 100%; display: flex; align-items: center; justify-content: center; background: var(--bg); background-image: radial-gradient(circle at 1px 1px, var(--border) 1px, transparent 0); background-size: 22px 22px; } .tk-auth__card { width: 380px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-xl); padding: 32px 32px 26px; } .tk-auth__brand { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 24px; } .tk-auth__mark { width: 46px; height: 46px; } .tk-auth__t { font-family: var(--font-serif); font-size: 24px; font-weight: 600; letter-spacing: -0.02em; color: var(--ink-strong); } .tk-auth__sub { font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted); letter-spacing: 0.04em; margin-top: -4px; } .tk-auth__fields { display: flex; flex-direction: column; gap: 12px; } .tk-auth__foot { margin-top: 14px; text-align: center; font-family: var(--font-sans); font-size: 12px; color: var(--ink-muted); } .tk-auth__link { color: var(--accent); cursor: pointer; text-decoration: underline; } .tk-auth__err { font-family: var(--font-sans); font-size: 12px; color: var(--danger-fg); background: var(--danger-bg); border: 1px solid var(--danger); border-radius: var(--radius-sm); padding: 7px 10px; } /* TopBar avatar menu */ .tk-topbar { display: flex; align-items: center; gap: 10px; height: var(--topbar-h); padding: 0 12px; flex: none; background: var(--surface); border-bottom: 1px solid var(--border); } .tk-crumb { display: flex; align-items: center; gap: 7px; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--ink-muted); } .tk-crumb b { color: var(--ink-strong); font-weight: 600; font-family: var(--font-mono); font-size: var(--text-sm); } .tk-topbar__spacer { flex: 1; } .tk-topbar__grp { display: flex; align-items: center; gap: 8px; } .tk-avatar-btn { width: 28px; height: 28px; border-radius: 50%; background: var(--accent); color: var(--accent-on); display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-sans); font-size: 11px; font-weight: 700; flex: none; border: none; cursor: pointer; position: relative; } .tk-avatar-menu { position: absolute; top: calc(100% + 8px); right: 0; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); min-width: 180px; padding: 6px; z-index: 200; } .tk-avatar-menu__item { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: var(--radius-md); font-family: var(--font-sans); font-size: var(--text-sm); color: var(--ink); cursor: pointer; border: none; background: none; width: 100%; } .tk-avatar-menu__item:hover { background: var(--surface-hover); } .tk-avatar-menu__item--danger { color: var(--danger-fg); } .tk-avatar-menu__sep { height: 1px; background: var(--border); margin: 4px 0; } .tk-avatar-menu__user { padding: 8px 10px 6px; } .tk-avatar-menu__user b { display: block; font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; color: var(--ink-strong); } .tk-avatar-menu__user span { font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted); } /* Dynamic generator */ .tk-dyn { flex: 1; min-height: 0; display: flex; } .tk-dyn__form { width: 432px; flex: none; min-height: 0; overflow: auto; background: var(--surface); border-right: 1px solid var(--border); } .tk-dyn__form-in { padding: 20px 22px 60px; display: flex; flex-direction: column; gap: 14px; } .tk-dyn__head { display: flex; align-items: center; gap: 10px; } .tk-dyn__head h2 { font-family: var(--font-serif); font-size: 20px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink-strong); margin: 0; } .tk-dyn__sec { font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-subtle); margin: 6px 0 -4px; } .tk-dyn__grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .tk-dyn__preview { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--bg-subtle); } .tk-dyn__bar { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 14px; flex: none; background: var(--surface); border-bottom: 1px solid var(--border); } .tk-dyn__bar b { font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted); } .tk-dyn__sp { flex: 1; } .tk-dyn__stage { flex: 1; min-height: 0; overflow: auto; padding: 28px; display: flex; justify-content: center; align-items: flex-start; position: relative; } .tk-dyn__page { width: 480px; background: var(--paper); box-shadow: var(--shadow-lg); border-radius: 2px; min-height: 620px; } .tk-dyn__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 14px; color: var(--ink-subtle); font-family: var(--font-mono); font-size: 13px; } .tk-dyn__overlay { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 13px; align-items: center; justify-content: center; background: color-mix(in srgb, var(--bg-subtle) 78%, transparent); backdrop-filter: blur(1.5px); } .tk-dyn__overlay span { font-family: var(--font-mono); font-size: 13px; color: var(--ink-muted); } .tk-items-grid { display: grid; grid-template-columns: 1fr 60px 90px; gap: 7px; align-items: center; } .tk-items-hdr { font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-subtle); padding: 0 2px; margin-bottom: -2px; } .tk-totals { display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-md); font-family: var(--font-mono); font-size: 12px; } .tk-totals .r { display: flex; justify-content: space-between; } .tk-totals .t { font-weight: 600; font-size: 14px; padding-top: 6px; margin-top: 2px; border-top: 1px solid var(--border-strong); } /* Free editor */ .tk-free { flex: 1; min-height: 0; display: flex; flex-direction: column; } .tk-free__bar { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 14px; flex: none; background: var(--surface); border-bottom: 1px solid var(--border); } .tk-free__body { flex: 1; min-height: 0; display: flex; } .tk-free__editor { flex: 1; min-height: 0; display: flex; flex-direction: column; border-right: 1px solid var(--border); } .tk-free__ta { flex: 1; width: 100%; resize: none; border: none; outline: none; padding: 16px 18px; font-family: var(--font-mono); font-size: 13px; line-height: 1.6; background: var(--code-bg); color: var(--code-text); tab-size: 2; } .tk-free__preview { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--bg-subtle); position: relative; } /* Template editor with inspector */ .tk-tpl-edit { flex: 1; min-height: 0; display: flex; } .tk-tpl-insp { width: 240px; flex: none; min-height: 0; overflow: auto; background: var(--surface); border-right: 1px solid var(--border); padding: 16px; display: flex; flex-direction: column; gap: 14px; } .tk-tpl-insp h3 { font-family: var(--font-serif); font-size: 16px; font-weight: 600; color: var(--ink-strong); margin: 0; } .tk-tpl-insp__sec { font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-subtle); margin-top: 4px; } .tk-var-chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-sm); font-family: var(--font-mono); font-size: 11px; color: var(--code-cmd); cursor: pointer; margin: 2px; } .tk-var-chip:hover { background: var(--accent-subtle-bg); border-color: var(--border-accent); } /* Assets screen */ .tk-assets-real { flex: 1; overflow: auto; background: var(--bg); } .tk-assets-real__in { max-width: 960px; margin: 0 auto; padding: 30px 36px 70px; } .tk-asset-real { display: flex; align-items: center; gap: 12px; padding: 10px 13px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); } .tk-asset-real__thumb { width: 48px; height: 48px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-sunken); display: flex; align-items: center; justify-content: center; flex: none; overflow: hidden; } .tk-asset-real__thumb img { width: 100%; height: 100%; object-fit: cover; } .tk-asset-real__n { font-family: var(--font-mono); font-size: 12px; color: var(--ink); flex: 1; } .tk-asset-real__m { font-family: var(--font-sans); font-size: 11px; color: var(--ink-subtle); } .tk-asset-real__acts { display: flex; align-items: center; gap: 4px; } /* Profile screen */ .tk-profile { flex: 1; overflow: auto; background: var(--bg); } .tk-profile__in { max-width: 560px; margin: 0 auto; padding: 36px 36px 80px; display: flex; flex-direction: column; gap: 22px; } .tk-profile__card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl); padding: 26px; box-shadow: var(--shadow-sm); display: flex; flex-direction: column; gap: 18px; } .tk-profile__avatar { width: 64px; height: 64px; border-radius: 50%; background: var(--accent); color: var(--accent-on); display: flex; align-items: center; justify-content: center; font-family: var(--font-serif); font-size: 26px; font-weight: 700; } .tk-profile__name { font-family: var(--font-serif); font-size: 22px; font-weight: 600; color: var(--ink-strong); } .tk-profile__email { font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted); } .tk-profile__fields { display: flex; flex-direction: column; gap: 12px; } /* Settings screen */ .tk-settings { flex: 1; overflow: auto; background: var(--bg); } .tk-settings__in { max-width: 600px; margin: 0 auto; padding: 32px 36px 80px; display: flex; flex-direction: column; gap: 20px; } .tk-set-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-sm); display: flex; flex-direction: column; gap: 14px; } .tk-set-card h3 { font-family: var(--font-serif); font-size: 16px; font-weight: 600; color: var(--ink-strong); margin: 0 0 2px; } .tk-set-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; } .tk-set-row span { font-family: var(--font-sans); font-size: var(--text-sm); color: var(--ink-muted); } /* Split pane divider */ .tk-split-divider { width: 5px; flex-shrink: 0; cursor: col-resize; background: var(--border); transition: background 150ms; user-select: none; } .tk-split-divider:hover, .tk-split-divider:active { background: var(--accent); } /* Plantillas categories */ .tk-cat-head { font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-subtle); margin: 20px 0 10px; } /* Login */ .tk-login__card { width: 360px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xl); box-shadow: var(--shadow-xl); padding: 30px 30px 26px; } .tk-login__brand { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-bottom: 22px; } .tk-login__mark { width: 46px; height: 46px; } .tk-login__t { font-family: var(--font-serif); font-size: 24px; font-weight: 600; letter-spacing: -0.02em; color: var(--ink-strong); } .tk-login__sub { font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted); letter-spacing: 0.04em; margin-top: -6px; } .tk-login__fields { display: flex; flex-direction: column; gap: 13px; } .tk-login__foot { margin-top: 16px; text-align: center; font-family: var(--font-sans); font-size: 11px; color: var(--ink-subtle); } /* Modal */ .tk-modal-ov { position:fixed;inset:0;background:rgba(0,0,0,.38);z-index:400;display:flex;align-items:center;justify-content:center; } .tk-modal { background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-xl);box-shadow:var(--shadow-xl);padding:26px;width:420px;display:flex;flex-direction:column;gap:14px; } .tk-modal__title { font-family:var(--font-serif);font-size:18px;font-weight:600;color:var(--ink-strong); } .tk-modal__field { display:flex;flex-direction:column;gap:5px; } .tk-modal__label { font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-muted); } .tk-modal__input { height:36px;padding:0 10px;border-radius:var(--radius-md);border:1px solid var(--border-strong);background:var(--surface);color:var(--ink);font-family:var(--font-sans);font-size:13px;outline:none;width:100%; } .tk-modal__input:focus { border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-ring); } .tk-modal__actions { display:flex;justify-content:flex-end;gap:8px;margin-top:4px; } /* Shared workspace layout */ .tk-ws { flex: 1; min-height: 0; overflow: auto; background: var(--bg); } .tk-ws__in { max-width: 960px; margin: 0 auto; padding: 30px 36px 70px; } .tk-ws__head { display: flex; align-items: flex-end; gap: 14px; margin-bottom: 6px; } .tk-ws__title { font-family: var(--font-serif); font-size: 28px; font-weight: 600; letter-spacing: -0.02em; color: var(--ink-strong); margin: 0; } .tk-ws__sub { font-family: var(--font-sans); font-size: var(--text-sm); color: var(--ink-muted); margin: 3px 0 0; } .tk-ws__sp { flex: 1; } .tk-ws__sec { font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-subtle); margin: 28px 0 13px; } `; const el = document.createElement('style'); el.id = 'tecto-screens-css'; el.textContent = css; document.head.appendChild(el); })(); // ── NewDocModal ─────────────────────────────────────────────────────────────── const NEW_CLIENT_SENTINEL = '__new__'; function NewDocModal({ templateName, clients, onClose, onCreate }) { const [docName, setDocName] = React.useState(''); const [clientId, setClientId] = React.useState(clients.length > 0 ? clients[0].id : NEW_CLIENT_SENTINEL); const [newClientName, setNewClientName] = React.useState(''); const [saving, setSaving] = React.useState(false); const { Button } = window.TectoDS; const isNew = clientId === NEW_CLIENT_SENTINEL; const canSubmit = docName.trim() && (!isNew || newClientName.trim()); const handleCreate = async () => { if (!canSubmit) return; setSaving(true); try { await onCreate({ name: docName.trim(), clientId: isNew ? null : clientId, newClientName: isNew ? newClientName.trim() : null, }); } finally { setSaving(false); } }; return (
e.stopPropagation()}>
Nuevo documento — {templateName}
setDocName(e.target.value)} autoFocus onKeyDown={e => { if (e.key === 'Enter' && canSubmit) handleCreate(); if (e.key === 'Escape') onClose(); }} />
{isNew && (
setNewClientName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && canSubmit) handleCreate(); if (e.key === 'Escape') onClose(); }} />
)}
); } // ── ItemsTable ──────────────────────────────────────────────────────────────── const ITEM_KEYS = new Set(['item_1_desc','item_1_hrs','item_1_total','item_2_desc','item_2_hrs','item_2_total', 'item_3_desc','item_3_hrs','item_3_total','subtotal','itbis','total','total_doc']); function ItemsTable({ data, setData }) { const { Input } = window.TectoDS; const set = (n, key, val) => { const updated = { ...data, [`item_${n}_${key}`]: val }; if (key === 'total') { const nums = [1,2,3].map(i => parseFloat((updated[`item_${i}_total`]||'0').replace(/,/g,'')) || 0); const sub = nums.reduce((a,b) => a+b, 0); const itbis = sub * 0.18; const fmt = (x) => x.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); updated.subtotal = fmt(sub); updated.itbis = fmt(itbis); updated.total = fmt(sub + itbis); } setData(updated); }; const moneda = data.moneda || 'USD'; return (
DescripciónHrs/UTotal
{[1,2,3].map(n => (
set(n,'desc',e.target.value)} /> set(n,'hrs',e.target.value)} /> set(n,'total',e.target.value)} />
))}
{[['Subtotal', data.subtotal],['ITBIS 18%', data.itbis],['Total', data.total]].map(([k,v]) => (
{k} {v ? `${moneda} ${v}` : '—'}
))}
); } // ── FieldInput ──────────────────────────────────────────────────────────────── function FieldInput({ field, value, onChange }) { const { Input, Select } = window.TectoDS; if (field.type === 'textarea') return (