modified: database.py

new file:   generar_unificado.py
	modified:   routes_admin.py
	modified:   routes_worker.py
	modified:   templates/admin_rendiciones.html
	new file:   templates/admin_report_horarios.html
	modified:   templates/macros/modals.html
	modified:   templates/worker_dashboard.html
	modified:   templates/worker_history.html
This commit is contained in:
2026-03-25 05:38:17 -03:00
parent cd576fbf1e
commit 46bfb93566
9 changed files with 484 additions and 42 deletions

View File

@@ -163,6 +163,10 @@ def init_db():
venta_credito INTEGER DEFAULT 0, venta_credito INTEGER DEFAULT 0,
venta_mp INTEGER DEFAULT 0, venta_mp INTEGER DEFAULT 0,
venta_efectivo INTEGER DEFAULT 0, venta_efectivo INTEGER DEFAULT 0,
boletas_debito INTEGER DEFAULT 0,
boletas_credito INTEGER DEFAULT 0,
boletas_mp INTEGER DEFAULT 0,
boletas_efectivo INTEGER DEFAULT 0,
gastos INTEGER DEFAULT 0, gastos INTEGER DEFAULT 0,
observaciones TEXT, observaciones TEXT,
FOREIGN KEY (worker_id) REFERENCES workers(id), FOREIGN KEY (worker_id) REFERENCES workers(id),

185
generar_unificado.py Normal file
View File

