WebGo Bookkeeper Complete V5 • Early Access • Licensed
Licensed Private System • Early Access • Business Position & Tax-Ready

WebGo Bookkeeper Complete

Un solo centro de la A hasta la Z. Cada entrada alimenta automáticamente bancos, POS, gastos, payroll, contratistas, activos, crédito, libro, posición diaria y organización Tax-Ready para revisión profesional.

Accesos rápidos del centro
License Verification Required

One product • every department

Complete module accessCompany + branches by licensed capacityMateo + Sofía reviewTax-ready positionPOS + bank matchingAudit history foundation
All operating blocks are included. Capacity is controlled by the assigned BK100, BK101, BK102 or Enterprise license. Tax preparation, signing and filing are not currently included.
1 • Modelo de trabajo

Self-service + Sofía + revisión WebGo

Toca cualquier cuadro para ir directo a su área de trabajo.

1. Cliente carga

Banco, recibos, facturas, payroll, sales tax, cash reports, comentarios o voz rápida.

Base self-service
Abrir Intake / Upload

2. Sofía distribuye

Lee, detecta vendor/fecha/monto/categoría, busca match y envía al módulo correcto.

Revisión antes de postear
Abrir Sofía Dispatcher

3. WebGo revisa

Analiza, alerta, pregunta al cliente, prepara reportes y cierre según paquete.

Capa de valor WebGo
Abrir Health / Alerts

4. Assisted add-on

Manual entry by WebGo staff is separate unless the plan/add-on includes it.

Setup • monthly • cleanup
Abrir Add-on / Plans
Regla comercial: el paquete normal es self-service. WebGo revisa y analiza; si WebGo tiene que entrar información manualmente, se cobra como setup, cleanup, add-on mensual o plan alto.
1B • Company / Branch Selector

Company Main + Branch Clouds

Company Main is the legal/tax house. Branch Clouds collect local branch activity only: local inflows, outflows, POS/registers, bank/deposit sources, evidence and corrections. At month-end each Branch Cloud sends clean totals back to the Main Ledger for bookkeeping and the consolidated tax-ready package.

Company Main

Simple rule: choose Main for company totals, or choose a Branch Cloud for local entries/exits. This selector does not create another company or another tax file.
Main receives monthly totals from every branch cloud.

Reusable Branch Cloud Template

Inside each branch cloudAddress/location, manager, POS/registers, bank/deposit source, local inflows, local outflows, Daily Shift Balance, evidence and corrections.
Back to MainAt month-end, each branch sends clean totals to the Company Main Ledger. Taxes use consolidated company totals unless the branch is a separate legal entity.
Flow: Branch activity → Branch Daily Shift Balance → Monthly Branch Summary → Send Totals to Company Main → Bookkeeper Ledger → CPA / Tax-ready package.
Other / Custom: if a branch, department, bank source, register source, or report type is missing, add a note inside the selected Branch Cloud and WebGo can attach it step by step without changing the Main.

Current Branch View

One Company MainReusable Branch CloudBranch totals → Main LedgerDetail stays inside cloud
Important: do not duplicate the whole index for every branch. Company Main keeps People Cloud, payroll, contractors, archive, ledger and tax-ready totals. Branch Clouds keep the local details, then only summary totals move to Main.
2 • WebGo Scan & Upload Center

Un solo botón: Mando a Sofía

Esta sección debe ser simple para el cliente: toma foto, sube archivo o arrastra documentos, agrega un comentario si hace falta y presiona Mando a Sofía. No necesita escoger destino; Sofía detecta y envía a revisión.

1. Regla simple

El cliente solo alimenta el sistema. La clasificación ocurre detrás del botón.

No destination neededReview before post

2. Foto / archivo

Drop files here

Receipts, statements, PDFs, CSV/Excel, cash proof, invoices.

3. Datos rápidos

4. Mando final

Selected document preview appears here.

Demo carga 3 archivos de ejemplo. Limpiar borra la cola.
Ready. Add photo/file and press Mando a Sofía.

Scan Queue → Sofía Auto-Dispatcher

Cada archivo queda en cola. Al presionar Mando a Sofía, el prototipo crea registros de intake y los manda al dispatcher/review.

3 • Mobile QR Receipt Capture

QR al teléfono: foto, mando y Sofía distribuye

Generate private QR

Desde la computadora se genera un QR privado para ese cliente/compañía. El teléfono abre una captura móvil sencilla: tomar foto y presionar Mando a Sofía.

No QR generated yet.

QR Preview

Generate QR to create a private mobile capture session.

Client tokenCompany lockedReview inboxNot final posting

Phone Capture

Esto representa lo que ve el cliente en el teléfono después de escanear el QR.

Mobile capture ready.

Mobile Upload Queue

4 • POS/App Daily Collector

Register / sales app → Daily Shift Balance → Sofía distribution

1. Optional Register Network

Register setup is optional. WebGo does not force the client to replace the register. The client adds the register ID, serial number, or internal inventory number if available; WebGo creates the network line and organizes activity by terminal, branch, day, week, and month.

Select a POS/register system to see the connection path.
Plan 2: 1 registerPlan 3: 2 registersPlan 4: up to 5Plan 5: up to 10Plan 6: multi-store/custom
Select POS
Create Line
Daily pull
Raw Folder
Sofía
Bookkeeper/Tax
No register line yet.Optional setup: create a line only when the client wants register-level control.
Ready. Optional register setup can be created now, or the client can start with daily uploads/manual close.

2. Daily Raw Excel Folder

This folder preserves what the machine/app collected, even if it is raw. It is the original daily record: sales, tax, cash, card, refunds, fees, products and terminal activity.

No daily folder created yet.

When created: /bookkeeper/client/store/date/POS_RAW_DAILY.xlsx

Gross Sales$0.00
Cash$0.00
Card$0.00
Tax$0.00

3. Sofía Legal Distribution

Sofía takes the raw daily folder and distributes it so the system keeps legal support and dollar-by-dollar detail.

Waiting for daily folder.

Once sent, Sofía routes to Retail, Sales Tax, Cash Control, Bank Match, Inventory/COGS and alerts.

4. Daily Shift Balance / Folder Cloud

The screen shows summaries. The cloud folder keeps unlimited detail. Each daily, weekly or monthly folder opens its own internal vertical scroll without making the page longer.

No POS rows yet.
No folders yet.

Load POS demo or upload a POS report to create Daily Shift Balance folders.

Fixed rule: Raw activity → Daily Shift Balance → Sofía Distribution → Bookkeeper Ledger → CPA/Tax-ready records. Details stay inside the folder with internal vertical scroll.
5 • Client Upload & Intake Room

Intake doors: each document goes to the correct review area

Intake Doors

Bank

CSV, Excel, PDF statement or future bank connection.

Receipts

Photos, PDFs, receipts and cash proof.

Reports

Payroll, POS, merchant, sales tax and AR/AP reports.

Voice

Quick entry for cash, fuel, tolls and small purchases.

Nothing posts directly to the final ledger. Every item goes through staging, review or approval first.

Upload / Document Intake

Intake Queue

Select an intake card to view its suggested route, review status and posting action.
6 • Sofía Auto-Dispatcher / Staging Review

Read, classify, route, and match Daily Activity

Professional rule

Sofía can organize automatically, but the book is protected: every item goes first to Staging / Review. WebGo or the client approves before final posting.

High confidenceMedium reviewNeed client answer
Daily match rule: every dispatched item must connect to a daily folder, daily balance area, ledger impact, and review status.

Dispatcher Results

SourceDetectedDestinationAmountConfidenceStatusAction

Daily Activity Breakdown / Balance Match

This is the control bridge: Intake → Sofía staging → Daily Shift Balance → Bookkeeper Ledger → Tax-ready records. The dispatcher does not replace the daily balance; it proves where each item goes before posting.

Daily FolderSource ItemDaily Balance AreaLedger ImpactMatch / Evidence CheckReview StatusOpen
Fixed rule: nothing moves to the final ledger until it matches the daily activity breakdown or is marked as a reviewed exception.
7 • Bank Import Center

Automatic Bank Import: statement copy, breakdown and accounting distribution

Monthly Bank Statement Vault

The bank statement is the official source copy. WebGo keeps the monthly statement copy, then creates a clean accounting distribution layer from it.

Original copy preservedMonthly folderNot manually erased
No statement copy saved yet. Upload or load demo, then save the monthly copy.

Bank / Card Import Staging

Paste CSV lines or import a bank/card file. Sofía creates staging rows first; final posting happens only after review, matching and approval.

DateDescriptionAmountDetected EntityCategoryMatchStatus

Statement Archive

PeriodAccountCopyStatus

Statement Category Breakdown

CategoryInOutRoute

Accounting Distribution

Rule: original bank statement stays in the monthly vault. Accounting rows are the distribution layer for Bookkeeper Ledger and Tax-ready records.
8 • Records Archive Cloud

Year / Month / Daily folders for long-term proof

Archive rule

The main screen only shows summaries. Details are stored inside year, month and daily cloud folders. Open the folder only when you need proof, review, correction notes or a tax-ready package.

Fixed rule: Main screen = summary. Folder cloud = detail. Archive search = find any year, month, day, bank statement, POS folder, payroll record, receipt, correction or evidence.
7-year defaultMonthly foldersOriginal copies preserved

Archive Search

Folders found0
Inflows$0.00
Outflows$0.00
Net archive impact$0.00

Folder Cloud

No archive folders yet.

Load demo or create folders from bank, POS, payroll and tax-ready records.

Retention policy

WebGo default: keep business archive folders for 7 years unless the client chooses a longer policy. Tax packages, property/asset records, loan payoff support, employee/contractor history and unresolved corrections may be kept longer when needed.
RecordDefaultOpen
Monthly bank statements7 years
Daily Shift Balance folders7 years
Payroll / employment tax support7 years default
Time cards / schedules7 years default
Tax-ready yearly packageLong-term archive
Open corrections / disputesUntil resolved + archive

Click any retention record to open the related archive cloud without stretching the page.

Archive Ledger / Search Results

FolderPeriodTypeInOutStatusContentsAction
8 • Accounts, Credit Cards & Loans Control Center

La casa central: banco + tarjetas + préstamos + cuentas pendientes

Account Hub

Total Sources0
Credit Card Balances$0.00
Loan Balances$0.00
Next Due Alerts0
AccountTypePurposeOriginalBalancePaymentDueStatus

Loan Detail & Payment Schedule

Pasado: se trae por banco/statements. Futuro: se guarda como expected, no como gasto real hasta que el banco confirme el pago. Principal baja deuda; interés/fees sí van a gasto financiero.

Loan / LenderPayment DateTotal PaymentPrincipalInterestFeesNew BalanceRecord TypeStatus
9 • Government-Ready Record Layers

Two layers of the same record: Daily Operations + CPA/Tax-Ready

One source record feeds two views. Layer A shows daily business operations; Layer B organizes the same approved records for accountant/tax-ready review. This is not a second book with different numbers.

Layer A — Operational Daily Ledger

Daily view of how money came in and went out: bank, POS, cash, cards, loans, receipts, voice entries and QR uploads. This view answers business control questions.

Daily Shift BalanceLive LedgerCash flowRisk alerts

Layer B — CPA / Tax Ledger

Accountant/tax-ready view using the same records, organized by income, deductions, COGS, sales tax, loan interest/fees, payroll support and reconciliation.

Same recordIncomeDeductionsCOGSInterest / feesSales tax

Evidence Vault + Audit Trail

Every line must be able to open its proof: receipt, bank line, POS report, cash sheet, invoice, statement, comment, approval, reviewer and change date.

Proof attachedReview stateWho / whenMissing support

Record Flow / Control Rule

1. Source recordBank / POS / Cash / Payroll / Receipt

Original source stays preserved in its monthly/daily folder.

2. Daily operationsDaily Shift Balance

Daily inflow/outflow summary for business control.

3. CPA/tax viewTax-ready classification

Same approved records sorted for accounting and evidence.

4. CorrectionsNever erase original

Adjustments are linked to the original record with notes and proof.

Fixed rule: Same source record, two views. The operational daily ledger and CPA/tax ledger must always reconcile back to the same folder, same proof and same audit trail.

Government-Ready Checklist

Gross receiptsPOS, invoices, deposits, 1099s, cash register tapes
Purchases / expensesPayee, amount, date, proof of payment, business purpose
Bank / card proofStatements, EFT, canceled checks, credit card statements
ReconciliationBank ↔ card ↔ POS ↔ cash ↔ receipts
LoansPayment split: principal, interest, fees, balance
RetentionArchive folders preserve proof and corrections

CPA / Tax Ledger View

This is the tax-ready view of the same approved activity. It does not replace the daily ledger.

DateEntityTax CategoryAmountEvidenceReconciledStatusAction

Evidence / Audit Trail

Proof, reviewer and action history connected to each record.

RecordSupportSourceReviewerActionStatus
10 • Tax Reserve & Payment Reminder Center

Balance diario de taxes: alerta, reserva y prueba de pago

Lo que hace WebGo

WebGo no mueve dinero ni paga al gobierno por el cliente. El sistema calcula una posición estimada diaria, muestra cuánto debe reservarse, manda alarmas y guarda evidencia cuando el cliente confirma el pago.

EstimatedClient approvedProof uploadNo direct payment by WebGo
Ready. This center is for estimated position and reminders only.

Daily Government Payment Watch

Estimated Tax Position$0.00
Suggested Reserve Shortage$0.00
Payroll Tax Watch$0.00
Next Reminder—
Client responsibility: El cliente hace el pago oficial y sube la confirmación. WebGo mantiene el recordatorio, el cálculo estimado y la evidencia.

Reserve Ledger

AreaBasisEstimated DueReservedShort / OverNext DueStatus

Payment Proof / Reminder Log

DateTypePeriodAmountProofConfirmationStatus
10B • Tax Deduction & Asset Review Center

Standard expenses first. Other only when needed.

This center organizes potential tax-ready support. It does not give final tax advice or file taxes. Every category must connect back to source records, evidence, review notes and the Records Archive.

Standard Business Expenses

Use normal categories first so Sofía can route bank lines, receipts, POS activity and payments into a clean CPA/tax-ready view.

Rule: standard categories first, evidence always, review before final tax-ready posting.

Other / Custom Expense

If a transaction does not fit a standard category, the client can add it here with a business purpose and proof.

Ready. Custom items must explain purpose and evidence.

Assets / Vehicle / Rent Review

Large purchases and special items must not be forced into regular expenses. They should open their own review folder.

Assets / EquipmentVehicle, equipment, POS machine, tools

Asset cloud, purchase proof, financing and review status.

Vehicle / MileageMileage, fuel, repairs, insurance

Business use and support folder.

Rent / LeaseLocation, equipment lease, warehouse

Lease folder, proof of payment and period support.

Deduction Staging / Review Queue

DateSourceCategoryAmountEvidenceReview noteRouteStatusAction
Tax-ready rule: WebGo organizes support. Final treatment depends on the client, entity type, evidence, tax year and authorized tax professional/self-filing review.
11 • People Cloud Command Center V19

Índice compacto: 4 personas por cuadro + carrusel + búsqueda

Juan, esta versión evita llenar la pantalla con tarjetas grandes. La clasificación se escribe una sola vez por grupo: W‑2 Employees y 1099 Contractors. Cada cuadro muestra hasta cuatro personas; al tocar un nombre se abre la nube completa con hoja de vida, payroll/time card, W‑2 o W‑9/1099, evidencia, print y export.

People Cloud Summary

Total People0Employees + contractors
W‑2 Employees0Payroll / W‑2 watch
1099 Contractors0W‑9 / 1099‑NEC watch
Year-End Ready0Review before delivery
Regla V19: el frente solo enseña nombres compactos. El detalle vive dentro de la nube individual de cada persona.

Search / Filtrar

Entrada compacta4 personas por cuadro
Búsqueda rápidaPepito sale al frente
Grupo únicoW‑2 / 1099 escrito una vez
Nube individualTodo el detalle adentro

Compact People Carousel

Internal Folder Map

PersonClassificationMain cloud filePayroll / payment folderYear-end folderDelivery / action
No people loaded yet.
El W‑9 es intake interno del contractor; el 1099‑NEC draft se va actualizando con cada pago. El W‑2 se prepara desde payroll/time card/withholding staging. Nada se presenta oficialmente desde esta pantalla sin revisión.
11 • Payroll Employee Cloud Files + QR TimeCard App + Benefits Premium Module

Payroll + 401(k): summary on Main, full detail inside controlled clouds

Main Rule

The main dashboard shows only totals, health, deadlines and tax-ready status. Detailed payroll records, deposits, corrections, provider reports and evidence stay inside Payroll Cloud and 401(k) Cloud.

Premium / add-onMain = summaryPayroll Cloud401(k) Cloud
Ready. Payroll is monitoring/control, not payroll processing or official filing.

Main Payroll Summary

