turns-00084.parquet:36170
afc854d04d06217ae94bcd2f
turn 3/9gpt-4.1-mini-2025-04-14SpanishColombia1666 words
degenerate_repetitionAbsentFinal dense release
USER
/administrador/facturas/crear.php a comoda todas la importacion o le que estes llamando esta es asi /administrador/crear.php
ASSISTANT
Perfecto, entonces si el archivo está en `/administrador/crear.php` en lugar de `/administrador/facturas/crear.php`, las rutas a recursos y includes deben ajustarse para que funcionen correctamente desde esa ubicación.
---
Aquí te dejo el archivo completo **/administrador/crear.php**, con rutas (includes, imágenes, scripts y hojas de estilo) actualizadas considerando que el archivo está en `/administrador/`.
---
```php
<?php
// Activar mostrar errores (solo para desarrollo)
error_reporting(E_ALL);
ini_set('display_errors', 1);
try {
// Ajustar rutas para incluir archivos desde /administrador/
require_once __DIR__.'/../modelo/val-admin.php';
require_once __DIR__.'/../modelo/datos-usuarios.php';
$mis_Usuarios = new misUsuarios();
$clientes = $mis_Usuarios->viewUsuariosCliente();
if (!is_array($clientes)) {
throw new Exception("Error al obtener clientes: el método no devolvió un array");
}
} catch (Exception $e) {
die("Error inicializando: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sistema de Facturación - Crear Factura</title>
<!-- CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="../librerias/plugins/select2/css/select2.min.css" rel="stylesheet" />
<link href="../librerias/plugins/select2-bootstrap4-theme/select2-bootstrap4.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet" />
<link href="../librerias/indexDashBoard.css" rel="stylesheet" />
<style>
.invoice-container {
max-width: 800px;
margin: 30px auto;
padding: 20px;
border: 1px solid #ddd;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
background: white;
}
.service-item {
border: 1px solid #eee;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
}
.totals-section {
background: #f9f9f9;
padding: 20px;
border-radius: 5px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="d-flex">
<!-- Sidebar -->
<div id="sidebar-container" class="bg-primary">
<div class="logo text-center py-3">
<img id="logo" src="../imagenes/logo-jaziz_sf.png" alt="Logo Jaziz Biológico" style="max-height: 80px;" />
</div>
<div class="menu">
<a href="reporte.php" class="d-block text-light p-3"><i class="bi bi-file-earmark-text-fill me-2"></i>Reportes</a>
<a href="calendario.php" class="d-block text-light p-3"><i class="bi bi-calendar me-2"></i>Calendario</a>
<a href="usuarios.php" class="d-block text-light p-3"><i class="bi bi-people-fill me-2"></i>Usuarios</a>
<a href="facturas-electronica.php" class="d-block text-light p-3 border-start border-3 border-warning"><i class="bi bi-receipt me-2"></i>Facturas</a>
</div>
</div>
<!-- Main -->
<div class="container-fluid d-block">
<nav class="navbar navbar-expand-lg border-bottom">
<div class="container-fluid">
<button class="navbar-toggler" type="button" onclick="mostrarOcultar(event)">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
<li class="nav-item"><span class="nav-link">Crear Factura</span></li>
</ul>
</div>
</div>
</nav>
<div class="content p-4">
<div class="invoice-container">
<div class="invoice-header row">
<div class="col-md-6">
<img src="../imagenes/logo-jaziz_sf.png" alt="Logo" style="height: 80px;" />
<h3>Jaziz Biológico</h3>
<p>Nit: 123456789-0</p>
<p>Dirección: Calle 123 #45-67</p>
<p>Teléfono: (123) 456-7890</p>
</div>
<div class="col-md-6 text-end">
<h3>FACTURA #<span id="invoice-number"><?= date('YmdHis') ?></span></h3>
<p>Fecha: <span id="invoice-date"><?= date('d/m/Y') ?></span></p>
<div class="mb-3">
<label class="form-label">Cliente</label>
<select id="client-select" class="form-select" style="width:100%;">
<option value="">Seleccione Cliente</option>
<?php foreach ($clientes as $cliente): ?>
<option value="<?= htmlspecialchars($cliente['identificacion'] . ' - ' . $cliente['nombre'], ENT_QUOTES) ?>">
<?= htmlspecialchars($cliente['identificacion'] . ' - ' . $cliente['nombre'] . ' ' . $cliente['apellido']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Empresa</label>
<select id="empresa_usu" class="form-select" style="width:100%" disabled>
<option value="">Seleccione una empresa</option>
</select>
</div>
</div>
</div>
<div class="mt-4">
<h4>Servicios</h4>
<div id="services-container">
<div class="service-item">
<div class="row">
<div class="col-md-6">
<label class="form-label">Servicio</label>
<select class="form-select service-select" name="service[]" required>
<option value="">Seleccione servicio</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Cantidad</label>
<input type="number" class="form-control quantity" name="quantity[]" min="1" value="1" required />
</div>
<div class="col-md-3">
<label class="form-label">Valor Unitario</label>
<input type="number" class="form-control unit-price" name="unit_price[]" min="0" step="0.01" required />
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<label class="form-label">Descripción</label>
<textarea class="form-control description" name="description[]" rows="2"></textarea>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-secondary btn-sm" id="add-service-btn">
<i class="bi bi-plus"></i> Agregar servicio
</button>
</div>
<div class="totals-section">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Forma de pago</label>
<select class="form-select" id="payment-method">
<option value="contado">Contado</option>
<option value="transferencia">Transferencia bancaria</option>
<option value="credito">Crédito</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Observaciones</label>
<textarea class="form-control" id="notes" rows="2"></textarea>
</div>
</div>
<div class="col-md-6">
<div class="text-end">
<p>Subtotal: <span id="subtotal-display">$0.00</span></p>
<p>IVA (19%): <span id="tax-display">$0.00</span></p>
<h4>Total: <span id="total-display">$0.00</span></h4>
<input type="hidden" id="subtotal" value="0" />
<input type="hidden" id="tax" value="0" />
<input type="hidden" id="total" value="0" />
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-light" id="preview-invoice-btn">
<i class="bi bi-printer"></i> Vista previa
</button>
<button type="button" class="btn btn-primary" id="generate-invoice-btn">
<i class="bi bi-file-earmark-text"></i> Generar factura
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js"></script>
<link href="https://code.jquery.com/ui/1.13.1/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="../librerias/plugins/select2/js/select2.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const MAX_SERVICES = 4;
const API_BASE = "../controlador/"; // Ajustar según tu estructura real
function mostrarError(mensaje) {
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
confirmButtonColor: '#3085d6'
});
}
function mostrarExito(mensaje) {
Swal.fire({
icon: 'success',
title: 'Éxito',
text: mensaje,
confirmButtonColor: '#3085d6'
});
}
function mostrarOcultar(event) {
event.preventDefault();
const sidebar = document.getElementById('sidebar-container');
sidebar.style.display = sidebar.style.display === 'none' ? 'block' : 'none';
}
$(function() {
$('#client-select').select2({
theme: 'bootstrap4',
placeholder: "Seleccione un cliente",
allowClear: true,
width: '100%'
}).on('change', function() {
const val = $(this).val();
if(val){
const identificacion = val.split(' - ')[0];
cargarEmpresas(identificacion);
} else {
$('#empresa_usu').empty().append('<option value="">Seleccione una empresa</option>').prop('disabled', true);
}
});
$('#empresa_usu').select2({
theme: 'bootstrap4',
placeholder: "Seleccione una empresa",
width: '100%'
}).prop('disabled', true);
cargarServicios();
calcularTotales();
$('#add-service-btn').on('click', function() {
if ($('.service-item').length >= MAX_SERVICES) {
mostrarError('Máximo ' + MAX_SERVICES + ' servicios permitidos.');
return;
}
agregarServicio();
});
$(document).on('click', '.remove-service', function() {
$(this).closest('.service-item').remove();
if ($('.service-item').length < MAX_SERVICES) {
$('#add-service-btn').prop('disabled', false);
}
calcularTotales();
});
$(document).on('change keyup', '.quantity, .unit-price', calcularTotales);
$(document).on('change', '.service-select', function() {
const price = $(this).find('option:selected').data('precio') || 0;
$(this).closest('.service-item').find('.unit-price').val(price);
calcularTotales();
});
$('#generate-invoice-btn').on('click', function() {
generarFactura();
});
$('#preview-invoice-btn').on('click', function() {
if(!validarFactura()) return;
const htmlFactura = generarHTMLFactura();
Swal.fire({
title: 'Vista previa de factura',
html: htmlFactura,
width: '800px',
showCloseButton: true,
showCancelButton: true,
focusConfirm: false,
confirmButtonText: 'Generar factura (PDF)'
}).then(result => {
if(result.isConfirmed){
generarFactura();
}
});
});
});
function cargarEmpresas(identificacion) {
$.ajax({
url: "../modelo/datos-empresa.php",
method: "POST",
data: { identificacion },
dataType: "json",
success: function(data) {
const $empresa = $("#empresa_usu").empty();
if(data.length > 0) {
$empresa.append('<option value="">Seleccione una empresa</option>');
data.forEach(emp => {
$empresa.append(
$('<option></option>')
.val(emp.numero + " - " + emp.nombre_empresa)
.text(emp.numero + " - " + emp.nombre_empresa)
);
});
$empresa.prop('disabled', false);
} else {
$empresa.append('<option value="">No hay empresas registradas</option>').prop('disabled', true);
}
},
error: function(xhr, status, error) {
mostrarError("Error al cargar empresas: " + error);
}
});
}
function cargarServicios() {
$.ajax({
url: API_BASE + 'cargarServicios.php',
method: 'GET',
dataType: 'json',
success: function(servicios) {
if (!servicios || servicios.error) {
mostrarError(servicios?.error || 'Error al cargar servicios');
return;
}
$('.service-select').each(function() {
const sel = $(this);
const currentVal = sel.val();
sel.empty().append('<option value="">Seleccione servicio</option>');
servicios.forEach(servicio => {
sel.append(
$('<option></option>')
.val(servicio.id)
.text(`${servicio.nombre} ($${servicio.precio.toFixed(2)})`)
.data('precio', servicio.precio)
);
});
sel.val(currentVal || '');
});
},
error: function() {
mostrarError('Error al cargar servicios');
}
});
}
function agregarServicio() {
const newService = `
<div class="service-item">
<div class="row">
<div class="col-md-6">
<label class="form-label">Servicio</label>
<select class="form-select service-select" name="service[]" required>
<option value="">Seleccione servicio</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Cantidad</label>
<input type="number" class="form-control quantity" name="quantity[]" min="1" value="1" required />
</div>
<div class="col-md-3">
<label class="form-label">Valor Unitario</label>
<input type="number" class="form-control unit-price" name="unit_price[]" min="0" step="0.01" required />
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<label class="form-label">Descripción</label>
<textarea class="form-control description" name="description[]" rows="2"></textarea>
</div>
</div>
<button type="button" class="btn btn-danger btn-sm remove-service mt-2">
<i class="bi bi-trash"></i> Eliminar
</button>
</div>
`;
$('#services-container').append(newService);
cargarServicios();
if ($('.service-item').length >= MAX_SERVICES) {
$('#add-service-btn').prop('disabled', true);
}
}
function calcularTotales() {
let subtotal = 0;
$('.service-item').each(function() {
let qty = parseFloat($(this).find('.quantity').val()) || 0;
let price = parseFloat($(this).find('.unit-price').val()) || 0;
subtotal += qty * price;
});
let tax = subtotal * 0.19;
let total = subtotal + tax;
$('#subtotal').val(subtotal.toFixed(2));
$('#tax').val(tax.toFixed(2));
$('#total').val(total.toFixed(2));
const formatMoney = (value) => '$' + value.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
$('#subtotal-display').text(formatMoney(subtotal));
$('#tax-display').text(formatMoney(tax));
$('#total-display').text(formatMoney(total));
}
function validarFactura() {
if(!$('#client-select').val()) {
mostrarError('Por favor seleccione un cliente');
$('#client-select').select2('open');
return false;
}
if(!$('#empresa_usu').val()) {
mostrarError('Por favor seleccione una empresa');
$('#empresa_usu').select2('open');
return false;
}
if(!$('#payment-method').val()) {
mostrarError('Por favor seleccione forma de pago');
$('#payment-method').focus();
return false;
}
let valid = true;
$('.service-item').each(function(i) {
let servicio = $(this).find('.service-select').val();
let cantidad = $(this).find('.quantity').val();
let precio = $(this).find('.unit-price').val();
if(!servicio || !cantidad || !precio) {
mostrarError(`Por favor complete todos los campos del servicio ${i+1}`);
valid = false;
return false;
}
});
return valid;
}
function generarHTMLFactura() {
const clienteTexto = $('#client-select option:selected').text();
const empresaTexto = $('#empresa_usu option:selected').text();
const fecha = $('#invoice-date').text() || '<?=date('d/m/Y')?>';
const numero = $('#invoice-number').text();
let serviciosHTML = '';
$('.service-item').each(function() {
const servicio = $(this).find('.service-select option:selected').text();
const cantidad = $(this).find('.quantity').val();
const precioU = parseFloat($(this).find('.unit-price').val()).toFixed(2);
const descripcion = $(this).find('.description').val();
const totalServicio = (cantidad * parseFloat(precioU)).toFixed(2);
serviciosHTML += `
<tr>
<td>${servicio}</td>
<td class="text-end">${cantidad}</td>
<td class="text-end">$${precioU.replace(/\d(?=(\d{3})+\.)/g, '$&,')}</td>
<td class="text-end">$${totalServicio.replace(/\d(?=(\d{3})+\.)/g, '$&,')}</td>
</tr>`;
if(descripcion) {
serviciosHTML += `<tr><td colspan="4"><strong>Descripción:</strong> ${descripcion}</td></tr>`;
}
});
const subtotal = $('#subtotal-display').text();
const iva = $('#tax-display').text();
const total = $('#total-display').text();
const formaPago = $('#payment-method option:selected').text();
const observaciones = $('#notes').val();
return `
<div>
<div class="d-flex justify-content-between mb-3">
<div>
<img src="../imagenes/logo-jaziz_sf.png" alt="Logo" style="max-height: 80px;" />
<h3>Jaziz Biológico</h3>
<p>Nit: 123456789-0</p>
<p>Dirección: Calle 123 #45-67</p>
<p>Teléfono: (123) 456-7890</p>
</div>
<div class="text-end">
<h3>Factura #${numero}</h3>
<p>Fecha: ${fecha}</p>
<h5>Cliente</h5>
<p>${clienteTexto}</p>
<h5>Empresa</h5>
<p>${empresaTexto}</p>
</div>
</div>
<h4>Detalle de Servicios</h4>
<table class="table table-bordered">
<thead>
<tr>
<th>Servicio</th>
<th class="text-end">Cantidad</th>
<th class="text-end">Valor Unitario</th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>${serviciosHTML}</tbody>
</table>
<div class="d-flex justify-content-between mt-4">
<div>
<p><strong>Forma de pago:</strong> ${formaPago}</p>
${observaciones ? `<p><strong>Observaciones:</strong> ${observaciones}</p>` : ''}
</div>
<div class="text-end">
<p>Subtotal: ${subtotal}</p>
<p>IVA (19%): ${iva}</p>
<h4>Total: ${total}</h4>
</div>
</div>
<hr />
<p style="font-size: 0.8em; color: #666;">
<strong>TÉRMINOS Y CONDICIONES:</strong><br>
1. El pago debe realizarse dentro de los 30 días siguientes a la fecha de facturación.<br>
2. Cualquier reclamo debe presentarse dentro de los 5 días hábiles posteriores a la recepción del servicio.<br>
3. Esta factura es un documento legal que ampara la transacción comercial.
</p>
</div>
`;
}
function generarFactura() {
if (!validarFactura()) return;
Swal.fire({
title: 'Generando factura...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
const facturaData = {
cliente: $('#client-select').val(),
cliente_texto: $('#client-select option:selected').text(),
empresa: $('#empresa_usu').val(),
empresa_texto: $('#empresa_usu option:selected').text(),
fecha: $('#invoice-date').text(),
numero: $('#invoice-number').text(),
servicios: [],
forma_pago: $('#payment-method').val(),
forma_pago_texto: $('#payment-method option:selected').text(),
observaciones: $('#notes').val(),
subtotal: parseFloat($('#subtotal').val()).toFixed(2),
iva: parseFloat($('#tax').val()).toFixed(2),
total: parseFloat($('#total').val()).toFixed(2),
};
$('.service-item').each(function() {
facturaData.servicios.push({
servicio: $(this).find('.service-select option:selected').text(),
servicio_id: $(this).find('.service-select').val(),
cantidad: $(this).find('.quantity').val(),
precio_unitario: $(this).find('.unit-price').val(),
descripcion: $(this).find('.description').val()
});
});
$.ajax({
url: API_BASE + 'generar_pdf_factura.php',
type: 'POST',
data: { factura: JSON.stringify(facturaData) },
xhrFields: { responseType: 'blob' },
success: function(response) {
Swal.close();
const blob = new Blob([response], { type: 'application/pdf' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = `factura_${facturaData.numero}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
mostrarExito('Factura generada y descargada correctamente');
},
error: function(xhr, status, error) {
Swal.close();
mostrarError('Error al generar factura: ' + (error || xhr.statusText));
}
});
}
</script>
</body>
</html>
```
---
### Resumen de ajustes principales
- Includes PHP (`val-admin.php` y `datos-usuarios.php`) cambian a `../modelo/...` desde `/administrador/`.
- Rutas en HTML para imágenes, CSS y JS ajustadas a `../` para apuntar correctamente.
- URL llamada para generación PDF (`generar_pdf_factura.php`) ajustada a `../controlador/`.
- URL para cargar empresa y servicios también ajustadas.
- JavaScript limpio y con el máximo de servicios, validaciones y carga dinámica según tu estructura.
---
Si quieres, puedo ayudarte también a ajustar el archivo `generar_pdf_factura.php` o la configuración AJAX. Solo dime.