@@ -0,0 +1,185 @@
import random
from datetime import date, timedelta
from werkzeug.security import generate_password_hash
from database import get_db_connection, init_db
def generar_historico_definitivo(dias_atras=180):
init_db()
conn = get_db_connection()
c = conn.cursor()
# 1. LIMPIEZA TOTAL (Evita el choque con los datos por defecto de database.py)
print("Limpiando datos de prueba anteriores...")
c.execute("DELETE FROM rendicion_items")
c.execute("DELETE FROM rendiciones")
c.execute("DELETE FROM workers WHERE is_admin = 0")
conn.commit()
c.execute("SELECT id, name FROM modulos")
modulos = c.fetchall()
if not modulos:
print("Error: No hay módulos creados.")
return
# 2. RECLUTAMIENTO FORZADO PARA TODOS LOS MÓDULOS
print(f"Reclutando personal para {len(modulos)} módulos...")
default_pass = generate_password_hash("123456")
workers_data = []
for mod_id, mod_name in modulos:
tipos = ["Full Time", "Full Time", "Part Time", "Part Time"]
for i in range(4):
# Usamos un RUT fijo basado en la iteración para no inflar la DB si lo corres sin limpiar
rut_falso = f"{10 + i}.{mod_id:03d}.100-{i}"
nombre_falso = f"Trabajador {i+1} ({mod_name})"
phone_falso = f"+56 9 8888 {mod_id:02d}{i:02d}"
workers_data.append((
rut_falso, nombre_falso, phone_falso,
default_pass, 0, mod_id, tipos[i]
))
c.executemany('''INSERT OR IGNORE INTO workers
(rut, name, phone, password_hash, is_admin, modulo_id, tipo)
VALUES (?, ?, ?, ?, ?, ?, ?)''', workers_data)
conn.commit()
# 3. PREPARACIÓN DE DATOS
c.execute("SELECT id, modulo_id FROM workers WHERE is_admin = 0")
all_workers_data = c.fetchall()
todos_los_trabajadores = [w[0] for w in all_workers_data]
trabajadores_por_modulo = {}
for w_id, m_id in all_workers_data:
if m_id not in trabajadores_por_modulo:
trabajadores_por_modulo[m_id] = []
trabajadores_por_modulo[m_id].append(w_id)
c.execute("SELECT id, price, commission FROM productos")
productos = c.fetchall()
# 4. VIAJE EN EL TIEMPO
hoy = date.today()
fecha_inicio = hoy - timedelta(days=dias_atras)
rendiciones_creadas = 0
# Textos de ejemplo para los gastos
motivos_gastos = [
"Compra bidón de agua",
"Artículos de aseo",
"Lápices y cuaderno",
"Reparación menor del módulo",
"Bolsas para entregar productos",
"Cinta adhesiva",
"Pilas para escáner"
]
print(f"Generando turnos con gastos aleatorios desde {fecha_inicio} hasta {hoy}...")
for i in range(dias_atras + 1):
fecha_actual = fecha_inicio + timedelta(days=i)
fecha_str = fecha_actual.strftime('%Y-%m-%d')
for modulo_id, mod_name in modulos:
workers_modulo = trabajadores_por_modulo.get(modulo_id, [])
if not workers_modulo:
continue
num_turnos = random.randint(1, 2)
turnos_a_hacer = [True, False] if num_turnos == 2 else [random.choice([True, False])]
for es_manana in turnos_a_hacer:
# Reemplazos (15%)
es_reemplazo = random.random() < 0.15
if es_reemplazo and len(todos_los_trabajadores) > len(workers_modulo):
posibles_reemplazos = [w for w in todos_los_trabajadores if w not in workers_modulo]
worker_id = random.choice(posibles_reemplazos)
else:
worker_id = random.choice(workers_modulo)
if es_manana:
hora_entrada = f"{random.randint(8, 10):02d}:{random.choice(['00', '30'])}"
hora_salida = f"{random.randint(14, 16):02d}:{random.choice(['00', '30'])}"
else:
hora_entrada = f"{random.randint(13, 15):02d}:{random.choice(['00', '30'])}"
hora_salida = f"{random.randint(19, 21):02d}:{random.choice(['00', '30'])}"
# Acompañante (70%)
companion_id = None
comp_in, comp_out = None, None
if random.random() < 0.70:
posibles_comp = [w for w in todos_los_trabajadores if w != worker_id]
if posibles_comp:
companion_id = random.choice(posibles_comp)
comp_in, comp_out = hora_entrada, hora_salida
num_prods = random.randint(1, 5)
prods_elegidos = random.sample(productos, min(num_prods, len(productos)))
items_a_insertar = []
total_calculado = 0
for prod in prods_elegidos:
p_id, p_price, p_comm = prod
cantidad = random.randint(1, 6)
items_a_insertar.append((p_id, cantidad, p_price, p_comm))
total_calculado += (p_price * cantidad)
debito, credito, mp, efectivo = 0, 0, 0, 0
b_debito, b_credito, b_mp, b_efectivo = 0, 0, 0, 0
divisiones = random.randint(1, 3)
monto_restante = int(total_calculado)
metodos = ["debito", "credito", "mp", "efectivo"]
random.shuffle(metodos)
for idx, metodo in enumerate(metodos[:divisiones]):
monto = monto_restante if idx == divisiones - 1 else random.randint(0, monto_restante // 2)
monto_restante -= monto
if monto > 0:
boletas = random.randint(1, 4)
if metodo == "debito": debito, b_debito = monto, boletas
elif metodo == "credito": credito, b_credito = monto, boletas
elif metodo == "mp": mp, b_mp = monto, boletas
elif metodo == "efectivo": efectivo, b_efectivo = monto, boletas
tipo_registro = "Reemplazo histórico" if es_reemplazo else "Turno histórico"
# === LÓGICA DE GASTOS RANDOM ===
# 15% de probabilidad de tener un gasto en el turno (entre $2.000 y $15.000)
gastos = 0
if random.random() < 0.15:
gastos = random.randint(2, 15) * 1000
tipo_registro += f" | {random.choice(motivos_gastos)}"
c.execute('''
INSERT INTO rendiciones
(worker_id, companion_id, modulo_id, fecha, hora_entrada, hora_salida,
companion_hora_entrada, companion_hora_salida,
venta_debito, venta_credito, venta_mp, venta_efectivo,
boletas_debito, boletas_credito, boletas_mp, boletas_efectivo,
gastos, observaciones, worker_comision, companion_comision)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)
''', (worker_id, companion_id, modulo_id, fecha_str, hora_entrada, hora_salida,
comp_in, comp_out, debito, credito, mp, efectivo,
b_debito, b_credito, b_mp, b_efectivo,
gastos, tipo_registro, 1 if companion_id else 0))
r_id = c.lastrowid
for item in items_a_insertar:
p_id, cant, p_price, p_comm = item
c.execute('''
INSERT INTO rendicion_items (rendicion_id, producto_id, cantidad, precio_historico, comision_historica)
VALUES (?, ?, ?, ?, ?)
''', (r_id, p_id, cant, p_price, p_comm))
rendiciones_creadas += 1
conn.commit()
conn.close()
print(f"Éxito: Se inyectaron {rendiciones_creadas} rendiciones para TODOS los módulos.")
if __name__ == '__main__':
generar_historico_definitivo(180)

View File

@@ -1,9 +1,10 @@
import sqlite3 import sqlite3
from flask import render_template, request, redirect, url_for, flash, session from flask import app, render_template, request, redirect, url_for, flash, session
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from datetime import date from datetime import date, datetime
from database import get_db_connection from database import get_db_connection
from utils import admin_required, validate_rut, format_rut, validate_phone, format_phone, generate_random_password from utils import admin_required, validate_rut, format_rut, validate_phone, format_phone, generate_random_password
import calendar
def register_admin_routes(app): def register_admin_routes(app):
@app.route('/admin/workers', methods=['GET', 'POST']) @app.route('/admin/workers', methods=['GET', 'POST'])
@@ -299,16 +300,17 @@ def register_admin_routes(app):
c = conn.cursor() c = conn.cursor()
c.execute(''' c.execute('''
SELECT r.id, r.fecha, w.name, m.name, SELECT r.id, r.fecha, w.name, m.name,
r.venta_debito, r.venta_credito, r.venta_mp, r.venta_efectivo, r.gastos, r.observaciones, r.venta_debito, r.venta_credito, r.venta_mp, r.venta_efectivo, r.gastos, r.observaciones,
c_w.name, r.worker_id, r.companion_id, r.modulo_id, c_w.name, r.worker_id, r.companion_id, r.modulo_id,
r.worker_comision, r.companion_comision r.worker_comision, r.companion_comision,
FROM rendiciones r r.boletas_debito, r.boletas_credito, r.boletas_mp, r.boletas_efectivo
JOIN workers w ON r.worker_id = w.id FROM rendiciones r
JOIN modulos m ON r.modulo_id = m.id JOIN workers w ON r.worker_id = w.id
LEFT JOIN workers c_w ON r.companion_id = c_w.id JOIN modulos m ON r.modulo_id = m.id
ORDER BY r.fecha DESC, r.id DESC LEFT JOIN workers c_w ON r.companion_id = c_w.id
''') ORDER BY r.fecha DESC, r.id DESC
''')
rendiciones_basicas = c.fetchall() rendiciones_basicas = c.fetchall()
rendiciones_completas = [] rendiciones_completas = []
@@ -388,6 +390,12 @@ def register_admin_routes(app):
credito = clean_money(request.form.get('venta_credito')) credito = clean_money(request.form.get('venta_credito'))
mp = clean_money(request.form.get('venta_mp')) mp = clean_money(request.form.get('venta_mp'))
efectivo = clean_money(request.form.get('venta_efectivo')) efectivo = clean_money(request.form.get('venta_efectivo'))
bol_debito = int(request.form.get('boletas_debito') or 0)
bol_credito = int(request.form.get('boletas_credito') or 0)
bol_mp = int(request.form.get('boletas_mp') or 0)
bol_efectivo = int(request.form.get('boletas_efectivo') or 0)
gastos = clean_money(request.form.get('gastos')) gastos = clean_money(request.form.get('gastos'))
observaciones = request.form.get('observaciones', '').strip() observaciones = request.form.get('observaciones', '').strip()
@@ -409,10 +417,12 @@ def register_admin_routes(app):
UPDATE rendiciones UPDATE rendiciones
SET fecha=?, worker_id=?, modulo_id=?, companion_id=?, SET fecha=?, worker_id=?, modulo_id=?, companion_id=?,
venta_debito=?, venta_credito=?, venta_mp=?, venta_efectivo=?, venta_debito=?, venta_credito=?, venta_mp=?, venta_efectivo=?,
boletas_debito=?, boletas_credito=?, boletas_mp=?, boletas_efectivo=?,
gastos=?, observaciones=?, worker_comision=?, companion_comision=? gastos=?, observaciones=?, worker_comision=?, companion_comision=?
WHERE id=? WHERE id=?
''', (fecha, worker_id, modulo_id, companion_id, ''', (fecha, worker_id, modulo_id, companion_id,
debito, credito, mp, efectivo, debito, credito, mp, efectivo,
bol_debito, bol_credito, bol_mp, bol_efectivo,
gastos, observaciones, worker_comision, companion_comision, id)) gastos, observaciones, worker_comision, companion_comision, id))
conn.commit() conn.commit()
@@ -598,3 +608,103 @@ def register_admin_routes(app):
mes_nombre=f'{mes_actual:02}/{anio_actual}', mes_nombre=f'{mes_actual:02}/{anio_actual}',
workers_data=workers_data, workers_data=workers_data,
dias_en_periodo=dias_en_periodo) dias_en_periodo=dias_en_periodo)
@app.route('/admin/reportes/modulo/<int:modulo_id>/horarios')
@admin_required
def report_modulo_horarios(modulo_id):
import calendar
from datetime import date, datetime
mes_actual = date.today().month
anio_actual = date.today().year
conn = get_db_connection()
c = conn.cursor()
c.execute("SELECT name FROM modulos WHERE id = ?", (modulo_id,))
modulo_info = c.fetchone()
if not modulo_info:
conn.close()
flash("Módulo no encontrado.", "danger")
return redirect(url_for('admin_reportes_index'))
modulo_name = modulo_info[0]
# 1. Pre-cargar a los trabajadores oficiales del módulo (aunque no hayan trabajado aún)
c.execute("SELECT id, name FROM workers WHERE modulo_id = ? AND is_admin = 0", (modulo_id,))
assigned_workers = c.fetchall()
workers_data = {}
for w_id, w_name in assigned_workers:
workers_data[w_id] = {'name': w_name, 'dias': {}, 'total_horas': 0.0}
# 2. Extraer rendiciones del mes/módulo
c.execute('''
SELECT
r.fecha,
w.id, w.name, r.hora_entrada, r.hora_salida,
cw.id, cw.name, r.companion_hora_entrada, r.companion_hora_salida
FROM rendiciones r
JOIN workers w ON r.worker_id = w.id
LEFT JOIN workers cw ON r.companion_id = cw.id
WHERE r.modulo_id = ? AND strftime('%m', r.fecha) = ? AND strftime('%Y', r.fecha) = ?
ORDER BY r.fecha ASC
''', (modulo_id, f'{mes_actual:02}', str(anio_actual)))
rendiciones = c.fetchall()
conn.close()
def calc_horas(in_str, out_str):
if not in_str or not out_str:
return 0.0, "0:00"
try:
t1 = datetime.strptime(in_str, '%H:%M')
t2 = datetime.strptime(out_str, '%H:%M')
delta = t2 - t1
return delta.seconds / 3600, f"{delta.seconds // 3600}:{(delta.seconds % 3600) // 60:02d}"
except ValueError:
return 0.0, "0:00"
for r in rendiciones:
fecha, w_id, w_name, w_in, w_out, c_id, c_name, c_in, c_out = r
dia = fecha[-2:]
# Titular (Si no es del módulo, lo metemos con etiqueta de Apoyo)
if w_id not in workers_data:
workers_data[w_id] = {'name': f"{w_name} (Apoyo)", 'dias': {}, 'total_horas': 0.0}
h_dec, h_str = calc_horas(w_in, w_out)
workers_data[w_id]['dias'][dia] = {'in': w_in, 'out': w_out, 'hrs': h_str}
workers_data[w_id]['total_horas'] += h_dec
# Acompañante
if c_id and c_in and c_out:
if c_id not in workers_data:
workers_data[c_id] = {'name': f"{c_name} (Apoyo)", 'dias': {}, 'total_horas': 0.0}
h_dec, h_str = calc_horas(c_in, c_out)
workers_data[c_id]['dias'][dia] = {'in': c_in, 'out': c_out, 'hrs': h_str}
workers_data[c_id]['total_horas'] += h_dec
for w_id in workers_data:
th = workers_data[w_id]['total_horas']
workers_data[w_id]['total_hrs_str'] = f"{int(th)}:{int(round((th - int(th)) * 60)):02d}"
# Ordenar alfabéticamente (Los de apoyo quedarán entremezclados por orden alfabético)
workers_data = dict(sorted(workers_data.items(), key=lambda x: x[1]['name']))
_, num_dias = calendar.monthrange(anio_actual, mes_actual)
nombres_dias = ['D', 'L', 'M', 'M', 'J', 'V', 'S'] # Ajustado para que el 0 de Python(Lunes) sea coherente si usas isoweekday
dias_en_periodo = []
for d in range(1, num_dias + 1):
dia_semana = date(anio_actual, mes_actual, d).weekday()
dias_en_periodo.append({
'num': f'{d:02}',
'name': ['L', 'M', 'M', 'J', 'V', 'S', 'D'][dia_semana] # weekday(): Lunes es 0, Domingo es 6
})
return render_template('admin_report_horarios.html',
modulo_name=modulo_name,
mes_nombre=f'{mes_actual:02}/{anio_actual}',
workers_data=workers_data,
dias_en_periodo=dias_en_periodo)