Payroll HealthSetup
Current Pay Period—
Gross Payroll$0.00
Net Pay$0.00
Employee 401(k)$0.00
Employer Match$0.00
Employer Tax Cost$0.00
Proof Alerts0
Default mode: Automatic with ReviewEvery item can be corrected without erasing the originalPayroll taxes and 401(k) deposits require proof
Hourly payroll comes from approved time cards. Salary uses the pay period setup. Contractors stay in 1099 tracking, not W‑2 payroll withholding.

Add Person / Payroll Profile

Producción real: las retenciones federales se calculan con la información W‑4 y los métodos oficiales de withholding. Esta plantilla muestra control, clasificación, alertas y staging.

Employee / Contractor Profile Vault + Mobile Onboarding

Cada persona tiene su hoja de vida interna. El trabajador puede llenar la información desde el celular con un QR privado; WebGo/manager revisa antes de activar payroll o 1099 readiness. Datos sensibles como SSN/TIN se muestran enmascarados y en producción deben guardarse cifrados en servidor seguro.

No onboarding QR generated yet.
W‑2 employees use W‑4/I‑9/payroll records; 1099 contractors use W‑9 and 1099‑NEC readiness. The profile vault supports the year-end folder but does not file forms by itself.

Employee Cloud File Index

Para simplificar cuando haya 3 o 1,000 personas: cada empleado o contractor aparece como una ficha. Al tocar el nombre se abre su nube individual con hoja de vida, documentos, time card, payroll, 401(k), beneficios, W‑2/1099 readiness, print y export.

En producción, datos sensibles como SSN completo, bank account y documentos deben guardarse cifrados en servidor seguro. La pantalla solo muestra datos enmascarados y estados de revisión.

Contractor / W‑9 / TIN Vault

Para subcontratistas 1099: el negocio normalmente paga el total acordado; el sistema guarda W‑9 status, TIN/SSN último 4, dirección, pagos acumulados, prueba de pago y alerta de 1099‑NEC. No se mezcla con payroll W‑2 ni con retenciones de empleado.

Producción: guardar W‑9 PDF y TIN/SSN completo solo en servidor cifrado/seguro. En esta plantilla se muestra únicamente el último 4 y el estado de revisión.
Contractors0
Over 1099 Watch0Threshold/rules vary by payment/form; review before filing.
Missing W‑90
Ready / Review0
ContractorBusiness / Tax classW‑9 / TINYTD paidPayment source1099‑NEC watchBackup withholdingAction

1099‑NEC Draft Builder + Recipient Delivery Center

La idea: no esperar a enero. Cada pago al subcontratista actualiza su récord diario; al final del año el sistema ya tiene el draft del 1099‑NEC, dirección, email, W‑9/TIN status y prueba de entrega. WebGo prepara el paquete; el cliente/payer sigue siendo responsable de revisar, aprobar y filing/distribución oficial.

Regla correcta: el W‑9 lo llena/certifica el contractor y se guarda en el expediente; el 1099‑NEC se construye desde los pagos acumulados. En producción, TIN completo y PDFs deben estar cifrados en servidor. Esta vista usa TIN enmascarado y staging.
Draft forms0
Total NEC tracked$0.00
Need W‑9/TIN0
Delivery ready0
ContractorTax profileEmail / mailingTINNEC amountBackup withholdingIRS / IRIS statusRecipient deliveryAction
DateContractorAmountMethodWork / projectProofNote

Profile Carousel / HR File Vault

Time Card / Hours Collection Center

La regla: no se calcula payroll hourly final sin time card u horas aprobadas. Salary puede pasar por periodo salarial; 1099 se controla como contractor/invoice tracking y normalmente usa W‑9 + 1099‑NEC readiness, no W‑4.

Employee QR TimeCard App

La idea es simple: el empleado escanea su QR privado, ve su turno, poncha entrada/salida desde el teléfono y el manager aprueba antes de pasar horas al payroll. WebGo guarda el récord para cálculo y revisión.

No TimeCard QR generated yet.

Generate QR to open the employee time clock app.

Employee tokenSchedule visibleManager approvalPayroll staging only

Employee TimeClock

Pantalla que ve el empleado después de escanear el QR.

EmployeeMaría GómezShift09:00 → 17:30BranchStore 1 / Front CounterStatusReady to punch in
Phone time clock ready.
EmployeeActionDate / TimeBranch / JobTokenStatus
Producción real: el QR abre una app/PWA móvil con login/token. El empleado poncha, el manager aprueba, y solo las horas aprobadas pasan al Payroll Employee Carousel.

Time Card Carousel / Approval Queue

PersonPeriodRegularOTPay HoursSourceApprovalNote

Employee / Contractor Carousel

Payroll Runs

Run DateSourceGrossNet PayEmployee TaxesEmployer TaxesStatus

401(k) / Benefits Ledger

EmployeeEmployee 401(k)Employer MatchProviderProofStatus

W‑2 / 1099 / Payroll Forms Readiness

Form / AreaWhoBasisDeadline / ReminderEvidence NeededStatus
WebGo prepara el folder y los totales. El cliente o su payroll provider/authorized filer emite y presenta las formas oficiales.
12 • QuickBooks Bridge / Future Connection

Conectar QuickBooks sin romper el flujo WebGo

Connection Status

Not connected. V4 shows the architecture and staging flow. Real connection requires secure backend OAuth, token storage and Intuit app setup.

Correct QuickBooks Flow

OAuth consent

Client authorizes WebGo app to access their QuickBooks company.

Read / Pull

Customers, vendors, accounts, invoices, bills, purchases, payments.

Stage / Review

Sofía compares QuickBooks with bank, receipts and cash records.

Push only approved

WebGo should not overwrite books blindly. Approved adjustments only.

Backend endpoints needed

For real production we need server-side routes, not only HTML:

/api/qb/auth/api/qb/callback/api/qb/sync/api/qb/webhooksecure tokensrealmId
QuickBooks is a bridge. WebGo remains the operating dashboard: scan, bank, cash, review, alerts and breakdowns.

QuickBooks Staging Table

QB EntityDateName / VendorAmountCategory / AccountWebGo DestinationStatus
14 • Cash Control Center

Control de efectivo: lo que no aparece directo en el banco

Cash Entry

Daily Cash Sheet

DateTypePersonVendor/ClientAmountProofDeposit MatchStatus
15 • Sofía Quick Voice Entry

Entrada rápida por voz o texto cuando el scanner no es lo más rápido

Dictado / Texto

Ejemplo: “Me gasté 152 dólares con 23 centavos en Home Depot, materiales, tarjeta.” O: “20 dólares con 5 centavos en gasolina, efectivo.”

Ready.

Voice Pending Support

16 • Plan Access Rules

Un sistema completo, módulos abiertos según paquete

17 • Balance Chip + Health Alerts

Daily Financial Control Center

The bars show operational health. The Balance Chip shows real daily totals: how money came in, how money went out, what is reserved, what is pending, and when the numbers are ready to move into the tax-ready layer.

Health Bars

Sofía Daily Review

Sofía ready to analyze.

Company Total Breakdown

Balance Chip • Money In / Money Out

This is the owner view: total money in, total money out, protected reserves, pending liabilities and net operational position. It is not the final tax return; it is the daily operational truth that feeds Bookkeeper and Tax-ready records.

Payroll + Government Tax Breakdown

Period rule: this panel shows the current payroll working period / current month watch. Year-end or full-year taxes must use the YTD / Tax-ready Bridge after payroll history is imported or reviewed.

Sofía 30 / 60 / 90 Day Forecast

Forecast is an operational estimate based on current records. Final decisions remain with the owner, CPA or authorized reviewer.

Tax-ready Bridge • Income → Expenses → Obligations → Net

Tax view: first show everything earned, then operating expenses, payroll, tax/reserve obligations and the estimated net position. This is review support for CPA / authorized filer, not the final tax return.

18 • Reports + Tax Ready Folder

Reportes, preguntas al cliente y carpeta de impuestos

Monthly Client Report

Ingresos, gastos, cash flow, documentos faltantes, riesgos, acciones y próximos pasos.

Open report →
Questions for Client

Preguntas creadas por Sofía para aclarar gastos, cash, vendors, receipts y categorías.

Open inbox →
Year-End Tax Ready Folder

P&L, balance, receipts, payroll, 1099, sales tax, loans, owner notes y soporte.

Open folder →

Details

Return to module

Person Cloud File: ${name}

${c}`);w.document.close();w.print()} function exportPersonYearEndPacket(name){let p=findProfile(name)||{}, type=personCloudType(name);let rows=[['Field','Value'],['Name',name],['Classification',type],['Contact',p.contact||''],['Address',p.address||''],['SSN/TIN Last 4',p.last4?('***-'+p.last4):''],['YTD / Tracked',personYtdAmount(name)],['Year-End Status',personCloudReadyStatus(name)],['Forms Required',String(type).includes('Contractor')?'W-9 + 1099-NEC':'W-4 + I-9 + W-2'],['Delivery','Email / print / mail after review']];let csv=rows.map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`webgo_person_cloud_${name.replace(/[^a-z0-9]+/gi,'_')}.csv`;a.click()} // ===== V19 People Cloud Patch: employer/employee ID + edit inside cloud ===== function safeV19(v){return String(v===undefined||v===null?'':v).replace(/[&<>]/g,function(c){return {'&':'&','<':'<','>':'>'}[c]})} function profileCompletion(p){let fields=['empNo','name','cls','contact','last4','address','hire','branch','payBasis','deposit','forms','i9','benefits'];let have=fields.filter(k=>p[k]&&String(p[k]).trim()&&!String(p[k]).includes('Need')).length;return Math.round(have/fields.length*100)} function addEmployeeProfile(){ let p={ empNo:(el('profEmpNo')&&el('profEmpNo').value.trim())||'', name:(el('profName').value||'').trim()||'New Worker', cls:el('profClass').value, contact:el('profContact').value||'Need contact', last4:el('profLast4').value||'', address:el('profAddress').value||'Need address', hire:el('profHire').value||today(), branch:el('profBranch').value||'Main Branch', payBasis:el('profPayBasis').value, deposit:el('profDeposit').value, forms:el('profForms').value, i9:el('profI9').value, benefits:el('profBenefits').value, status:el('profStatus').value, source:'Manual / staff review' }; if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9';} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(p.name).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p); else employeeProfiles.unshift(p); let payExists=employeeRows.find(e=>String(e.name).toLowerCase()===String(p.name).toLowerCase()); if(!payExists){employeeRows.unshift({name:p.name,type:p.cls,payType:p.payBasis==='Salary'?'Salary per payroll':'Hourly',rate:0,hours:0,docStatus:p.forms,emp401Pct:0,erMatchPct:0,benefits:0,status:'Profile created / needs pay setup'});} createYearEndForms(false); renderPayrollBenefits(); activeTab='Payroll'; renderCarousel(); if(el('payrollStatus'))el('payrollStatus').textContent='Profile updated with employer/employee ID. Edits can also be made inside the cloud file.'; } function generateEmployeeOnboardingQR(){ let name=(el('profName')&&el('profName').value)||'Worker'; let emp=(el('profEmpNo')&&el('profEmpNo').value)||''; let token='EP-'+Math.random().toString(36).slice(2,8).toUpperCase()+'-'+Date.now().toString().slice(-5); let url=`./employee_profile_app/index.html?token=${encodeURIComponent(token)}&name=${encodeURIComponent(name)}&empNo=${encodeURIComponent(emp)}&company=WebGo%20Client`; onboardingRows.unshift({date:today(),name,token,url,status:'Sent / waiting for worker'}); if(el('profileQrStatus'))el('profileQrStatus').innerHTML=`
${token} • ${name}${emp?' • ID '+emp:''}
${url}`; return token; } function loadEmployeeProfileDemo(){ employeeProfiles=[ {empNo:'EMP-1001',name:'María Gómez',cls:'Employee W-2',contact:'[email protected] / (813) 555-0101',last4:'1234',address:'Tampa, FL',hire:'2026-05-01',branch:'Store 1 / Front Counter',payBasis:'Hourly',deposit:'On file',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) + benefits elected',status:'Ready for payroll',source:'Mobile onboarding + manager review'}, {empNo:'EMP-1002',name:'Pedro Ruiz',cls:'Employee W-2',contact:'[email protected] / (813) 555-0102',last4:'7788',address:'Wesley Chapel, FL',hire:'2026-04-15',branch:'Store 1 / Manager',payBasis:'Salary',deposit:'On file',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) elected',status:'Ready for payroll',source:'Manager setup'}, {empNo:'CON-2001',name:'Ana Contractor',cls:'Independent Contractor 1099-NEC',contact:'[email protected] / (813) 555-0199',last4:'9911',address:'Lutz, FL',hire:'2026-05-10',branch:'Job / Project Support',payBasis:'Contractor invoice',deposit:'Not applicable',forms:'W-9 on file',i9:'Not applicable / contractor',benefits:'Not eligible / not elected',status:'Manager review',source:'Mobile W-9 intake'} ]; onboardingRows=[{date:today(),name:'New worker example',token:'EP-DEMO-10001',url:'./employee_profile_app/index.html?token=EP-DEMO-10001',status:'Waiting for worker'}]; renderPayrollBenefits(); if(el('payrollStatus'))el('payrollStatus').textContent='Employee profile demo loaded with employer/employee ID numbers.'; } function ensureProfileForContractorV19(r){ let i=employeeProfiles.findIndex(p=>String(p.name).toLowerCase()===String(r.name).toLowerCase()); let row={empNo:r.empNo||('CON-'+String(Math.abs(hashCodeV19(r.name))%9000+1000)),name:r.name,cls:'Independent Contractor 1099-NEC',contact:r.contact,last4:r.tin4,address:r.address,hire:'2026-01-01',branch:'Contractor / vendor file',payBasis:'Contractor invoice',deposit:'Not applicable / vendor payment',forms:r.w9Status,i9:'Not applicable / contractor',benefits:'Not eligible / contractor',status:String(r.w9Status).includes('on file')?'Ready for 1099 review':'Need W-9',source:'Contractor W-9/TIN Vault'}; if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],row); else employeeProfiles.push(row); } function hashCodeV19(s){let h=0;String(s).split('').forEach(ch=>{h=((h<<5)-h)+ch.charCodeAt(0);h|=0});return h} function loadContractorW9Demo(){ contractorW9Rows=[ {empNo:'CON-2001',name:'Ana Contractor',business:'Ana Renovation Services LLC',taxClass:'Single-member LLC',w9Status:'W-9 on file',tin4:'8821',address:'Tampa, FL',contact:'[email protected]',paid:9800,source:'Bank transfer',backup:'No backup withholding noted',agreement:'Agreement + COI on file',note:'Subcontract flooring / repairs',status:'Ready / Review'}, {empNo:'CON-2002',name:'Carlos Delivery',business:'',taxClass:'Individual / Sole proprietor',w9Status:'Need W-9',tin4:'',address:'Need address',contact:'555-0188',paid:740,source:'Zelle',backup:'Need correct TIN before more payments',agreement:'Need agreement / insurance',note:'Delivery helper. Need W-9 before year-end.',status:'Need W-9'}, {empNo:'CON-2003',name:'Bright Signs Studio',business:'Bright Signs Studio Inc.',taxClass:'S Corporation',w9Status:'W-9 requested',tin4:'4460',address:'Orlando, FL',contact:'[email protected]',paid:420,source:'Check',backup:'Verify with tax professional',agreement:'Agreement on file',note:'Signage/vendor; verify entity/form requirement.',status:'Review'} ]; contractorW9Rows.forEach(r=>{ if(!employeeRows.some(e=>String(e.name).toLowerCase()===String(r.name).toLowerCase())) employeeRows.push({name:r.name,type:'Independent Contractor 1099-NEC',payType:'Hourly',rate:0,hours:0,docStatus:r.w9Status,emp401Pct:0,erMatchPct:0,benefits:0,status:'1099 contractor tracking'}); ensureProfileForContractorV19(r); }); buildContractor1099Readiness(false); renderPayrollBenefits(); if(el('payrollStatus'))el('payrollStatus').textContent='Contractor W-9/TIN demo loaded with contractor ID numbers.'; } function renderProfileCarouselHTML(){ if(!employeeProfiles.length)return '
No profiles yet.

Create a profile or generate a mobile onboarding QR so the worker can fill their information from the phone.

'; return employeeProfiles.map(p=>{let pct=profileCompletion(p);let contractor=String(p.cls).includes('Contractor');return `

${safeV19(p.name)}

${safeV19(p.empNo||'No ID')}${safeV19(p.cls)}${safeV19(p.payBasis)}${safeV19(requiredFormsForProfile(p))}
Employer / Employee ID${safeV19(p.empNo||'Need ID')}Contact${safeV19(p.contact)}SSN/TIN${maskLast4(p.last4)}Address${safeV19(p.address)}Start${safeV19(p.hire)}Branch / Job${safeV19(p.branch)}Direct deposit${safeV19(p.deposit)}Forms${safeV19(p.forms)}I-9${safeV19(p.i9)}Benefits${safeV19(p.benefits)}Status${safeV19(p.status)}

Completion: ${pct}% • Source: ${safeV19(p.source||'Profile vault')}

