Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
e023793
docs(scheduling): point cross references to docs/scheduling
bpstack Oct 8, 2026
2a1ff72
docs(scheduling): replace archived plan references in solver comments
bpstack Oct 8, 2026
0cad6b3
fix(settings): fall back to the first available tab
bpstack Oct 8, 2026
c855777
test(auth): pin current role access for excludeMantenimiento
bpstack Oct 8, 2026
843485a
refactor(auth): replace the maintenance exclusion with an allowlist
bpstack Oct 9, 2026
bb71801
feat(i18n): translate BACKOFFICE error codes
bpstack Oct 9, 2026
5878e37
refactor(auth): type the remaining any
bpstack Oct 9, 2026
40af16a
refactor(backend): type the remaining any in cron, config and scripts
bpstack Oct 9, 2026
5102cc9
test: type the remaining any
bpstack Oct 9, 2026
c569a07
build(lint): make no-explicit-any an error
bpstack Oct 9, 2026
dfe64f9
feat(i18n): translate ACTIVITY error codes
bpstack Oct 9, 2026
2c8e6ae
feat(i18n): translate DEPARTMENTS error codes
bpstack Oct 9, 2026
efd8539
feat(i18n): translate NOTIFICATIONS error codes
bpstack Oct 10, 2026
db40fb8
feat(i18n): translate BLACKLIST error codes
bpstack Oct 10, 2026
c443ad8
feat(i18n): translate LOGBOOK error codes
bpstack Oct 10, 2026
cea17d7
feat(i18n): translate CONCILIATION error codes
bpstack Oct 10, 2026
b52c92f
feat(i18n): translate PARKING error codes
bpstack Oct 10, 2026
c99198f
feat(i18n): translate GROUP error codes
bpstack Oct 10, 2026
fb55531
feat(i18n): translate MESSAGES error codes
bpstack Oct 10, 2026
0f46700
feat(i18n): translate CASHIER error codes
bpstack Oct 10, 2026
4a5e3e2
style(test): format error code message tests
bpstack Oct 10, 2026
0c0cdb5
test(auth): type the role access mocks
bpstack Oct 10, 2026
23d4887
docs(scheduling): point test headers to the sections that hold their …
bpstack Oct 11, 2026
286cb5c
docs(todo): drop the settings tab fallback, now fixed
bpstack Oct 11, 2026
4e82749
refactor(auth): share the front-desk allowlist and word the 403 for a…
bpstack Oct 11, 2026
4c1023a
docs(todo): drop the excludeMantenimiento allowlist, now done
bpstack Oct 11, 2026
fef5942
docs(todo): drop the remaining backend any, now typed
bpstack Oct 11, 2026
259d97c
fix(api): send the code with the blacklist image and demo reset errors
bpstack Oct 11, 2026
7dd71a4
feat(i18n): translate the remaining error codes and plain-fetch errors
bpstack Oct 11, 2026
1809203
docs(todo): narrow the untranslated errors entry to what is left
bpstack Oct 11, 2026
aaa3fca
fix(maintenance): write each change and its history in one transaction
bpstack Oct 11, 2026
d0659c8
fix(parking): refuse a check-out earlier than its check-in
bpstack Oct 11, 2026
abe35ce
docs(todo): drop the parking real dates entry, now settled
bpstack Oct 11, 2026
0cd74f3
fix(notifications): send manual notifications only to active users
bpstack Oct 11, 2026
46f84d3
fix(parking): keep the booking source and offer only the ENUM values
bpstack Oct 11, 2026
25d46f7
docs(todo): drop the lost parking booking source, now fixed
bpstack Oct 11, 2026
aadcc41
fix(groups): keep payment amounts, status and percentages coherent
bpstack Oct 11, 2026
2ca4afd
fix(groups): keep a group's history when it is deleted
bpstack Oct 11, 2026
acdef6d
docs(todo): close the entries fixed on 2026-10-11 and add the Aiven step
bpstack Oct 11, 2026
8b8cf4c
fix(scheduling): keep fixed-day and approved-request cells when gener…
bpstack Oct 11, 2026
1c4899e
fix(scheduling): make a published month read-only
bpstack Oct 11, 2026
9d96e60
fix(scheduling): keep employee dates and order when saving the list
bpstack Oct 11, 2026
89e957a
docs(scheduling): record that the solver stays non-deterministic
bpstack Oct 11, 2026
8496d9f
docs(todo): close the four scheduling entries reviewed on 2026-10-11
bpstack Oct 11, 2026
355b844
fix(cashier): final voucher states, atomic voucher creation, guarded …
bpstack Oct 11, 2026
2c0101f
fix(checklist): keep closed days' comments and photos to admin
bpstack Oct 11, 2026
22a8ec7
docs(todo): close the checklist and cashier entries reviewed on 2026-…
bpstack Oct 11, 2026
bb77dd6
fix(backoffice): check uploads by their bytes and cap stored-file dow…
bpstack Oct 11, 2026
f573421
fix(backoffice): refuse negative amounts, duplicate invoices and impo…
bpstack Oct 11, 2026
200cae2
docs(todo): close the backoffice and conciliation entries reviewed on…
bpstack Oct 11, 2026
2ceb147
docs(todo): widen the Aiven UTC entry to the timezone handling of dates
bpstack Oct 11, 2026
1896adb
fix(scheduling): keep bulk edits inside their month and reset a month…
bpstack Oct 11, 2026
cbec8c8
docs(scheduling): record that LI stays outside the rest rules of the …
bpstack Oct 11, 2026
8093de3
docs(todo): close the low-priority entries reviewed in A5.1
bpstack Oct 11, 2026
e7a5443
fix(groups): refresh the group page after an edit and keep contacts f…
bpstack Oct 11, 2026
6b60a4c
docs(groups): describe how the automatic reminders really repeat
bpstack Oct 11, 2026
9f1344e
docs(scheduling): record that the solver daemon queue stays as it is
bpstack Oct 11, 2026
8f5ad0f
docs(todo): close the low-priority entries reviewed in A5.2
bpstack Oct 11, 2026
c237ce3
fix(maintenance): survive reports and photos created at the same time
bpstack Oct 11, 2026
18d71fe
fix(security): log authorization decisions and keep SQL values out of…
bpstack Oct 11, 2026
39762ab
docs(todo): close the low-priority entries reviewed in A5.3
bpstack Oct 11, 2026
3c18741
perf(home): load the important logbook entries with one request per p…
bpstack Oct 11, 2026
e7436cd
docs(todo): close the low-priority entries reviewed in A5.4
bpstack Oct 12, 2026
bbdbd3d
docs(todo): add the leaked user password to the rotation and keep the…
bpstack Oct 12, 2026
1508194
fix(parking): declare the check-in status and occupied-spot error codes
bpstack Oct 12, 2026
b97086e
docs(todo): measure the backend errors that still arrive without a code
bpstack Oct 12, 2026
ae0308c
docs(db): mark 20261012_unique_invoice_supplier_number as applied on …
bpstack Oct 12, 2026
8f3de7b
docs(db): mark 20261011_keep_group_history_on_delete as applied on Aiven
bpstack Oct 12, 2026
880bc21
docs(todo): close the Aiven migration entry and update the migration …
bpstack Oct 12, 2026
572b7ee
Merge pull request #7 from bpstack/opencode/doc-refs
bpstack Oct 12, 2026
cc45039
Merge pull request #8 from bpstack/opencode/settings-tab
bpstack Oct 12, 2026
47a95b5
Merge pull request #9 from bpstack/opencode/role-access
bpstack Oct 12, 2026
f733894
Merge pull request #10 from bpstack/opencode/any
bpstack Oct 12, 2026
533c00d
Merge remote-tracking branch 'origin/claude/compassionate-planck-gh6a…
bpstack Oct 12, 2026
a6e01a1
Merge pull request #11 from bpstack/opencode/error-codes
bpstack Oct 12, 2026
baedd94
Merge pull request #12 from bpstack/opencode/fix-maintenance-history
bpstack Oct 12, 2026
83983cd
Merge pull request #13 from bpstack/opencode/fix-parking-dates
bpstack Oct 12, 2026
d9efd01
Merge pull request #14 from bpstack/opencode/fix-parking-source
bpstack Oct 12, 2026
53df258
test(notifications): mock the database pool in the manual recipients …
bpstack Oct 12, 2026
cea12ee
Merge pull request #15 from bpstack/opencode/fix-notification-recipients
bpstack Oct 12, 2026
5c65167
Merge pull request #16 from bpstack/opencode/fix-group-payments
bpstack Oct 12, 2026
567922d
Merge pull request #17 from bpstack/opencode/fix-group-deletion
bpstack Oct 12, 2026
8cc0b3a
Merge pull request #18 from bpstack/opencode/fix-group-detail
bpstack Oct 12, 2026
c800dbe
Merge remote-tracking branch 'origin/claude/compassionate-planck-gh6a…
bpstack Oct 12, 2026
9a925bb
Merge pull request #19 from bpstack/opencode/fix-scheduling
bpstack Oct 12, 2026
fab4cf3
Merge pull request #20 from bpstack/opencode/fix-cashier
bpstack Oct 12, 2026
15e5aa7
Merge pull request #21 from bpstack/opencode/fix-checklist
bpstack Oct 12, 2026
bdf1932
Merge pull request #22 from bpstack/opencode/fix-backoffice-files
bpstack Oct 12, 2026
05d830f
Merge pull request #23 from bpstack/opencode/fix-backoffice-invoices
bpstack Oct 12, 2026
fdb264e
Merge pull request #24 from bpstack/opencode/fix-logging
bpstack Oct 12, 2026
dac5d76
Merge pull request #25 from bpstack/opencode/fix-home-logbooks
bpstack Oct 12, 2026
3e945d9
Merge pull request #26 from bpstack/opencode/todo-closeout
bpstack Oct 12, 2026
9c5bdca
docs(session): close the OpenCode review and point to the PR to main
bpstack Oct 12, 2026
856d320
Merge pull request #27 from bpstack/opencode/session
bpstack Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/config/db-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface DBConfig {
user: string
password: string | undefined
database: string
ssl?: any
ssl?: object
}