View File

@@ -16,7 +16,8 @@ def register_worker_routes(app):
SELECT r.id, r.fecha, w.name, m.name, SELECT r.id, r.fecha, w.name, m.name,
r.venta_debito, r.venta_credito, r.venta_mp, r.venta_efectivo, r.gastos, r.observaciones, r.venta_debito, r.venta_credito, r.venta_mp, r.venta_efectivo, r.gastos, r.observaciones,
c_w.name, r.worker_id, r.companion_id, r.modulo_id, c_w.name, r.worker_id, r.companion_id, r.modulo_id,
r.worker_comision, r.companion_comision r.worker_comision, r.companion_comision,
r.boletas_debito, r.boletas_credito, r.boletas_mp, r.boletas_efectivo
FROM rendiciones r FROM rendiciones r
JOIN workers w ON r.worker_id = w.id JOIN workers w ON r.worker_id = w.id
JOIN modulos m ON r.modulo_id = m.id JOIN modulos m ON r.modulo_id = m.id
@@ -87,6 +88,10 @@ def register_worker_routes(app):
credito = clean_and_validate(request.form.get('venta_credito')) credito = clean_and_validate(request.form.get('venta_credito'))
mp = clean_and_validate(request.form.get('venta_mp')) mp = clean_and_validate(request.form.get('venta_mp'))
efectivo = clean_and_validate(request.form.get('venta_efectivo')) efectivo = clean_and_validate(request.form.get('venta_efectivo'))
bol_debito = int(request.form.get('boletas_debito') or 0)
bol_credito = int(request.form.get('boletas_credito') or 0)
bol_mp = int(request.form.get('boletas_mp') or 0)
bol_efectivo = int(request.form.get('boletas_efectivo') or 0)
gastos = clean_and_validate(request.form.get('gastos')) or 0 gastos = clean_and_validate(request.form.get('gastos')) or 0
obs = request.form.get('observaciones', '').strip() obs = request.form.get('observaciones', '').strip()
companion_id = request.form.get('companion_id') companion_id = request.form.get('companion_id')
@@ -116,10 +121,11 @@ def register_worker_routes(app):
c.execute('''INSERT INTO rendiciones c.execute('''INSERT INTO rendiciones
(worker_id, companion_id, modulo_id, fecha, hora_entrada, hora_salida, companion_hora_entrada, companion_hora_salida, (worker_id, companion_id, modulo_id, fecha, hora_entrada, hora_salida, companion_hora_entrada, companion_hora_salida,
venta_debito, venta_credito, venta_mp, venta_efectivo, gastos, observaciones, worker_comision, companion_comision) venta_debito, venta_credito, venta_mp, venta_efectivo, boletas_debito, boletas_credito, boletas_mp, boletas_efectivo, gastos, observaciones, worker_comision, companion_comision)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
(session['user_id'], companion_id, modulo_id, fecha, hora_entrada, hora_salida, companion_hora_entrada, companion_hora_salida, (session['user_id'], companion_id, modulo_id, fecha, hora_entrada, hora_salida, companion_hora_entrada, companion_hora_salida,
debito, credito, mp, efectivo, gastos, obs, worker_comision, companion_comision)) debito, credito, mp, efectivo, bol_debito, bol_credito, bol_mp, bol_efectivo, gastos, obs, worker_comision, companion_comision))
rendicion_id = c.lastrowid rendicion_id = c.lastrowid
for key, value in request.form.items(): for key, value in request.form.items():