`}).join(''); } function peopleCloudNamesFiltered(){ let names=allPersonNames(); let q=(el('peopleCloudSearch')&&el('peopleCloudSearch').value||'').toLowerCase().trim(); let f=(el('peopleCloudFilter')&&el('peopleCloudFilter').value)||'All people'; let keep=(n)=>{let type=personCloudType(n), p=findProfile(n)||{}, status=personCloudReadyStatus(n);let text=[n,p.empNo,p.contact,p.address,p.branch,type,status].join(' ').toLowerCase();if(q&&!text.includes(q))return false;if(f==='W-2 employees'&&!String(type).includes('Employee'))return false;if(f==='1099 contractors'&&!String(type).includes('Contractor'))return false;if(f==='Needs year-end review'&&!String(status).includes('Need'))return false;if(f==='Ready / Review'&&(String(status).includes('Need')||String(status).includes('missing')))return false;return true}; return names.filter(keep); } function peopleMiniCard(name){ let type=personCloudType(name), p=findProfile(name)||{}, status=personCloudReadyStatus(name), ytd=personYtdAmount(name), pct=personCloudCompletion(name), contractor=String(type).includes('Contractor'); let q=(el('peopleCloudSearch')&&el('peopleCloudSearch').value||'').toLowerCase().trim(); let hit=q && ([name,p.empNo,p.contact].join(' ').toLowerCase().includes(q)); return `
${safeV19(name)}ID: ${safeV19(p.empNo||'Need ID')}${safeV19(p.contact||'Need contact')}YTD: ${money(ytd)} • Ready ${pct}%
${safeV19(status)}CloudEdit
`; } function personFormsFolderHTML(name){let type=personCloudType(name), p=findProfile(name)||{}, isC=String(type).includes('Contractor'), cw=findContractorW9(name)||{}, d=form1099DraftRows.find(r=>String(r.name).toLowerCase()===String(name).toLowerCase())||{};if(isC)return `

Tax Forms Folder: W‑9 → 1099‑NEC

Employer / Contractor ID${safeV19(p.empNo||cw.empNo||'Need ID')}W‑9 intake${safeV19(cw.w9Status||p.forms||'Need W‑9')}TIN/SSN${cw.tin4?'***-'+cw.tin4:maskLast4(p.last4)}1099‑NEC amount${money(d.amount||paymentTotalForContractor(name))}Email / address${safeV19((cw.contact||p.contact||'Need email'))}
${safeV19(cw.address||p.address||'Need address')}
Delivery${safeV19(d.delivery||'Need review')}
W‑9 stays in the secure requester file; 1099‑NEC draft is built from approved contractor payments.
`;return `

Tax Forms Folder: W‑2 Employee

Employer / Employee ID${safeV19(p.empNo||'Need ID')}W‑4${safeV19(p.forms||'Need W‑4')}I‑9${safeV19(p.i9||'Need I‑9')}W‑2 readiness${safeV19(yearEndStatusFor(name))}Employee copy delivery${safeV19(p.contact||'Need email/address')}Payroll basis${safeV19((findPayrollPerson(name)||{}).payType||p.payBasis||'Need setup')}
W‑2 readiness is built from payroll/time card/withholding staging and must be reviewed before delivery or filing.
`} function openPersonCloudMaster(name){ if(!form1099DraftRows.length)build1099Drafts(false);let p=findProfile(name)||{}, e=findPayrollPerson(name)||{}, type=personCloudType(name), isC=String(type).includes('Contractor'), c=payrollCalc(e), ytd=personYtdAmount(name), status=personCloudReadyStatus(name); let html=`
Person Cloud${safeV19(name)}${safeV19(type)}
Employer / ID${safeV19(p.empNo||'Need ID')}Internal number for lookup
YTD / Tracked${money(ytd)}${isC?'Contractor payments':'Payroll staging'}
Year-End${safeV19(status)}Review before delivery
Cloud Ready${personCloudCompletion(name)}%Profile + forms + records

Hoja de vida / Main File

Employer / Employee ID${safeV19(p.empNo||'Need ID')}Legal name${safeV19(p.name||name)}Classification${safeV19(type)}Contact${safeV19(p.contact||'Need contact')}SSN/TIN${maskLast4(p.last4||'')}Address${safeV19(p.address||'Need address')}Start / hire${safeV19(p.hire||'Need date')}Branch / Job${safeV19(p.branch||'Need branch/job')}Profile status${safeV19(p.status||'Need profile review')}
${personFormsFolderHTML(name)}${paymentLedgerHTML(name)}

Year-End Delivery Folder

${isC?'1099‑NEC':'W‑2'} Draft${safeV19(status)}

Built from approved records inside this cloud.

Recipient Contact${safeV19(p.contact||'Need email/phone')}

Email copy or print/mail package after review.

Evidence${isC?'W‑9 + invoices + payment proof':'W‑4/I‑9 + time cards + payroll proof'}

Stored under this person cloud.

ActionsPrintExportEmail readyEdit in cloud
This is an internal readiness packet. Final official filing/delivery must be reviewed by the client or authorized payroll/tax provider.
`;openCloud(`${isC?'Contractor':'Employee'} Cloud File: ${name}`,html) } function openEmployeeCloudByName(name){ let p=findProfile(name)||{name,empNo:'',cls:'Profile missing',contact:'',last4:'',address:'',hire:'',branch:'',payBasis:'',deposit:'',forms:'Need profile',i9:'Need review',benefits:'',status:'Need setup',source:'Created from payroll/timecard record'}; let e=findPayrollPerson(name)||{name,type:p.cls||'Person',payType:p.payBasis||'Unknown',rate:0,hours:0,docStatus:p.forms||'Need forms',emp401Pct:0,erMatchPct:0,benefits:0,status:'Need payroll setup'}; let c=payrollCalc(e), tcs=timeCardsFor(name), punches=timeClockFor(name), ret=k401For(name), y=yearEndStatusFor(name);let isContractor=String((p.cls||e.type)).includes('Contractor');let formNeeded=isContractor?'W‑9 + 1099‑NEC readiness':'W‑4 + I‑9 + W‑2 readiness'; let html=`
Person${safeV19(name)}${safeV19(p.cls||e.type)}
Employer / ID${safeV19(p.empNo||'Need ID')}Internal lookup number
Year-End Status${safeV19(y)}${formNeeded}
Gross / Period${money(c.gross||0)}${safeV19(e.payType||p.payBasis)}
Net Estimate${money(c.net||0)}Staging only
Time Cards${tcs.length}Approved rows feed payroll

Hoja de vida / Profile

Employer / Employee ID${safeV19(p.empNo||'Need ID')}Legal name${safeV19(p.name)}Class${safeV19(p.cls)}Contact${safeV19(p.contact)}SSN/TIN${maskLast4(p.last4||'')}Address${safeV19(p.address)}Start${safeV19(p.hire)}Branch / Job${safeV19(p.branch)}Direct deposit${safeV19(p.deposit)}Source${safeV19(p.source||'Profile vault')}

Forms & compliance file

Required${formNeeded}Form status${safeV19(p.forms)}I‑9${safeV19(p.i9)}Benefits${safeV19(p.benefits)}Profile status${safeV19(p.status)}Year-end${safeV19(y)}
This cloud file organizes records; it does not file W‑2, W‑3, 1099‑NEC, payroll returns, or taxes by itself.
${isContractor?contractorCloudCardHTML(name):''}${paymentLedgerHTML(name)}

QR Punch Log

${punches.length?punches.map(x=>``).join(''):''}
EmployeeActionDate / TimeBranch / JobTokenStatus
${safeV19(x.employee||x.name||name)}${safeV19(x.action)}${safeV19(x.dateTime||x.datetime||x.date||'')}${safeV19(x.branch||x.job||'')}${safeV19(x.token||'')}${safeV19(x.status||'')}
No QR punches yet.
`;openCloud(`Employee Cloud File: ${name}`,html); } function editOptionsV19(values,selected){return values.map(v=>``).join('')} function openPersonEditCloud(name,returnMode='master'){ let p=findProfile(name)||{name,empNo:'',cls:personCloudType(name),contact:'',last4:'',address:'',hire:today(),branch:'',payBasis:'Hourly',deposit:'Need bank info',forms:'Need W-4',i9:'Need I-9',benefits:'Not eligible / not elected',status:'Need setup',source:'Cloud edit'}; let html=`

Edit Cloud File

Corrige aquí mismo sin regresar al formulario grande. Al guardar, la nube, el índice compacto, payroll/1099 readiness y búsqueda se refrescan.

Producción: SSN/TIN completo y documentos deben vivir cifrados en servidor. Esta vista edita el expediente interno y mantiene el último 4 en pantalla.
`; openCloud('Edit Cloud File: '+name,html); } function renameInRowsV19(arr,oldName,newName,keys=['name','employee']){arr.forEach(r=>keys.forEach(k=>{if(r[k]&&String(r[k]).toLowerCase()===String(oldName).toLowerCase())r[k]=newName}))} function savePersonCloudEdit(oldName,returnMode='master'){ let newName=(el('editName').value||oldName).trim(); let p={empNo:el('editEmpNo').value.trim(),name:newName,cls:el('editClass').value,contact:el('editContact').value,last4:el('editLast4').value,address:el('editAddress').value,hire:el('editHire').value||today(),branch:el('editBranch').value,payBasis:el('editPayBasis').value,deposit:el('editDeposit').value,forms:el('editForms').value,i9:el('editI9').value,benefits:el('editBenefits').value,status:el('editStatus').value,source:'Edited inside cloud'}; if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9';} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(oldName).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p);else employeeProfiles.unshift(p); if(String(oldName).toLowerCase()!==String(newName).toLowerCase()){ renameInRowsV19(employeeRows,oldName,newName,['name']);renameInRowsV19(timeCardRows,oldName,newName,['name']);renameInRowsV19(timeClockRows,oldName,newName,['name','employee']);renameInRowsV19(k401Rows,oldName,newName,['employee']);renameInRowsV19(contractorW9Rows,oldName,newName,['name']);renameInRowsV19(contractorPaymentRows,oldName,newName,['name']);renameInRowsV19(form1099DraftRows,oldName,newName,['name']); } let er=employeeRows.find(e=>String(e.name).toLowerCase()===String(newName).toLowerCase()); if(er){er.type=p.cls;er.payType=p.payBasis==='Salary'?'Salary per payroll':(p.payBasis==='Contractor invoice'?'Hourly':p.payBasis);er.docStatus=p.forms;} if(String(p.cls).includes('Contractor')){ let cw=contractorW9Rows.find(r=>String(r.name).toLowerCase()===String(newName).toLowerCase()); if(cw){cw.empNo=p.empNo;cw.contact=p.contact;cw.tin4=p.last4;cw.address=p.address;cw.w9Status=p.forms;} else contractorW9Rows.unshift({empNo:p.empNo,name:newName,business:'',taxClass:'Individual / Sole proprietor',w9Status:p.forms,tin4:p.last4,address:p.address,contact:p.contact,paid:paymentTotalForContractor(newName),source:'Need setup',backup:'Verify',agreement:'Need agreement / insurance',note:'Created from cloud edit',status:'Review'}); } createYearEndForms(false);build1099Drafts(false);renderPayrollBenefits();renderPeopleCloudCommand(); if(returnMode==='detail')openEmployeeCloudByName(newName);else openPersonCloudMaster(newName); } function exportPersonYearEndPacket(name){let p=findProfile(name)||{}, type=personCloudType(name);let rows=[['Field','Value'],['Employer/Employee ID',p.empNo||''],['Name',name],['Classification',type],['Contact',p.contact||''],['Address',p.address||''],['SSN/TIN Last 4',p.last4?('***-'+p.last4):''],['YTD / Tracked',personYtdAmount(name)],['Year-End Status',personCloudReadyStatus(name)],['Forms Required',String(type).includes('Contractor')?'W-9 + 1099-NEC':'W-4 + I-9 + W-2'],['Delivery','Email / print / mail after review']];let csv=rows.map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`webgo_person_cloud_${name.replace(/[^a-z0-9]+/gi,'_')}.csv`;a.click()} // ===== End V19 Patch ===== // ===== V19 Patch: separate email/phone + pay numbers inside every cloud file ===== function getEmailV19(p){ if(!p)return ''; if(p.email)return p.email; let c=String(p.contact||''); if(c.includes('@')) return c.split('/')[0].trim(); return ''; } function getPhoneV19(p){ if(!p)return ''; if(p.phone)return p.phone; let c=String(p.contact||''); if(c.includes('/')) return c.split('/').slice(1).join('/').trim(); let m=c.match(/\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/); return m?m[0]:''; } function contactV19(p){let e=getEmailV19(p), ph=getPhoneV19(p);return [e,ph].filter(Boolean).join(' / ') || p?.contact || 'Need email + phone'} function numericV19(v){let n=Number(v||0); return Number.isFinite(n)?n:0} function payLabelV19(p,e={}){ let basis=p?.payBasis || e.payType || 'Need setup'; let hourly=numericV19(p?.hourlyRate || (String(basis).includes('Hourly')?e.rate:0)); let salary=numericV19(p?.salaryAmount || (String(basis).includes('Salary')?e.rate:0)); let contractor=numericV19(p?.contractorPay || (String(p?.cls||e.type||'').includes('Contractor')?e.rate:0)); if(String(p?.cls||e.type||'').includes('Contractor')) return contractor?`Contractor pay / invoice: ${money(contractor)}`:'Contractor pay / invoice: Need amount'; if(String(basis).includes('Salary')) return salary?`Salary / period: ${money(salary)}`:'Salary / period: Need amount'; return hourly?`Hourly rate: ${money(hourly)}`:'Hourly rate: Need amount'; } function profileCompletion(p){let fields=['empNo','name','cls','email','phone','last4','address','hire','branch','payBasis','deposit','forms','i9','benefits'];let have=fields.filter(k=>{let v=p[k]; if((k==='email'||k==='phone')&&!v){v=(k==='email'?getEmailV19(p):getPhoneV19(p))} return v&&String(v).trim()&&!String(v).includes('Need')}).length; let payOk=!!(numericV19(p.hourlyRate)||numericV19(p.salaryAmount)||numericV19(p.contractorPay)); return Math.round((have+(payOk?1:0))/(fields.length+1)*100)} function syncProfilePayToPayrollV19(p){ let row=employeeRows.find(e=>String(e.name).toLowerCase()===String(p.name).toLowerCase()); let payType=p.payBasis==='Salary'?'Salary per payroll':(p.payBasis==='Contractor invoice'?'Hourly':'Hourly'); let rate=p.payBasis==='Salary'?numericV19(p.salaryAmount):(p.payBasis==='Contractor invoice'?numericV19(p.contractorPay):numericV19(p.hourlyRate)); if(!row){employeeRows.unshift({name:p.name,type:p.cls,payType,rate,hours:0,docStatus:p.forms,emp401Pct:0,erMatchPct:0,benefits:0,status:'Profile created / needs pay setup'});} else {row.type=p.cls; row.payType=payType; if(rate>0)row.rate=rate; row.docStatus=p.forms;} } function addEmployeeProfile(){ let emailEl=el('profEmail'), phoneEl=el('profPhone'), oldContactEl=el('profContact'); let email=(emailEl&&emailEl.value.trim())||''; let phone=(phoneEl&&phoneEl.value.trim())||''; if((!email||!phone)&&oldContactEl){let tmp={contact:oldContactEl.value}; email=email||getEmailV19(tmp); phone=phone||getPhoneV19(tmp)} let p={ empNo:(el('profEmpNo')&&el('profEmpNo').value.trim())||'', name:(el('profName').value||'').trim()||'New Worker', cls:el('profClass').value, email,phone,contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone', last4:el('profLast4').value||'', address:el('profAddress').value||'Need address', hire:el('profHire').value||today(), branch:el('profBranch').value||'Main Branch', payBasis:el('profPayBasis').value, hourlyRate:numericV19(el('profHourlyRate')?.value), salaryAmount:numericV19(el('profSalaryAmount')?.value), contractorPay:numericV19(el('profContractorPay')?.value), deposit:el('profDeposit').value, forms:el('profForms').value, i9:el('profI9').value, benefits:el('profBenefits').value, status:el('profStatus').value, source:'Manual / staff review V19' }; if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9'; if(!p.contractorPay&&p.hourlyRate)p.contractorPay=p.hourlyRate;} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(p.name).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p); else employeeProfiles.unshift(p); syncProfilePayToPayrollV19(p); createYearEndForms(false); build1099Drafts(false); renderPayrollBenefits(); activeTab='Payroll'; renderCarousel(); if(el('payrollStatus'))el('payrollStatus').textContent='V19: profile saved with separate email, phone, and pay amount inside the cloud file.'; } function generateEmployeeOnboardingQR(){ let name=(el('profName')&&el('profName').value)||'Worker'; let emp=(el('profEmpNo')&&el('profEmpNo').value)||''; let email=(el('profEmail')&&el('profEmail').value)||''; let phone=(el('profPhone')&&el('profPhone').value)||''; let token='EP-'+Math.random().toString(36).slice(2,8).toUpperCase()+'-'+Date.now().toString().slice(-5); let url=`./employee_profile_app/index.html?token=${encodeURIComponent(token)}&name=${encodeURIComponent(name)}&empNo=${encodeURIComponent(emp)}&email=${encodeURIComponent(email)}&phone=${encodeURIComponent(phone)}&company=WebGo%20Client`; onboardingRows.unshift({date:today(),name,token,url,status:'Sent / waiting for worker'}); if(el('profileQrStatus'))el('profileQrStatus').innerHTML=`
${token} • ${name}${emp?' • ID '+emp:''}
${url}`; return token; } function loadEmployeeProfileDemo(){ employeeProfiles=[ {empNo:'EMP-1001',name:'María Gómez',cls:'Employee W-2',email:'[email protected]',phone:'(813) 555-0101',contact:'[email protected] / (813) 555-0101',last4:'1234',address:'Tampa, FL',hire:'2026-05-01',branch:'Store 1 / Front Counter',payBasis:'Hourly',hourlyRate:22,salaryAmount:0,contractorPay:0,deposit:'On file',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) + benefits elected',status:'Ready for payroll',source:'Mobile onboarding + manager review'}, {empNo:'EMP-1002',name:'Pedro Ruiz',cls:'Employee W-2',email:'[email protected]',phone:'(813) 555-0102',contact:'[email protected] / (813) 555-0102',last4:'7788',address:'Wesley Chapel, FL',hire:'2026-04-15',branch:'Store 1 / Manager',payBasis:'Salary',hourlyRate:0,salaryAmount:2100,contractorPay:0,deposit:'On file',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) elected',status:'Ready for payroll',source:'Manager setup'}, {empNo:'CON-2001',name:'Ana Contractor',cls:'Independent Contractor 1099-NEC',email:'[email protected]',phone:'(813) 555-0199',contact:'[email protected] / (813) 555-0199',last4:'9911',address:'Lutz, FL',hire:'2026-05-10',branch:'Job / Project Support',payBasis:'Contractor invoice',hourlyRate:0,salaryAmount:0,contractorPay:35,deposit:'Not applicable',forms:'W-9 on file',i9:'Not applicable / contractor',benefits:'Not eligible / not elected',status:'Manager review',source:'Mobile W-9 intake'} ]; onboardingRows=[{date:today(),name:'New worker example',token:'EP-DEMO-10001',url:'./employee_profile_app/index.html?token=EP-DEMO-10001',status:'Waiting for worker'}]; // keep payroll rows aligned with profile pay numbers employeeProfiles.forEach(syncProfilePayToPayrollV19); renderPayrollBenefits(); if(el('payrollStatus'))el('payrollStatus').textContent='V19 demo loaded: email + phone separated, hourly/salary/contractor pay stored in each cloud.'; } function renderProfileCarouselHTML(){ if(!employeeProfiles.length)return '
No profiles yet.

Create a profile or generate a mobile onboarding QR so the worker can fill their information from the phone.

'; return employeeProfiles.map(p=>{let pct=profileCompletion(p);let contractor=String(p.cls).includes('Contractor');return `

