Milagros Solórzano Un proyecto de Milagros Solórzano
💻 Código de demostración

RegistroBot

El script que reemplaza el registro manual, uno por uno, de operaciones repetitivas en un portal web — adaptado aquí a un portal ficticio para poder compartirlo sin exponer nada real.

Python Playwright pandas

Cómo funciona

1

Lee y valida

Carga el Excel del periodo y confirma que exista el documento de sustento de cada fila antes de abrir el navegador.

2

Llena y envía

Abre el portal, completa el formulario y adjunta el archivo correspondiente para cada registro, en orden.

3

Verifica o se detiene

Busca la confirmación exacta del código esperado. Si no la encuentra, el proceso se detiene — nunca reintenta a ciegas.

El código

Esta es una versión simplificada, adaptada para funcionar contra un portal ficticio propio — no contra ningún sistema real. Conserva la misma lógica de validación y verificación del proceso original.


"""
DEMO PARA VIDEO / LINKEDIN — datos 100% ficticios.
Este script NO toca ningún sistema real. Corre contra portal_demo.html,
una página local hecha solo para esta demostración.

Cómo usarlo:
1. Deja este archivo y portal_demo.html en la misma carpeta, junto con
   la carpeta dummy_pdfs/.
2. Activa tu entorno virtual (venv\\Scripts\\activate).
3. Corre: python demo_registro.py -- esto abre Chromium y el script
   se queda ESPERANDO (no hace nada todavía).
4. Recién ahí abre el Bloc de recortes (Snipping Tool), modo grabación
   de video, dibuja el área a grabar (la ventana de Chromium) y dale
   Iniciar.
5. Vuelve a la consola y presiona Enter -- ahí arranca la automatización.
6. Cuando termine y Chromium se cierre solo, detén la grabación.
"""

import os
import time
from playwright.sync_api import sync_playwright

CARPETA = os.path.dirname(os.path.abspath(__file__))
RUTA_PORTAL = "file://" + os.path.join(CARPETA, "portal_demo.html")

BANCOS = ["Banco Alfa", "Banco Beta", "Banco Gamma", "Banco Delta"]

# Datos 100% ficticios, generados solo para esta demo.
LOTE_DEMO = []
for i in range(1, 13):
    codigo = f"DEMO-2609-{i:03d}"
    LOTE_DEMO.append({
        "codigo": codigo,
        "fecha": "2026-09-04",
        "banco": BANCOS[(i - 1) % len(BANCOS)],
        "moneda": "SOL" if i % 2 == 0 else "USD",
        "cuenta": f"00{i:02d}-000{i}-11-22334455{i:02d}",
        "motivo": f"NUEVO DEPÓSITO {BANCOS[(i - 1) % len(BANCOS)]}",
        "pdf": os.path.join(CARPETA, "dummy_pdfs", f"{codigo}.pdf"),
    })