View File

@@ -48,8 +48,8 @@
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#deleteRendicion{{ r[0] }}" title="Eliminar Rendición"><i class="bi bi-trash"></i></button> <button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#deleteRendicion{{ r[0] }}" title="Eliminar Rendición"><i class="bi bi-trash"></i></button>
</div> </div>
{{ rendicion_detail_modal(r, r[16], r[17], r[18]) }} {{ rendicion_detail_modal(r, r[20], r[21], r[22]) }}
{{ edit_rendicion_modal(r, r[16], workers, modulos) }} {{ edit_rendicion_modal(r, r[20], workers, modulos) }}
{{ confirm_modal( {{ confirm_modal(
id='deleteRendicion' ~ r[0], id='deleteRendicion' ~ r[0],

View File

@@ -0,0 +1,105 @@
{% extends "macros/base.html" %}
{% block title %}Reporte: Horarios - {{ modulo_name }}{% endblock %}
{% block styles %}
<style>
.table-container {
max-height: 75vh;
overflow-y: auto;
overflow-x: auto;
}
.sticky-col {
position: sticky;
left: 0;
z-index: 2;
background-color: var(--bs-body-bg);
border-right: 2px solid var(--bs-border-color) !important;
}
thead th {
position: sticky;
top: 0;
z-index: 1;
background-color: var(--bs-body-bg);
box-shadow: inset 0 -1px 0 var(--bs-border-color);
}
thead th.sticky-col {
z-index: 3;
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
<i class="bi bi-arrow-left"></i> Volver al Menú
</a>
<h2>Control de Horarios</h2>
</div>
<div class="text-end">
<div><strong class="text-primary fs-5">{{ modulo_name }}</strong></div>
<div class="text-muted"><small>Período: {{ mes_nombre }}</small></div>
</div>
</div>
{% if workers_data %}
<div class="card shadow-sm border-0">
<div class="card-body p-0">
<div class="table-responsive table-container custom-scrollbar">
<table class="table table-bordered table-hover table-sm mb-0 text-center text-nowrap align-middle">
<thead>
<tr>
<th rowspan="2" class="sticky-col bg-dark text-white align-middle" style="min-width: 70px;">Día</th>
{% for w_id, data in workers_data.items() %}
<th colspan="3" class="bg-secondary text-white">{{ data.name }}</th>
{% endfor %}
</tr>
<tr>
{% for w_id, data in workers_data.items() %}
<th class="bg-body-tertiary">Ent</th>
<th class="bg-body-tertiary">Sal</th>
<th class="bg-success-subtle text-success">Hrs</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for dia in dias_en_periodo %}
<tr>
<td class="fw-bold sticky-col p-0 align-middle">
<div class="d-flex justify-content-between align-items-center px-2 py-1 h-100">
<span class="text-muted" style="font-weight: 500;">{{ dia.name }}</span>
<span>{{ dia.num }}</span>
</div>
</td>
{% for w_id, data in workers_data.items() %}
{% set turno = data.dias.get(dia.num) %}
<td>{{ turno.in if turno else '-' }}</td>
<td>{{ turno.out if turno else '-' }}</td>
<td class="bg-success-subtle fw-bold text-success">{{ turno.hrs if turno else '0:00' }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
<tfoot class="table-dark">
<tr>
<th class="sticky-col bg-dark text-white">TOTAL</th>
{% for w_id, data in workers_data.items() %}
<td colspan="2" class="text-end pe-2">Horas Totales:</td>
<td class="fs-6 text-success fw-bold">{{ data.total_hrs_str }}</td>
{% endfor %}
</tr>
</tfoot>
</table>
</div>
</div>
</div>
{% else %}
<div class="alert alert-info text-center shadow-sm mt-4">
No hay trabajadores asignados ni registros de horarios para este módulo.
</div>
{% endif %}
{% endblock %}

View File

@@ -239,19 +239,19 @@
</dl> </dl>
<hr> <hr>
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<span class="text-muted">Débito:</span> <span class="text-muted">Débito <small>(x{{ rendicion[16] }})</small>:</span>
<span>${{ "{:,.0f}".format(rendicion[4] or 0).replace(',', '.') }}</span> <span>${{ "{:,.0f}".format(rendicion[4] or 0).replace(',', '.') }}</span>
</div> </div>
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<span class="text-muted">Crédito:</span> <span class="text-muted">Crédito <small>(x{{ rendicion[17] }})</small>:</span>
<span>${{ "{:,.0f}".format(rendicion[5] or 0).replace(',', '.') }}</span> <span>${{ "{:,.0f}".format(rendicion[5] or 0).replace(',', '.') }}</span>
</div> </div>
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<span class="text-muted">Mercado Pago:</span> <span class="text-muted">Mercado Pago <small>(x{{ rendicion[18] }})</small>:</span>
<span>${{ "{:,.0f}".format(rendicion[6] or 0).replace(',', '.') }}</span> <span>${{ "{:,.0f}".format(rendicion[6] or 0).replace(',', '.') }}</span>
</div> </div>
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<span class="text-muted">Efectivo:</span> <span class="text-muted">Efectivo <small>(x{{ rendicion[19] }})</small>:</span>
<span>${{ "{:,.0f}".format(rendicion[7] or 0).replace(',', '.') }}</span> <span>${{ "{:,.0f}".format(rendicion[7] or 0).replace(',', '.') }}</span>
</div> </div>
@@ -398,22 +398,38 @@
</div> </div>
</div> </div>
<div class="row g-1 mt-1 border-top pt-2"> <div class="row g-2 mt-1 border-top pt-2">
<div class="col-6"> <div class="col-6">
<label class="small text-muted mb-0">Débito</label> <label class="small text-muted mb-0">Débito</label>
<input type="text" class="form-control form-control-sm text-end money-input" id="edit_debito_{{ rendicion[0] }}" name="venta_debito" value="{{ '{:,.0f}'.format(rendicion[4] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})"> <input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_debito_{{ rendicion[0] }}" name="venta_debito" value="{{ '{:,.0f}'.format(rendicion[4] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
<div class="input-group input-group-sm">
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_debito" value="{{ rendicion[16] or 0 }}">
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<label class="small text-muted mb-0">Crédito</label> <label class="small text-muted mb-0">Crédito</label>
<input type="text" class="form-control form-control-sm text-end money-input" id="edit_credito_{{ rendicion[0] }}" name="venta_credito" value="{{ '{:,.0f}'.format(rendicion[5] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})"> <input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_credito_{{ rendicion[0] }}" name="venta_credito" value="{{ '{:,.0f}'.format(rendicion[5] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
<div class="input-group input-group-sm">
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_credito" value="{{ rendicion[17] or 0 }}">
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<label class="small text-muted mb-0">Mercado Pago</label> <label class="small text-muted mb-0">Mercado Pago</label>
<input type="text" class="form-control form-control-sm text-end money-input" id="edit_mp_{{ rendicion[0] }}" name="venta_mp" value="{{ '{:,.0f}'.format(rendicion[6] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})"> <input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_mp_{{ rendicion[0] }}" name="venta_mp" value="{{ '{:,.0f}'.format(rendicion[6] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
<div class="input-group input-group-sm">
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_mp" value="{{ rendicion[18] or 0 }}">
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<label class="small text-muted mb-0">Efectivo</label> <label class="small text-muted mb-0">Efectivo</label>
<input type="text" class="form-control form-control-sm text-end money-input" id="edit_efectivo_{{ rendicion[0] }}" name="venta_efectivo" value="{{ '{:,.0f}'.format(rendicion[7] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})"> <input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_efectivo_{{ rendicion[0] }}" name="venta_efectivo" value="{{ '{:,.0f}'.format(rendicion[7] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
<div class="input-group input-group-sm">
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_efectivo" value="{{ rendicion[19] or 0 }}">
</div>
</div> </div>
</div> </div>
@@ -502,7 +518,7 @@
<h5 class="card-title mb-0">Control de Horarios</h5> <h5 class="card-title mb-0">Control de Horarios</h5>
</div> </div>
<p class="text-muted small flex-grow-1">Registro de horas de entrada y salida de los trabajadores y acompañantes.</p> <p class="text-muted small flex-grow-1">Registro de horas de entrada y salida de los trabajadores y acompañantes.</p>
<button class="btn btn-outline-warning w-100 mt-3" onclick="alert('Reporte en construcción')">Generar Reporte</button> <a href="{{ url_for('report_modulo_horarios', modulo_id=modulo_id) }}" class="btn btn-warning w-100 mt-3">Generar Reporte</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -131,32 +131,48 @@
<div class="card-body"> <div class="card-body">
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Venta Débito</label> <label class="form-label">Débito</label>
<div class="input-group"> <div class="input-group mb-2">
<span class="input-group-text">$</span> <span class="input-group-text">$</span>
<input type="text" class="form-control money-input sale-input" name="venta_debito" id="venta_debito" required> <input type="text" class="form-control money-input sale-input" name="venta_debito" id="venta_debito" required>
</div> </div>
<div class="input-group input-group-sm">
<span class="input-group-text bg-secondary text-white border-0">Nº Boletas</span>
<input type="number" class="form-control" name="boletas_debito" min="0" placeholder="0">
</div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Venta Crédito</label> <label class="form-label">Crédito</label>
<div class="input-group"> <div class="input-group mb-2">
<span class="input-group-text">$</span> <span class="input-group-text">$</span>
<input type="text" class="form-control money-input sale-input" name="venta_credito" id="venta_credito" required> <input type="text" class="form-control money-input sale-input" name="venta_credito" id="venta_credito" required>
</div> </div>
</div> <div class="input-group input-group-sm">
<div class="col-md-3"> <span class="input-group-text bg-secondary text-white border-0">Nº Boletas</span>
<label class="form-label">Venta Mercado Pago</label> <input type="number" class="form-control" name="boletas_credito" min="0" placeholder="0">
<div class="input-group">
<span class="input-group-text">$</span>
<input type="text" class="form-control money-input sale-input" placeholder="0" name="venta_mp" id="venta_mp" required>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Venta Efectivo</label> <label class="form-label">Mercado Pago</label>
<div class="input-group"> <div class="input-group mb-2">
<span class="input-group-text">$</span>
<input type="text" class="form-control money-input sale-input" placeholder="0" name="venta_mp" id="venta_mp" required>
</div>
<div class="input-group input-group-sm">
<span class="input-group-text bg-secondary text-white border-0">Nº Boletas</span>
<input type="number" class="form-control" name="boletas_mp" min="0" placeholder="0">
</div>
</div>
<div class="col-md-3">
<label class="form-label">Efectivo</label>
<div class="input-group mb-2">
<span class="input-group-text">$</span> <span class="input-group-text">$</span>
<input type="text" class="form-control money-input sale-input" placeholder="0" name="venta_efectivo" id="venta_efectivo" required> <input type="text" class="form-control money-input sale-input" placeholder="0" name="venta_efectivo" id="venta_efectivo" required>
</div> </div>
<div class="input-group input-group-sm">
<span class="input-group-text bg-secondary text-white border-0">Nº Boletas</span>
<input type="number" class="form-control" name="boletas_efectivo" min="0" placeholder="0">
</div>
</div> </div>
</div> </div>

View File

@@ -64,7 +64,7 @@
<button type="button" class="btn btn-sm btn-info text-white" data-bs-toggle="modal" data-bs-target="#viewRendicion{{ r[0] }}" title="Ver Detalle"> <button type="button" class="btn btn-sm btn-info text-white" data-bs-toggle="modal" data-bs-target="#viewRendicion{{ r[0] }}" title="Ver Detalle">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </button>
{{ rendicion_detail_modal(r, r[16], r[17], r[18]) }} {{ rendicion_detail_modal(r, r[20], r[21], r[22]) }}
</td> </td>
</tr> </tr>
{% else %} {% else %}