${safeV18(p.name)}

${safeV18(p.empNo||'No ID')}${safeV18(p.cls)}${safeV18(p.payBasis)}${safeV18(requiredFormsForProfile(p))}
Employer / Employee ID${safeV18(p.empNo||'Need ID')}Email${safeV18(getEmailV19(p)||'Need email')}Phone${safeV18(getPhoneV19(p)||'Need phone')}Pay amount${safeV18(payLabelV19(p))}SSN/TIN${maskLast4(p.last4)}Address${safeV18(p.address)}Start${safeV18(p.hire)}Branch / Job${safeV18(p.branch)}Direct deposit${safeV18(p.deposit)}Forms${safeV18(p.forms)}Status${safeV18(p.status)}

Completion: ${pct}% • Source: ${safeV18(p.source||'Profile vault')}

`}).join(''); } function ensureProfileForContractorV18(r){ let i=employeeProfiles.findIndex(p=>String(p.name).toLowerCase()===String(r.name).toLowerCase()); let contact=r.contact||[r.email,r.phone].filter(Boolean).join(' / '); let row={empNo:r.empNo||('CON-'+String(Math.abs(hashCodeV18(r.name))%9000+1000)),name:r.name,cls:'Independent Contractor 1099-NEC',email:r.email||getEmailV19({contact}),phone:r.phone||getPhoneV19({contact}),contact,last4:r.tin4,address:r.address,hire:'2026-01-01',branch:'Contractor / vendor file',payBasis:'Contractor invoice',hourlyRate:0,salaryAmount:0,contractorPay:numericV19(r.defaultPay||0),deposit:'Not applicable / vendor payment',forms:r.w9Status,i9:'Not applicable / contractor',benefits:'Not eligible / contractor',status:String(r.w9Status).includes('on file')?'Ready for 1099 review':'Need W-9',source:'Contractor W-9/TIN Vault'}; if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],row); else employeeProfiles.push(row); } function openPersonCloudMaster(name){ if(!form1099DraftRows.length)build1099Drafts(false);let p=findProfile(name)||{}, e=findPayrollPerson(name)||{}, type=personCloudType(name), isC=String(type).includes('Contractor'), ytd=personYtdAmount(name), status=personCloudReadyStatus(name); let html=`
Person Cloud${safeV18(name)}${safeV18(type)}
YTD / Tracked${money(ytd)}${isC?'Contractor payments':'Payroll staging'}
Pay Setup${safeV18(payLabelV19(p,e))}Stored in profile
Cloud Ready${personCloudCompletion(name)}%Profile + forms + records

Hoja de vida / Main File

Employer / Employee ID${safeV18(p.empNo||'Need ID')}Legal name${safeV18(p.name||name)}Classification${safeV18(type)}Email${safeV18(getEmailV19(p)||'Need email')}Phone${safeV18(getPhoneV19(p)||'Need phone')}Pay basis${safeV18(p.payBasis||e.payType||'Need setup')}Pay amount${safeV18(payLabelV19(p,e))}SSN/TIN${maskLast4(p.last4||'')}Address${safeV18(p.address||'Need address')}Start / hire${safeV18(p.hire||'Need date')}Branch / Job${safeV18(p.branch||'Need branch/job')}Profile status${safeV18(p.status||'Need profile review')}
${personFormsFolderHTML(name)}${paymentLedgerHTML(name)}

Year-End Delivery Folder

${isC?'1099‑NEC':'W‑2'} Draft${safeV18(status)}

Built from approved records inside this cloud.

Recipient Email${safeV18(getEmailV19(p)||'Need email')}
Recipient Phone${safeV18(getPhoneV19(p)||'Need phone')}
Evidence${isC?'W‑9 + invoices + payment proof':'W‑4/I‑9 + time cards + payroll proof'}
V19: email and phone are separate. Pay amount lives inside the person cloud so year-end folders are ready without searching in January.
`;openCloud(`${isC?'Contractor':'Employee'} Cloud File: ${name}`,html) } function openEmployeeCloudByName(name){ let p=findProfile(name)||{name,cls:personCloudType(name)}, e=findPayrollPerson(name)||{}, isContractor=String(personCloudType(name)).includes('Contractor'), c=payrollCalc(e), y=personCloudReadyStatus(name), tcs=timeCardsFor(name), punches=timeClockRows.filter(x=>String(x.employee||x.name).toLowerCase()===String(name).toLowerCase()); let formNeeded=isContractor?'W‑9 + 1099‑NEC readiness':'W‑4 + I‑9 + W‑2 readiness'; let html=`
Person${safeV18(name)}${safeV18(p.cls||e.type)}
Employer / ID${safeV18(p.empNo||'Need ID')}Internal lookup number
Pay Setup${safeV18(payLabelV19(p,e))}${safeV18(p.payBasis||e.payType||'Pay basis')}
Year-End Status${safeV18(y)}${formNeeded}
Gross / Period${money(c.gross||0)}Staging only
Net Estimate${money(c.net||0)}Staging only

Hoja de vida / Profile

Employer / Employee ID${safeV18(p.empNo||'Need ID')}Legal name${safeV18(p.name||name)}Class${safeV18(p.cls||personCloudType(name))}Email${safeV18(getEmailV19(p)||'Need email')}Phone${safeV18(getPhoneV19(p)||'Need phone')}Pay basis${safeV18(p.payBasis||e.payType||'Need setup')}Pay amount${safeV18(payLabelV19(p,e))}SSN/TIN${maskLast4(p.last4||'')}Address${safeV18(p.address||'Need address')}Start${safeV18(p.hire||'Need date')}Branch / Job${safeV18(p.branch||'Need branch')}Direct deposit${safeV18(p.deposit||'Need setup')}

Forms & compliance file

Required${formNeeded}Form status${safeV18(p.forms||'Need form')}I‑9${safeV18(p.i9||'Need I-9')}Benefits${safeV18(p.benefits||'Need review')}Profile status${safeV18(p.status||'Need review')}Year-end${safeV18(y)}
This cloud file organizes records; it does not file W‑2, W‑3, 1099‑NEC, payroll returns, or taxes by itself.
${isContractor?contractorCloudCardHTML(name):''}${paymentLedgerHTML(name)}

QR Punch Log

${punches.length?punches.map(x=>``).join(''):''}
EmployeeActionDate / TimeBranch / JobTokenStatus
${safeV18(x.employee||x.name||name)}${safeV18(x.action)}${safeV18(x.dateTime||x.datetime||x.date||'')}${safeV18(x.branch||x.job||'')}${safeV18(x.token||'')}${safeV18(x.status||'')}
No QR punches yet.
`;openCloud(`Employee Cloud File: ${name}`,html); } function openPersonEditCloud(name,returnMode='master'){ let p=findProfile(name)||{name,empNo:'',cls:personCloudType(name),email:'',phone:'',contact:'',last4:'',address:'',hire:today(),branch:'',payBasis:'Hourly',hourlyRate:0,salaryAmount:0,contractorPay:0,deposit:'Need bank info',forms:'Need W-4',i9:'Need I-9',benefits:'Not eligible / not elected',status:'Need setup',source:'Cloud edit'}; let html=`

Edit Cloud File

Corrige aquí mismo sin regresar al formulario grande. V19 separa email/teléfono y guarda el número de pago dentro de la hoja de vida.