print(f"Lote de demostración: {len(LOTE_DEMO)} registros ficticios\n")

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, args=["--start-maximized"])
    page = browser.new_page(no_viewport=True)
    page.goto(RUTA_PORTAL)

    input(
        "\n>>> El navegador ya está abierto y visible. "
        "Haz clic sobre esa ventana para que quede enfocada, "
        "empieza tu grabación de pantalla (Win + Alt + R), "
        "y RECIÉN AHÍ vuelve aquí y presiona Enter para arrancar...\n"
    )

    for idx, fila in enumerate(LOTE_DEMO):
        page.click("#btn-nuevo")
        time.sleep(0.4)

        page.fill("#fecha", fila["fecha"])
        page.select_option("#banco", fila["banco"])
        page.select_option("#moneda", fila["moneda"])
        page.fill("#cuenta", fila["cuenta"])
        page.fill("#motivo", fila["motivo"])
        time.sleep(0.3)

        page.set_input_files("#archivo", fila["pdf"])
        time.sleep(0.3)
        print(f"✅ Documento adjuntado: {os.path.basename(fila['pdf'])}")

        page.click("#btn-enviar")
        time.sleep(0.3)
        page.click("#btn-si")
        time.sleep(0.6)

        codigo_generado = page.evaluate("window.__ultimoCodigo")
        if codigo_generado != fila["codigo"]:
            print(f"⚠️ No coincide: esperado {fila['codigo']}, obtenido {codigo_generado}")
            break

        print(f"✅ CONFIRMADO: código {codigo_generado} enviado correctamente")
        print(f"🎉 Registro {idx + 1}/{len(LOTE_DEMO)} completado\n")
        time.sleep(0.3)

    print("✅ Lote de demostración finalizado.")
    time.sleep(2)
    browser.close()
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Portal de Registro de Operaciones — ENTORNO DE DEMOSTRACIÓN</title>
<style>
  :root {
    --azul: #0b3d63;
    --azul-claro: #eaf2f8;
    --verde: #1e8449;
    --borde: #cfd8dc;
  }
  * { box-sizing: border-box; }
  body {
    font-family: "Segoe UI", Arial, sans-serif;
    background: #f4f6f8;
    margin: 0;
    color: #1c2833;
  }
  header {
    background: var(--azul);
    color: white;
    padding: 18px 28px;
  }
  header h1 { margin: 0; font-size: 20px; }
  header .tag {
    display: inline-block;
    margin-top: 6px;
    background: #d68910;
    color: #1c2833;
    font-size: 12px;
    font-weight: bold;
    padding: 2px 10px;
    border-radius: 12px;
  }
  main {
    max-width: 780px;
    margin: 24px auto;
    background: white;
    border: 1px solid var(--borde);
    border-radius: 6px;
    padding: 24px 28px;
  }
  .barra-superior {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 18px;
  }
  .contador {
    background: var(--azul-claro);
    padding: 8px 16px;
    border-radius: 6px;
    font-weight: bold;
    color: var(--azul);
  }
  button {
    background: var(--azul);
    color: white;
    border: none;
    padding: 10px 18px;
    border-radius: 5px;
    font-size: 14px;
    cursor: pointer;
  }
  button:hover { background: #08304d; }
  button.secundario {
    background: white;
    color: var(--azul);
    border: 1px solid var(--azul);
  }
  table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 10px;
    font-size: 13px;
  }
  th, td {
    text-align: left;
    padding: 8px 6px;
    border-bottom: 1px solid var(--borde);
  }
  th { color: #566573; font-weight: 600; }
  .vacio { color: #909497; font-style: italic; padding: 14px 0; }

  #formulario { display: none; margin-top: 10px; }
  .campo { margin-bottom: 14px; }
  .campo label {
    display: block;
    font-size: 13px;
    font-weight: 600;
    margin-bottom: 4px;
    color: #34495e;
  }
  .campo input, .campo select {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--borde);
    border-radius: 4px;
    font-size: 14px;
  }
  .fila-doble { display: flex; gap: 16px; }
  .fila-doble .campo { flex: 1; }

  #overlay {
    display: none;
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.35);
    align-items: center; justify-content: center;
  }
  #dialogo {
    background: white;
    padding: 22px 26px;
    border-radius: 8px;
    max-width: 360px;
    text-align: center;
  }
  #dialogo p { margin-top: 0; }
  #dialogo .acciones { display: flex; gap: 10px; justify-content: center; margin-top: 16px; }

  #toast {
    display: none;
    position: fixed;
    bottom: 24px; right: 24px;
    background: #eafaf1;
    border: 1px solid var(--verde);
    color: #145a32;
    padding: 14px 18px;
    border-radius: 6px;
    max-width: 360px;
    font-size: 13px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.15);
  }
  #toast b { display: block; margin-bottom: 4px; }

  footer {
    text-align: center;
    color: #909497;
    font-size: 12px;
    margin: 30px 0;
  }
</style>
</head>
<body>

<header>
  <h1>Portal de Registro de Operaciones</h1>
  <span class="tag">ENTORNO DE DEMOSTRACIÓN — DATOS FICTICIOS</span>
</header>