// ========================================
Expand Down
8 changes: 8 additions & 0 deletions backend/config/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const ERROR_CODES = {
BACKOFFICE_SUPPLIER_NOT_FOUND: 'BACKOFFICE_SUPPLIER_NOT_FOUND',
BACKOFFICE_REJECTION_REASON_REQUIRED: 'BACKOFFICE_REJECTION_REASON_REQUIRED',
BACKOFFICE_PAYMENT_DATE_REQUIRED: 'BACKOFFICE_PAYMENT_DATE_REQUIRED',
BACKOFFICE_INVALID_PAYMENT_DATE: 'BACKOFFICE_INVALID_PAYMENT_DATE',
BACKOFFICE_INVALID_AMOUNT: 'BACKOFFICE_INVALID_AMOUNT',
BACKOFFICE_INVOICE_DUPLICATE: 'BACKOFFICE_INVOICE_DUPLICATE',
BACKOFFICE_NO_FILE_SENT: 'BACKOFFICE_NO_FILE_SENT',
BACKOFFICE_PDF_ONLY: 'BACKOFFICE_PDF_ONLY',
BACKOFFICE_FILE_TOO_LARGE: 'BACKOFFICE_FILE_TOO_LARGE',
Expand Down Expand Up @@ -253,6 +256,8 @@ export const ERROR_CODES = {
// Group Payment
GROUP_PAYMENT_MISSING_FIELDS: 'GROUP_PAYMENT_MISSING_FIELDS',
GROUP_PAYMENT_INVALID_AMOUNT: 'GROUP_PAYMENT_INVALID_AMOUNT',
GROUP_PAYMENT_OVERPAID: 'GROUP_PAYMENT_OVERPAID',
GROUP_PAYMENT_PERCENTAGE_OVER_100: 'GROUP_PAYMENT_PERCENTAGE_OVER_100',
GROUP_PAYMENT_NOT_FOUND: 'GROUP_PAYMENT_NOT_FOUND',
GROUP_PAYMENT_NOT_IN_GROUP: 'GROUP_PAYMENT_NOT_IN_GROUP',
GROUP_PAYMENT_FETCH_ERROR: 'GROUP_PAYMENT_FETCH_ERROR',
Expand Down Expand Up @@ -387,6 +392,7 @@ export const ERROR_CODES = {
NOTIFICATIONS_MISSING_REQUIRED_FIELDS: 'NOTIFICATIONS_MISSING_REQUIRED_FIELDS',
NOTIFICATIONS_INVALID_SCHEDULED_DATE: 'NOTIFICATIONS_INVALID_SCHEDULED_DATE',
NOTIFICATIONS_INVALID_MODULE: 'NOTIFICATIONS_INVALID_MODULE',
NOTIFICATIONS_INVALID_RECIPIENTS: 'NOTIFICATIONS_INVALID_RECIPIENTS',
NOTIFICATIONS_NOT_FOUND: 'NOTIFICATIONS_NOT_FOUND',
NOTIFICATIONS_FETCH_ERROR: 'NOTIFICATIONS_FETCH_ERROR',
NOTIFICATIONS_FETCH_UNREAD_ERROR: 'NOTIFICATIONS_FETCH_UNREAD_ERROR',
Expand Down Expand Up @@ -417,6 +423,8 @@ export const ERROR_CODES = {
PARKING_FETCH_BOOKING_ERROR: 'PARKING_FETCH_BOOKING_ERROR',
PARKING_CREATE_BOOKING_ERROR: 'PARKING_CREATE_BOOKING_ERROR',
PARKING_CHECKIN_ERROR: 'PARKING_CHECKIN_ERROR',
PARKING_CHECKIN_INVALID_STATUS: 'PARKING_CHECKIN_INVALID_STATUS',
PARKING_SPOT_OCCUPIED: 'PARKING_SPOT_OCCUPIED',
PARKING_CHECKOUT_ERROR: 'PARKING_CHECKOUT_ERROR',
PARKING_CANCEL_ERROR: 'PARKING_CANCEL_ERROR',
PARKING_NOSHOW_ERROR: 'PARKING_NOSHOW_ERROR',
Expand Down
28 changes: 17 additions & 11 deletions backend/config/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ import pino from 'pino'
const isDev = process.env.NODE_ENV !== 'production'
const level = process.env.LOG_LEVEL ?? (isDev ? 'debug' : 'info')

const SECRET_KEYS = ['password', 'currentPassword', 'newPassword', 'token', 'refreshToken']

// Redactar campos sensibles si aparecen accidentalmente. pino has no
// recursive wildcard: secrets are hidden at the first three levels. The SQL
// of a failed db.query carries its values interpolated (texts, plates,
// hashes); err.code and err.message stay
export const redact = {
paths: [
...SECRET_KEYS.flatMap((key) => [key, `*.${key}`, `*.*.${key}`]),
'req.headers.authorization',
'req.headers.cookie',
'*.sql',
],
censor: '[REDACTED]',
}

export const logger = pino({
level,
...(isDev
Expand All @@ -31,15 +47,5 @@ export const logger = pino({
},
}
: {}),
// Redactar campos sensibles si aparecen accidentalmente
redact: {
paths: [
'*.password',
'*.token',
'*.refreshToken',
'req.headers.authorization',
'req.headers.cookie',
],
censor: '[REDACTED]',
},
redact,
})
6 changes: 3 additions & 3 deletions backend/controllers/auth/user-controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ export const resetUserPassword = async (req: Request, res: Response): Promise<vo
}

res.status(200).json({ message: 'Contraseña actualizada correctamente' })
} catch (error: any) {
} catch (error) {
logger.error({ err: error }, 'Error resetUserPassword')
if (error.message === 'Usuario no encontrado') {
res.status(404).json({ error: error.message })
if ((error as Error).message === 'Usuario no encontrado') {
res.status(404).json({ error: (error as Error).message })
return
}
res.status(500).json({ error: 'Error al resetear contraseña' })
Expand Down
95 changes: 63 additions & 32 deletions backend/controllers/backoffice/backoffice-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import {
executeBatchPaymentSchema,
revertBatchPaymentSchema,
} from '../../validations/backoffice/batch-payment.js'
import {
hasValidAmounts,
isDuplicateEntry,
isValidPaidDate,
} from '../../validations/backoffice/invoice.js'
import { formatDateMadrid, getTodayMadrid } from '../../config/date-utils.js'
import {
isOwnCloudinaryUrl,
isPublicIdInFolder,
Expand All @@ -26,6 +32,23 @@ import {
import { fetchStoredFile, sendPrivateFile } from '../../services/uploads/private-files.js'
import { presentAsset, presentInvoice, presentInvoiceOrNull } from './backoffice-files.js'

function respondInvalidAmount(res: Response): void {
res.status(400).json({
success: false,
error: ERROR_CODES.BACKOFFICE_INVALID_AMOUNT,
code: ERROR_CODES.BACKOFFICE_INVALID_AMOUNT,
})
}

// uk_invoice_supplier_number: the supplier already has an invoice with that number
function respondDuplicateInvoice(res: Response): void {
res.status(409).json({
success: false,
error: ERROR_CODES.BACKOFFICE_INVOICE_DUPLICATE,
code: ERROR_CODES.BACKOFFICE_INVOICE_DUPLICATE,
})
}

// An invoice action changed no row: the invoice does not exist (404) or its
// status does not allow the action (409)
async function respondActionNotApplied(res: Response, id: number): Promise<void> {
Expand Down Expand Up @@ -570,6 +593,11 @@ export class BackofficeController {
return
}

if (!hasValidAmounts(req.body)) {
respondInvalidAmount(res)
return
}

// The PDF must be one we uploaded: the server later downloads this URL
if (
(req.body.original_pdf_url && !isOwnCloudinaryUrl(req.body.original_pdf_url)) ||
Expand Down Expand Up @@ -611,6 +639,10 @@ export class BackofficeController {
invoice: presentInvoiceOrNull(invoice),
})
} catch (error) {
if (isDuplicateEntry(error)) {
respondDuplicateInvoice(res)
return
}
logger.error({ err: error }, '[BackofficeController.createInvoice] Error')
res.status(500).json({
success: false,
Expand All @@ -637,6 +669,11 @@ export class BackofficeController {

const { id } = req.params

if (!hasValidAmounts(req.body)) {
respondInvalidAmount(res)
return
}

const updated = await BackofficeRepository.updateInvoice(Number(id), req.body, req.user.id)

if (!updated) {
Expand All @@ -653,6 +690,10 @@ export class BackofficeController {
invoice: presentInvoiceOrNull(invoice),
})
} catch (error) {
if (isDuplicateEntry(error)) {
respondDuplicateInvoice(res)
return
}
logger.error({ err: error }, '[BackofficeController.updateInvoice] Error')
res.status(500).json({
success: false,
Expand Down Expand Up @@ -872,6 +913,26 @@ export class BackofficeController {
return
}

const current = await BackofficeRepository.getInvoiceById(Number(id))
if (!current) {
res.status(404).json({
success: false,
error: ERROR_CODES.BACKOFFICE_INVOICE_NOT_FOUND,
code: ERROR_CODES.BACKOFFICE_INVOICE_NOT_FOUND,
})
return
}

// A real day, not before the invoice nor in the future
if (!isValidPaidDate(paid_date, formatDateMadrid(current.invoice_date), getTodayMadrid())) {
res.status(400).json({
success: false,
error: ERROR_CODES.BACKOFFICE_INVALID_PAYMENT_DATE,
code: ERROR_CODES.BACKOFFICE_INVALID_PAYMENT_DATE,
})
return
}

const paid = await BackofficeRepository.markAsPaid(Number(id), paid_date, req.user.id)

if (!paid) {
Expand Down Expand Up @@ -1097,6 +1158,7 @@ export class BackofficeController {
const previousPublicId =
type === 'original' ? pdfInfo?.original_pdf_public_id : pdfInfo?.validated_pdf_public_id

// Type, bytes and size were checked by singleInvoicePdf (backofficeUpload.ts)
if (!req.file) {
res.status(400).json({
success: false,
Expand All @@ -1106,27 +1168,6 @@ export class BackofficeController {
return
}

// Validar tipo de archivo
if (req.file.mimetype !== 'application/pdf') {
res.status(400).json({
success: false,
error: ERROR_CODES.BACKOFFICE_PDF_ONLY,
code: ERROR_CODES.BACKOFFICE_PDF_ONLY,
})
return
}

// Validar tamaño (máx 10MB)
const maxSize = 10 * 1024 * 1024
if (req.file.size > maxSize) {
res.status(400).json({
success: false,
error: ERROR_CODES.BACKOFFICE_FILE_TOO_LARGE,
code: ERROR_CODES.BACKOFFICE_FILE_TOO_LARGE,
})
return
}

logger.debug('[BackofficeController.uploadInvoicePdf] Uploading to Cloudinary...')

// Subir a Cloudinary (usar uploadPdf para archivos PDF)
Expand Down Expand Up @@ -1282,6 +1323,7 @@ export class BackofficeController {
return
}

// Type, bytes and size were checked by singleAssetImage (backofficeUpload.ts)
if (!req.file) {
res.status(400).json({
success: false,
Expand All @@ -1291,17 +1333,6 @@ export class BackofficeController {
return
}

// Validar tipo de archivo
const allowedTypes = ['image/png', 'image/webp']
if (!allowedTypes.includes(req.file.mimetype)) {
res.status(400).json({
success: false,
error: ERROR_CODES.BACKOFFICE_INVALID_IMAGE_TYPE,
code: ERROR_CODES.BACKOFFICE_INVALID_IMAGE_TYPE,
})
return
}

// Subir a Cloudinary
const cloudinaryResult = await CloudinaryService.uploadImage(
req.file.buffer,
Expand Down
8 changes: 6 additions & 2 deletions backend/controllers/blacklist/blacklist-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,9 @@ export class BlacklistController {
static async getImage(req: Request, res: Response): Promise<void> {
const file = String(req.params.file)
if (!isBlacklistImageFile(file)) {
res.status(404).json({ success: false, error: ERROR_CODES.NOT_FOUND })
res
.status(404)
.json({ success: false, error: ERROR_CODES.NOT_FOUND, code: ERROR_CODES.NOT_FOUND })
return
}
const { publicId, format } = parseBlacklistImageFile(file)
Expand All @@ -435,7 +437,9 @@ export class BlacklistController {
sendPrivateFile(res, fetched)
} catch (error) {
logger.warn({ err: error, file }, '[BlacklistController.getImage] Not available')
res.status(404).json({ success: false, error: ERROR_CODES.NOT_FOUND })
res
.status(404)
.json({ success: false, error: ERROR_CODES.NOT_FOUND, code: ERROR_CODES.NOT_FOUND })
}
}

Expand Down
4 changes: 4 additions & 0 deletions backend/controllers/cashier/cashier-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const CASHIER_DOMAIN_ERRORS: Record<string, number> = {
'El vale ya está justificado': 409,
'El vale ya está cancelado': 409,
'Solo se pueden eliminar vales pendientes': 409,
'Solo se pueden modificar vales pendientes': 409,
'Solo se pueden justificar vales pendientes': 409,
'Solo se pueden cancelar vales pendientes': 409,
'El turno ya tiene el máximo de 5 vales': 400,
}

/** Known domain errors keep their message; anything else is a generic 500 (no SQL details). */
Expand Down
17 changes: 3 additions & 14 deletions backend/controllers/cashier/cashier-voucher-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,8 @@ export class CashierVoucherController {
}
const voucherData = { ...parsed.data, created_by: userId }

// Verificar límite de 5 vales por turno
const canAdd = await CashierVoucherRepository.canAddVoucherToShift(parseInt(shiftId))
if (!canAdd) {
res.status(400).json({ error: 'El turno ya tiene el máximo de 5 vales' })
return
}

// Crear vale
const voucher = await CashierVoucherRepository.create(voucherData)

// Asociar con turno
await CashierVoucherRepository.associateWithShift(voucher.id, parseInt(shiftId))
// Crear el vale y asociarlo al turno (límite de 5, en una transacción)
const voucher = await CashierVoucherRepository.createForShift(voucherData, parseInt(shiftId))

// Registrar en historial
await CashierHistoryRepository.create({
Expand All @@ -158,8 +148,7 @@ export class CashierVoucherController {

res.status(201).json(voucher)
} catch (error) {
logger.error({ err: error }, 'Error al crear vale')
res.status(500).json({ error: 'Error al crear vale' })
sendCashierError(res, error, 'Error al crear vale')
}
}

Expand Down
34 changes: 20 additions & 14 deletions backend/controllers/checklist/checklist-comments-controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ import { createCommentSchema } from '../../validations/checklist/checklist-schem
import { logger } from '../../config/logger.js'
import { fetchStoredFile, sendPrivateFile } from '../../services/uploads/private-files.js'

// Answers a refused delete; returns true when it did
function refuseRemove(
res: Response,
result: commentsService.RemoveResult,
what: 'comentario' | 'adjunto'
): boolean {
if (result === 'deleted') return false
if (result === 'not_found') res.status(404).json({ error: `No existe ese ${what}` })
else if (result === 'closed')
res.status(409).json({ error: 'El día está cerrado: solo un administrador puede borrar' })
else res.status(403).json({ error: `No tienes permiso para eliminar este ${what}` })
return true
}

function isAdmin(req: Request): boolean {
return req.user?.role?.toLowerCase() === 'admin'
}
Expand Down Expand Up @@ -45,17 +59,13 @@ export async function addCommentController(req: Request, res: Response): Promise
// DELETE /api/checklists/:id/steps/:stepId/comments/:commentId
export async function deleteCommentController(req: Request, res: Response): Promise<void> {
try {
const { id, commentId } = req.params
const deleted = await commentsService.removeComment(
id,
const { commentId } = req.params
const result = await commentsService.removeComment(
Number(commentId),
req.user!.id,
isAdmin(req)
)
if (!deleted) {
res.status(403).json({ error: 'No tienes permiso para eliminar este comentario' })
return
}
if (refuseRemove(res, result, 'comentario')) return
res.json({ success: true })
} catch (err) {
logger.error({ err }, '[checklist] deleteComment')
Expand Down Expand Up @@ -113,17 +123,13 @@ export async function addAttachmentController(req: Request, res: Response): Prom
// DELETE /api/checklists/:id/steps/:stepId/attachments/:attachmentId
export async function deleteAttachmentController(req: Request, res: Response): Promise<void> {
try {
const { id, attachmentId } = req.params
const deleted = await commentsService.removeAttachment(
id,
const { attachmentId } = req.params
const result = await commentsService.removeAttachment(
Number(attachmentId),
req.user!.id,
isAdmin(req)
)
if (!deleted) {
res.status(403).json({ error: 'No tienes permiso para eliminar este adjunto' })
return
}
if (refuseRemove(res, result, 'adjunto')) return
res.json({ success: true })
} catch (err) {
logger.error({ err }, '[checklist] deleteAttachment')
Expand Down
Loading
Loading