Producción: SSN/TIN completo y documentos deben vivir cifrados en servidor. Esta vista edita email, teléfono y pago sin volver al formulario grande.
`; openCloud('Edit Cloud File: '+name,html); } function savePersonCloudEdit(oldName,returnMode='master'){ let newName=(el('editName').value||oldName).trim(); let email=el('editEmail').value.trim(), phone=el('editPhone').value.trim(); let p={empNo:el('editEmpNo').value.trim(),name:newName,cls:el('editClass').value,email,phone,contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone',last4:el('editLast4').value,address:el('editAddress').value,hire:el('editHire').value||today(),branch:el('editBranch').value,payBasis:el('editPayBasis').value,hourlyRate:numericV19(el('editHourlyRate').value),salaryAmount:numericV19(el('editSalaryAmount').value),contractorPay:numericV19(el('editContractorPay').value),deposit:el('editDeposit').value,forms:el('editForms').value,i9:el('editI9').value,benefits:el('editBenefits').value,status:el('editStatus').value,source:'Edited inside cloud V19'}; if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9';} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(oldName).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p);else employeeProfiles.unshift(p); if(String(oldName).toLowerCase()!==String(newName).toLowerCase()){renameInRowsV18(employeeRows,oldName,newName,['name']);renameInRowsV18(timeCardRows,oldName,newName,['name']);renameInRowsV18(timeClockRows,oldName,newName,['name','employee']);renameInRowsV18(k401Rows,oldName,newName,['employee']);renameInRowsV18(contractorW9Rows,oldName,newName,['name']);renameInRowsV18(contractorPaymentRows,oldName,newName,['name']);renameInRowsV18(form1099DraftRows,oldName,newName,['name']);} syncProfilePayToPayrollV19(p); if(String(p.cls).includes('Contractor')){let cw=contractorW9Rows.find(r=>String(r.name).toLowerCase()===String(newName).toLowerCase()); if(cw){cw.empNo=p.empNo;cw.email=p.email;cw.phone=p.phone;cw.contact=p.contact;cw.tin4=p.last4;cw.address=p.address;cw.w9Status=p.forms;} else contractorW9Rows.unshift({empNo:p.empNo,name:newName,business:'',taxClass:'Individual / Sole proprietor',w9Status:p.forms,tin4:p.last4,address:p.address,email:p.email,phone:p.phone,contact:p.contact,paid:paymentTotalForContractor(newName),source:'Need setup',backup:'Verify',agreement:'Need agreement / insurance',note:'Created from cloud edit',status:'Review'});} createYearEndForms(false); build1099Drafts(false); renderPayrollBenefits(); renderPeopleCloudCommand(); if(returnMode==='detail')openEmployeeCloudByName(newName);else openPersonCloudMaster(newName); } function exportPersonYearEndPacket(name){let p=findProfile(name)||{}, type=personCloudType(name);let rows=[['Field','Value'],['Employer/Employee ID',p.empNo||''],['Name',name],['Classification',type],['Email',getEmailV19(p)||''],['Phone',getPhoneV19(p)||''],['Pay Basis',p.payBasis||''],['Hourly Rate',p.hourlyRate||''],['Salary / Period',p.salaryAmount||''],['Contractor Pay',p.contractorPay||''],['Address',p.address||''],['SSN/TIN Last 4',p.last4?('***-'+p.last4):''],['YTD / Tracked',personYtdAmount(name)],['Year-End Status',personCloudReadyStatus(name)],['Forms Required',String(type).includes('Contractor')?'W-9 + 1099-NEC':'W-4 + I-9 + W-2'],['Delivery','Email / print / mail after review']];let csv=rows.map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`webgo_person_cloud_${name.replace(/[^a-z0-9]+/gi,'_')}.csv`;a.click()} // ===== End V19 Patch ===== // ===== V19B Patch: contractor W-9 form also separates email/phone ===== function addContractorW9(){ let name=(el('cw9Name')&&el('cw9Name').value.trim())||'New Contractor'; let email=(el('cw9Email')&&el('cw9Email').value.trim())||''; let phone=(el('cw9Phone')&&el('cw9Phone').value.trim())||''; let oldContact=(el('cw9Contact')&&el('cw9Contact').value.trim())||''; if((!email||!phone)&&oldContact){email=email||getEmailV19({contact:oldContact});phone=phone||getPhoneV19({contact:oldContact});} let row={name,business:(el('cw9Business')&&el('cw9Business').value.trim())||'',taxClass:el('cw9Class')?el('cw9Class').value:'Individual / Sole proprietor',w9Status:el('cw9Status')?el('cw9Status').value:'Need W-9',tin4:(el('cw9Tin4')&&el('cw9Tin4').value.trim())||'',address:(el('cw9Address')&&el('cw9Address').value.trim())||'',email,phone,contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone',paid:Number((el('cw9Paid')&&el('cw9Paid').value)||0),defaultPay:numericV19(el('cw9DefaultPay')?.value),source:el('cw9Source')?el('cw9Source').value:'Bank transfer',backup:el('cw9Backup')?el('cw9Backup').value:'No backup withholding noted',agreement:el('cw9Agreement')?el('cw9Agreement').value:'Need agreement / insurance',note:(el('cw9Note')&&el('cw9Note').value.trim())||'',status:'1099 staging / review'}; let i=contractorW9Rows.findIndex(x=>String(x.name).toLowerCase()===String(name).toLowerCase()); if(i>=0) contractorW9Rows[i]=Object.assign({},contractorW9Rows[i],row); else contractorW9Rows.unshift(row); ensureProfileForContractorV18(row); syncProfilePayToPayrollV19(findProfile(name)||{}); buildContractor1099Readiness(false); build1099Drafts(false); renderPayrollBenefits(); activeTab='Payroll'; renderCarousel(); if(el('payrollStatus'))el('payrollStatus').textContent='Contractor W-9/TIN file updated with separate email and phone.'; } function loadContractorW9Demo(){ contractorW9Rows=[ {empNo:'CON-2001',name:'Ana Contractor',business:'Ana Renovation Services LLC',taxClass:'Single-member LLC',w9Status:'W-9 on file',tin4:'8821',address:'Tampa, FL',email:'[email protected]',phone:'(813) 555-0199',contact:'[email protected] / (813) 555-0199',paid:9800,defaultPay:35,source:'Bank transfer',backup:'No backup withholding noted',agreement:'Agreement + COI on file',note:'Subcontract flooring / repairs',status:'Ready / Review'}, {empNo:'CON-2002',name:'Carlos Delivery',business:'',taxClass:'Individual / Sole proprietor',w9Status:'Need W-9',tin4:'',address:'Need address',email:'',phone:'555-0188',contact:'555-0188',paid:740,defaultPay:0,source:'Zelle',backup:'Need correct TIN before more payments',agreement:'Need agreement / insurance',note:'Delivery helper. Need W-9 before year-end.',status:'Need W-9'}, {empNo:'CON-2003',name:'Bright Signs Studio',business:'Bright Signs Studio Inc.',taxClass:'S Corporation',w9Status:'W-9 requested',tin4:'4460',address:'Orlando, FL',email:'[email protected]',phone:'',contact:'[email protected]',paid:420,defaultPay:0,source:'Check',backup:'Verify with tax professional',agreement:'Agreement on file',note:'Signage/vendor; verify entity/form requirement.',status:'Review'} ]; contractorW9Rows.forEach(r=>{ensureProfileForContractorV18(r); if(!employeeRows.some(e=>String(e.name).toLowerCase()===String(r.name).toLowerCase())) employeeRows.push({name:r.name,type:'Independent Contractor 1099-NEC',payType:'Hourly',rate:numericV19(r.defaultPay),hours:0,docStatus:r.w9Status,emp401Pct:0,erMatchPct:0,benefits:0,status:'1099 contractor tracking'});}); buildContractor1099Readiness(false); renderPayrollBenefits(); if(el('payrollStatus'))el('payrollStatus').textContent='Contractor W-9/TIN demo loaded with separate email and phone.'; } // ===== End V19B Patch ===== // ===== V20 Patch: Direct Deposit / Bank Transfer Vault ===== function safeV20(v){return (typeof safeV19==='function'?safeV19(v):String(v===undefined||v===null?'':v).replace(/[&<>]/g,function(c){return {'&':'&','<':'<','>':'>'}[c]}))} function valV20(id){let x=el(id);return x?String(x.value||'').trim():''} function numV20(v){let n=Number(v||0);return Number.isFinite(n)?n:0} function bankSummaryV20(p){ p=p||{}; let method=p.deposit||'Need bank info'; let bank=p.bankName||''; let routing=p.routingLast4?('Routing ***'+p.routingLast4):''; let acct=p.acctLast4?('Acct ***'+p.acctLast4):''; let type=p.accountType||''; let holder=p.acctHolder||''; let handle=p.payHandle||''; let detail=[bank,type,acct,routing,handle].filter(Boolean).join(' • '); return `${safeV20(method)}${detail?`
${safeV20(detail)}`:''}${holder?`
Holder: ${safeV20(holder)}`:''}`; } function bankReadyStatusV20(p){ p=p||{}; if(String(p.deposit||'').includes('Not applicable'))return 'Not applicable'; if(String(p.deposit||'').includes('Need'))return 'Need bank info'; if(p.bankName && (p.acctLast4 || p.payHandle))return 'Payment info on file'; if(p.payHandle)return 'Digital transfer on file'; return 'Need bank/account detail'; } function paymentFieldsFromFormV20(prefix){ return { bankName:valV20(prefix+'BankName'), routingLast4:valV20(prefix+'RoutingLast4'), acctLast4:valV20(prefix+'AcctLast4'), accountType:valV20(prefix+'AccountType')||'Checking', acctHolder:valV20(prefix+'AcctHolder'), payHandle:valV20(prefix+'PayHandle') }; } function profileCompletion(p){ let fields=['empNo','name','cls','email','phone','last4','address','hire','branch','payBasis','deposit','forms','i9','benefits']; let have=fields.filter(k=>{let v=p&&p[k]; if((k==='email'||k==='phone')&&!v){v=(k==='email'?getEmailV19(p):getPhoneV19(p))} return v&&String(v).trim()&&!String(v).includes('Need')}).length; let payOk=!!(numV20(p&&p.hourlyRate)||numV20(p&&p.salaryAmount)||numV20(p&&p.contractorPay)); let bankOk=String(p&&p.deposit||'').includes('Not applicable') || !!(p&&p.bankName&&(p.acctLast4||p.payHandle)); return Math.round((have+(payOk?1:0)+(bankOk?1:0))/(fields.length+2)*100) } function addEmployeeProfile(){ let email=valV20('profEmail'), phone=valV20('profPhone'); let p={ empNo:valV20('profEmpNo'), name:valV20('profName')||'New Worker', cls:valV20('profClass')||'Employee W-2', email, phone, contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone', last4:valV20('profLast4'), address:valV20('profAddress')||'Need address', hire:valV20('profHire')||today(), branch:valV20('profBranch')||'Main Branch', payBasis:valV20('profPayBasis')||'Hourly', hourlyRate:numV20(valV20('profHourlyRate')), salaryAmount:numV20(valV20('profSalaryAmount')), contractorPay:numV20(valV20('profContractorPay')), deposit:valV20('profDeposit')||'Need bank info', forms:valV20('profForms')||'Need W-4', i9:valV20('profI9')||'Need I-9', benefits:valV20('profBenefits')||'Not eligible / not elected', status:valV20('profStatus')||'Pending employee', source:'Manual / staff review V20' }; Object.assign(p,paymentFieldsFromFormV20('prof')); if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9'; if(!p.contractorPay&&p.hourlyRate)p.contractorPay=p.hourlyRate;} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(p.name).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p); else employeeProfiles.unshift(p); syncProfilePayToPayrollV19(p); createYearEndForms(false); build1099Drafts(false); renderPayrollBenefits(); renderPeopleCloudCommand(); activeTab='Payroll'; renderCarousel(); if(el('payrollStatus'))el('payrollStatus').textContent='V20: profile saved with direct deposit / bank transfer information inside the cloud file.'; } function generateEmployeeOnboardingQR(){ let name=valV20('profName')||'Worker', emp=valV20('profEmpNo'), email=valV20('profEmail'), phone=valV20('profPhone'); let token='EP-'+Math.random().toString(36).slice(2,8).toUpperCase()+'-'+Date.now().toString().slice(-5); let url=`./employee_profile_app/index.html?token=${encodeURIComponent(token)}&name=${encodeURIComponent(name)}&empNo=${encodeURIComponent(emp)}&email=${encodeURIComponent(email)}&phone=${encodeURIComponent(phone)}&company=WebGo%20Client`; onboardingRows.unshift({date:today(),name,token,url,status:'Sent / waiting for worker bank/payroll info'}); if(el('profileQrStatus'))el('profileQrStatus').innerHTML=`
${token} • ${safeV20(name)}${emp?' • ID '+safeV20(emp):''}
${safeV20(url)}`; return token; } function loadEmployeeProfileDemo(){ employeeProfiles=[ {empNo:'EMP-1001',name:'María Gómez',cls:'Employee W-2',email:'[email protected]',phone:'(813) 555-0101',contact:'[email protected] / (813) 555-0101',last4:'1234',address:'Tampa, FL',hire:'2026-05-01',branch:'Store 1 / Front Counter',payBasis:'Hourly',hourlyRate:22,salaryAmount:0,contractorPay:0,deposit:'Direct deposit / ACH',bankName:'Chase',routingLast4:'0210',acctLast4:'1234',accountType:'Checking',acctHolder:'María Gómez',payHandle:'',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) + benefits elected',status:'Ready for payroll',source:'Mobile onboarding + manager review V20'}, {empNo:'EMP-1002',name:'Pedro Ruiz',cls:'Employee W-2',email:'[email protected]',phone:'(813) 555-0102',contact:'[email protected] / (813) 555-0102',last4:'7788',address:'Wesley Chapel, FL',hire:'2026-04-15',branch:'Store 1 / Manager',payBasis:'Salary',hourlyRate:0,salaryAmount:2100,contractorPay:0,deposit:'Direct deposit / ACH',bankName:'Bank of America',routingLast4:'0631',acctLast4:'7788',accountType:'Checking',acctHolder:'Pedro Ruiz',payHandle:'',forms:'W-4 on file',i9:'I-9 completed',benefits:'401(k) elected',status:'Ready for payroll',source:'Manager setup V20'}, {empNo:'CON-2001',name:'Ana Contractor',cls:'Independent Contractor 1099-NEC',email:'[email protected]',phone:'(813) 555-0199',contact:'[email protected] / (813) 555-0199',last4:'9911',address:'Lutz, FL',hire:'2026-05-10',branch:'Job / Project Support',payBasis:'Contractor invoice',hourlyRate:0,salaryAmount:0,contractorPay:35,deposit:'Bank transfer',bankName:'Wells Fargo',routingLast4:'1210',acctLast4:'8821',accountType:'Business checking',acctHolder:'Ana Renovation Services LLC',payHandle:'[email protected]',forms:'W-9 on file',i9:'Not applicable / contractor',benefits:'Not eligible / not elected',status:'Manager review',source:'Mobile W-9 intake V20'} ]; onboardingRows=[{date:today(),name:'New worker example',token:'EP-DEMO-10001',url:'./employee_profile_app/index.html?token=EP-DEMO-10001',status:'Waiting for worker profile + bank info'}]; employeeProfiles.forEach(syncProfilePayToPayrollV19); renderPayrollBenefits(); renderPeopleCloudCommand(); if(el('payrollStatus'))el('payrollStatus').textContent='V20 demo loaded: direct deposit / bank transfer data lives inside each cloud file.'; } function renderProfileCarouselHTML(){ if(!employeeProfiles.length)return '
No profiles yet.

Create a profile or generate a mobile onboarding QR so the worker can fill their information from the phone.

'; return employeeProfiles.map(p=>{let pct=profileCompletion(p);let contractor=String(p.cls).includes('Contractor');return `

${safeV20(p.name)}

${safeV20(p.empNo||'No ID')}${safeV20(p.cls)}${safeV20(p.payBasis)}${safeV20(bankReadyStatusV20(p))}
Employer / Employee ID${safeV20(p.empNo||'Need ID')}Email${safeV20(getEmailV19(p)||'Need email')}Phone${safeV20(getPhoneV19(p)||'Need phone')}Pay amount${safeV20(payLabelV19(p))}SSN/TIN${maskLast4(p.last4)}Bank / transfer${bankSummaryV20(p)}Forms${safeV20(p.forms)}Status${safeV20(p.status)}

Completion: ${pct}% • Source: ${safeV20(p.source||'Profile vault')}

`}).join(''); } function paymentBankFolderHTML(name){ let p=findProfile(name)||{}; return `

Direct Deposit / Transfer Vault

Payment method${safeV20(p.deposit||'Need bank info')}Bank / provider${safeV20(p.bankName||'Need bank/provider')}Account type${safeV20(p.accountType||'Need type')}Routing${p.routingLast4?'***'+safeV20(p.routingLast4):'Need routing info'}Account${p.acctLast4?'***'+safeV20(p.acctLast4):'Need account info'}Holder / handle${safeV20(p.acctHolder||p.payHandle||'Need holder/handle')}
Demo muestra solo últimos 4. Producción: cuenta y routing completos deben guardarse cifrados en servidor seguro; no en HTML/localStorage.
`; } function openPersonCloudMaster(name){ if(!form1099DraftRows.length)build1099Drafts(false);let p=findProfile(name)||{}, e=findPayrollPerson(name)||{}, type=personCloudType(name), isC=String(type).includes('Contractor'), ytd=personYtdAmount(name), status=personCloudReadyStatus(name); let html=`
Person Cloud${safeV20(name)}${safeV20(type)}
Employer / ID${safeV20(p.empNo||'Need ID')}Internal lookup number
Payment Setup${safeV20(bankReadyStatusV20(p))}${safeV20(p.deposit||'Need bank info')}
YTD / Tracked${money(ytd)}${isC?'Contractor payments':'Payroll staging'}
Year-End${safeV20(status)}Review before delivery
Cloud Ready${personCloudCompletion(name)}%Profile + forms + records

Hoja de vida / Main File

Employer / Employee ID${safeV20(p.empNo||'Need ID')}Legal name${safeV20(p.name||name)}Classification${safeV20(type)}Email${safeV20(getEmailV19(p)||'Need email')}Phone${safeV20(getPhoneV19(p)||'Need phone')}SSN/TIN${maskLast4(p.last4||'')}Address${safeV20(p.address||'Need address')}Start / hire${safeV20(p.hire||'Need date')}Branch / Job${safeV20(p.branch||'Need branch/job')}Profile status${safeV20(p.status||'Need profile review')}
${paymentBankFolderHTML(name)}${personFormsFolderHTML(name)}${paymentLedgerHTML(name)}

Year-End Delivery Folder

${isC?'1099‑NEC':'W‑2'} Draft${safeV20(status)}

Built from approved records inside this cloud.

Recipient Contact${safeV20(contactV19(p))}

Email copy or print/mail package after review.

Payment Info${safeV20(bankReadyStatusV20(p))}

Direct deposit / transfer evidence stays in the cloud.

ActionsPrintExportEmail readyEdit in cloud
This is an internal readiness packet. Final official filing/delivery must be reviewed by the client or authorized payroll/tax provider.
`;openCloud(`${isC?'Contractor':'Employee'} Cloud File: ${name}`,html) } function openEmployeeCloudByName(name){ let p=findProfile(name)||{name,cls:'Profile missing',email:'',phone:'',contact:'',last4:'',address:'',hire:'',branch:'',payBasis:'',deposit:'Need bank info',forms:'Need profile',i9:'Need review',benefits:'',status:'Need setup',source:'Created from payroll/timecard record'}; let e=findPayrollPerson(name)||{name,type:p.cls||'Person',payType:p.payBasis||'Unknown',rate:0,hours:0,docStatus:p.forms||'Need forms',emp401Pct:0,erMatchPct:0,benefits:0,status:'Need payroll setup'}; let c=payrollCalc(e), tcs=timeCardsFor(name), punches=timeClockFor(name), y=yearEndStatusFor(name), isContractor=String((p.cls||e.type)).includes('Contractor'); let formNeeded=isContractor?'W‑9 + 1099‑NEC readiness':'W‑4 + I‑9 + W‑2 readiness'; let html=`
Person${safeV20(name)}${safeV20(p.cls||e.type)}
Employer / ID${safeV20(p.empNo||'Need ID')}Internal lookup number
Payment Setup${safeV20(bankReadyStatusV20(p))}${safeV20(p.deposit||'Need bank info')}
Gross / Period${money(c.gross||0)}${safeV20(e.payType||p.payBasis)}
Net Estimate${money(c.net||0)}Staging only
Time Cards${tcs.length}Approved rows feed payroll

Hoja de vida / Profile

Employer / Employee ID${safeV20(p.empNo||'Need ID')}Legal name${safeV20(p.name)}Class${safeV20(p.cls)}Email${safeV20(getEmailV19(p)||'Need email')}Phone${safeV20(getPhoneV19(p)||'Need phone')}SSN/TIN${maskLast4(p.last4||'')}Address${safeV20(p.address)}Start${safeV20(p.hire)}Branch / Job${safeV20(p.branch)}Pay amount${safeV20(payLabelV19(p,e))}Source${safeV20(p.source||'Profile vault')}
${paymentBankFolderHTML(name)}

Forms & compliance file

Required${formNeeded}Form status${safeV20(p.forms)}I‑9${safeV20(p.i9)}Benefits${safeV20(p.benefits)}Profile status${safeV20(p.status)}Year-end${safeV20(y)}
This cloud file organizes records; it does not file W‑2, W‑3, 1099‑NEC, payroll returns, or taxes by itself.
${isContractor?contractorCloudCardHTML(name):''}${paymentLedgerHTML(name)}

QR Punch Log

${punches.length?punches.map(x=>``).join(''):''}
EmployeeActionDate / TimeBranch / JobTokenStatus
${safeV20(x.employee||x.name||name)}${safeV20(x.action)}${safeV20(x.dateTime||x.datetime||x.date||'')}${safeV20(x.branch||x.job||'')}${safeV20(x.token||'')}${safeV20(x.status||'')}
No QR punches yet.
`;openCloud(`Employee Cloud File: ${name}`,html); } function openPersonEditCloud(name,returnMode='master'){ let p=findProfile(name)||{name,empNo:'',cls:personCloudType(name),email:'',phone:'',contact:'',last4:'',address:'',hire:today(),branch:'',payBasis:'Hourly',hourlyRate:0,salaryAmount:0,contractorPay:0,deposit:'Need bank info',bankName:'',routingLast4:'',acctLast4:'',accountType:'Checking',acctHolder:'',payHandle:'',forms:'Need W-4',i9:'Need I-9',benefits:'Not eligible / not elected',status:'Need setup',source:'Cloud edit'}; let html=`

Edit Cloud File

Corrige aquí mismo sin regresar al formulario grande. V20 agrega direct deposit / bank transfer dentro del expediente.

Direct Deposit / Transfer Information