<main>
  <div class="barra-superior">
    <div class="contador">Registros completados: <span id="contador">0</span></div>
    <button id="btn-nuevo">Nuevo registro</button>
  </div>

  <div id="listado">
    <table>
      <thead>
        <tr><th>#</th><th>Código</th><th>Entidad</th><th>Estado</th></tr>
      </thead>
      <tbody id="cuerpo-tabla"></tbody>
    </table>
    <div id="vacio" class="vacio">No se ha encontrado ningún registro.</div>
  </div>

  <form id="formulario">
    <div class="fila-doble">
      <div class="campo">
        <label for="fecha">Fecha de operación</label>
        <input type="date" id="fecha" />
      </div>
      <div class="campo">
        <label for="moneda">Moneda</label>
        <select id="moneda">
          <option value="SOL">Soles</option>
          <option value="USD">Dólares</option>
        </select>
      </div>
    </div>
    <div class="fila-doble">
      <div class="campo">
        <label for="banco">Entidad financiera</label>
        <select id="banco">
          <option value="Banco Alfa">Banco Alfa</option>
          <option value="Banco Beta">Banco Beta</option>
          <option value="Banco Gamma">Banco Gamma</option>
          <option value="Banco Delta">Banco Delta</option>
        </select>
      </div>
      <div class="campo">
        <label for="cuenta">Número de cuenta</label>
        <input type="text" id="cuenta" />
      </div>
    </div>
    <div class="campo">
      <label for="motivo">Motivo</label>
      <input type="text" id="motivo" />
    </div>
    <div class="campo">
      <label for="archivo">Adjuntar documento sustentatorio</label>
      <input type="file" id="archivo" />
      <div id="nombre-archivo" style="font-size:12px;color:#1e8449;margin-top:4px;"></div>
    </div>
    <button type="button" id="btn-enviar">Guardar y Enviar</button>
    <button type="button" class="secundario" id="btn-cancelar">Cancelar</button>
  </form>
</main>

<footer>Portal de Registro de Operaciones · Entorno de demostración · v-DEMO</footer>

<div id="overlay">
  <div id="dialogo">
    <p>¿Confirma el envío de este registro?</p>
    <div class="acciones">
      <button id="btn-si">Sí</button>
      <button class="secundario" id="btn-no">No</button>
    </div>
  </div>
</div>

<div id="toast"></div>

<script>
let contador = 0;
let siguienteCodigo = 1;

const btnNuevo = document.getElementById('btn-nuevo');
const formulario = document.getElementById('formulario');
const listado = document.getElementById('listado');
const overlay = document.getElementById('overlay');
const toast = document.getElementById('toast');
const cuerpoTabla = document.getElementById('cuerpo-tabla');
const vacio = document.getElementById('vacio');

btnNuevo.addEventListener('click', () => {
  listado.style.display = 'none';
  formulario.style.display = 'block';
  formulario.reset();
  document.getElementById('nombre-archivo').textContent = '';
});

document.getElementById('btn-cancelar').addEventListener('click', () => {
  formulario.style.display = 'none';
  listado.style.display = 'block';
});

document.getElementById('archivo').addEventListener('change', (e) => {
  const f = e.target.files[0];
  document.getElementById('nombre-archivo').textContent = f ? ('Archivo adjuntado: ' + f.name) : '';
});

document.getElementById('btn-enviar').addEventListener('click', () => {
  overlay.style.display = 'flex';
});

document.getElementById('btn-no').addEventListener('click', () => {
  overlay.style.display = 'none';
});

document.getElementById('btn-si').addEventListener('click', () => {
  overlay.style.display = 'none';

  const codigo = 'DEMO-2609-' + String(siguienteCodigo).padStart(3, '0');
  siguienteCodigo++;
  const banco = document.getElementById('banco').value;

  contador++;
  document.getElementById('contador').textContent = contador;

  const fila = document.createElement('tr');
  fila.innerHTML = `<td>${contador}</td><td>${codigo}</td><td>${banco}</td><td>Enviado</td>`;
  cuerpoTabla.prepend(fila);
  vacio.style.display = 'none';

  toast.innerHTML = `<b>Éxito</b>El registro con código ${codigo} ha sido enviado correctamente.`;
  toast.style.display = 'block';
  setTimeout(() => { toast.style.display = 'none'; }, 4000);

  formulario.style.display = 'none';
  listado.style.display = 'block';

  window.__ultimoCodigo = codigo;
});
</script>

</body>
</html>
⚠️ Nota de confidencialidad: el portal, los códigos, los bancos y los documentos que ves aquí son 100% ficticios, generados solo para esta demostración. La versión real de este script trabaja contra el portal de un regulador financiero con datos internos de la empresa y no se comparte por confidencialidad — esta es una réplica fiel de la lógica, no del sistema.