Guardar solo últimos 4 en esta plantilla. El número completo de cuenta/routing va cifrado en servidor seguro.
`; openCloud('Edit Cloud File: '+name,html); } function savePersonCloudEdit(oldName,returnMode='master'){ let newName=(el('editName').value||oldName).trim(), email=valV20('editEmail'), phone=valV20('editPhone'); let p={empNo:valV20('editEmpNo'),name:newName,cls:valV20('editClass'),email,phone,contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone',last4:valV20('editLast4'),address:valV20('editAddress'),hire:valV20('editHire')||today(),branch:valV20('editBranch'),payBasis:valV20('editPayBasis'),hourlyRate:numV20(valV20('editHourlyRate')),salaryAmount:numV20(valV20('editSalaryAmount')),contractorPay:numV20(valV20('editContractorPay')),deposit:valV20('editDeposit'),bankName:valV20('editBankName'),routingLast4:valV20('editRoutingLast4'),acctLast4:valV20('editAcctLast4'),accountType:valV20('editAccountType'),acctHolder:valV20('editAcctHolder'),payHandle:valV20('editPayHandle'),forms:valV20('editForms'),i9:valV20('editI9'),benefits:valV20('editBenefits'),status:valV20('editStatus'),source:'Edited inside cloud V20'}; if(String(p.cls).includes('Contractor')){p.i9='Not applicable / contractor'; if(p.forms.includes('W-4'))p.forms='Need W-9';} let i=employeeProfiles.findIndex(x=>String(x.name).toLowerCase()===String(oldName).toLowerCase()); if(i>=0)employeeProfiles[i]=Object.assign({},employeeProfiles[i],p);else employeeProfiles.unshift(p); if(String(oldName).toLowerCase()!==String(newName).toLowerCase()){renameInRowsV18(employeeRows,oldName,newName,['name']);renameInRowsV18(timeCardRows,oldName,newName,['name']);renameInRowsV18(timeClockRows,oldName,newName,['name','employee']);renameInRowsV18(k401Rows,oldName,newName,['employee']);renameInRowsV18(contractorW9Rows,oldName,newName,['name']);renameInRowsV18(contractorPaymentRows,oldName,newName,['name']);renameInRowsV18(form1099DraftRows,oldName,newName,['name']);} syncProfilePayToPayrollV19(p); if(String(p.cls).includes('Contractor')){let cw=contractorW9Rows.find(r=>String(r.name).toLowerCase()===String(newName).toLowerCase()); let data={empNo:p.empNo,email:p.email,phone:p.phone,contact:p.contact,tin4:p.last4,address:p.address,w9Status:p.forms,source:p.deposit,bankName:p.bankName,routingLast4:p.routingLast4,acctLast4:p.acctLast4,accountType:p.accountType,acctHolder:p.acctHolder,payHandle:p.payHandle}; if(cw){Object.assign(cw,data);} else contractorW9Rows.unshift(Object.assign({name:newName,business:'',taxClass:'Individual / Sole proprietor',paid:paymentTotalForContractor(newName),backup:'Verify',agreement:'Need agreement / insurance',note:'Created from cloud edit',status:'Review'},data));} createYearEndForms(false); build1099Drafts(false); renderPayrollBenefits(); renderPeopleCloudCommand(); if(returnMode==='detail')openEmployeeCloudByName(newName);else openPersonCloudMaster(newName); } function exportPersonYearEndPacket(name){let p=findProfile(name)||{}, type=personCloudType(name);let rows=[['Field','Value'],['Employer/Employee ID',p.empNo||''],['Name',name],['Classification',type],['Email',getEmailV19(p)||''],['Phone',getPhoneV19(p)||''],['Pay Basis',p.payBasis||''],['Hourly Rate',p.hourlyRate||''],['Salary / Period',p.salaryAmount||''],['Contractor Pay',p.contractorPay||''],['Payment Method',p.deposit||''],['Bank / Provider',p.bankName||''],['Account Type',p.accountType||''],['Account Last 4',p.acctLast4?('***'+p.acctLast4):''],['Routing Last 4',p.routingLast4?('***'+p.routingLast4):''],['Transfer Handle',p.payHandle||''],['Address',p.address||''],['SSN/TIN Last 4',p.last4?('***-'+p.last4):''],['YTD / Tracked',personYtdAmount(name)],['Year-End Status',personCloudReadyStatus(name)],['Forms Required',String(type).includes('Contractor')?'W-9 + 1099-NEC':'W-4 + I-9 + W-2'],['Delivery','Email / print / mail after review']];let csv=rows.map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`webgo_person_cloud_${name.replace(/[^a-z0-9]+/gi,'_')}.csv`;a.click()} function addContractorW9(){ let name=valV20('cw9Name')||'New Contractor', email=valV20('cw9Email'), phone=valV20('cw9Phone'); let row={name,business:valV20('cw9Business'),taxClass:valV20('cw9Class')||'Individual / Sole proprietor',w9Status:valV20('cw9Status')||'Need W-9',tin4:valV20('cw9Tin4'),address:valV20('cw9Address'),email,phone,contact:[email,phone].filter(Boolean).join(' / ')||'Need email + phone',paid:numV20(valV20('cw9Paid')),defaultPay:numV20(valV20('cw9DefaultPay')),source:valV20('cw9Source')||'Bank transfer / ACH',bankName:valV20('cw9BankName'),routingLast4:valV20('cw9RoutingLast4'),acctLast4:valV20('cw9AcctLast4'),accountType:'Business checking',acctHolder:valV20('cw9Business')||name,payHandle:valV20('cw9PayHandle'),backup:valV20('cw9Backup')||'No backup withholding noted',agreement:valV20('cw9Agreement')||'Need agreement / insurance',note:valV20('cw9Note'),status:'1099 staging / review'}; let i=contractorW9Rows.findIndex(x=>String(x.name).toLowerCase()===String(name).toLowerCase()); if(i>=0) contractorW9Rows[i]=Object.assign({},contractorW9Rows[i],row); else contractorW9Rows.unshift(row); ensureProfileForContractorV18(row); let prof=findProfile(name); if(prof){Object.assign(prof,{deposit:row.source,bankName:row.bankName,routingLast4:row.routingLast4,acctLast4:row.acctLast4,accountType:row.accountType,acctHolder:row.acctHolder,payHandle:row.payHandle}); syncProfilePayToPayrollV19(prof)} buildContractor1099Readiness(false); build1099Drafts(false); renderPayrollBenefits(); renderPeopleCloudCommand(); activeTab='Payroll'; renderCarousel(); if(el('payrollStatus'))el('payrollStatus').textContent='V20 contractor W-9/TIN file updated with bank transfer information.'; } function loadContractorW9Demo(){ contractorW9Rows=[ {empNo:'CON-2001',name:'Ana Contractor',business:'Ana Renovation Services LLC',taxClass:'Single-member LLC',w9Status:'W-9 on file',tin4:'8821',address:'Tampa, FL',email:'[email protected]',phone:'(813) 555-0199',contact:'[email protected] / (813) 555-0199',paid:9800,defaultPay:35,source:'Bank transfer / ACH',bankName:'Wells Fargo',routingLast4:'1210',acctLast4:'8821',accountType:'Business checking',acctHolder:'Ana Renovation Services LLC',payHandle:'[email protected]',backup:'No backup withholding noted',agreement:'Agreement + COI on file',note:'Subcontract flooring / repairs',status:'Ready / Review'}, {empNo:'CON-2002',name:'Carlos Delivery',business:'',taxClass:'Individual / Sole proprietor',w9Status:'Need W-9',tin4:'',address:'Need address',email:'',phone:'555-0188',contact:'555-0188',paid:740,defaultPay:0,source:'Zelle',bankName:'Zelle',routingLast4:'',acctLast4:'',accountType:'Zelle / digital wallet',acctHolder:'Carlos Delivery',payHandle:'555-0188',backup:'Need correct TIN before more payments',agreement:'Need agreement / insurance',note:'Delivery helper. Need W-9 before year-end.',status:'Need W-9'}, {empNo:'CON-2003',name:'Bright Signs Studio',business:'Bright Signs Studio Inc.',taxClass:'S Corporation',w9Status:'W-9 requested',tin4:'4460',address:'Orlando, FL',email:'[email protected]',phone:'',contact:'[email protected]',paid:420,defaultPay:0,source:'Bank transfer / ACH',bankName:'Need bank info',routingLast4:'',acctLast4:'',accountType:'Business checking',acctHolder:'Bright Signs Studio Inc.',payHandle:'[email protected]',backup:'Verify with tax professional',agreement:'Agreement on file',note:'Signage/vendor; verify entity/form requirement.',status:'Review'} ]; contractorW9Rows.forEach(r=>{ensureProfileForContractorV18(r); let prof=findProfile(r.name); if(prof)Object.assign(prof,{deposit:r.source,bankName:r.bankName,routingLast4:r.routingLast4,acctLast4:r.acctLast4,accountType:r.accountType,acctHolder:r.acctHolder,payHandle:r.payHandle}); if(!employeeRows.some(e=>String(e.name).toLowerCase()===String(r.name).toLowerCase())) employeeRows.push({name:r.name,type:'Independent Contractor 1099-NEC',payType:'Hourly',rate:numV20(r.defaultPay),hours:0,docStatus:r.w9Status,emp401Pct:0,erMatchPct:0,benefits:0,status:'1099 contractor tracking'});}); buildContractor1099Readiness(false); build1099Drafts(false); renderPayrollBenefits(); renderPeopleCloudCommand(); if(el('payrollStatus'))el('payrollStatus').textContent='V20 contractor demo loaded with bank transfer / direct deposit data.'; } function contractorCloudCardHTML(name){ let r=findContractorW9(name)||{}, st=contractor1099Status(r), p=findProfile(name)||{}; return `

Contractor W‑9 / TIN / 1099‑NEC File

Legal / business${safeV20(r.name||name)}

${safeV20(r.business||'No business name')} • ${safeV20(r.taxClass||'Need class')}

W‑9 / TIN${safeV20(r.w9Status||'Need W‑9')}

TIN/SSN: ${r.tin4?'***-'+safeV20(r.tin4):maskLast4(p.last4)} • full TIN stored only in secure server vault in production

1099‑NEC watch${safeV20(st)}

Tracked paid amount: ${money(r.paid||0)}

Address${safeV20(r.address||'Need address')}Contact${safeV20(r.contact||contactV19(p)||'Need contact')}Payment source${safeV20(r.source||p.deposit||'Need setup')}Bank / transfer${safeV20(r.bankName||p.bankName||'Need bank/provider')} ${r.acctLast4||p.acctLast4?' • Acct ***'+safeV20(r.acctLast4||p.acctLast4):''}Agreement / COI${safeV20(r.agreement||'Need review')}Backup withholding${safeV20(r.backup||'Verify')}Work type / notes${safeV20(r.note||'No notes')}
Contractor payments are tracked as nonemployee compensation/vendor payments. Bank details are masked here; production must store full account data encrypted.
`; } // ===== End V20 Patch ===== /* V20 Records Archive Cloud: year/month/day folder search + retention */ function archivePeriodFromDate(d){d=String(d||today());return d.slice(0,7)} function archiveYearFromPeriod(p){return String(p||today()).slice(0,4)} function archiveMonthFromPeriod(p){return String(p||today()).slice(5,7)} function archiveStatusClass(s){s=String(s||'');if(s.includes('Ready')||s.includes('Archived')||s.includes('Distributed'))return 'archive-status-good';if(s.includes('Corrected')||s.includes('Review')||s.includes('Expected'))return 'archive-status-warn';if(s.includes('Need')||s.includes('Missing')||s.includes('Risk'))return 'archive-status-risk';return ''} function archiveBaseRecords(){ let rows=[]; bankStatements.forEach(s=>{let period=s.period||'2026-05';let related=bankRows.filter(b=>(b.period||String(b.date||'').slice(0,7))===period&&(b.account||s.account)===s.account);let inflow=related.reduce((a,b)=>a+(b.amount>0?Number(b.amount):0),0),outflow=related.reduce((a,b)=>a+(b.amount<0?Math.abs(Number(b.amount)):0),0);rows.push({id:'bank-'+period+'-'+s.account,type:'Monthly Bank Statement',period,label:`${period} • ${s.account}`,inflow,outflow,status:'Archived / Ready',contents:`Original statement copy: ${s.file||'pending'} • ${related.length} staged bank line(s)`,source:'Bank Statement Vault',items:related});}); let posMonths={};posRows.forEach(r=>{let p=archivePeriodFromDate(r.date);if(!posMonths[p])posMonths[p]=[];posMonths[p].push(r)});Object.entries(posMonths).forEach(([period,items])=>{let t=items.reduce((a,r)=>{a.inflow+=(r.gross||0)+(r.tax||0);a.outflow+=(r.refunds||0)+(r.discounts||0)+(r.fees||0)+(r.cogs||0);return a},{inflow:0,outflow:0});rows.push({id:'pos-month-'+period,type:'Daily Shift Balance',period,label:`${period} • Daily Shift Balance Folder`,inflow:t.inflow,outflow:t.outflow,status:posDistributed?'Distributed / Review':'Raw / Review',contents:`${items.length} POS/app raw row(s) • folder detail inside cloud`,source:'POS/App Daily Collector',items});}); let cashMonths={};cashRows.forEach(r=>{let p=archivePeriodFromDate(r.date);if(!cashMonths[p])cashMonths[p]=[];cashMonths[p].push(r)});Object.entries(cashMonths).forEach(([period,items])=>{let inflow=items.reduce((s,r)=>s+(String(r.type||'').includes('Income')?Number(r.amount)||0:0),0),outflow=items.reduce((s,r)=>s+(String(r.type||'').includes('Expense')?Number(r.amount)||0:0),0);rows.push({id:'cash-'+period,type:'Daily Shift Balance',period,label:`${period} • Cash Control Folder`,inflow,outflow,status:items.some(x=>String(x.deposit||'').includes('No'))?'Needs proof':'Ready',contents:`${items.length} cash record(s) with deposit/proof status`,source:'Cash Control',items});}); let payMonths={};payrollRuns.forEach(r=>{let p=archivePeriodFromDate(r.date);if(!payMonths[p])payMonths[p]=[];payMonths[p].push(r)});Object.entries(payMonths).forEach(([period,items])=>{let gross=items.reduce((s,r)=>s+(r.gross||0),0),net=items.reduce((s,r)=>s+(r.net||0),0),tax=items.reduce((s,r)=>s+(r.employeeTax||0)+(r.employerTax||0),0);rows.push({id:'payroll-'+period,type:'Payroll / People Cloud',period,label:`${period} • Payroll / People Cloud`,inflow:0,outflow:net+tax,status:'Review',contents:`Gross ${money(gross)} • Net ${money(net)} • Tax watch ${money(tax)}`,source:'Payroll Premium',items});}); let contractorMonths={};contractorPaymentRows.forEach(r=>{let p=archivePeriodFromDate(r.date);if(!contractorMonths[p])contractorMonths[p]=[];contractorMonths[p].push(r)});Object.entries(contractorMonths).forEach(([period,items])=>{let outflow=items.reduce((s,r)=>s+(Number(r.amount)||0),0);rows.push({id:'contractor-'+period,type:'Contractor / 1099',period,label:`${period} • Contractor / 1099 Folder`,inflow:0,outflow,status:items.some(x=>!x.proof)?'Needs proof':'Ready',contents:`${items.length} contractor payment(s) • W-9 / 1099-NEC readiness`,source:'Contractor W-9 / 1099 Center',items});}); let govMonths={};govLedgerRows.forEach(r=>{let p=archivePeriodFromDate(r.date||today());if(!govMonths[p])govMonths[p]=[];govMonths[p].push(r)});Object.entries(govMonths).forEach(([period,items])=>{rows.push({id:'tax-'+period,type:'Tax-ready Package',period,label:`${period} • Tax-ready Evidence Folder`,inflow:0,outflow:0,status:'Ready',contents:`${items.length} CPA/tax ledger line(s) and evidence support`,source:'CPA / Tax Ledger',items});}); if(archiveDemoLoaded||!rows.length){rows=rows.concat([ {id:'demo-bank-2026-05',type:'Monthly Bank Statement',period:'2026-05',label:'2026-05 • Operating Bank Statement',inflow:10940,outflow:6740,status:'Archived / Ready',contents:'Original PDF + CSV breakdown + reconciliation summary',source:'Bank Statement Vault',items:[]}, {id:'demo-dsb-2026-05',type:'Daily Shift Balance',period:'2026-05',label:'2026-05 • Daily Shift Balance Folders',inflow:72900,outflow:24680,status:'Distributed / Review',contents:'31 daily folders • POS, cash, fees, refunds, tax and COGS',source:'POS/App Daily Collector',items:[]}, {id:'demo-week-2026-05',type:'Weekly POS Folder',period:'2026-05',label:'May 18–24 • Weekly POS Folder',inflow:18450,outflow:6220,status:'Ready',contents:'Weekly statement folder with internal vertical detail',source:'Daily Shift Balance',items:[]}, {id:'demo-correction-2026-05',type:'Correction / Adjustment',period:'2026-05',label:'2026-05 • Refund / Complaint Adjustment',inflow:0,outflow:900,status:'Corrected / linked',contents:'Original record preserved; refund/reversal note linked to source',source:'Review / Correction Note',items:[]}, {id:'demo-payroll-2026-05',type:'Payroll / People Cloud',period:'2026-05',label:'2026-05 • Payroll and People Cloud',inflow:0,outflow:8420,status:'Review',contents:'Payroll run, time cards, direct deposit proof and tax watch',source:'Payroll Center',items:[]}, {id:'demo-tax-2026',type:'Tax-ready Package',period:'2026-12',label:'2026 • Year-End Tax-ready Package',inflow:0,outflow:0,status:'Ready',contents:'Annual CPA/tax package, evidence vault, exports and archive summary',source:'Tax-ready Records',items:[]} ]);} return rows; } function filteredArchiveRecords(){let y=el('archiveYear')?el('archiveYear').value:'all',m=el('archiveMonth')?el('archiveMonth').value:'all',t=el('archiveType')?el('archiveType').value:'all',st=el('archiveStatus')?el('archiveStatus').value:'all',q=(el('archiveSearch')?el('archiveSearch').value:'').toLowerCase().trim();return archiveBaseRecords().filter(r=>{let ok=true;if(y!=='all')ok=archiveYearFromPeriod(r.period)===y;if(ok&&m!=='all')ok=archiveMonthFromPeriod(r.period)===m;if(ok&&t!=='all')ok=r.type===t;if(ok&&st!=='all')ok=String(r.status||'').toLowerCase().includes(st.toLowerCase());if(ok&&q){let hay=[r.label,r.type,r.period,r.status,r.contents,r.source].join(' ').toLowerCase();ok=hay.includes(q)}return ok})} function renderRecordsArchive(){if(!el('recordsArchiveBody'))return;let rows=filteredArchiveRecords();let inflow=rows.reduce((s,r)=>s+(Number(r.inflow)||0),0),outflow=rows.reduce((s,r)=>s+(Number(r.outflow)||0),0);el('archiveCount').textContent=rows.length;el('archiveInflows').textContent=money(inflow);el('archiveOutflows').textContent=money(outflow);el('archiveNet').textContent=money(inflow-outflow);el('recordsArchiveBody').innerHTML=rows.map(r=>`${r.label}${r.period}${r.type}${money(r.inflow)}${money(r.outflow)}${r.status}${r.contents}`).join('')||'No archive folders match this search.';el('recordsArchiveCloud').innerHTML=rows.slice(0,8).map(r=>`
${r.type}${r.label}

${r.contents}

In ${money(r.inflow)}Out ${money(r.outflow)}${r.status}
`).join('')||'
No archive folders found.

Change filters or load the archive demo.

'} function archiveDetailRows(r){let items=r.items||[];if(!items.length)return `${r.period}${r.source}${r.type}${r.contents}${money(r.inflow)}${money(r.outflow)}${r.status}`;return items.map(x=>{let date=x.date||r.period,source=x.source||x.account||x.vendor||x.employee||x.name||r.source,desc=x.desc||x.description||x.products||x.work||x.note||x.status||r.type,amount=x.amount||(x.gross?x.gross:0),out=x.outflow||x.out||((Number(amount)<0)?Math.abs(Number(amount)):0),inc=x.inflow||x.in||((Number(amount)>0)?Number(amount):0);return `${date}${source}${r.type}${desc}${money(inc)}${money(out)}${x.status||r.status}`}).join('')} function openArchiveFolder(id){let r=archiveBaseRecords().find(x=>x.id===id)||filteredArchiveRecords()[0];if(!r){openCloud('Records Archive Folder','

No archive folder found.

');return}let html=`
Folder${r.period}${r.type}
Inflows${money(r.inflow)}
Outflows${money(r.outflow)}
Net impact${money((r.inflow||0)-(r.outflow||0))}${r.status}
Archive rule: original source copies stay preserved. Accounting distribution, Daily Shift Balance, correction notes and tax-ready evidence are linked to the folder instead of stretching the main page.
Source${r.source}

${r.contents}

Retention7-year WebGo default

Keep longer if needed for taxes, disputes, loans, assets or unresolved corrections.

FlowFolder → Review → Ledger → Tax-ready

Main screen stays summary-only.

Folder Detail / Internal Vertical Scroll

${archiveDetailRows(r)}
DateSourceTypeDescriptionInOutStatus
Review / Correction Note: if a complaint, refund, chargeback, bank reversal, duplicate or returned money appears later, WebGo does not erase the original record. It creates a correction/adjustment linked to this folder and preserves the audit trail.
`;openCloud('Records Archive Cloud: '+r.label,html)} function loadRecordsArchiveDemo(){archiveDemoLoaded=true;let y=el('archiveYear');if(y)y.value='all';let m=el('archiveMonth');if(m)m.value='all';let t=el('archiveType');if(t)t.value='all';let s=el('archiveStatus');if(s)s.value='all';let q=el('archiveSearch');if(q)q.value='';renderRecordsArchive()} function openRetentionRecord(kind){ archiveDemoLoaded=true; let typeMap={bank:'Monthly Bank Statement',daily:'Daily Shift Balance',payroll:'Payroll / People Cloud',tax:'Tax-ready Package',corrections:'Correction / Adjustment'}; let titleMap={bank:'Monthly Bank Statements',daily:'Daily Shift Balance Folders',payroll:'Payroll / Employment Tax Support',timecards:'Time Cards / Schedules',tax:'Tax-ready Yearly Package',corrections:'Open Corrections / Disputes'}; if(kind==='timecards'){ let html=`
Folder typeTime CardsSchedules + punch records
Retention7 yearsWebGo default
Linked toPeople CloudPayroll support
Time Card Archive: schedules, QR punch records, breaks, manager approvals and payroll-ready hours stay inside the employee cloud and monthly payroll folders. The main page shows summary only; detail opens in a cloud with internal vertical scroll.
SourceEmployee QR TimeCard App

Clock in/out, lunch/break, branch/job and manager approval.

Monthly folderPayroll / People Cloud

Time card support is linked to payroll run and year-end readiness.

EvidenceNever erase original

Corrections create adjustment notes with who/when/why.

`; openCloud('Records Archive: '+titleMap[kind],html); return; } let wanted=typeMap[kind]; let rows=archiveBaseRecords().filter(r=>r.type===wanted); if(!rows.length){loadRecordsArchiveDemo();rows=archiveBaseRecords().filter(r=>r.type===wanted);} if(rows.length){openArchiveFolder(rows[0].id);return;} openCloud('Records Archive: '+(titleMap[kind]||'Record'),'

No folder found yet. Load archive demo or create the related monthly/daily folder first.

'); } /* V20 Branch Cloud / Department Hub - simplified Main + Branch selector */ function makeBranchCloud(i, overrides={}){ let id='BR-'+String(i).padStart(3,'0'); return Object.assign({ id, name:'Branch '+i, category:'Custom business category', manager:'Add inside cloud', address:'Add location inside cloud', bank:'Add bank/deposit source inside cloud', registers:0, mode:'Automatic', income:0, expenses:0, cash:0, card:0, tax:0, status:'Template ready', inflows:'POS sales, cash drawer, card deposits, invoices, local deposits', outflows:'Local expenses, refunds, petty cash, COGS, supplies, deposits', other:'Use this branch cloud for any local operational activity that belongs to this branch/department.', sentToMain:false, lastSent:'' }, overrides); } let branchClouds = [ makeBranchCloud(1,{name:'Branch 1 / Main Location',category:'Main branch operations',manager:'Main Manager',address:'Main location',bank:'Main operating bank / merchant deposits',registers:2,income:32400,expenses:11200,cash:6200,card:28450,tax:2268,status:'Active / Routed'}), makeBranchCloud(2,{name:'Branch 2',category:'Branch operations',mode:'Automatic + Review',registers:1,income:18750,expenses:6900,cash:3180,card:16620,tax:1312,status:'Review / Ready'}), makeBranchCloud(3,{name:'Branch 3',category:'Branch operations'}), makeBranchCloud(4),makeBranchCloud(5),makeBranchCloud(6),makeBranchCloud(7),makeBranchCloud(8),makeBranchCloud(9),makeBranchCloud(10) ]; let selectedBranchId = 'BR-001'; function branchById(id){return branchClouds.find(b=>String(b.id)===String(id)) || branchClouds[0]} function branchTotals(){return branchClouds.reduce((a,b)=>{a.income+=Number(b.income)||0;a.expenses+=Number(b.expenses)||0;a.cash+=Number(b.cash)||0;a.card+=Number(b.card)||0;a.tax+=Number(b.tax)||0;a.registers+=Number(b.registers)||0;return a},{income:0,expenses:0,cash:0,card:0,tax:0,registers:0})} function renderBranchControl(){ if(!el('branchCloudIndex'))return; let sel=el('branchSelector'); if(sel){let old=selectedBranchId;sel.innerHTML=branchClouds.map(b=>``).join('');} let b=branchById(selectedBranchId), t=branchTotals(), company=el('branchCompanyName')?el('branchCompanyName').value:'Company Main'; el('branchCloudIndex').innerHTML = `
Company Main${safeV20(company)}

Legal/tax house. Receives monthly totals from branch clouds.

Income ${money(t.income)}Expenses ${money(t.expenses)}Tax support ${money(t.tax)}
Selected Branch Cloud${safeV20(b.id)} • ${safeV20(b.name)}

Local operational activity only. Open cloud to manage address, bank source, inflows/outflows and Daily Shift Balance.

${safeV20(b.mode)}${safeV20(b.registers)} POS/register${safeV20(b.status)}
Month-end flowBranch totals → Main Ledger

Details stay inside branch folders. Main receives summary totals for bookkeeping and tax-ready records.

${branchClouds.length} branch slotsConsolidated net ${money(t.income-t.expenses)}
`; if(el('branchStatusNote'))el('branchStatusNote').textContent=`Selected: ${b.id} • ${b.name}. Company Main remains ${company}.`; } function setBranchView(v){if(v==='consolidated')openConsolidatedLedgerCloud();else openSelectedBranchCloud()} function selectBranchFromDropdown(id){selectedBranchId=id; if(el('branchCurrentView'))el('branchCurrentView').value='branch'; renderBranchControl();} function nextBranchId(){let n=branchClouds.length+1;return 'BR-'+String(n).padStart(3,'0')} function addBranchCloud(){ // Kept for compatibility with older buttons. New design uses branch slots/templates instead of a large visible form. let id=nextBranchId(); let b=makeBranchCloud(branchClouds.length+1,{id,name:'Branch '+(branchClouds.length+1),status:'Created / Needs setup inside cloud'}); branchClouds.push(b); selectedBranchId=b.id; renderBranchControl(); openBranchCloud(b.id); } function loadBranchCloudDemo(){ branchClouds=[ makeBranchCloud(1,{name:'Branch 1 / Main Location',category:'Main branch operations',manager:'Main Manager',address:'Main location',bank:'Main operating bank / merchant deposits',registers:3,mode:'Automatic',income:72900,expenses:24680,cash:15600,card:55000,tax:4980,status:'Active / Distributed',other:'Main location local activity folder'}), makeBranchCloud(2,{name:'Branch 2',category:'Branch operations',manager:'Add manager inside cloud',address:'Add location inside cloud',bank:'Main bank or local deposit source',registers:2,mode:'Automatic + Review',income:38450,expenses:14220,cash:7200,card:30110,tax:2691,status:'Review / Ready',other:'Review refunds, local deposits and cash drawer'}), makeBranchCloud(3,{name:'Branch 3',category:'Branch operations',registers:1,mode:'Automatic',income:12600,expenses:4100,cash:2200,card:10300,tax:882,status:'Active / Template'}), makeBranchCloud(4),makeBranchCloud(5),makeBranchCloud(6),makeBranchCloud(7),makeBranchCloud(8),makeBranchCloud(9),makeBranchCloud(10) ]; selectedBranchId='BR-001'; renderBranchControl(); } function routeBranchDemo(){ let b=branchById(selectedBranchId); if(!b)return; b.income=(Number(b.income)||0)+1250; b.card=(Number(b.card)||0)+1125; b.cash=(Number(b.cash)||0)+125; b.tax=(Number(b.tax)||0)+87.50; b.status='Sofía routed latest branch activity'; renderBranchControl(); openBranchCloud(b.id); } function openBranchSetupNote(){ openCloud('Other / Custom Branch Need', `
Use this when something is missing.
The client can upload a report, add a note, or WebGo can attach a new source step by step. This does not duplicate the Main system.
ExamplesNew local bank source, register group, department, cash drawer, storage/warehouse, seasonal location, or special report.
RuleCreate it inside the selected Branch Cloud, then send monthly totals back to Main.
`); } function branchDailyRows(b){ let income=Number(b.income)||0, expenses=Number(b.expenses)||0, cash=Number(b.cash)||0, card=Number(b.card)||0, tax=Number(b.tax)||0; let rows=[ ['Daily', 'Local inflows', 'Daily Shift Balance', income*.55, 0, 'Sales/income collected inside this branch cloud'], ['Daily', 'Local outflows', 'Branch Expense Folder', 0, expenses*.35, 'Local expenses, refunds, supplies and cash paid out'], ['Daily', 'Cash drawer / deposits', 'Cash Control', cash, 0, 'Cash count, over/short, and deposit proof stay inside cloud'], ['Daily', 'Card / merchant deposits', 'Bank Match', card, 0, 'Expected deposit matched to bank/merchant account'], ['Monthly', 'Sales tax / liability support', 'Tax-ready Support', tax, 0, 'Tax support rolls to Main totals after review'], ['Monthly', 'Branch Monthly Summary', 'Send to Main Ledger', income, expenses, b.sentToMain?'Sent to Main Ledger':'Ready to send totals to Main'], ['Other / Custom', 'Manual report or missing item', 'Needs Branch Review', 0, 0, b.other||'Add custom details'] ]; return rows.map(r=>`${r[0]}${safeV20(r[1])}${safeV20(r[2])}${money(r[3])}${money(r[4])}${safeV20(r[5])}`).join('') } function branchMonthlySummaryRows(b){ let income=Number(b.income)||0, expenses=Number(b.expenses)||0, cash=Number(b.cash)||0, card=Number(b.card)||0, tax=Number(b.tax)||0; let net=income-expenses; let rows=[ ['Total local income', income, 'Main Ledger income'], ['Total local expenses', expenses, 'Main Ledger deductions/expenses'], ['Cash collected', cash, 'Cash Control / evidence'], ['Card / merchant activity', card, 'Bank Match / merchant deposits'], ['Sales tax support', tax, 'Tax Reserve / liability review'], ['Net branch balance', net, 'Company Main monthly totals'] ]; return rows.map(r=>`${safeV20(r[0])}${money(r[1])}${safeV20(r[2])}`).join(''); } function sendBranchTotalsToMain(id){ let b=branchById(id||selectedBranchId); if(!b)return; b.sentToMain=true; b.lastSent=new Date().toLocaleString(); b.status='Monthly totals sent to Company Main'; renderBranchControl(); openConsolidatedLedgerCloud(); } function openBranchCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; selectedBranchId=b.id; let income=Number(b.income)||0, expenses=Number(b.expenses)||0, net=income-expenses; let html=`
${safeV20(b.id)} • ${safeV20(b.name)}Independent Branch Cloud workspace. The Main page stays behind; close this cloud to return.
Branch Cloud${safeV20(b.id)}${safeV20(b.name)}
Monthly inflows${money(income)}Local entries only
Monthly outflows${money(expenses)}Local exits only
Net to Main${money(net)}${safeV20(b.status)}
Branch rule: this cloud collects and stores the full local detail. Company Main does not duplicate every local line; it receives clean monthly totals for the Bookkeeper Ledger and CPA/Tax-ready package.
Branch profile${safeV20(b.name)}

Category: ${safeV20(b.category)} • Manager: ${safeV20(b.manager)} • Address: ${safeV20(b.address)}

Local inflows${safeV20(b.inflows||'POS sales, cash drawer, card deposits')}

Local income lines feed the Branch Daily Shift Balance.

Local outflows${safeV20(b.outflows||'Local expenses, refunds, supplies, deposits')}

Local expense lines stay in this cloud until monthly totals are sent to Main.

Bank / deposit / POS sources${safeV20(b.bank)}

Registers/POS: ${safeV20(b.registers)}. Details and statements remain in branch folders.

Processing mode${safeV20(b.mode)}

Automatic first, with review or manual override for corrections.

Branch Detail Folders

${branchDailyRows(b)}
PeriodSourceFolderInflowsOutflowsStatus / note

Monthly Branch Summary → Main

${branchMonthlySummaryRows(b)}
Summary lineTotalMain destination
Other / Custom: if this branch has something not listed, the client can add details, upload a report, or WebGo can attach it step by step without changing the Main structure.
${safeV20(b.other||'No custom note')}
Branch detail stays inside this cloud. Close it to return to Company / Branch Control.
`; openCloud('Branch Cloud: '+b.name, html); } function openSelectedBranchCloud(){openBranchCloud(selectedBranchId)} function openConsolidatedLedgerCloud(){ let t=branchTotals(), company=el('branchCompanyName')?el('branchCompanyName').value:'Company Main'; let active=branchClouds.filter(b=>(Number(b.income)||0)||(Number(b.expenses)||0)||(Number(b.registers)||0)||b.status!=='Template ready'); let rows=active.length?active:branchClouds.slice(0,3); let body=rows.map(b=>`${safeV20(b.id)}${safeV20(b.name)}${safeV20(b.category)}${money(b.income)}${money(b.expenses)}${money((b.income||0)-(b.expenses||0))}${b.sentToMain?('Sent • '+safeV20(b.lastSent||'')):safeV20(b.status)}`).join(''); let html=`
Company Main Consolidated TotalsOnly summary totals live here. Branch details remain inside each Branch Cloud.
Company Main${safeV20(company)}${branchClouds.length} branch slot(s)
Total income${money(t.income)}
Total expenses${money(t.expenses)}
Net company result${money(t.income-t.expenses)}
Tax support${money(t.tax)}Consolidated at Main
Main consolidation: Branch Clouds keep detail. Company Main receives branch monthly totals only, then builds one Bookkeeper Ledger and one CPA/Tax-ready package for the legal company, unless a branch is a separate legal entity.
${body}
Branch IDBranchCategoryIncomeExpensesNetMain statusCloud
Main Ledger outputCompany income total: ${money(t.income)}

Built from branch monthly summaries and non-branch company activity.

Deduction supportCompany expense total: ${money(t.expenses)}

Branch expense totals roll up; evidence stays linked in each cloud.

Sales tax / reserve support${money(t.tax)}

Branch tax support consolidates to the company tax-ready layer.

Audit trailOpen branch folder when detail is needed

Original records, corrections and evidence remain in Branch Cloud folders.

Other / Custom line: if a department/source is missing, create it inside the selected Branch Cloud or attach a manual report before month-end close.
Company Main shows totals only. Open any branch cloud for detail.
`; openCloud('Company Main Consolidated Ledger', html); } /* V20 Branch Bank/POS Full Breakdown Override */ function branchBankSampleRows(b){ let base=b && b.id ? b.id : 'BR-001'; return [ {date:'2026-05-01',desc:'Opening branch balance',amount:0,entity:'Opening balance',type:'Carryforward',category:'Beginning balance',dest:'Branch monthly folder',match:'Statement copy preserved',status:'Locked / archive'}, {date:'2026-05-02',desc:'Merchant deposit batch',amount:2650,entity:'Merchant processor',type:'Inflow',category:'Card deposit',dest:'Daily Shift Balance / Bank Match',match:'Match POS card batch',status:'Ready to review'}, {date:'2026-05-02',desc:'Cash deposit',amount:740,entity:'Branch cash drawer',type:'Inflow',category:'Cash deposit',dest:'Cash Control / Daily Shift Balance',match:'Needs cash count proof',status:'Review'}, {date:'2026-05-03',desc:'Local supply purchase',amount:-184.55,entity:'Retail supply vendor',type:'Outflow',category:'Supplies / materials',dest:'Branch expense folder',match:'Receipt attached or requested',status:'Staged'}, {date:'2026-05-03',desc:'Fuel / vehicle support',amount:-20.05,entity:'Fuel station',type:'Outflow',category:'Vehicle / fuel',dest:'Expense review / evidence',match:'Need vehicle/person note',status:'Client answer'}, {date:'2026-05-04',desc:'Payroll provider debit',amount:-1250,entity:'Payroll provider',type:'Outflow',category:'Payroll support',dest:'Payroll / People Cloud',match:'Match payroll run',status:'Staged'}, {date:'2026-05-04',desc:'Inventory supplier invoice',amount:-720,entity:'Wholesale supplier',type:'Outflow',category:'Inventory / COGS',dest:'AP / Vendor Bills',match:'Verify inventory received',status:'Review'}, {date:'2026-05-05',desc:'Internal transfer to tax reserve',amount:-500,entity:'Company transfer',type:'Transfer',category:'Internal transfer',dest:'Tax Reserve / not expense',match:'Do not duplicate as expense',status:'Linked'}, {date:'2026-05-06',desc:'Client ACH payment',amount:900,entity:'Client payment',type:'Inflow',category:'Sales / service income',dest:'Daily Shift Balance',match:'Open invoice / receipt match',status:'Ready'} ].map(x=>Object.assign({branch:base},x)); } function branchRegisterSampleRows(b){ return [ {id:'REG-01',name:'Front Register',provider:'POS/app or manual close',mode:'Automatic / Upload fallback',cash:420,card:1200,tax:112,status:'Connected / ready'}, {id:'REG-02',name:'Second Register',provider:'POS/app or manual close',mode:'Auto + Review',cash:310,card:980,tax:89,status:'Review shift close'}, {id:'APP-01',name:'Online / App Orders',provider:'Online ordering / merchant',mode:'Automatic',cash:0,card:433.35,tax:28.35,status:'Staged by Sofía'} ]; } function branchEntryExitRows(b){ let bank=branchBankSampleRows(b), regs=branchRegisterSampleRows(b); let posIncome=regs.reduce((s,r)=>s+Number(r.cash||0)+Number(r.card||0),0); return [ {area:'POS/Register inflows',source:'Registers + online/app',inflow:posIncome,outflow:0,destination:'Daily Shift Balance',status:'Collected inside branch cloud'}, {area:'Bank deposits',source:'Branch bank/deposit source',inflow:bank.filter(x=>x.amount>0).reduce((s,x)=>s+x.amount,0),outflow:0,destination:'Bank Match',status:'Match to POS/cash/invoices'}, {area:'Local expenses',source:'Receipts / vendor payments',inflow:0,outflow:bank.filter(x=>x.amount<0 && x.type!=='Transfer').reduce((s,x)=>s+Math.abs(x.amount),0),destination:'Branch expense folder',status:'Evidence required'}, {area:'Internal transfers',source:'Savings / tax reserve / payroll bank',inflow:0,outflow:500,destination:'Transfer match',status:'Not income / not expense'}, {area:'Corrections / disputes',source:'Refunds, chargebacks, reversals',inflow:0,outflow:0,destination:'Correction note',status:'Original stays preserved'} ]; } function branchMoneyClass(v){return Number(v)>=0?'moneyIn':'moneyOut'} function bankRowsHtml(b){ return branchBankSampleRows(b).map(r=>`${safeV20(r.date)}${safeV20(r.desc)}${money(r.amount)}${safeV20(r.entity)}${safeV20(r.type)}${safeV20(r.category)}${safeV20(r.dest)}${safeV20(r.match)}${safeV20(r.status)}`).join(''); } function registerRowsHtml(b){ return branchRegisterSampleRows(b).map(r=>`${safeV20(r.id)}${safeV20(r.name)}${safeV20(r.provider)}${safeV20(r.mode)}${money(r.cash)}${money(r.card)}${money(r.tax)}${safeV20(r.status)}`).join(''); } function entryExitRowsHtml(b){ return branchEntryExitRows(b).map(r=>`${safeV20(r.area)}${safeV20(r.source)}${money(r.inflow)}${money(r.outflow)}${safeV20(r.destination)}${safeV20(r.status)}`).join(''); } function branchSourceCardsHtml(b){ return `
1 • Bank / Statement CopyConnect, upload, preserve

Branch statement copy, bank/card connection, deposits, expenses, fees and transfer lines.

Connect bank/cardUpload PDF/CSVSave original
2 • POS / Register NetworkRegisters + machines

Each register/POS/app is a source. API when available, upload report or manual close as fallback.

Add registerPOS reportDaily close
3 • Entries / ExitsLocal inflow + outflow breakdown

All local branch money movement before it becomes the Daily Shift Balance.

IncomeExpensesTransfersCorrections
4 • Daily Shift BalanceDaily result for this branch

Clean daily balance connects POS, cash, bank deposits, fees, tax, local expenses and evidence.

DailyWeeklyMonthly
5 • Monthly Summary to MainTotals only

Branch details stay in the cloud. Main receives clean totals for ledger and tax-ready records.

Send to MainLedgerTax-ready
Other / CustomMissing source or special report

Use this when a bank source, register, department, report type or correction is missing.

Add noteUpload supportWebGo review
`; } function branchSubTop(b, title){ return `
${safeV20(b.id)} • ${safeV20(title)}${safeV20(b.name)} • details stay in this branch cloud until totals are sent to Main.
`; } function openBranchStatementCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let rows=branchBankSampleRows(b), inflow=rows.filter(x=>x.amount>0).reduce((s,x)=>s+x.amount,0), out=rows.filter(x=>x.amount<0).reduce((s,x)=>s+Math.abs(x.amount),0); let html=`${branchSubTop(b,'Bank / Statement Breakdown')}
Bank source rule: this branch can use its own bank/deposit source folder. The original monthly statement copy stays preserved here; Sofía creates the accounting distribution, then monthly totals move to Company Main.
Monthly statementMay 2026Original copy preserved
Total deposits${money(inflow)}Bank inflows
Total exits${money(out)}Expenses/transfers
Net bank activity${money(inflow-out)}Before final review
Important: transfers between company accounts are not income or expenses. Credit card payments, tax reserve moves and payroll bank moves must be matched, not duplicated.
${bankRowsHtml(b)}
DateDescriptionAmountDetected EntityTypeCategoryDestinationMatch / EvidenceStatus
`; openCloud('Branch Bank Statement Cloud: '+b.name, html); } function openBranchRegisterCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let regs=branchRegisterSampleRows(b), cash=regs.reduce((s,r)=>s+r.cash,0), card=regs.reduce((s,r)=>s+r.card,0), tax=regs.reduce((s,r)=>s+r.tax,0); let html=`${branchSubTop(b,'POS / Register Network')}
Register rule: register setup is optional. If the client has register number, serial number or internal terminal ID, WebGo creates a register network line. API/webhook is preferred; upload and manual close remain fallback options.
Registers / POS${regs.length}Branch sources
Cash${money(cash)}Shift cash
Card${money(card)}Merchant batches
Tax${money(tax)}Collected support
${registerRowsHtml(b)}
Register IDNameProviderConnection ModeCashCardTaxStatus
`; openCloud('Branch Register / POS Cloud: '+b.name, html); } function openBranchEntriesCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let rows=branchEntryExitRows(b), inflow=rows.reduce((s,r)=>s+Number(r.inflow||0),0), out=rows.reduce((s,r)=>s+Number(r.outflow||0),0); let html=`${branchSubTop(b,'Entries / Exits Breakdown')}
Branch activity rule: this cloud stores local entries and exits only. People Cloud, payroll master, contractors and final tax-ready totals remain in Company Main.
Local inflows${money(inflow)}
Local outflows${money(out)}
Branch net${money(inflow-out)}
Review mode${safeV20(b.mode)}
${entryExitRowsHtml(b)}
AreaSourceInflowOutflowDestinationStatus
Correction rule: complaints, refunds, chargebacks, bank reversals or returned money create an adjustment note linked to the original record. The original is not erased.
`; openCloud('Branch Entries / Exits Cloud: '+b.name, html); } function openBranchDailyBalanceCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let regs=branchRegisterSampleRows(b), cash=regs.reduce((s,r)=>s+r.cash,0), card=regs.reduce((s,r)=>s+r.card,0), tax=regs.reduce((s,r)=>s+r.tax,0), bank=branchBankSampleRows(b), localOut=bank.filter(x=>x.amount<0 && x.type!=='Transfer').reduce((s,x)=>s+Math.abs(x.amount),0), fees=78.10, refunds=65, gross=cash+card+refunds; let net=gross-refunds-fees-localOut; let html=`${branchSubTop(b,'Daily Shift Balance')}
Daily Shift Balance: screen shows summary, cloud keeps detail. This is the clean daily result that later becomes the monthly branch summary.
Gross activity${money(gross)}
Cash${money(cash)}
Card${money(card)}
Tax support${money(tax)}
Refunds${money(refunds)}
Fees${money(fees)}
Local outflows${money(localOut)}
Net branch result${money(net)}
StepSourceAmountFolder / destinationEvidenceStatus
1POS/register activity${money(gross)}Daily raw folderPOS report / register closeStaged
2Cash drawer${money(cash)}Cash ControlCash count / deposit proofReview
3Card merchant batch${money(card)}Bank MatchMerchant depositMatch pending
4Branch expenses${money(localOut)}Expense folderReceipts / statement linesReview before post
5Sales tax support${money(tax)}Tax ReservePOS tax linesLiability review
6Daily net balance${money(net)}Monthly Branch SummaryDaily folder linkedReady to roll up
`; openCloud('Branch Daily Shift Balance: '+b.name, html); } function openBranchMainSummaryCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let income=Number(b.income)||0, expenses=Number(b.expenses)||0, cash=Number(b.cash)||0, card=Number(b.card)||0, tax=Number(b.tax)||0, net=income-expenses; let html=`${branchSubTop(b,'Monthly Summary → Company Main')}
Main roll-up rule: Branch details stay in the cloud. Company Main receives clean monthly totals only, then builds the Main Ledger and CPA/Tax-ready package.
Main lineTotalMain destinationDetail folderStatus
Branch income total${money(income)}Company Main Ledger / incomeBranch inflow foldersReady
Branch expense total${money(expenses)}Company Main Ledger / deductionsBranch outflow foldersReady
Cash activity${money(cash)}Cash Control summaryCash drawer foldersLinked
Card / merchant activity${money(card)}Bank Match / depositsPOS + merchant foldersLinked
Sales tax support${money(tax)}Tax Reserve / liability reviewPOS tax supportLinked
Net branch result${money(net)}Consolidated company totalsMonthly Branch Folder${b.sentToMain?'Sent':'Ready to send'}
`; openCloud('Branch Monthly Summary: '+b.name, html); } function openBranchOtherCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; let html=`${branchSubTop(b,'Other / Custom Branch Need')}
Use this when something is missing.
The client can report it, upload support, add a note, or WebGo can attach a new local source step by step.
Possible missing itemsNew bank source, extra register, local cash drawer, storage room, department, seasonal booth, delivery app, special report.
ProcessingAutomatic | Auto + Review | Manual Override

Any correction must create a note and preserve the original.

Main ruleDo not duplicate Company Main

Create it inside this Branch Cloud and send monthly totals to Main.

`; openCloud('Branch Other / Custom: '+b.name, html); } /* Override previous branch cloud with full bank/POS/details breakdown */ function openBranchCloud(id){ let b=branchById(id||selectedBranchId); if(!b)return; selectedBranchId=b.id; let income=Number(b.income)||0, expenses=Number(b.expenses)||0, net=income-expenses; let html=`
${safeV20(b.id)} • ${safeV20(b.name)}Independent Branch Cloud workspace. Full local detail stays here; Main receives totals only.
Branch Cloud${safeV20(b.id)}${safeV20(b.name)}
Monthly inflows${money(income)}Local entries only
Monthly outflows${money(expenses)}Local exits only
Net to Main${money(net)}${safeV20(b.status)}
Branch detail rule: use this cloud to connect/import bank statements, POS/register machines, cash drawers, local expenses and daily evidence. After review, only clean monthly totals go to Company Main for bookkeeping and taxes.
Branch profile${safeV20(b.name)}

Category: ${safeV20(b.category)} • Manager: ${safeV20(b.manager)} • Address: ${safeV20(b.address)}

Bank / deposit source${safeV20(b.bank)}

Open the bank breakdown to connect, upload statement, import lines, match deposits and preserve original monthly copy.

POS / registers${safeV20(b.registers)} local source(s)

Each register/app can be API, upload report or manual close. All feed Daily Shift Balance.

Processing mode${safeV20(b.mode)}

Automatic first, with review and manual override for corrections.

Branch Source Breakdown

${branchSourceCardsHtml(b)}

Branch Detail Folders

${branchDailyRows(b).replace(/<\/tr>/g,``)}
PeriodSourceFolderInflowsOutflowsStatus / noteOpen

Monthly Branch Summary → Main

${branchMonthlySummaryRows(b)}
Summary lineTotalMain destination
Other / Custom: if this branch has something not listed, add details, upload a report, or let WebGo attach it step by step. Do not change the Company Main; create the source inside this Branch Cloud.
${safeV20(b.other||'No custom note')}
Branch detail stays inside this cloud. Close it to return to Company / Branch Control.
`; openCloud('Branch Cloud: '+b.name, html); } function renderAll(){renderPeopleCloudCommand();renderBranchControl();renderScanQueue();renderMobileQueue();updatePOSConnector();renderPOSApp();renderIntake();renderDispatcher();renderBank();renderRecordsArchive();renderAccountsCenter();renderGovernmentRecords();renderTaxReserve();renderTaxDeductions();renderPayrollBenefits();renderQuickBooks();renderCarousel();renderCash();renderVoice();updateHealth();enhanceVerticalDataCarousels()} function seedDemo(){loadDocumentDemo();runDispatcher();loadBankDemo();autoMatchReceipts();loadCashDemo();loadPOSDemo();createRawExcelFolder();sendPOSToSofia(true);loadAccountsDemo();distributeDebtPayments();loadTaxReserveDemo();loadTaxDeductionDemo();loadPayrollBenefitsDemo();loadTimeClockDemo();sendPayrollToReserve();voiceGasDemo();activeTab='Payroll';renderAll()} applyPlan();seedDemo();